Compare commits

...

170 Commits

Author SHA1 Message Date
Daniel Hougaard
739ef8e05a Merge pull request #3701 from Infisical/daniel/cli-auto-open-login
feat(cli): automatically open browser on login
2025-06-02 21:57:18 +04:00
Daniel Hougaard
644659bc10 Merge pull request #3688 from Infisical/daniel/super-admin-view-orgs
feat(instance-management): organizations overview and control
2025-06-02 21:26:15 +04:00
Daniel Hougaard
21e4fa83ef Update Sidebar.tsx 2025-06-02 20:48:01 +04:00
Daniel Hougaard
a6a6c72397 requested changes 2025-06-02 20:43:58 +04:00
Daniel Hougaard
4061feba21 Update login.go 2025-06-02 20:38:07 +04:00
carlosmonastyrski
90a415722c Merge pull request #3697 from Infisical/approvals-redesign
revamp UI for access requests
2025-06-02 13:15:38 -03:00
carlosmonastyrski
f3d5790e2c Fix lint issues 2025-06-02 13:10:50 -03:00
Daniel Hougaard
0d0fddb53a feat(cli): automatically open browser on login 2025-06-02 18:52:55 +04:00
Maidul Islam
9f2e379d4d Merge pull request #3700 from akhilmhdh/fix/gateway-dns-resolve
feat: resolved gateway verify issue and validation check
2025-06-02 10:15:38 -04:00
Scott Wilson
14e898351f Merge pull request #3673 from Infisical/check-for-recipients-on-project-access
Fix(org-admin-project-access): Check for recipients prior to sending project access email
2025-06-02 07:05:53 -07:00
=
16e0aa13c8 feat: fixed type error 2025-06-02 19:18:04 +05:30
Daniel Hougaard
dc130ecd7f Update routes.ts 2025-06-02 17:45:47 +04:00
Daniel Hougaard
b70c6b6260 fix: refactored admin panel layout 2025-06-02 17:45:27 +04:00
=
a701635f08 feat: remove gateway condition 2025-06-02 16:23:10 +05:30
=
9eb98dd276 feat: resolved gateway verify issue and validation check 2025-06-02 15:40:32 +05:30
Maidul Islam
96e9bc3b2f Merge pull request #3667 from akhilmhdh/feat/dynamic-secret-username-template
Feat/dynamic secret username template
2025-06-01 21:59:56 -04:00
Daniel Hougaard
90d213a8ab Merge pull request #3696 from Infisical/daniel/remove-fips-section
docs: remove fips section
2025-06-01 17:46:46 +04:00
Vladyslav Matsiiako
52a26b51af revamp UI for access requests 2025-05-31 17:46:01 -07:00
Daniel Hougaard
3b28e946cf Update hsm-integration.mdx 2025-06-01 00:23:27 +04:00
x032205
4db82e37c1 Merge pull request #3657 from Infisical/ENG-2608
feat(secret-rotation): MySQL Secret Rotation v2
2025-05-30 19:12:57 -04:00
x032205
3a8789af76 Merge pull request #3692 from Infisical/fix/secret-sync-regex
fix(secret-sync): RE2 for regex + input limits
2025-05-30 18:10:30 -04:00
x032205
79ebfc92e9 RE2 for regex + input limits 2025-05-30 18:01:49 -04:00
x032205
ffca4aa054 lint 2025-05-30 16:52:37 -04:00
x032205
52b3f7e8c8 ui fix 2025-05-30 16:36:09 -04:00
Maidul Islam
9de33d8c23 Merge pull request #3689 from Infisical/add-gloo-docs
Gloo mesh docs
2025-05-30 15:55:05 -04:00
carlosmonastyrski
97aed61c54 Merge pull request #3691 from Infisical/fix/accessApprovalIssueOnDeletedPrivileges
feat(access-request): fix issue for deleted custom privileges reopening old closed access requests
2025-05-30 19:19:32 +01:00
Maidul Islam
972dbac7db Merge pull request #3686 from akhilmhdh/feat/template-k8-issuer
Feat/template k8 issuer
2025-05-30 14:16:49 -04:00
Akhil Mohan
5c0e265703 fix: resolved merge conflict 2025-05-30 18:03:04 +00:00
Akhil Mohan
4efbb8dca6 fix: resolved merge conflict 2025-05-30 17:54:57 +00:00
=
09db9e340b feat: review comments addressed 2025-05-30 17:53:22 +00:00
=
5e3d4edec9 feat: added new lottie 2025-05-30 17:53:22 +00:00
=
86348eb434 feat: completed reptile reviews 2025-05-30 17:53:22 +00:00
=
d31d28666a feat: added slugification to old routes 2025-05-30 17:53:22 +00:00
=
3362ec29cd feat: updated doc for k8s issuer 2025-05-30 17:53:21 +00:00
=
3a0e2bf88b feat: completed frontend changes for new pki templates 2025-05-30 17:53:21 +00:00
=
86862b932c feat: completed backend changes for new pki template 2025-05-30 17:53:21 +00:00
carlosmonastyrski
85fefb2a82 feat(access-request): code improvements 2025-05-30 14:53:12 -03:00
carlosmonastyrski
858ec2095e feat(access-request): fix issue for deleted custom privileges reopening old closed access requests 2025-05-30 14:17:52 -03:00
Maidul Islam
a5bb80d2cf Merge pull request #3690 from Infisical/policy-ui-tweak
New policy warning UI
2025-05-30 13:09:28 -04:00
x032205
3156057278 New policy warning UI 2025-05-30 13:08:10 -04:00
x032205
b5da1d7a6c Merge pull request #3662 from Infisical/ENG-2800
feat(policies): Bypass Approval Rework
2025-05-30 12:00:11 -04:00
x032205
8fa8161602 lint 2025-05-30 11:51:15 -04:00
Maidul Islam
b12aca62ff Update docs/documentation/platform/pki/pki-issuer.mdx
Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
2025-05-30 11:44:23 -04:00
Maidul Islam
c9cd843184 Update docs/documentation/platform/pki/integration-guides/gloo-mesh.mdx
Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
2025-05-30 11:44:05 -04:00
Maidul Islam
47442b16f5 Update docs/documentation/platform/pki/integration-guides/gloo-mesh.mdx
Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
2025-05-30 11:43:47 -04:00
x032205
0bdb5d3f19 Merge branch 'main' into ENG-2800 2025-05-30 11:42:24 -04:00
Maidul Islam
cd9ab0024e Gloo mesh docs
Added docs for Gloo Mesh. To be merged after infisical-core PKI updates are made and Issuer is released
2025-05-30 11:41:19 -04:00
x032205
f4bed26781 Rename user to username 2025-05-30 11:39:50 -04:00
Daniel Hougaard
abedb4b53c feat(instance-management): organizations overview and control 2025-05-30 19:28:16 +04:00
Daniel Hougaard
29561d37e9 feat(instance-management): organizations overview and control 2025-05-30 19:28:05 +04:00
x032205
75e9ea9c5d reworded docs 2025-05-30 02:11:44 -04:00
x032205
d0c10838e1 Added docs 2025-05-30 02:02:14 -04:00
Maidul Islam
4dc587576b Merge pull request #3683 from Infisical/offline-lottie
Add support for offline lottie
2025-05-29 22:22:16 -04:00
Maidul Islam
7097731539 downgrade dolottie-web to match dotlottie-react 2025-05-29 22:05:19 -04:00
Maidul Islam
4261281b0f address lint 2025-05-29 21:55:44 -04:00
Maidul Islam
ff7ff06a6a add dotlottie-web as direct import 2025-05-29 21:55:12 -04:00
Maidul Islam
6cbeb4ddf9 Add support for offline lottie
In air gapped, lotties won't load because the WASM player is fetched from CDN. This PR bundles the player so we can fetch it directly from file system
2025-05-29 21:46:45 -04:00
Maidul Islam
5a07c3d1d4 Merge pull request #3682 from Infisical/add-managed-permission
add manage permission for billing
2025-05-29 18:51:35 -04:00
Maidul Islam
d96e880015 updates billing types else where 2025-05-29 18:26:34 -04:00
Maidul Islam
4df6c8c2cc Merge pull request #3681 from Infisical/fix/secretPoliciesDeletedBehavior
feat(access-request): fix deleted policy interfering with the newest and valid policy and fix for default values on the creation form
2025-05-29 17:50:52 -04:00
Maidul Islam
70860e0d26 fix backend lint 2025-05-29 17:48:50 -04:00
Maidul Islam
3f3b81f9bf fix frontend lint 2025-05-29 17:34:05 -04:00
Maidul Islam
5181cac9c8 add manage permission for billing 2025-05-29 17:29:06 -04:00
carlosmonastyrski
5af39b1a40 feat(access-request): fix deleted policy interfering with the newest and valid policy and fix for default values on the creation form 2025-05-29 17:43:47 -03:00
x032205
a9723134f9 Review fixes 2025-05-29 14:43:54 -04:00
Maidul Islam
fe237fbf4a update program 2025-05-29 14:32:14 -04:00
Sheen
98e79207cc Merge pull request #3680 from Infisical/misc/pki-improvements
misc: general improvements
2025-05-30 01:48:36 +08:00
Maidul Islam
26375715e4 Remove log from oidc 2025-05-29 13:12:39 -04:00
Sheen Capadngan
5c435f7645 misc: removed updating configuration for internal CAs 2025-05-30 00:09:47 +08:00
Sheen Capadngan
f7a9e13209 misc: general improvements 2025-05-29 23:36:31 +08:00
Maidul Islam
04908edb5b update 2025-05-29 10:28:35 -04:00
Maidul Islam
e8753a3ce8 Update 2025-05-29 10:16:59 -04:00
Sheen
1947989ca5 Merge pull request #3668 from Infisical/feat/add-kubernetes-dynamic-secret
feat: add kubernetes dynamic secret
2025-05-29 21:45:22 +08:00
Daniel Hougaard
a47e6910b1 Merge pull request #3678 from Infisical/daniel/fix-k8s-https-protocol
fix: allow https on gateway k8s hosts
2025-05-29 17:06:20 +04:00
Daniel Hougaard
78c4a591a9 requested changes 2025-05-29 16:57:22 +04:00
Daniel Hougaard
f6b7717517 fix: allow https on gateway k8s hosts 2025-05-29 16:39:47 +04:00
=
0885620981 feat: removed all tooltip text as it's doc 2025-05-29 17:54:45 +05:30
=
f67511fa19 feat: added max to validation of dynamic secret username template 2025-05-29 17:51:18 +05:30
x032205
476671e6ef Merge branch 'main' into ENG-2800 2025-05-28 23:39:57 -04:00
x032205
b21a5b6425 Merge pull request #3672 from Infisical/ENG-2843
Improved Key Schema docs + tooltip
2025-05-28 23:39:01 -04:00
Maidul Islam
66a5691ffd Merge pull request #3675 from Infisical/revert-3546-feat/point-in-time-revamp
Revert "feat(PIT): Point In Time Revamp"
2025-05-28 20:56:38 -04:00
Maidul Islam
6bdf62d453 Revert "feat(PIT): Point In Time Revamp" 2025-05-28 20:56:04 -04:00
Maidul Islam
652a48b520 Merge pull request #3674 from Infisical/revert-3671-fix/pitCheckpointCreationBatch
Revert "PIT: fix checkpoint creation to do it in batches to avoid insert fails"
2025-05-28 20:55:56 -04:00
Maidul Islam
3148c54e18 Revert "PIT: fix checkpoint creation to do it in batches to avoid insert fails" 2025-05-28 20:55:46 -04:00
Scott Wilson
44367f9149 add boolean filter 2025-05-28 17:06:08 -07:00
Scott Wilson
286dc39ed2 fix: check for recipients to send project access email 2025-05-28 16:45:43 -07:00
x032205
bd4cf64fc6 Merge pull request #3670 from Infisical/ENG-2827
feat(secret-sharing): Require Login for Secrets Shared to Specific Emails
2025-05-28 19:23:26 -04:00
x032205
f4e3d7d576 Review fix 2025-05-28 19:22:46 -04:00
x032205
8298f9974f Improved Key Schema docs + tooltip 2025-05-28 19:18:09 -04:00
carlosmonastyrski
da347e96e1 Merge pull request #3671 from Infisical/fix/pitCheckpointCreationBatch
PIT: fix checkpoint creation to do it in batches to avoid insert fails
2025-05-29 00:17:33 +01:00
carlosmonastyrski
5df96234a0 PIT: fix checkpoint creation to do it in batches to avoid insert fails 2025-05-28 20:10:12 -03:00
Maidul Islam
e78682560c Merge pull request #3546 from Infisical/feat/point-in-time-revamp
feat(PIT): Point In Time Revamp
2025-05-28 18:24:37 -04:00
carlosmonastyrski
1602fac5ca PIT: decrese PIT_CHECKPOINT_WINDOW to 1 for deployment 2025-05-28 19:16:19 -03:00
carlosmonastyrski
0100bf7032 PIT: decrese PIT_CHECKPOINT_WINDOW to 5 for deployment 2025-05-28 19:13:28 -03:00
Maidul Islam
e2c49878c6 Merge pull request #3666 from Infisical/feat/add-token-period-support
feat: add token period support for ua
2025-05-28 17:38:59 -04:00
Maidul Islam
e74117b7fd add link to secret zero section 2025-05-28 17:32:03 -04:00
x032205
335aada941 Doc and review tweaks 2025-05-28 17:28:34 -04:00
x032205
b949fe06c3 Doc update 2025-05-28 17:25:21 -04:00
carlosmonastyrski
28e539c481 PIT: improve wording on the revert button 2025-05-28 17:37:44 -03:00
x032205
5c4c881b60 Docs update 2025-05-28 15:50:46 -04:00
x032205
8ffb92bfb3 Docs revamp 2025-05-28 15:39:44 -04:00
Sheen Capadngan
db9a1726c2 misc: doc improvments 2025-05-29 03:32:19 +08:00
carlosmonastyrski
15986633c7 PIT: omit commit version check on rollbacks and reverts 2025-05-28 16:07:42 -03:00
carlosmonastyrski
c4809bbb54 PIT: remove reminders from commit history 2025-05-28 15:51:51 -03:00
x032205
6305aab0d1 Merge branch 'main' into ENG-2827 2025-05-28 14:44:51 -04:00
x032205
456493ff5a feat(secret-sharing): Require Login for Email Sharing 2025-05-28 14:44:27 -04:00
Maidul Islam
5fe93dc35a Merge pull request #3669 from Infisical/update-oidc-logs
Update OIDC logs
2025-05-28 12:34:36 -04:00
Scott Wilson
5e0e7763a3 Merge pull request #3664 from Infisical/aws-secret-manager-fix
Fix: Update aws secret manager sync to handle constrained iam policies
2025-05-28 09:31:41 -07:00
Maidul Islam
f663d1d4a6 update log 2025-05-28 12:28:33 -04:00
=
90c36eeded feat: reptile requested changes 2025-05-28 19:37:08 +05:30
carlosmonastyrski
48619ed24c Fix lint issue 2025-05-28 08:50:40 -03:00
carlosmonastyrski
21fb8df39b Merge branch 'feat/point-in-time-revamp' of https://github.com/Infisical/infisical into feat/point-in-time-revamp 2025-05-28 08:44:16 -03:00
carlosmonastyrski
f03a7cc249 PIT: add description to folder versioning 2025-05-28 08:43:32 -03:00
=
b5c3f17ec1 feat: resolved reptile changes 2025-05-28 17:04:43 +05:30
=
99d88f7687 doc: updated doc for dynamic secret to have user template input 2025-05-28 16:09:35 +05:30
=
8e3559828f feat: ui changes for input template 2025-05-28 16:09:12 +05:30
=
93d7c812e7 feat: backend changes for dynamic secret 2025-05-28 16:08:26 +05:30
Sheen Capadngan
d08510ebe4 misc: add proper grace period for max ttl and descriptive comment 2025-05-28 16:24:23 +08:00
Sheen
767159bf8f doc: added mention of periodic token to ua section 2025-05-28 08:10:27 +00:00
Sheen Capadngan
98457cdb34 misc: addressed frontend lint 2025-05-28 15:40:09 +08:00
Sheen Capadngan
8ed8f1200d feat: add token period support for ua 2025-05-28 15:35:10 +08:00
Maidul Islam
30252c2bcb minor text updates 2025-05-28 00:06:50 -04:00
Scott Wilson
cc3551c417 fix: update aws secret manager sync to handle constrained iam policies 2025-05-27 18:25:20 -07:00
x032205
accb21f7ed Greptile review fixes 2025-05-27 21:11:19 -04:00
x032205
8f010e740f Docs update 2025-05-27 20:50:19 -04:00
x032205
f3768c90c7 Merge branch 'main' into ENG-2800 2025-05-27 20:47:13 -04:00
x032205
3190ff2eb1 feat(policies): Bypass Approval Rework 2025-05-27 20:46:46 -04:00
carlosmonastyrski
c7ec825830 Improve restore buttons on the UI and reconstruct folder children on revert by default 2025-05-27 19:42:31 -03:00
carlosmonastyrski
5b7f445e33 PIT: fix for folder commit order on cascade deletion 2025-05-27 18:28:00 -03:00
carlosmonastyrski
7fe53ab00e PIT: add batch logic to initializeFolder migration 2025-05-27 11:58:17 -03:00
carlosmonastyrski
8168b5faf8 PIT: fix resourceChangeSchema schema 2025-05-26 23:25:05 -03:00
carlosmonastyrski
8b9e035bf6 PIT: fix folder update issue 2025-05-26 23:08:01 -03:00
carlosmonastyrski
d36d0784ca PIT: Add delete commit for cascade deletion 2025-05-26 21:51:43 -03:00
carlosmonastyrski
f3a84f6001 Merge branch 'main' into feat/point-in-time-revamp 2025-05-26 17:28:38 -03:00
carlosmonastyrski
13672481a8 Merge branch 'main' into feat/point-in-time-revamp 2025-05-26 17:14:30 -03:00
x032205
4f26b43789 License revert 2025-05-26 14:59:01 -04:00
x032205
4817eb2fc6 Docs 2025-05-26 14:58:39 -04:00
carlosmonastyrski
c623c615a1 Fix lint issue 2025-05-26 14:52:04 -03:00
carlosmonastyrski
034a8112b7 Merge branch 'main' into feat/point-in-time-revamp 2025-05-26 14:42:55 -03:00
carlosmonastyrski
5fc6fd71ce Fix tag and metadata insert/update logic on revert/rollback and fix tree checkpoint logic to exclude reserved folders 2025-05-26 14:31:05 -03:00
x032205
f45c917922 Merge 2025-05-26 12:56:15 -04:00
x032205
debef510e4 Merge 2025-05-26 12:54:36 -04:00
carlosmonastyrski
e5bc609a2a PIT: add last commit indicator and remove unnecessary empty folder commit 2025-05-25 12:07:00 -03:00
carlosmonastyrski
b812761bdd PIT: hide restore button for last commit 2025-05-25 11:52:28 -03:00
carlosmonastyrski
14362dbe6a PIT: general improvements and fixes 2025-05-25 11:00:06 -03:00
carlosmonastyrski
b7b90aea33 PIT: general improvements and fixes 2025-05-25 00:12:31 -03:00
x032205
14cc21787d checkpoint 2025-05-24 03:50:24 -04:00
x032205
f551806737 checkpoint 2025-05-23 17:04:16 -04:00
carlosmonastyrski
28a3bf0b94 Improvement on createCommit function to add changes in batches 2025-05-23 10:59:05 -03:00
carlosmonastyrski
5712c24370 Fix migration to initialize pit projects 2025-05-23 10:45:39 -03:00
carlosmonastyrski
4a391c7ac2 PIT: add commits to snapshots and improve old role hidding 2025-05-23 01:46:13 -03:00
carlosmonastyrski
2b21c9d348 Fix for secret-sync import secrets creating a new version for secrets that did not change 2025-05-22 13:02:38 -03:00
carlosmonastyrski
2b948a18f3 Type fixes and PIT history pagination 2025-05-21 23:43:41 -03:00
carlosmonastyrski
f06004370d PIT: address PR suggestions 2025-05-21 19:42:09 -03:00
carlosmonastyrski
2493bbbc97 PIT: fix blocker for deep rollbacks 2025-05-21 09:08:12 -03:00
carlosmonastyrski
44aa743d56 Type fixes 2025-05-20 11:09:25 -03:00
carlosmonastyrski
fefb71dd86 Merge branch 'main' into feat/point-in-time-revamp 2025-05-20 10:52:20 -03:00
carlosmonastyrski
1748052cb0 Merge branch 'main' into feat/point-in-time-revamp 2025-05-20 10:37:41 -03:00
carlosmonastyrski
c01a98ccf1 Merge pull request #3555 from Infisical/feat/point-in-time-revamp-2710
Feat/point in time revamp 2710
2025-05-20 09:46:08 -03:00
carlosmonastyrski
9ea9f90928 PIT: add envID to rollback endpoint 2025-05-20 09:34:43 -03:00
carlosmonastyrski
6319f53802 PIT: UI views 2025-05-20 08:22:14 -03:00
carlosmonastyrski
8bfd3913da PIT: add backend logic for deep PIT and rollback 2025-05-14 10:26:41 -03:00
carlosmonastyrski
9e1d38a27b Add PIT rollback 2025-05-09 16:03:50 -03:00
carlosmonastyrski
78d5bc823d PIT: Add folder reconstruction functions 2025-05-09 09:20:17 -03:00
carlosmonastyrski
e8d424bbb0 PIT: Add initialization and checkpoint logic 2025-05-08 09:41:01 -03:00
carlosmonastyrski
f0c52cc8da Add comments to provide context on this change 2025-05-07 08:43:56 -03:00
carlosmonastyrski
e58dbe853e Minor improvements on commits code quality 2025-05-07 08:38:19 -03:00
carlosmonastyrski
f493a617b1 Add new commit logic on every folder/secret operation 2025-05-06 18:57:25 -03:00
carlosmonastyrski
32a3e1d200 commit 2025-05-06 08:11:50 -03:00
carlosmonastyrski
c6e56f0380 Stop removing secret/folder versions on projects with version >= 3 2025-05-05 16:43:58 -03:00
363 changed files with 12829 additions and 1942 deletions

View File

@@ -83,6 +83,7 @@ import { TOrgAdminServiceFactory } from "@app/services/org-admin/org-admin-servi
import { TPkiAlertServiceFactory } from "@app/services/pki-alert/pki-alert-service"; import { TPkiAlertServiceFactory } from "@app/services/pki-alert/pki-alert-service";
import { TPkiCollectionServiceFactory } from "@app/services/pki-collection/pki-collection-service"; import { TPkiCollectionServiceFactory } from "@app/services/pki-collection/pki-collection-service";
import { TPkiSubscriberServiceFactory } from "@app/services/pki-subscriber/pki-subscriber-service"; import { TPkiSubscriberServiceFactory } from "@app/services/pki-subscriber/pki-subscriber-service";
import { TPkiTemplatesServiceFactory } from "@app/services/pki-templates/pki-templates-service";
import { TProjectServiceFactory } from "@app/services/project/project-service"; import { TProjectServiceFactory } from "@app/services/project/project-service";
import { TProjectBotServiceFactory } from "@app/services/project-bot/project-bot-service"; import { TProjectBotServiceFactory } from "@app/services/project-bot/project-bot-service";
import { TProjectEnvServiceFactory } from "@app/services/project-env/project-env-service"; import { TProjectEnvServiceFactory } from "@app/services/project-env/project-env-service";
@@ -271,6 +272,7 @@ declare module "fastify" {
assumePrivileges: TAssumePrivilegeServiceFactory; assumePrivileges: TAssumePrivilegeServiceFactory;
githubOrgSync: TGithubOrgSyncServiceFactory; githubOrgSync: TGithubOrgSyncServiceFactory;
internalCertificateAuthority: TInternalCertificateAuthorityServiceFactory; internalCertificateAuthority: TInternalCertificateAuthorityServiceFactory;
pkiTemplate: TPkiTemplatesServiceFactory;
}; };
// this is exclusive use for middlewares in which we need to inject data // this is exclusive use for middlewares in which we need to inject data
// everywhere else access using service layer // everywhere else access using service layer

View File

@@ -6,6 +6,9 @@ import {
TAccessApprovalPoliciesApprovers, TAccessApprovalPoliciesApprovers,
TAccessApprovalPoliciesApproversInsert, TAccessApprovalPoliciesApproversInsert,
TAccessApprovalPoliciesApproversUpdate, TAccessApprovalPoliciesApproversUpdate,
TAccessApprovalPoliciesBypassers,
TAccessApprovalPoliciesBypassersInsert,
TAccessApprovalPoliciesBypassersUpdate,
TAccessApprovalPoliciesInsert, TAccessApprovalPoliciesInsert,
TAccessApprovalPoliciesUpdate, TAccessApprovalPoliciesUpdate,
TAccessApprovalRequests, TAccessApprovalRequests,
@@ -276,6 +279,9 @@ import {
TSecretApprovalPoliciesApprovers, TSecretApprovalPoliciesApprovers,
TSecretApprovalPoliciesApproversInsert, TSecretApprovalPoliciesApproversInsert,
TSecretApprovalPoliciesApproversUpdate, TSecretApprovalPoliciesApproversUpdate,
TSecretApprovalPoliciesBypassers,
TSecretApprovalPoliciesBypassersInsert,
TSecretApprovalPoliciesBypassersUpdate,
TSecretApprovalPoliciesInsert, TSecretApprovalPoliciesInsert,
TSecretApprovalPoliciesUpdate, TSecretApprovalPoliciesUpdate,
TSecretApprovalRequests, TSecretApprovalRequests,
@@ -820,6 +826,12 @@ declare module "knex/types/tables" {
TAccessApprovalPoliciesApproversUpdate TAccessApprovalPoliciesApproversUpdate
>; >;
[TableName.AccessApprovalPolicyBypasser]: KnexOriginal.CompositeTableType<
TAccessApprovalPoliciesBypassers,
TAccessApprovalPoliciesBypassersInsert,
TAccessApprovalPoliciesBypassersUpdate
>;
[TableName.AccessApprovalRequest]: KnexOriginal.CompositeTableType< [TableName.AccessApprovalRequest]: KnexOriginal.CompositeTableType<
TAccessApprovalRequests, TAccessApprovalRequests,
TAccessApprovalRequestsInsert, TAccessApprovalRequestsInsert,
@@ -843,6 +855,11 @@ declare module "knex/types/tables" {
TSecretApprovalPoliciesApproversInsert, TSecretApprovalPoliciesApproversInsert,
TSecretApprovalPoliciesApproversUpdate TSecretApprovalPoliciesApproversUpdate
>; >;
[TableName.SecretApprovalPolicyBypasser]: KnexOriginal.CompositeTableType<
TSecretApprovalPoliciesBypassers,
TSecretApprovalPoliciesBypassersInsert,
TSecretApprovalPoliciesBypassersUpdate
>;
[TableName.SecretApprovalRequest]: KnexOriginal.CompositeTableType< [TableName.SecretApprovalRequest]: KnexOriginal.CompositeTableType<
TSecretApprovalRequests, TSecretApprovalRequests,
TSecretApprovalRequestsInsert, TSecretApprovalRequestsInsert,

View File

@@ -0,0 +1,48 @@
import { Knex } from "knex";
import { TableName } from "../schemas";
import { createOnUpdateTrigger, dropOnUpdateTrigger } from "../utils";
export async function up(knex: Knex): Promise<void> {
if (!(await knex.schema.hasTable(TableName.AccessApprovalPolicyBypasser))) {
await knex.schema.createTable(TableName.AccessApprovalPolicyBypasser, (t) => {
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
t.uuid("bypasserGroupId").nullable();
t.foreign("bypasserGroupId").references("id").inTable(TableName.Groups).onDelete("CASCADE");
t.uuid("bypasserUserId").nullable();
t.foreign("bypasserUserId").references("id").inTable(TableName.Users).onDelete("CASCADE");
t.uuid("policyId").notNullable();
t.foreign("policyId").references("id").inTable(TableName.AccessApprovalPolicy).onDelete("CASCADE");
t.timestamps(true, true, true);
});
await createOnUpdateTrigger(knex, TableName.AccessApprovalPolicyBypasser);
}
if (!(await knex.schema.hasTable(TableName.SecretApprovalPolicyBypasser))) {
await knex.schema.createTable(TableName.SecretApprovalPolicyBypasser, (t) => {
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
t.uuid("bypasserGroupId").nullable();
t.foreign("bypasserGroupId").references("id").inTable(TableName.Groups).onDelete("CASCADE");
t.uuid("bypasserUserId").nullable();
t.foreign("bypasserUserId").references("id").inTable(TableName.Users).onDelete("CASCADE");
t.uuid("policyId").notNullable();
t.foreign("policyId").references("id").inTable(TableName.SecretApprovalPolicy).onDelete("CASCADE");
t.timestamps(true, true, true);
});
await createOnUpdateTrigger(knex, TableName.SecretApprovalPolicyBypasser);
}
}
export async function down(knex: Knex): Promise<void> {
await knex.schema.dropTableIfExists(TableName.SecretApprovalPolicyBypasser);
await knex.schema.dropTableIfExists(TableName.AccessApprovalPolicyBypasser);
await dropOnUpdateTrigger(knex, TableName.SecretApprovalPolicyBypasser);
await dropOnUpdateTrigger(knex, TableName.AccessApprovalPolicyBypasser);
}

View File

@@ -0,0 +1,21 @@
import { Knex } from "knex";
import { TableName } from "../schemas";
export async function up(knex: Knex): Promise<void> {
const hasColumn = await knex.schema.hasColumn(TableName.DynamicSecret, "usernameTemplate");
if (!hasColumn) {
await knex.schema.alterTable(TableName.DynamicSecret, (t) => {
t.string("usernameTemplate").nullable();
});
}
}
export async function down(knex: Knex): Promise<void> {
const hasColumn = await knex.schema.hasColumn(TableName.DynamicSecret, "usernameTemplate");
if (hasColumn) {
await knex.schema.alterTable(TableName.DynamicSecret, (t) => {
t.dropColumn("usernameTemplate");
});
}
}

View File

@@ -0,0 +1,139 @@
import { Knex } from "knex";
import { TableName } from "../schemas";
export async function up(knex: Knex): Promise<void> {
if (!(await knex.schema.hasColumn(TableName.IdentityAccessToken, "accessTokenPeriod"))) {
await knex.schema.alterTable(TableName.IdentityAccessToken, (t) => {
t.bigInteger("accessTokenPeriod").defaultTo(0).notNullable();
});
}
if (!(await knex.schema.hasColumn(TableName.IdentityUniversalAuth, "accessTokenPeriod"))) {
await knex.schema.alterTable(TableName.IdentityUniversalAuth, (t) => {
t.bigInteger("accessTokenPeriod").defaultTo(0).notNullable();
});
}
if (!(await knex.schema.hasColumn(TableName.IdentityAwsAuth, "accessTokenPeriod"))) {
await knex.schema.alterTable(TableName.IdentityAwsAuth, (t) => {
t.bigInteger("accessTokenPeriod").defaultTo(0).notNullable();
});
}
if (!(await knex.schema.hasColumn(TableName.IdentityOidcAuth, "accessTokenPeriod"))) {
await knex.schema.alterTable(TableName.IdentityOidcAuth, (t) => {
t.bigInteger("accessTokenPeriod").defaultTo(0).notNullable();
});
}
if (!(await knex.schema.hasColumn(TableName.IdentityAzureAuth, "accessTokenPeriod"))) {
await knex.schema.alterTable(TableName.IdentityAzureAuth, (t) => {
t.bigInteger("accessTokenPeriod").defaultTo(0).notNullable();
});
}
if (!(await knex.schema.hasColumn(TableName.IdentityGcpAuth, "accessTokenPeriod"))) {
await knex.schema.alterTable(TableName.IdentityGcpAuth, (t) => {
t.bigInteger("accessTokenPeriod").defaultTo(0).notNullable();
});
}
if (!(await knex.schema.hasColumn(TableName.IdentityJwtAuth, "accessTokenPeriod"))) {
await knex.schema.alterTable(TableName.IdentityJwtAuth, (t) => {
t.bigInteger("accessTokenPeriod").defaultTo(0).notNullable();
});
}
if (!(await knex.schema.hasColumn(TableName.IdentityKubernetesAuth, "accessTokenPeriod"))) {
await knex.schema.alterTable(TableName.IdentityKubernetesAuth, (t) => {
t.bigInteger("accessTokenPeriod").defaultTo(0).notNullable();
});
}
if (!(await knex.schema.hasColumn(TableName.IdentityLdapAuth, "accessTokenPeriod"))) {
await knex.schema.alterTable(TableName.IdentityLdapAuth, (t) => {
t.bigInteger("accessTokenPeriod").defaultTo(0).notNullable();
});
}
if (!(await knex.schema.hasColumn(TableName.IdentityOciAuth, "accessTokenPeriod"))) {
await knex.schema.alterTable(TableName.IdentityOciAuth, (t) => {
t.bigInteger("accessTokenPeriod").defaultTo(0).notNullable();
});
}
if (!(await knex.schema.hasColumn(TableName.IdentityTokenAuth, "accessTokenPeriod"))) {
await knex.schema.alterTable(TableName.IdentityTokenAuth, (t) => {
t.bigInteger("accessTokenPeriod").defaultTo(0).notNullable();
});
}
}
export async function down(knex: Knex): Promise<void> {
if (await knex.schema.hasColumn(TableName.IdentityAccessToken, "accessTokenPeriod")) {
await knex.schema.alterTable(TableName.IdentityAccessToken, (t) => {
t.dropColumn("accessTokenPeriod");
});
}
if (await knex.schema.hasColumn(TableName.IdentityUniversalAuth, "accessTokenPeriod")) {
await knex.schema.alterTable(TableName.IdentityUniversalAuth, (t) => {
t.dropColumn("accessTokenPeriod");
});
}
if (await knex.schema.hasColumn(TableName.IdentityAwsAuth, "accessTokenPeriod")) {
await knex.schema.alterTable(TableName.IdentityAwsAuth, (t) => {
t.dropColumn("accessTokenPeriod");
});
}
if (await knex.schema.hasColumn(TableName.IdentityOidcAuth, "accessTokenPeriod")) {
await knex.schema.alterTable(TableName.IdentityOidcAuth, (t) => {
t.dropColumn("accessTokenPeriod");
});
}
if (await knex.schema.hasColumn(TableName.IdentityAzureAuth, "accessTokenPeriod")) {
await knex.schema.alterTable(TableName.IdentityAzureAuth, (t) => {
t.dropColumn("accessTokenPeriod");
});
}
if (await knex.schema.hasColumn(TableName.IdentityGcpAuth, "accessTokenPeriod")) {
await knex.schema.alterTable(TableName.IdentityGcpAuth, (t) => {
t.dropColumn("accessTokenPeriod");
});
}
if (await knex.schema.hasColumn(TableName.IdentityJwtAuth, "accessTokenPeriod")) {
await knex.schema.alterTable(TableName.IdentityJwtAuth, (t) => {
t.dropColumn("accessTokenPeriod");
});
}
if (await knex.schema.hasColumn(TableName.IdentityKubernetesAuth, "accessTokenPeriod")) {
await knex.schema.alterTable(TableName.IdentityKubernetesAuth, (t) => {
t.dropColumn("accessTokenPeriod");
});
}
if (await knex.schema.hasColumn(TableName.IdentityLdapAuth, "accessTokenPeriod")) {
await knex.schema.alterTable(TableName.IdentityLdapAuth, (t) => {
t.dropColumn("accessTokenPeriod");
});
}
if (await knex.schema.hasColumn(TableName.IdentityOciAuth, "accessTokenPeriod")) {
await knex.schema.alterTable(TableName.IdentityOciAuth, (t) => {
t.dropColumn("accessTokenPeriod");
});
}
if (await knex.schema.hasColumn(TableName.IdentityTokenAuth, "accessTokenPeriod")) {
await knex.schema.alterTable(TableName.IdentityTokenAuth, (t) => {
t.dropColumn("accessTokenPeriod");
});
}
}

View File

@@ -0,0 +1,24 @@
import slugify from "@sindresorhus/slugify";
import { Knex } from "knex";
import { alphaNumericNanoId } from "@app/lib/nanoid";
import { TableName } from "../schemas";
export async function up(knex: Knex): Promise<void> {
const hasNameCol = await knex.schema.hasColumn(TableName.CertificateTemplate, "name");
if (hasNameCol) {
const templates = await knex(TableName.CertificateTemplate).select("id", "name");
await Promise.all(
templates.map((el) => {
const slugifiedName = el.name
? slugify(`${el.name.slice(0, 16)}-${alphaNumericNanoId(8)}`)
: slugify(alphaNumericNanoId(12));
return knex(TableName.CertificateTemplate).where({ id: el.id }).update({ name: slugifiedName });
})
);
}
}
export async function down(): Promise<void> {}

View File

@@ -0,0 +1,27 @@
import { Knex } from "knex";
import { TableName } from "../schemas";
export async function up(knex: Knex): Promise<void> {
if (await knex.schema.hasTable(TableName.SecretSharing)) {
const hasEncryptedSalt = await knex.schema.hasColumn(TableName.SecretSharing, "encryptedSalt");
if (hasEncryptedSalt) {
await knex.schema.alterTable(TableName.SecretSharing, (t) => {
t.dropColumn("encryptedSalt");
});
}
}
}
export async function down(knex: Knex): Promise<void> {
if (await knex.schema.hasTable(TableName.SecretSharing)) {
const hasEncryptedSalt = await knex.schema.hasColumn(TableName.SecretSharing, "encryptedSalt");
if (!hasEncryptedSalt) {
await knex.schema.alterTable(TableName.SecretSharing, (t) => {
t.binary("encryptedSalt").nullable();
});
}
}
}

View File

@@ -0,0 +1,63 @@
import { Knex } from "knex";
import { ApprovalStatus } from "@app/ee/services/secret-approval-request/secret-approval-request-types";
import { TableName } from "../schemas";
export async function up(knex: Knex): Promise<void> {
const hasPrivilegeDeletedAtColumn = await knex.schema.hasColumn(
TableName.AccessApprovalRequest,
"privilegeDeletedAt"
);
const hasStatusColumn = await knex.schema.hasColumn(TableName.AccessApprovalRequest, "status");
if (!hasPrivilegeDeletedAtColumn) {
await knex.schema.alterTable(TableName.AccessApprovalRequest, (t) => {
t.timestamp("privilegeDeletedAt").nullable();
});
}
if (!hasStatusColumn) {
await knex.schema.alterTable(TableName.AccessApprovalRequest, (t) => {
t.string("status").defaultTo(ApprovalStatus.PENDING).notNullable();
});
// Update existing rows based on business logic
// If privilegeId is not null, set status to "approved"
await knex(TableName.AccessApprovalRequest).whereNotNull("privilegeId").update({ status: ApprovalStatus.APPROVED });
// If privilegeId is null and there's a rejected reviewer, set to "rejected"
const rejectedRequestIds = await knex(TableName.AccessApprovalRequestReviewer)
.select("requestId")
.where("status", "rejected")
.distinct()
.pluck("requestId");
if (rejectedRequestIds.length > 0) {
await knex(TableName.AccessApprovalRequest)
.whereNull("privilegeId")
.whereIn("id", rejectedRequestIds)
.update({ status: ApprovalStatus.REJECTED });
}
}
}
export async function down(knex: Knex): Promise<void> {
const hasPrivilegeDeletedAtColumn = await knex.schema.hasColumn(
TableName.AccessApprovalRequest,
"privilegeDeletedAt"
);
const hasStatusColumn = await knex.schema.hasColumn(TableName.AccessApprovalRequest, "status");
if (hasPrivilegeDeletedAtColumn) {
await knex.schema.alterTable(TableName.AccessApprovalRequest, (t) => {
t.dropColumn("privilegeDeletedAt");
});
}
if (hasStatusColumn) {
await knex.schema.alterTable(TableName.AccessApprovalRequest, (t) => {
t.dropColumn("status");
});
}
}

View File

@@ -0,0 +1,26 @@
// Code generated by automation script, DO NOT EDIT.
// Automated by pulling database and generating zod schema
// To update. Just run npm run generate:schema
// Written by akhilmhdh.
import { z } from "zod";
import { TImmutableDBKeys } from "./models";
export const AccessApprovalPoliciesBypassersSchema = z.object({
id: z.string().uuid(),
bypasserGroupId: z.string().uuid().nullable().optional(),
bypasserUserId: z.string().uuid().nullable().optional(),
policyId: z.string().uuid(),
createdAt: z.date(),
updatedAt: z.date()
});
export type TAccessApprovalPoliciesBypassers = z.infer<typeof AccessApprovalPoliciesBypassersSchema>;
export type TAccessApprovalPoliciesBypassersInsert = Omit<
z.input<typeof AccessApprovalPoliciesBypassersSchema>,
TImmutableDBKeys
>;
export type TAccessApprovalPoliciesBypassersUpdate = Partial<
Omit<z.input<typeof AccessApprovalPoliciesBypassersSchema>, TImmutableDBKeys>
>;

View File

@@ -18,7 +18,9 @@ export const AccessApprovalRequestsSchema = z.object({
createdAt: z.date(), createdAt: z.date(),
updatedAt: z.date(), updatedAt: z.date(),
requestedByUserId: z.string().uuid(), requestedByUserId: z.string().uuid(),
note: z.string().nullable().optional() note: z.string().nullable().optional(),
privilegeDeletedAt: z.date().nullable().optional(),
status: z.string().default("pending")
}); });
export type TAccessApprovalRequests = z.infer<typeof AccessApprovalRequestsSchema>; export type TAccessApprovalRequests = z.infer<typeof AccessApprovalRequestsSchema>;

View File

@@ -28,7 +28,8 @@ export const DynamicSecretsSchema = z.object({
updatedAt: z.date(), updatedAt: z.date(),
encryptedInput: zodBuffer, encryptedInput: zodBuffer,
projectGatewayId: z.string().uuid().nullable().optional(), projectGatewayId: z.string().uuid().nullable().optional(),
gatewayId: z.string().uuid().nullable().optional() gatewayId: z.string().uuid().nullable().optional(),
usernameTemplate: z.string().nullable().optional()
}); });
export type TDynamicSecrets = z.infer<typeof DynamicSecretsSchema>; export type TDynamicSecrets = z.infer<typeof DynamicSecretsSchema>;

View File

@@ -21,7 +21,8 @@ export const IdentityAccessTokensSchema = z.object({
createdAt: z.date(), createdAt: z.date(),
updatedAt: z.date(), updatedAt: z.date(),
name: z.string().nullable().optional(), name: z.string().nullable().optional(),
authMethod: z.string() authMethod: z.string(),
accessTokenPeriod: z.coerce.number().default(0)
}); });
export type TIdentityAccessTokens = z.infer<typeof IdentityAccessTokensSchema>; export type TIdentityAccessTokens = z.infer<typeof IdentityAccessTokensSchema>;

View File

@@ -19,7 +19,8 @@ export const IdentityAwsAuthsSchema = z.object({
type: z.string(), type: z.string(),
stsEndpoint: z.string(), stsEndpoint: z.string(),
allowedPrincipalArns: z.string(), allowedPrincipalArns: z.string(),
allowedAccountIds: z.string() allowedAccountIds: z.string(),
accessTokenPeriod: z.coerce.number().default(0)
}); });
export type TIdentityAwsAuths = z.infer<typeof IdentityAwsAuthsSchema>; export type TIdentityAwsAuths = z.infer<typeof IdentityAwsAuthsSchema>;

View File

@@ -18,7 +18,8 @@ export const IdentityAzureAuthsSchema = z.object({
identityId: z.string().uuid(), identityId: z.string().uuid(),
tenantId: z.string(), tenantId: z.string(),
resource: z.string(), resource: z.string(),
allowedServicePrincipalIds: z.string() allowedServicePrincipalIds: z.string(),
accessTokenPeriod: z.coerce.number().default(0)
}); });
export type TIdentityAzureAuths = z.infer<typeof IdentityAzureAuthsSchema>; export type TIdentityAzureAuths = z.infer<typeof IdentityAzureAuthsSchema>;

View File

@@ -19,7 +19,8 @@ export const IdentityGcpAuthsSchema = z.object({
type: z.string(), type: z.string(),
allowedServiceAccounts: z.string().nullable().optional(), allowedServiceAccounts: z.string().nullable().optional(),
allowedProjects: z.string().nullable().optional(), allowedProjects: z.string().nullable().optional(),
allowedZones: z.string().nullable().optional() allowedZones: z.string().nullable().optional(),
accessTokenPeriod: z.coerce.number().default(0)
}); });
export type TIdentityGcpAuths = z.infer<typeof IdentityGcpAuthsSchema>; export type TIdentityGcpAuths = z.infer<typeof IdentityGcpAuthsSchema>;

View File

@@ -25,7 +25,8 @@ export const IdentityJwtAuthsSchema = z.object({
boundClaims: z.unknown(), boundClaims: z.unknown(),
boundSubject: z.string(), boundSubject: z.string(),
createdAt: z.date(), createdAt: z.date(),
updatedAt: z.date() updatedAt: z.date(),
accessTokenPeriod: z.coerce.number().default(0)
}); });
export type TIdentityJwtAuths = z.infer<typeof IdentityJwtAuthsSchema>; export type TIdentityJwtAuths = z.infer<typeof IdentityJwtAuthsSchema>;

View File

@@ -30,7 +30,8 @@ export const IdentityKubernetesAuthsSchema = z.object({
allowedAudience: z.string(), allowedAudience: z.string(),
encryptedKubernetesTokenReviewerJwt: zodBuffer.nullable().optional(), encryptedKubernetesTokenReviewerJwt: zodBuffer.nullable().optional(),
encryptedKubernetesCaCertificate: zodBuffer.nullable().optional(), encryptedKubernetesCaCertificate: zodBuffer.nullable().optional(),
gatewayId: z.string().uuid().nullable().optional() gatewayId: z.string().uuid().nullable().optional(),
accessTokenPeriod: z.coerce.number().default(0)
}); });
export type TIdentityKubernetesAuths = z.infer<typeof IdentityKubernetesAuthsSchema>; export type TIdentityKubernetesAuths = z.infer<typeof IdentityKubernetesAuthsSchema>;

View File

@@ -24,7 +24,8 @@ export const IdentityLdapAuthsSchema = z.object({
searchFilter: z.string(), searchFilter: z.string(),
allowedFields: z.unknown().nullable().optional(), allowedFields: z.unknown().nullable().optional(),
createdAt: z.date(), createdAt: z.date(),
updatedAt: z.date() updatedAt: z.date(),
accessTokenPeriod: z.coerce.number().default(0)
}); });
export type TIdentityLdapAuths = z.infer<typeof IdentityLdapAuthsSchema>; export type TIdentityLdapAuths = z.infer<typeof IdentityLdapAuthsSchema>;

View File

@@ -18,7 +18,8 @@ export const IdentityOciAuthsSchema = z.object({
identityId: z.string().uuid(), identityId: z.string().uuid(),
type: z.string(), type: z.string(),
tenancyOcid: z.string(), tenancyOcid: z.string(),
allowedUsernames: z.string().nullable().optional() allowedUsernames: z.string().nullable().optional(),
accessTokenPeriod: z.coerce.number().default(0)
}); });
export type TIdentityOciAuths = z.infer<typeof IdentityOciAuthsSchema>; export type TIdentityOciAuths = z.infer<typeof IdentityOciAuthsSchema>;

View File

@@ -27,7 +27,8 @@ export const IdentityOidcAuthsSchema = z.object({
createdAt: z.date(), createdAt: z.date(),
updatedAt: z.date(), updatedAt: z.date(),
encryptedCaCertificate: zodBuffer.nullable().optional(), encryptedCaCertificate: zodBuffer.nullable().optional(),
claimMetadataMapping: z.unknown().nullable().optional() claimMetadataMapping: z.unknown().nullable().optional(),
accessTokenPeriod: z.coerce.number().default(0)
}); });
export type TIdentityOidcAuths = z.infer<typeof IdentityOidcAuthsSchema>; export type TIdentityOidcAuths = z.infer<typeof IdentityOidcAuthsSchema>;

View File

@@ -15,7 +15,8 @@ export const IdentityTokenAuthsSchema = z.object({
accessTokenTrustedIps: z.unknown(), accessTokenTrustedIps: z.unknown(),
createdAt: z.date(), createdAt: z.date(),
updatedAt: z.date(), updatedAt: z.date(),
identityId: z.string().uuid() identityId: z.string().uuid(),
accessTokenPeriod: z.coerce.number().default(0)
}); });
export type TIdentityTokenAuths = z.infer<typeof IdentityTokenAuthsSchema>; export type TIdentityTokenAuths = z.infer<typeof IdentityTokenAuthsSchema>;

View File

@@ -17,7 +17,8 @@ export const IdentityUniversalAuthsSchema = z.object({
accessTokenTrustedIps: z.unknown(), accessTokenTrustedIps: z.unknown(),
createdAt: z.date(), createdAt: z.date(),
updatedAt: z.date(), updatedAt: z.date(),
identityId: z.string().uuid() identityId: z.string().uuid(),
accessTokenPeriod: z.coerce.number().default(0)
}); });
export type TIdentityUniversalAuths = z.infer<typeof IdentityUniversalAuthsSchema>; export type TIdentityUniversalAuths = z.infer<typeof IdentityUniversalAuthsSchema>;

View File

@@ -1,5 +1,6 @@
export * from "./access-approval-policies"; export * from "./access-approval-policies";
export * from "./access-approval-policies-approvers"; export * from "./access-approval-policies-approvers";
export * from "./access-approval-policies-bypassers";
export * from "./access-approval-requests"; export * from "./access-approval-requests";
export * from "./access-approval-requests-reviewers"; export * from "./access-approval-requests-reviewers";
export * from "./api-keys"; export * from "./api-keys";
@@ -92,6 +93,7 @@ export * from "./saml-configs";
export * from "./scim-tokens"; export * from "./scim-tokens";
export * from "./secret-approval-policies"; export * from "./secret-approval-policies";
export * from "./secret-approval-policies-approvers"; export * from "./secret-approval-policies-approvers";
export * from "./secret-approval-policies-bypassers";
export * from "./secret-approval-request-secret-tags"; export * from "./secret-approval-request-secret-tags";
export * from "./secret-approval-request-secret-tags-v2"; export * from "./secret-approval-request-secret-tags-v2";
export * from "./secret-approval-requests"; export * from "./secret-approval-requests";

View File

@@ -95,10 +95,12 @@ export enum TableName {
ScimToken = "scim_tokens", ScimToken = "scim_tokens",
AccessApprovalPolicy = "access_approval_policies", AccessApprovalPolicy = "access_approval_policies",
AccessApprovalPolicyApprover = "access_approval_policies_approvers", AccessApprovalPolicyApprover = "access_approval_policies_approvers",
AccessApprovalPolicyBypasser = "access_approval_policies_bypassers",
AccessApprovalRequest = "access_approval_requests", AccessApprovalRequest = "access_approval_requests",
AccessApprovalRequestReviewer = "access_approval_requests_reviewers", AccessApprovalRequestReviewer = "access_approval_requests_reviewers",
SecretApprovalPolicy = "secret_approval_policies", SecretApprovalPolicy = "secret_approval_policies",
SecretApprovalPolicyApprover = "secret_approval_policies_approvers", SecretApprovalPolicyApprover = "secret_approval_policies_approvers",
SecretApprovalPolicyBypasser = "secret_approval_policies_bypassers",
SecretApprovalRequest = "secret_approval_requests", SecretApprovalRequest = "secret_approval_requests",
SecretApprovalRequestReviewer = "secret_approval_requests_reviewers", SecretApprovalRequestReviewer = "secret_approval_requests_reviewers",
SecretApprovalRequestSecret = "secret_approval_requests_secrets", SecretApprovalRequestSecret = "secret_approval_requests_secrets",

View File

@@ -0,0 +1,26 @@
// Code generated by automation script, DO NOT EDIT.
// Automated by pulling database and generating zod schema
// To update. Just run npm run generate:schema
// Written by akhilmhdh.
import { z } from "zod";
import { TImmutableDBKeys } from "./models";
export const SecretApprovalPoliciesBypassersSchema = z.object({
id: z.string().uuid(),
bypasserGroupId: z.string().uuid().nullable().optional(),
bypasserUserId: z.string().uuid().nullable().optional(),
policyId: z.string().uuid(),
createdAt: z.date(),
updatedAt: z.date()
});
export type TSecretApprovalPoliciesBypassers = z.infer<typeof SecretApprovalPoliciesBypassersSchema>;
export type TSecretApprovalPoliciesBypassersInsert = Omit<
z.input<typeof SecretApprovalPoliciesBypassersSchema>,
TImmutableDBKeys
>;
export type TSecretApprovalPoliciesBypassersUpdate = Partial<
Omit<z.input<typeof SecretApprovalPoliciesBypassersSchema>, TImmutableDBKeys>
>;

View File

@@ -28,7 +28,6 @@ export const SecretSharingSchema = z.object({
encryptedSecret: zodBuffer.nullable().optional(), encryptedSecret: zodBuffer.nullable().optional(),
identifier: z.string().nullable().optional(), identifier: z.string().nullable().optional(),
type: z.string().default("share"), type: z.string().default("share"),
encryptedSalt: zodBuffer.nullable().optional(),
authorizedEmails: z.unknown().nullable().optional() authorizedEmails: z.unknown().nullable().optional()
}); });

View File

@@ -1,7 +1,7 @@
import { nanoid } from "nanoid"; import { nanoid } from "nanoid";
import { z } from "zod"; import { z } from "zod";
import { ApproverType } from "@app/ee/services/access-approval-policy/access-approval-policy-types"; import { ApproverType, BypasserType } from "@app/ee/services/access-approval-policy/access-approval-policy-types";
import { EnforcementLevel } from "@app/lib/types"; import { EnforcementLevel } from "@app/lib/types";
import { readLimit, writeLimit } from "@app/server/config/rateLimiter"; import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
@@ -24,10 +24,19 @@ export const registerAccessApprovalPolicyRouter = async (server: FastifyZodProvi
approvers: z approvers: z
.discriminatedUnion("type", [ .discriminatedUnion("type", [
z.object({ type: z.literal(ApproverType.Group), id: z.string() }), z.object({ type: z.literal(ApproverType.Group), id: z.string() }),
z.object({ type: z.literal(ApproverType.User), id: z.string().optional(), name: z.string().optional() }) z.object({ type: z.literal(ApproverType.User), id: z.string().optional(), username: z.string().optional() })
]) ])
.array() .array()
.max(100, "Cannot have more than 100 approvers")
.min(1, { message: "At least one approver should be provided" }), .min(1, { message: "At least one approver should be provided" }),
bypassers: z
.discriminatedUnion("type", [
z.object({ type: z.literal(BypasserType.Group), id: z.string() }),
z.object({ type: z.literal(BypasserType.User), id: z.string().optional(), username: z.string().optional() })
])
.array()
.max(100, "Cannot have more than 100 bypassers")
.optional(),
approvals: z.number().min(1).default(1), approvals: z.number().min(1).default(1),
enforcementLevel: z.nativeEnum(EnforcementLevel).default(EnforcementLevel.Hard), enforcementLevel: z.nativeEnum(EnforcementLevel).default(EnforcementLevel.Hard),
allowedSelfApprovals: z.boolean().default(true) allowedSelfApprovals: z.boolean().default(true)
@@ -72,7 +81,8 @@ export const registerAccessApprovalPolicyRouter = async (server: FastifyZodProvi
.object({ type: z.nativeEnum(ApproverType), id: z.string().nullable().optional() }) .object({ type: z.nativeEnum(ApproverType), id: z.string().nullable().optional() })
.array() .array()
.nullable() .nullable()
.optional() .optional(),
bypassers: z.object({ type: z.nativeEnum(BypasserType), id: z.string().nullable().optional() }).array()
}) })
.array() .array()
.nullable() .nullable()
@@ -143,10 +153,19 @@ export const registerAccessApprovalPolicyRouter = async (server: FastifyZodProvi
approvers: z approvers: z
.discriminatedUnion("type", [ .discriminatedUnion("type", [
z.object({ type: z.literal(ApproverType.Group), id: z.string() }), z.object({ type: z.literal(ApproverType.Group), id: z.string() }),
z.object({ type: z.literal(ApproverType.User), id: z.string().optional(), name: z.string().optional() }) z.object({ type: z.literal(ApproverType.User), id: z.string().optional(), username: z.string().optional() })
]) ])
.array() .array()
.min(1, { message: "At least one approver should be provided" }), .min(1, { message: "At least one approver should be provided" })
.max(100, "Cannot have more than 100 approvers"),
bypassers: z
.discriminatedUnion("type", [
z.object({ type: z.literal(BypasserType.Group), id: z.string() }),
z.object({ type: z.literal(BypasserType.User), id: z.string().optional(), username: z.string().optional() })
])
.array()
.max(100, "Cannot have more than 100 bypassers")
.optional(),
approvals: z.number().min(1).optional(), approvals: z.number().min(1).optional(),
enforcementLevel: z.nativeEnum(EnforcementLevel).default(EnforcementLevel.Hard), enforcementLevel: z.nativeEnum(EnforcementLevel).default(EnforcementLevel.Hard),
allowedSelfApprovals: z.boolean().default(true) allowedSelfApprovals: z.boolean().default(true)
@@ -220,6 +239,15 @@ export const registerAccessApprovalPolicyRouter = async (server: FastifyZodProvi
}) })
.array() .array()
.nullable() .nullable()
.optional(),
bypassers: z
.object({
type: z.nativeEnum(BypasserType),
id: z.string().nullable().optional(),
name: z.string().nullable().optional()
})
.array()
.nullable()
.optional() .optional()
}) })
}) })

View File

@@ -113,6 +113,7 @@ export const registerAccessApprovalRequestRouter = async (server: FastifyZodProv
name: z.string(), name: z.string(),
approvals: z.number(), approvals: z.number(),
approvers: z.string().array(), approvers: z.string().array(),
bypassers: z.string().array(),
secretPath: z.string().nullish(), secretPath: z.string().nullish(),
envId: z.string(), envId: z.string(),
enforcementLevel: z.string(), enforcementLevel: z.string(),

View File

@@ -6,6 +6,8 @@ import { ApiDocsTags, DYNAMIC_SECRETS } from "@app/lib/api-docs";
import { daysToMillisecond } from "@app/lib/dates"; import { daysToMillisecond } from "@app/lib/dates";
import { removeTrailingSlash } from "@app/lib/fn"; import { removeTrailingSlash } from "@app/lib/fn";
import { ms } from "@app/lib/ms"; import { ms } from "@app/lib/ms";
import { isValidHandleBarTemplate } from "@app/lib/template/validate-handlebars";
import { CharacterType, characterValidator } from "@app/lib/validator/validate-string";
import { readLimit, writeLimit } from "@app/server/config/rateLimiter"; import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
import { slugSchema } from "@app/server/lib/schemas"; import { slugSchema } from "@app/server/lib/schemas";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
@@ -13,6 +15,28 @@ import { SanitizedDynamicSecretSchema } from "@app/server/routes/sanitizedSchema
import { AuthMode } from "@app/services/auth/auth-type"; import { AuthMode } from "@app/services/auth/auth-type";
import { ResourceMetadataSchema } from "@app/services/resource-metadata/resource-metadata-schema"; import { ResourceMetadataSchema } from "@app/services/resource-metadata/resource-metadata-schema";
const validateUsernameTemplateCharacters = characterValidator([
CharacterType.AlphaNumeric,
CharacterType.Underscore,
CharacterType.Hyphen,
CharacterType.OpenBrace,
CharacterType.CloseBrace,
CharacterType.CloseBracket,
CharacterType.OpenBracket,
CharacterType.Fullstop
]);
const userTemplateSchema = z
.string()
.trim()
.max(255)
.refine((el) => validateUsernameTemplateCharacters(el))
.refine((el) =>
isValidHandleBarTemplate(el, {
allowedExpressions: (val) => ["randomUsername", "unixTimestamp"].includes(val)
})
);
export const registerDynamicSecretRouter = async (server: FastifyZodProvider) => { export const registerDynamicSecretRouter = async (server: FastifyZodProvider) => {
server.route({ server.route({
method: "POST", method: "POST",
@@ -52,7 +76,8 @@ export const registerDynamicSecretRouter = async (server: FastifyZodProvider) =>
path: z.string().describe(DYNAMIC_SECRETS.CREATE.path).trim().default("/").transform(removeTrailingSlash), path: z.string().describe(DYNAMIC_SECRETS.CREATE.path).trim().default("/").transform(removeTrailingSlash),
environmentSlug: z.string().describe(DYNAMIC_SECRETS.CREATE.environmentSlug).min(1), environmentSlug: z.string().describe(DYNAMIC_SECRETS.CREATE.environmentSlug).min(1),
name: slugSchema({ min: 1, max: 64, field: "Name" }).describe(DYNAMIC_SECRETS.CREATE.name), name: slugSchema({ min: 1, max: 64, field: "Name" }).describe(DYNAMIC_SECRETS.CREATE.name),
metadata: ResourceMetadataSchema.optional() metadata: ResourceMetadataSchema.optional(),
usernameTemplate: userTemplateSchema.optional()
}), }),
response: { response: {
200: z.object({ 200: z.object({
@@ -73,39 +98,6 @@ export const registerDynamicSecretRouter = async (server: FastifyZodProvider) =>
} }
}); });
server.route({
method: "POST",
url: "/entra-id/users",
config: {
rateLimit: readLimit
},
schema: {
body: z.object({
tenantId: z.string().min(1).describe("The tenant ID of the Azure Entra ID"),
applicationId: z.string().min(1).describe("The application ID of the Azure Entra ID App Registration"),
clientSecret: z.string().min(1).describe("The client secret of the Azure Entra ID App Registration")
}),
response: {
200: z
.object({
name: z.string().min(1).describe("The name of the user"),
id: z.string().min(1).describe("The ID of the user"),
email: z.string().min(1).describe("The email of the user")
})
.array()
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const data = await server.services.dynamicSecret.fetchAzureEntraIdUsers({
tenantId: req.body.tenantId,
applicationId: req.body.applicationId,
clientSecret: req.body.clientSecret
});
return data;
}
});
server.route({ server.route({
method: "PATCH", method: "PATCH",
url: "/:name", url: "/:name",
@@ -150,7 +142,8 @@ export const registerDynamicSecretRouter = async (server: FastifyZodProvider) =>
}) })
.nullable(), .nullable(),
newName: z.string().describe(DYNAMIC_SECRETS.UPDATE.newName).optional(), newName: z.string().describe(DYNAMIC_SECRETS.UPDATE.newName).optional(),
metadata: ResourceMetadataSchema.optional() metadata: ResourceMetadataSchema.optional(),
usernameTemplate: userTemplateSchema.nullable().optional()
}) })
}), }),
response: { response: {
@@ -328,4 +321,37 @@ export const registerDynamicSecretRouter = async (server: FastifyZodProvider) =>
return { leases }; return { leases };
} }
}); });
server.route({
method: "POST",
url: "/entra-id/users",
config: {
rateLimit: readLimit
},
schema: {
body: z.object({
tenantId: z.string().min(1).describe("The tenant ID of the Azure Entra ID"),
applicationId: z.string().min(1).describe("The application ID of the Azure Entra ID App Registration"),
clientSecret: z.string().min(1).describe("The client secret of the Azure Entra ID App Registration")
}),
response: {
200: z
.object({
name: z.string().min(1).describe("The name of the user"),
id: z.string().min(1).describe("The ID of the user"),
email: z.string().min(1).describe("The email of the user")
})
.array()
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const data = await server.services.dynamicSecret.fetchAzureEntraIdUsers({
tenantId: req.body.tenantId,
applicationId: req.body.applicationId,
clientSecret: req.body.clientSecret
});
return data;
}
});
}; };

View File

@@ -1,7 +1,7 @@
import { nanoid } from "nanoid"; import { nanoid } from "nanoid";
import { z } from "zod"; import { z } from "zod";
import { ApproverType } from "@app/ee/services/access-approval-policy/access-approval-policy-types"; import { ApproverType, BypasserType } from "@app/ee/services/access-approval-policy/access-approval-policy-types";
import { removeTrailingSlash } from "@app/lib/fn"; import { removeTrailingSlash } from "@app/lib/fn";
import { EnforcementLevel } from "@app/lib/types"; import { EnforcementLevel } from "@app/lib/types";
import { readLimit, writeLimit } from "@app/server/config/rateLimiter"; import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
@@ -30,10 +30,19 @@ export const registerSecretApprovalPolicyRouter = async (server: FastifyZodProvi
approvers: z approvers: z
.discriminatedUnion("type", [ .discriminatedUnion("type", [
z.object({ type: z.literal(ApproverType.Group), id: z.string() }), z.object({ type: z.literal(ApproverType.Group), id: z.string() }),
z.object({ type: z.literal(ApproverType.User), id: z.string().optional(), name: z.string().optional() }) z.object({ type: z.literal(ApproverType.User), id: z.string().optional(), username: z.string().optional() })
]) ])
.array() .array()
.min(1, { message: "At least one approver should be provided" }), .min(1, { message: "At least one approver should be provided" })
.max(100, "Cannot have more than 100 approvers"),
bypassers: z
.discriminatedUnion("type", [
z.object({ type: z.literal(BypasserType.Group), id: z.string() }),
z.object({ type: z.literal(BypasserType.User), id: z.string().optional(), username: z.string().optional() })
])
.array()
.max(100, "Cannot have more than 100 bypassers")
.optional(),
approvals: z.number().min(1).default(1), approvals: z.number().min(1).default(1),
enforcementLevel: z.nativeEnum(EnforcementLevel).default(EnforcementLevel.Hard), enforcementLevel: z.nativeEnum(EnforcementLevel).default(EnforcementLevel.Hard),
allowedSelfApprovals: z.boolean().default(true) allowedSelfApprovals: z.boolean().default(true)
@@ -75,10 +84,19 @@ export const registerSecretApprovalPolicyRouter = async (server: FastifyZodProvi
approvers: z approvers: z
.discriminatedUnion("type", [ .discriminatedUnion("type", [
z.object({ type: z.literal(ApproverType.Group), id: z.string() }), z.object({ type: z.literal(ApproverType.Group), id: z.string() }),
z.object({ type: z.literal(ApproverType.User), id: z.string().optional(), name: z.string().optional() }) z.object({ type: z.literal(ApproverType.User), id: z.string().optional(), username: z.string().optional() })
]) ])
.array() .array()
.min(1, { message: "At least one approver should be provided" }), .min(1, { message: "At least one approver should be provided" })
.max(100, "Cannot have more than 100 approvers"),
bypassers: z
.discriminatedUnion("type", [
z.object({ type: z.literal(BypasserType.Group), id: z.string() }),
z.object({ type: z.literal(BypasserType.User), id: z.string().optional(), username: z.string().optional() })
])
.array()
.max(100, "Cannot have more than 100 bypassers")
.optional(),
approvals: z.number().min(1).default(1), approvals: z.number().min(1).default(1),
secretPath: z secretPath: z
.string() .string()
@@ -157,6 +175,12 @@ export const registerSecretApprovalPolicyRouter = async (server: FastifyZodProvi
id: z.string().nullable().optional(), id: z.string().nullable().optional(),
type: z.nativeEnum(ApproverType) type: z.nativeEnum(ApproverType)
}) })
.array(),
bypassers: z
.object({
id: z.string().nullable().optional(),
type: z.nativeEnum(BypasserType)
})
.array() .array()
}) })
.array() .array()
@@ -193,7 +217,14 @@ export const registerSecretApprovalPolicyRouter = async (server: FastifyZodProvi
.object({ .object({
id: z.string().nullable().optional(), id: z.string().nullable().optional(),
type: z.nativeEnum(ApproverType), type: z.nativeEnum(ApproverType),
name: z.string().nullable().optional() username: z.string().nullable().optional()
})
.array(),
bypassers: z
.object({
id: z.string().nullable().optional(),
type: z.nativeEnum(BypasserType),
username: z.string().nullable().optional()
}) })
.array() .array()
}) })

View File

@@ -47,6 +47,11 @@ export const registerSecretApprovalRequestRouter = async (server: FastifyZodProv
userId: z.string().nullable().optional() userId: z.string().nullable().optional()
}) })
.array(), .array(),
bypassers: z
.object({
userId: z.string().nullable().optional()
})
.array(),
secretPath: z.string().optional().nullable(), secretPath: z.string().optional().nullable(),
enforcementLevel: z.string(), enforcementLevel: z.string(),
deletedAt: z.date().nullish(), deletedAt: z.date().nullish(),
@@ -266,6 +271,7 @@ export const registerSecretApprovalRequestRouter = async (server: FastifyZodProv
name: z.string(), name: z.string(),
approvals: z.number(), approvals: z.number(),
approvers: approvalRequestUser.array(), approvers: approvalRequestUser.array(),
bypassers: approvalRequestUser.array(),
secretPath: z.string().optional().nullable(), secretPath: z.string().optional().nullable(),
enforcementLevel: z.string(), enforcementLevel: z.string(),
deletedAt: z.date().nullish(), deletedAt: z.date().nullish(),

View File

@@ -5,6 +5,7 @@ import { registerAwsIamUserSecretRotationRouter } from "./aws-iam-user-secret-ro
import { registerAzureClientSecretRotationRouter } from "./azure-client-secret-rotation-router"; import { registerAzureClientSecretRotationRouter } from "./azure-client-secret-rotation-router";
import { registerLdapPasswordRotationRouter } from "./ldap-password-rotation-router"; import { registerLdapPasswordRotationRouter } from "./ldap-password-rotation-router";
import { registerMsSqlCredentialsRotationRouter } from "./mssql-credentials-rotation-router"; import { registerMsSqlCredentialsRotationRouter } from "./mssql-credentials-rotation-router";
import { registerMySqlCredentialsRotationRouter } from "./mysql-credentials-rotation-router";
import { registerPostgresCredentialsRotationRouter } from "./postgres-credentials-rotation-router"; import { registerPostgresCredentialsRotationRouter } from "./postgres-credentials-rotation-router";
export * from "./secret-rotation-v2-router"; export * from "./secret-rotation-v2-router";
@@ -15,6 +16,7 @@ export const SECRET_ROTATION_REGISTER_ROUTER_MAP: Record<
> = { > = {
[SecretRotation.PostgresCredentials]: registerPostgresCredentialsRotationRouter, [SecretRotation.PostgresCredentials]: registerPostgresCredentialsRotationRouter,
[SecretRotation.MsSqlCredentials]: registerMsSqlCredentialsRotationRouter, [SecretRotation.MsSqlCredentials]: registerMsSqlCredentialsRotationRouter,
[SecretRotation.MySqlCredentials]: registerMySqlCredentialsRotationRouter,
[SecretRotation.Auth0ClientSecret]: registerAuth0ClientSecretRotationRouter, [SecretRotation.Auth0ClientSecret]: registerAuth0ClientSecretRotationRouter,
[SecretRotation.AzureClientSecret]: registerAzureClientSecretRotationRouter, [SecretRotation.AzureClientSecret]: registerAzureClientSecretRotationRouter,
[SecretRotation.AwsIamUserSecret]: registerAwsIamUserSecretRotationRouter, [SecretRotation.AwsIamUserSecret]: registerAwsIamUserSecretRotationRouter,

View File

@@ -0,0 +1,19 @@
import {
CreateMySqlCredentialsRotationSchema,
MySqlCredentialsRotationSchema,
UpdateMySqlCredentialsRotationSchema
} from "@app/ee/services/secret-rotation-v2/mysql-credentials";
import { SecretRotation } from "@app/ee/services/secret-rotation-v2/secret-rotation-v2-enums";
import { SqlCredentialsRotationGeneratedCredentialsSchema } from "@app/ee/services/secret-rotation-v2/shared/sql-credentials";
import { registerSecretRotationEndpoints } from "./secret-rotation-v2-endpoints";
export const registerMySqlCredentialsRotationRouter = async (server: FastifyZodProvider) =>
registerSecretRotationEndpoints({
type: SecretRotation.MySqlCredentials,
server,
responseSchema: MySqlCredentialsRotationSchema,
createSchema: CreateMySqlCredentialsRotationSchema,
updateSchema: UpdateMySqlCredentialsRotationSchema,
generatedCredentialsSchema: SqlCredentialsRotationGeneratedCredentialsSchema
});

View File

@@ -6,6 +6,7 @@ import { AwsIamUserSecretRotationListItemSchema } from "@app/ee/services/secret-
import { AzureClientSecretRotationListItemSchema } from "@app/ee/services/secret-rotation-v2/azure-client-secret"; import { AzureClientSecretRotationListItemSchema } from "@app/ee/services/secret-rotation-v2/azure-client-secret";
import { LdapPasswordRotationListItemSchema } from "@app/ee/services/secret-rotation-v2/ldap-password"; import { LdapPasswordRotationListItemSchema } from "@app/ee/services/secret-rotation-v2/ldap-password";
import { MsSqlCredentialsRotationListItemSchema } from "@app/ee/services/secret-rotation-v2/mssql-credentials"; import { MsSqlCredentialsRotationListItemSchema } from "@app/ee/services/secret-rotation-v2/mssql-credentials";
import { MySqlCredentialsRotationListItemSchema } from "@app/ee/services/secret-rotation-v2/mysql-credentials";
import { PostgresCredentialsRotationListItemSchema } from "@app/ee/services/secret-rotation-v2/postgres-credentials"; import { PostgresCredentialsRotationListItemSchema } from "@app/ee/services/secret-rotation-v2/postgres-credentials";
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 { ApiDocsTags, SecretRotations } from "@app/lib/api-docs"; import { ApiDocsTags, SecretRotations } from "@app/lib/api-docs";
@@ -16,6 +17,7 @@ import { AuthMode } from "@app/services/auth/auth-type";
const SecretRotationV2OptionsSchema = z.discriminatedUnion("type", [ const SecretRotationV2OptionsSchema = z.discriminatedUnion("type", [
PostgresCredentialsRotationListItemSchema, PostgresCredentialsRotationListItemSchema,
MsSqlCredentialsRotationListItemSchema, MsSqlCredentialsRotationListItemSchema,
MySqlCredentialsRotationListItemSchema,
Auth0ClientSecretRotationListItemSchema, Auth0ClientSecretRotationListItemSchema,
AzureClientSecretRotationListItemSchema, AzureClientSecretRotationListItemSchema,
AwsIamUserSecretRotationListItemSchema, AwsIamUserSecretRotationListItemSchema,

View File

@@ -8,3 +8,10 @@ export const accessApprovalPolicyApproverDALFactory = (db: TDbClient) => {
const accessApprovalPolicyApproverOrm = ormify(db, TableName.AccessApprovalPolicyApprover); const accessApprovalPolicyApproverOrm = ormify(db, TableName.AccessApprovalPolicyApprover);
return { ...accessApprovalPolicyApproverOrm }; return { ...accessApprovalPolicyApproverOrm };
}; };
export type TAccessApprovalPolicyBypasserDALFactory = ReturnType<typeof accessApprovalPolicyBypasserDALFactory>;
export const accessApprovalPolicyBypasserDALFactory = (db: TDbClient) => {
const accessApprovalPolicyBypasserOrm = ormify(db, TableName.AccessApprovalPolicyBypasser);
return { ...accessApprovalPolicyBypasserOrm };
};

View File

@@ -1,11 +1,11 @@
import { Knex } from "knex"; import { Knex } from "knex";
import { TDbClient } from "@app/db"; import { TDbClient } from "@app/db";
import { AccessApprovalPoliciesSchema, TableName, TAccessApprovalPolicies } from "@app/db/schemas"; import { AccessApprovalPoliciesSchema, TableName, TAccessApprovalPolicies, TUsers } from "@app/db/schemas";
import { DatabaseError } from "@app/lib/errors"; import { DatabaseError } from "@app/lib/errors";
import { buildFindFilter, ormify, selectAllTableCols, sqlNestRelationships, TFindFilter } from "@app/lib/knex"; import { buildFindFilter, ormify, selectAllTableCols, sqlNestRelationships, TFindFilter } from "@app/lib/knex";
import { ApproverType } from "./access-approval-policy-types"; import { ApproverType, BypasserType } from "./access-approval-policy-types";
export type TAccessApprovalPolicyDALFactory = ReturnType<typeof accessApprovalPolicyDALFactory>; export type TAccessApprovalPolicyDALFactory = ReturnType<typeof accessApprovalPolicyDALFactory>;
@@ -34,9 +34,22 @@ export const accessApprovalPolicyDALFactory = (db: TDbClient) => {
`${TableName.AccessApprovalPolicyApprover}.policyId` `${TableName.AccessApprovalPolicyApprover}.policyId`
) )
.leftJoin(TableName.Users, `${TableName.AccessApprovalPolicyApprover}.approverUserId`, `${TableName.Users}.id`) .leftJoin(TableName.Users, `${TableName.AccessApprovalPolicyApprover}.approverUserId`, `${TableName.Users}.id`)
.leftJoin(
TableName.AccessApprovalPolicyBypasser,
`${TableName.AccessApprovalPolicy}.id`,
`${TableName.AccessApprovalPolicyBypasser}.policyId`
)
.leftJoin<TUsers>(
db(TableName.Users).as("bypasserUsers"),
`${TableName.AccessApprovalPolicyBypasser}.bypasserUserId`,
`bypasserUsers.id`
)
.select(tx.ref("username").withSchema(TableName.Users).as("approverUsername")) .select(tx.ref("username").withSchema(TableName.Users).as("approverUsername"))
.select(tx.ref("username").withSchema("bypasserUsers").as("bypasserUsername"))
.select(tx.ref("approverUserId").withSchema(TableName.AccessApprovalPolicyApprover)) .select(tx.ref("approverUserId").withSchema(TableName.AccessApprovalPolicyApprover))
.select(tx.ref("approverGroupId").withSchema(TableName.AccessApprovalPolicyApprover)) .select(tx.ref("approverGroupId").withSchema(TableName.AccessApprovalPolicyApprover))
.select(tx.ref("bypasserUserId").withSchema(TableName.AccessApprovalPolicyBypasser))
.select(tx.ref("bypasserGroupId").withSchema(TableName.AccessApprovalPolicyBypasser))
.select(tx.ref("name").withSchema(TableName.Environment).as("envName")) .select(tx.ref("name").withSchema(TableName.Environment).as("envName"))
.select(tx.ref("slug").withSchema(TableName.Environment).as("envSlug")) .select(tx.ref("slug").withSchema(TableName.Environment).as("envSlug"))
.select(tx.ref("id").withSchema(TableName.Environment).as("envId")) .select(tx.ref("id").withSchema(TableName.Environment).as("envId"))
@@ -129,6 +142,23 @@ export const accessApprovalPolicyDALFactory = (db: TDbClient) => {
id, id,
type: ApproverType.Group type: ApproverType.Group
}) })
},
{
key: "bypasserUserId",
label: "bypassers" as const,
mapper: ({ bypasserUserId: id, bypasserUsername }) => ({
id,
type: BypasserType.User,
name: bypasserUsername
})
},
{
key: "bypasserGroupId",
label: "bypassers" as const,
mapper: ({ bypasserGroupId: id }) => ({
id,
type: BypasserType.Group
})
} }
] ]
}); });
@@ -144,5 +174,28 @@ export const accessApprovalPolicyDALFactory = (db: TDbClient) => {
return softDeletedPolicy; return softDeletedPolicy;
}; };
return { ...accessApprovalPolicyOrm, find, findById, softDeleteById }; const findLastValidPolicy = async ({ envId, secretPath }: { envId: string; secretPath: string }, tx?: Knex) => {
try {
const result = await (tx || db.replicaNode())(TableName.AccessApprovalPolicy)
.where(
// eslint-disable-next-line @typescript-eslint/no-misused-promises
buildFindFilter(
{
envId,
secretPath
},
TableName.AccessApprovalPolicy
)
)
.orderBy("deletedAt", "desc")
.orderByRaw(`"deletedAt" IS NULL`)
.first();
return result;
} catch (error) {
throw new DatabaseError({ error, name: "FindLastValidPolicy" });
}
};
return { ...accessApprovalPolicyOrm, find, findById, softDeleteById, findLastValidPolicy };
}; };

View File

@@ -2,8 +2,9 @@ import { ForbiddenError } 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 { ProjectPermissionApprovalActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission"; import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors"; import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors";
import { TOrgMembershipDALFactory } from "@app/services/org-membership/org-membership-dal";
import { TProjectDALFactory } from "@app/services/project/project-dal"; import { TProjectDALFactory } from "@app/services/project/project-dal";
import { TProjectEnvDALFactory } from "@app/services/project-env/project-env-dal"; import { TProjectEnvDALFactory } from "@app/services/project-env/project-env-dal";
import { TProjectMembershipDALFactory } from "@app/services/project-membership/project-membership-dal"; import { TProjectMembershipDALFactory } from "@app/services/project-membership/project-membership-dal";
@@ -14,10 +15,14 @@ import { TAccessApprovalRequestReviewerDALFactory } from "../access-approval-req
import { ApprovalStatus } from "../access-approval-request/access-approval-request-types"; import { ApprovalStatus } from "../access-approval-request/access-approval-request-types";
import { TGroupDALFactory } from "../group/group-dal"; import { TGroupDALFactory } from "../group/group-dal";
import { TProjectUserAdditionalPrivilegeDALFactory } from "../project-user-additional-privilege/project-user-additional-privilege-dal"; import { TProjectUserAdditionalPrivilegeDALFactory } from "../project-user-additional-privilege/project-user-additional-privilege-dal";
import { TAccessApprovalPolicyApproverDALFactory } from "./access-approval-policy-approver-dal"; import {
TAccessApprovalPolicyApproverDALFactory,
TAccessApprovalPolicyBypasserDALFactory
} from "./access-approval-policy-approver-dal";
import { TAccessApprovalPolicyDALFactory } from "./access-approval-policy-dal"; import { TAccessApprovalPolicyDALFactory } from "./access-approval-policy-dal";
import { import {
ApproverType, ApproverType,
BypasserType,
TCreateAccessApprovalPolicy, TCreateAccessApprovalPolicy,
TDeleteAccessApprovalPolicy, TDeleteAccessApprovalPolicy,
TGetAccessApprovalPolicyByIdDTO, TGetAccessApprovalPolicyByIdDTO,
@@ -32,12 +37,14 @@ type TAccessApprovalPolicyServiceFactoryDep = {
accessApprovalPolicyDAL: TAccessApprovalPolicyDALFactory; accessApprovalPolicyDAL: TAccessApprovalPolicyDALFactory;
projectEnvDAL: Pick<TProjectEnvDALFactory, "find" | "findOne">; projectEnvDAL: Pick<TProjectEnvDALFactory, "find" | "findOne">;
accessApprovalPolicyApproverDAL: TAccessApprovalPolicyApproverDALFactory; accessApprovalPolicyApproverDAL: TAccessApprovalPolicyApproverDALFactory;
accessApprovalPolicyBypasserDAL: TAccessApprovalPolicyBypasserDALFactory;
projectMembershipDAL: Pick<TProjectMembershipDALFactory, "find">; projectMembershipDAL: Pick<TProjectMembershipDALFactory, "find">;
groupDAL: TGroupDALFactory; groupDAL: TGroupDALFactory;
userDAL: Pick<TUserDALFactory, "find">; userDAL: Pick<TUserDALFactory, "find">;
accessApprovalRequestDAL: Pick<TAccessApprovalRequestDALFactory, "update" | "find">; accessApprovalRequestDAL: Pick<TAccessApprovalRequestDALFactory, "update" | "find">;
additionalPrivilegeDAL: Pick<TProjectUserAdditionalPrivilegeDALFactory, "delete">; additionalPrivilegeDAL: Pick<TProjectUserAdditionalPrivilegeDALFactory, "delete">;
accessApprovalRequestReviewerDAL: Pick<TAccessApprovalRequestReviewerDALFactory, "update">; accessApprovalRequestReviewerDAL: Pick<TAccessApprovalRequestReviewerDALFactory, "update">;
orgMembershipDAL: Pick<TOrgMembershipDALFactory, "find">;
}; };
export type TAccessApprovalPolicyServiceFactory = ReturnType<typeof accessApprovalPolicyServiceFactory>; export type TAccessApprovalPolicyServiceFactory = ReturnType<typeof accessApprovalPolicyServiceFactory>;
@@ -45,6 +52,7 @@ export type TAccessApprovalPolicyServiceFactory = ReturnType<typeof accessApprov
export const accessApprovalPolicyServiceFactory = ({ export const accessApprovalPolicyServiceFactory = ({
accessApprovalPolicyDAL, accessApprovalPolicyDAL,
accessApprovalPolicyApproverDAL, accessApprovalPolicyApproverDAL,
accessApprovalPolicyBypasserDAL,
groupDAL, groupDAL,
permissionService, permissionService,
projectEnvDAL, projectEnvDAL,
@@ -52,7 +60,8 @@ export const accessApprovalPolicyServiceFactory = ({
userDAL, userDAL,
accessApprovalRequestDAL, accessApprovalRequestDAL,
additionalPrivilegeDAL, additionalPrivilegeDAL,
accessApprovalRequestReviewerDAL accessApprovalRequestReviewerDAL,
orgMembershipDAL
}: TAccessApprovalPolicyServiceFactoryDep) => { }: TAccessApprovalPolicyServiceFactoryDep) => {
const createAccessApprovalPolicy = async ({ const createAccessApprovalPolicy = async ({
name, name,
@@ -63,6 +72,7 @@ export const accessApprovalPolicyServiceFactory = ({
actorAuthMethod, actorAuthMethod,
approvals, approvals,
approvers, approvers,
bypassers,
projectSlug, projectSlug,
environment, environment,
enforcementLevel, enforcementLevel,
@@ -82,7 +92,7 @@ export const accessApprovalPolicyServiceFactory = ({
.filter(Boolean) as string[]; .filter(Boolean) as string[];
const userApproverNames = approvers const userApproverNames = approvers
.map((approver) => (approver.type === ApproverType.User ? approver.name : undefined)) .map((approver) => (approver.type === ApproverType.User ? approver.username : undefined))
.filter(Boolean) as string[]; .filter(Boolean) as string[];
if (!groupApprovers && approvals > userApprovers.length + userApproverNames.length) if (!groupApprovers && approvals > userApprovers.length + userApproverNames.length)
@@ -98,7 +108,7 @@ export const accessApprovalPolicyServiceFactory = ({
}); });
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionApprovalActions.Create, ProjectPermissionActions.Create,
ProjectPermissionSub.SecretApproval ProjectPermissionSub.SecretApproval
); );
const env = await projectEnvDAL.findOne({ slug: environment, projectId: project.id }); const env = await projectEnvDAL.findOne({ slug: environment, projectId: project.id });
@@ -147,6 +157,44 @@ export const accessApprovalPolicyServiceFactory = ({
.map((user) => user.id); .map((user) => user.id);
verifyAllApprovers.push(...verifyGroupApprovers); verifyAllApprovers.push(...verifyGroupApprovers);
let groupBypassers: string[] = [];
let bypasserUserIds: string[] = [];
if (bypassers && bypassers.length) {
groupBypassers = bypassers
.filter((bypasser) => bypasser.type === BypasserType.Group)
.map((bypasser) => bypasser.id) as string[];
const userBypassers = bypassers
.filter((bypasser) => bypasser.type === BypasserType.User)
.map((bypasser) => bypasser.id)
.filter(Boolean) as string[];
const userBypasserNames = bypassers
.map((bypasser) => (bypasser.type === BypasserType.User ? bypasser.username : undefined))
.filter(Boolean) as string[];
bypasserUserIds = userBypassers;
if (userBypasserNames.length) {
const bypasserUsers = await userDAL.find({
$in: {
username: userBypasserNames
}
});
const bypasserNamesFromDb = bypasserUsers.map((user) => user.username);
const invalidUsernames = userBypasserNames.filter((username) => !bypasserNamesFromDb.includes(username));
if (invalidUsernames.length) {
throw new BadRequestError({
message: `Invalid bypasser user: ${invalidUsernames.join(", ")}`
});
}
bypasserUserIds = bypasserUserIds.concat(bypasserUsers.map((user) => user.id));
}
}
const accessApproval = await accessApprovalPolicyDAL.transaction(async (tx) => { const accessApproval = await accessApprovalPolicyDAL.transaction(async (tx) => {
const doc = await accessApprovalPolicyDAL.create( const doc = await accessApprovalPolicyDAL.create(
{ {
@@ -159,6 +207,7 @@ export const accessApprovalPolicyServiceFactory = ({
}, },
tx tx
); );
if (approverUserIds.length) { if (approverUserIds.length) {
await accessApprovalPolicyApproverDAL.insertMany( await accessApprovalPolicyApproverDAL.insertMany(
approverUserIds.map((userId) => ({ approverUserIds.map((userId) => ({
@@ -179,8 +228,29 @@ export const accessApprovalPolicyServiceFactory = ({
); );
} }
if (bypasserUserIds.length) {
await accessApprovalPolicyBypasserDAL.insertMany(
bypasserUserIds.map((userId) => ({
bypasserUserId: userId,
policyId: doc.id
})),
tx
);
}
if (groupBypassers.length) {
await accessApprovalPolicyBypasserDAL.insertMany(
groupBypassers.map((groupId) => ({
bypasserGroupId: groupId,
policyId: doc.id
})),
tx
);
}
return doc; return doc;
}); });
return { ...accessApproval, environment: env, projectId: project.id }; return { ...accessApproval, environment: env, projectId: project.id };
}; };
@@ -211,6 +281,7 @@ export const accessApprovalPolicyServiceFactory = ({
const updateAccessApprovalPolicy = async ({ const updateAccessApprovalPolicy = async ({
policyId, policyId,
approvers, approvers,
bypassers,
secretPath, secretPath,
name, name,
actorId, actorId,
@@ -231,15 +302,15 @@ export const accessApprovalPolicyServiceFactory = ({
.filter(Boolean) as string[]; .filter(Boolean) as string[];
const userApproverNames = approvers const userApproverNames = approvers
.map((approver) => (approver.type === ApproverType.User ? approver.name : undefined)) .map((approver) => (approver.type === ApproverType.User ? approver.username : undefined))
.filter(Boolean) as string[]; .filter(Boolean) as string[];
const accessApprovalPolicy = await accessApprovalPolicyDAL.findById(policyId); const accessApprovalPolicy = await accessApprovalPolicyDAL.findById(policyId);
const currentAppovals = approvals || accessApprovalPolicy.approvals; const currentApprovals = approvals || accessApprovalPolicy.approvals;
if ( if (
groupApprovers?.length === 0 && groupApprovers?.length === 0 &&
userApprovers && userApprovers &&
currentAppovals > userApprovers.length + userApproverNames.length currentApprovals > userApprovers.length + userApproverNames.length
) { ) {
throw new BadRequestError({ message: "Approvals cannot be greater than approvers" }); throw new BadRequestError({ message: "Approvals cannot be greater than approvers" });
} }
@@ -256,10 +327,79 @@ export const accessApprovalPolicyServiceFactory = ({
actionProjectType: ActionProjectType.SecretManager actionProjectType: ActionProjectType.SecretManager
}); });
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.SecretApproval);
ProjectPermissionApprovalActions.Edit,
ProjectPermissionSub.SecretApproval let groupBypassers: string[] = [];
); let bypasserUserIds: string[] = [];
if (bypassers && bypassers.length) {
groupBypassers = bypassers
.filter((bypasser) => bypasser.type === BypasserType.Group)
.map((bypasser) => bypasser.id) as string[];
groupBypassers = [...new Set(groupBypassers)];
const userBypassers = bypassers
.filter((bypasser) => bypasser.type === BypasserType.User)
.map((bypasser) => bypasser.id)
.filter(Boolean) as string[];
const userBypasserNames = bypassers
.map((bypasser) => (bypasser.type === BypasserType.User ? bypasser.username : undefined))
.filter(Boolean) as string[];
bypasserUserIds = userBypassers;
if (userBypasserNames.length) {
const bypasserUsers = await userDAL.find({
$in: {
username: userBypasserNames
}
});
const bypasserNamesFromDb = bypasserUsers.map((user) => user.username);
const invalidUsernames = userBypasserNames.filter((username) => !bypasserNamesFromDb.includes(username));
if (invalidUsernames.length) {
throw new BadRequestError({
message: `Invalid bypasser user: ${invalidUsernames.join(", ")}`
});
}
bypasserUserIds = [...new Set(bypasserUserIds.concat(bypasserUsers.map((user) => user.id)))];
}
// Validate user bypassers
if (bypasserUserIds.length > 0) {
const orgMemberships = await orgMembershipDAL.find({
$in: { userId: bypasserUserIds },
orgId: actorOrgId
});
if (orgMemberships.length !== bypasserUserIds.length) {
const foundUserIdsInOrg = new Set(orgMemberships.map((mem) => mem.userId));
const missingUserIds = bypasserUserIds.filter((id) => !foundUserIdsInOrg.has(id));
throw new BadRequestError({
message: `One or more specified bypasser users are not part of the organization or do not exist. Invalid or non-member user IDs: ${missingUserIds.join(", ")}`
});
}
}
// Validate group bypassers
if (groupBypassers.length > 0) {
const orgGroups = await groupDAL.find({
$in: { id: groupBypassers },
orgId: actorOrgId
});
if (orgGroups.length !== groupBypassers.length) {
const foundGroupIdsInOrg = new Set(orgGroups.map((group) => group.id));
const missingGroupIds = groupBypassers.filter((id) => !foundGroupIdsInOrg.has(id));
throw new BadRequestError({
message: `One or more specified bypasser groups are not part of the organization or do not exist. Invalid or non-member group IDs: ${missingGroupIds.join(", ")}`
});
}
}
}
const updatedPolicy = await accessApprovalPolicyDAL.transaction(async (tx) => { const updatedPolicy = await accessApprovalPolicyDAL.transaction(async (tx) => {
const doc = await accessApprovalPolicyDAL.updateById( const doc = await accessApprovalPolicyDAL.updateById(
@@ -316,6 +456,28 @@ export const accessApprovalPolicyServiceFactory = ({
); );
} }
await accessApprovalPolicyBypasserDAL.delete({ policyId: doc.id }, tx);
if (bypasserUserIds.length) {
await accessApprovalPolicyBypasserDAL.insertMany(
bypasserUserIds.map((userId) => ({
bypasserUserId: userId,
policyId: doc.id
})),
tx
);
}
if (groupBypassers.length) {
await accessApprovalPolicyBypasserDAL.insertMany(
groupBypassers.map((groupId) => ({
bypasserGroupId: groupId,
policyId: doc.id
})),
tx
);
}
return doc; return doc;
}); });
return { return {
@@ -344,7 +506,7 @@ export const accessApprovalPolicyServiceFactory = ({
actionProjectType: ActionProjectType.SecretManager actionProjectType: ActionProjectType.SecretManager
}); });
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionApprovalActions.Delete, ProjectPermissionActions.Delete,
ProjectPermissionSub.SecretApproval ProjectPermissionSub.SecretApproval
); );
@@ -435,10 +597,7 @@ export const accessApprovalPolicyServiceFactory = ({
actionProjectType: ActionProjectType.SecretManager actionProjectType: ActionProjectType.SecretManager
}); });
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.SecretApproval);
ProjectPermissionApprovalActions.Read,
ProjectPermissionSub.SecretApproval
);
return policy; return policy;
}; };

View File

@@ -18,11 +18,20 @@ export enum ApproverType {
User = "user" User = "user"
} }
export enum BypasserType {
Group = "group",
User = "user"
}
export type TCreateAccessApprovalPolicy = { export type TCreateAccessApprovalPolicy = {
approvals: number; approvals: number;
secretPath: string; secretPath: string;
environment: string; environment: string;
approvers: ({ type: ApproverType.Group; id: string } | { type: ApproverType.User; id?: string; name?: string })[]; approvers: ({ type: ApproverType.Group; id: string } | { type: ApproverType.User; id?: string; username?: string })[];
bypassers?: (
| { type: BypasserType.Group; id: string }
| { type: BypasserType.User; id?: string; username?: string }
)[];
projectSlug: string; projectSlug: string;
name: string; name: string;
enforcementLevel: EnforcementLevel; enforcementLevel: EnforcementLevel;
@@ -32,7 +41,11 @@ export type TCreateAccessApprovalPolicy = {
export type TUpdateAccessApprovalPolicy = { export type TUpdateAccessApprovalPolicy = {
policyId: string; policyId: string;
approvals?: number; approvals?: number;
approvers: ({ type: ApproverType.Group; id: string } | { type: ApproverType.User; id?: string; name?: string })[]; approvers: ({ type: ApproverType.Group; id: string } | { type: ApproverType.User; id?: string; username?: string })[];
bypassers?: (
| { type: BypasserType.Group; id: string }
| { type: BypasserType.User; id?: string; username?: string }
)[];
secretPath?: string; secretPath?: string;
name?: string; name?: string;
enforcementLevel?: EnforcementLevel; enforcementLevel?: EnforcementLevel;

View File

@@ -1,7 +1,13 @@
import { Knex } from "knex"; import { Knex } from "knex";
import { TDbClient } from "@app/db"; import { TDbClient } from "@app/db";
import { AccessApprovalRequestsSchema, TableName, TAccessApprovalRequests, TUsers } from "@app/db/schemas"; import {
AccessApprovalRequestsSchema,
TableName,
TAccessApprovalRequests,
TUserGroupMembership,
TUsers
} from "@app/db/schemas";
import { DatabaseError } from "@app/lib/errors"; import { DatabaseError } from "@app/lib/errors";
import { ormify, selectAllTableCols, sqlNestRelationships, TFindFilter } from "@app/lib/knex"; import { ormify, selectAllTableCols, sqlNestRelationships, TFindFilter } from "@app/lib/knex";
@@ -28,12 +34,12 @@ export const accessApprovalRequestDALFactory = (db: TDbClient) => {
`${TableName.AccessApprovalRequest}.policyId`, `${TableName.AccessApprovalRequest}.policyId`,
`${TableName.AccessApprovalPolicy}.id` `${TableName.AccessApprovalPolicy}.id`
) )
.leftJoin( .leftJoin(
TableName.AccessApprovalRequestReviewer, TableName.AccessApprovalRequestReviewer,
`${TableName.AccessApprovalRequest}.id`, `${TableName.AccessApprovalRequest}.id`,
`${TableName.AccessApprovalRequestReviewer}.requestId` `${TableName.AccessApprovalRequestReviewer}.requestId`
) )
.leftJoin( .leftJoin(
TableName.AccessApprovalPolicyApprover, TableName.AccessApprovalPolicyApprover,
`${TableName.AccessApprovalPolicy}.id`, `${TableName.AccessApprovalPolicy}.id`,
@@ -46,6 +52,17 @@ export const accessApprovalRequestDALFactory = (db: TDbClient) => {
) )
.leftJoin(TableName.Users, `${TableName.UserGroupMembership}.userId`, `${TableName.Users}.id`) .leftJoin(TableName.Users, `${TableName.UserGroupMembership}.userId`, `${TableName.Users}.id`)
.leftJoin(
TableName.AccessApprovalPolicyBypasser,
`${TableName.AccessApprovalPolicy}.id`,
`${TableName.AccessApprovalPolicyBypasser}.policyId`
)
.leftJoin<TUserGroupMembership>(
db(TableName.UserGroupMembership).as("bypasserUserGroupMembership"),
`${TableName.AccessApprovalPolicyBypasser}.bypasserGroupId`,
`bypasserUserGroupMembership.groupId`
)
.join<TUsers>( .join<TUsers>(
db(TableName.Users).as("requestedByUser"), db(TableName.Users).as("requestedByUser"),
`${TableName.AccessApprovalRequest}.requestedByUserId`, `${TableName.AccessApprovalRequest}.requestedByUserId`,
@@ -69,6 +86,9 @@ export const accessApprovalRequestDALFactory = (db: TDbClient) => {
.select(db.ref("approverUserId").withSchema(TableName.AccessApprovalPolicyApprover)) .select(db.ref("approverUserId").withSchema(TableName.AccessApprovalPolicyApprover))
.select(db.ref("userId").withSchema(TableName.UserGroupMembership).as("approverGroupUserId")) .select(db.ref("userId").withSchema(TableName.UserGroupMembership).as("approverGroupUserId"))
.select(db.ref("bypasserUserId").withSchema(TableName.AccessApprovalPolicyBypasser))
.select(db.ref("userId").withSchema("bypasserUserGroupMembership").as("bypasserGroupUserId"))
.select( .select(
db.ref("projectId").withSchema(TableName.Environment), db.ref("projectId").withSchema(TableName.Environment),
db.ref("slug").withSchema(TableName.Environment).as("envSlug"), db.ref("slug").withSchema(TableName.Environment).as("envSlug"),
@@ -145,7 +165,7 @@ export const accessApprovalRequestDALFactory = (db: TDbClient) => {
} }
: null, : null,
isApproved: !!doc.policyDeletedAt || !!doc.privilegeId isApproved: !!doc.policyDeletedAt || !!doc.privilegeId || doc.status !== ApprovalStatus.PENDING
}), }),
childrenMapper: [ childrenMapper: [
{ {
@@ -158,6 +178,12 @@ export const accessApprovalRequestDALFactory = (db: TDbClient) => {
key: "approverGroupUserId", key: "approverGroupUserId",
label: "approvers" as const, label: "approvers" as const,
mapper: ({ approverGroupUserId }) => approverGroupUserId mapper: ({ approverGroupUserId }) => approverGroupUserId
},
{ key: "bypasserUserId", label: "bypassers" as const, mapper: ({ bypasserUserId }) => bypasserUserId },
{
key: "bypasserGroupUserId",
label: "bypassers" as const,
mapper: ({ bypasserGroupUserId }) => bypasserGroupUserId
} }
] ]
}); });
@@ -166,7 +192,7 @@ export const accessApprovalRequestDALFactory = (db: TDbClient) => {
return formattedDocs.map((doc) => ({ return formattedDocs.map((doc) => ({
...doc, ...doc,
policy: { ...doc.policy, approvers: doc.approvers } policy: { ...doc.policy, approvers: doc.approvers, bypassers: doc.bypassers }
})); }));
} catch (error) { } catch (error) {
throw new DatabaseError({ error, name: "FindRequestsWithPrivilege" }); throw new DatabaseError({ error, name: "FindRequestsWithPrivilege" });
@@ -193,7 +219,6 @@ export const accessApprovalRequestDALFactory = (db: TDbClient) => {
`${TableName.AccessApprovalPolicy}.id`, `${TableName.AccessApprovalPolicy}.id`,
`${TableName.AccessApprovalPolicyApprover}.policyId` `${TableName.AccessApprovalPolicyApprover}.policyId`
) )
.leftJoin<TUsers>( .leftJoin<TUsers>(
db(TableName.Users).as("accessApprovalPolicyApproverUser"), db(TableName.Users).as("accessApprovalPolicyApproverUser"),
`${TableName.AccessApprovalPolicyApprover}.approverUserId`, `${TableName.AccessApprovalPolicyApprover}.approverUserId`,
@@ -204,13 +229,33 @@ export const accessApprovalRequestDALFactory = (db: TDbClient) => {
`${TableName.AccessApprovalPolicyApprover}.approverGroupId`, `${TableName.AccessApprovalPolicyApprover}.approverGroupId`,
`${TableName.UserGroupMembership}.groupId` `${TableName.UserGroupMembership}.groupId`
) )
.leftJoin<TUsers>( .leftJoin<TUsers>(
db(TableName.Users).as("accessApprovalPolicyGroupApproverUser"), db(TableName.Users).as("accessApprovalPolicyGroupApproverUser"),
`${TableName.UserGroupMembership}.userId`, `${TableName.UserGroupMembership}.userId`,
"accessApprovalPolicyGroupApproverUser.id" "accessApprovalPolicyGroupApproverUser.id"
) )
.leftJoin(
TableName.AccessApprovalPolicyBypasser,
`${TableName.AccessApprovalPolicy}.id`,
`${TableName.AccessApprovalPolicyBypasser}.policyId`
)
.leftJoin<TUsers>(
db(TableName.Users).as("accessApprovalPolicyBypasserUser"),
`${TableName.AccessApprovalPolicyBypasser}.bypasserUserId`,
"accessApprovalPolicyBypasserUser.id"
)
.leftJoin<TUserGroupMembership>(
db(TableName.UserGroupMembership).as("bypasserUserGroupMembership"),
`${TableName.AccessApprovalPolicyBypasser}.bypasserGroupId`,
`bypasserUserGroupMembership.groupId`
)
.leftJoin<TUsers>(
db(TableName.Users).as("accessApprovalPolicyGroupBypasserUser"),
`bypasserUserGroupMembership.userId`,
"accessApprovalPolicyGroupBypasserUser.id"
)
.leftJoin( .leftJoin(
TableName.AccessApprovalRequestReviewer, TableName.AccessApprovalRequestReviewer,
`${TableName.AccessApprovalRequest}.id`, `${TableName.AccessApprovalRequest}.id`,
@@ -241,6 +286,18 @@ export const accessApprovalRequestDALFactory = (db: TDbClient) => {
tx.ref("firstName").withSchema("requestedByUser").as("requestedByUserFirstName"), tx.ref("firstName").withSchema("requestedByUser").as("requestedByUserFirstName"),
tx.ref("lastName").withSchema("requestedByUser").as("requestedByUserLastName"), tx.ref("lastName").withSchema("requestedByUser").as("requestedByUserLastName"),
// Bypassers
tx.ref("bypasserUserId").withSchema(TableName.AccessApprovalPolicyBypasser),
tx.ref("userId").withSchema("bypasserUserGroupMembership").as("bypasserGroupUserId"),
tx.ref("email").withSchema("accessApprovalPolicyBypasserUser").as("bypasserEmail"),
tx.ref("email").withSchema("accessApprovalPolicyGroupBypasserUser").as("bypasserGroupEmail"),
tx.ref("username").withSchema("accessApprovalPolicyBypasserUser").as("bypasserUsername"),
tx.ref("username").withSchema("accessApprovalPolicyGroupBypasserUser").as("bypasserGroupUsername"),
tx.ref("firstName").withSchema("accessApprovalPolicyBypasserUser").as("bypasserFirstName"),
tx.ref("firstName").withSchema("accessApprovalPolicyGroupBypasserUser").as("bypasserGroupFirstName"),
tx.ref("lastName").withSchema("accessApprovalPolicyBypasserUser").as("bypasserLastName"),
tx.ref("lastName").withSchema("accessApprovalPolicyGroupBypasserUser").as("bypasserGroupLastName"),
tx.ref("reviewerUserId").withSchema(TableName.AccessApprovalRequestReviewer), tx.ref("reviewerUserId").withSchema(TableName.AccessApprovalRequestReviewer),
tx.ref("status").withSchema(TableName.AccessApprovalRequestReviewer).as("reviewerStatus"), tx.ref("status").withSchema(TableName.AccessApprovalRequestReviewer).as("reviewerStatus"),
@@ -265,7 +322,7 @@ export const accessApprovalRequestDALFactory = (db: TDbClient) => {
try { try {
const sql = findQuery({ [`${TableName.AccessApprovalRequest}.id` as "id"]: id }, tx || db.replicaNode()); const sql = findQuery({ [`${TableName.AccessApprovalRequest}.id` as "id"]: id }, tx || db.replicaNode());
const docs = await sql; const docs = await sql;
const formatedDoc = sqlNestRelationships({ const formattedDoc = sqlNestRelationships({
data: docs, data: docs,
key: "id", key: "id",
parentMapper: (el) => ({ parentMapper: (el) => ({
@@ -335,13 +392,51 @@ export const accessApprovalRequestDALFactory = (db: TDbClient) => {
lastName, lastName,
username username
}) })
},
{
key: "bypasserUserId",
label: "bypassers" as const,
mapper: ({
bypasserUserId,
bypasserEmail: email,
bypasserUsername: username,
bypasserLastName: lastName,
bypasserFirstName: firstName
}) => ({
userId: bypasserUserId,
email,
firstName,
lastName,
username
})
},
{
key: "bypasserGroupUserId",
label: "bypassers" as const,
mapper: ({
userId,
bypasserGroupEmail: email,
bypasserGroupUsername: username,
bypasserGroupLastName: lastName,
bypasserFirstName: firstName
}) => ({
userId,
email,
firstName,
lastName,
username
})
} }
] ]
}); });
if (!formatedDoc?.[0]) return; if (!formattedDoc?.[0]) return;
return { return {
...formatedDoc[0], ...formattedDoc[0],
policy: { ...formatedDoc[0].policy, approvers: formatedDoc[0].approvers } policy: {
...formattedDoc[0].policy,
approvers: formattedDoc[0].approvers,
bypassers: formattedDoc[0].bypassers
}
}; };
} catch (error) { } catch (error) {
throw new DatabaseError({ error, name: "FindByIdAccessApprovalRequest" }); throw new DatabaseError({ error, name: "FindByIdAccessApprovalRequest" });
@@ -392,14 +487,20 @@ export const accessApprovalRequestDALFactory = (db: TDbClient) => {
] ]
}); });
// an approval is pending if there is no reviewer rejections and no privilege ID is set // an approval is pending if there is no reviewer rejections, no privilege ID is set and the status is pending
const pendingApprovals = formattedRequests.filter( const pendingApprovals = formattedRequests.filter(
(req) => !req.privilegeId && !req.reviewers.some((r) => r.status === ApprovalStatus.REJECTED) (req) =>
!req.privilegeId &&
!req.reviewers.some((r) => r.status === ApprovalStatus.REJECTED) &&
req.status === ApprovalStatus.PENDING
); );
// an approval is finalized if there are any rejections or a privilege ID is set // an approval is finalized if there are any rejections, a privilege ID is set or the number of approvals is equal to the number of approvals required
const finalizedApprovals = formattedRequests.filter( const finalizedApprovals = formattedRequests.filter(
(req) => req.privilegeId || req.reviewers.some((r) => r.status === ApprovalStatus.REJECTED) (req) =>
req.privilegeId ||
req.reviewers.some((r) => r.status === ApprovalStatus.REJECTED) ||
req.status !== ApprovalStatus.PENDING
); );
return { pendingCount: pendingApprovals.length, finalizedCount: finalizedApprovals.length }; return { pendingCount: pendingApprovals.length, finalizedCount: finalizedApprovals.length };

View File

@@ -23,7 +23,6 @@ import { TAccessApprovalPolicyApproverDALFactory } from "../access-approval-poli
import { TAccessApprovalPolicyDALFactory } from "../access-approval-policy/access-approval-policy-dal"; import { TAccessApprovalPolicyDALFactory } from "../access-approval-policy/access-approval-policy-dal";
import { TGroupDALFactory } from "../group/group-dal"; import { TGroupDALFactory } from "../group/group-dal";
import { TPermissionServiceFactory } from "../permission/permission-service"; import { TPermissionServiceFactory } from "../permission/permission-service";
import { ProjectPermissionApprovalActions, ProjectPermissionSub } from "../permission/project-permission";
import { TProjectUserAdditionalPrivilegeDALFactory } from "../project-user-additional-privilege/project-user-additional-privilege-dal"; import { TProjectUserAdditionalPrivilegeDALFactory } from "../project-user-additional-privilege/project-user-additional-privilege-dal";
import { ProjectUserAdditionalPrivilegeTemporaryMode } from "../project-user-additional-privilege/project-user-additional-privilege-types"; import { ProjectUserAdditionalPrivilegeTemporaryMode } from "../project-user-additional-privilege/project-user-additional-privilege-types";
import { TAccessApprovalRequestDALFactory } from "./access-approval-request-dal"; import { TAccessApprovalRequestDALFactory } from "./access-approval-request-dal";
@@ -57,7 +56,7 @@ type TSecretApprovalRequestServiceFactoryDep = {
| "findOne" | "findOne"
| "getCount" | "getCount"
>; >;
accessApprovalPolicyDAL: Pick<TAccessApprovalPolicyDALFactory, "findOne" | "find">; accessApprovalPolicyDAL: Pick<TAccessApprovalPolicyDALFactory, "findOne" | "find" | "findLastValidPolicy">;
accessApprovalRequestReviewerDAL: Pick< accessApprovalRequestReviewerDAL: Pick<
TAccessApprovalRequestReviewerDALFactory, TAccessApprovalRequestReviewerDALFactory,
"create" | "find" | "findOne" | "transaction" "create" | "find" | "findOne" | "transaction"
@@ -132,7 +131,7 @@ export const accessApprovalRequestServiceFactory = ({
if (!environment) throw new NotFoundError({ message: `Environment with slug '${envSlug}' not found` }); if (!environment) throw new NotFoundError({ message: `Environment with slug '${envSlug}' not found` });
const policy = await accessApprovalPolicyDAL.findOne({ const policy = await accessApprovalPolicyDAL.findLastValidPolicy({
envId: environment.id, envId: environment.id,
secretPath secretPath
}); });
@@ -204,7 +203,7 @@ export const accessApprovalRequestServiceFactory = ({
const isRejected = reviewers.some((reviewer) => reviewer.status === ApprovalStatus.REJECTED); const isRejected = reviewers.some((reviewer) => reviewer.status === ApprovalStatus.REJECTED);
if (!isRejected) { if (!isRejected && duplicateRequest.status === ApprovalStatus.PENDING) {
throw new BadRequestError({ message: "You already have a pending access request with the same criteria" }); throw new BadRequestError({ message: "You already have a pending access request with the same criteria" });
} }
} }
@@ -340,7 +339,7 @@ export const accessApprovalRequestServiceFactory = ({
}); });
} }
const { membership, hasRole, permission } = await permissionService.getProjectPermission({ const { membership, hasRole } = await permissionService.getProjectPermission({
actor, actor,
actorId, actorId,
projectId: accessApprovalRequest.projectId, projectId: accessApprovalRequest.projectId,
@@ -355,13 +354,13 @@ export const accessApprovalRequestServiceFactory = ({
const isSelfApproval = actorId === accessApprovalRequest.requestedByUserId; const isSelfApproval = actorId === accessApprovalRequest.requestedByUserId;
const isSoftEnforcement = policy.enforcementLevel === EnforcementLevel.Soft; const isSoftEnforcement = policy.enforcementLevel === EnforcementLevel.Soft;
const canBypassApproval = permission.can( const canBypass = !policy.bypassers.length || policy.bypassers.some((bypasser) => bypasser.userId === actorId);
ProjectPermissionApprovalActions.AllowAccessBypass, const cannotBypassUnderSoftEnforcement = !(isSoftEnforcement && canBypass);
ProjectPermissionSub.SecretApproval
);
const cannotBypassUnderSoftEnforcement = !(isSoftEnforcement && canBypassApproval);
if (!policy.allowedSelfApprovals && isSelfApproval && cannotBypassUnderSoftEnforcement) { const isApprover = policy.approvers.find((approver) => approver.userId === actorId);
// If user is (not an approver OR cant self approve) AND can't bypass policy
if ((!isApprover || (!policy.allowedSelfApprovals && isSelfApproval)) && cannotBypassUnderSoftEnforcement) {
throw new BadRequestError({ throw new BadRequestError({
message: "Failed to review access approval request. Users are not authorized to review their own request." message: "Failed to review access approval request. Users are not authorized to review their own request."
}); });
@@ -370,7 +369,7 @@ export const accessApprovalRequestServiceFactory = ({
if ( if (
!hasRole(ProjectMembershipRole.Admin) && !hasRole(ProjectMembershipRole.Admin) &&
accessApprovalRequest.requestedByUserId !== actorId && // The request wasn't made by the current user accessApprovalRequest.requestedByUserId !== actorId && // The request wasn't made by the current user
!policy.approvers.find((approver) => approver.userId === actorId) // The request isn't performed by an assigned approver !isApprover // The request isn't performed by an assigned approver
) { ) {
throw new ForbiddenRequestError({ message: "You are not authorized to approve this request" }); throw new ForbiddenRequestError({ message: "You are not authorized to approve this request" });
} }
@@ -478,7 +477,11 @@ export const accessApprovalRequestServiceFactory = ({
); );
privilegeIdToSet = privilege.id; privilegeIdToSet = privilege.id;
} }
await accessApprovalRequestDAL.updateById(accessApprovalRequest.id, { privilegeId: privilegeIdToSet }, tx); await accessApprovalRequestDAL.updateById(
accessApprovalRequest.id,
{ privilegeId: privilegeIdToSet, status: ApprovalStatus.APPROVED },
tx
);
} }
} }

View File

@@ -132,7 +132,11 @@ export const dynamicSecretLeaseServiceFactory = ({
let result; let result;
try { try {
result = await selectedProvider.create(decryptedStoredInput, expireAt.getTime()); result = await selectedProvider.create({
inputs: decryptedStoredInput,
expireAt: expireAt.getTime(),
usernameTemplate: dynamicSecretCfg.usernameTemplate
});
} catch (error: unknown) { } catch (error: unknown) {
if (error && typeof error === "object" && error !== null && "sqlMessage" in error) { if (error && typeof error === "object" && error !== null && "sqlMessage" in error) {
throw new BadRequestError({ message: error.sqlMessage as string }); throw new BadRequestError({ message: error.sqlMessage as string });

View File

@@ -11,6 +11,8 @@ export const verifyHostInputValidity = async (host: string, isGateway = false) =
if (appCfg.isDevelopmentMode) return [host]; if (appCfg.isDevelopmentMode) return [host];
if (isGateway) return [host];
const reservedHosts = [appCfg.DB_HOST || getDbConnectionHost(appCfg.DB_CONNECTION_URI)].concat( const reservedHosts = [appCfg.DB_HOST || getDbConnectionHost(appCfg.DB_CONNECTION_URI)].concat(
(appCfg.DB_READ_REPLICAS || []).map((el) => getDbConnectionHost(el.DB_CONNECTION_URI)), (appCfg.DB_READ_REPLICAS || []).map((el) => getDbConnectionHost(el.DB_CONNECTION_URI)),
getDbConnectionHost(appCfg.REDIS_URL), getDbConnectionHost(appCfg.REDIS_URL),
@@ -58,7 +60,7 @@ export const verifyHostInputValidity = async (host: string, isGateway = false) =
} }
} }
if (!isGateway && !(appCfg.DYNAMIC_SECRET_ALLOW_INTERNAL_IP || appCfg.ALLOW_INTERNAL_IP_CONNECTIONS)) { if (!(appCfg.DYNAMIC_SECRET_ALLOW_INTERNAL_IP || appCfg.ALLOW_INTERNAL_IP_CONNECTIONS)) {
const isInternalIp = inputHostIps.some((el) => isPrivateIp(el)); const isInternalIp = inputHostIps.some((el) => isPrivateIp(el));
if (isInternalIp) throw new BadRequestError({ message: "Invalid db host" }); if (isInternalIp) throw new BadRequestError({ message: "Invalid db host" });
} }

View File

@@ -78,7 +78,8 @@ export const dynamicSecretServiceFactory = ({
actorOrgId, actorOrgId,
defaultTTL, defaultTTL,
actorAuthMethod, actorAuthMethod,
metadata metadata,
usernameTemplate
}: TCreateDynamicSecretDTO) => { }: TCreateDynamicSecretDTO) => {
const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId); const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId);
if (!project) throw new NotFoundError({ message: `Project with slug '${projectSlug}' not found` }); if (!project) throw new NotFoundError({ message: `Project with slug '${projectSlug}' not found` });
@@ -163,7 +164,8 @@ export const dynamicSecretServiceFactory = ({
defaultTTL, defaultTTL,
folderId: folder.id, folderId: folder.id,
name, name,
gatewayId: selectedGatewayId gatewayId: selectedGatewayId,
usernameTemplate
}, },
tx tx
); );
@@ -199,7 +201,8 @@ export const dynamicSecretServiceFactory = ({
newName, newName,
actorOrgId, actorOrgId,
actorAuthMethod, actorAuthMethod,
metadata metadata,
usernameTemplate
}: TUpdateDynamicSecretDTO) => { }: TUpdateDynamicSecretDTO) => {
const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId); const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId);
if (!project) throw new NotFoundError({ message: `Project with slug '${projectSlug}' not found` }); if (!project) throw new NotFoundError({ message: `Project with slug '${projectSlug}' not found` });
@@ -311,7 +314,8 @@ export const dynamicSecretServiceFactory = ({
defaultTTL, defaultTTL,
name: newName ?? name, name: newName ?? name,
status: null, status: null,
gatewayId: selectedGatewayId gatewayId: selectedGatewayId,
usernameTemplate
}, },
tx tx
); );

View File

@@ -22,6 +22,7 @@ export type TCreateDynamicSecretDTO = {
name: string; name: string;
projectSlug: string; projectSlug: string;
metadata?: ResourceMetadataDTO; metadata?: ResourceMetadataDTO;
usernameTemplate?: string | null;
} & Omit<TProjectPermission, "projectId">; } & Omit<TProjectPermission, "projectId">;
export type TUpdateDynamicSecretDTO = { export type TUpdateDynamicSecretDTO = {
@@ -34,6 +35,7 @@ export type TUpdateDynamicSecretDTO = {
inputs?: TProvider["inputs"]; inputs?: TProvider["inputs"];
projectSlug: string; projectSlug: string;
metadata?: ResourceMetadataDTO; metadata?: ResourceMetadataDTO;
usernameTemplate?: string | null;
} & Omit<TProjectPermission, "projectId">; } & Omit<TProjectPermission, "projectId">;
export type TDeleteDynamicSecretDTO = { export type TDeleteDynamicSecretDTO = {

View File

@@ -132,9 +132,15 @@ const generatePassword = () => {
return customAlphabet(charset, 64)(); return customAlphabet(charset, 64)();
}; };
const generateUsername = () => { const generateUsername = (usernameTemplate?: string | null) => {
const charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-"; const charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-";
return `inf-${customAlphabet(charset, 32)()}`; // Username must start with an ascii letter, so we prepend the username with "inf-" const randomUsername = `inf-${customAlphabet(charset, 32)()}`;
if (!usernameTemplate) return randomUsername;
return handlebars.compile(usernameTemplate)({
randomUsername,
unixTimestamp: Math.floor(Date.now() / 100)
});
}; };
export const AwsElastiCacheDatabaseProvider = (): TDynamicProviderFns => { export const AwsElastiCacheDatabaseProvider = (): TDynamicProviderFns => {
@@ -168,13 +174,14 @@ export const AwsElastiCacheDatabaseProvider = (): TDynamicProviderFns => {
return true; return true;
}; };
const create = async (inputs: unknown, expireAt: number) => { const create = async (data: { inputs: unknown; expireAt: number; usernameTemplate?: string | null }) => {
const { inputs, expireAt, usernameTemplate } = data;
const providerInputs = await validateProviderInputs(inputs); const providerInputs = await validateProviderInputs(inputs);
if (!(await validateConnection(providerInputs))) { if (!(await validateConnection(providerInputs))) {
throw new BadRequestError({ message: "Failed to establish connection" }); throw new BadRequestError({ message: "Failed to establish connection" });
} }
const leaseUsername = generateUsername(); const leaseUsername = generateUsername(usernameTemplate);
const leasePassword = generatePassword(); const leasePassword = generatePassword();
const leaseExpiration = new Date(expireAt).toISOString(); const leaseExpiration = new Date(expireAt).toISOString();

View File

@@ -16,6 +16,7 @@ import {
PutUserPolicyCommand, PutUserPolicyCommand,
RemoveUserFromGroupCommand RemoveUserFromGroupCommand
} from "@aws-sdk/client-iam"; } from "@aws-sdk/client-iam";
import handlebars from "handlebars";
import { z } from "zod"; import { z } from "zod";
import { BadRequestError } from "@app/lib/errors"; import { BadRequestError } from "@app/lib/errors";
@@ -23,8 +24,14 @@ import { alphaNumericNanoId } from "@app/lib/nanoid";
import { DynamicSecretAwsIamSchema, TDynamicProviderFns } from "./models"; import { DynamicSecretAwsIamSchema, TDynamicProviderFns } from "./models";
const generateUsername = () => { const generateUsername = (usernameTemplate?: string | null) => {
return alphaNumericNanoId(32); const randomUsername = alphaNumericNanoId(32);
if (!usernameTemplate) return randomUsername;
return handlebars.compile(usernameTemplate)({
randomUsername,
unixTimestamp: Math.floor(Date.now() / 100)
});
}; };
export const AwsIamProvider = (): TDynamicProviderFns => { export const AwsIamProvider = (): TDynamicProviderFns => {
@@ -53,11 +60,13 @@ export const AwsIamProvider = (): TDynamicProviderFns => {
return isConnected; return isConnected;
}; };
const create = async (inputs: unknown) => { const create = async (data: { inputs: unknown; expireAt: number; usernameTemplate?: string | null }) => {
const { inputs, usernameTemplate } = data;
const providerInputs = await validateProviderInputs(inputs); const providerInputs = await validateProviderInputs(inputs);
const client = await $getClient(providerInputs); const client = await $getClient(providerInputs);
const username = generateUsername(); const username = generateUsername(usernameTemplate);
const { policyArns, userGroups, policyDocument, awsPath, permissionBoundaryPolicyArn } = providerInputs; const { policyArns, userGroups, policyDocument, awsPath, permissionBoundaryPolicyArn } = providerInputs;
const createUserRes = await client.send( const createUserRes = await client.send(
new CreateUserCommand({ new CreateUserCommand({

View File

@@ -55,7 +55,7 @@ export const AzureEntraIDProvider = (): TDynamicProviderFns & {
return data.success; return data.success;
}; };
const create = async (inputs: unknown) => { const create = async ({ inputs }: { inputs: unknown }) => {
const providerInputs = await validateProviderInputs(inputs); const providerInputs = await validateProviderInputs(inputs);
const data = await $getToken(providerInputs.tenantId, providerInputs.applicationId, providerInputs.clientSecret); const data = await $getToken(providerInputs.tenantId, providerInputs.applicationId, providerInputs.clientSecret);
if (!data.success) { if (!data.success) {
@@ -88,7 +88,7 @@ export const AzureEntraIDProvider = (): TDynamicProviderFns & {
const revoke = async (inputs: unknown, entityId: string) => { const revoke = async (inputs: unknown, entityId: string) => {
// Creates a new password // Creates a new password
await create(inputs); await create({ inputs });
return { entityId }; return { entityId };
}; };

View File

@@ -14,8 +14,14 @@ const generatePassword = (size = 48) => {
return customAlphabet(charset, 48)(size); return customAlphabet(charset, 48)(size);
}; };
const generateUsername = () => { const generateUsername = (usernameTemplate?: string | null) => {
return alphaNumericNanoId(32); const randomUsername = alphaNumericNanoId(32); // Username must start with an ascii letter, so we prepend the username with "inf-"
if (!usernameTemplate) return randomUsername;
return handlebars.compile(usernameTemplate)({
randomUsername,
unixTimestamp: Math.floor(Date.now() / 100)
});
}; };
export const CassandraProvider = (): TDynamicProviderFns => { export const CassandraProvider = (): TDynamicProviderFns => {
@@ -69,11 +75,12 @@ export const CassandraProvider = (): TDynamicProviderFns => {
return isConnected; return isConnected;
}; };
const create = async (inputs: unknown, expireAt: number) => { const create = async (data: { inputs: unknown; expireAt: number; usernameTemplate?: string | null }) => {
const { inputs, expireAt, usernameTemplate } = data;
const providerInputs = await validateProviderInputs(inputs); const providerInputs = await validateProviderInputs(inputs);
const client = await $getClient(providerInputs); const client = await $getClient(providerInputs);
const username = generateUsername(); const username = generateUsername(usernameTemplate);
const password = generatePassword(); const password = generatePassword();
const { keyspace } = providerInputs; const { keyspace } = providerInputs;
const expiration = new Date(expireAt).toISOString(); const expiration = new Date(expireAt).toISOString();

View File

@@ -1,4 +1,5 @@
import { Client as ElasticSearchClient } from "@elastic/elasticsearch"; import { Client as ElasticSearchClient } from "@elastic/elasticsearch";
import handlebars from "handlebars";
import { customAlphabet } from "nanoid"; import { customAlphabet } from "nanoid";
import { z } from "zod"; import { z } from "zod";
@@ -12,8 +13,14 @@ const generatePassword = () => {
return customAlphabet(charset, 64)(); return customAlphabet(charset, 64)();
}; };
const generateUsername = () => { const generateUsername = (usernameTemplate?: string | null) => {
return alphaNumericNanoId(32); const randomUsername = alphaNumericNanoId(32); // Username must start with an ascii letter, so we prepend the username with "inf-"
if (!usernameTemplate) return randomUsername;
return handlebars.compile(usernameTemplate)({
randomUsername,
unixTimestamp: Math.floor(Date.now() / 100)
});
}; };
export const ElasticSearchProvider = (): TDynamicProviderFns => { export const ElasticSearchProvider = (): TDynamicProviderFns => {
@@ -64,11 +71,12 @@ export const ElasticSearchProvider = (): TDynamicProviderFns => {
return infoResponse; return infoResponse;
}; };
const create = async (inputs: unknown) => { const create = async (data: { inputs: unknown; usernameTemplate?: string | null }) => {
const { inputs, usernameTemplate } = data;
const providerInputs = await validateProviderInputs(inputs); const providerInputs = await validateProviderInputs(inputs);
const connection = await $getClient(providerInputs); const connection = await $getClient(providerInputs);
const username = generateUsername(); const username = generateUsername(usernameTemplate);
const password = generatePassword(); const password = generatePassword();
await connection.security.putUser({ await connection.security.putUser({

View File

@@ -116,7 +116,7 @@ export const KubernetesProvider = ({ gatewayService }: TKubernetesProviderDTO):
} }
}; };
const create = async (inputs: unknown, expireAt: number) => { const create = async ({ inputs, expireAt }: { inputs: unknown; expireAt: number }) => {
const providerInputs = await validateProviderInputs(inputs); const providerInputs = await validateProviderInputs(inputs);
const tokenRequestCallback = async (host: string, port: number) => { const tokenRequestCallback = async (host: string, port: number) => {

View File

@@ -22,8 +22,14 @@ const encodePassword = (password?: string) => {
return base64Password; return base64Password;
}; };
const generateUsername = () => { const generateUsername = (usernameTemplate?: string | null) => {
return alphaNumericNanoId(20); const randomUsername = alphaNumericNanoId(32); // Username must start with an ascii letter, so we prepend the username with "inf-"
if (!usernameTemplate) return randomUsername;
return handlebars.compile(usernameTemplate)({
randomUsername,
unixTimestamp: Math.floor(Date.now() / 100)
});
}; };
const generateLDIF = ({ const generateLDIF = ({
@@ -190,7 +196,8 @@ export const LdapProvider = (): TDynamicProviderFns => {
return dnArray; return dnArray;
}; };
const create = async (inputs: unknown) => { const create = async (data: { inputs: unknown; usernameTemplate?: string | null }) => {
const { inputs, usernameTemplate } = data;
const providerInputs = await validateProviderInputs(inputs); const providerInputs = await validateProviderInputs(inputs);
const client = await $getClient(providerInputs); const client = await $getClient(providerInputs);
@@ -217,7 +224,7 @@ export const LdapProvider = (): TDynamicProviderFns => {
}); });
} }
} else { } else {
const username = generateUsername(); const username = generateUsername(usernameTemplate);
const password = generatePassword(); const password = generatePassword();
const generatedLdif = generateLDIF({ username, password, ldifTemplate: providerInputs.creationLdif }); const generatedLdif = generateLDIF({ username, password, ldifTemplate: providerInputs.creationLdif });

View File

@@ -360,7 +360,11 @@ export const DynamicSecretProviderSchema = z.discriminatedUnion("type", [
]); ]);
export type TDynamicProviderFns = { export type TDynamicProviderFns = {
create: (inputs: unknown, expireAt: number) => Promise<{ entityId: string; data: unknown }>; create: (arg: {
inputs: unknown;
expireAt: number;
usernameTemplate?: string | null;
}) => Promise<{ entityId: string; data: unknown }>;
validateConnection: (inputs: unknown) => Promise<boolean>; validateConnection: (inputs: unknown) => Promise<boolean>;
validateProviderInputs: (inputs: object) => Promise<unknown>; validateProviderInputs: (inputs: object) => Promise<unknown>;
revoke: (inputs: unknown, entityId: string) => Promise<{ entityId: string }>; revoke: (inputs: unknown, entityId: string) => Promise<{ entityId: string }>;

View File

@@ -1,4 +1,5 @@
import axios, { AxiosError } from "axios"; import axios, { AxiosError } from "axios";
import handlebars from "handlebars";
import { customAlphabet } from "nanoid"; import { customAlphabet } from "nanoid";
import { z } from "zod"; import { z } from "zod";
@@ -12,8 +13,14 @@ const generatePassword = (size = 48) => {
return customAlphabet(charset, 48)(size); return customAlphabet(charset, 48)(size);
}; };
const generateUsername = () => { const generateUsername = (usernameTemplate?: string | null) => {
return alphaNumericNanoId(32); const randomUsername = alphaNumericNanoId(32);
if (!usernameTemplate) return randomUsername;
return handlebars.compile(usernameTemplate)({
randomUsername,
unixTimestamp: Math.floor(Date.now() / 100)
});
}; };
export const MongoAtlasProvider = (): TDynamicProviderFns => { export const MongoAtlasProvider = (): TDynamicProviderFns => {
@@ -57,11 +64,12 @@ export const MongoAtlasProvider = (): TDynamicProviderFns => {
return isConnected; return isConnected;
}; };
const create = async (inputs: unknown, expireAt: number) => { const create = async (data: { inputs: unknown; expireAt: number; usernameTemplate?: string | null }) => {
const { inputs, expireAt, usernameTemplate } = data;
const providerInputs = await validateProviderInputs(inputs); const providerInputs = await validateProviderInputs(inputs);
const client = await $getClient(providerInputs); const client = await $getClient(providerInputs);
const username = generateUsername(); const username = generateUsername(usernameTemplate);
const password = generatePassword(); const password = generatePassword();
const expiration = new Date(expireAt).toISOString(); const expiration = new Date(expireAt).toISOString();
await client({ await client({

View File

@@ -1,3 +1,4 @@
import handlebars from "handlebars";
import { MongoClient } from "mongodb"; import { MongoClient } from "mongodb";
import { customAlphabet } from "nanoid"; import { customAlphabet } from "nanoid";
import { z } from "zod"; import { z } from "zod";
@@ -12,8 +13,14 @@ const generatePassword = (size = 48) => {
return customAlphabet(charset, 48)(size); return customAlphabet(charset, 48)(size);
}; };
const generateUsername = () => { const generateUsername = (usernameTemplate?: string | null) => {
return alphaNumericNanoId(32); const randomUsername = alphaNumericNanoId(32);
if (!usernameTemplate) return randomUsername;
return handlebars.compile(usernameTemplate)({
randomUsername,
unixTimestamp: Math.floor(Date.now() / 100)
});
}; };
export const MongoDBProvider = (): TDynamicProviderFns => { export const MongoDBProvider = (): TDynamicProviderFns => {
@@ -53,11 +60,12 @@ export const MongoDBProvider = (): TDynamicProviderFns => {
return isConnected; return isConnected;
}; };
const create = async (inputs: unknown) => { const create = async (data: { inputs: unknown; usernameTemplate?: string | null }) => {
const { inputs, usernameTemplate } = data;
const providerInputs = await validateProviderInputs(inputs); const providerInputs = await validateProviderInputs(inputs);
const client = await $getClient(providerInputs); const client = await $getClient(providerInputs);
const username = generateUsername(); const username = generateUsername(usernameTemplate);
const password = generatePassword(); const password = generatePassword();
const db = client.db(providerInputs.database); const db = client.db(providerInputs.database);

View File

@@ -1,4 +1,5 @@
import axios, { Axios } from "axios"; import axios, { Axios } from "axios";
import handlebars from "handlebars";
import https from "https"; import https from "https";
import { customAlphabet } from "nanoid"; import { customAlphabet } from "nanoid";
import { z } from "zod"; import { z } from "zod";
@@ -14,8 +15,14 @@ const generatePassword = () => {
return customAlphabet(charset, 64)(); return customAlphabet(charset, 64)();
}; };
const generateUsername = () => { const generateUsername = (usernameTemplate?: string | null) => {
return alphaNumericNanoId(32); const randomUsername = alphaNumericNanoId(32); // Username must start with an ascii letter, so we prepend the username with "inf-"
if (!usernameTemplate) return randomUsername;
return handlebars.compile(usernameTemplate)({
randomUsername,
unixTimestamp: Math.floor(Date.now() / 100)
});
}; };
type TCreateRabbitMQUser = { type TCreateRabbitMQUser = {
@@ -110,11 +117,12 @@ export const RabbitMqProvider = (): TDynamicProviderFns => {
return infoResponse; return infoResponse;
}; };
const create = async (inputs: unknown) => { const create = async (data: { inputs: unknown; usernameTemplate?: string | null }) => {
const { inputs, usernameTemplate } = data;
const providerInputs = await validateProviderInputs(inputs); const providerInputs = await validateProviderInputs(inputs);
const connection = await $getClient(providerInputs); const connection = await $getClient(providerInputs);
const username = generateUsername(); const username = generateUsername(usernameTemplate);
const password = generatePassword(); const password = generatePassword();
await createRabbitMqUser({ await createRabbitMqUser({

View File

@@ -15,8 +15,14 @@ const generatePassword = () => {
return customAlphabet(charset, 64)(); return customAlphabet(charset, 64)();
}; };
const generateUsername = () => { const generateUsername = (usernameTemplate?: string | null) => {
return alphaNumericNanoId(32); const randomUsername = alphaNumericNanoId(32); // Username must start with an ascii letter, so we prepend the username with "inf-"
if (!usernameTemplate) return randomUsername;
return handlebars.compile(usernameTemplate)({
randomUsername,
unixTimestamp: Math.floor(Date.now() / 100)
});
}; };
const executeTransactions = async (connection: Redis, commands: string[]): Promise<(string | null)[] | null> => { const executeTransactions = async (connection: Redis, commands: string[]): Promise<(string | null)[] | null> => {
@@ -115,11 +121,12 @@ export const RedisDatabaseProvider = (): TDynamicProviderFns => {
return pingResponse; return pingResponse;
}; };
const create = async (inputs: unknown, expireAt: number) => { const create = async (data: { inputs: unknown; expireAt: number; usernameTemplate?: string | null }) => {
const { inputs, expireAt, usernameTemplate } = data;
const providerInputs = await validateProviderInputs(inputs); const providerInputs = await validateProviderInputs(inputs);
const connection = await $getClient(providerInputs); const connection = await $getClient(providerInputs);
const username = generateUsername(); const username = generateUsername(usernameTemplate);
const password = generatePassword(); const password = generatePassword();
const expiration = new Date(expireAt).toISOString(); const expiration = new Date(expireAt).toISOString();

View File

@@ -15,8 +15,14 @@ const generatePassword = (size = 48) => {
return customAlphabet(charset, 48)(size); return customAlphabet(charset, 48)(size);
}; };
const generateUsername = () => { const generateUsername = (usernameTemplate?: string | null) => {
return alphaNumericNanoId(25); const randomUsername = `inf_${alphaNumericNanoId(25)}`; // Username must start with an ascii letter, so we prepend the username with "inf-"
if (!usernameTemplate) return randomUsername;
return handlebars.compile(usernameTemplate)({
randomUsername,
unixTimestamp: Math.floor(Date.now() / 100)
});
}; };
enum SapCommands { enum SapCommands {
@@ -81,11 +87,12 @@ export const SapAseProvider = (): TDynamicProviderFns => {
return true; return true;
}; };
const create = async (inputs: unknown) => { const create = async (data: { inputs: unknown; usernameTemplate?: string | null }) => {
const { inputs, usernameTemplate } = data;
const providerInputs = await validateProviderInputs(inputs); const providerInputs = await validateProviderInputs(inputs);
const username = `inf_${generateUsername()}`; const username = generateUsername(usernameTemplate);
const password = `${generatePassword()}`; const password = generatePassword();
const client = await $getClient(providerInputs); const client = await $getClient(providerInputs);
const masterClient = await $getClient(providerInputs, true); const masterClient = await $getClient(providerInputs, true);

View File

@@ -21,8 +21,14 @@ const generatePassword = (size = 48) => {
return customAlphabet(charset, 48)(size); return customAlphabet(charset, 48)(size);
}; };
const generateUsername = () => { const generateUsername = (usernameTemplate?: string | null) => {
return alphaNumericNanoId(32); const randomUsername = alphaNumericNanoId(32); // Username must start with an ascii letter, so we prepend the username with "inf-"
if (!usernameTemplate) return randomUsername;
return handlebars.compile(usernameTemplate)({
randomUsername,
unixTimestamp: Math.floor(Date.now() / 100)
});
}; };
export const SapHanaProvider = (): TDynamicProviderFns => { export const SapHanaProvider = (): TDynamicProviderFns => {
@@ -91,10 +97,11 @@ export const SapHanaProvider = (): TDynamicProviderFns => {
return testResult; return testResult;
}; };
const create = async (inputs: unknown, expireAt: number) => { const create = async (data: { inputs: unknown; expireAt: number; usernameTemplate?: string | null }) => {
const { inputs, expireAt, usernameTemplate } = data;
const providerInputs = await validateProviderInputs(inputs); const providerInputs = await validateProviderInputs(inputs);
const username = generateUsername(); const username = generateUsername(usernameTemplate);
const password = generatePassword(); const password = generatePassword();
const expiration = new Date(expireAt).toISOString(); const expiration = new Date(expireAt).toISOString();

View File

@@ -17,8 +17,14 @@ const generatePassword = (size = 48) => {
return customAlphabet(charset, 48)(size); return customAlphabet(charset, 48)(size);
}; };
const generateUsername = () => { const generateUsername = (usernameTemplate?: string | null) => {
return `infisical_${alphaNumericNanoId(32)}`; // username must start with alpha character, hence prefix const randomUsername = `infisical_${alphaNumericNanoId(32)}`; // Username must start with an ascii letter, so we prepend the username with "inf-"
if (!usernameTemplate) return randomUsername;
return handlebars.compile(usernameTemplate)({
randomUsername,
unixTimestamp: Math.floor(Date.now() / 100)
});
}; };
const getDaysToExpiry = (expiryDate: Date) => { const getDaysToExpiry = (expiryDate: Date) => {
@@ -82,12 +88,13 @@ export const SnowflakeProvider = (): TDynamicProviderFns => {
return isValidConnection; return isValidConnection;
}; };
const create = async (inputs: unknown, expireAt: number) => { const create = async (data: { inputs: unknown; expireAt: number; usernameTemplate?: string | null }) => {
const { inputs, expireAt, usernameTemplate } = data;
const providerInputs = await validateProviderInputs(inputs); const providerInputs = await validateProviderInputs(inputs);
const client = await $getClient(providerInputs); const client = await $getClient(providerInputs);
const username = generateUsername(); const username = generateUsername(usernameTemplate);
const password = generatePassword(); const password = generatePassword();
try { try {

View File

@@ -104,11 +104,21 @@ const generatePassword = (provider: SqlProviders, requirements?: PasswordRequire
} }
}; };
const generateUsername = (provider: SqlProviders) => { const generateUsername = (provider: SqlProviders, usernameTemplate?: string | null) => {
// For oracle, the client assumes everything is upper case when not using quotes around the password let randomUsername = "";
if (provider === SqlProviders.Oracle) return alphaNumericNanoId(32).toUpperCase();
return alphaNumericNanoId(32); // For oracle, the client assumes everything is upper case when not using quotes around the password
if (provider === SqlProviders.Oracle) {
randomUsername = alphaNumericNanoId(32).toUpperCase();
} else {
randomUsername = alphaNumericNanoId(32);
}
if (!usernameTemplate) return randomUsername;
return handlebars.compile(usernameTemplate)({
randomUsername,
unixTimestamp: Math.floor(Date.now() / 100)
});
}; };
type TSqlDatabaseProviderDTO = { type TSqlDatabaseProviderDTO = {
@@ -210,9 +220,12 @@ export const SqlDatabaseProvider = ({ gatewayService }: TSqlDatabaseProviderDTO)
return isConnected; return isConnected;
}; };
const create = async (inputs: unknown, expireAt: number) => { const create = async (data: { inputs: unknown; expireAt: number; usernameTemplate?: string | null }) => {
const { inputs, expireAt, usernameTemplate } = data;
const providerInputs = await validateProviderInputs(inputs); const providerInputs = await validateProviderInputs(inputs);
const username = generateUsername(providerInputs.client); const username = generateUsername(providerInputs.client, usernameTemplate);
const password = generatePassword(providerInputs.client, providerInputs.passwordRequirements); const password = generatePassword(providerInputs.client, providerInputs.passwordRequirements);
const gatewayCallback = async (host = providerInputs.host, port = providerInputs.port) => { const gatewayCallback = async (host = providerInputs.host, port = providerInputs.port) => {
const db = await $getClient({ ...providerInputs, port, host }); const db = await $getClient({ ...providerInputs, port, host });

View File

@@ -17,7 +17,7 @@ import { TIdentityOrgDALFactory } from "@app/services/identity/identity-org-dal"
import { TOrgDALFactory } from "@app/services/org/org-dal"; import { TOrgDALFactory } from "@app/services/org/org-dal";
import { TProjectDALFactory } from "@app/services/project/project-dal"; import { TProjectDALFactory } from "@app/services/project/project-dal";
import { OrgPermissionActions, OrgPermissionSubjects } from "../permission/org-permission"; import { OrgPermissionBillingActions, OrgPermissionSubjects } from "../permission/org-permission";
import { TPermissionServiceFactory } from "../permission/permission-service"; import { TPermissionServiceFactory } from "../permission/permission-service";
import { BillingPlanRows, BillingPlanTableHead } from "./licence-enums"; import { BillingPlanRows, BillingPlanTableHead } from "./licence-enums";
import { TLicenseDALFactory } from "./license-dal"; import { TLicenseDALFactory } from "./license-dal";
@@ -288,7 +288,7 @@ export const licenseServiceFactory = ({
billingCycle billingCycle
}: TOrgPlansTableDTO) => { }: TOrgPlansTableDTO) => {
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId); const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Billing); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionBillingActions.Read, OrgPermissionSubjects.Billing);
const { data } = await licenseServerCloudApi.request.get( const { data } = await licenseServerCloudApi.request.get(
`/api/license-server/v1/cloud-products?billing-cycle=${billingCycle}` `/api/license-server/v1/cloud-products?billing-cycle=${billingCycle}`
); );
@@ -310,8 +310,10 @@ export const licenseServiceFactory = ({
success_url success_url
}: TStartOrgTrialDTO) => { }: TStartOrgTrialDTO) => {
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId); const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.Billing); ForbiddenError.from(permission).throwUnlessCan(
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Billing); OrgPermissionBillingActions.ManageBilling,
OrgPermissionSubjects.Billing
);
const organization = await orgDAL.findOrgById(orgId); const organization = await orgDAL.findOrgById(orgId);
if (!organization) { if (!organization) {
@@ -338,8 +340,10 @@ export const licenseServiceFactory = ({
actorOrgId actorOrgId
}: TCreateOrgPortalSession) => { }: TCreateOrgPortalSession) => {
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId); const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.Billing); ForbiddenError.from(permission).throwUnlessCan(
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Billing); OrgPermissionBillingActions.ManageBilling,
OrgPermissionSubjects.Billing
);
const organization = await orgDAL.findOrgById(orgId); const organization = await orgDAL.findOrgById(orgId);
if (!organization) { if (!organization) {
@@ -385,7 +389,7 @@ export const licenseServiceFactory = ({
const getOrgBillingInfo = async ({ orgId, actor, actorId, actorAuthMethod, actorOrgId }: TGetOrgBillInfoDTO) => { const getOrgBillingInfo = async ({ orgId, actor, actorId, actorAuthMethod, actorOrgId }: TGetOrgBillInfoDTO) => {
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId); const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Billing); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionBillingActions.Read, OrgPermissionSubjects.Billing);
const organization = await orgDAL.findOrgById(orgId); const organization = await orgDAL.findOrgById(orgId);
if (!organization) { if (!organization) {
@@ -413,7 +417,7 @@ export const licenseServiceFactory = ({
// returns org current plan feature table // returns org current plan feature table
const getOrgPlanTable = async ({ orgId, actor, actorId, actorAuthMethod, actorOrgId }: TGetOrgBillInfoDTO) => { const getOrgPlanTable = async ({ orgId, actor, actorId, actorAuthMethod, actorOrgId }: TGetOrgBillInfoDTO) => {
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId); const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Billing); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionBillingActions.Read, OrgPermissionSubjects.Billing);
const organization = await orgDAL.findOrgById(orgId); const organization = await orgDAL.findOrgById(orgId);
if (!organization) { if (!organization) {
@@ -484,7 +488,7 @@ export const licenseServiceFactory = ({
const getOrgBillingDetails = async ({ orgId, actor, actorId, actorAuthMethod, actorOrgId }: TGetOrgBillInfoDTO) => { const getOrgBillingDetails = async ({ orgId, actor, actorId, actorAuthMethod, actorOrgId }: TGetOrgBillInfoDTO) => {
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId); const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Billing); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionBillingActions.Read, OrgPermissionSubjects.Billing);
const organization = await orgDAL.findOrgById(orgId); const organization = await orgDAL.findOrgById(orgId);
if (!organization) { if (!organization) {
@@ -509,7 +513,10 @@ export const licenseServiceFactory = ({
email email
}: TUpdateOrgBillingDetailsDTO) => { }: TUpdateOrgBillingDetailsDTO) => {
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId); const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Billing); ForbiddenError.from(permission).throwUnlessCan(
OrgPermissionBillingActions.ManageBilling,
OrgPermissionSubjects.Billing
);
const organization = await orgDAL.findOrgById(orgId); const organization = await orgDAL.findOrgById(orgId);
if (!organization) { if (!organization) {
@@ -529,7 +536,7 @@ export const licenseServiceFactory = ({
const getOrgPmtMethods = async ({ orgId, actor, actorId, actorAuthMethod, actorOrgId }: TOrgPmtMethodsDTO) => { const getOrgPmtMethods = async ({ orgId, actor, actorId, actorAuthMethod, actorOrgId }: TOrgPmtMethodsDTO) => {
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId); const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Billing); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionBillingActions.Read, OrgPermissionSubjects.Billing);
const organization = await orgDAL.findOrgById(orgId); const organization = await orgDAL.findOrgById(orgId);
if (!organization) { if (!organization) {
@@ -556,7 +563,10 @@ export const licenseServiceFactory = ({
cancel_url cancel_url
}: TAddOrgPmtMethodDTO) => { }: TAddOrgPmtMethodDTO) => {
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId); const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Billing); ForbiddenError.from(permission).throwUnlessCan(
OrgPermissionBillingActions.ManageBilling,
OrgPermissionSubjects.Billing
);
const organization = await orgDAL.findOrgById(orgId); const organization = await orgDAL.findOrgById(orgId);
if (!organization) { if (!organization) {
@@ -585,7 +595,10 @@ export const licenseServiceFactory = ({
pmtMethodId pmtMethodId
}: TDelOrgPmtMethodDTO) => { }: TDelOrgPmtMethodDTO) => {
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId); const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Billing); ForbiddenError.from(permission).throwUnlessCan(
OrgPermissionBillingActions.ManageBilling,
OrgPermissionSubjects.Billing
);
const organization = await orgDAL.findOrgById(orgId); const organization = await orgDAL.findOrgById(orgId);
if (!organization) { if (!organization) {
@@ -602,7 +615,7 @@ export const licenseServiceFactory = ({
const getOrgTaxIds = async ({ orgId, actor, actorId, actorAuthMethod, actorOrgId }: TGetOrgTaxIdDTO) => { const getOrgTaxIds = async ({ orgId, actor, actorId, actorAuthMethod, actorOrgId }: TGetOrgTaxIdDTO) => {
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId); const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Billing); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionBillingActions.Read, OrgPermissionSubjects.Billing);
const organization = await orgDAL.findOrgById(orgId); const organization = await orgDAL.findOrgById(orgId);
if (!organization) { if (!organization) {
@@ -620,7 +633,10 @@ export const licenseServiceFactory = ({
const addOrgTaxId = async ({ actorId, actor, actorAuthMethod, actorOrgId, orgId, type, value }: TAddOrgTaxIdDTO) => { const addOrgTaxId = async ({ actorId, actor, actorAuthMethod, actorOrgId, orgId, type, value }: TAddOrgTaxIdDTO) => {
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId); const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Billing); ForbiddenError.from(permission).throwUnlessCan(
OrgPermissionBillingActions.ManageBilling,
OrgPermissionSubjects.Billing
);
const organization = await orgDAL.findOrgById(orgId); const organization = await orgDAL.findOrgById(orgId);
if (!organization) { if (!organization) {
@@ -641,7 +657,10 @@ export const licenseServiceFactory = ({
const delOrgTaxId = async ({ orgId, actor, actorId, actorAuthMethod, actorOrgId, taxId }: TDelOrgTaxIdDTO) => { const delOrgTaxId = async ({ orgId, actor, actorId, actorAuthMethod, actorOrgId, taxId }: TDelOrgTaxIdDTO) => {
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId); const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Billing); ForbiddenError.from(permission).throwUnlessCan(
OrgPermissionBillingActions.ManageBilling,
OrgPermissionSubjects.Billing
);
const organization = await orgDAL.findOrgById(orgId); const organization = await orgDAL.findOrgById(orgId);
if (!organization) { if (!organization) {
@@ -658,7 +677,7 @@ export const licenseServiceFactory = ({
const getOrgTaxInvoices = async ({ actorId, actor, actorOrgId, actorAuthMethod, orgId }: TOrgInvoiceDTO) => { const getOrgTaxInvoices = async ({ actorId, actor, actorOrgId, actorAuthMethod, orgId }: TOrgInvoiceDTO) => {
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId); const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Billing); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionBillingActions.Read, OrgPermissionSubjects.Billing);
const organization = await orgDAL.findOrgById(orgId); const organization = await orgDAL.findOrgById(orgId);
if (!organization) { if (!organization) {
@@ -675,7 +694,7 @@ export const licenseServiceFactory = ({
const getOrgLicenses = async ({ orgId, actor, actorId, actorAuthMethod, actorOrgId }: TOrgLicensesDTO) => { const getOrgLicenses = async ({ orgId, actor, actorId, actorAuthMethod, actorOrgId }: TOrgLicensesDTO) => {
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId); const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Billing); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionBillingActions.Read, OrgPermissionSubjects.Billing);
const organization = await orgDAL.findOrgById(orgId); const organization = await orgDAL.findOrgById(orgId);
if (!organization) { if (!organization) {

View File

@@ -44,7 +44,6 @@ import {
TOidcLoginDTO, TOidcLoginDTO,
TUpdateOidcCfgDTO TUpdateOidcCfgDTO
} from "./oidc-config-types"; } from "./oidc-config-types";
import { logger } from "@app/lib/logger";
type TOidcConfigServiceFactoryDep = { type TOidcConfigServiceFactoryDep = {
userDAL: Pick< userDAL: Pick<
@@ -700,7 +699,6 @@ export const oidcConfigServiceFactory = ({
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
(_req: any, tokenSet: TokenSet, cb: any) => { (_req: any, tokenSet: TokenSet, cb: any) => {
const claims = tokenSet.claims(); const claims = tokenSet.claims();
logger.info(`User OIDC claims received for [orgId=${org.id}]`, JSON.stringify(claims, null, 2));
if (!claims.email || !claims.given_name) { if (!claims.email || !claims.given_name) {
throw new BadRequestError({ throw new BadRequestError({
message: "Invalid request. Missing email or first name" message: "Invalid request. Missing email or first name"

View File

@@ -2,7 +2,6 @@ import { AbilityBuilder, createMongoAbility, MongoAbility } from "@casl/ability"
import { import {
ProjectPermissionActions, ProjectPermissionActions,
ProjectPermissionApprovalActions,
ProjectPermissionCertificateActions, ProjectPermissionCertificateActions,
ProjectPermissionCmekActions, ProjectPermissionCmekActions,
ProjectPermissionDynamicSecretActions, ProjectPermissionDynamicSecretActions,
@@ -11,6 +10,7 @@ import {
ProjectPermissionKmipActions, ProjectPermissionKmipActions,
ProjectPermissionMemberActions, ProjectPermissionMemberActions,
ProjectPermissionPkiSubscriberActions, ProjectPermissionPkiSubscriberActions,
ProjectPermissionPkiTemplateActions,
ProjectPermissionSecretActions, ProjectPermissionSecretActions,
ProjectPermissionSecretRotationActions, ProjectPermissionSecretRotationActions,
ProjectPermissionSecretSyncActions, ProjectPermissionSecretSyncActions,
@@ -36,7 +36,6 @@ const buildAdminPermissionRules = () => {
ProjectPermissionSub.AuditLogs, ProjectPermissionSub.AuditLogs,
ProjectPermissionSub.IpAllowList, ProjectPermissionSub.IpAllowList,
ProjectPermissionSub.CertificateAuthorities, ProjectPermissionSub.CertificateAuthorities,
ProjectPermissionSub.CertificateTemplates,
ProjectPermissionSub.PkiAlerts, ProjectPermissionSub.PkiAlerts,
ProjectPermissionSub.PkiCollections, ProjectPermissionSub.PkiCollections,
ProjectPermissionSub.SshCertificateAuthorities, ProjectPermissionSub.SshCertificateAuthorities,
@@ -57,12 +56,22 @@ const buildAdminPermissionRules = () => {
can( can(
[ [
ProjectPermissionApprovalActions.Read, ProjectPermissionPkiTemplateActions.Read,
ProjectPermissionApprovalActions.Edit, ProjectPermissionPkiTemplateActions.Edit,
ProjectPermissionApprovalActions.Create, ProjectPermissionPkiTemplateActions.Create,
ProjectPermissionApprovalActions.Delete, ProjectPermissionPkiTemplateActions.Delete,
ProjectPermissionApprovalActions.AllowChangeBypass, ProjectPermissionPkiTemplateActions.IssueCert,
ProjectPermissionApprovalActions.AllowAccessBypass ProjectPermissionPkiTemplateActions.ListCerts
],
ProjectPermissionSub.CertificateTemplates
);
can(
[
ProjectPermissionActions.Read,
ProjectPermissionActions.Edit,
ProjectPermissionActions.Create,
ProjectPermissionActions.Delete
], ],
ProjectPermissionSub.SecretApproval ProjectPermissionSub.SecretApproval
); );
@@ -255,7 +264,7 @@ const buildMemberPermissionRules = () => {
ProjectPermissionSub.SecretImports ProjectPermissionSub.SecretImports
); );
can([ProjectPermissionApprovalActions.Read], ProjectPermissionSub.SecretApproval); can([ProjectPermissionActions.Read], ProjectPermissionSub.SecretApproval);
can([ProjectPermissionSecretRotationActions.Read], ProjectPermissionSub.SecretRotation); can([ProjectPermissionSecretRotationActions.Read], ProjectPermissionSub.SecretRotation);
can([ProjectPermissionActions.Read, ProjectPermissionActions.Create], ProjectPermissionSub.SecretRollback); can([ProjectPermissionActions.Read, ProjectPermissionActions.Create], ProjectPermissionSub.SecretRollback);
@@ -351,7 +360,7 @@ const buildMemberPermissionRules = () => {
ProjectPermissionSub.Certificates ProjectPermissionSub.Certificates
); );
can([ProjectPermissionActions.Read], ProjectPermissionSub.CertificateTemplates); can([ProjectPermissionPkiTemplateActions.Read], ProjectPermissionSub.CertificateTemplates);
can([ProjectPermissionActions.Read], ProjectPermissionSub.PkiAlerts); can([ProjectPermissionActions.Read], ProjectPermissionSub.PkiAlerts);
can([ProjectPermissionActions.Read], ProjectPermissionSub.PkiCollections); can([ProjectPermissionActions.Read], ProjectPermissionSub.PkiCollections);
@@ -403,7 +412,7 @@ const buildViewerPermissionRules = () => {
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);
can(ProjectPermissionApprovalActions.Read, ProjectPermissionSub.SecretApproval); can(ProjectPermissionActions.Read, ProjectPermissionSub.SecretApproval);
can(ProjectPermissionActions.Read, ProjectPermissionSub.SecretRollback); can(ProjectPermissionActions.Read, ProjectPermissionSub.SecretRollback);
can(ProjectPermissionSecretRotationActions.Read, ProjectPermissionSub.SecretRotation); can(ProjectPermissionSecretRotationActions.Read, ProjectPermissionSub.SecretRotation);
can(ProjectPermissionMemberActions.Read, ProjectPermissionSub.Member); can(ProjectPermissionMemberActions.Read, ProjectPermissionSub.Member);
@@ -420,6 +429,7 @@ const buildViewerPermissionRules = () => {
can(ProjectPermissionActions.Read, ProjectPermissionSub.IpAllowList); can(ProjectPermissionActions.Read, ProjectPermissionSub.IpAllowList);
can(ProjectPermissionActions.Read, ProjectPermissionSub.CertificateAuthorities); can(ProjectPermissionActions.Read, ProjectPermissionSub.CertificateAuthorities);
can(ProjectPermissionCertificateActions.Read, ProjectPermissionSub.Certificates); can(ProjectPermissionCertificateActions.Read, ProjectPermissionSub.Certificates);
can(ProjectPermissionPkiTemplateActions.Read, ProjectPermissionSub.CertificateTemplates);
can(ProjectPermissionCmekActions.Read, ProjectPermissionSub.Cmek); can(ProjectPermissionCmekActions.Read, ProjectPermissionSub.Cmek);
can(ProjectPermissionActions.Read, ProjectPermissionSub.SshCertificates); can(ProjectPermissionActions.Read, ProjectPermissionSub.SshCertificates);
can(ProjectPermissionActions.Read, ProjectPermissionSub.SshCertificateTemplates); can(ProjectPermissionActions.Read, ProjectPermissionSub.SshCertificateTemplates);

View File

@@ -67,6 +67,11 @@ export enum OrgPermissionGroupActions {
RemoveMembers = "remove-members" RemoveMembers = "remove-members"
} }
export enum OrgPermissionBillingActions {
Read = "read",
ManageBilling = "manage-billing"
}
export enum OrgPermissionSubjects { export enum OrgPermissionSubjects {
Workspace = "workspace", Workspace = "workspace",
Role = "role", Role = "role",
@@ -107,7 +112,7 @@ export type OrgPermissionSet =
| [OrgPermissionActions, OrgPermissionSubjects.Ldap] | [OrgPermissionActions, OrgPermissionSubjects.Ldap]
| [OrgPermissionGroupActions, OrgPermissionSubjects.Groups] | [OrgPermissionGroupActions, OrgPermissionSubjects.Groups]
| [OrgPermissionActions, OrgPermissionSubjects.SecretScanning] | [OrgPermissionActions, OrgPermissionSubjects.SecretScanning]
| [OrgPermissionActions, OrgPermissionSubjects.Billing] | [OrgPermissionBillingActions, OrgPermissionSubjects.Billing]
| [OrgPermissionIdentityActions, OrgPermissionSubjects.Identity] | [OrgPermissionIdentityActions, OrgPermissionSubjects.Identity]
| [OrgPermissionActions, OrgPermissionSubjects.Kms] | [OrgPermissionActions, OrgPermissionSubjects.Kms]
| [OrgPermissionActions, OrgPermissionSubjects.AuditLogs] | [OrgPermissionActions, OrgPermissionSubjects.AuditLogs]
@@ -298,10 +303,8 @@ const buildAdminPermission = () => {
can(OrgPermissionGroupActions.AddMembers, OrgPermissionSubjects.Groups); can(OrgPermissionGroupActions.AddMembers, OrgPermissionSubjects.Groups);
can(OrgPermissionGroupActions.RemoveMembers, OrgPermissionSubjects.Groups); can(OrgPermissionGroupActions.RemoveMembers, OrgPermissionSubjects.Groups);
can(OrgPermissionActions.Read, OrgPermissionSubjects.Billing); can(OrgPermissionBillingActions.Read, OrgPermissionSubjects.Billing);
can(OrgPermissionActions.Create, OrgPermissionSubjects.Billing); can(OrgPermissionBillingActions.ManageBilling, OrgPermissionSubjects.Billing);
can(OrgPermissionActions.Edit, OrgPermissionSubjects.Billing);
can(OrgPermissionActions.Delete, OrgPermissionSubjects.Billing);
can(OrgPermissionIdentityActions.Read, OrgPermissionSubjects.Identity); can(OrgPermissionIdentityActions.Read, OrgPermissionSubjects.Identity);
can(OrgPermissionIdentityActions.Create, OrgPermissionSubjects.Identity); can(OrgPermissionIdentityActions.Create, OrgPermissionSubjects.Identity);
@@ -362,7 +365,7 @@ const buildMemberPermission = () => {
can(OrgPermissionGroupActions.Read, OrgPermissionSubjects.Groups); can(OrgPermissionGroupActions.Read, OrgPermissionSubjects.Groups);
can(OrgPermissionActions.Read, OrgPermissionSubjects.Role); can(OrgPermissionActions.Read, OrgPermissionSubjects.Role);
can(OrgPermissionActions.Read, OrgPermissionSubjects.Settings); can(OrgPermissionActions.Read, OrgPermissionSubjects.Settings);
can(OrgPermissionActions.Read, OrgPermissionSubjects.Billing); can(OrgPermissionBillingActions.Read, OrgPermissionSubjects.Billing);
can(OrgPermissionActions.Read, OrgPermissionSubjects.IncidentAccount); can(OrgPermissionActions.Read, OrgPermissionSubjects.IncidentAccount);
can(OrgPermissionActions.Read, OrgPermissionSubjects.SecretScanning); can(OrgPermissionActions.Read, OrgPermissionSubjects.SecretScanning);

View File

@@ -34,15 +34,6 @@ export enum ProjectPermissionSecretActions {
Delete = "delete" Delete = "delete"
} }
export enum ProjectPermissionApprovalActions {
Read = "read",
Create = "create",
Edit = "edit",
Delete = "delete",
AllowChangeBypass = "allow-change-bypass",
AllowAccessBypass = "allow-access-bypass"
}
export enum ProjectPermissionCmekActions { export enum ProjectPermissionCmekActions {
Read = "read", Read = "read",
Create = "create", Create = "create",
@@ -96,6 +87,15 @@ export enum ProjectPermissionSshHostActions {
IssueHostCert = "issue-host-cert" IssueHostCert = "issue-host-cert"
} }
export enum ProjectPermissionPkiTemplateActions {
Read = "read",
Create = "create",
Edit = "edit",
Delete = "delete",
IssueCert = "issue-cert",
ListCerts = "list-certs"
}
export enum ProjectPermissionPkiSubscriberActions { export enum ProjectPermissionPkiSubscriberActions {
Read = "read", Read = "read",
Create = "create", Create = "create",
@@ -209,6 +209,11 @@ export type SshHostSubjectFields = {
hostname: string; hostname: string;
}; };
export type PkiTemplateSubjectFields = {
name: string;
// (dangtony98): consider adding [commonName] as a subject field in the future
};
export type PkiSubscriberSubjectFields = { export type PkiSubscriberSubjectFields = {
name: string; name: string;
// (dangtony98): consider adding [commonName] as a subject field in the future // (dangtony98): consider adding [commonName] as a subject field in the future
@@ -251,7 +256,7 @@ export type ProjectPermissionSet =
| [ProjectPermissionActions, ProjectPermissionSub.IpAllowList] | [ProjectPermissionActions, ProjectPermissionSub.IpAllowList]
| [ProjectPermissionActions, ProjectPermissionSub.Settings] | [ProjectPermissionActions, ProjectPermissionSub.Settings]
| [ProjectPermissionActions, ProjectPermissionSub.ServiceTokens] | [ProjectPermissionActions, ProjectPermissionSub.ServiceTokens]
| [ProjectPermissionApprovalActions, ProjectPermissionSub.SecretApproval] | [ProjectPermissionActions, ProjectPermissionSub.SecretApproval]
| [ | [
ProjectPermissionSecretRotationActions, ProjectPermissionSecretRotationActions,
( (
@@ -265,7 +270,13 @@ export type ProjectPermissionSet =
] ]
| [ProjectPermissionActions, ProjectPermissionSub.CertificateAuthorities] | [ProjectPermissionActions, ProjectPermissionSub.CertificateAuthorities]
| [ProjectPermissionCertificateActions, ProjectPermissionSub.Certificates] | [ProjectPermissionCertificateActions, ProjectPermissionSub.Certificates]
| [ProjectPermissionActions, ProjectPermissionSub.CertificateTemplates] | [
ProjectPermissionPkiTemplateActions,
(
| ProjectPermissionSub.CertificateTemplates
| (ForcedSubject<ProjectPermissionSub.CertificateTemplates> & PkiTemplateSubjectFields)
)
]
| [ProjectPermissionActions, ProjectPermissionSub.SshCertificateAuthorities] | [ProjectPermissionActions, ProjectPermissionSub.SshCertificateAuthorities]
| [ProjectPermissionActions, ProjectPermissionSub.SshCertificates] | [ProjectPermissionActions, ProjectPermissionSub.SshCertificates]
| [ProjectPermissionActions, ProjectPermissionSub.SshCertificateTemplates] | [ProjectPermissionActions, ProjectPermissionSub.SshCertificateTemplates]
@@ -445,10 +456,25 @@ const PkiSubscriberConditionSchema = z
}) })
.partial(); .partial();
const PkiTemplateConditionSchema = z
.object({
name: z.union([
z.string(),
z
.object({
[PermissionConditionOperators.$EQ]: PermissionConditionSchema[PermissionConditionOperators.$EQ],
[PermissionConditionOperators.$GLOB]: PermissionConditionSchema[PermissionConditionOperators.$GLOB],
[PermissionConditionOperators.$IN]: PermissionConditionSchema[PermissionConditionOperators.$IN]
})
.partial()
])
})
.partial();
const GeneralPermissionSchema = [ const GeneralPermissionSchema = [
z.object({ z.object({
subject: z.literal(ProjectPermissionSub.SecretApproval).describe("The entity this permission pertains to."), subject: z.literal(ProjectPermissionSub.SecretApproval).describe("The entity this permission pertains to."),
action: CASL_ACTION_SCHEMA_NATIVE_ENUM(ProjectPermissionApprovalActions).describe( action: CASL_ACTION_SCHEMA_NATIVE_ENUM(ProjectPermissionActions).describe(
"Describe what action an entity can take." "Describe what action an entity can take."
) )
}), }),
@@ -536,12 +562,6 @@ const GeneralPermissionSchema = [
"Describe what action an entity can take." "Describe what action an entity can take."
) )
}), }),
z.object({
subject: z.literal(ProjectPermissionSub.CertificateTemplates).describe("The entity this permission pertains to."),
action: CASL_ACTION_SCHEMA_NATIVE_ENUM(ProjectPermissionActions).describe(
"Describe what action an entity can take."
)
}),
z.object({ z.object({
subject: z subject: z
.literal(ProjectPermissionSub.SshCertificateAuthorities) .literal(ProjectPermissionSub.SshCertificateAuthorities)
@@ -719,6 +739,16 @@ export const ProjectPermissionV2Schema = z.discriminatedUnion("subject", [
"When specified, only matching conditions will be allowed to access given resource." "When specified, only matching conditions will be allowed to access given resource."
).optional() ).optional()
}), }),
z.object({
subject: z.literal(ProjectPermissionSub.CertificateTemplates).describe("The entity this permission pertains to."),
action: CASL_ACTION_SCHEMA_NATIVE_ENUM(ProjectPermissionPkiTemplateActions).describe(
"Describe what action an entity can take."
),
inverted: z.boolean().optional().describe("Whether rule allows or forbids."),
conditions: PkiTemplateConditionSchema.describe(
"When specified, only matching conditions will be allowed to access given resource."
).optional()
}),
z.object({ z.object({
subject: z.literal(ProjectPermissionSub.SecretRotation).describe("The entity this permission pertains to."), subject: z.literal(ProjectPermissionSub.SecretRotation).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."),
@@ -729,6 +759,7 @@ export const ProjectPermissionV2Schema = z.discriminatedUnion("subject", [
"When specified, only matching conditions will be allowed to access given resource." "When specified, only matching conditions will be allowed to access given resource."
).optional() ).optional()
}), }),
...GeneralPermissionSchema ...GeneralPermissionSchema
]); ]);

View File

@@ -9,6 +9,7 @@ import { UnpackedPermissionSchema } from "@app/server/routes/sanitizedSchema/per
import { ActorType } from "@app/services/auth/auth-type"; import { ActorType } from "@app/services/auth/auth-type";
import { TProjectMembershipDALFactory } from "@app/services/project-membership/project-membership-dal"; import { TProjectMembershipDALFactory } from "@app/services/project-membership/project-membership-dal";
import { TAccessApprovalRequestDALFactory } from "../access-approval-request/access-approval-request-dal";
import { constructPermissionErrorMessage, validatePrivilegeChangeOperation } from "../permission/permission-fns"; import { constructPermissionErrorMessage, validatePrivilegeChangeOperation } from "../permission/permission-fns";
import { TPermissionServiceFactory } from "../permission/permission-service"; import { TPermissionServiceFactory } from "../permission/permission-service";
import { import {
@@ -16,6 +17,7 @@ import {
ProjectPermissionSet, ProjectPermissionSet,
ProjectPermissionSub ProjectPermissionSub
} from "../permission/project-permission"; } from "../permission/project-permission";
import { ApprovalStatus } from "../secret-approval-request/secret-approval-request-types";
import { TProjectUserAdditionalPrivilegeDALFactory } from "./project-user-additional-privilege-dal"; import { TProjectUserAdditionalPrivilegeDALFactory } from "./project-user-additional-privilege-dal";
import { import {
ProjectUserAdditionalPrivilegeTemporaryMode, ProjectUserAdditionalPrivilegeTemporaryMode,
@@ -30,6 +32,7 @@ type TProjectUserAdditionalPrivilegeServiceFactoryDep = {
projectUserAdditionalPrivilegeDAL: TProjectUserAdditionalPrivilegeDALFactory; projectUserAdditionalPrivilegeDAL: TProjectUserAdditionalPrivilegeDALFactory;
projectMembershipDAL: Pick<TProjectMembershipDALFactory, "findById" | "findOne">; projectMembershipDAL: Pick<TProjectMembershipDALFactory, "findById" | "findOne">;
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission">; permissionService: Pick<TPermissionServiceFactory, "getProjectPermission">;
accessApprovalRequestDAL: Pick<TAccessApprovalRequestDALFactory, "update">;
}; };
export type TProjectUserAdditionalPrivilegeServiceFactory = ReturnType< export type TProjectUserAdditionalPrivilegeServiceFactory = ReturnType<
@@ -44,7 +47,8 @@ const unpackPermissions = (permissions: unknown) =>
export const projectUserAdditionalPrivilegeServiceFactory = ({ export const projectUserAdditionalPrivilegeServiceFactory = ({
projectUserAdditionalPrivilegeDAL, projectUserAdditionalPrivilegeDAL,
projectMembershipDAL, projectMembershipDAL,
permissionService permissionService,
accessApprovalRequestDAL
}: TProjectUserAdditionalPrivilegeServiceFactoryDep) => { }: TProjectUserAdditionalPrivilegeServiceFactoryDep) => {
const create = async ({ const create = async ({
slug, slug,
@@ -279,6 +283,15 @@ export const projectUserAdditionalPrivilegeServiceFactory = ({
}); });
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionMemberActions.Edit, ProjectPermissionSub.Member); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionMemberActions.Edit, ProjectPermissionSub.Member);
await accessApprovalRequestDAL.update(
{
privilegeId: userPrivilege.id
},
{
privilegeDeletedAt: new Date(),
status: ApprovalStatus.REJECTED
}
);
const deletedPrivilege = await projectUserAdditionalPrivilegeDAL.deleteById(userPrivilege.id); const deletedPrivilege = await projectUserAdditionalPrivilegeDAL.deleteById(userPrivilege.id);
return { return {
...deletedPrivilege, ...deletedPrivilege,

View File

@@ -8,3 +8,10 @@ export const secretApprovalPolicyApproverDALFactory = (db: TDbClient) => {
const sapApproverOrm = ormify(db, TableName.SecretApprovalPolicyApprover); const sapApproverOrm = ormify(db, TableName.SecretApprovalPolicyApprover);
return sapApproverOrm; return sapApproverOrm;
}; };
export type TSecretApprovalPolicyBypasserDALFactory = ReturnType<typeof secretApprovalPolicyBypasserDALFactory>;
export const secretApprovalPolicyBypasserDALFactory = (db: TDbClient) => {
const sapBypasserOrm = ormify(db, TableName.SecretApprovalPolicyBypasser);
return sapBypasserOrm;
};

View File

@@ -1,11 +1,17 @@
import { Knex } from "knex"; import { Knex } from "knex";
import { TDbClient } from "@app/db"; import { TDbClient } from "@app/db";
import { SecretApprovalPoliciesSchema, TableName, TSecretApprovalPolicies, TUsers } from "@app/db/schemas"; import {
SecretApprovalPoliciesSchema,
TableName,
TSecretApprovalPolicies,
TUserGroupMembership,
TUsers
} from "@app/db/schemas";
import { DatabaseError } from "@app/lib/errors"; import { DatabaseError } from "@app/lib/errors";
import { buildFindFilter, ormify, selectAllTableCols, sqlNestRelationships, TFindFilter } from "@app/lib/knex"; import { buildFindFilter, ormify, selectAllTableCols, sqlNestRelationships, TFindFilter } from "@app/lib/knex";
import { ApproverType } from "../access-approval-policy/access-approval-policy-types"; import { ApproverType, BypasserType } from "../access-approval-policy/access-approval-policy-types";
export type TSecretApprovalPolicyDALFactory = ReturnType<typeof secretApprovalPolicyDALFactory>; export type TSecretApprovalPolicyDALFactory = ReturnType<typeof secretApprovalPolicyDALFactory>;
@@ -43,6 +49,22 @@ export const secretApprovalPolicyDALFactory = (db: TDbClient) => {
`${TableName.SecretApprovalPolicyApprover}.approverUserId`, `${TableName.SecretApprovalPolicyApprover}.approverUserId`,
"secretApprovalPolicyApproverUser.id" "secretApprovalPolicyApproverUser.id"
) )
// Bypasser
.leftJoin(
TableName.SecretApprovalPolicyBypasser,
`${TableName.SecretApprovalPolicy}.id`,
`${TableName.SecretApprovalPolicyBypasser}.policyId`
)
.leftJoin<TUserGroupMembership>(
db(TableName.UserGroupMembership).as("bypasserUserGroupMembership"),
`${TableName.SecretApprovalPolicyBypasser}.bypasserGroupId`,
`bypasserUserGroupMembership.groupId`
)
.leftJoin<TUsers>(
db(TableName.Users).as("secretApprovalPolicyBypasserUser"),
`${TableName.SecretApprovalPolicyBypasser}.bypasserUserId`,
"secretApprovalPolicyBypasserUser.id"
)
.leftJoin<TUsers>(TableName.Users, `${TableName.UserGroupMembership}.userId`, `${TableName.Users}.id`) .leftJoin<TUsers>(TableName.Users, `${TableName.UserGroupMembership}.userId`, `${TableName.Users}.id`)
.select( .select(
tx.ref("id").withSchema("secretApprovalPolicyApproverUser").as("approverUserId"), tx.ref("id").withSchema("secretApprovalPolicyApproverUser").as("approverUserId"),
@@ -58,6 +80,20 @@ export const secretApprovalPolicyDALFactory = (db: TDbClient) => {
tx.ref("firstName").withSchema(TableName.Users).as("approverGroupFirstName"), tx.ref("firstName").withSchema(TableName.Users).as("approverGroupFirstName"),
tx.ref("lastName").withSchema(TableName.Users).as("approverGroupLastName") tx.ref("lastName").withSchema(TableName.Users).as("approverGroupLastName")
) )
.select(
tx.ref("id").withSchema("secretApprovalPolicyBypasserUser").as("bypasserUserId"),
tx.ref("email").withSchema("secretApprovalPolicyBypasserUser").as("bypasserEmail"),
tx.ref("firstName").withSchema("secretApprovalPolicyBypasserUser").as("bypasserFirstName"),
tx.ref("username").withSchema("secretApprovalPolicyBypasserUser").as("bypasserUsername"),
tx.ref("lastName").withSchema("secretApprovalPolicyBypasserUser").as("bypasserLastName")
)
.select(
tx.ref("bypasserGroupId").withSchema(TableName.SecretApprovalPolicyBypasser),
tx.ref("userId").withSchema("bypasserUserGroupMembership").as("bypasserGroupUserId"),
tx.ref("email").withSchema(TableName.Users).as("bypasserGroupEmail"),
tx.ref("firstName").withSchema(TableName.Users).as("bypasserGroupFirstName"),
tx.ref("lastName").withSchema(TableName.Users).as("bypasserGroupLastName")
)
.select( .select(
tx.ref("name").withSchema(TableName.Environment).as("envName"), tx.ref("name").withSchema(TableName.Environment).as("envName"),
tx.ref("slug").withSchema(TableName.Environment).as("envSlug"), tx.ref("slug").withSchema(TableName.Environment).as("envSlug"),
@@ -143,7 +179,7 @@ export const secretApprovalPolicyDALFactory = (db: TDbClient) => {
label: "approvers" as const, label: "approvers" as const,
mapper: ({ approverUserId: id, approverUsername }) => ({ mapper: ({ approverUserId: id, approverUsername }) => ({
type: ApproverType.User, type: ApproverType.User,
name: approverUsername, username: approverUsername,
id id
}) })
}, },
@@ -155,6 +191,23 @@ export const secretApprovalPolicyDALFactory = (db: TDbClient) => {
id id
}) })
}, },
{
key: "bypasserUserId",
label: "bypassers" as const,
mapper: ({ bypasserUserId: id, bypasserUsername }) => ({
type: BypasserType.User,
username: bypasserUsername,
id
})
},
{
key: "bypasserGroupId",
label: "bypassers" as const,
mapper: ({ bypasserGroupId: id }) => ({
type: BypasserType.Group,
id
})
},
{ {
key: "approverUserId", key: "approverUserId",
label: "userApprovers" as const, label: "userApprovers" as const,

View File

@@ -3,18 +3,21 @@ import picomatch from "picomatch";
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 { ProjectPermissionApprovalActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission"; import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
import { BadRequestError, NotFoundError } from "@app/lib/errors"; import { BadRequestError, NotFoundError } from "@app/lib/errors";
import { removeTrailingSlash } from "@app/lib/fn"; import { removeTrailingSlash } from "@app/lib/fn";
import { containsGlobPatterns } from "@app/lib/picomatch"; import { containsGlobPatterns } from "@app/lib/picomatch";
import { TProjectEnvDALFactory } from "@app/services/project-env/project-env-dal"; import { TProjectEnvDALFactory } from "@app/services/project-env/project-env-dal";
import { TUserDALFactory } from "@app/services/user/user-dal"; import { TUserDALFactory } from "@app/services/user/user-dal";
import { ApproverType } from "../access-approval-policy/access-approval-policy-types"; import { ApproverType, BypasserType } from "../access-approval-policy/access-approval-policy-types";
import { TLicenseServiceFactory } from "../license/license-service"; import { TLicenseServiceFactory } from "../license/license-service";
import { TSecretApprovalRequestDALFactory } from "../secret-approval-request/secret-approval-request-dal"; import { TSecretApprovalRequestDALFactory } from "../secret-approval-request/secret-approval-request-dal";
import { RequestState } from "../secret-approval-request/secret-approval-request-types"; import { RequestState } from "../secret-approval-request/secret-approval-request-types";
import { TSecretApprovalPolicyApproverDALFactory } from "./secret-approval-policy-approver-dal"; import {
TSecretApprovalPolicyApproverDALFactory,
TSecretApprovalPolicyBypasserDALFactory
} from "./secret-approval-policy-approver-dal";
import { TSecretApprovalPolicyDALFactory } from "./secret-approval-policy-dal"; import { TSecretApprovalPolicyDALFactory } from "./secret-approval-policy-dal";
import { import {
TCreateSapDTO, TCreateSapDTO,
@@ -36,6 +39,7 @@ type TSecretApprovalPolicyServiceFactoryDep = {
projectEnvDAL: Pick<TProjectEnvDALFactory, "findOne">; projectEnvDAL: Pick<TProjectEnvDALFactory, "findOne">;
userDAL: Pick<TUserDALFactory, "find">; userDAL: Pick<TUserDALFactory, "find">;
secretApprovalPolicyApproverDAL: TSecretApprovalPolicyApproverDALFactory; secretApprovalPolicyApproverDAL: TSecretApprovalPolicyApproverDALFactory;
secretApprovalPolicyBypasserDAL: TSecretApprovalPolicyBypasserDALFactory;
licenseService: Pick<TLicenseServiceFactory, "getPlan">; licenseService: Pick<TLicenseServiceFactory, "getPlan">;
secretApprovalRequestDAL: Pick<TSecretApprovalRequestDALFactory, "update">; secretApprovalRequestDAL: Pick<TSecretApprovalRequestDALFactory, "update">;
}; };
@@ -46,6 +50,7 @@ export const secretApprovalPolicyServiceFactory = ({
secretApprovalPolicyDAL, secretApprovalPolicyDAL,
permissionService, permissionService,
secretApprovalPolicyApproverDAL, secretApprovalPolicyApproverDAL,
secretApprovalPolicyBypasserDAL,
projectEnvDAL, projectEnvDAL,
userDAL, userDAL,
licenseService, licenseService,
@@ -59,6 +64,7 @@ export const secretApprovalPolicyServiceFactory = ({
actorAuthMethod, actorAuthMethod,
approvals, approvals,
approvers, approvers,
bypassers,
projectId, projectId,
secretPath, secretPath,
environment, environment,
@@ -74,7 +80,7 @@ export const secretApprovalPolicyServiceFactory = ({
.filter(Boolean) as string[]; .filter(Boolean) as string[];
const userApproverNames = approvers const userApproverNames = approvers
.map((approver) => (approver.type === ApproverType.User ? approver.name : undefined)) .map((approver) => (approver.type === ApproverType.User ? approver.username : undefined))
.filter(Boolean) as string[]; .filter(Boolean) as string[];
if (!groupApprovers.length && approvals > approvers.length) if (!groupApprovers.length && approvals > approvers.length)
@@ -89,7 +95,7 @@ export const secretApprovalPolicyServiceFactory = ({
actionProjectType: ActionProjectType.SecretManager actionProjectType: ActionProjectType.SecretManager
}); });
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionApprovalActions.Create, ProjectPermissionActions.Create,
ProjectPermissionSub.SecretApproval ProjectPermissionSub.SecretApproval
); );
@@ -107,6 +113,44 @@ export const secretApprovalPolicyServiceFactory = ({
message: `Environment with slug '${environment}' not found in project with ID ${projectId}` message: `Environment with slug '${environment}' not found in project with ID ${projectId}`
}); });
let groupBypassers: string[] = [];
let bypasserUserIds: string[] = [];
if (bypassers && bypassers.length) {
groupBypassers = bypassers
.filter((bypasser) => bypasser.type === BypasserType.Group)
.map((bypasser) => bypasser.id) as string[];
const userBypassers = bypassers
.filter((bypasser) => bypasser.type === BypasserType.User)
.map((bypasser) => bypasser.id)
.filter(Boolean) as string[];
const userBypasserNames = bypassers
.map((bypasser) => (bypasser.type === BypasserType.User ? bypasser.username : undefined))
.filter(Boolean) as string[];
bypasserUserIds = userBypassers;
if (userBypasserNames.length) {
const bypasserUsers = await userDAL.find({
$in: {
username: userBypasserNames
}
});
const bypasserNamesFromDb = bypasserUsers.map((user) => user.username);
const invalidUsernames = userBypasserNames.filter((username) => !bypasserNamesFromDb.includes(username));
if (invalidUsernames.length) {
throw new BadRequestError({
message: `Invalid bypasser user: ${invalidUsernames.join(", ")}`
});
}
bypasserUserIds = bypasserUserIds.concat(bypasserUsers.map((user) => user.id));
}
}
const secretApproval = await secretApprovalPolicyDAL.transaction(async (tx) => { const secretApproval = await secretApprovalPolicyDAL.transaction(async (tx) => {
const doc = await secretApprovalPolicyDAL.create( const doc = await secretApprovalPolicyDAL.create(
{ {
@@ -158,6 +202,27 @@ export const secretApprovalPolicyServiceFactory = ({
})), })),
tx tx
); );
if (bypasserUserIds.length) {
await secretApprovalPolicyBypasserDAL.insertMany(
bypasserUserIds.map((userId) => ({
bypasserUserId: userId,
policyId: doc.id
})),
tx
);
}
if (groupBypassers.length) {
await secretApprovalPolicyBypasserDAL.insertMany(
groupBypassers.map((groupId) => ({
bypasserGroupId: groupId,
policyId: doc.id
})),
tx
);
}
return doc; return doc;
}); });
@@ -166,6 +231,7 @@ export const secretApprovalPolicyServiceFactory = ({
const updateSecretApprovalPolicy = async ({ const updateSecretApprovalPolicy = async ({
approvers, approvers,
bypassers,
secretPath, secretPath,
name, name,
actorId, actorId,
@@ -186,7 +252,7 @@ export const secretApprovalPolicyServiceFactory = ({
.filter(Boolean) as string[]; .filter(Boolean) as string[];
const userApproverNames = approvers const userApproverNames = approvers
.map((approver) => (approver.type === ApproverType.User ? approver.name : undefined)) .map((approver) => (approver.type === ApproverType.User ? approver.username : undefined))
.filter(Boolean) as string[]; .filter(Boolean) as string[];
const secretApprovalPolicy = await secretApprovalPolicyDAL.findById(secretPolicyId); const secretApprovalPolicy = await secretApprovalPolicyDAL.findById(secretPolicyId);
@@ -204,10 +270,7 @@ export const secretApprovalPolicyServiceFactory = ({
actorOrgId, actorOrgId,
actionProjectType: ActionProjectType.SecretManager actionProjectType: ActionProjectType.SecretManager
}); });
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.SecretApproval);
ProjectPermissionApprovalActions.Edit,
ProjectPermissionSub.SecretApproval
);
const plan = await licenseService.getPlan(actorOrgId); const plan = await licenseService.getPlan(actorOrgId);
if (!plan.secretApproval) { if (!plan.secretApproval) {
@@ -217,6 +280,44 @@ export const secretApprovalPolicyServiceFactory = ({
}); });
} }
let groupBypassers: string[] = [];
let bypasserUserIds: string[] = [];
if (bypassers && bypassers.length) {
groupBypassers = bypassers
.filter((bypasser) => bypasser.type === BypasserType.Group)
.map((bypasser) => bypasser.id) as string[];
const userBypassers = bypassers
.filter((bypasser) => bypasser.type === BypasserType.User)
.map((bypasser) => bypasser.id)
.filter(Boolean) as string[];
const userBypasserNames = bypassers
.map((bypasser) => (bypasser.type === BypasserType.User ? bypasser.username : undefined))
.filter(Boolean) as string[];
bypasserUserIds = userBypassers;
if (userBypasserNames.length) {
const bypasserUsers = await userDAL.find({
$in: {
username: userBypasserNames
}
});
const bypasserNamesFromDb = bypasserUsers.map((user) => user.username);
const invalidUsernames = userBypasserNames.filter((username) => !bypasserNamesFromDb.includes(username));
if (invalidUsernames.length) {
throw new BadRequestError({
message: `Invalid bypasser user: ${invalidUsernames.join(", ")}`
});
}
bypasserUserIds = bypasserUserIds.concat(bypasserUsers.map((user) => user.id));
}
}
const updatedSap = await secretApprovalPolicyDAL.transaction(async (tx) => { const updatedSap = await secretApprovalPolicyDAL.transaction(async (tx) => {
const doc = await secretApprovalPolicyDAL.updateById( const doc = await secretApprovalPolicyDAL.updateById(
secretApprovalPolicy.id, secretApprovalPolicy.id,
@@ -275,6 +376,28 @@ export const secretApprovalPolicyServiceFactory = ({
); );
} }
await secretApprovalPolicyBypasserDAL.delete({ policyId: doc.id }, tx);
if (bypasserUserIds.length) {
await secretApprovalPolicyBypasserDAL.insertMany(
bypasserUserIds.map((userId) => ({
bypasserUserId: userId,
policyId: doc.id
})),
tx
);
}
if (groupBypassers.length) {
await secretApprovalPolicyBypasserDAL.insertMany(
groupBypassers.map((groupId) => ({
bypasserGroupId: groupId,
policyId: doc.id
})),
tx
);
}
return doc; return doc;
}); });
return { return {
@@ -304,7 +427,7 @@ export const secretApprovalPolicyServiceFactory = ({
actionProjectType: ActionProjectType.SecretManager actionProjectType: ActionProjectType.SecretManager
}); });
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionApprovalActions.Delete, ProjectPermissionActions.Delete,
ProjectPermissionSub.SecretApproval ProjectPermissionSub.SecretApproval
); );
@@ -343,10 +466,7 @@ export const secretApprovalPolicyServiceFactory = ({
actorOrgId, actorOrgId,
actionProjectType: ActionProjectType.SecretManager actionProjectType: ActionProjectType.SecretManager
}); });
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.SecretApproval);
ProjectPermissionApprovalActions.Read,
ProjectPermissionSub.SecretApproval
);
const sapPolicies = await secretApprovalPolicyDAL.find({ projectId, deletedAt: null }); const sapPolicies = await secretApprovalPolicyDAL.find({ projectId, deletedAt: null });
return sapPolicies; return sapPolicies;
@@ -419,10 +539,7 @@ export const secretApprovalPolicyServiceFactory = ({
actionProjectType: ActionProjectType.SecretManager actionProjectType: ActionProjectType.SecretManager
}); });
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.SecretApproval);
ProjectPermissionApprovalActions.Read,
ProjectPermissionSub.SecretApproval
);
return sapPolicy; return sapPolicy;
}; };

View File

@@ -1,12 +1,16 @@
import { EnforcementLevel, TProjectPermission } from "@app/lib/types"; import { EnforcementLevel, TProjectPermission } from "@app/lib/types";
import { ApproverType } from "../access-approval-policy/access-approval-policy-types"; import { ApproverType, BypasserType } from "../access-approval-policy/access-approval-policy-types";
export type TCreateSapDTO = { export type TCreateSapDTO = {
approvals: number; approvals: number;
secretPath?: string | null; secretPath?: string | null;
environment: string; environment: string;
approvers: ({ type: ApproverType.Group; id: string } | { type: ApproverType.User; id?: string; name?: string })[]; approvers: ({ type: ApproverType.Group; id: string } | { type: ApproverType.User; id?: string; username?: string })[];
bypassers?: (
| { type: BypasserType.Group; id: string }
| { type: BypasserType.User; id?: string; username?: string }
)[];
projectId: string; projectId: string;
name: string; name: string;
enforcementLevel: EnforcementLevel; enforcementLevel: EnforcementLevel;
@@ -17,7 +21,11 @@ export type TUpdateSapDTO = {
secretPolicyId: string; secretPolicyId: string;
approvals?: number; approvals?: number;
secretPath?: string | null; secretPath?: string | null;
approvers: ({ type: ApproverType.Group; id: string } | { type: ApproverType.User; id?: string; name?: string })[]; approvers: ({ type: ApproverType.Group; id: string } | { type: ApproverType.User; id?: string; username?: string })[];
bypassers?: (
| { type: BypasserType.Group; id: string }
| { type: BypasserType.User; id?: string; username?: string }
)[];
name?: string; name?: string;
enforcementLevel?: EnforcementLevel; enforcementLevel?: EnforcementLevel;
allowedSelfApprovals?: boolean; allowedSelfApprovals?: boolean;

View File

@@ -6,6 +6,7 @@ import {
TableName, TableName,
TSecretApprovalRequests, TSecretApprovalRequests,
TSecretApprovalRequestsSecrets, TSecretApprovalRequestsSecrets,
TUserGroupMembership,
TUsers TUsers
} from "@app/db/schemas"; } from "@app/db/schemas";
import { DatabaseError } from "@app/lib/errors"; import { DatabaseError } from "@app/lib/errors";
@@ -58,16 +59,36 @@ export const secretApprovalRequestDALFactory = (db: TDbClient) => {
`${TableName.SecretApprovalPolicyApprover}.approverUserId`, `${TableName.SecretApprovalPolicyApprover}.approverUserId`,
"secretApprovalPolicyApproverUser.id" "secretApprovalPolicyApproverUser.id"
) )
.leftJoin( .leftJoin<TUserGroupMembership>(
TableName.UserGroupMembership, db(TableName.UserGroupMembership).as("approverUserGroupMembership"),
`${TableName.SecretApprovalPolicyApprover}.approverGroupId`, `${TableName.SecretApprovalPolicyApprover}.approverGroupId`,
`${TableName.UserGroupMembership}.groupId` `approverUserGroupMembership.groupId`
) )
.leftJoin<TUsers>( .leftJoin<TUsers>(
db(TableName.Users).as("secretApprovalPolicyGroupApproverUser"), db(TableName.Users).as("secretApprovalPolicyGroupApproverUser"),
`${TableName.UserGroupMembership}.userId`, `approverUserGroupMembership.userId`,
`secretApprovalPolicyGroupApproverUser.id` `secretApprovalPolicyGroupApproverUser.id`
) )
.leftJoin(
TableName.SecretApprovalPolicyBypasser,
`${TableName.SecretApprovalPolicy}.id`,
`${TableName.SecretApprovalPolicyBypasser}.policyId`
)
.leftJoin<TUsers>(
db(TableName.Users).as("secretApprovalPolicyBypasserUser"),
`${TableName.SecretApprovalPolicyBypasser}.bypasserUserId`,
"secretApprovalPolicyBypasserUser.id"
)
.leftJoin<TUserGroupMembership>(
db(TableName.UserGroupMembership).as("bypasserUserGroupMembership"),
`${TableName.SecretApprovalPolicyBypasser}.bypasserGroupId`,
`bypasserUserGroupMembership.groupId`
)
.leftJoin<TUsers>(
db(TableName.Users).as("secretApprovalPolicyGroupBypasserUser"),
`bypasserUserGroupMembership.userId`,
`secretApprovalPolicyGroupBypasserUser.id`
)
.leftJoin( .leftJoin(
TableName.SecretApprovalRequestReviewer, TableName.SecretApprovalRequestReviewer,
`${TableName.SecretApprovalRequest}.id`, `${TableName.SecretApprovalRequest}.id`,
@@ -81,7 +102,7 @@ export const secretApprovalRequestDALFactory = (db: TDbClient) => {
.select(selectAllTableCols(TableName.SecretApprovalRequest)) .select(selectAllTableCols(TableName.SecretApprovalRequest))
.select( .select(
tx.ref("approverUserId").withSchema(TableName.SecretApprovalPolicyApprover), tx.ref("approverUserId").withSchema(TableName.SecretApprovalPolicyApprover),
tx.ref("userId").withSchema(TableName.UserGroupMembership).as("approverGroupUserId"), tx.ref("userId").withSchema("approverUserGroupMembership").as("approverGroupUserId"),
tx.ref("email").withSchema("secretApprovalPolicyApproverUser").as("approverEmail"), tx.ref("email").withSchema("secretApprovalPolicyApproverUser").as("approverEmail"),
tx.ref("email").withSchema("secretApprovalPolicyGroupApproverUser").as("approverGroupEmail"), tx.ref("email").withSchema("secretApprovalPolicyGroupApproverUser").as("approverGroupEmail"),
tx.ref("username").withSchema("secretApprovalPolicyApproverUser").as("approverUsername"), tx.ref("username").withSchema("secretApprovalPolicyApproverUser").as("approverUsername"),
@@ -90,6 +111,20 @@ export const secretApprovalRequestDALFactory = (db: TDbClient) => {
tx.ref("firstName").withSchema("secretApprovalPolicyGroupApproverUser").as("approverGroupFirstName"), tx.ref("firstName").withSchema("secretApprovalPolicyGroupApproverUser").as("approverGroupFirstName"),
tx.ref("lastName").withSchema("secretApprovalPolicyApproverUser").as("approverLastName"), tx.ref("lastName").withSchema("secretApprovalPolicyApproverUser").as("approverLastName"),
tx.ref("lastName").withSchema("secretApprovalPolicyGroupApproverUser").as("approverGroupLastName"), tx.ref("lastName").withSchema("secretApprovalPolicyGroupApproverUser").as("approverGroupLastName"),
// Bypasser fields
tx.ref("bypasserUserId").withSchema(TableName.SecretApprovalPolicyBypasser),
tx.ref("bypasserGroupId").withSchema(TableName.SecretApprovalPolicyBypasser),
tx.ref("userId").withSchema("bypasserUserGroupMembership").as("bypasserGroupUserId"),
tx.ref("email").withSchema("secretApprovalPolicyBypasserUser").as("bypasserEmail"),
tx.ref("email").withSchema("secretApprovalPolicyGroupBypasserUser").as("bypasserGroupEmail"),
tx.ref("username").withSchema("secretApprovalPolicyBypasserUser").as("bypasserUsername"),
tx.ref("username").withSchema("secretApprovalPolicyGroupBypasserUser").as("bypasserGroupUsername"),
tx.ref("firstName").withSchema("secretApprovalPolicyBypasserUser").as("bypasserFirstName"),
tx.ref("firstName").withSchema("secretApprovalPolicyGroupBypasserUser").as("bypasserGroupFirstName"),
tx.ref("lastName").withSchema("secretApprovalPolicyBypasserUser").as("bypasserLastName"),
tx.ref("lastName").withSchema("secretApprovalPolicyGroupBypasserUser").as("bypasserGroupLastName"),
tx.ref("email").withSchema("statusChangedByUser").as("statusChangedByUserEmail"), tx.ref("email").withSchema("statusChangedByUser").as("statusChangedByUserEmail"),
tx.ref("username").withSchema("statusChangedByUser").as("statusChangedByUserUsername"), tx.ref("username").withSchema("statusChangedByUser").as("statusChangedByUserUsername"),
tx.ref("firstName").withSchema("statusChangedByUser").as("statusChangedByUserFirstName"), tx.ref("firstName").withSchema("statusChangedByUser").as("statusChangedByUserFirstName"),
@@ -121,7 +156,7 @@ export const secretApprovalRequestDALFactory = (db: TDbClient) => {
try { try {
const sql = findQuery({ [`${TableName.SecretApprovalRequest}.id` as "id"]: id }, tx || db.replicaNode()); const sql = findQuery({ [`${TableName.SecretApprovalRequest}.id` as "id"]: id }, tx || db.replicaNode());
const docs = await sql; const docs = await sql;
const formatedDoc = sqlNestRelationships({ const formattedDoc = sqlNestRelationships({
data: docs, data: docs,
key: "id", key: "id",
parentMapper: (el) => ({ parentMapper: (el) => ({
@@ -203,13 +238,51 @@ export const secretApprovalRequestDALFactory = (db: TDbClient) => {
lastName, lastName,
username username
}) })
},
{
key: "bypasserUserId",
label: "bypassers" as const,
mapper: ({
bypasserUserId: userId,
bypasserEmail: email,
bypasserUsername: username,
bypasserLastName: lastName,
bypasserFirstName: firstName
}) => ({
userId,
email,
firstName,
lastName,
username
})
},
{
key: "bypasserGroupUserId",
label: "bypassers" as const,
mapper: ({
bypasserGroupUserId: userId,
bypasserGroupEmail: email,
bypasserGroupUsername: username,
bypasserGroupLastName: lastName,
bypasserGroupFirstName: firstName
}) => ({
userId,
email,
firstName,
lastName,
username
})
} }
] ]
}); });
if (!formatedDoc?.[0]) return; if (!formattedDoc?.[0]) return;
return { return {
...formatedDoc[0], ...formattedDoc[0],
policy: { ...formatedDoc[0].policy, approvers: formatedDoc[0].approvers } policy: {
...formattedDoc[0].policy,
approvers: formattedDoc[0].approvers,
bypassers: formattedDoc[0].bypassers
}
}; };
} catch (error) { } catch (error) {
throw new DatabaseError({ error, name: "FindByIdSAR" }); throw new DatabaseError({ error, name: "FindByIdSAR" });
@@ -291,6 +364,16 @@ export const secretApprovalRequestDALFactory = (db: TDbClient) => {
`${TableName.SecretApprovalPolicyApprover}.approverGroupId`, `${TableName.SecretApprovalPolicyApprover}.approverGroupId`,
`${TableName.UserGroupMembership}.groupId` `${TableName.UserGroupMembership}.groupId`
) )
.leftJoin(
TableName.SecretApprovalPolicyBypasser,
`${TableName.SecretApprovalPolicy}.id`,
`${TableName.SecretApprovalPolicyBypasser}.policyId`
)
.leftJoin<TUserGroupMembership>(
db(TableName.UserGroupMembership).as("bypasserUserGroupMembership"),
`${TableName.SecretApprovalPolicyBypasser}.bypasserGroupId`,
`bypasserUserGroupMembership.groupId`
)
.join<TUsers>( .join<TUsers>(
db(TableName.Users).as("committerUser"), db(TableName.Users).as("committerUser"),
`${TableName.SecretApprovalRequest}.committerUserId`, `${TableName.SecretApprovalRequest}.committerUserId`,
@@ -342,6 +425,11 @@ export const secretApprovalRequestDALFactory = (db: TDbClient) => {
db.ref("approvals").withSchema(TableName.SecretApprovalPolicy).as("policyApprovals"), db.ref("approvals").withSchema(TableName.SecretApprovalPolicy).as("policyApprovals"),
db.ref("approverUserId").withSchema(TableName.SecretApprovalPolicyApprover), db.ref("approverUserId").withSchema(TableName.SecretApprovalPolicyApprover),
db.ref("userId").withSchema(TableName.UserGroupMembership).as("approverGroupUserId"), db.ref("userId").withSchema(TableName.UserGroupMembership).as("approverGroupUserId"),
// Bypasser fields
db.ref("bypasserUserId").withSchema(TableName.SecretApprovalPolicyBypasser),
db.ref("userId").withSchema("bypasserUserGroupMembership").as("bypasserGroupUserId"),
db.ref("email").withSchema("committerUser").as("committerUserEmail"), db.ref("email").withSchema("committerUser").as("committerUserEmail"),
db.ref("username").withSchema("committerUser").as("committerUserUsername"), db.ref("username").withSchema("committerUser").as("committerUserUsername"),
db.ref("firstName").withSchema("committerUser").as("committerUserFirstName"), db.ref("firstName").withSchema("committerUser").as("committerUserFirstName"),
@@ -355,7 +443,7 @@ export const secretApprovalRequestDALFactory = (db: TDbClient) => {
.from<Awaited<typeof query>[number]>("w") .from<Awaited<typeof query>[number]>("w")
.where("w.rank", ">=", offset) .where("w.rank", ">=", offset)
.andWhere("w.rank", "<", offset + limit); .andWhere("w.rank", "<", offset + limit);
const formatedDoc = sqlNestRelationships({ const formattedDoc = sqlNestRelationships({
data: docs, data: docs,
key: "id", key: "id",
parentMapper: (el) => ({ parentMapper: (el) => ({
@@ -403,12 +491,22 @@ export const secretApprovalRequestDALFactory = (db: TDbClient) => {
key: "approverGroupUserId", key: "approverGroupUserId",
label: "approvers" as const, label: "approvers" as const,
mapper: ({ approverGroupUserId }) => ({ userId: approverGroupUserId }) mapper: ({ approverGroupUserId }) => ({ userId: approverGroupUserId })
},
{
key: "bypasserUserId",
label: "bypassers" as const,
mapper: ({ bypasserUserId }) => ({ userId: bypasserUserId })
},
{
key: "bypasserGroupUserId",
label: "bypassers" as const,
mapper: ({ bypasserGroupUserId }) => ({ userId: bypasserGroupUserId })
} }
] ]
}); });
return formatedDoc.map((el) => ({ return formattedDoc.map((el) => ({
...el, ...el,
policy: { ...el.policy, approvers: el.approvers } policy: { ...el.policy, approvers: el.approvers, bypassers: el.bypassers }
})); }));
} catch (error) { } catch (error) {
throw new DatabaseError({ error, name: "FindSAR" }); throw new DatabaseError({ error, name: "FindSAR" });
@@ -440,6 +538,16 @@ export const secretApprovalRequestDALFactory = (db: TDbClient) => {
`${TableName.SecretApprovalPolicyApprover}.approverGroupId`, `${TableName.SecretApprovalPolicyApprover}.approverGroupId`,
`${TableName.UserGroupMembership}.groupId` `${TableName.UserGroupMembership}.groupId`
) )
.leftJoin(
TableName.SecretApprovalPolicyBypasser,
`${TableName.SecretApprovalPolicy}.id`,
`${TableName.SecretApprovalPolicyBypasser}.policyId`
)
.leftJoin<TUserGroupMembership>(
db(TableName.UserGroupMembership).as("bypasserUserGroupMembership"),
`${TableName.SecretApprovalPolicyBypasser}.bypasserGroupId`,
`bypasserUserGroupMembership.groupId`
)
.join<TUsers>( .join<TUsers>(
db(TableName.Users).as("committerUser"), db(TableName.Users).as("committerUser"),
`${TableName.SecretApprovalRequest}.committerUserId`, `${TableName.SecretApprovalRequest}.committerUserId`,
@@ -491,6 +599,11 @@ export const secretApprovalRequestDALFactory = (db: TDbClient) => {
db.ref("enforcementLevel").withSchema(TableName.SecretApprovalPolicy).as("policyEnforcementLevel"), db.ref("enforcementLevel").withSchema(TableName.SecretApprovalPolicy).as("policyEnforcementLevel"),
db.ref("approverUserId").withSchema(TableName.SecretApprovalPolicyApprover), db.ref("approverUserId").withSchema(TableName.SecretApprovalPolicyApprover),
db.ref("userId").withSchema(TableName.UserGroupMembership).as("approverGroupUserId"), db.ref("userId").withSchema(TableName.UserGroupMembership).as("approverGroupUserId"),
// Bypasser
db.ref("bypasserUserId").withSchema(TableName.SecretApprovalPolicyBypasser),
db.ref("userId").withSchema("bypasserUserGroupMembership").as("bypasserGroupUserId"),
db.ref("email").withSchema("committerUser").as("committerUserEmail"), db.ref("email").withSchema("committerUser").as("committerUserEmail"),
db.ref("username").withSchema("committerUser").as("committerUserUsername"), db.ref("username").withSchema("committerUser").as("committerUserUsername"),
db.ref("firstName").withSchema("committerUser").as("committerUserFirstName"), db.ref("firstName").withSchema("committerUser").as("committerUserFirstName"),
@@ -504,7 +617,7 @@ export const secretApprovalRequestDALFactory = (db: TDbClient) => {
.from<Awaited<typeof query>[number]>("w") .from<Awaited<typeof query>[number]>("w")
.where("w.rank", ">=", offset) .where("w.rank", ">=", offset)
.andWhere("w.rank", "<", offset + limit); .andWhere("w.rank", "<", offset + limit);
const formatedDoc = sqlNestRelationships({ const formattedDoc = sqlNestRelationships({
data: docs, data: docs,
key: "id", key: "id",
parentMapper: (el) => ({ parentMapper: (el) => ({
@@ -554,12 +667,24 @@ export const secretApprovalRequestDALFactory = (db: TDbClient) => {
mapper: ({ approverGroupUserId }) => ({ mapper: ({ approverGroupUserId }) => ({
userId: approverGroupUserId userId: approverGroupUserId
}) })
},
{
key: "bypasserUserId",
label: "bypassers" as const,
mapper: ({ bypasserUserId }) => ({ userId: bypasserUserId })
},
{
key: "bypasserGroupUserId",
label: "bypassers" as const,
mapper: ({ bypasserGroupUserId }) => ({
userId: bypasserGroupUserId
})
} }
] ]
}); });
return formatedDoc.map((el) => ({ return formattedDoc.map((el) => ({
...el, ...el,
policy: { ...el.policy, approvers: el.approvers } policy: { ...el.policy, approvers: el.approvers, bypassers: el.bypassers }
})); }));
} catch (error) { } catch (error) {
throw new DatabaseError({ error, name: "FindSAR" }); throw new DatabaseError({ error, name: "FindSAR" });

View File

@@ -62,11 +62,7 @@ import { TUserDALFactory } from "@app/services/user/user-dal";
import { TLicenseServiceFactory } from "../license/license-service"; import { TLicenseServiceFactory } from "../license/license-service";
import { throwIfMissingSecretReadValueOrDescribePermission } from "../permission/permission-fns"; import { throwIfMissingSecretReadValueOrDescribePermission } from "../permission/permission-fns";
import { TPermissionServiceFactory } from "../permission/permission-service"; import { TPermissionServiceFactory } from "../permission/permission-service";
import { import { ProjectPermissionSecretActions, ProjectPermissionSub } from "../permission/project-permission";
ProjectPermissionApprovalActions,
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";
@@ -501,14 +497,14 @@ export const secretApprovalRequestServiceFactory = ({
}); });
} }
const { policy, folderId, projectId } = secretApprovalRequest; const { policy, folderId, projectId, bypassers } = secretApprovalRequest;
if (policy.deletedAt) { if (policy.deletedAt) {
throw new BadRequestError({ throw new BadRequestError({
message: "The policy associated with this secret approval request has been deleted." message: "The policy associated with this secret approval request has been deleted."
}); });
} }
const { hasRole, permission } = await permissionService.getProjectPermission({ const { hasRole } = await permissionService.getProjectPermission({
actor: ActorType.USER, actor: ActorType.USER,
actorId, actorId,
projectId, projectId,
@@ -534,14 +530,9 @@ export const secretApprovalRequestServiceFactory = ({
approverId ? reviewers[approverId] === ApprovalStatus.APPROVED : false approverId ? reviewers[approverId] === ApprovalStatus.APPROVED : false
).length; ).length;
const isSoftEnforcement = secretApprovalRequest.policy.enforcementLevel === EnforcementLevel.Soft; const isSoftEnforcement = secretApprovalRequest.policy.enforcementLevel === EnforcementLevel.Soft;
const canBypass = !bypassers.length || bypassers.some((bypasser) => bypasser.userId === actorId);
if ( if (!hasMinApproval && !(isSoftEnforcement && canBypass))
!hasMinApproval &&
!(
isSoftEnforcement &&
permission.can(ProjectPermissionApprovalActions.AllowChangeBypass, ProjectPermissionSub.SecretApproval)
)
)
throw new BadRequestError({ message: "Doesn't have minimum approvals needed" }); throw new BadRequestError({ message: "Doesn't have minimum approvals needed" });
const { botKey, shouldUseSecretV2Bridge, project } = await projectBotService.getBotKey(projectId); const { botKey, shouldUseSecretV2Bridge, project } = await projectBotService.getBotKey(projectId);

View File

@@ -0,0 +1,3 @@
export * from "./mysql-credentials-rotation-constants";
export * from "./mysql-credentials-rotation-schemas";
export * from "./mysql-credentials-rotation-types";

View File

@@ -0,0 +1,23 @@
import { SecretRotation } from "@app/ee/services/secret-rotation-v2/secret-rotation-v2-enums";
import { TSecretRotationV2ListItem } from "@app/ee/services/secret-rotation-v2/secret-rotation-v2-types";
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
export const MYSQL_CREDENTIALS_ROTATION_LIST_OPTION: TSecretRotationV2ListItem = {
name: "MySQL Credentials",
type: SecretRotation.MySqlCredentials,
connection: AppConnection.MySql,
template: {
createUserStatement: `-- create user
CREATE USER 'infisical_user'@'%' IDENTIFIED BY 'temporary_password';
-- grant all privileges
GRANT ALL PRIVILEGES ON my_database.* TO 'infisical_user'@'%';
-- apply the privilege changes
FLUSH PRIVILEGES;`,
secretsMapping: {
username: "MYSQL_USERNAME",
password: "MYSQL_PASSWORD"
}
}
};

View File

@@ -0,0 +1,41 @@
import { z } from "zod";
import { SecretRotation } from "@app/ee/services/secret-rotation-v2/secret-rotation-v2-enums";
import {
BaseCreateSecretRotationSchema,
BaseSecretRotationSchema,
BaseUpdateSecretRotationSchema
} from "@app/ee/services/secret-rotation-v2/secret-rotation-v2-schemas";
import {
SqlCredentialsRotationParametersSchema,
SqlCredentialsRotationSecretsMappingSchema,
SqlCredentialsRotationTemplateSchema
} from "@app/ee/services/secret-rotation-v2/shared/sql-credentials";
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
export const MySqlCredentialsRotationSchema = BaseSecretRotationSchema(SecretRotation.MySqlCredentials).extend({
type: z.literal(SecretRotation.MySqlCredentials),
parameters: SqlCredentialsRotationParametersSchema,
secretsMapping: SqlCredentialsRotationSecretsMappingSchema
});
export const CreateMySqlCredentialsRotationSchema = BaseCreateSecretRotationSchema(
SecretRotation.MySqlCredentials
).extend({
parameters: SqlCredentialsRotationParametersSchema,
secretsMapping: SqlCredentialsRotationSecretsMappingSchema
});
export const UpdateMySqlCredentialsRotationSchema = BaseUpdateSecretRotationSchema(
SecretRotation.MySqlCredentials
).extend({
parameters: SqlCredentialsRotationParametersSchema.optional(),
secretsMapping: SqlCredentialsRotationSecretsMappingSchema.optional()
});
export const MySqlCredentialsRotationListItemSchema = z.object({
name: z.literal("MySQL Credentials"),
connection: z.literal(AppConnection.MySql),
type: z.literal(SecretRotation.MySqlCredentials),
template: SqlCredentialsRotationTemplateSchema
});

View File

@@ -0,0 +1,19 @@
import { z } from "zod";
import { TMySqlConnection } from "@app/services/app-connection/mysql";
import {
CreateMySqlCredentialsRotationSchema,
MySqlCredentialsRotationListItemSchema,
MySqlCredentialsRotationSchema
} from "./mysql-credentials-rotation-schemas";
export type TMySqlCredentialsRotation = z.infer<typeof MySqlCredentialsRotationSchema>;
export type TMySqlCredentialsRotationInput = z.infer<typeof CreateMySqlCredentialsRotationSchema>;
export type TMySqlCredentialsRotationListItem = z.infer<typeof MySqlCredentialsRotationListItemSchema>;
export type TMySqlCredentialsRotationWithConnection = TMySqlCredentialsRotation & {
connection: TMySqlConnection;
};

View File

@@ -1,6 +1,7 @@
export enum SecretRotation { export enum SecretRotation {
PostgresCredentials = "postgres-credentials", PostgresCredentials = "postgres-credentials",
MsSqlCredentials = "mssql-credentials", MsSqlCredentials = "mssql-credentials",
MySqlCredentials = "mysql-credentials",
Auth0ClientSecret = "auth0-client-secret", Auth0ClientSecret = "auth0-client-secret",
AzureClientSecret = "azure-client-secret", AzureClientSecret = "azure-client-secret",
AwsIamUserSecret = "aws-iam-user-secret", AwsIamUserSecret = "aws-iam-user-secret",

View File

@@ -9,6 +9,7 @@ import { AWS_IAM_USER_SECRET_ROTATION_LIST_OPTION } from "./aws-iam-user-secret"
import { AZURE_CLIENT_SECRET_ROTATION_LIST_OPTION } from "./azure-client-secret"; import { AZURE_CLIENT_SECRET_ROTATION_LIST_OPTION } from "./azure-client-secret";
import { LDAP_PASSWORD_ROTATION_LIST_OPTION, TLdapPasswordRotation } from "./ldap-password"; import { LDAP_PASSWORD_ROTATION_LIST_OPTION, TLdapPasswordRotation } from "./ldap-password";
import { MSSQL_CREDENTIALS_ROTATION_LIST_OPTION } from "./mssql-credentials"; import { MSSQL_CREDENTIALS_ROTATION_LIST_OPTION } from "./mssql-credentials";
import { MYSQL_CREDENTIALS_ROTATION_LIST_OPTION } from "./mysql-credentials";
import { POSTGRES_CREDENTIALS_ROTATION_LIST_OPTION } from "./postgres-credentials"; import { POSTGRES_CREDENTIALS_ROTATION_LIST_OPTION } from "./postgres-credentials";
import { SecretRotation, SecretRotationStatus } from "./secret-rotation-v2-enums"; import { SecretRotation, SecretRotationStatus } from "./secret-rotation-v2-enums";
import { TSecretRotationV2ServiceFactoryDep } from "./secret-rotation-v2-service"; import { TSecretRotationV2ServiceFactoryDep } from "./secret-rotation-v2-service";
@@ -23,6 +24,7 @@ import {
const SECRET_ROTATION_LIST_OPTIONS: Record<SecretRotation, TSecretRotationV2ListItem> = { const SECRET_ROTATION_LIST_OPTIONS: Record<SecretRotation, TSecretRotationV2ListItem> = {
[SecretRotation.PostgresCredentials]: POSTGRES_CREDENTIALS_ROTATION_LIST_OPTION, [SecretRotation.PostgresCredentials]: POSTGRES_CREDENTIALS_ROTATION_LIST_OPTION,
[SecretRotation.MsSqlCredentials]: MSSQL_CREDENTIALS_ROTATION_LIST_OPTION, [SecretRotation.MsSqlCredentials]: MSSQL_CREDENTIALS_ROTATION_LIST_OPTION,
[SecretRotation.MySqlCredentials]: MYSQL_CREDENTIALS_ROTATION_LIST_OPTION,
[SecretRotation.Auth0ClientSecret]: AUTH0_CLIENT_SECRET_ROTATION_LIST_OPTION, [SecretRotation.Auth0ClientSecret]: AUTH0_CLIENT_SECRET_ROTATION_LIST_OPTION,
[SecretRotation.AzureClientSecret]: AZURE_CLIENT_SECRET_ROTATION_LIST_OPTION, [SecretRotation.AzureClientSecret]: AZURE_CLIENT_SECRET_ROTATION_LIST_OPTION,
[SecretRotation.AwsIamUserSecret]: AWS_IAM_USER_SECRET_ROTATION_LIST_OPTION, [SecretRotation.AwsIamUserSecret]: AWS_IAM_USER_SECRET_ROTATION_LIST_OPTION,

View File

@@ -4,6 +4,7 @@ import { AppConnection } from "@app/services/app-connection/app-connection-enums
export const SECRET_ROTATION_NAME_MAP: Record<SecretRotation, string> = { export const SECRET_ROTATION_NAME_MAP: Record<SecretRotation, string> = {
[SecretRotation.PostgresCredentials]: "PostgreSQL Credentials", [SecretRotation.PostgresCredentials]: "PostgreSQL Credentials",
[SecretRotation.MsSqlCredentials]: "Microsoft SQL Server Credentials", [SecretRotation.MsSqlCredentials]: "Microsoft SQL Server Credentials",
[SecretRotation.MySqlCredentials]: "MySQL Credentials",
[SecretRotation.Auth0ClientSecret]: "Auth0 Client Secret", [SecretRotation.Auth0ClientSecret]: "Auth0 Client Secret",
[SecretRotation.AzureClientSecret]: "Azure Client Secret", [SecretRotation.AzureClientSecret]: "Azure Client Secret",
[SecretRotation.AwsIamUserSecret]: "AWS IAM User Secret", [SecretRotation.AwsIamUserSecret]: "AWS IAM User Secret",
@@ -13,6 +14,7 @@ export const SECRET_ROTATION_NAME_MAP: Record<SecretRotation, string> = {
export const SECRET_ROTATION_CONNECTION_MAP: Record<SecretRotation, AppConnection> = { export const SECRET_ROTATION_CONNECTION_MAP: Record<SecretRotation, AppConnection> = {
[SecretRotation.PostgresCredentials]: AppConnection.Postgres, [SecretRotation.PostgresCredentials]: AppConnection.Postgres,
[SecretRotation.MsSqlCredentials]: AppConnection.MsSql, [SecretRotation.MsSqlCredentials]: AppConnection.MsSql,
[SecretRotation.MySqlCredentials]: AppConnection.MySql,
[SecretRotation.Auth0ClientSecret]: AppConnection.Auth0, [SecretRotation.Auth0ClientSecret]: AppConnection.Auth0,
[SecretRotation.AzureClientSecret]: AppConnection.AzureClientSecrets, [SecretRotation.AzureClientSecret]: AppConnection.AzureClientSecrets,
[SecretRotation.AwsIamUserSecret]: AppConnection.AWS, [SecretRotation.AwsIamUserSecret]: AppConnection.AWS,

View File

@@ -120,6 +120,7 @@ type TRotationFactoryImplementation = TRotationFactory<
const SECRET_ROTATION_FACTORY_MAP: Record<SecretRotation, TRotationFactoryImplementation> = { const SECRET_ROTATION_FACTORY_MAP: Record<SecretRotation, TRotationFactoryImplementation> = {
[SecretRotation.PostgresCredentials]: sqlCredentialsRotationFactory as TRotationFactoryImplementation, [SecretRotation.PostgresCredentials]: sqlCredentialsRotationFactory as TRotationFactoryImplementation,
[SecretRotation.MsSqlCredentials]: sqlCredentialsRotationFactory as TRotationFactoryImplementation, [SecretRotation.MsSqlCredentials]: sqlCredentialsRotationFactory as TRotationFactoryImplementation,
[SecretRotation.MySqlCredentials]: sqlCredentialsRotationFactory as TRotationFactoryImplementation,
[SecretRotation.Auth0ClientSecret]: auth0ClientSecretRotationFactory as TRotationFactoryImplementation, [SecretRotation.Auth0ClientSecret]: auth0ClientSecretRotationFactory as TRotationFactoryImplementation,
[SecretRotation.AzureClientSecret]: azureClientSecretRotationFactory as TRotationFactoryImplementation, [SecretRotation.AzureClientSecret]: azureClientSecretRotationFactory as TRotationFactoryImplementation,
[SecretRotation.AwsIamUserSecret]: awsIamUserSecretRotationFactory as TRotationFactoryImplementation, [SecretRotation.AwsIamUserSecret]: awsIamUserSecretRotationFactory as TRotationFactoryImplementation,

View File

@@ -39,6 +39,12 @@ import {
TMsSqlCredentialsRotationListItem, TMsSqlCredentialsRotationListItem,
TMsSqlCredentialsRotationWithConnection TMsSqlCredentialsRotationWithConnection
} from "./mssql-credentials"; } from "./mssql-credentials";
import {
TMySqlCredentialsRotation,
TMySqlCredentialsRotationInput,
TMySqlCredentialsRotationListItem,
TMySqlCredentialsRotationWithConnection
} from "./mysql-credentials";
import { import {
TPostgresCredentialsRotation, TPostgresCredentialsRotation,
TPostgresCredentialsRotationInput, TPostgresCredentialsRotationInput,
@@ -51,6 +57,7 @@ import { SecretRotation } from "./secret-rotation-v2-enums";
export type TSecretRotationV2 = export type TSecretRotationV2 =
| TPostgresCredentialsRotation | TPostgresCredentialsRotation
| TMsSqlCredentialsRotation | TMsSqlCredentialsRotation
| TMySqlCredentialsRotation
| TAuth0ClientSecretRotation | TAuth0ClientSecretRotation
| TAzureClientSecretRotation | TAzureClientSecretRotation
| TLdapPasswordRotation | TLdapPasswordRotation
@@ -59,6 +66,7 @@ export type TSecretRotationV2 =
export type TSecretRotationV2WithConnection = export type TSecretRotationV2WithConnection =
| TPostgresCredentialsRotationWithConnection | TPostgresCredentialsRotationWithConnection
| TMsSqlCredentialsRotationWithConnection | TMsSqlCredentialsRotationWithConnection
| TMySqlCredentialsRotationWithConnection
| TAuth0ClientSecretRotationWithConnection | TAuth0ClientSecretRotationWithConnection
| TAzureClientSecretRotationWithConnection | TAzureClientSecretRotationWithConnection
| TLdapPasswordRotationWithConnection | TLdapPasswordRotationWithConnection
@@ -74,6 +82,7 @@ export type TSecretRotationV2GeneratedCredentials =
export type TSecretRotationV2Input = export type TSecretRotationV2Input =
| TPostgresCredentialsRotationInput | TPostgresCredentialsRotationInput
| TMsSqlCredentialsRotationInput | TMsSqlCredentialsRotationInput
| TMySqlCredentialsRotationInput
| TAuth0ClientSecretRotationInput | TAuth0ClientSecretRotationInput
| TAzureClientSecretRotationInput | TAzureClientSecretRotationInput
| TLdapPasswordRotationInput | TLdapPasswordRotationInput
@@ -82,6 +91,7 @@ export type TSecretRotationV2Input =
export type TSecretRotationV2ListItem = export type TSecretRotationV2ListItem =
| TPostgresCredentialsRotationListItem | TPostgresCredentialsRotationListItem
| TMsSqlCredentialsRotationListItem | TMsSqlCredentialsRotationListItem
| TMySqlCredentialsRotationListItem
| TAuth0ClientSecretRotationListItem | TAuth0ClientSecretRotationListItem
| TAzureClientSecretRotationListItem | TAzureClientSecretRotationListItem
| TLdapPasswordRotationListItem | TLdapPasswordRotationListItem

View File

@@ -4,6 +4,7 @@ import { Auth0ClientSecretRotationSchema } from "@app/ee/services/secret-rotatio
import { AzureClientSecretRotationSchema } from "@app/ee/services/secret-rotation-v2/azure-client-secret"; import { AzureClientSecretRotationSchema } from "@app/ee/services/secret-rotation-v2/azure-client-secret";
import { LdapPasswordRotationSchema } from "@app/ee/services/secret-rotation-v2/ldap-password"; import { LdapPasswordRotationSchema } from "@app/ee/services/secret-rotation-v2/ldap-password";
import { MsSqlCredentialsRotationSchema } from "@app/ee/services/secret-rotation-v2/mssql-credentials"; import { MsSqlCredentialsRotationSchema } from "@app/ee/services/secret-rotation-v2/mssql-credentials";
import { MySqlCredentialsRotationSchema } from "@app/ee/services/secret-rotation-v2/mysql-credentials";
import { PostgresCredentialsRotationSchema } from "@app/ee/services/secret-rotation-v2/postgres-credentials"; import { PostgresCredentialsRotationSchema } from "@app/ee/services/secret-rotation-v2/postgres-credentials";
import { AwsIamUserSecretRotationSchema } from "./aws-iam-user-secret"; import { AwsIamUserSecretRotationSchema } from "./aws-iam-user-secret";
@@ -11,6 +12,7 @@ import { AwsIamUserSecretRotationSchema } from "./aws-iam-user-secret";
export const SecretRotationV2Schema = z.discriminatedUnion("type", [ export const SecretRotationV2Schema = z.discriminatedUnion("type", [
PostgresCredentialsRotationSchema, PostgresCredentialsRotationSchema,
MsSqlCredentialsRotationSchema, MsSqlCredentialsRotationSchema,
MySqlCredentialsRotationSchema,
Auth0ClientSecretRotationSchema, Auth0ClientSecretRotationSchema,
AzureClientSecretRotationSchema, AzureClientSecretRotationSchema,
LdapPasswordRotationSchema, LdapPasswordRotationSchema,

View File

@@ -1,13 +1,15 @@
import { z } from "zod"; import { z } from "zod";
import { TMsSqlCredentialsRotationWithConnection } from "@app/ee/services/secret-rotation-v2/mssql-credentials"; import { TMsSqlCredentialsRotationWithConnection } from "@app/ee/services/secret-rotation-v2/mssql-credentials";
import { TMySqlCredentialsRotationWithConnection } from "@app/ee/services/secret-rotation-v2/mysql-credentials";
import { TPostgresCredentialsRotationWithConnection } from "@app/ee/services/secret-rotation-v2/postgres-credentials"; import { TPostgresCredentialsRotationWithConnection } from "@app/ee/services/secret-rotation-v2/postgres-credentials";
import { SqlCredentialsRotationGeneratedCredentialsSchema } from "./sql-credentials-rotation-schemas"; import { SqlCredentialsRotationGeneratedCredentialsSchema } from "./sql-credentials-rotation-schemas";
export type TSqlCredentialsRotationWithConnection = export type TSqlCredentialsRotationWithConnection =
| TPostgresCredentialsRotationWithConnection | TPostgresCredentialsRotationWithConnection
| TMsSqlCredentialsRotationWithConnection; | TMsSqlCredentialsRotationWithConnection
| TMySqlCredentialsRotationWithConnection;
export type TSqlCredentialsRotationGeneratedCredentials = z.infer< export type TSqlCredentialsRotationGeneratedCredentials = z.infer<
typeof SqlCredentialsRotationGeneratedCredentialsSchema typeof SqlCredentialsRotationGeneratedCredentialsSchema

View File

@@ -171,6 +171,13 @@ export const getDbSetQuery = (db: TDbProviderClients, variables: { username: str
}; };
} }
if (db === TDbProviderClients.MySql) {
return {
query: `ALTER USER ??@'%' IDENTIFIED BY '${variables.password}'`,
variables: [variables.username]
};
}
// add more based on client // add more based on client
return { return {
query: `ALTER USER ?? IDENTIFIED BY '${variables.password}'`, query: `ALTER USER ?? IDENTIFIED BY '${variables.password}'`,

View File

@@ -147,7 +147,9 @@ export const UNIVERSAL_AUTH = {
accessTokenMaxTTL: accessTokenMaxTTL:
"The maximum lifetime for an access token in seconds. This value will be referenced at renewal time.", "The maximum lifetime for an access token in seconds. This value will be referenced at renewal time.",
accessTokenNumUsesLimit: accessTokenNumUsesLimit:
"The maximum number of times that an access token can be used; a value of 0 implies infinite number of uses." "The maximum number of times that an access token can be used; a value of 0 implies infinite number of uses.",
accessTokenPeriod:
"The period for an access token in seconds. This value will be referenced at renewal time. Default value is 0."
}, },
RETRIEVE: { RETRIEVE: {
identityId: "The ID of the identity to retrieve the auth method for." identityId: "The ID of the identity to retrieve the auth method for."
@@ -161,7 +163,8 @@ export const UNIVERSAL_AUTH = {
accessTokenTrustedIps: "The new list of IPs or CIDR ranges that access tokens can be used from.", accessTokenTrustedIps: "The new list of IPs or CIDR ranges that access tokens can be used from.",
accessTokenTTL: "The new lifetime for an access token in seconds.", accessTokenTTL: "The new lifetime for an access token in seconds.",
accessTokenMaxTTL: "The new maximum lifetime for an access token in seconds.", accessTokenMaxTTL: "The new maximum lifetime for an access token in seconds.",
accessTokenNumUsesLimit: "The new maximum number of times that an access token can be used." accessTokenNumUsesLimit: "The new maximum number of times that an access token can be used.",
accessTokenPeriod: "The new period for an access token in seconds."
}, },
CREATE_CLIENT_SECRET: { CREATE_CLIENT_SECRET: {
identityId: "The ID of the identity to create a client secret for.", identityId: "The ID of the identity to create a client secret for.",

View File

@@ -44,7 +44,7 @@ const createQuicConnection = async (
if (!certs || certs.length === 0) return quic.native.CryptoError.CertificateRequired; if (!certs || certs.length === 0) return quic.native.CryptoError.CertificateRequired;
const serverCertificate = new crypto.X509Certificate(Buffer.from(certs[0])); const serverCertificate = new crypto.X509Certificate(Buffer.from(certs[0]));
const caCertificate = new crypto.X509Certificate(tlsOptions.ca); const caCertificate = new crypto.X509Certificate(tlsOptions.ca);
const isValidServerCertificate = serverCertificate.checkIssued(caCertificate); const isValidServerCertificate = serverCertificate.verify(caCertificate.publicKey);
if (!isValidServerCertificate) return quic.native.CryptoError.BadCertificate; if (!isValidServerCertificate) return quic.native.CryptoError.BadCertificate;
const subjectDetails = parseSubjectDetails(serverCertificate.subject); const subjectDetails = parseSubjectDetails(serverCertificate.subject);

View File

@@ -19,3 +19,15 @@ export const validateHandlebarTemplate = (templateName: string, template: string
throw new BadRequestError({ message: `Template sanitization failed: ${templateName}` }); throw new BadRequestError({ message: `Template sanitization failed: ${templateName}` });
}); });
}; };
export const isValidHandleBarTemplate = (template: string, dto: SanitizationArg) => {
const parsedAst = handlebars.parse(template);
return parsedAst.body.every((el) => {
if (el.type === "ContentStatement") return true;
if (el.type === "MustacheStatement" && "path" in el) {
const { path } = el as { type: "MustacheStatement"; path: { type: "PathExpression"; original: string } };
if (path.type === "PathExpression" && dto?.allowedExpressions?.(path.original)) return true;
}
return false;
});
};

View File

@@ -6,7 +6,10 @@ import { z } from "zod";
import { registerCertificateEstRouter } from "@app/ee/routes/est/certificate-est-router"; import { registerCertificateEstRouter } from "@app/ee/routes/est/certificate-est-router";
import { registerV1EERoutes } from "@app/ee/routes/v1"; import { registerV1EERoutes } from "@app/ee/routes/v1";
import { registerV2EERoutes } from "@app/ee/routes/v2"; import { registerV2EERoutes } from "@app/ee/routes/v2";
import { accessApprovalPolicyApproverDALFactory } from "@app/ee/services/access-approval-policy/access-approval-policy-approver-dal"; import {
accessApprovalPolicyApproverDALFactory,
accessApprovalPolicyBypasserDALFactory
} from "@app/ee/services/access-approval-policy/access-approval-policy-approver-dal";
import { accessApprovalPolicyDALFactory } from "@app/ee/services/access-approval-policy/access-approval-policy-dal"; import { accessApprovalPolicyDALFactory } from "@app/ee/services/access-approval-policy/access-approval-policy-dal";
import { accessApprovalPolicyServiceFactory } from "@app/ee/services/access-approval-policy/access-approval-policy-service"; import { accessApprovalPolicyServiceFactory } from "@app/ee/services/access-approval-policy/access-approval-policy-service";
import { accessApprovalRequestDALFactory } from "@app/ee/services/access-approval-request/access-approval-request-dal"; import { accessApprovalRequestDALFactory } from "@app/ee/services/access-approval-request/access-approval-request-dal";
@@ -67,7 +70,10 @@ import { samlConfigDALFactory } from "@app/ee/services/saml-config/saml-config-d
import { samlConfigServiceFactory } from "@app/ee/services/saml-config/saml-config-service"; import { samlConfigServiceFactory } from "@app/ee/services/saml-config/saml-config-service";
import { scimDALFactory } from "@app/ee/services/scim/scim-dal"; import { scimDALFactory } from "@app/ee/services/scim/scim-dal";
import { scimServiceFactory } from "@app/ee/services/scim/scim-service"; import { scimServiceFactory } from "@app/ee/services/scim/scim-service";
import { secretApprovalPolicyApproverDALFactory } from "@app/ee/services/secret-approval-policy/secret-approval-policy-approver-dal"; import {
secretApprovalPolicyApproverDALFactory,
secretApprovalPolicyBypasserDALFactory
} from "@app/ee/services/secret-approval-policy/secret-approval-policy-approver-dal";
import { secretApprovalPolicyDALFactory } from "@app/ee/services/secret-approval-policy/secret-approval-policy-dal"; import { secretApprovalPolicyDALFactory } from "@app/ee/services/secret-approval-policy/secret-approval-policy-dal";
import { secretApprovalPolicyServiceFactory } from "@app/ee/services/secret-approval-policy/secret-approval-policy-service"; import { secretApprovalPolicyServiceFactory } from "@app/ee/services/secret-approval-policy/secret-approval-policy-service";
import { secretApprovalRequestDALFactory } from "@app/ee/services/secret-approval-request/secret-approval-request-dal"; import { secretApprovalRequestDALFactory } from "@app/ee/services/secret-approval-request/secret-approval-request-dal";
@@ -205,6 +211,8 @@ import { pkiCollectionServiceFactory } from "@app/services/pki-collection/pki-co
import { pkiSubscriberDALFactory } from "@app/services/pki-subscriber/pki-subscriber-dal"; import { pkiSubscriberDALFactory } from "@app/services/pki-subscriber/pki-subscriber-dal";
import { pkiSubscriberQueueServiceFactory } from "@app/services/pki-subscriber/pki-subscriber-queue"; import { pkiSubscriberQueueServiceFactory } from "@app/services/pki-subscriber/pki-subscriber-queue";
import { pkiSubscriberServiceFactory } from "@app/services/pki-subscriber/pki-subscriber-service"; import { pkiSubscriberServiceFactory } from "@app/services/pki-subscriber/pki-subscriber-service";
import { pkiTemplatesDALFactory } from "@app/services/pki-templates/pki-templates-dal";
import { pkiTemplatesServiceFactory } from "@app/services/pki-templates/pki-templates-service";
import { projectDALFactory } from "@app/services/project/project-dal"; import { projectDALFactory } from "@app/services/project/project-dal";
import { projectQueueFactory } from "@app/services/project/project-queue"; import { projectQueueFactory } from "@app/services/project/project-queue";
import { projectServiceFactory } from "@app/services/project/project-service"; import { projectServiceFactory } from "@app/services/project/project-service";
@@ -385,9 +393,11 @@ export const registerRoutes = async (
const accessApprovalPolicyDAL = accessApprovalPolicyDALFactory(db); const accessApprovalPolicyDAL = accessApprovalPolicyDALFactory(db);
const accessApprovalRequestDAL = accessApprovalRequestDALFactory(db); const accessApprovalRequestDAL = accessApprovalRequestDALFactory(db);
const accessApprovalPolicyApproverDAL = accessApprovalPolicyApproverDALFactory(db); const accessApprovalPolicyApproverDAL = accessApprovalPolicyApproverDALFactory(db);
const accessApprovalPolicyBypasserDAL = accessApprovalPolicyBypasserDALFactory(db);
const accessApprovalRequestReviewerDAL = accessApprovalRequestReviewerDALFactory(db); const accessApprovalRequestReviewerDAL = accessApprovalRequestReviewerDALFactory(db);
const sapApproverDAL = secretApprovalPolicyApproverDALFactory(db); const sapApproverDAL = secretApprovalPolicyApproverDALFactory(db);
const sapBypasserDAL = secretApprovalPolicyBypasserDALFactory(db);
const secretApprovalPolicyDAL = secretApprovalPolicyDALFactory(db); const secretApprovalPolicyDAL = secretApprovalPolicyDALFactory(db);
const secretApprovalRequestDAL = secretApprovalRequestDALFactory(db); const secretApprovalRequestDAL = secretApprovalRequestDALFactory(db);
const secretApprovalRequestReviewerDAL = secretApprovalRequestReviewerDALFactory(db); const secretApprovalRequestReviewerDAL = secretApprovalRequestReviewerDALFactory(db);
@@ -519,6 +529,7 @@ export const registerRoutes = async (
const secretApprovalPolicyService = secretApprovalPolicyServiceFactory({ const secretApprovalPolicyService = secretApprovalPolicyServiceFactory({
projectEnvDAL, projectEnvDAL,
secretApprovalPolicyApproverDAL: sapApproverDAL, secretApprovalPolicyApproverDAL: sapApproverDAL,
secretApprovalPolicyBypasserDAL: sapBypasserDAL,
permissionService, permissionService,
secretApprovalPolicyDAL, secretApprovalPolicyDAL,
licenseService, licenseService,
@@ -730,12 +741,14 @@ export const registerRoutes = async (
userAliasDAL, userAliasDAL,
identityTokenAuthDAL, identityTokenAuthDAL,
identityAccessTokenDAL, identityAccessTokenDAL,
orgMembershipDAL,
identityOrgMembershipDAL, identityOrgMembershipDAL,
authService: loginService, authService: loginService,
serverCfgDAL: superAdminDAL, serverCfgDAL: superAdminDAL,
kmsRootConfigDAL, kmsRootConfigDAL,
orgService, orgService,
keyStore, keyStore,
orgDAL,
licenseService, licenseService,
kmsService, kmsService,
microsoftTeamsService, microsoftTeamsService,
@@ -794,7 +807,8 @@ export const registerRoutes = async (
const projectUserAdditionalPrivilegeService = projectUserAdditionalPrivilegeServiceFactory({ const projectUserAdditionalPrivilegeService = projectUserAdditionalPrivilegeServiceFactory({
permissionService, permissionService,
projectMembershipDAL, projectMembershipDAL,
projectUserAdditionalPrivilegeDAL projectUserAdditionalPrivilegeDAL,
accessApprovalRequestDAL
}); });
const projectKeyService = projectKeyServiceFactory({ const projectKeyService = projectKeyServiceFactory({
permissionService, permissionService,
@@ -838,6 +852,7 @@ export const registerRoutes = async (
const pkiCollectionDAL = pkiCollectionDALFactory(db); const pkiCollectionDAL = pkiCollectionDALFactory(db);
const pkiCollectionItemDAL = pkiCollectionItemDALFactory(db); const pkiCollectionItemDAL = pkiCollectionItemDALFactory(db);
const pkiSubscriberDAL = pkiSubscriberDALFactory(db); const pkiSubscriberDAL = pkiSubscriberDALFactory(db);
const pkiTemplatesDAL = pkiTemplatesDALFactory(db);
const certificateService = certificateServiceFactory({ const certificateService = certificateServiceFactory({
certificateDAL, certificateDAL,
@@ -1218,6 +1233,7 @@ export const registerRoutes = async (
const accessApprovalPolicyService = accessApprovalPolicyServiceFactory({ const accessApprovalPolicyService = accessApprovalPolicyServiceFactory({
accessApprovalPolicyDAL, accessApprovalPolicyDAL,
accessApprovalPolicyApproverDAL, accessApprovalPolicyApproverDAL,
accessApprovalPolicyBypasserDAL,
groupDAL, groupDAL,
permissionService, permissionService,
projectEnvDAL, projectEnvDAL,
@@ -1226,7 +1242,8 @@ export const registerRoutes = async (
userDAL, userDAL,
accessApprovalRequestDAL, accessApprovalRequestDAL,
additionalPrivilegeDAL: projectUserAdditionalPrivilegeDAL, additionalPrivilegeDAL: projectUserAdditionalPrivilegeDAL,
accessApprovalRequestReviewerDAL accessApprovalRequestReviewerDAL,
orgMembershipDAL
}); });
const accessApprovalRequestService = accessApprovalRequestServiceFactory({ const accessApprovalRequestService = accessApprovalRequestServiceFactory({
@@ -1743,6 +1760,21 @@ export const registerRoutes = async (
internalCaFns internalCaFns
}); });
const pkiTemplateService = pkiTemplatesServiceFactory({
pkiTemplatesDAL,
certificateAuthorityDAL,
certificateAuthorityCertDAL,
certificateAuthoritySecretDAL,
certificateAuthorityCrlDAL,
certificateDAL,
certificateBodyDAL,
certificateSecretDAL,
projectDAL,
kmsService,
permissionService,
internalCaFns
});
await secretRotationV2QueueServiceFactory({ await secretRotationV2QueueServiceFactory({
secretRotationV2Service, secretRotationV2Service,
secretRotationV2DAL, secretRotationV2DAL,
@@ -1836,6 +1868,7 @@ export const registerRoutes = async (
pkiAlert: pkiAlertService, pkiAlert: pkiAlertService,
pkiCollection: pkiCollectionService, pkiCollection: pkiCollectionService,
pkiSubscriber: pkiSubscriberService, pkiSubscriber: pkiSubscriberService,
pkiTemplate: pkiTemplateService,
secretScanning: secretScanningService, secretScanning: secretScanningService,
license: licenseService, license: licenseService,
trustedIp: trustedIpService, trustedIp: trustedIpService,

View File

@@ -235,11 +235,9 @@ export const SanitizedDynamicSecretSchema = DynamicSecretsSchema.omit({
inputIV: true, inputIV: true,
inputTag: true, inputTag: true,
algorithm: true algorithm: true
}).merge( }).extend({
z.object({
metadata: ResourceMetadataSchema.optional() metadata: ResourceMetadataSchema.optional()
}) });
);
export const SanitizedAuditLogStreamSchema = z.object({ export const SanitizedAuditLogStreamSchema = z.object({
id: z.string(), id: z.string(),

View File

@@ -1,7 +1,13 @@
import DOMPurify from "isomorphic-dompurify"; import DOMPurify from "isomorphic-dompurify";
import { z } from "zod"; import { z } from "zod";
import { IdentitiesSchema, OrganizationsSchema, SuperAdminSchema, UsersSchema } from "@app/db/schemas"; import {
IdentitiesSchema,
OrganizationsSchema,
OrgMembershipsSchema,
SuperAdminSchema,
UsersSchema
} from "@app/db/schemas";
import { getConfig } from "@app/lib/config/env"; import { getConfig } from "@app/lib/config/env";
import { BadRequestError } from "@app/lib/errors"; import { BadRequestError } from "@app/lib/errors";
import { invalidateCacheLimit, readLimit, writeLimit } from "@app/server/config/rateLimiter"; import { invalidateCacheLimit, readLimit, writeLimit } from "@app/server/config/rateLimiter";
@@ -161,6 +167,129 @@ export const registerAdminRouter = async (server: FastifyZodProvider) => {
} }
}); });
server.route({
method: "GET",
url: "/organization-management/organizations",
config: {
rateLimit: readLimit
},
schema: {
querystring: z.object({
searchTerm: z.string().default(""),
offset: z.coerce.number().default(0),
limit: z.coerce.number().max(100).default(20)
}),
response: {
200: z.object({
organizations: OrganizationsSchema.extend({
members: z
.object({
user: z.object({
id: z.string(),
email: z.string().nullish(),
username: z.string(),
firstName: z.string().nullish(),
lastName: z.string().nullish()
}),
membershipId: z.string(),
role: z.string(),
roleId: z.string().nullish()
})
.array(),
projects: z
.object({
name: z.string(),
id: z.string(),
slug: z.string(),
createdAt: z.date()
})
.array()
}).array()
})
}
},
onRequest: (req, res, done) => {
verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN])(req, res, () => {
verifySuperAdmin(req, res, done);
});
},
handler: async (req) => {
const organizations = await server.services.superAdmin.getOrganizations({
...req.query
});
return {
organizations
};
}
});
server.route({
method: "DELETE",
url: "/organization-management/organizations/:organizationId/memberships/:membershipId",
config: {
rateLimit: writeLimit
},
schema: {
params: z.object({
organizationId: z.string(),
membershipId: z.string()
}),
response: {
200: z.object({
organizationMembership: OrgMembershipsSchema
})
}
},
onRequest: (req, res, done) => {
verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN])(req, res, () => {
verifySuperAdmin(req, res, done);
});
},
handler: async (req) => {
const organizationMembership = await server.services.superAdmin.deleteOrganizationMembership(
req.params.organizationId,
req.params.membershipId,
req.permission.id,
req.permission.type
);
return {
organizationMembership
};
}
});
server.route({
method: "DELETE",
url: "/organization-management/organizations/:organizationId",
config: {
rateLimit: writeLimit
},
schema: {
params: z.object({
organizationId: z.string()
}),
response: {
200: z.object({
organization: OrganizationsSchema
})
}
},
onRequest: (req, res, done) => {
verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN])(req, res, () => {
verifySuperAdmin(req, res, done);
});
},
handler: async (req) => {
const organization = await server.services.superAdmin.deleteOrganization(req.params.organizationId);
return {
organization
};
}
});
server.route({ server.route({
method: "GET", method: "GET",
url: "/identity-management/identities", url: "/identity-management/identities",

View File

@@ -43,6 +43,7 @@ import {
} from "@app/services/app-connection/humanitec"; } from "@app/services/app-connection/humanitec";
import { LdapConnectionListItemSchema, SanitizedLdapConnectionSchema } from "@app/services/app-connection/ldap"; import { LdapConnectionListItemSchema, SanitizedLdapConnectionSchema } from "@app/services/app-connection/ldap";
import { MsSqlConnectionListItemSchema, SanitizedMsSqlConnectionSchema } from "@app/services/app-connection/mssql"; import { MsSqlConnectionListItemSchema, SanitizedMsSqlConnectionSchema } from "@app/services/app-connection/mssql";
import { MySqlConnectionListItemSchema, SanitizedMySqlConnectionSchema } from "@app/services/app-connection/mysql";
import { import {
PostgresConnectionListItemSchema, PostgresConnectionListItemSchema,
SanitizedPostgresConnectionSchema SanitizedPostgresConnectionSchema
@@ -75,6 +76,7 @@ const SanitizedAppConnectionSchema = z.union([
...SanitizedVercelConnectionSchema.options, ...SanitizedVercelConnectionSchema.options,
...SanitizedPostgresConnectionSchema.options, ...SanitizedPostgresConnectionSchema.options,
...SanitizedMsSqlConnectionSchema.options, ...SanitizedMsSqlConnectionSchema.options,
...SanitizedMySqlConnectionSchema.options,
...SanitizedCamundaConnectionSchema.options, ...SanitizedCamundaConnectionSchema.options,
...SanitizedAuth0ConnectionSchema.options, ...SanitizedAuth0ConnectionSchema.options,
...SanitizedHCVaultConnectionSchema.options, ...SanitizedHCVaultConnectionSchema.options,
@@ -98,6 +100,7 @@ const AppConnectionOptionsSchema = z.discriminatedUnion("app", [
VercelConnectionListItemSchema, VercelConnectionListItemSchema,
PostgresConnectionListItemSchema, PostgresConnectionListItemSchema,
MsSqlConnectionListItemSchema, MsSqlConnectionListItemSchema,
MySqlConnectionListItemSchema,
CamundaConnectionListItemSchema, CamundaConnectionListItemSchema,
Auth0ConnectionListItemSchema, Auth0ConnectionListItemSchema,
HCVaultConnectionListItemSchema, HCVaultConnectionListItemSchema,

View File

@@ -15,6 +15,7 @@ import { registerHCVaultConnectionRouter } from "./hc-vault-connection-router";
import { registerHumanitecConnectionRouter } from "./humanitec-connection-router"; import { registerHumanitecConnectionRouter } from "./humanitec-connection-router";
import { registerLdapConnectionRouter } from "./ldap-connection-router"; import { registerLdapConnectionRouter } from "./ldap-connection-router";
import { registerMsSqlConnectionRouter } from "./mssql-connection-router"; import { registerMsSqlConnectionRouter } from "./mssql-connection-router";
import { registerMySqlConnectionRouter } from "./mysql-connection-router";
import { registerPostgresConnectionRouter } from "./postgres-connection-router"; import { registerPostgresConnectionRouter } from "./postgres-connection-router";
import { registerTeamCityConnectionRouter } from "./teamcity-connection-router"; import { registerTeamCityConnectionRouter } from "./teamcity-connection-router";
import { registerTerraformCloudConnectionRouter } from "./terraform-cloud-router"; import { registerTerraformCloudConnectionRouter } from "./terraform-cloud-router";
@@ -37,6 +38,7 @@ export const APP_CONNECTION_REGISTER_ROUTER_MAP: Record<AppConnection, (server:
[AppConnection.Vercel]: registerVercelConnectionRouter, [AppConnection.Vercel]: registerVercelConnectionRouter,
[AppConnection.Postgres]: registerPostgresConnectionRouter, [AppConnection.Postgres]: registerPostgresConnectionRouter,
[AppConnection.MsSql]: registerMsSqlConnectionRouter, [AppConnection.MsSql]: registerMsSqlConnectionRouter,
[AppConnection.MySql]: registerMySqlConnectionRouter,
[AppConnection.Camunda]: registerCamundaConnectionRouter, [AppConnection.Camunda]: registerCamundaConnectionRouter,
[AppConnection.Windmill]: registerWindmillConnectionRouter, [AppConnection.Windmill]: registerWindmillConnectionRouter,
[AppConnection.Auth0]: registerAuth0ConnectionRouter, [AppConnection.Auth0]: registerAuth0ConnectionRouter,

View File

@@ -0,0 +1,18 @@
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
import {
CreateMySqlConnectionSchema,
SanitizedMySqlConnectionSchema,
UpdateMySqlConnectionSchema
} from "@app/services/app-connection/mysql";
import { registerAppConnectionEndpoints } from "./app-connection-endpoints";
export const registerMySqlConnectionRouter = async (server: FastifyZodProvider) => {
registerAppConnectionEndpoints({
app: AppConnection.MySql,
server,
sanitizedResponseSchema: SanitizedMySqlConnectionSchema,
createSchema: CreateMySqlConnectionSchema,
updateSchema: UpdateMySqlConnectionSchema
});
};

View File

@@ -5,6 +5,7 @@ import { EventType } from "@app/ee/services/audit-log/audit-log-types";
import { ApiDocsTags, CERTIFICATE_TEMPLATES } from "@app/lib/api-docs"; import { ApiDocsTags, CERTIFICATE_TEMPLATES } from "@app/lib/api-docs";
import { ms } from "@app/lib/ms"; import { ms } from "@app/lib/ms";
import { readLimit, writeLimit } from "@app/server/config/rateLimiter"; import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
import { slugSchema } from "@app/server/lib/schemas";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type"; import { AuthMode } from "@app/services/auth/auth-type";
import { CertExtendedKeyUsage, CertKeyUsage } from "@app/services/certificate/certificate-types"; import { CertExtendedKeyUsage, CertKeyUsage } from "@app/services/certificate/certificate-types";
@@ -72,7 +73,7 @@ export const registerCertificateTemplateRouter = async (server: FastifyZodProvid
body: z.object({ body: z.object({
caId: z.string().describe(CERTIFICATE_TEMPLATES.CREATE.caId), caId: z.string().describe(CERTIFICATE_TEMPLATES.CREATE.caId),
pkiCollectionId: z.string().optional().describe(CERTIFICATE_TEMPLATES.CREATE.pkiCollectionId), pkiCollectionId: z.string().optional().describe(CERTIFICATE_TEMPLATES.CREATE.pkiCollectionId),
name: z.string().min(1).describe(CERTIFICATE_TEMPLATES.CREATE.name), name: slugSchema().describe(CERTIFICATE_TEMPLATES.CREATE.name),
commonName: validateTemplateRegexField.describe(CERTIFICATE_TEMPLATES.CREATE.commonName), commonName: validateTemplateRegexField.describe(CERTIFICATE_TEMPLATES.CREATE.commonName),
subjectAlternativeName: validateTemplateRegexField.describe( subjectAlternativeName: validateTemplateRegexField.describe(
CERTIFICATE_TEMPLATES.CREATE.subjectAlternativeName CERTIFICATE_TEMPLATES.CREATE.subjectAlternativeName
@@ -141,7 +142,7 @@ export const registerCertificateTemplateRouter = async (server: FastifyZodProvid
body: z.object({ body: z.object({
caId: z.string().optional().describe(CERTIFICATE_TEMPLATES.UPDATE.caId), caId: z.string().optional().describe(CERTIFICATE_TEMPLATES.UPDATE.caId),
pkiCollectionId: z.string().optional().describe(CERTIFICATE_TEMPLATES.UPDATE.pkiCollectionId), pkiCollectionId: z.string().optional().describe(CERTIFICATE_TEMPLATES.UPDATE.pkiCollectionId),
name: z.string().min(1).optional().describe(CERTIFICATE_TEMPLATES.UPDATE.name), name: slugSchema().optional().describe(CERTIFICATE_TEMPLATES.UPDATE.name),
commonName: validateTemplateRegexField.optional().describe(CERTIFICATE_TEMPLATES.UPDATE.commonName), commonName: validateTemplateRegexField.optional().describe(CERTIFICATE_TEMPLATES.UPDATE.commonName),
subjectAlternativeName: validateTemplateRegexField subjectAlternativeName: validateTemplateRegexField
.optional() .optional()

View File

@@ -47,8 +47,15 @@ export const registerIdentityUaRouter = async (server: FastifyZodProvider) => {
} }
}, },
handler: async (req) => { handler: async (req) => {
const { identityUa, accessToken, identityAccessToken, validClientSecretInfo, identityMembershipOrg } = const {
await server.services.identityUa.login(req.body.clientId, req.body.clientSecret, req.realIp); identityUa,
accessToken,
identityAccessToken,
validClientSecretInfo,
identityMembershipOrg,
accessTokenTTL,
accessTokenMaxTTL
} = await server.services.identityUa.login(req.body.clientId, req.body.clientSecret, req.realIp);
await server.services.auditLog.createAuditLog({ await server.services.auditLog.createAuditLog({
...req.auditLogInfo, ...req.auditLogInfo,
@@ -63,11 +70,12 @@ export const registerIdentityUaRouter = async (server: FastifyZodProvider) => {
} }
} }
}); });
return { return {
accessToken, accessToken,
tokenType: "Bearer" as const, tokenType: "Bearer" as const,
expiresIn: identityUa.accessTokenTTL, expiresIn: accessTokenTTL,
accessTokenMaxTTL: identityUa.accessTokenMaxTTL accessTokenMaxTTL
}; };
} }
}); });
@@ -128,7 +136,8 @@ export const registerIdentityUaRouter = async (server: FastifyZodProvider) => {
.int() .int()
.min(0) .min(0)
.default(0) .default(0)
.describe(UNIVERSAL_AUTH.ATTACH.accessTokenNumUsesLimit) .describe(UNIVERSAL_AUTH.ATTACH.accessTokenNumUsesLimit),
accessTokenPeriod: z.number().int().min(0).default(0).describe(UNIVERSAL_AUTH.ATTACH.accessTokenPeriod)
}) })
.refine( .refine(
(val) => val.accessTokenTTL <= val.accessTokenMaxTTL, (val) => val.accessTokenTTL <= val.accessTokenMaxTTL,
@@ -227,7 +236,14 @@ export const registerIdentityUaRouter = async (server: FastifyZodProvider) => {
.min(0) .min(0)
.max(315360000) .max(315360000)
.optional() .optional()
.describe(UNIVERSAL_AUTH.UPDATE.accessTokenMaxTTL) .describe(UNIVERSAL_AUTH.UPDATE.accessTokenMaxTTL),
accessTokenPeriod: z
.number()
.int()
.min(0)
.max(315360000)
.optional()
.describe(UNIVERSAL_AUTH.UPDATE.accessTokenPeriod)
}) })
.refine( .refine(
(val) => (val.accessTokenMaxTTL && val.accessTokenTTL ? val.accessTokenTTL <= val.accessTokenMaxTTL : true), (val) => (val.accessTokenMaxTTL && val.accessTokenTTL ? val.accessTokenTTL <= val.accessTokenMaxTTL : true),

View File

@@ -62,9 +62,7 @@ export const registerSecretSharingRouter = async (server: FastifyZodProvider) =>
}), }),
body: z.object({ body: z.object({
hashedHex: z.string().min(1).optional(), hashedHex: z.string().min(1).optional(),
password: z.string().optional(), password: z.string().optional()
email: z.string().optional(),
hash: z.string().optional()
}), }),
response: { response: {
200: z.object({ 200: z.object({
@@ -91,8 +89,7 @@ export const registerSecretSharingRouter = async (server: FastifyZodProvider) =>
hashedHex: req.body.hashedHex, hashedHex: req.body.hashedHex,
password: req.body.password, password: req.body.password,
orgId: req.permission?.orgId, orgId: req.permission?.orgId,
email: req.body.email, actorId: req.permission?.id
hash: req.body.hash
}); });
if (sharedSecret.secret?.orgId) { if (sharedSecret.secret?.orgId) {
@@ -156,7 +153,13 @@ export const registerSecretSharingRouter = async (server: FastifyZodProvider) =>
expiresAt: z.string(), expiresAt: z.string(),
expiresAfterViews: z.number().min(1).optional(), expiresAfterViews: z.number().min(1).optional(),
accessType: z.nativeEnum(SecretSharingAccessType).default(SecretSharingAccessType.Organization), accessType: z.nativeEnum(SecretSharingAccessType).default(SecretSharingAccessType.Organization),
emails: z.string().email().array().max(100).optional() emails: z
.string()
.email()
.array()
.max(100)
.optional()
.transform((val) => (val ? [...new Set(val)] : undefined))
}), }),
response: { response: {
200: z.object({ 200: z.object({

View File

@@ -5,6 +5,7 @@ import { registerIdentityProjectRouter } from "./identity-project-router";
import { registerMfaRouter } from "./mfa-router"; import { registerMfaRouter } from "./mfa-router";
import { registerOrgRouter } from "./organization-router"; import { registerOrgRouter } from "./organization-router";
import { registerPasswordRouter } from "./password-router"; import { registerPasswordRouter } from "./password-router";
import { registerPkiTemplatesRouter } from "./pki-templates-router";
import { registerProjectMembershipRouter } from "./project-membership-router"; import { registerProjectMembershipRouter } from "./project-membership-router";
import { registerProjectRouter } from "./project-router"; import { registerProjectRouter } from "./project-router";
import { registerServiceTokenRouter } from "./service-token-router"; import { registerServiceTokenRouter } from "./service-token-router";
@@ -15,7 +16,15 @@ export const registerV2Routes = async (server: FastifyZodProvider) => {
await server.register(registerUserRouter, { prefix: "/users" }); await server.register(registerUserRouter, { prefix: "/users" });
await server.register(registerServiceTokenRouter, { prefix: "/service-token" }); await server.register(registerServiceTokenRouter, { prefix: "/service-token" });
await server.register(registerPasswordRouter, { prefix: "/password" }); await server.register(registerPasswordRouter, { prefix: "/password" });
await server.register(registerCaRouter, { prefix: "/pki/ca" });
await server.register(
async (pkiRouter) => {
await pkiRouter.register(registerCaRouter, { prefix: "/ca" });
await pkiRouter.register(registerPkiTemplatesRouter, { prefix: "/certificate-templates" });
},
{ prefix: "/pki" }
);
await server.register( await server.register(
async (orgRouter) => { async (orgRouter) => {
await orgRouter.register(registerOrgRouter); await orgRouter.register(registerOrgRouter);

View File

@@ -0,0 +1,309 @@
import { z } from "zod";
import { CertificateTemplatesSchema } from "@app/db/schemas";
import { ApiDocsTags } from "@app/lib/api-docs";
import { ms } from "@app/lib/ms";
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
import { slugSchema } from "@app/server/lib/schemas";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type";
import { CertExtendedKeyUsage, CertKeyUsage } from "@app/services/certificate/certificate-types";
import {
validateAltNamesField,
validateCaDateField
} from "@app/services/certificate-authority/certificate-authority-validators";
import { validateTemplateRegexField } from "@app/services/certificate-template/certificate-template-validators";
export const registerPkiTemplatesRouter = async (server: FastifyZodProvider) => {
server.route({
method: "POST",
url: "/",
config: {
rateLimit: writeLimit
},
schema: {
hide: false,
tags: [ApiDocsTags.PkiCertificateTemplates],
body: z.object({
name: slugSchema(),
caName: slugSchema({ field: "caName" }),
projectId: z.string(),
commonName: validateTemplateRegexField,
subjectAlternativeName: validateTemplateRegexField,
ttl: z.string().refine((val) => ms(val) > 0, "TTL must be a positive number"),
keyUsages: z
.nativeEnum(CertKeyUsage)
.array()
.optional()
.default([CertKeyUsage.DIGITAL_SIGNATURE, CertKeyUsage.KEY_ENCIPHERMENT]),
extendedKeyUsages: z.nativeEnum(CertExtendedKeyUsage).array().optional().default([])
}),
response: {
200: z.object({
certificateTemplate: CertificateTemplatesSchema
})
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const certificateTemplate = await server.services.pkiTemplate.createTemplate({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
...req.body
});
return { certificateTemplate };
}
});
server.route({
method: "PATCH",
url: "/:templateName",
config: {
rateLimit: writeLimit
},
schema: {
hide: false,
tags: [ApiDocsTags.PkiCertificateTemplates],
params: z.object({
templateName: slugSchema()
}),
body: z.object({
name: slugSchema().optional(),
caName: slugSchema(),
projectId: z.string(),
commonName: validateTemplateRegexField.optional(),
subjectAlternativeName: validateTemplateRegexField.optional(),
ttl: z
.string()
.refine((val) => ms(val) > 0, "TTL must be a positive number")
.optional(),
keyUsages: z
.nativeEnum(CertKeyUsage)
.array()
.optional()
.default([CertKeyUsage.DIGITAL_SIGNATURE, CertKeyUsage.KEY_ENCIPHERMENT]),
extendedKeyUsages: z.nativeEnum(CertExtendedKeyUsage).array().optional().default([])
}),
response: {
200: z.object({
certificateTemplate: CertificateTemplatesSchema
})
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const certificateTemplate = await server.services.pkiTemplate.updateTemplate({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
templateName: req.params.templateName,
...req.body
});
return { certificateTemplate };
}
});
server.route({
method: "DELETE",
url: "/:templateName",
config: {
rateLimit: writeLimit
},
schema: {
hide: false,
tags: [ApiDocsTags.PkiCertificateTemplates],
params: z.object({
templateName: z.string().min(1)
}),
body: z.object({
projectId: z.string()
}),
response: {
200: z.object({
certificateTemplate: CertificateTemplatesSchema
})
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const certificateTemplate = await server.services.pkiTemplate.deleteTemplate({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
templateName: req.params.templateName,
projectId: req.body.projectId
});
return { certificateTemplate };
}
});
server.route({
method: "GET",
url: "/:templateName",
config: {
rateLimit: readLimit
},
schema: {
hide: false,
tags: [ApiDocsTags.PkiCertificateTemplates],
params: z.object({
templateName: slugSchema()
}),
querystring: z.object({
projectId: z.string()
}),
response: {
200: z.object({
certificateTemplate: CertificateTemplatesSchema.extend({
ca: z.object({ id: z.string(), name: z.string() })
})
})
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const certificateTemplate = await server.services.pkiTemplate.getTemplateByName({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
templateName: req.params.templateName,
projectId: req.query.projectId
});
return { certificateTemplate };
}
});
server.route({
method: "GET",
url: "/",
config: {
rateLimit: readLimit
},
schema: {
hide: false,
tags: [ApiDocsTags.PkiCertificateTemplates],
querystring: z.object({
projectId: z.string(),
limit: z.coerce.number().default(100),
offset: z.coerce.number().default(0)
}),
response: {
200: z.object({
certificateTemplates: CertificateTemplatesSchema.extend({
ca: z.object({ id: z.string(), name: z.string() })
}).array(),
totalCount: z.number()
})
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const { certificateTemplates, totalCount } = await server.services.pkiTemplate.listTemplate({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
...req.query
});
return { certificateTemplates, totalCount };
}
});
server.route({
method: "POST",
url: "/:templateName/issue-certificate",
config: {
rateLimit: writeLimit
},
schema: {
hide: false,
tags: [ApiDocsTags.PkiCertificateTemplates],
params: z.object({
templateName: slugSchema()
}),
body: z.object({
projectId: z.string(),
commonName: validateTemplateRegexField,
ttl: z.string().refine((val) => ms(val) > 0, "TTL must be a positive number"),
keyUsages: z.nativeEnum(CertKeyUsage).array().optional(),
extendedKeyUsages: z.nativeEnum(CertExtendedKeyUsage).array().optional(),
notBefore: validateCaDateField.optional(),
notAfter: validateCaDateField.optional(),
altNames: validateAltNamesField
}),
response: {
200: z.object({
certificate: z.string().trim(),
issuingCaCertificate: z.string().trim(),
certificateChain: z.string().trim(),
privateKey: z.string().trim(),
serialNumber: z.string().trim()
})
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const data = await server.services.pkiTemplate.issueCertificate({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
templateName: req.params.templateName,
...req.body
});
return data;
}
});
server.route({
method: "POST",
url: "/:templateName/sign-certificate",
config: {
rateLimit: writeLimit
},
schema: {
hide: false,
tags: [ApiDocsTags.PkiCertificateTemplates],
params: z.object({
templateName: slugSchema()
}),
body: z.object({
projectId: z.string(),
ttl: z.string().refine((val) => ms(val) > 0, "TTL must be a positive number"),
csr: z.string().trim().min(1).max(4096)
}),
response: {
200: z.object({
certificate: z.string().trim(),
issuingCaCertificate: z.string().trim(),
certificateChain: z.string().trim(),
serialNumber: z.string().trim()
})
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const data = await server.services.pkiTemplate.signCertificate({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
templateName: req.params.templateName,
...req.body
});
return data;
}
});
};

View File

@@ -11,6 +11,7 @@ export enum AppConnection {
Vercel = "vercel", Vercel = "vercel",
Postgres = "postgres", Postgres = "postgres",
MsSql = "mssql", MsSql = "mssql",
MySql = "mysql",
Camunda = "camunda", Camunda = "camunda",
Windmill = "windmill", Windmill = "windmill",
Auth0 = "auth0", Auth0 = "auth0",

View File

@@ -64,6 +64,8 @@ import {
} from "./humanitec"; } from "./humanitec";
import { getLdapConnectionListItem, LdapConnectionMethod, validateLdapConnectionCredentials } from "./ldap"; import { getLdapConnectionListItem, LdapConnectionMethod, validateLdapConnectionCredentials } from "./ldap";
import { getMsSqlConnectionListItem, MsSqlConnectionMethod } from "./mssql"; import { getMsSqlConnectionListItem, MsSqlConnectionMethod } from "./mssql";
import { MySqlConnectionMethod } from "./mysql/mysql-connection-enums";
import { getMySqlConnectionListItem } from "./mysql/mysql-connection-fns";
import { getPostgresConnectionListItem, PostgresConnectionMethod } from "./postgres"; import { getPostgresConnectionListItem, PostgresConnectionMethod } from "./postgres";
import { import {
getTeamCityConnectionListItem, getTeamCityConnectionListItem,
@@ -96,6 +98,7 @@ export const listAppConnectionOptions = () => {
getVercelConnectionListItem(), getVercelConnectionListItem(),
getPostgresConnectionListItem(), getPostgresConnectionListItem(),
getMsSqlConnectionListItem(), getMsSqlConnectionListItem(),
getMySqlConnectionListItem(),
getCamundaConnectionListItem(), getCamundaConnectionListItem(),
getAzureClientSecretsConnectionListItem(), getAzureClientSecretsConnectionListItem(),
getWindmillConnectionListItem(), getWindmillConnectionListItem(),
@@ -166,6 +169,7 @@ export const validateAppConnectionCredentials = async (
[AppConnection.Humanitec]: validateHumanitecConnectionCredentials as TAppConnectionCredentialsValidator, [AppConnection.Humanitec]: validateHumanitecConnectionCredentials as TAppConnectionCredentialsValidator,
[AppConnection.Postgres]: validateSqlConnectionCredentials as TAppConnectionCredentialsValidator, [AppConnection.Postgres]: validateSqlConnectionCredentials as TAppConnectionCredentialsValidator,
[AppConnection.MsSql]: validateSqlConnectionCredentials as TAppConnectionCredentialsValidator, [AppConnection.MsSql]: validateSqlConnectionCredentials as TAppConnectionCredentialsValidator,
[AppConnection.MySql]: validateSqlConnectionCredentials as TAppConnectionCredentialsValidator,
[AppConnection.Camunda]: validateCamundaConnectionCredentials as TAppConnectionCredentialsValidator, [AppConnection.Camunda]: validateCamundaConnectionCredentials as TAppConnectionCredentialsValidator,
[AppConnection.Vercel]: validateVercelConnectionCredentials as TAppConnectionCredentialsValidator, [AppConnection.Vercel]: validateVercelConnectionCredentials as TAppConnectionCredentialsValidator,
[AppConnection.TerraformCloud]: validateTerraformCloudConnectionCredentials as TAppConnectionCredentialsValidator, [AppConnection.TerraformCloud]: validateTerraformCloudConnectionCredentials as TAppConnectionCredentialsValidator,
@@ -208,6 +212,7 @@ export const getAppConnectionMethodName = (method: TAppConnection["method"]) =>
return "API Token"; return "API Token";
case PostgresConnectionMethod.UsernameAndPassword: case PostgresConnectionMethod.UsernameAndPassword:
case MsSqlConnectionMethod.UsernameAndPassword: case MsSqlConnectionMethod.UsernameAndPassword:
case MySqlConnectionMethod.UsernameAndPassword:
return "Username & Password"; return "Username & Password";
case WindmillConnectionMethod.AccessToken: case WindmillConnectionMethod.AccessToken:
case HCVaultConnectionMethod.AccessToken: case HCVaultConnectionMethod.AccessToken:
@@ -259,6 +264,7 @@ export const TRANSITION_CONNECTION_CREDENTIALS_TO_PLATFORM: Record<
[AppConnection.Humanitec]: platformManagedCredentialsNotSupported, [AppConnection.Humanitec]: platformManagedCredentialsNotSupported,
[AppConnection.Postgres]: transferSqlConnectionCredentialsToPlatform as TAppConnectionTransitionCredentialsToPlatform, [AppConnection.Postgres]: transferSqlConnectionCredentialsToPlatform as TAppConnectionTransitionCredentialsToPlatform,
[AppConnection.MsSql]: transferSqlConnectionCredentialsToPlatform as TAppConnectionTransitionCredentialsToPlatform, [AppConnection.MsSql]: transferSqlConnectionCredentialsToPlatform as TAppConnectionTransitionCredentialsToPlatform,
[AppConnection.MySql]: transferSqlConnectionCredentialsToPlatform as TAppConnectionTransitionCredentialsToPlatform,
[AppConnection.TerraformCloud]: platformManagedCredentialsNotSupported, [AppConnection.TerraformCloud]: platformManagedCredentialsNotSupported,
[AppConnection.Camunda]: platformManagedCredentialsNotSupported, [AppConnection.Camunda]: platformManagedCredentialsNotSupported,
[AppConnection.Vercel]: platformManagedCredentialsNotSupported, [AppConnection.Vercel]: platformManagedCredentialsNotSupported,

Some files were not shown because too many files have changed in this diff Show More