Compare commits

..

559 Commits

Author SHA1 Message Date
fec55bc9f8 fix greptile recs 2025-05-02 16:40:56 -04:00
47bb3c10fa Add identity-specific-privilege v2 API to docs
Add identity-specific-privilege v2 API to docs
2025-05-02 00:32:17 -04:00
5d44d58ff4 update postgres reqs 2025-04-30 17:53:41 -04:00
ff294dab8d Merge pull request #3507 from Infisical/feat/orgUserAuthTokenExpiration
feat(user-auth): make users auth token expiration customizable for orgs
2025-04-30 18:18:38 -03:00
c99440ba81 feat(user-auth): use ms library and update docs 2025-04-30 16:49:33 -03:00
6d5a6f42e0 Merge branch 'main' into feat/orgUserAuthTokenExpiration 2025-04-30 15:59:52 -03:00
0c027fdc43 Merge pull request #3516 from Infisical/feat/teamcity-root-project
remove _Root filter for projects
2025-04-30 12:07:24 -04:00
x
727a6a7701 remove _Root filter for projects 2025-04-30 10:31:40 -04:00
7f1f9e7fd0 Merge pull request #3491 from Infisical/feat/improveSecretReferenceWarning
feat(secrets-ui): Add direct reference warning on secrets updates and add secret sync warning on deletion
2025-04-30 08:17:55 -03:00
98f742a807 Merge pull request #3513 from Infisical/daniel/k8s-hsm-docs
docs: fix hsm kubernetes documentation
2025-04-30 06:10:30 +04:00
66f1967f88 Update hsm-integration.mdx 2025-04-30 05:37:55 +04:00
da6cf85c8d fix: remove log output file 2025-04-30 05:37:07 +04:00
e8b6eb0573 docs: fix hsm kubernetes documentation 2025-04-30 05:09:39 +04:00
03ad5c5db0 Merge pull request #3512 from Infisical/daniel/kms-docs
docs: prerequisite for aws key
2025-04-29 20:39:30 -04:00
e6c4c27a87 docs: added pre-req for aws key 2025-04-30 03:36:07 +04:00
d4ac4f8d8f Update CollapsibleSecretImports.tsx 2025-04-30 03:13:10 +04:00
f0229c5ecf feat(user-auth): fix migration bug for e2e suite 2025-04-29 18:48:08 -03:00
8d711af23b feat(secrets-ui): change secret sync icon color 2025-04-29 18:39:41 -03:00
7bd61d88fc feat(user-auth): improve token refresh logic and default values 2025-04-29 18:28:18 -03:00
ba94b91974 Merge pull request #3510 from Infisical/internal-ip-check-fix
fix(external-connections): Use Hostname for Blocking Internal IPs DNS Resolve
2025-04-29 12:37:46 -07:00
b65f62fda8 fix: use hostname for blocking internal IPs 2025-04-29 12:26:29 -07:00
c47d76a6c7 feat(secrets-ui): improve warning message table 2025-04-29 14:19:52 -03:00
9138a9e71d Merge pull request #3509 from Infisical/feat/teamcity-ignore-inherited-secrets
feat(secret-sync): TeamCity ignore inherited and non-env values
2025-04-29 12:49:01 -04:00
x
8e4ad8baf8 docs tweak 2025-04-29 12:43:44 -04:00
x
9f158d5b3f feat(docs): Added note stating that inherited secrets are ignored 2025-04-29 10:35:56 -04:00
x
0e1cb4ebb2 Merge branch 'main' into feat/teamcity-ignore-inherited-secrets 2025-04-29 10:31:51 -04:00
e959ed7fab feat(secrets-ui): improve warning message and logic for secret-sync on secret imports 2025-04-29 10:15:53 -03:00
4e4b1b689b Merge branch 'main' into feat/improveSecretReferenceWarning 2025-04-29 08:43:35 -03:00
8f07f43fbd Merge pull request #3504 from akhilmhdh/doc/assume-privilege
doc: added doc for assume privilege feature
2025-04-28 20:08:44 -07:00
023f5d1286 revise docs 2025-04-28 23:06:37 -04:00
72b03d4bdf Merge pull request #3506 from Infisical/daniel/build-strict-find-filter
feat: strict find filter
2025-04-29 05:41:39 +04:00
e870e35002 consolidated filtering functions into one 2025-04-29 04:27:10 +04:00
4544f621af Merge pull request #3478 from Infisical/fix/UISecretEditPermissionButNotReadValuePermission
fix(secrets-table): UI fix for users with edit permissions but not read secret value permission
2025-04-28 20:23:34 -03:00
x
ddb5098eda only sync non-inherited environment variables 2025-04-28 19:09:13 -04:00
35749e8d12 feat(user-auth): allow edit overwritter rotation value on overview table 2025-04-28 20:02:50 -03:00
024ed0c0d8 feat(user-auth): add pr suggestions 2025-04-28 18:19:44 -03:00
e99e360339 feat(user-auth): make users auth token expiration customizable for orgs 2025-04-28 17:43:10 -03:00
85965184f8 Update secret-v2-bridge-dal.ts 2025-04-29 00:18:13 +04:00
a1bbd50c0b feat: build strict find filter 2025-04-29 00:09:30 +04:00
f9c936865a feat(secrets-ui): minor improvements from PR suggestions 2025-04-28 16:49:29 -03:00
2be10b5f9d Merge pull request #3503 from Infisical/feat/add-support-for-eddsa-jwt-alg
feat: add support for eddsa jwt alg for oidc
2025-04-29 03:27:58 +08:00
3b6e35e13c Merge pull request #3505 from akhilmhdh/feat/cache-jitter
feat: increased secret caching to 10mins with jitter of 2min
2025-04-28 12:16:00 -07:00
=
fcf984965e feat: increased secret caching to 10mins with jitter of 2min 2025-04-29 00:36:39 +05:30
=
6bca854475 doc: added doc for assume privilege feature 2025-04-29 00:12:37 +05:30
a69ce50da9 Merge pull request #3495 from Infisical/ENG-2656
feat(login): Update all SSO login methods to use PKCE
2025-04-28 14:33:02 -04:00
1b798bd5d5 misc: fixed casing 2025-04-29 02:08:13 +08:00
bd3ebe75c9 feat: add support for eddsa jwt alg for oidc 2025-04-29 02:05:19 +08:00
0f2b8e4266 Update github-org-sync.mdx 2025-04-28 14:04:02 -04:00
x
c4ae8f2987 Remove false comment 2025-04-28 13:30:06 -04:00
x
b50a022d11 PKCE check logic fix 2025-04-28 13:28:47 -04:00
x
8a035c8d82 check if OIDC provider supports PKCE before applying it 2025-04-28 12:51:18 -04:00
4fa7ba2ec7 Merge branch 'main' into fix/UISecretEditPermissionButNotReadValuePermission 2025-04-28 13:33:05 -03:00
x
03d7f9f786 scope fix for google strategy 2025-04-28 12:17:04 -04:00
x
1b3e8b0a1c fixed merge conflicts 2025-04-28 10:52:12 -04:00
6a26a11cbb Merge pull request #3471 from Infisical/feat/add-support-for-org-sso-bypass-for-sso
feat: enabled sso (google, gitlab, github) to bypass org sso
2025-04-28 22:35:53 +08:00
d673c8d8e9 Merge pull request #3498 from akhilmhdh/feat/gh-sync
feat: github org sync
2025-04-28 07:26:07 -07:00
=
b39c7070b5 feat: linted merge issues 2025-04-28 19:51:10 +05:30
=
fa3dd03074 feat: updated review comments by @sheen 2025-04-28 19:48:57 +05:30
=
ee40ffd304 feat: changed get user to get org membership details 2025-04-28 19:48:56 +05:30
=
d3d76467ac feat: addressed rabbit and reptile feedback 2025-04-28 19:48:56 +05:30
=
58940f31e3 docs: added doc for github org sync 2025-04-28 19:48:56 +05:30
=
6d2175cf9f feat: completed github org sync 2025-04-28 19:48:56 +05:30
dbb0b28453 Merge pull request #3494 from Infisical/fix/moveablePermissionList
feat(project-permissions): allow users to sort permissions on the UI
2025-04-28 07:14:57 -07:00
225862aed8 Merge pull request #3453 from Infisical/daniel/reminders
feat(reminders): specify recipients
2025-04-28 18:14:23 +04:00
8d1bd6aabb Merge pull request #3447 from akhilmhdh/feat/assume-role
Implemented project permission impersonation
2025-04-28 06:59:09 -07:00
740c650441 fix import 2025-04-28 09:54:02 -04:00
78ccb5acb7 Merge pull request #3497 from Infisical/ssh-host-alias
Infisical SSH: Add Alias Field to SSH Hosts
2025-04-28 06:41:29 -07:00
e9aa8b317b Merge branch 'main' into feat/assume-role 2025-04-28 06:33:26 -07:00
=
7b42f666f9 feat: updated files on review changes 2025-04-28 18:56:17 +05:30
8a0cfa34d2 Merge pull request #3501 from Infisical/fix-kms-memory-leak
Fix KMS memory leak
2025-04-28 05:02:26 -07:00
ca9825c1fe remove unused logger 2025-04-28 07:59:00 -04:00
1dfc9511c1 throw only error and remove bool return 2025-04-28 07:55:33 -04:00
694ab35f53 Fix KMS memory leak
Adds a clean up method because KMS clients like GCP use a persistent connection snd if not closed, will continue to eat up the memory.
2025-04-28 07:48:31 -04:00
44ae0519d1 Revise ssh host alias field handling/validation 2025-04-27 14:34:26 -07:00
3d89a7f45d Revise ssh host alias PR 2025-04-26 18:18:22 -07:00
de63c8cb6c Add alias field to ssh hosts for improved ux 2025-04-26 18:04:21 -07:00
632572f7c3 Merge pull request #3452 from Infisical/ldaps-connection-and-password-rotation
Feature: LDAP Connection and Password Rotation
2025-04-26 09:13:08 -07:00
0a5f6274f5 Update CreateReminderForm.tsx 2025-04-26 05:56:11 +04:00
11ee13676d fix: deletion corner cases 2025-04-26 05:55:25 +04:00
e7783fe6cc requested changes & edge cases 2025-04-26 05:19:02 +04:00
a524690d01 deconflict merge 2025-04-25 17:20:30 -07:00
c229d6888c feat(secrets-ui): allow read access to personal overrides 2025-04-25 20:41:44 -03:00
2e459c161d feat(project-permissions): type fix 2025-04-25 19:51:08 -03:00
x
680f1a2230 Merge branch 'main' into ENG-2656 2025-04-25 18:46:05 -04:00
x
68e21ba8ce PKCE for Github, Gitlab, Google, and OIDC SSO 2025-04-25 18:45:23 -04:00
1e9722474f feat(project-permissions): allow users to sort permissions on the UI 2025-04-25 19:35:42 -03:00
f93edbb37f Merge pull request #3493 from Infisical/improve-aws-connection-error-propagation
improvement(app-connections): Improve AWS Connection Error Propagation
2025-04-25 15:25:55 -07:00
fa8154ecdd improvement: add undefined handling 2025-04-25 15:06:16 -07:00
d977092502 improvement: improve validate aws connection error propagation 2025-04-25 15:05:22 -07:00
f345801bd6 feat(secrets-ui): improve types and code quality 2025-04-25 18:17:33 -03:00
f460acf9b4 fix(secrets-permissions): Fix case for rotated secrets 2025-04-25 17:56:56 -03:00
4160009913 feat(secrets-ui): add direct reference warning on secrets updates 2025-04-25 17:38:43 -03:00
cceb29b93a Merge pull request #3476 from Infisical/ENG-2625
feat(secret-sync): TeamCity App Connection & Secret Sync
2025-04-25 15:44:37 -04:00
02b44365f1 Merge pull request #3470 from Infisical/feat/awsSecretRotationV2
feat(secret-rotation-v2): Add AWS IAM User Secret rotation
2025-04-25 16:43:22 -03:00
b506393765 feat(aws-iam-rotation): docs improvements 2025-04-25 16:35:57 -03:00
d5065af7e9 feat(secrets-ui): add secret syncs to referenced secret warning 2025-04-25 15:26:34 -03:00
204269a10d Merge pull request #3480 from Infisical/feat/paginationAndFilterOnProjectMembers
feat(project-members): Persist pagination setting and add role filtering
2025-04-25 14:51:05 -03:00
cf1f83aaa3 Merge pull request #3446 from Infisical/ssh-non-interactive
Improvements to Infisical V2: Support for Non-Interactive Mode, Updating Default SSH CAs.
2025-04-25 10:15:06 -07:00
7894181234 Merge pull request #3490 from Infisical/ENG-2546
feat(auth): Persist pre-login-redirect path and redirect after login
2025-04-25 13:12:46 -04:00
0c214a2f26 Adjust CLI flags to be dash-case 2025-04-25 10:03:51 -07:00
f5862cbb9a Merge 2025-04-25 09:32:48 -07:00
bb699ecb5f Merge remote-tracking branch 'origin' into ssh-non-interactive 2025-04-25 09:31:39 -07:00
x
04b20ed11d feat(auth): Persist pre-login-redirect path and redirect after login 2025-04-25 12:09:18 -04:00
cd1e2af9bf Merge pull request #3489 from Infisical/feat/add-user-get-token-and-revamp-session-management
feat: add user get token CLI and revamp session management
2025-04-25 23:45:38 +08:00
7a4a877e39 feat(aws-iam-rotation): remove credentials validation due to excesive await time 2025-04-25 12:38:41 -03:00
8f670bde88 feat(aws-iam-rotation): add credentials validation 2025-04-25 12:06:30 -03:00
ff9011c899 feat(aws-iam-rotation): add view credentials component 2025-04-25 11:23:43 -03:00
57c96abe03 feat(aws-iam-rotation): address PR comments 2025-04-25 11:01:35 -03:00
178acc412d misc: added optional accesS 2025-04-25 20:52:55 +08:00
b0288c49c0 feat: add user get token CLI and revamp session management 2025-04-25 20:43:20 +08:00
3de5fa066b fix(secrets-permissions): Fix setTimeout and eye icon size 2025-04-25 08:54:25 -03:00
f5bb0d4a86 Merge pull request #3484 from Infisical/fix/dynamicSecretSqlErrorPropagation
fix(dynamic-secret): improve error propagation and add FAQ to docs
2025-04-25 08:41:42 -03:00
x
7699705334 tiny encodeURIComponent tweak 2025-04-24 23:36:11 -04:00
x
7c49f6e302 review fixes 2025-04-24 23:30:35 -04:00
b329b5aa4b improvements: address feedback 2025-04-24 19:35:56 -07:00
x
0882c181d0 docs(native-integrations): Add deprication warnings on Windmill + TeamCity 2025-04-24 21:55:44 -04:00
x
8672dd641a Merge branch 'main' into ENG-2625 2025-04-24 21:26:05 -04:00
c613bb642e Merge pull request #3485 from Infisical/daniel/kms-logs
fix(kms): better error logs
2025-04-24 17:06:01 -07:00
90fdba0b77 Update kms-service.ts 2025-04-25 04:04:26 +04:00
795ce11062 Update kms-service.ts 2025-04-25 04:00:14 +04:00
2d4adfc651 fix(kms): better error logs 2025-04-25 03:54:59 +04:00
cb826f1a77 fix(dynamic-secret): improve error propagation and add FAQ to docs 2025-04-24 19:21:30 -03:00
55f6a06440 Merge pull request #2718 from akhilmhdh/doc/infisical-package
docs: added new docs for infisical package installation instructions
2025-04-24 14:18:07 -07:00
a19e5ff905 add min version 2025-04-24 14:16:56 -07:00
dccada8a12 Update docs/self-hosting/deployment-options/native/linux-package/installation.mdx
Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
2025-04-24 14:13:59 -07:00
68bbff455f Update docs/self-hosting/overview.mdx
Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
2025-04-24 14:12:59 -07:00
fcb59a1482 Update docs/self-hosting/overview.mdx
Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
2025-04-24 14:12:45 -07:00
b92bc2183a Update docs/self-hosting/deployment-options/native/linux-package/commands-configuration.mdx
Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
2025-04-24 14:12:27 -07:00
aff318cf3c Merge branch 'main' into doc/infisical-package 2025-04-24 14:12:01 -07:00
c97a3f07a7 update linux docs 2025-04-24 14:10:21 -07:00
e0dc2dd6d8 improvements: address feedback 2025-04-24 13:44:43 -07:00
8bf5b0f457 Merge pull request #3481 from Infisical/fix/AddDeleteProjectProtectedTooltip
fix(delete-project): Add tooltip for delete project button when it has protection enabled
2025-04-24 12:59:35 -03:00
4973447676 feat(project-members): PR suggestions improvements 2025-04-24 12:21:19 -03:00
bd2e2b7931 feat(project-members): PR suggestions improvements 2025-04-24 12:14:06 -03:00
13b7729af8 Merge pull request #3472 from Infisical/ENG-2618
Admin SSO bypass (break-glass login) sends out email to all org admins + creates audit log
2025-04-24 10:37:00 -04:00
x
e25c1199bc Made email URL use SITE_URL 2025-04-24 10:24:42 -04:00
b377d2a6b1 fix(secrets-permissions): Fix setTimeout 2025-04-24 11:15:42 -03:00
6b3726957a Merge pull request #3443 from akhilmhdh/doc/sql-change
Updated doc to have europe infisical aws account id
2025-04-24 19:07:43 +05:30
c64e6310a6 fix(delete-project): Add tooltip for delete project button when it has protection enabled 2025-04-24 10:26:54 -03:00
aa893a40a9 feat(project-members): Persist pagination setting and add role filtering 2025-04-24 10:06:09 -03:00
350272aa57 fix(secrets-permissions): UI improvements 2025-04-24 08:10:10 -03:00
0e488d840f Merge pull request #3479 from Infisical/update-org-structure-blueprint
Update the organization structure guide to include organizations and …
2025-04-23 21:18:43 -07:00
95489e1b0a fix(secrets-permissions): UI improvements 2025-04-23 22:24:41 -03:00
d6186f1fe8 Update organization-structure.mdx 2025-04-23 17:48:26 -07:00
cd199f9d3e Update the organization structure guide to include organizations and clusters 2025-04-23 17:44:51 -07:00
71258b6ea7 Merge pull request #3477 from Infisical/native-integration-deleted-import-fix
Fix: Filter Out Deleted Imports with Replication
2025-04-23 17:22:04 -07:00
56b3e7a76d fix(secrets-permissions): UI fix for users with edit permissions but not read secret value permission 2025-04-23 21:09:19 -03:00
49c90c801e fix: filter out deleted imports with replication 2025-04-23 17:03:33 -07:00
x
d019011822 Made findOrgMembersByUsername use replicaNode to stay consistent 2025-04-23 19:53:14 -04:00
x
8bd21ffa63 Attached settings URL to email, actor no longer a recipient, removed error handling for email send, used read replica node for findOrgMembersByRole 2025-04-23 19:46:25 -04:00
024a1891d3 Merge pull request #3450 from Infisical/google-cloud-run-guide
Adding a guide on deploying Infisical using Google Cloud Run
2025-04-23 16:08:26 -07:00
ac7ac79463 add to nav bar 2025-04-23 16:00:30 -07:00
x
23df78eff8 feat(secret-sync): Only import secrets that have a value from destination to infisical: 2025-04-23 18:57:08 -04:00
x
84255d1b26 remove debug logs, update comments, other nitpicks 2025-04-23 18:44:14 -04:00
x
3a6b2a593b Merge branch 'main' into ENG-2625 2025-04-23 17:59:34 -04:00
x
d3ee30f5e6 feat(secret-sync): TeamCity App Connection & Secret Sync 2025-04-23 17:58:59 -04:00
317b15157d Update google-cloud-run.mdx 2025-04-23 10:44:39 -07:00
9ea6eca560 requested changes 2025-04-23 21:40:01 +04:00
f145a00ef5 Merge pull request #3451 from Infisical/daniel/kms-improvements
improvement(kms): return kms key id in project response
2025-04-23 21:35:51 +04:00
2e34167a24 Update google-cloud-run.mdx 2025-04-23 10:29:13 -07:00
0fc7d04455 Merge pull request #3475 from akhilmhdh/feat/secret-cache-v2
feat(api): implemented secret caching version 2
2025-04-23 09:58:00 -07:00
=
af12518f54 fix: resolved lints, addressed feedback from rabbit, reptile and maidul 2025-04-23 22:23:32 +05:30
cc193b9a9f Merge pull request #3459 from Infisical/ENG-2635
Moved certificate manager overview tabs to left sidebar
2025-04-23 12:42:48 -04:00
0e95600db3 Merge pull request #3469 from Infisical/misc/reordered-kube-auth-not-found-check
misc: reordered kube auth not found check
2025-04-23 22:18:54 +08:00
=
b60172f2be feat(api): implemented secret caching version 2 2025-04-23 19:15:50 +05:30
33dea34061 chore: removed unused pick 2025-04-22 18:51:40 -07:00
bc1cce62ab Adding more architecture detail to the Cloud Run document 2025-04-22 18:50:45 -07:00
da68073e86 chore: revert secret rotation flag 2025-04-22 18:06:44 -07:00
7bd312a287 improvements: update regex checks 2025-04-22 17:57:59 -07:00
d61e6752d6 Merge branch 'main' into ldaps-connection-and-password-rotation 2025-04-22 17:42:48 -07:00
636aee2ea9 improvements: address feedback 2025-04-22 17:36:18 -07:00
b20e6a9265 Merge pull request #3473 from Infisical/add-winget-dcs
docs: add winget docs
2025-04-22 15:43:36 -07:00
5de9bf25e0 add winget docs 2025-04-22 15:37:45 -07:00
5819b8c576 PR fix suggestions for aws secret rotations 2025-04-22 17:40:15 -03:00
d5888f9de7 misc: only append isAdminLogin query param when relevant 2025-04-23 03:27:22 +08:00
1590b528bf misc: used url search params 2025-04-23 03:07:50 +08:00
x
a838f84601 Revert license overwrites, fix type errors, add error handling to email function 2025-04-22 14:58:17 -04:00
x
a32b590dc5 Merge branch 'main' into ENG-2618 2025-04-22 14:37:22 -04:00
x
b330fdbc58 Admin SSO bypass (breakglass login) sends out email to all org admins + creates audit log 2025-04-22 14:36:31 -04:00
75f1ce7b86 feat: enabled sso to bypass org sso 2025-04-23 02:28:58 +08:00
4e10f51e50 Merge pull request #3455 from akhilmhdh/feat/hide-swagger
Hide non public endpoints in swagger
2025-04-22 10:42:29 -07:00
b85809293c Lint fix 2025-04-22 13:53:56 -03:00
f143d8c358 Merge branch 'main' into feat/awsSecretRotationV2 2025-04-22 13:46:35 -03:00
26c14119be Merge pull request #3463 from Infisical/fix-enterprise-plan-display
Fix: Correct Enterprise Plan Display
2025-04-22 09:33:31 -07:00
2e3330bf69 Add AWS secret rotation V2 2025-04-22 13:26:48 -03:00
778d6b9bbf misc: reordered kube auth not found check 2025-04-22 23:06:47 +08:00
b4e831d3e2 Merge pull request #3468 from akhilmhdh/fix/remove-banner
feat: removed banner on ui for subscription crossing
2025-04-22 19:38:02 +05:30
=
8818d5c94b feat: removed banner for now 2025-04-22 19:32:48 +05:30
=
8bfbac153c feat: nit fixing 2025-04-22 12:44:45 +05:30
d7af9e84be added more validation to region 2025-04-21 22:09:52 -07:00
f2a984e6b6 fix: correct plan check for when to display enterprise plan 2025-04-21 19:39:13 -07:00
2cff90913b Merge pull request #3461 from Infisical/ENG-2623
Removed low entropy password regexes that threw false positives
2025-04-21 22:25:58 -04:00
c783fa32e9 Merge pull request #3462 from Infisical/daniel/fix-saml-sso-creation
fix: stuck on saml sso creation page
2025-04-22 06:19:52 +04:00
109971916b fix: stuck on saml sso creation page 2025-04-22 06:07:14 +04:00
x
f7d35e61f7 removed low entropy password regexes that threw false positives 2025-04-21 19:40:20 -04:00
x
ddd46acbde replace alerting icon with notification bell, add new notification bell lotties icon, update permission check wrapper to display access restricted popup 2025-04-21 19:01:12 -04:00
x
e6165f7790 remove commented code, combine a UI if-check, split permission check for cert section and pki collection section 2025-04-21 17:39:42 -04:00
x
ac12f9fc66 update file and export names to be accurate 2025-04-21 16:59:37 -04:00
6107adcc15 Merge pull request #3460 from Infisical/improve-sql-connection-valdiation-error-propogtation
Improvement: Improve SQL Connection Validation Error Propogation
2025-04-21 13:54:41 -07:00
x
7408d38065 fix an import issue 2025-04-21 16:51:30 -04:00
a4eb2e77c2 improvement: move client instantation to try/catch for sql connection validation for error propogation 2025-04-21 13:49:09 -07:00
x
e0c458df4b Merge branch 'ENG-2635' of https://github.com/Infisical/infisical into ENG-2635 2025-04-21 16:21:56 -04:00
x
6a751e720c Changed cert-manager overview tabs to be proper routes 2025-04-21 16:16:47 -04:00
40d119b462 Merge pull request #3457 from Infisical/misc/moved-regex-use-to-re2
misc: moved regex use to re2
2025-04-22 04:02:54 +08:00
6f738d7ed0 Merge pull request #3458 from Infisical/fix/SamlRemovalCornerCase
Allow user to remove SAML config
2025-04-21 15:45:36 -03:00
7f4d4b931b Add entryPoint zod validation 2025-04-21 15:28:59 -03:00
ac2ee6884c misc: updated to use regex literal 2025-04-22 02:10:56 +08:00
=
a80520e425 feat: removed all impersonate word in ui 2025-04-21 23:29:25 +05:30
608e9a644c Make entryPoint mandatory on SSOModal and check all fields on isSamlConfigured check 2025-04-21 14:52:33 -03:00
c15a1c6ed3 misc: moved regex use to re2 2025-04-22 01:38:21 +08:00
35f0e8f49a Merge pull request #3456 from Infisical/fix/secretVersionReferenceIssue
Fix SecretVersionV2 reference issue blocking users and identities deletion
2025-04-21 10:20:54 -07:00
efb8b69777 Fix SecretVersionV2 reference issue blocking users and identities deletion 2025-04-21 14:06:33 -03:00
=
4aa3552060 feat: fixed ts issues 2025-04-21 21:30:28 +05:30
b4226e7e1b Merge pull request #3427 from akhilmhdh/feat/block-user-on-trail
Block user on crossing the identity limit
2025-04-21 20:06:22 +05:30
=
40781949a6 feat: updated ui based on feedback 2025-04-21 20:02:23 +05:30
=
2ee423174a feat: updated code by rabbit, reptile and maidul changes 2025-04-21 18:43:21 +05:30
=
649f7b560f feat: added audit log for assume 2025-04-21 18:43:21 +05:30
=
7219ba3b46 feat: implemented user role impersonation 2025-04-21 18:43:21 +05:30
=
ca1f7d3448 feat: reptile and rabbit changes 2025-04-21 15:23:48 +05:30
=
4d569d70d6 fix: broken docs 2025-04-21 14:36:33 +05:30
=
5fccc62213 feat: updated docs to include various endpoints in cli only 2025-04-21 13:22:42 +05:30
eba12912f8 Merge pull request #3396 from akhilmhdh/feat/msg-crct
Updated error message on update org for saml/oidc enforcement
2025-04-20 12:07:07 -04:00
80edccc953 Update org-service.ts 2025-04-20 12:06:34 -04:00
6e65656360 Update CreateReminderForm.tsx 2025-04-19 07:15:29 +04:00
e0491c2056 Update types.ts 2025-04-19 07:11:22 +04:00
b8db15563a Update 20250419004044_secret-reminder-recipients.ts 2025-04-19 07:07:45 +04:00
9982ade219 feat(reminders): specify recipients 2025-04-19 06:59:22 +04:00
9032bbe514 feature: ldap connection and password rotation 2025-04-18 17:55:03 -07:00
1ea8e5a81e Add frontend uniqueness check for ssh hostnames 2025-04-18 15:25:13 -07:00
39ff7fddee improvement: add ID to external KMS list and add copy button 2025-04-19 00:20:18 +04:00
a0014230f9 improvement: include kms secret manager key ID on project response 2025-04-19 00:19:57 +04:00
60d0bc827c Update google-cloud-run.mdx 2025-04-18 12:37:18 -07:00
6e9651d188 Adding a guide on deploying Infisical using Google Cloud Run 2025-04-18 11:35:59 -07:00
f1b1d6f480 Merge pull request #3449 from Infisical/rbac-developer-role-correction
Documentation: Correct Developer Role Description
2025-04-18 14:19:09 -04:00
42aa3c3d46 Remove extra tx in ssh nullable ca defaults migration, update ssh docs 2025-04-18 11:06:59 -07:00
07d6616f3c Merge pull request #3448 from akhilmhdh/feat/better-saml-error-message
Improved saml error messages
2025-04-18 22:36:59 +05:30
=
7364717f60 feat: added an additional 10 as threshold 2025-04-18 22:35:41 +05:30
28d056cf7a documentation: correct developer description 2025-04-18 09:54:58 -07:00
=
f5d7809515 feat: improved saml error messages 2025-04-18 22:24:01 +05:30
184d353de5 Update infisical ssh docs to clarify ssh connect command in different modes 2025-04-17 23:29:20 -07:00
b2360f9cc8 Reuse writeToFile fn in ssh connect command 2025-04-17 23:12:44 -07:00
846a5a6e19 impl improvements according to greptile 2025-04-17 23:08:33 -07:00
c6cd3a8cc0 Add audit logs to project ssh config endpoints 2025-04-17 23:00:46 -07:00
796f5510ca Add cli docs for infisical ssh connect command 2025-04-17 22:40:43 -07:00
0265665e83 Make infisical ssh v2 work in non-interactive mode, allow reassignment of default ssh cas 2025-04-17 22:35:25 -07:00
233740e029 Merge pull request #3429 from Infisical/windmill-connection-and-sync
Feature: Windmill Connection and Sync
2025-04-17 17:53:19 -07:00
767fdc645f Merge branch 'heads/main' into windmill-connection-and-sync 2025-04-18 04:44:32 +04:00
c477703dda Merge pull request #3406 from juhnny5/patch-1
Typo correction in the go-sdk example
2025-04-18 03:46:45 +04:00
923d639c40 Merge pull request #3445 from Infisical/vercel-connection-validation-fix
Fix: Vercel Connection Validation API Call
2025-04-17 14:20:23 -07:00
7655dc7f3c chore: remove unused type 2025-04-17 12:22:02 -07:00
6c6c4db92c fix: use a non-team based api call for validation 2025-04-17 12:20:02 -07:00
8cf125ed32 Merge pull request #3444 from Infisical/default-project-delete-protection-false
Improvement: Set Project Delete Protection to False by Default
2025-04-17 12:21:46 -04:00
886cc9a113 improvement: set project delete protection to false by default 2025-04-17 09:16:36 -07:00
=
79e425d807 feat: updated doc to have europe infisical aws account id 2025-04-17 14:25:55 +05:30
e1016f0a8b Merge pull request #3441 from Infisical/revert-3439-daniel/remove-docs
Revert "fix: removed legacy sdk's"
2025-04-17 07:05:30 +04:00
9c0a5f0bd4 fix: deprecation notices 2025-04-17 07:03:20 +04:00
7facd0e89e Revert "fix: removed legacy sdk's" 2025-04-17 05:52:07 +04:00
3afe2552d5 documentation: fix grammar 2025-04-16 15:59:18 -07:00
1fdb695240 deconflict merge 2025-04-16 15:40:18 -07:00
d9bd1ac878 improvements: address feedback 2025-04-16 15:28:29 -07:00
ee185cbe47 Merge pull request #3425 from akhilmhdh/feat/aws-cf-invalidate
Added aws cf invalidation on cli deployment pipeline
2025-04-16 17:22:10 -04:00
abc2f3808e Merge pull request #3438 from akhilmhdh/doc/sql-change
Updated doc on db permission change
2025-04-16 17:15:57 -04:00
733440a7b5 update docs for pg permissions 2025-04-16 17:15:11 -04:00
1ef3525917 Merge pull request #3439 from Infisical/daniel/remove-docs
fix: removed legacy sdk's
2025-04-16 16:42:58 -04:00
6664add428 fix: removed legacy sdk's 2025-04-17 00:41:29 +04:00
242e8fd2c6 Merge pull request #3424 from Infisical/misc/allow-org-admins-to-bypass-sso-enforcement
misc: allow org admins to bypass sso enforcement
2025-04-17 00:22:18 +04:00
1137247e69 misc: addressed feedback 2025-04-17 04:00:02 +08:00
=
32b951f6e9 doc: updated doc on db permission change 2025-04-17 01:09:23 +05:30
6f5fe053cd Merge pull request #3422 from Infisical/feat/addProjectDeletionProtection
Add project delete protection
2025-04-16 16:30:46 -03:00
875ec6a24e fix: lowercase workspace for url 2025-04-16 12:08:22 -07:00
17233e6a6f Merge pull request #3437 from Infisical/feat/addDocsOnSamlModal
Add SAML doc links to Org Settings
2025-04-16 14:20:28 -03:00
0dd06c1d66 Merge pull request #3419 from Infisical/feat/notifyOnServiceTokenExpiration
Add notification on Service Token expiration
2025-04-16 14:20:09 -03:00
fc2e5d18b7 misc: displayed full admin login url 2025-04-17 00:52:24 +08:00
ae1ee25687 Merge pull request #3436 from Infisical/misc/made-jwt-signature-alg-configurable-for-oidc
misc: made jwt signature alg configurable for oidc
2025-04-17 00:47:16 +08:00
5d0bbce12d misc: added admin login url to tooltip 2025-04-17 00:41:42 +08:00
8c87c40467 misc: only bypass when from admin login 2025-04-17 00:33:07 +08:00
a9dab557d9 misc: correct labels 2025-04-17 00:06:27 +08:00
76c3f1c152 misc: made bypass opt-in 2025-04-16 23:58:20 +08:00
965084cc0c notifyExpiredTokens fixes 2025-04-16 12:48:00 -03:00
4650ba9fdd Merge pull request #3397 from Infisical/auth0-connection-and-secret-rotation
Feature: Auth0 Connection and Client Secret Rotation
2025-04-16 08:19:50 -07:00
73dea6a0be Merge branch 'main' into feat/addProjectDeletionProtection 2025-04-16 10:00:23 -03:00
e7742afcd3 Merge pull request #3434 from Infisical/fix/improveRandomValueGeneratorUI
Improve random value generator modal UI
2025-04-16 09:58:07 -03:00
7d3dd765ad Add SAML doc links to Org Settings 2025-04-16 09:52:58 -03:00
927eb0407d misc: update documentation 2025-04-16 12:33:22 +00:00
17ddb79def misc: made jwt signature alg configurable for oidc 2025-04-16 20:20:37 +08:00
5ef5a5a107 Improve random value generator modal UI 2025-04-16 08:04:41 -03:00
9ae0880f50 Improve random value generator modal UI 2025-04-16 07:07:37 -03:00
3814c65f38 Merge pull request #3433 from akhilmhdh/fix/permission
feat: dashboard failing on failing check
2025-04-16 13:00:43 +05:30
3fa98e2a8d Merge pull request #3428 from x032205/cli-secrets-folders-get-path
Fixed `v1/folders` API backward compatibility with `directory` parameter
2025-04-16 12:49:02 +05:30
=
c6b21491db feat: dashboard failing on failing check 2025-04-16 12:37:39 +05:30
357381b0d6 feature: windmill connection and sync 2025-04-15 19:10:13 -07:00
82af77c480 Add hasDeleteProtection to update endpoint 2025-04-15 22:37:53 -03:00
x
b2fae5c439 fixed backward compatibility with --directory flag on every v1/folders endpoint 2025-04-15 18:46:08 -04:00
x
f16e96759f fixed --path not working with infisical secrets folders get 2025-04-15 18:42:01 -04:00
5eb9a1a667 improvement: add doc additions for single credential rotations 2025-04-15 15:07:39 -07:00
03ad6f822a merge deconflict 2025-04-15 14:32:21 -07:00
23a5a7a624 Improvements on notify expired service tokens 2025-04-15 18:31:05 -03:00
98447e9402 improvements: address feedback 2025-04-15 14:22:41 -07:00
0f7e8585dc Merge pull request #3391 from Infisical/feat/add-metadata-based-permissions-for-dynamic-secret
feat: add metadata based permissions for dynamic secret
2025-04-16 04:28:52 +08:00
8568d1f6fe Merge pull request #3426 from Infisical/fix/addConfusedDeputyProblemOnAWSDocs
Add confused deputy problem to AWS assume role docs
2025-04-15 17:03:04 -03:00
27198869d8 Confused Deputy Attacks docs improvement 2025-04-15 16:55:05 -03:00
dd0880825b doc: added reference to admin login portal 2025-04-15 19:50:49 +00:00
f27050a1c3 Merge pull request #3421 from x032205/san-size-limit
Increase Certificate Alternative Names (SAN) Character Limit to 4096
2025-04-15 15:29:58 -04:00
785173747f misc: introduce admin login portal 2025-04-16 03:28:04 +08:00
=
5b20c1feba feat: reptile jaw spaced 2025-04-16 00:30:11 +05:30
=
ac73800acb feat: added banner on crossing user/identity limit 2025-04-16 00:25:21 +05:30
d33b06dd8a Add confused deputy problem to AWS assume role docs 2025-04-15 15:41:13 -03:00
=
9a6e27d4be feat: added aws cf invalidation on cli deployment pipeline 2025-04-15 23:41:06 +05:30
d0db5c00e8 misc: allow org admins to bypass sso enforcement 2025-04-16 01:35:39 +08:00
=
dd323eccd4 feat: added banner on projects when limit reached 2025-04-15 22:00:22 +05:30
9475c1671e Merge pull request #3418 from Infisical/allow-internal-ip-connection-env-var
Feature: Add General Env Var for Allowing Internal IP Connections
2025-04-15 08:26:00 -07:00
0f710b1ccc misc: updated documentation 2025-04-15 15:01:10 +00:00
71c55d5a53 misc: addressed review comments 2025-04-15 22:42:38 +08:00
32bca651df Merge pull request #3423 from Infisical/fix/getFolderIsImportedByThrow
Avoid throwing on getFolderIsImportedBy no folder found
2025-04-15 18:51:51 +05:30
82533f49ca Avoid throwing on getFolderIsImportedBy no folder found 2025-04-15 10:19:41 -03:00
1d8c513da1 Improve invalidateQueries for useToggleDeleteProjectProtection 2025-04-15 08:07:21 -03:00
ae8a78b883 Fix cron schedule used to test 2025-04-15 07:46:35 -03:00
x
b08b53b77d increase certificate altnames character limit to 4096 2025-04-15 00:40:28 -04:00
862ed4f4e7 Merge pull request #3411 from Infisical/daniel/kms-signing-docs
docs(kms): KMS sign/verify docs
2025-04-15 05:39:21 +04:00
7b9254d09a Merge pull request #3358 from Infisical/daniel/go-sdk-kms-docs
docs(sdk): go sdk kms docs
2025-04-15 05:30:48 +04:00
c6305045e3 Revert "fix(docs): rename isDigest to preDigested"
This reverts commit 2642f7501d.
2025-04-15 05:28:41 +04:00
24bf9f7a2a Revert "fix: rename IsDigest to IsPreDigested"
This reverts commit 8d4fa0bdb9.
2025-04-15 05:24:39 +04:00
86d7fca8fb Add minor improvements to notifyExpiredTokens 2025-04-14 21:52:16 -03:00
cac4f30ca8 Update backend/src/services/service-token/service-token-dal.ts
Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
2025-04-14 21:43:19 -03:00
101c056f43 Add project delete protection 2025-04-14 21:41:46 -03:00
8d4fa0bdb9 fix: rename IsDigest to IsPreDigested 2025-04-15 03:51:30 +04:00
2642f7501d fix(docs): rename isDigest to preDigested 2025-04-15 03:49:29 +04:00
68ba807b43 Merge pull request #3417 from Infisical/rollback-rotation-v1-deprecation
Improvement: Rollback Secret V1 Create Deprecation
2025-04-14 15:28:23 -07:00
80352acc8a Add notification on Service Token expiration 2025-04-14 18:31:06 -03:00
499ff3635b feature: add general env var for allowing internal ip connections and update relevant docs 2025-04-14 14:04:26 -07:00
78fc8a693d Merge pull request #3356 from Infisical/feat/showWarningOnImportedSecretDeletion
Add warning on secret deletions where it's being imported by another folder
2025-04-14 17:37:16 -03:00
78687984b7 Merge pull request #3404 from Infisical/native-integrations-ui-deprecation-for-sync-parity
Improvement: Native Integration Deprecation Details and Sync Redirect
2025-04-14 13:29:56 -07:00
25d3fb6a8c improvements: address feedback 2025-04-14 13:22:25 -07:00
31a4bcafbe Merge branch 'main' into feat/showWarningOnImportedSecretDeletion 2025-04-14 15:30:41 -03:00
ac8b3aca60 Merge pull request #3415 from Infisical/feat/addBackstagePluginsDocs
Add Backstage Plugins docs
2025-04-14 15:18:20 -03:00
4ea0cc62e3 Change External Integrations to Others 2025-04-14 15:07:16 -03:00
bdab16f64b Merge pull request #3414 from Infisical/misc/add-proper-display-of-auth-failure-message
misc: add proper display of auth failure message for OIDC
2025-04-15 01:54:08 +08:00
9d0020fa4e improvement: rollback deprecate all secret rotation v1 create, update UI to only prevent pg/mssql 2025-04-14 10:50:45 -07:00
3c07204532 Merge pull request #3416 from Infisical/daniel/make-idoment
fix: improve kms key migration
2025-04-14 23:08:59 +05:30
c0926bec69 fix: no check for encryption algorithm on external KMS 2025-04-14 21:36:38 +04:00
b9d74e0aed requested changes 2025-04-14 21:36:16 +04:00
f3078040fc fix: improve kms key migration 2025-04-14 21:22:59 +04:00
f2fead7a51 Add Backstage Plugins docs 2025-04-14 14:15:42 -03:00
3483ed85ff misc: add proper display of auth failure message oidc 2025-04-15 01:03:45 +08:00
3c58bf890d Merge branch 'main' into feat/showWarningOnImportedSecretDeletion 2025-04-14 09:04:47 -03:00
dc219b8e9f Fix edge case for referenced secrets batch delete and empty message 2025-04-14 08:54:43 -03:00
85627eb825 Merge pull request #3412 from x032205/github-username
Github & Gitlab SSO display name fallback to username
2025-04-13 17:45:25 -04:00
f1e30fd06b requested changes 2025-04-14 01:42:05 +04:00
fcc6f812d5 Merge branch 'Infisical:main' into github-username 2025-04-13 16:01:33 -04:00
x
7c38932878 github & gitlab sso display name fallback to username 2025-04-13 15:59:25 -04:00
e339b81bf1 docs(kms): signing documentation 2025-04-13 23:19:06 +04:00
b9bfe19b64 feat(kms/signing): better error handling 2025-04-13 23:17:50 +04:00
966ca1a3c6 Merge pull request #3357 from Infisical/daniel/kms-sign-verify
feat(kms): sign & verify data
2025-04-13 22:22:23 +04:00
fa030417ef Typo correction in the go-sdk example 2025-04-12 10:15:04 +02:00
8bfbae1037 chore: remove outdated comment 2025-04-11 19:38:47 -07:00
d00b34663e improvement: native integration legacy details and sync redirects 2025-04-11 19:34:33 -07:00
cdc364d44c Merge pull request #3401 from Infisical/fix/AddVersionToGoSdkRetrieveSecretOptionsDocs
Add go-sdk version parameter to RetrieveSecretOptions docs
2025-04-11 17:45:14 -03:00
34a6ec1b64 Add go-sdk version parameter to RetrieveSecretOptions docs 2025-04-11 17:41:03 -03:00
32641cfc3a Merge pull request #3394 from akhilmhdh/feat/secret-cache
Added caching for secret dal
2025-04-11 16:11:30 -04:00
fe58508136 Merge pull request #3360 from Infisical/feat/terraformCloudIntegration
Terraform cloud integration
2025-04-11 16:59:06 -03:00
65f78c556f Update files.ts 2025-04-11 23:52:14 +04:00
dd52f4d7e0 Merge pull request #3400 from Infisical/update-vite
update vite to 5.4.18
2025-04-11 15:49:35 -04:00
aa7ad9a8c8 update vite to 5.4.18 2025-04-11 15:42:17 -04:00
85a716628b Merge branch 'main' into feat/terraformCloudIntegration 2025-04-11 16:37:05 -03:00
581e4b35f9 rebase 2025-04-11 12:25:26 -07:00
4b0e5fa05b Address PR comment for terraform sync integration 2025-04-11 16:23:07 -03:00
4a9e24884d fix: RSA not working in UI 2025-04-11 23:21:55 +04:00
=
9565ef29d0 feat: update with review changes 2025-04-12 00:36:42 +05:30
=
7107a1b225 feat: added cache invalidation for old secret rotation 2025-04-12 00:36:41 +05:30
=
8676421a10 feat: resolved failing test 2025-04-12 00:36:41 +05:30
=
5f6db870a6 feat: added caching for secret dal\ 2025-04-12 00:36:41 +05:30
5bc8e4729f chore: moved signing fns to files lib 2025-04-11 22:59:57 +04:00
27fdf68e42 Merge pull request #3395 from Infisical/feat/addCommentToAccessRequests
Add access request note and change secret request to change request
2025-04-11 15:57:38 -03:00
9a5bc33517 Add approval request note max lenght on endpoint parameter 2025-04-11 15:52:48 -03:00
0fecbad43c Merge pull request #3347 from Infisical/ssh-host-key-signing-docs2
Infisical SSH - V2
2025-04-11 11:31:19 -07:00
511a81a464 Merge pull request #3373 from Infisical/feat/camunda-app-connection-and-secret-sync
feat: camunda app connection and secret sync
2025-04-12 02:12:11 +08:00
f33a777fae misc: updated form declaration for consistency 2025-04-12 02:04:49 +08:00
8a870131e9 misc: updated missing tx 2025-04-12 02:02:41 +08:00
041fac7f42 Update signing-fns.ts 2025-04-11 21:58:21 +04:00
70f5f21e7f misc: updated file name 2025-04-12 01:54:21 +08:00
d97057b43b misc: address metadata type 2025-04-12 01:50:05 +08:00
5ce738bba0 fix: better file cleanup 2025-04-11 21:49:57 +04:00
19b0cd9735 feat: update dynamic secret permissioning 2025-04-12 01:47:33 +08:00
=
7dcd3d24aa feat: corrected oidc message 2025-04-11 23:11:23 +05:30
=
3c5c6aeca8 feat: updated error message on update org for saml/oidc enforcement 2025-04-11 23:09:27 +05:30
b5b0d42dd5 Add writeHostCaToFile to cli for infisical ssh connect 2025-04-11 10:28:18 -07:00
1ec87fae75 Add referenced secret delete warning to overview page 2025-04-11 14:00:31 -03:00
d888d990d0 misc: added loading state 2025-04-11 22:25:10 +08:00
1cbab41609 misc: added description for fields 2025-04-11 22:13:50 +08:00
49b5b488ef misc: added missing break 2025-04-11 22:10:59 +08:00
bb59e04c28 misc: updated ui to show cluster name instead of just ID 2025-04-11 22:09:37 +08:00
46b08dccd1 Merge remote-tracking branch 'origin/main' into feat/camunda-app-connection-and-secret-sync 2025-04-11 21:53:56 +08:00
53ca8d7161 misc: address comments 2025-04-11 21:47:30 +08:00
aec131543f Add referenced secret delete warning to batch delete modal inside env 2025-04-11 10:12:41 -03:00
e19c3630d9 Rename TerraformCloudSyncDestinationSection file 2025-04-11 09:54:24 -03:00
071dab723a Merge branch 'main' into feat/terraformCloudIntegration 2025-04-11 09:52:14 -03:00
aeaa5babab Improve referenced secret deletion message logic 2025-04-11 09:29:01 -03:00
1ce155e2fd Merge pull request #3338 from Infisical/feat/vercelSecretSyncIntegration
Add secret sync vercel integration
2025-04-11 07:52:02 -03:00
2ed05c26e8 Fix minor login mapping update description 2025-04-11 00:53:49 -07:00
9e0fdb10b1 Add unique constraints for ssh login user and login user mapping tables 2025-04-11 00:52:50 -07:00
5c40347c52 Update default on frontend user cert ttl form 2025-04-10 21:57:40 -07:00
edf375ca48 Bring back ssh host read permission 2025-04-10 21:48:25 -07:00
264177638f Address greptile suggestions 2025-04-10 16:45:24 -07:00
230b44fca1 Add access request note and change secret request to change request 2025-04-10 20:10:38 -03:00
3d02feaad9 Merge pull request #3389 from Infisical/daniel/get-project-identity-membership-by-id
feat(project-identity): get project identity by membership ID
2025-04-11 00:55:03 +04:00
77dd768a38 Fix merge conflicts 2025-04-10 12:39:09 -07:00
eb11efcafa Run linter 2025-04-10 12:27:56 -07:00
8522420e7f Minor cleans for consistency 2025-04-10 12:19:37 -07:00
81331ec4d1 Update db schema for ssh login mappings 2025-04-10 10:50:23 -07:00
f15491d102 Merge pull request #3393 from Infisical/fix/address-type-issue-for-secret-approval-requests
fix: address runtime error for secret approval requests
2025-04-11 01:46:31 +08:00
4d4547015e fix: address runtime error for secret approval requests 2025-04-11 01:26:56 +08:00
06cd496ab3 Merge pull request #3392 from Infisical/fix/avoidForwardSlachOnSecretKeys
Add condition to avoid secret names that contain forward slashes
2025-04-10 14:16:40 -03:00
4119478704 Add condition to avoid secret names that contain forward slashes 2025-04-10 13:59:20 -03:00
07898414a3 feat: add metadata based permissions for dynamic secret 2025-04-11 00:20:02 +08:00
f15b30ff85 Improve referenced secret deletion message component 2025-04-10 13:08:52 -03:00
700efc9b6d Merge pull request #3304 from Infisical/daniel/scim-fixes
fix: scim improvements and ui fixes
2025-04-10 20:06:49 +04:00
894633143d fix(kms-signing): requested changes 2025-04-10 19:55:59 +04:00
b76ee9cc49 Merge pull request #3374 from thomas-infisical/feb-mar-changelog
docs: update changelog for february & march 2025
2025-04-10 11:38:03 -04:00
c498178923 Update scim-service.ts 2025-04-10 18:10:58 +04:00
8bb68f9889 Update identity-project-service.ts 2025-04-10 17:53:17 +04:00
1c121ec30d feat(project-identity): get project identity by membership ID 2025-04-10 17:48:41 +04:00
8ee2b54182 Improve referenced secret deletion message component 2025-04-10 10:24:02 -03:00
956d97eda2 Add missing describe on TerraformCloudConnectionAccessTokenCredentialsSchema 2025-04-10 09:24:25 -03:00
e877a4c9e9 Improve vercer secret sync integration 2025-04-10 09:20:18 -03:00
ee9a7cd5a1 Improve terraform-cloud secret sync schema 2025-04-10 07:54:06 -03:00
a84dddaf6f Improve terraform-cloud secret sync destination variables 2025-04-10 07:38:11 -03:00
8cbfeffe4c Merge pull request #3386 from Infisical/disable-ratelimits-onselfhost
Remove rate limits on self host
2025-04-09 21:01:51 -04:00
2084539f61 fix logic 2025-04-09 20:55:41 -04:00
9baab63b29 Add docs for Infisical SSH V2 2025-04-09 17:48:52 -07:00
34cf47a5eb remove console 2025-04-09 20:47:16 -04:00
b90c6cf3fc remove rate limits for self host 2025-04-09 20:45:51 -04:00
68374a17f0 Fix lint issue 2025-04-09 20:16:05 -03:00
993eb4d239 General improvements to Terraform Integration 2025-04-09 20:15:24 -03:00
2382937385 Add configure sshd flag to infisical ssh add-host command, update issue user cert permissioning 2025-04-09 14:41:10 -07:00
ac0f4aa8bd Merge branch 'heads/main' into daniel/kms-sign-verify 2025-04-10 01:12:13 +04:00
05af70161a Merge branch 'main' into feat/terraformCloudIntegration 2025-04-09 17:55:23 -03:00
b121ec891f UI changes on reference secret warning 2025-04-09 17:36:57 -03:00
ab566bcbe4 Merge branch 'main' into feat/showWarningOnImportedSecretDeletion 2025-04-09 15:39:43 -03:00
2940300164 Merge pull request #3385 from akhilmhdh/feat/add-max-role
Added max to $OR in search function
2025-04-09 22:37:36 +05:30
=
9356ab7cbc feat: added max to search or 2025-04-09 22:04:31 +05:30
bbc94da522 Merge pull request #3384 from akhilmhdh/feat/win-get
feat: added winget to build
2025-04-09 12:24:37 -04:00
=
8a241771ec feat: added winget to build 2025-04-09 21:11:39 +05:30
ed5c18b5ac Add rate-limit to vercel sync fns 2025-04-09 12:36:43 -03:00
1f23515aac Merge pull request #3367 from akhilmhdh/feat/syntax-highlight
Add filter by role for org identity and search identity api
2025-04-09 20:02:52 +05:30
d01cb282f9 General improvements to Vercel Integration 2025-04-09 11:32:48 -03:00
8fa8117fa1 Update signing.ts 2025-04-09 18:28:50 +04:00
6dc085b970 Merge branch 'main' into feat/vercelSecretSyncIntegration 2025-04-09 09:15:52 -03:00
=
63dc9ec35d feat: updated search message on empty result with role filter 2025-04-09 15:15:54 +05:30
=
1d083befe4 feat: added order by 2025-04-09 15:09:55 +05:30
=
c01e29b932 feat: rabbit review changes 2025-04-09 15:09:54 +05:30
=
3aed79071b feat: added search endpoint to docs 2025-04-09 15:09:54 +05:30
=
140fa49871 feat: added advance filter for identities list table in org 2025-04-09 15:09:54 +05:30
=
03a3e80082 feat: completed api for new search identities 2025-04-09 15:09:54 +05:30
5a114586dc Add ssh host host ca public key endpoint 2025-04-08 18:54:08 -07:00
20ebfcefaa Update permission logic 2025-04-08 18:45:16 -07:00
bfcfffbabf update notice 2025-04-08 21:15:31 -04:00
210bd220e5 Delete .github/workflows/codeql.yml 2025-04-08 20:51:25 -04:00
7be2a10631 Merge pull request #3380 from Infisical/end-cloudsmith-publish
update install scrip for deb
2025-04-08 20:49:52 -04:00
5753eb7d77 rename install file 2025-04-08 20:49:14 -04:00
cb86aa40fa update install scrip for deb 2025-04-08 20:47:33 -04:00
1131143a71 remove gpg passphrase 2025-04-08 18:28:23 -04:00
041d585f19 Update go.mdx 2025-04-09 02:11:43 +04:00
728c3f56a7 Add rbac permissioning support for ssh hosts, render access tree for secrets projects only 2025-04-08 14:56:05 -07:00
939b77b050 fix: fixed local verification & added digest support 2025-04-09 01:55:26 +04:00
a50b8120fd Merge pull request #3378 from akhilmhdh/fix/doc-p-access-image
feat: updated ruby action
2025-04-08 16:21:06 -04:00
=
f1ee53d417 feat: updated ruby action 2025-04-09 01:49:35 +05:30
229ad79f49 Merge pull request #3377 from akhilmhdh/fix/doc-p-access-image
feat: added passphrase
2025-04-08 15:56:34 -04:00
=
d7dbd01ecf feat: banner respect silent 2025-04-09 01:24:38 +05:30
=
026fd21fd4 feat: added passphrase 2025-04-09 01:05:31 +05:30
9b9c1a52b3 Merge pull request #3376 from akhilmhdh/fix/doc-p-access-image
feat: added s3 deb pipeline
2025-04-08 15:05:32 -04:00
98aa424e2e Update .github/workflows/release_build_infisical_cli.yml
Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
2025-04-08 15:02:47 -04:00
=
2cd5df1ab3 feat: updated message 2025-04-09 00:30:48 +05:30
e0d863e06e Merge pull request #3375 from Infisical/helm-update-v0.9.1
Update Helm chart to version v0.9.1
2025-04-08 22:52:42 +04:00
=
d991af557b feat: added s3 deb pipeline 2025-04-09 00:22:00 +05:30
ae54d04357 Update Helm chart to version v0.9.1 2025-04-08 18:51:31 +00:00
fa590ba697 Merge pull request #3348 from Infisical/daniel/k8s-auto-reviewer-token
feat(k8s): k8s auth automatic service account token creation
2025-04-08 22:45:57 +04:00
9899864133 docs: update changelog for february & march 2025 2025-04-08 20:13:46 +02:00
06715b1b58 misc: code rabbit 2025-04-09 02:10:45 +08:00
038f43b769 doc: add camunda secret sync 2025-04-08 18:01:30 +00:00
35d7881613 doc: added camundo app connection 2025-04-08 17:08:13 +00:00
b444908022 doc: added api reference 2025-04-09 00:06:17 +08:00
3f9a793578 feat: added camunda secret sync 2025-04-08 23:52:27 +08:00
479d6445a7 feat: added camunda app connection 2025-04-08 21:57:24 +08:00
bf5e8d8c8b Add ssh host command to cli 2025-04-07 22:25:37 -07:00
99aa567a6f Add ssh host endpoint for issuing ssh host cert 2025-04-07 20:47:52 -07:00
1da2896bb0 Create codeql.yml 2025-04-07 21:00:43 -04:00
423a2f38ea Merge pull request #3371 from Infisical/misc/add-missing-version-filter
misc: add missing version filter in get secret by name
2025-04-08 02:55:21 +08:00
db0a72f7b4 misc: add missing version filter 2025-04-08 02:26:20 +08:00
4a202d180a Merge pull request #3370 from akhilmhdh/fix/doc-p-access-image
added check for org in project id
2025-04-07 18:24:08 +01:00
=
33103f1e95 added check for org in project id 2025-04-07 22:51:14 +05:30
ce8a4bc50e Merge pull request #3369 from akhilmhdh/fix/doc-p-access-image
feat: added env for dynamic secret to allow internal ip
2025-04-07 22:30:59 +05:30
=
141a821091 feat: added env for dynamic secret to allow internal ip 2025-04-07 21:43:17 +05:30
b3dd5410d7 Merge pull request #3368 from akhilmhdh/fix/doc-p-access-image
doc: updated project access control images
2025-04-07 15:55:44 +01:00
=
74574c6c29 doc: updated project access control images 2025-04-07 20:19:25 +05:30
4f32756951 Merge pull request #3343 from Infisical/secret-rotations-v2-feature
Feature: Secret Rotations v2
2025-04-07 15:24:40 +01:00
961fe09a6e Merge pull request #3366 from Infisical/update-all-projcts-ui
Update UI for all projects/my projects
2025-04-07 15:29:30 +05:30
eb4816fd29 Add infisical ssh connect command 2025-04-06 21:17:23 -07:00
5ab853d3e6 fix lint 2025-04-06 21:17:51 -04:00
0e073cc9fc Update UI for all projects/my projects
Updated the UI so that instead of having a dropdown to select + a heading on the project overview page, the heading becomes the drop down for my projects vs all projects
2025-04-06 21:12:08 -04:00
715bb447e6 Add list accessible ssh hosts endpoint 2025-04-06 17:28:46 -07:00
c2f2a038ad Add ssh project default cas 2025-04-06 14:22:17 -07:00
433b1a49f0 Merge pull request #3362 from Infisical/misc/add-dev-setup-for-fips 2025-04-06 19:41:49 +08:00
5671cd5cef Begin ssh host permissions 2025-04-05 22:57:46 -07:00
b8f04d6738 preliminary ssh host structs, api, ui 2025-04-05 22:25:06 -07:00
b0b255461d improvements: address coderabbit 2025-04-04 18:08:07 -07:00
c2f2dc1e72 docs: improve mssql statement order 2025-04-04 17:56:24 -07:00
0ee1b425df improvements: add ssl options to sql connections and update ui/docs 2025-04-04 17:51:05 -07:00
46e72e9fba merge and deconflict main 2025-04-04 13:30:57 -07:00
06fc4e955d improvements: final feedback and ssl enforcement 2025-04-04 13:28:01 -07:00
18c8fc66ee Update docs for Infisical SSH, fix Infisical SSH project deletion bug 2025-04-04 11:59:05 -07:00
ece294c483 Merge pull request #3363 from asp143/fix/pg-admin-dev
feat: Fix pgadmin in dev
2025-04-04 19:03:43 +01:00
9a712b5c85 feat: Fix pgadmin in dev 2025-04-05 00:17:48 +08:00
1ec427053b Merge pull request #3355 from Infisical/feat/refetchInstancePlan
Fetch self hosted license feature set without needing to redeploy the instance
2025-04-04 12:57:20 -03:00
6c636415bb Improve logs of syncLicenseKeyOnPremFeatures 2025-04-04 12:53:28 -03:00
224b167000 Improve delete referenced secret warning message 2025-04-04 11:22:45 -03:00
e323cb4630 Merge pull request #3361 from Infisical/akhilmhdh-patch-2
feat: updated project access note to max a limit
2025-04-04 19:29:53 +05:30
e87a1bd402 Add flag to throw on syncLicenseKeyOnPremFeatures for the initial bootstrap 2025-04-04 10:55:30 -03:00
3b09173bb1 feat: updated project access note to max a limit 2025-04-04 19:22:38 +05:30
d957419b94 Fix mist.json ngrok url used to test 2025-04-04 10:49:01 -03:00
ec9897d561 Terraform Cloud Secret Sync Integration Docs 2025-04-04 10:46:23 -03:00
4d41513abf Terraform Cloud Secret Sync Integration 2025-04-04 09:54:53 -03:00
83206aad93 fix: public key encoding as DER 2025-04-04 11:08:06 +04:00
9fc9f69fc9 Finish preliminary support for external key source for ssh cas 2025-04-03 22:46:41 -07:00
cd333a7923 improvements: address coderabbit 2025-04-03 21:42:59 -07:00
e11fdf8f3a fix: add doc images and format constants 2025-04-03 21:37:10 -07:00
4725108319 Merge branch 'main' into secret-rotations-v2-feature 2025-04-03 21:21:06 -07:00
715441908b improvements: address feedback, feature docs and cert validation with dns rebinding handling 2025-04-03 21:17:04 -07:00
e1a11c37e3 docs(sdk): go sdk kms docs 2025-04-04 06:02:47 +04:00
cd83efb060 Update types.ts 2025-04-04 04:24:43 +04:00
53b5497271 fix: requested changes 2025-04-04 04:21:00 +04:00
3f190426fe fix: added docs for operator managed service account tokens & made audience optional 2025-04-04 03:15:11 +04:00
15130a433c UI improvements on secret deletion warning 2025-04-03 17:40:08 -03:00
a0bf03b2ae UI improvements on secret deletion warning 2025-04-03 16:00:41 -03:00
c7416c825c Update audit-log-types.ts 2025-04-03 20:13:01 +04:00
419dd37d03 Allow vercel importSecrets 2025-04-03 11:38:20 -03:00
f00a54ed54 Initial Commit for terraform cloud intergation 2025-04-03 11:15:38 -03:00
a25c25434c Lint fix 2025-04-03 08:31:00 -03:00
4f72d09458 Merge branch 'main' into feat/vercelSecretSyncIntegration 2025-04-03 08:30:24 -03:00
08baf02ef0 Add docs for API setup Vercel Connection 2025-04-03 08:26:24 -03:00
fe172e39bf feat(kms): audit logs for sign/verify 2025-04-03 09:30:51 +04:00
fda77fe464 fix: better error handling & renamed handler function 2025-04-03 08:23:12 +04:00
c4c065ea9e docs(kms): signing api endpoints 2025-04-03 08:17:35 +04:00
c6ca668db9 feat(kms): sign & verify data 2025-04-03 07:17:29 +04:00
4d8598a019 Fix lint issue 2025-04-02 19:06:27 -03:00
a9da2d6241 Truncate folder name on warning message 2025-04-02 19:02:24 -03:00
4420985669 Add warning on secret deletions where it's being imported by another folder 2025-04-02 18:58:34 -03:00
577c81be65 improvements: address feedback 2025-04-02 14:11:59 -07:00
064322936b Add try-catch to syncLicenseKeyOnPremFeatures 2025-04-02 16:36:17 -03:00
7634fc94a6 Fix lint issue 2025-04-02 16:34:42 -03:00
d82b06c72b Add LICENSE_KEY refresh job for self hosted instances 2025-04-02 14:57:34 -03:00
3d072c2f48 feat(k8s): automatic service account token creation for k8s auth 2025-04-01 23:39:22 +04:00
82b828c10e feat(k8s): automatic service account token creation for k8s auth 2025-04-01 23:16:38 +04:00
5e7ad5614d Update max ttl param constraint on ssh certificate template creation 2025-04-01 11:08:03 -07:00
f825a62af2 Add docs for host key signing 2025-04-01 11:04:19 -07:00
90bf8f800b Add vercel secret syncs docs 2025-04-01 10:56:36 -03:00
766c1242fd feature secret rotations v2 2025-03-31 20:34:48 -07:00
dbabb4f964 Add secret sync vercel integration 2025-03-31 18:10:29 -03:00
4b9f409ea5 fix: scim improvements and ui fixes 2025-03-25 07:12:56 +04:00
=
c1570930a9 docs: added new docs for infisical package installation instructions 2024-11-11 19:23:31 +05:30
1388 changed files with 46372 additions and 5273 deletions

View File

@ -1,132 +1,153 @@
name: Build and release CLI
on:
workflow_dispatch:
workflow_dispatch:
push:
# run only against tags
tags:
- "infisical-cli/v*.*.*"
push:
# run only against tags
tags:
- "infisical-cli/v*.*.*"
permissions:
contents: write
contents: write
jobs:
cli-integration-tests:
name: Run tests before deployment
uses: ./.github/workflows/run-cli-tests.yml
secrets:
CLI_TESTS_UA_CLIENT_ID: ${{ secrets.CLI_TESTS_UA_CLIENT_ID }}
CLI_TESTS_UA_CLIENT_SECRET: ${{ secrets.CLI_TESTS_UA_CLIENT_SECRET }}
CLI_TESTS_SERVICE_TOKEN: ${{ secrets.CLI_TESTS_SERVICE_TOKEN }}
CLI_TESTS_PROJECT_ID: ${{ secrets.CLI_TESTS_PROJECT_ID }}
CLI_TESTS_ENV_SLUG: ${{ secrets.CLI_TESTS_ENV_SLUG }}
CLI_TESTS_USER_EMAIL: ${{ secrets.CLI_TESTS_USER_EMAIL }}
CLI_TESTS_USER_PASSWORD: ${{ secrets.CLI_TESTS_USER_PASSWORD }}
CLI_TESTS_INFISICAL_VAULT_FILE_PASSPHRASE: ${{ secrets.CLI_TESTS_INFISICAL_VAULT_FILE_PASSPHRASE }}
cli-integration-tests:
name: Run tests before deployment
uses: ./.github/workflows/run-cli-tests.yml
secrets:
CLI_TESTS_UA_CLIENT_ID: ${{ secrets.CLI_TESTS_UA_CLIENT_ID }}
CLI_TESTS_UA_CLIENT_SECRET: ${{ secrets.CLI_TESTS_UA_CLIENT_SECRET }}
CLI_TESTS_SERVICE_TOKEN: ${{ secrets.CLI_TESTS_SERVICE_TOKEN }}
CLI_TESTS_PROJECT_ID: ${{ secrets.CLI_TESTS_PROJECT_ID }}
CLI_TESTS_ENV_SLUG: ${{ secrets.CLI_TESTS_ENV_SLUG }}
CLI_TESTS_USER_EMAIL: ${{ secrets.CLI_TESTS_USER_EMAIL }}
CLI_TESTS_USER_PASSWORD: ${{ secrets.CLI_TESTS_USER_PASSWORD }}
CLI_TESTS_INFISICAL_VAULT_FILE_PASSPHRASE: ${{ secrets.CLI_TESTS_INFISICAL_VAULT_FILE_PASSPHRASE }}
npm-release:
runs-on: ubuntu-latest
npm-release:
runs-on: ubuntu-latest
env:
working-directory: ./npm
needs:
- cli-integration-tests
- goreleaser
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Extract version
run: |
VERSION=$(echo ${{ github.ref_name }} | sed 's/infisical-cli\/v//')
echo "Version extracted: $VERSION"
echo "CLI_VERSION=$VERSION" >> $GITHUB_ENV
- name: Print version
run: echo ${{ env.CLI_VERSION }}
- name: Setup Node
uses: actions/setup-node@8f152de45cc393bb48ce5d89d36b731f54556e65 # v4.0.0
with:
node-version: 20
cache: "npm"
cache-dependency-path: ./npm/package-lock.json
- name: Install dependencies
working-directory: ${{ env.working-directory }}
run: npm install --ignore-scripts
- name: Set NPM version
working-directory: ${{ env.working-directory }}
run: npm version ${{ env.CLI_VERSION }} --allow-same-version --no-git-tag-version
- name: Setup NPM
working-directory: ${{ env.working-directory }}
run: |
echo 'registry="https://registry.npmjs.org/"' > ./.npmrc
echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" >> ./.npmrc
echo 'registry="https://registry.npmjs.org/"' > ~/.npmrc
echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" >> ~/.npmrc
env:
working-directory: ./npm
needs:
- cli-integration-tests
- goreleaser
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
- name: Extract version
run: |
VERSION=$(echo ${{ github.ref_name }} | sed 's/infisical-cli\/v//')
echo "Version extracted: $VERSION"
echo "CLI_VERSION=$VERSION" >> $GITHUB_ENV
- name: Pack NPM
working-directory: ${{ env.working-directory }}
run: npm pack
- name: Print version
run: echo ${{ env.CLI_VERSION }}
- name: Publish NPM
working-directory: ${{ env.working-directory }}
run: npm publish --tarball=./infisical-sdk-${{github.ref_name}} --access public --registry=https://registry.npmjs.org/
env:
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
- name: Setup Node
uses: actions/setup-node@8f152de45cc393bb48ce5d89d36b731f54556e65 # v4.0.0
with:
node-version: 20
cache: "npm"
cache-dependency-path: ./npm/package-lock.json
- name: Install dependencies
working-directory: ${{ env.working-directory }}
run: npm install --ignore-scripts
- name: Set NPM version
working-directory: ${{ env.working-directory }}
run: npm version ${{ env.CLI_VERSION }} --allow-same-version --no-git-tag-version
- name: Setup NPM
working-directory: ${{ env.working-directory }}
run: |
echo 'registry="https://registry.npmjs.org/"' > ./.npmrc
echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" >> ./.npmrc
echo 'registry="https://registry.npmjs.org/"' > ~/.npmrc
echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" >> ~/.npmrc
env:
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
- name: Pack NPM
working-directory: ${{ env.working-directory }}
run: npm pack
- name: Publish NPM
working-directory: ${{ env.working-directory }}
run: npm publish --tarball=./infisical-sdk-${{github.ref_name}} --access public --registry=https://registry.npmjs.org/
env:
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
goreleaser:
runs-on: ubuntu-latest
needs: [cli-integration-tests]
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0
- name: 🐋 Login to Docker Hub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: 🔧 Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- run: git fetch --force --tags
- run: echo "Ref name ${{github.ref_name}}"
- uses: actions/setup-go@v3
with:
go-version: ">=1.19.3"
cache: true
cache-dependency-path: cli/go.sum
- name: Setup for libssl1.0-dev
run: |
echo 'deb http://security.ubuntu.com/ubuntu bionic-security main' | sudo tee -a /etc/apt/sources.list
sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 3B4FE6ACC0B21F32
sudo apt update
sudo apt-get install -y libssl1.0-dev
- name: OSXCross for CGO Support
run: |
mkdir ../../osxcross
git clone https://github.com/plentico/osxcross-target.git ../../osxcross/target
- uses: goreleaser/goreleaser-action@v4
with:
distribution: goreleaser-pro
version: v1.26.2-pro
args: release --clean
env:
GITHUB_TOKEN: ${{ secrets.GO_RELEASER_GITHUB_TOKEN }}
POSTHOG_API_KEY_FOR_CLI: ${{ secrets.POSTHOG_API_KEY_FOR_CLI }}
FURY_TOKEN: ${{ secrets.FURYPUSHTOKEN }}
AUR_KEY: ${{ secrets.AUR_KEY }}
GORELEASER_KEY: ${{ secrets.GORELEASER_KEY }}
- uses: actions/setup-python@v4
- run: pip install --upgrade cloudsmith-cli
- name: Publish to CloudSmith
run: sh cli/upload_to_cloudsmith.sh
env:
CLOUDSMITH_API_KEY: ${{ secrets.CLOUDSMITH_API_KEY }}
goreleaser:
runs-on: ubuntu-latest
needs: [cli-integration-tests]
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0
- name: 🐋 Login to Docker Hub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: 🔧 Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- run: git fetch --force --tags
- run: echo "Ref name ${{github.ref_name}}"
- uses: actions/setup-go@v3
with:
go-version: ">=1.19.3"
cache: true
cache-dependency-path: cli/go.sum
- name: Setup for libssl1.0-dev
run: |
echo 'deb http://security.ubuntu.com/ubuntu bionic-security main' | sudo tee -a /etc/apt/sources.list
sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 3B4FE6ACC0B21F32
sudo apt update
sudo apt-get install -y libssl1.0-dev
- name: OSXCross for CGO Support
run: |
mkdir ../../osxcross
git clone https://github.com/plentico/osxcross-target.git ../../osxcross/target
- uses: goreleaser/goreleaser-action@v4
with:
distribution: goreleaser-pro
version: v1.26.2-pro
args: release --clean
env:
GITHUB_TOKEN: ${{ secrets.GO_RELEASER_GITHUB_TOKEN }}
POSTHOG_API_KEY_FOR_CLI: ${{ secrets.POSTHOG_API_KEY_FOR_CLI }}
FURY_TOKEN: ${{ secrets.FURYPUSHTOKEN }}
AUR_KEY: ${{ secrets.AUR_KEY }}
GORELEASER_KEY: ${{ secrets.GORELEASER_KEY }}
- uses: actions/setup-python@v4
- run: pip install --upgrade cloudsmith-cli
- uses: ruby/setup-ruby@354a1ad156761f5ee2b7b13fa8e09943a5e8d252
with:
ruby-version: "3.3" # Not needed with a .ruby-version, .tool-versions or mise.toml
bundler-cache: true # runs 'bundle install' and caches installed gems automatically
- name: Install deb-s3
run: gem install deb-s3
- name: Configure GPG Key
run: echo -n "$GPG_SIGNING_KEY" | base64 --decode | gpg --batch --import
env:
GPG_SIGNING_KEY: ${{ secrets.GPG_SIGNING_KEY }}
GPG_SIGNING_KEY_PASSPHRASE: ${{ secrets.GPG_SIGNING_KEY_PASSPHRASE }}
- name: Publish to CloudSmith
run: sh cli/upload_to_cloudsmith.sh
env:
CLOUDSMITH_API_KEY: ${{ secrets.CLOUDSMITH_API_KEY }}
INFISICAL_CLI_S3_BUCKET: ${{ secrets.INFISICAL_CLI_S3_BUCKET }}
INFISICAL_CLI_REPO_SIGNING_KEY_ID: ${{ secrets.INFISICAL_CLI_REPO_SIGNING_KEY_ID }}
AWS_ACCESS_KEY_ID: ${{ secrets.INFISICAL_CLI_REPO_AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.INFISICAL_CLI_REPO_AWS_SECRET_ACCESS_KEY }}
- name: Invalidate Cloudfront cache
run: aws cloudfront create-invalidation --distribution-id $CLOUDFRONT_DISTRIBUTION_ID --paths '/deb/dists/stable/*'
env:
AWS_ACCESS_KEY_ID: ${{ secrets.INFISICAL_CLI_REPO_AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.INFISICAL_CLI_REPO_AWS_SECRET_ACCESS_KEY }}
CLOUDFRONT_DISTRIBUTION_ID: ${{ secrets.INFISICAL_CLI_REPO_CLOUDFRONT_DISTRIBUTION_ID }}

View File

@ -162,6 +162,24 @@ scoop:
description: "The official Infisical CLI"
license: MIT
winget:
- name: infisical
publisher: infisical
license: MIT
homepage: https://infisical.com
short_description: "The official Infisical CLI"
repository:
owner: infisical
name: winget-pkgs
branch: "infisical-{{.Version}}"
pull_request:
enabled: true
draft: false
base:
owner: microsoft
name: winget-pkgs
branch: master
aurs:
- name: infisical-bin
homepage: "https://infisical.com"

View File

@ -14,3 +14,13 @@ docs/self-hosting/guides/automated-bootstrapping.mdx:jwt:74
frontend/src/pages/secret-manager/SecretDashboardPage/components/SecretListView/SecretDetailSidebar.tsx:generic-api-key:72
k8-operator/config/samples/crd/pushsecret/source-secret-with-templating.yaml:private-key:11
k8-operator/config/samples/crd/pushsecret/push-secret-with-template.yaml:private-key:52
backend/src/ee/services/secret-rotation-v2/secret-rotation-v2-types.ts:generic-api-key:125
frontend/src/components/permissions/AccessTree/nodes/RoleNode.tsx:generic-api-key:67
frontend/src/components/secret-rotations-v2/RotateSecretRotationV2Modal.tsx:generic-api-key:14
frontend/src/components/secret-rotations-v2/SecretRotationV2StatusBadge.tsx:generic-api-key:11
frontend/src/components/secret-rotations-v2/ViewSecretRotationV2GeneratedCredentials/ViewSecretRotationV2GeneratedCredentials.tsx:generic-api-key:23
frontend/src/hooks/api/secretRotationsV2/types/index.ts:generic-api-key:28
frontend/src/hooks/api/secretRotationsV2/types/index.ts:generic-api-key:65
frontend/src/pages/secret-manager/SecretDashboardPage/components/SecretRotationListView/SecretRotationItem.tsx:generic-api-key:26
docs/documentation/platform/kms/overview.mdx:generic-api-key:281
docs/documentation/platform/kms/overview.mdx:generic-api-key:344

View File

@ -50,7 +50,7 @@ We're on a mission to make security tooling more accessible to everyone, not jus
- **[Dashboard](https://infisical.com/docs/documentation/platform/project)**: Manage secrets across projects and environments (e.g. development, production, etc.) through a user-friendly interface.
- **[Native Integrations](https://infisical.com/docs/integrations/overview)**: Sync secrets to platforms like [GitHub](https://infisical.com/docs/integrations/cicd/githubactions), [Vercel](https://infisical.com/docs/integrations/cloud/vercel), [AWS](https://infisical.com/docs/integrations/cloud/aws-secret-manager), and use tools like [Terraform](https://infisical.com/docs/integrations/frameworks/terraform), [Ansible](https://infisical.com/docs/integrations/platforms/ansible), and more.
- **[Secret versioning](https://infisical.com/docs/documentation/platform/secret-versioning)** and **[Point-in-Time Recovery](https://infisical.com/docs/documentation/platform/pit-recovery)**: Keep track of every secret and project state; roll back when needed.
- **[Secret Rotation](https://infisical.com/docs/documentation/platform/secret-rotation/overview)**: Rotate secrets at regular intervals for services like [PostgreSQL](https://infisical.com/docs/documentation/platform/secret-rotation/postgres), [MySQL](https://infisical.com/docs/documentation/platform/secret-rotation/mysql), [AWS IAM](https://infisical.com/docs/documentation/platform/secret-rotation/aws-iam), and more.
- **[Secret Rotation](https://infisical.com/docs/documentation/platform/secret-rotation/overview)**: Rotate secrets at regular intervals for services like [PostgreSQL](https://infisical.com/docs/documentation/platform/secret-rotation/postgres-credentials), [MySQL](https://infisical.com/docs/documentation/platform/secret-rotation/mysql), [AWS IAM](https://infisical.com/docs/documentation/platform/secret-rotation/aws-iam), and more.
- **[Dynamic Secrets](https://infisical.com/docs/documentation/platform/dynamic-secrets/overview)**: Generate ephemeral secrets on-demand for services like [PostgreSQL](https://infisical.com/docs/documentation/platform/dynamic-secrets/postgresql), [MySQL](https://infisical.com/docs/documentation/platform/dynamic-secrets/mysql), [RabbitMQ](https://infisical.com/docs/documentation/platform/dynamic-secrets/rabbit-mq), and more.
- **[Secret Scanning and Leak Prevention](https://infisical.com/docs/cli/scanning-overview)**: Prevent secrets from leaking to git.
- **[Infisical Kubernetes Operator](https://infisical.com/docs/documentation/getting-started/kubernetes)**: Deliver secrets to your Kubernetes workloads and automatically reload deployments.

View File

@ -8,7 +8,8 @@ RUN apt-get update && apt-get install -y \
python3 \
make \
g++ \
openssh-client
openssh-client \
openssl
# Install dependencies for TDS driver (required for SAP ASE dynamic secrets)
RUN apt-get install -y \

View File

@ -19,6 +19,7 @@ RUN apt-get update && apt-get install -y \
make \
g++ \
openssh-client \
openssl \
curl \
pkg-config

View File

@ -9,6 +9,7 @@ export const mockKeyStore = (): TKeyStoreFactory => {
store[key] = value;
return "OK";
},
setExpiry: async () => 0,
setItemWithExpiry: async (key, value) => {
store[key] = value;
return "OK";

View File

@ -11,6 +11,7 @@ export const mockQueue = (): TQueueServiceFactory => {
job[name] = jobData;
},
queuePg: async () => {},
schedulePg: async () => {},
initialize: async () => {},
shutdown: async () => undefined,
stopRepeatableJob: async () => true,

1210
backend/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -89,9 +89,8 @@
"@types/jsrp": "^0.2.6",
"@types/libsodium-wrappers": "^0.7.13",
"@types/lodash.isequal": "^4.5.8",
"@types/node": "^20.9.5",
"@types/node": "^20.17.30",
"@types/nodemailer": "^6.4.14",
"@types/passport-github": "^1.1.12",
"@types/passport-google-oauth20": "^2.0.14",
"@types/pg": "^8.10.9",
"@types/picomatch": "^2.3.3",
@ -150,6 +149,7 @@
"@infisical/quic": "^1.0.8",
"@node-saml/passport-saml": "^5.0.1",
"@octokit/auth-app": "^7.1.1",
"@octokit/plugin-paginate-graphql": "^5.2.4",
"@octokit/plugin-retry": "^5.0.5",
"@octokit/rest": "^20.0.2",
"@octokit/webhooks-types": "^7.3.1",
@ -208,10 +208,10 @@
"ora": "^7.0.1",
"oracledb": "^6.4.0",
"otplib": "^12.0.1",
"passport-github": "^1.1.0",
"passport-gitlab2": "^5.0.0",
"passport-google-oauth20": "^2.0.0",
"passport-ldapauth": "^3.0.1",
"passport-oauth2": "^1.8.0",
"pg": "^8.11.3",
"pg-boss": "^10.1.5",
"pg-query-stream": "^4.5.3",
@ -221,6 +221,7 @@
"pkijs": "^3.2.4",
"posthog-node": "^3.6.2",
"probot": "^13.3.8",
"re2": "^1.21.4",
"safe-regex": "^2.1.1",
"scim-patch": "^0.8.3",
"scim2-parse-filter": "^0.2.10",

View File

@ -5,6 +5,7 @@ import { Redis } from "ioredis";
import { TUsers } from "@app/db/schemas";
import { TAccessApprovalPolicyServiceFactory } from "@app/ee/services/access-approval-policy/access-approval-policy-service";
import { TAccessApprovalRequestServiceFactory } from "@app/ee/services/access-approval-request/access-approval-request-service";
import { TAssumePrivilegeServiceFactory } from "@app/ee/services/assume-privilege/assume-privilege-service";
import { TAuditLogServiceFactory } from "@app/ee/services/audit-log/audit-log-service";
import { TCreateAuditLogDTO } from "@app/ee/services/audit-log/audit-log-types";
import { TAuditLogStreamServiceFactory } from "@app/ee/services/audit-log-stream/audit-log-stream-service";
@ -14,6 +15,7 @@ import { TDynamicSecretServiceFactory } from "@app/ee/services/dynamic-secret/dy
import { TDynamicSecretLeaseServiceFactory } from "@app/ee/services/dynamic-secret-lease/dynamic-secret-lease-service";
import { TExternalKmsServiceFactory } from "@app/ee/services/external-kms/external-kms-service";
import { TGatewayServiceFactory } from "@app/ee/services/gateway/gateway-service";
import { TGithubOrgSyncServiceFactory } from "@app/ee/services/github-org-sync/github-org-sync-service";
import { TGroupServiceFactory } from "@app/ee/services/group/group-service";
import { TIdentityProjectAdditionalPrivilegeServiceFactory } from "@app/ee/services/identity-project-additional-privilege/identity-project-additional-privilege-service";
import { TIdentityProjectAdditionalPrivilegeV2ServiceFactory } from "@app/ee/services/identity-project-additional-privilege-v2/identity-project-additional-privilege-v2-service";
@ -33,10 +35,12 @@ import { TScimServiceFactory } from "@app/ee/services/scim/scim-service";
import { TSecretApprovalPolicyServiceFactory } from "@app/ee/services/secret-approval-policy/secret-approval-policy-service";
import { TSecretApprovalRequestServiceFactory } from "@app/ee/services/secret-approval-request/secret-approval-request-service";
import { TSecretRotationServiceFactory } from "@app/ee/services/secret-rotation/secret-rotation-service";
import { TSecretRotationV2ServiceFactory } from "@app/ee/services/secret-rotation-v2/secret-rotation-v2-service";
import { TSecretScanningServiceFactory } from "@app/ee/services/secret-scanning/secret-scanning-service";
import { TSecretSnapshotServiceFactory } from "@app/ee/services/secret-snapshot/secret-snapshot-service";
import { TSshCertificateAuthorityServiceFactory } from "@app/ee/services/ssh/ssh-certificate-authority-service";
import { TSshCertificateTemplateServiceFactory } from "@app/ee/services/ssh-certificate-template/ssh-certificate-template-service";
import { TSshHostServiceFactory } from "@app/ee/services/ssh-host/ssh-host-service";
import { TTrustedIpServiceFactory } from "@app/ee/services/trusted-ip/trusted-ip-service";
import { TAuthMode } from "@app/server/plugins/auth/inject-identity";
import { TApiKeyServiceFactory } from "@app/services/api-key/api-key-service";
@ -107,12 +111,14 @@ declare module "@fastify/request-context" {
};
};
identityPermissionMetadata?: Record<string, unknown>; // filled by permission service
assumedPrivilegeDetails?: { requesterId: string; actorId: string; actorType: ActorType; projectId: string };
}
}
declare module "fastify" {
interface Session {
callbackPort: string;
isAdminLogin: boolean;
}
interface FastifyRequest {
@ -134,8 +140,9 @@ declare module "fastify" {
rateLimits: RateLimitConfiguration;
// passport data
passportUser: {
isUserCompleted: string;
isUserCompleted: boolean;
providerAuthToken: string;
externalProviderAccessToken?: string;
};
kmipUser: {
projectId: string;
@ -205,6 +212,7 @@ declare module "fastify" {
certificateTemplate: TCertificateTemplateServiceFactory;
sshCertificateAuthority: TSshCertificateAuthorityServiceFactory;
sshCertificateTemplate: TSshCertificateTemplateServiceFactory;
sshHost: TSshHostServiceFactory;
certificateAuthority: TCertificateAuthorityServiceFactory;
certificateAuthorityCrl: TCertificateAuthorityCrlServiceFactory;
certificateEst: TCertificateEstServiceFactory;
@ -237,6 +245,9 @@ declare module "fastify" {
kmip: TKmipServiceFactory;
kmipOperation: TKmipOperationServiceFactory;
gateway: TGatewayServiceFactory;
secretRotationV2: TSecretRotationV2ServiceFactory;
assumePrivileges: TAssumePrivilegeServiceFactory;
githubOrgSync: TGithubOrgSyncServiceFactory;
};
// this is exclusive use for middlewares in which we need to inject data
// everywhere else access using service layer

View File

@ -17,6 +17,9 @@ import {
TApiKeys,
TApiKeysInsert,
TApiKeysUpdate,
TAppConnections,
TAppConnectionsInsert,
TAppConnectionsUpdate,
TAuditLogs,
TAuditLogsInsert,
TAuditLogStreams,
@ -65,6 +68,9 @@ import {
TDynamicSecrets,
TDynamicSecretsInsert,
TDynamicSecretsUpdate,
TExternalGroupOrgRoleMappings,
TExternalGroupOrgRoleMappingsInsert,
TExternalGroupOrgRoleMappingsUpdate,
TExternalKms,
TExternalKmsInsert,
TExternalKmsUpdate,
@ -77,6 +83,9 @@ import {
TGitAppOrg,
TGitAppOrgInsert,
TGitAppOrgUpdate,
TGithubOrgSyncConfigs,
TGithubOrgSyncConfigsInsert,
TGithubOrgSyncConfigsUpdate,
TGroupProjectMembershipRoles,
TGroupProjectMembershipRolesInsert,
TGroupProjectMembershipRolesUpdate,
@ -226,6 +235,9 @@ import {
TProjectSplitBackfillIds,
TProjectSplitBackfillIdsInsert,
TProjectSplitBackfillIdsUpdate,
TProjectSshConfigs,
TProjectSshConfigsInsert,
TProjectSshConfigsUpdate,
TProjectsUpdate,
TProjectTemplates,
TProjectTemplatesInsert,
@ -299,6 +311,12 @@ import {
TSecretRotations,
TSecretRotationsInsert,
TSecretRotationsUpdate,
TSecretRotationsV2,
TSecretRotationsV2Insert,
TSecretRotationsV2Update,
TSecretRotationV2SecretMappings,
TSecretRotationV2SecretMappingsInsert,
TSecretRotationV2SecretMappingsUpdate,
TSecrets,
TSecretScanningGitRisks,
TSecretScanningGitRisksInsert,
@ -320,15 +338,27 @@ import {
TSecretSnapshotsInsert,
TSecretSnapshotsUpdate,
TSecretsUpdate,
TSecretsV2,
TSecretsV2Insert,
TSecretsV2Update,
TSecretSyncs,
TSecretSyncsInsert,
TSecretSyncsUpdate,
TSecretTagJunction,
TSecretTagJunctionInsert,
TSecretTagJunctionUpdate,
TSecretTags,
TSecretTagsInsert,
TSecretTagsUpdate,
TSecretV2TagJunction,
TSecretV2TagJunctionInsert,
TSecretV2TagJunctionUpdate,
TSecretVersions,
TSecretVersionsInsert,
TSecretVersionsUpdate,
TSecretVersionsV2,
TSecretVersionsV2Insert,
TSecretVersionsV2Update,
TSecretVersionTagJunction,
TSecretVersionTagJunctionInsert,
TSecretVersionTagJunctionUpdate,
@ -356,6 +386,15 @@ import {
TSshCertificateTemplates,
TSshCertificateTemplatesInsert,
TSshCertificateTemplatesUpdate,
TSshHostLoginUserMappings,
TSshHostLoginUserMappingsInsert,
TSshHostLoginUserMappingsUpdate,
TSshHostLoginUsers,
TSshHostLoginUsersInsert,
TSshHostLoginUsersUpdate,
TSshHosts,
TSshHostsInsert,
TSshHostsUpdate,
TSuperAdmin,
TSuperAdminInsert,
TSuperAdminUpdate,
@ -387,24 +426,11 @@ import {
TWorkflowIntegrationsInsert,
TWorkflowIntegrationsUpdate
} from "@app/db/schemas";
import { TAppConnections, TAppConnectionsInsert, TAppConnectionsUpdate } from "@app/db/schemas/app-connections";
import {
TExternalGroupOrgRoleMappings,
TExternalGroupOrgRoleMappingsInsert,
TExternalGroupOrgRoleMappingsUpdate
} from "@app/db/schemas/external-group-org-role-mappings";
import { TSecretSyncs, TSecretSyncsInsert, TSecretSyncsUpdate } from "@app/db/schemas/secret-syncs";
import {
TSecretV2TagJunction,
TSecretV2TagJunctionInsert,
TSecretV2TagJunctionUpdate
} from "@app/db/schemas/secret-v2-tag-junction";
import {
TSecretVersionsV2,
TSecretVersionsV2Insert,
TSecretVersionsV2Update
} from "@app/db/schemas/secret-versions-v2";
import { TSecretsV2, TSecretsV2Insert, TSecretsV2Update } from "@app/db/schemas/secrets-v2";
TSecretReminderRecipients,
TSecretReminderRecipientsInsert,
TSecretReminderRecipientsUpdate
} from "@app/db/schemas/secret-reminder-recipients";
declare module "knex" {
namespace Knex {
@ -419,6 +445,7 @@ declare module "knex/types/tables" {
interface Tables {
[TableName.Users]: KnexOriginal.CompositeTableType<TUsers, TUsersInsert, TUsersUpdate>;
[TableName.Groups]: KnexOriginal.CompositeTableType<TGroups, TGroupsInsert, TGroupsUpdate>;
[TableName.SshHost]: KnexOriginal.CompositeTableType<TSshHosts, TSshHostsInsert, TSshHostsUpdate>;
[TableName.SshCertificateAuthority]: KnexOriginal.CompositeTableType<
TSshCertificateAuthorities,
TSshCertificateAuthoritiesInsert,
@ -444,6 +471,16 @@ declare module "knex/types/tables" {
TSshCertificateBodiesInsert,
TSshCertificateBodiesUpdate
>;
[TableName.SshHostLoginUser]: KnexOriginal.CompositeTableType<
TSshHostLoginUsers,
TSshHostLoginUsersInsert,
TSshHostLoginUsersUpdate
>;
[TableName.SshHostLoginUserMapping]: KnexOriginal.CompositeTableType<
TSshHostLoginUserMappings,
TSshHostLoginUserMappingsInsert,
TSshHostLoginUserMappingsUpdate
>;
[TableName.CertificateAuthority]: KnexOriginal.CompositeTableType<
TCertificateAuthorities,
TCertificateAuthoritiesInsert,
@ -548,6 +585,11 @@ declare module "knex/types/tables" {
[TableName.SuperAdmin]: KnexOriginal.CompositeTableType<TSuperAdmin, TSuperAdminInsert, TSuperAdminUpdate>;
[TableName.ApiKey]: KnexOriginal.CompositeTableType<TApiKeys, TApiKeysInsert, TApiKeysUpdate>;
[TableName.Project]: KnexOriginal.CompositeTableType<TProjects, TProjectsInsert, TProjectsUpdate>;
[TableName.ProjectSshConfig]: KnexOriginal.CompositeTableType<
TProjectSshConfigs,
TProjectSshConfigsInsert,
TProjectSshConfigsUpdate
>;
[TableName.ProjectMembership]: KnexOriginal.CompositeTableType<
TProjectMemberships,
TProjectMembershipsInsert,
@ -950,5 +992,25 @@ declare module "knex/types/tables" {
TOrgGatewayConfigInsert,
TOrgGatewayConfigUpdate
>;
[TableName.SecretRotationV2]: KnexOriginal.CompositeTableType<
TSecretRotationsV2,
TSecretRotationsV2Insert,
TSecretRotationsV2Update
>;
[TableName.SecretRotationV2SecretMapping]: KnexOriginal.CompositeTableType<
TSecretRotationV2SecretMappings,
TSecretRotationV2SecretMappingsInsert,
TSecretRotationV2SecretMappingsUpdate
>;
[TableName.SecretReminderRecipients]: KnexOriginal.CompositeTableType<
TSecretReminderRecipients,
TSecretReminderRecipientsInsert,
TSecretReminderRecipientsUpdate
>;
[TableName.GithubOrgSyncConfig]: KnexOriginal.CompositeTableType<
TGithubOrgSyncConfigs,
TGithubOrgSyncConfigsInsert,
TGithubOrgSyncConfigsUpdate
>;
}
}

View File

@ -0,0 +1,19 @@
import { Knex } from "knex";
import { TableName } from "@app/db/schemas";
export async function up(knex: Knex): Promise<void> {
if (!(await knex.schema.hasColumn(TableName.AppConnection, "isPlatformManagedCredentials"))) {
await knex.schema.alterTable(TableName.AppConnection, (t) => {
t.boolean("isPlatformManagedCredentials").defaultTo(false);
});
}
}
export async function down(knex: Knex): Promise<void> {
if (await knex.schema.hasColumn(TableName.AppConnection, "isPlatformManagedCredentials")) {
await knex.schema.alterTable(TableName.AppConnection, (t) => {
t.dropColumn("isPlatformManagedCredentials");
});
}
}

View File

@ -0,0 +1,58 @@
import { Knex } from "knex";
import { TableName } from "@app/db/schemas";
import { createOnUpdateTrigger, dropOnUpdateTrigger } from "@app/db/utils";
export async function up(knex: Knex): Promise<void> {
if (!(await knex.schema.hasTable(TableName.SecretRotationV2))) {
await knex.schema.createTable(TableName.SecretRotationV2, (t) => {
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
t.string("name", 32).notNullable();
t.string("description");
t.string("type").notNullable();
t.jsonb("parameters").notNullable();
t.jsonb("secretsMapping").notNullable();
t.binary("encryptedGeneratedCredentials").notNullable();
t.boolean("isAutoRotationEnabled").notNullable().defaultTo(true);
t.integer("activeIndex").notNullable().defaultTo(0);
t.uuid("folderId").notNullable();
t.foreign("folderId").references("id").inTable(TableName.SecretFolder).onDelete("CASCADE");
t.uuid("connectionId").notNullable();
t.foreign("connectionId").references("id").inTable(TableName.AppConnection);
t.timestamps(true, true, true);
t.integer("rotationInterval").notNullable();
t.jsonb("rotateAtUtc").notNullable(); // { hours: number; minutes: number }
t.string("rotationStatus").notNullable();
t.datetime("lastRotationAttemptedAt").notNullable();
t.datetime("lastRotatedAt").notNullable();
t.binary("encryptedLastRotationMessage"); // we encrypt this because it may contain sensitive info (SQL errors showing credentials)
t.string("lastRotationJobId");
t.datetime("nextRotationAt");
t.boolean("isLastRotationManual").notNullable().defaultTo(true); // creation is considered a "manual" rotation
});
await createOnUpdateTrigger(knex, TableName.SecretRotationV2);
await knex.schema.alterTable(TableName.SecretRotationV2, (t) => {
t.unique(["folderId", "name"]);
});
}
if (!(await knex.schema.hasTable(TableName.SecretRotationV2SecretMapping))) {
await knex.schema.createTable(TableName.SecretRotationV2SecretMapping, (t) => {
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
t.uuid("secretId").notNullable();
// scott: this is deferred to block secret deletion but not prevent folder/environment/project deletion
// ie, if rotation is being deleted as well we permit it, otherwise throw
t.foreign("secretId").references("id").inTable(TableName.SecretV2).deferrable("deferred");
t.uuid("rotationId").notNullable();
t.foreign("rotationId").references("id").inTable(TableName.SecretRotationV2).onDelete("CASCADE");
});
}
}
export async function down(knex: Knex): Promise<void> {
await knex.schema.dropTableIfExists(TableName.SecretRotationV2SecretMapping);
await knex.schema.dropTableIfExists(TableName.SecretRotationV2);
await dropOnUpdateTrigger(knex, TableName.SecretRotationV2);
}

View File

@ -0,0 +1,25 @@
import { Knex } from "knex";
import { KmsKeyUsage } from "@app/services/kms/kms-types";
import { TableName } from "../schemas";
export async function up(knex: Knex): Promise<void> {
const hasKeyUsageColumn = await knex.schema.hasColumn(TableName.KmsKey, "keyUsage");
if (!hasKeyUsageColumn) {
await knex.schema.alterTable(TableName.KmsKey, (t) => {
t.string("keyUsage").notNullable().defaultTo(KmsKeyUsage.ENCRYPT_DECRYPT);
});
}
}
export async function down(knex: Knex): Promise<void> {
const hasKeyUsageColumn = await knex.schema.hasColumn(TableName.KmsKey, "keyUsage");
if (hasKeyUsageColumn) {
await knex.schema.alterTable(TableName.KmsKey, (t) => {
t.dropColumn("keyUsage");
});
}
}

View File

@ -0,0 +1,32 @@
import { Knex } from "knex";
import { TableName } from "../schemas";
export async function up(knex: Knex): Promise<void> {
if (!(await knex.schema.hasColumn(TableName.SshCertificateAuthority, "keySource"))) {
await knex.schema.alterTable(TableName.SshCertificateAuthority, (t) => {
t.string("keySource");
});
// Backfilling the keySource to internal
await knex(TableName.SshCertificateAuthority).update({ keySource: "internal" });
await knex.schema.alterTable(TableName.SshCertificateAuthority, (t) => {
t.string("keySource").notNullable().alter();
});
}
if (await knex.schema.hasColumn(TableName.SshCertificate, "sshCaId")) {
await knex.schema.alterTable(TableName.SshCertificate, (t) => {
t.uuid("sshCaId").nullable().alter();
});
}
}
export async function down(knex: Knex): Promise<void> {
if (await knex.schema.hasColumn(TableName.SshCertificateAuthority, "keySource")) {
await knex.schema.alterTable(TableName.SshCertificateAuthority, (t) => {
t.dropColumn("keySource");
});
}
}

View File

@ -0,0 +1,93 @@
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.SshHost))) {
await knex.schema.createTable(TableName.SshHost, (t) => {
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
t.timestamps(true, true, true);
t.string("projectId").notNullable();
t.foreign("projectId").references("id").inTable(TableName.Project).onDelete("CASCADE");
t.string("hostname").notNullable();
t.string("userCertTtl").notNullable();
t.string("hostCertTtl").notNullable();
t.uuid("userSshCaId").notNullable();
t.foreign("userSshCaId").references("id").inTable(TableName.SshCertificateAuthority).onDelete("CASCADE");
t.uuid("hostSshCaId").notNullable();
t.foreign("hostSshCaId").references("id").inTable(TableName.SshCertificateAuthority).onDelete("CASCADE");
t.unique(["projectId", "hostname"]);
});
await createOnUpdateTrigger(knex, TableName.SshHost);
}
if (!(await knex.schema.hasTable(TableName.SshHostLoginUser))) {
await knex.schema.createTable(TableName.SshHostLoginUser, (t) => {
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
t.timestamps(true, true, true);
t.uuid("sshHostId").notNullable();
t.foreign("sshHostId").references("id").inTable(TableName.SshHost).onDelete("CASCADE");
t.string("loginUser").notNullable(); // e.g. ubuntu, root, ec2-user, ...
t.unique(["sshHostId", "loginUser"]);
});
await createOnUpdateTrigger(knex, TableName.SshHostLoginUser);
}
if (!(await knex.schema.hasTable(TableName.SshHostLoginUserMapping))) {
await knex.schema.createTable(TableName.SshHostLoginUserMapping, (t) => {
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
t.timestamps(true, true, true);
t.uuid("sshHostLoginUserId").notNullable();
t.foreign("sshHostLoginUserId").references("id").inTable(TableName.SshHostLoginUser).onDelete("CASCADE");
t.uuid("userId").nullable();
t.foreign("userId").references("id").inTable(TableName.Users).onDelete("CASCADE");
t.unique(["sshHostLoginUserId", "userId"]);
});
await createOnUpdateTrigger(knex, TableName.SshHostLoginUserMapping);
}
if (!(await knex.schema.hasTable(TableName.ProjectSshConfig))) {
// new table to store configuration for projects of type SSH (i.e. Infisical SSH)
await knex.schema.createTable(TableName.ProjectSshConfig, (t) => {
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
t.timestamps(true, true, true);
t.string("projectId").notNullable();
t.foreign("projectId").references("id").inTable(TableName.Project).onDelete("CASCADE");
t.uuid("defaultUserSshCaId");
t.foreign("defaultUserSshCaId").references("id").inTable(TableName.SshCertificateAuthority).onDelete("CASCADE");
t.uuid("defaultHostSshCaId");
t.foreign("defaultHostSshCaId").references("id").inTable(TableName.SshCertificateAuthority).onDelete("CASCADE");
});
await createOnUpdateTrigger(knex, TableName.ProjectSshConfig);
}
const hasColumn = await knex.schema.hasColumn(TableName.SshCertificate, "sshHostId");
if (!hasColumn) {
await knex.schema.alterTable(TableName.SshCertificate, (t) => {
t.uuid("sshHostId").nullable();
t.foreign("sshHostId").references("id").inTable(TableName.SshHost).onDelete("SET NULL");
});
}
}
export async function down(knex: Knex): Promise<void> {
await knex.schema.dropTableIfExists(TableName.ProjectSshConfig);
await dropOnUpdateTrigger(knex, TableName.ProjectSshConfig);
await knex.schema.dropTableIfExists(TableName.SshHostLoginUserMapping);
await dropOnUpdateTrigger(knex, TableName.SshHostLoginUserMapping);
await knex.schema.dropTableIfExists(TableName.SshHostLoginUser);
await dropOnUpdateTrigger(knex, TableName.SshHostLoginUser);
const hasColumn = await knex.schema.hasColumn(TableName.SshCertificate, "sshHostId");
if (hasColumn) {
await knex.schema.alterTable(TableName.SshCertificate, (t) => {
t.dropColumn("sshHostId");
});
}
await knex.schema.dropTableIfExists(TableName.SshHost);
await dropOnUpdateTrigger(knex, TableName.SshHost);
}

View File

@ -0,0 +1,20 @@
import { Knex } from "knex";
import { TableName } from "../schemas";
export async function up(knex: Knex): Promise<void> {
if (!(await knex.schema.hasColumn(TableName.ResourceMetadata, "dynamicSecretId"))) {
await knex.schema.alterTable(TableName.ResourceMetadata, (tb) => {
tb.uuid("dynamicSecretId");
tb.foreign("dynamicSecretId").references("id").inTable(TableName.DynamicSecret).onDelete("CASCADE");
});
}
}
export async function down(knex: Knex): Promise<void> {
if (await knex.schema.hasColumn(TableName.ResourceMetadata, "dynamicSecretId")) {
await knex.schema.alterTable(TableName.ResourceMetadata, (tb) => {
tb.dropColumn("dynamicSecretId");
});
}
}

View File

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

View File

@ -0,0 +1,27 @@
import { Knex } from "knex";
import { TableName } from "../schemas";
export async function up(knex: Knex): Promise<void> {
const hasCol = await knex.schema.hasColumn(TableName.ServiceToken, "expiryNotificationSent");
if (!hasCol) {
await knex.schema.alterTable(TableName.ServiceToken, (t) => {
t.boolean("expiryNotificationSent").defaultTo(false);
});
// Update only tokens where expiresAt is before current time
await knex(TableName.ServiceToken)
.whereRaw(`${TableName.ServiceToken}."expiresAt" < NOW()`)
.whereNotNull("expiresAt")
.update({ expiryNotificationSent: true });
}
}
export async function down(knex: Knex): Promise<void> {
const hasCol = await knex.schema.hasColumn(TableName.ServiceToken, "expiryNotificationSent");
if (hasCol) {
await knex.schema.alterTable(TableName.ServiceToken, (t) => {
t.dropColumn("expiryNotificationSent");
});
}
}

View File

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

View File

@ -0,0 +1,15 @@
import { Knex } from "knex";
import { TableName } from "../schemas";
export async function up(knex: Knex): Promise<void> {
await knex.schema.alterTable(TableName.Certificate, (t) => {
t.string("altNames", 4096).alter();
});
}
export async function down(knex: Knex): Promise<void> {
await knex.schema.alterTable(TableName.Certificate, (t) => {
t.string("altNames").alter(); // Defaults to varchar(255)
});
}

View File

@ -0,0 +1,15 @@
import { Knex } from "knex";
import { TableName } from "../schemas";
export async function up(knex: Knex): Promise<void> {
await knex.schema.alterTable(TableName.KmipOrgServerCertificates, (t) => {
t.string("altNames", 4096).alter();
});
}
export async function down(knex: Knex): Promise<void> {
await knex.schema.alterTable(TableName.KmipOrgServerCertificates, (t) => {
t.string("altNames").alter(); // Defaults to varchar(255)
});
}

View File

@ -0,0 +1,21 @@
import { Knex } from "knex";
import { OIDCJWTSignatureAlgorithm } from "@app/ee/services/oidc/oidc-config-types";
import { TableName } from "../schemas";
export async function up(knex: Knex): Promise<void> {
if (!(await knex.schema.hasColumn(TableName.OidcConfig, "jwtSignatureAlgorithm"))) {
await knex.schema.alterTable(TableName.OidcConfig, (t) => {
t.string("jwtSignatureAlgorithm").defaultTo(OIDCJWTSignatureAlgorithm.RS256).notNullable();
});
}
}
export async function down(knex: Knex): Promise<void> {
if (await knex.schema.hasColumn(TableName.OidcConfig, "jwtSignatureAlgorithm")) {
await knex.schema.alterTable(TableName.OidcConfig, (t) => {
t.dropColumn("jwtSignatureAlgorithm");
});
}
}

View File

@ -0,0 +1,19 @@
import { Knex } from "knex";
import { TableName } from "../schemas";
export async function up(knex: Knex): Promise<void> {
if (!(await knex.schema.hasColumn(TableName.Organization, "bypassOrgAuthEnabled"))) {
await knex.schema.alterTable(TableName.Organization, (t) => {
t.boolean("bypassOrgAuthEnabled").defaultTo(false).notNullable();
});
}
}
export async function down(knex: Knex): Promise<void> {
if (await knex.schema.hasColumn(TableName.Organization, "bypassOrgAuthEnabled")) {
await knex.schema.alterTable(TableName.Organization, (t) => {
t.dropColumn("bypassOrgAuthEnabled");
});
}
}

View File

@ -0,0 +1,34 @@
import { Knex } from "knex";
import { TableName } from "../schemas";
export async function up(knex: Knex): Promise<void> {
const hasSecretReminderRecipientsTable = await knex.schema.hasTable(TableName.SecretReminderRecipients);
if (!hasSecretReminderRecipientsTable) {
await knex.schema.createTable(TableName.SecretReminderRecipients, (table) => {
table.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
table.timestamps(true, true, true);
table.uuid("secretId").notNullable();
table.uuid("userId").notNullable();
table.string("projectId").notNullable();
// Based on userId rather than project membership ID so we can easily extend group support in the future if need be.
// This does however mean we need to manually clean up once a user is removed from a project.
table.foreign("userId").references("id").inTable(TableName.Users).onDelete("CASCADE");
table.foreign("secretId").references("id").inTable(TableName.SecretV2).onDelete("CASCADE");
table.foreign("projectId").references("id").inTable(TableName.Project).onDelete("CASCADE");
table.index("secretId");
table.unique(["secretId", "userId"]);
});
}
}
export async function down(knex: Knex): Promise<void> {
const hasSecretReminderRecipientsTable = await knex.schema.hasTable(TableName.SecretReminderRecipients);
if (hasSecretReminderRecipientsTable) {
await knex.schema.dropTableIfExists(TableName.SecretReminderRecipients);
}
}

View File

@ -0,0 +1,29 @@
import { Knex } from "knex";
import { TableName } from "@app/db/schemas";
export async function up(knex: Knex): Promise<void> {
await knex.schema.alterTable(TableName.SecretVersionV2, (table) => {
table.dropForeign(["userActorId"]);
table.dropForeign(["identityActorId"]);
});
await knex.schema.alterTable(TableName.SecretVersionV2, (table) => {
table.foreign("userActorId").references("id").inTable(TableName.Users).onDelete("SET NULL");
table.foreign("identityActorId").references("id").inTable(TableName.Identity).onDelete("SET NULL");
});
}
export async function down(knex: Knex): Promise<void> {
await knex.schema.alterTable(TableName.SecretVersionV2, (table) => {
table.dropForeign(["userActorId"]);
table.dropForeign(["identityActorId"]);
});
await knex.schema.alterTable(TableName.SecretVersionV2, (table) => {
table.foreign("userActorId").references("id").inTable(TableName.Users);
table.foreign("identityActorId").references("id").inTable(TableName.Identity);
});
}

View File

@ -0,0 +1,47 @@
import { Knex } from "knex";
import { ProjectType, TableName } from "../schemas";
export async function up(knex: Knex): Promise<void> {
const hasDefaultUserCaCol = await knex.schema.hasColumn(TableName.ProjectSshConfig, "defaultUserSshCaId");
const hasDefaultHostCaCol = await knex.schema.hasColumn(TableName.ProjectSshConfig, "defaultHostSshCaId");
if (hasDefaultUserCaCol && hasDefaultHostCaCol) {
await knex.schema.alterTable(TableName.ProjectSshConfig, (t) => {
t.dropForeign(["defaultUserSshCaId"]);
t.dropForeign(["defaultHostSshCaId"]);
});
await knex.schema.alterTable(TableName.ProjectSshConfig, (t) => {
// allow nullable (does not wipe existing values)
t.uuid("defaultUserSshCaId").nullable().alter();
t.uuid("defaultHostSshCaId").nullable().alter();
// re-add with SET NULL behavior (previously CASCADE)
t.foreign("defaultUserSshCaId").references("id").inTable(TableName.SshCertificateAuthority).onDelete("SET NULL");
t.foreign("defaultHostSshCaId").references("id").inTable(TableName.SshCertificateAuthority).onDelete("SET NULL");
});
}
// (dangtony98): backfill by adding null defaults CAs for all existing Infisical SSH projects
// that do not have an associated ProjectSshConfig record introduced in Infisical SSH V2.
const allProjects = await knex(TableName.Project).where("type", ProjectType.SSH).select("id");
const projectsWithConfig = await knex(TableName.ProjectSshConfig).select("projectId");
const projectIdsWithConfig = new Set(projectsWithConfig.map((config) => config.projectId));
const projectsNeedingConfig = allProjects.filter((project) => !projectIdsWithConfig.has(project.id));
if (projectsNeedingConfig.length > 0) {
const configsToInsert = projectsNeedingConfig.map((project) => ({
projectId: project.id,
defaultUserSshCaId: null,
defaultHostSshCaId: null,
createdAt: new Date(),
updatedAt: new Date()
}));
await knex.batchInsert(TableName.ProjectSshConfig, configsToInsert);
}
}
export async function down(): Promise<void> {}

View File

@ -0,0 +1,23 @@
import { Knex } from "knex";
import { TableName } from "../schemas";
export async function up(knex: Knex): Promise<void> {
const hasAliasColumn = await knex.schema.hasColumn(TableName.SshHost, "alias");
if (!hasAliasColumn) {
await knex.schema.alterTable(TableName.SshHost, (t) => {
t.string("alias").nullable();
t.unique(["projectId", "alias"]);
});
}
}
export async function down(knex: Knex): Promise<void> {
const hasAliasColumn = await knex.schema.hasColumn(TableName.SshHost, "alias");
if (hasAliasColumn) {
await knex.schema.alterTable(TableName.SshHost, (t) => {
t.dropUnique(["projectId", "alias"]);
t.dropColumn("alias");
});
}
}

View File

@ -0,0 +1,26 @@
import { Knex } from "knex";
import { TableName } from "../schemas";
import { createOnUpdateTrigger, dropOnUpdateTrigger } from "../utils";
export async function up(knex: Knex): Promise<void> {
const hasTable = await knex.schema.hasTable(TableName.GithubOrgSyncConfig);
if (!hasTable) {
await knex.schema.createTable(TableName.GithubOrgSyncConfig, (t) => {
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
t.string("githubOrgName").notNullable();
t.boolean("isActive").defaultTo(false);
t.binary("encryptedGithubOrgAccessToken");
t.uuid("orgId").notNullable().unique();
t.foreign("orgId").references("id").inTable(TableName.Organization).onDelete("CASCADE");
t.timestamps(true, true, true);
});
}
await createOnUpdateTrigger(knex, TableName.GithubOrgSyncConfig);
}
export async function down(knex: Knex): Promise<void> {
await knex.schema.dropTableIfExists(TableName.GithubOrgSyncConfig);
await dropOnUpdateTrigger(knex, TableName.GithubOrgSyncConfig);
}

View File

@ -0,0 +1,27 @@
import { Knex } from "knex";
import { getConfig } from "@app/lib/config/env";
import { TableName } from "../schemas";
export async function up(knex: Knex): Promise<void> {
const appCfg = getConfig();
const tokenDuration = appCfg?.JWT_REFRESH_LIFETIME;
if (!(await knex.schema.hasColumn(TableName.Organization, "userTokenExpiration"))) {
await knex.schema.alterTable(TableName.Organization, (t) => {
t.string("userTokenExpiration");
});
if (tokenDuration) {
await knex(TableName.Organization).update({ userTokenExpiration: tokenDuration });
}
}
}
export async function down(knex: Knex): Promise<void> {
if (await knex.schema.hasColumn(TableName.Organization, "userTokenExpiration")) {
await knex.schema.alterTable(TableName.Organization, (t) => {
t.dropColumn("userTokenExpiration");
});
}
}

View File

@ -17,7 +17,8 @@ export const AccessApprovalRequestsSchema = z.object({
permissions: z.unknown(),
createdAt: z.date(),
updatedAt: z.date(),
requestedByUserId: z.string().uuid()
requestedByUserId: z.string().uuid(),
note: z.string().nullable().optional()
});
export type TAccessApprovalRequests = z.infer<typeof AccessApprovalRequestsSchema>;

View File

@ -19,7 +19,8 @@ export const AppConnectionsSchema = z.object({
version: z.number().default(1),
orgId: z.string().uuid(),
createdAt: z.date(),
updatedAt: z.date()
updatedAt: z.date(),
isPlatformManagedCredentials: z.boolean().default(false).nullable().optional()
});
export type TAppConnections = z.infer<typeof AppConnectionsSchema>;

View File

@ -20,7 +20,7 @@ export const CertificatesSchema = z.object({
notAfter: z.date(),
revokedAt: z.date().nullable().optional(),
revocationReason: z.number().nullable().optional(),
altNames: z.string().default("").nullable().optional(),
altNames: z.string().nullable().optional(),
caCertId: z.string().uuid(),
certificateTemplateId: z.string().uuid().nullable().optional(),
keyUsages: z.string().array().nullable().optional(),

View File

@ -0,0 +1,24 @@
// 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 { zodBuffer } from "@app/lib/zod";
import { TImmutableDBKeys } from "./models";
export const GithubOrgSyncConfigsSchema = z.object({
id: z.string().uuid(),
githubOrgName: z.string(),
isActive: z.boolean().default(false).nullable().optional(),
encryptedGithubOrgAccessToken: zodBuffer.nullable().optional(),
orgId: z.string().uuid(),
createdAt: z.date(),
updatedAt: z.date()
});
export type TGithubOrgSyncConfigs = z.infer<typeof GithubOrgSyncConfigsSchema>;
export type TGithubOrgSyncConfigsInsert = Omit<z.input<typeof GithubOrgSyncConfigsSchema>, TImmutableDBKeys>;
export type TGithubOrgSyncConfigsUpdate = Partial<Omit<z.input<typeof GithubOrgSyncConfigsSchema>, TImmutableDBKeys>>;

View File

@ -3,6 +3,7 @@ export * from "./access-approval-policies-approvers";
export * from "./access-approval-requests";
export * from "./access-approval-requests-reviewers";
export * from "./api-keys";
export * from "./app-connections";
export * from "./audit-log-streams";
export * from "./audit-logs";
export * from "./auth-token-sessions";
@ -19,10 +20,12 @@ export * from "./certificate-templates";
export * from "./certificates";
export * from "./dynamic-secret-leases";
export * from "./dynamic-secrets";
export * from "./external-group-org-role-mappings";
export * from "./external-kms";
export * from "./gateways";
export * from "./git-app-install-sessions";
export * from "./git-app-org";
export * from "./github-org-sync-configs";
export * from "./group-project-membership-roles";
export * from "./group-project-memberships";
export * from "./groups";
@ -73,6 +76,7 @@ export * from "./project-memberships";
export * from "./project-roles";
export * from "./project-slack-configs";
export * from "./project-split-backfill-ids";
export * from "./project-ssh-configs";
export * from "./project-templates";
export * from "./project-user-additional-privilege";
export * from "./project-user-membership-roles";
@ -97,13 +101,16 @@ export * from "./secret-references";
export * from "./secret-references-v2";
export * from "./secret-rotation-output-v2";
export * from "./secret-rotation-outputs";
export * from "./secret-rotation-v2-secret-mappings";
export * from "./secret-rotations";
export * from "./secret-rotations-v2";
export * from "./secret-scanning-git-risks";
export * from "./secret-sharing";
export * from "./secret-snapshot-folders";
export * from "./secret-snapshot-secrets";
export * from "./secret-snapshot-secrets-v2";
export * from "./secret-snapshots";
export * from "./secret-syncs";
export * from "./secret-tag-junction";
export * from "./secret-tags";
export * from "./secret-v2-tag-junction";
@ -120,6 +127,9 @@ export * from "./ssh-certificate-authority-secrets";
export * from "./ssh-certificate-bodies";
export * from "./ssh-certificate-templates";
export * from "./ssh-certificates";
export * from "./ssh-host-login-user-mappings";
export * from "./ssh-host-login-users";
export * from "./ssh-hosts";
export * from "./super-admin";
export * from "./totp-configs";
export * from "./trusted-ips";

View File

@ -13,7 +13,7 @@ export const KmipOrgServerCertificatesSchema = z.object({
id: z.string().uuid(),
orgId: z.string().uuid(),
commonName: z.string(),
altNames: z.string(),
altNames: z.string().nullable().optional(),
serialNumber: z.string(),
keyAlgorithm: z.string(),
issuedAt: z.date(),

View File

@ -16,7 +16,8 @@ export const KmsKeysSchema = z.object({
name: z.string(),
createdAt: z.date(),
updatedAt: z.date(),
projectId: z.string().nullable().optional()
projectId: z.string().nullable().optional(),
keyUsage: z.string().default("encrypt-decrypt")
});
export type TKmsKeys = z.infer<typeof KmsKeysSchema>;

View File

@ -2,6 +2,9 @@ import { z } from "zod";
export enum TableName {
Users = "users",
SshHost = "ssh_hosts",
SshHostLoginUser = "ssh_host_login_users",
SshHostLoginUserMapping = "ssh_host_login_user_mappings",
SshCertificateAuthority = "ssh_certificate_authorities",
SshCertificateAuthoritySecret = "ssh_certificate_authority_secrets",
SshCertificateTemplate = "ssh_certificate_templates",
@ -38,6 +41,7 @@ export enum TableName {
SuperAdmin = "super_admin",
RateLimit = "rate_limit",
ApiKey = "api_keys",
ProjectSshConfig = "project_ssh_configs",
Project = "projects",
ProjectBot = "project_bots",
Environment = "project_environments",
@ -140,7 +144,11 @@ export enum TableName {
KmipClient = "kmip_clients",
KmipOrgConfig = "kmip_org_configs",
KmipOrgServerCertificates = "kmip_org_server_certificates",
KmipClientCertificates = "kmip_client_certificates"
KmipClientCertificates = "kmip_client_certificates",
SecretRotationV2 = "secret_rotations_v2",
SecretRotationV2SecretMapping = "secret_rotation_v2_secret_mappings",
SecretReminderRecipients = "secret_reminder_recipients",
GithubOrgSyncConfig = "github_org_sync_configs"
}
export type TImmutableDBKeys = "id" | "createdAt" | "updatedAt";

View File

@ -32,7 +32,8 @@ export const OidcConfigsSchema = z.object({
lastUsed: z.date().nullable().optional(),
manageGroupMemberships: z.boolean().default(false),
encryptedOidcClientId: zodBuffer,
encryptedOidcClientSecret: zodBuffer
encryptedOidcClientSecret: zodBuffer,
jwtSignatureAlgorithm: z.string().default("RS256")
});
export type TOidcConfigs = z.infer<typeof OidcConfigsSchema>;

View File

@ -23,10 +23,13 @@ export const OrganizationsSchema = z.object({
defaultMembershipRole: z.string().default("member"),
enforceMfa: z.boolean().default(false),
selectedMfaMethod: z.string().nullable().optional(),
secretShareSendToAnyone: z.boolean().default(true).nullable().optional(),
allowSecretSharingOutsideOrganization: z.boolean().default(true).nullable().optional(),
shouldUseNewPrivilegeSystem: z.boolean().default(true),
privilegeUpgradeInitiatedByUsername: z.string().nullable().optional(),
privilegeUpgradeInitiatedAt: z.date().nullable().optional(),
allowSecretSharingOutsideOrganization: z.boolean().default(true).nullable().optional()
bypassOrgAuthEnabled: z.boolean().default(false),
userTokenExpiration: z.string().nullable().optional()
});
export type TOrganizations = z.infer<typeof OrganizationsSchema>;

View File

@ -0,0 +1,21 @@
// 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 ProjectSshConfigsSchema = z.object({
id: z.string().uuid(),
createdAt: z.date(),
updatedAt: z.date(),
projectId: z.string(),
defaultUserSshCaId: z.string().uuid().nullable().optional(),
defaultHostSshCaId: z.string().uuid().nullable().optional()
});
export type TProjectSshConfigs = z.infer<typeof ProjectSshConfigsSchema>;
export type TProjectSshConfigsInsert = Omit<z.input<typeof ProjectSshConfigsSchema>, TImmutableDBKeys>;
export type TProjectSshConfigsUpdate = Partial<Omit<z.input<typeof ProjectSshConfigsSchema>, TImmutableDBKeys>>;

View File

@ -26,7 +26,8 @@ export const ProjectsSchema = z.object({
kmsSecretManagerEncryptedDataKey: zodBuffer.nullable().optional(),
description: z.string().nullable().optional(),
type: z.string(),
enforceCapitalization: z.boolean().default(false)
enforceCapitalization: z.boolean().default(false),
hasDeleteProtection: z.boolean().default(true).nullable().optional()
});
export type TProjects = z.infer<typeof ProjectsSchema>;

View File

@ -16,7 +16,8 @@ export const ResourceMetadataSchema = z.object({
identityId: z.string().uuid().nullable().optional(),
secretId: z.string().uuid().nullable().optional(),
createdAt: z.date(),
updatedAt: z.date()
updatedAt: z.date(),
dynamicSecretId: z.string().uuid().nullable().optional()
});
export type TResourceMetadata = z.infer<typeof ResourceMetadataSchema>;

View File

@ -0,0 +1,23 @@
// 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 SecretReminderRecipientsSchema = z.object({
id: z.string().uuid(),
createdAt: z.date(),
updatedAt: z.date(),
secretId: z.string().uuid(),
userId: z.string().uuid(),
projectId: z.string()
});
export type TSecretReminderRecipients = z.infer<typeof SecretReminderRecipientsSchema>;
export type TSecretReminderRecipientsInsert = Omit<z.input<typeof SecretReminderRecipientsSchema>, TImmutableDBKeys>;
export type TSecretReminderRecipientsUpdate = Partial<
Omit<z.input<typeof SecretReminderRecipientsSchema>, TImmutableDBKeys>
>;

View File

@ -0,0 +1,23 @@
// 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 SecretRotationV2SecretMappingsSchema = z.object({
id: z.string().uuid(),
secretId: z.string().uuid(),
rotationId: z.string().uuid()
});
export type TSecretRotationV2SecretMappings = z.infer<typeof SecretRotationV2SecretMappingsSchema>;
export type TSecretRotationV2SecretMappingsInsert = Omit<
z.input<typeof SecretRotationV2SecretMappingsSchema>,
TImmutableDBKeys
>;
export type TSecretRotationV2SecretMappingsUpdate = Partial<
Omit<z.input<typeof SecretRotationV2SecretMappingsSchema>, TImmutableDBKeys>
>;

View File

@ -0,0 +1,39 @@
// 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 { zodBuffer } from "@app/lib/zod";
import { TImmutableDBKeys } from "./models";
export const SecretRotationsV2Schema = z.object({
id: z.string().uuid(),
name: z.string(),
description: z.string().nullable().optional(),
type: z.string(),
parameters: z.unknown(),
secretsMapping: z.unknown(),
encryptedGeneratedCredentials: zodBuffer,
isAutoRotationEnabled: z.boolean().default(true),
activeIndex: z.number().default(0),
folderId: z.string().uuid(),
connectionId: z.string().uuid(),
createdAt: z.date(),
updatedAt: z.date(),
rotationInterval: z.number(),
rotateAtUtc: z.unknown(),
rotationStatus: z.string(),
lastRotationAttemptedAt: z.date(),
lastRotatedAt: z.date(),
encryptedLastRotationMessage: zodBuffer.nullable().optional(),
lastRotationJobId: z.string().nullable().optional(),
nextRotationAt: z.date().nullable().optional(),
isLastRotationManual: z.boolean().default(true)
});
export type TSecretRotationsV2 = z.infer<typeof SecretRotationsV2Schema>;
export type TSecretRotationsV2Insert = Omit<z.input<typeof SecretRotationsV2Schema>, TImmutableDBKeys>;
export type TSecretRotationsV2Update = Partial<Omit<z.input<typeof SecretRotationsV2Schema>, TImmutableDBKeys>>;

View File

@ -21,7 +21,8 @@ export const ServiceTokensSchema = z.object({
createdAt: z.date(),
updatedAt: z.date(),
createdBy: z.string(),
projectId: z.string()
projectId: z.string(),
expiryNotificationSent: z.boolean().default(false).nullable().optional()
});
export type TServiceTokens = z.infer<typeof ServiceTokensSchema>;

View File

@ -14,7 +14,8 @@ export const SshCertificateAuthoritiesSchema = z.object({
projectId: z.string(),
status: z.string(),
friendlyName: z.string(),
keyAlgorithm: z.string()
keyAlgorithm: z.string(),
keySource: z.string()
});
export type TSshCertificateAuthorities = z.infer<typeof SshCertificateAuthoritiesSchema>;

View File

@ -11,14 +11,15 @@ export const SshCertificatesSchema = z.object({
id: z.string().uuid(),
createdAt: z.date(),
updatedAt: z.date(),
sshCaId: z.string().uuid(),
sshCaId: z.string().uuid().nullable().optional(),
sshCertificateTemplateId: z.string().uuid().nullable().optional(),
serialNumber: z.string(),
certType: z.string(),
principals: z.string().array(),
keyId: z.string(),
notBefore: z.date(),
notAfter: z.date()
notAfter: z.date(),
sshHostId: z.string().uuid().nullable().optional()
});
export type TSshCertificates = z.infer<typeof SshCertificatesSchema>;

View File

@ -0,0 +1,22 @@
// 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 SshHostLoginUserMappingsSchema = z.object({
id: z.string().uuid(),
createdAt: z.date(),
updatedAt: z.date(),
sshHostLoginUserId: z.string().uuid(),
userId: z.string().uuid().nullable().optional()
});
export type TSshHostLoginUserMappings = z.infer<typeof SshHostLoginUserMappingsSchema>;
export type TSshHostLoginUserMappingsInsert = Omit<z.input<typeof SshHostLoginUserMappingsSchema>, TImmutableDBKeys>;
export type TSshHostLoginUserMappingsUpdate = Partial<
Omit<z.input<typeof SshHostLoginUserMappingsSchema>, TImmutableDBKeys>
>;

View File

@ -0,0 +1,20 @@
// 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 SshHostLoginUsersSchema = z.object({
id: z.string().uuid(),
createdAt: z.date(),
updatedAt: z.date(),
sshHostId: z.string().uuid(),
loginUser: z.string()
});
export type TSshHostLoginUsers = z.infer<typeof SshHostLoginUsersSchema>;
export type TSshHostLoginUsersInsert = Omit<z.input<typeof SshHostLoginUsersSchema>, TImmutableDBKeys>;
export type TSshHostLoginUsersUpdate = Partial<Omit<z.input<typeof SshHostLoginUsersSchema>, TImmutableDBKeys>>;

View File

@ -0,0 +1,25 @@
// 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 SshHostsSchema = z.object({
id: z.string().uuid(),
createdAt: z.date(),
updatedAt: z.date(),
projectId: z.string(),
hostname: z.string(),
userCertTtl: z.string(),
hostCertTtl: z.string(),
userSshCaId: z.string().uuid(),
hostSshCaId: z.string().uuid(),
alias: z.string().nullable().optional()
});
export type TSshHosts = z.infer<typeof SshHostsSchema>;
export type TSshHostsInsert = Omit<z.input<typeof SshHostsSchema>, TImmutableDBKeys>;
export type TSshHostsUpdate = Partial<Omit<z.input<typeof SshHostsSchema>, TImmutableDBKeys>>;

View File

@ -22,7 +22,8 @@ export const registerAccessApprovalRequestRouter = async (server: FastifyZodProv
body: z.object({
permissions: z.any().array(),
isTemporary: z.boolean(),
temporaryRange: z.string().optional()
temporaryRange: z.string().optional(),
note: z.string().max(255).optional()
}),
querystring: z.object({
projectSlug: z.string().trim()
@ -43,7 +44,8 @@ export const registerAccessApprovalRequestRouter = async (server: FastifyZodProv
actorOrgId: req.permission.orgId,
projectSlug: req.query.projectSlug,
temporaryRange: req.body.temporaryRange,
isTemporary: req.body.isTemporary
isTemporary: req.body.isTemporary,
note: req.body.note
});
return { approval: request };
}

View File

@ -0,0 +1,124 @@
import { requestContext } from "@fastify/request-context";
import { z } from "zod";
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
import { getConfig } from "@app/lib/config/env";
import { BadRequestError } from "@app/lib/errors";
import { writeLimit } from "@app/server/config/rateLimiter";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { ActorType, AuthMode } from "@app/services/auth/auth-type";
export const registerAssumePrivilegeRouter = async (server: FastifyZodProvider) => {
server.route({
method: "POST",
url: "/:projectId/assume-privileges",
config: {
rateLimit: writeLimit
},
schema: {
params: z.object({
projectId: z.string()
}),
body: z.object({
actorType: z.enum([ActorType.USER, ActorType.IDENTITY]),
actorId: z.string()
}),
response: {
200: z.object({
message: z.string()
})
}
},
onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req, res) => {
if (req.auth.authMode === AuthMode.JWT) {
const payload = await server.services.assumePrivileges.assumeProjectPrivileges({
targetActorType: req.body.actorType,
targetActorId: req.body.actorId,
projectId: req.params.projectId,
actorPermissionDetails: req.permission,
tokenVersionId: req.auth.tokenVersionId
});
const appCfg = getConfig();
void res.setCookie("infisical-project-assume-privileges", payload.assumePrivilegesToken, {
httpOnly: true,
path: "/",
sameSite: "strict",
secure: appCfg.HTTPS_ENABLED,
maxAge: 3600 // 1 hour in seconds
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
orgId: req.permission.orgId,
event: {
type: EventType.PROJECT_ASSUME_PRIVILEGE_SESSION_START,
metadata: {
projectId: req.params.projectId,
requesterEmail: req.auth.user.username,
requesterId: req.auth.user.id,
targetActorType: req.body.actorType,
targetActorId: req.body.actorId,
duration: "1hr"
}
}
});
return { message: "Successfully assumed role" };
}
throw new BadRequestError({ message: "Invalid auth mode" });
}
});
server.route({
method: "DELETE",
url: "/:projectId/assume-privileges",
config: {
rateLimit: writeLimit
},
schema: {
params: z.object({
projectId: z.string()
}),
response: {
200: z.object({
message: z.string()
})
}
},
onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req, res) => {
const assumedPrivilegeDetails = requestContext.get("assumedPrivilegeDetails");
if (req.auth.authMode === AuthMode.JWT && assumedPrivilegeDetails) {
const appCfg = getConfig();
void res.setCookie("infisical-project-assume-privileges", "", {
httpOnly: true,
path: "/",
sameSite: "strict",
secure: appCfg.HTTPS_ENABLED,
expires: new Date(0)
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
orgId: req.permission.orgId,
event: {
type: EventType.PROJECT_ASSUME_PRIVILEGE_SESSION_END,
metadata: {
projectId: req.params.projectId,
requesterEmail: req.auth.user.username,
requesterId: req.auth.user.id,
targetActorId: assumedPrivilegeDetails.actorId,
targetActorType: assumedPrivilegeDetails.actorType
}
}
});
return { message: "Successfully exited assumed role" };
}
throw new BadRequestError({ message: "Invalid auth mode" });
}
});
};

View File

@ -1,7 +1,7 @@
import { z } from "zod";
import { DynamicSecretLeasesSchema } from "@app/db/schemas";
import { DYNAMIC_SECRET_LEASES } from "@app/lib/api-docs";
import { ApiDocsTags, DYNAMIC_SECRET_LEASES } from "@app/lib/api-docs";
import { daysToMillisecond } from "@app/lib/dates";
import { removeTrailingSlash } from "@app/lib/fn";
import { ms } from "@app/lib/ms";
@ -18,6 +18,8 @@ export const registerDynamicSecretLeaseRouter = async (server: FastifyZodProvide
rateLimit: writeLimit
},
schema: {
hide: false,
tags: [ApiDocsTags.DynamicSecrets],
body: z.object({
dynamicSecretName: z.string().min(1).describe(DYNAMIC_SECRET_LEASES.CREATE.dynamicSecretName).toLowerCase(),
projectSlug: z.string().min(1).describe(DYNAMIC_SECRET_LEASES.CREATE.projectSlug),
@ -65,6 +67,8 @@ export const registerDynamicSecretLeaseRouter = async (server: FastifyZodProvide
rateLimit: writeLimit
},
schema: {
hide: false,
tags: [ApiDocsTags.DynamicSecrets],
params: z.object({
leaseId: z.string().min(1).describe(DYNAMIC_SECRET_LEASES.DELETE.leaseId)
}),
@ -107,6 +111,8 @@ export const registerDynamicSecretLeaseRouter = async (server: FastifyZodProvide
rateLimit: writeLimit
},
schema: {
hide: false,
tags: [ApiDocsTags.DynamicSecrets],
params: z.object({
leaseId: z.string().min(1).describe(DYNAMIC_SECRET_LEASES.RENEW.leaseId)
}),
@ -160,6 +166,8 @@ export const registerDynamicSecretLeaseRouter = async (server: FastifyZodProvide
rateLimit: readLimit
},
schema: {
hide: false,
tags: [ApiDocsTags.DynamicSecrets],
params: z.object({
leaseId: z.string().min(1).describe(DYNAMIC_SECRET_LEASES.GET_BY_LEASEID.leaseId)
}),

View File

@ -2,7 +2,7 @@ import { z } from "zod";
import { DynamicSecretLeasesSchema } from "@app/db/schemas";
import { DynamicSecretProviderSchema } from "@app/ee/services/dynamic-secret/providers/models";
import { DYNAMIC_SECRETS } from "@app/lib/api-docs";
import { ApiDocsTags, DYNAMIC_SECRETS } from "@app/lib/api-docs";
import { daysToMillisecond } from "@app/lib/dates";
import { removeTrailingSlash } from "@app/lib/fn";
import { ms } from "@app/lib/ms";
@ -11,6 +11,7 @@ import { slugSchema } from "@app/server/lib/schemas";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { SanitizedDynamicSecretSchema } from "@app/server/routes/sanitizedSchemas";
import { AuthMode } from "@app/services/auth/auth-type";
import { ResourceMetadataSchema } from "@app/services/resource-metadata/resource-metadata-schema";
export const registerDynamicSecretRouter = async (server: FastifyZodProvider) => {
server.route({
@ -20,6 +21,8 @@ export const registerDynamicSecretRouter = async (server: FastifyZodProvider) =>
rateLimit: writeLimit
},
schema: {
hide: false,
tags: [ApiDocsTags.DynamicSecrets],
body: z.object({
projectSlug: z.string().min(1).describe(DYNAMIC_SECRETS.CREATE.projectSlug),
provider: DynamicSecretProviderSchema.describe(DYNAMIC_SECRETS.CREATE.provider),
@ -48,7 +51,8 @@ export const registerDynamicSecretRouter = async (server: FastifyZodProvider) =>
.nullable(),
path: z.string().describe(DYNAMIC_SECRETS.CREATE.path).trim().default("/").transform(removeTrailingSlash),
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()
}),
response: {
200: z.object({
@ -109,6 +113,8 @@ export const registerDynamicSecretRouter = async (server: FastifyZodProvider) =>
rateLimit: writeLimit
},
schema: {
hide: false,
tags: [ApiDocsTags.DynamicSecrets],
params: z.object({
name: z.string().toLowerCase().describe(DYNAMIC_SECRETS.UPDATE.name)
}),
@ -143,7 +149,8 @@ export const registerDynamicSecretRouter = async (server: FastifyZodProvider) =>
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" });
})
.nullable(),
newName: z.string().describe(DYNAMIC_SECRETS.UPDATE.newName).optional()
newName: z.string().describe(DYNAMIC_SECRETS.UPDATE.newName).optional(),
metadata: ResourceMetadataSchema.optional()
})
}),
response: {
@ -176,6 +183,8 @@ export const registerDynamicSecretRouter = async (server: FastifyZodProvider) =>
rateLimit: writeLimit
},
schema: {
hide: false,
tags: [ApiDocsTags.DynamicSecrets],
params: z.object({
name: z.string().toLowerCase().describe(DYNAMIC_SECRETS.DELETE.name)
}),
@ -212,6 +221,8 @@ export const registerDynamicSecretRouter = async (server: FastifyZodProvider) =>
rateLimit: readLimit
},
schema: {
hide: false,
tags: [ApiDocsTags.DynamicSecrets],
params: z.object({
name: z.string().min(1).describe(DYNAMIC_SECRETS.GET_BY_NAME.name)
}),
@ -238,6 +249,7 @@ export const registerDynamicSecretRouter = async (server: FastifyZodProvider) =>
name: req.params.name,
...req.query
});
return { dynamicSecret: dynamicSecretCfg };
}
});
@ -249,6 +261,8 @@ export const registerDynamicSecretRouter = async (server: FastifyZodProvider) =>
rateLimit: readLimit
},
schema: {
hide: false,
tags: [ApiDocsTags.DynamicSecrets],
querystring: z.object({
projectSlug: z.string().min(1).describe(DYNAMIC_SECRETS.LIST.projectSlug),
path: z.string().trim().default("/").transform(removeTrailingSlash).describe(DYNAMIC_SECRETS.LIST.path),
@ -280,18 +294,20 @@ export const registerDynamicSecretRouter = async (server: FastifyZodProvider) =>
rateLimit: readLimit
},
schema: {
hide: false,
tags: [ApiDocsTags.DynamicSecrets],
params: z.object({
name: z.string().min(1).describe(DYNAMIC_SECRETS.LIST_LEAES_BY_NAME.name)
name: z.string().min(1).describe(DYNAMIC_SECRETS.LIST_LEASES_BY_NAME.name)
}),
querystring: z.object({
projectSlug: z.string().min(1).describe(DYNAMIC_SECRETS.LIST_LEAES_BY_NAME.projectSlug),
projectSlug: z.string().min(1).describe(DYNAMIC_SECRETS.LIST_LEASES_BY_NAME.projectSlug),
path: z
.string()
.trim()
.default("/")
.transform(removeTrailingSlash)
.describe(DYNAMIC_SECRETS.LIST_LEAES_BY_NAME.path),
environmentSlug: z.string().min(1).describe(DYNAMIC_SECRETS.LIST_LEAES_BY_NAME.environmentSlug)
.describe(DYNAMIC_SECRETS.LIST_LEASES_BY_NAME.path),
environmentSlug: z.string().min(1).describe(DYNAMIC_SECRETS.LIST_LEASES_BY_NAME.environmentSlug)
}),
response: {
200: z.object({

View File

@ -0,0 +1,129 @@
import { z } from "zod";
import { GithubOrgSyncConfigsSchema } from "@app/db/schemas";
import { CharacterType, zodValidateCharacters } from "@app/lib/validator/validate-string";
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type";
const SanitizedGithubOrgSyncSchema = GithubOrgSyncConfigsSchema.pick({
isActive: true,
id: true,
createdAt: true,
updatedAt: true,
orgId: true,
githubOrgName: true
});
const githubOrgNameValidator = zodValidateCharacters([CharacterType.AlphaNumeric, CharacterType.Hyphen]);
export const registerGithubOrgSyncRouter = async (server: FastifyZodProvider) => {
server.route({
url: "/",
method: "POST",
config: {
rateLimit: writeLimit
},
onRequest: verifyAuth([AuthMode.JWT]),
schema: {
body: z.object({
githubOrgName: githubOrgNameValidator(z.string().trim(), "GitHub Org Name"),
githubOrgAccessToken: z.string().trim().max(1000).optional(),
isActive: z.boolean().default(false)
}),
response: {
200: z.object({
githubOrgSyncConfig: SanitizedGithubOrgSyncSchema
})
}
},
handler: async (req) => {
const githubOrgSyncConfig = await server.services.githubOrgSync.createGithubOrgSync({
orgPermission: req.permission,
githubOrgName: req.body.githubOrgName,
githubOrgAccessToken: req.body.githubOrgAccessToken,
isActive: req.body.isActive
});
return { githubOrgSyncConfig };
}
});
server.route({
url: "/",
method: "PATCH",
config: {
rateLimit: writeLimit
},
onRequest: verifyAuth([AuthMode.JWT]),
schema: {
body: z
.object({
githubOrgName: githubOrgNameValidator(z.string().trim(), "GitHub Org Name"),
githubOrgAccessToken: z.string().trim().max(1000),
isActive: z.boolean()
})
.partial(),
response: {
200: z.object({
githubOrgSyncConfig: SanitizedGithubOrgSyncSchema
})
}
},
handler: async (req) => {
const githubOrgSyncConfig = await server.services.githubOrgSync.updateGithubOrgSync({
orgPermission: req.permission,
githubOrgName: req.body.githubOrgName,
githubOrgAccessToken: req.body.githubOrgAccessToken,
isActive: req.body.isActive
});
return { githubOrgSyncConfig };
}
});
server.route({
url: "/",
method: "DELETE",
config: {
rateLimit: writeLimit
},
onRequest: verifyAuth([AuthMode.JWT]),
schema: {
response: {
200: z.object({
githubOrgSyncConfig: SanitizedGithubOrgSyncSchema
})
}
},
handler: async (req) => {
const githubOrgSyncConfig = await server.services.githubOrgSync.deleteGithubOrgSync({
orgPermission: req.permission
});
return { githubOrgSyncConfig };
}
});
server.route({
url: "/",
method: "GET",
config: {
rateLimit: readLimit
},
onRequest: verifyAuth([AuthMode.JWT]),
schema: {
response: {
200: z.object({
githubOrgSyncConfig: SanitizedGithubOrgSyncSchema
})
}
},
handler: async (req) => {
const githubOrgSyncConfig = await server.services.githubOrgSync.getGithubOrgSync({
orgPermission: req.permission
});
return { githubOrgSyncConfig };
}
});
};

View File

@ -2,7 +2,7 @@ import { z } from "zod";
import { GroupsSchema, OrgMembershipRole, UsersSchema } from "@app/db/schemas";
import { EFilterReturnedUsers } from "@app/ee/services/group/group-types";
import { GROUPS } from "@app/lib/api-docs";
import { ApiDocsTags, GROUPS } from "@app/lib/api-docs";
import { slugSchema } from "@app/server/lib/schemas";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type";
@ -13,6 +13,8 @@ export const registerGroupRouter = async (server: FastifyZodProvider) => {
method: "POST",
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
schema: {
hide: false,
tags: [ApiDocsTags.Groups],
body: z.object({
name: z.string().trim().min(1).max(50).describe(GROUPS.CREATE.name),
slug: slugSchema({ min: 5, max: 36 }).optional().describe(GROUPS.CREATE.slug),
@ -40,6 +42,8 @@ export const registerGroupRouter = async (server: FastifyZodProvider) => {
method: "GET",
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
schema: {
hide: false,
tags: [ApiDocsTags.Groups],
params: z.object({
id: z.string().trim().describe(GROUPS.GET_BY_ID.id)
}),
@ -65,6 +69,8 @@ export const registerGroupRouter = async (server: FastifyZodProvider) => {
method: "GET",
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
schema: {
hide: false,
tags: [ApiDocsTags.Groups],
response: {
200: GroupsSchema.array()
}
@ -87,6 +93,8 @@ export const registerGroupRouter = async (server: FastifyZodProvider) => {
method: "PATCH",
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
schema: {
hide: false,
tags: [ApiDocsTags.Groups],
params: z.object({
id: z.string().trim().describe(GROUPS.UPDATE.id)
}),
@ -120,6 +128,8 @@ export const registerGroupRouter = async (server: FastifyZodProvider) => {
method: "DELETE",
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
schema: {
hide: false,
tags: [ApiDocsTags.Groups],
params: z.object({
id: z.string().trim().describe(GROUPS.DELETE.id)
}),
@ -145,6 +155,8 @@ export const registerGroupRouter = async (server: FastifyZodProvider) => {
url: "/:id/users",
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
schema: {
hide: false,
tags: [ApiDocsTags.Groups],
params: z.object({
id: z.string().trim().describe(GROUPS.LIST_USERS.id)
}),
@ -194,6 +206,8 @@ export const registerGroupRouter = async (server: FastifyZodProvider) => {
url: "/:id/users/:username",
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
schema: {
hide: false,
tags: [ApiDocsTags.Groups],
params: z.object({
id: z.string().trim().describe(GROUPS.ADD_USER.id),
username: z.string().trim().describe(GROUPS.ADD_USER.username)
@ -227,6 +241,8 @@ export const registerGroupRouter = async (server: FastifyZodProvider) => {
url: "/:id/users/:username",
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
schema: {
hide: false,
tags: [ApiDocsTags.Groups],
params: z.object({
id: z.string().trim().describe(GROUPS.DELETE_USER.id),
username: z.string().trim().describe(GROUPS.DELETE_USER.username)

View File

@ -3,7 +3,7 @@ import { z } from "zod";
import { IdentityProjectAdditionalPrivilegeTemporaryMode } from "@app/ee/services/identity-project-additional-privilege/identity-project-additional-privilege-types";
import { backfillPermissionV1SchemaToV2Schema } from "@app/ee/services/permission/project-permission";
import { IDENTITY_ADDITIONAL_PRIVILEGE } from "@app/lib/api-docs";
import { ApiDocsTags, IDENTITY_ADDITIONAL_PRIVILEGE } from "@app/lib/api-docs";
import { UnauthorizedError } from "@app/lib/errors";
import { ms } from "@app/lib/ms";
import { alphaNumericNanoId } from "@app/lib/nanoid";
@ -25,6 +25,8 @@ export const registerIdentityProjectAdditionalPrivilegeRouter = async (server: F
rateLimit: writeLimit
},
schema: {
hide: false,
tags: [ApiDocsTags.IdentitySpecificPrivilegesV1],
description: "Create a permanent or a non expiry specific privilege for identity.",
security: [
{
@ -85,6 +87,8 @@ export const registerIdentityProjectAdditionalPrivilegeRouter = async (server: F
rateLimit: writeLimit
},
schema: {
hide: false,
tags: [ApiDocsTags.IdentitySpecificPrivilegesV1],
description: "Create a temporary or a expiring specific privilege for identity.",
security: [
{
@ -157,6 +161,8 @@ export const registerIdentityProjectAdditionalPrivilegeRouter = async (server: F
rateLimit: writeLimit
},
schema: {
hide: false,
tags: [ApiDocsTags.IdentitySpecificPrivilegesV1],
description: "Update a specific privilege of an identity.",
security: [
{
@ -240,6 +246,8 @@ export const registerIdentityProjectAdditionalPrivilegeRouter = async (server: F
rateLimit: writeLimit
},
schema: {
hide: false,
tags: [ApiDocsTags.IdentitySpecificPrivilegesV1],
description: "Delete a specific privilege of an identity.",
security: [
{
@ -279,6 +287,8 @@ export const registerIdentityProjectAdditionalPrivilegeRouter = async (server: F
rateLimit: readLimit
},
schema: {
hide: false,
tags: [ApiDocsTags.IdentitySpecificPrivilegesV1],
description: "Retrieve details of a specific privilege by privilege slug.",
security: [
{
@ -319,6 +329,8 @@ export const registerIdentityProjectAdditionalPrivilegeRouter = async (server: F
rateLimit: readLimit
},
schema: {
hide: false,
tags: [ApiDocsTags.IdentitySpecificPrivilegesV1],
description: "List of a specific privilege of an identity in a project.",
security: [
{

View File

@ -2,12 +2,14 @@ import { registerProjectTemplateRouter } from "@app/ee/routes/v1/project-templat
import { registerAccessApprovalPolicyRouter } from "./access-approval-policy-router";
import { registerAccessApprovalRequestRouter } from "./access-approval-request-router";
import { registerAssumePrivilegeRouter } from "./assume-privilege-router";
import { registerAuditLogStreamRouter } from "./audit-log-stream-router";
import { registerCaCrlRouter } from "./certificate-authority-crl-router";
import { registerDynamicSecretLeaseRouter } from "./dynamic-secret-lease-router";
import { registerDynamicSecretRouter } from "./dynamic-secret-router";
import { registerExternalKmsRouter } from "./external-kms-router";
import { registerGatewayRouter } from "./gateway-router";
import { registerGithubOrgSyncRouter } from "./github-org-sync-router";
import { registerGroupRouter } from "./group-router";
import { registerIdentityProjectAdditionalPrivilegeRouter } from "./identity-project-additional-privilege-router";
import { registerKmipRouter } from "./kmip-router";
@ -32,6 +34,7 @@ import { registerSnapshotRouter } from "./snapshot-router";
import { registerSshCaRouter } from "./ssh-certificate-authority-router";
import { registerSshCertRouter } from "./ssh-certificate-router";
import { registerSshCertificateTemplateRouter } from "./ssh-certificate-template-router";
import { registerSshHostRouter } from "./ssh-host-router";
import { registerTrustedIpRouter } from "./trusted-ip-router";
import { registerUserAdditionalPrivilegeRouter } from "./user-additional-privilege-router";
@ -44,6 +47,7 @@ export const registerV1EERoutes = async (server: FastifyZodProvider) => {
await projectRouter.register(registerProjectRoleRouter);
await projectRouter.register(registerProjectRouter);
await projectRouter.register(registerTrustedIpRouter);
await projectRouter.register(registerAssumePrivilegeRouter);
},
{ prefix: "/workspace" }
);
@ -69,6 +73,7 @@ export const registerV1EERoutes = async (server: FastifyZodProvider) => {
);
await server.register(registerGatewayRouter, { prefix: "/gateways" });
await server.register(registerGithubOrgSyncRouter, { prefix: "/github-org-sync-config" });
await server.register(
async (pkiRouter) => {
@ -82,6 +87,7 @@ export const registerV1EERoutes = async (server: FastifyZodProvider) => {
await sshRouter.register(registerSshCaRouter, { prefix: "/ca" });
await sshRouter.register(registerSshCertRouter, { prefix: "/certificates" });
await sshRouter.register(registerSshCertificateTemplateRouter, { prefix: "/certificate-templates" });
await sshRouter.register(registerSshHostRouter, { prefix: "/hosts" });
},
{ prefix: "/ssh" }
);

View File

@ -2,7 +2,7 @@ import z from "zod";
import { KmsKeysSchema } from "@app/db/schemas";
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
import { SymmetricEncryption } from "@app/lib/crypto/cipher";
import { SymmetricKeyAlgorithm } from "@app/lib/crypto/cipher";
import { ForbiddenRequestError, NotFoundError } from "@app/lib/errors";
import { writeLimit } from "@app/server/config/rateLimiter";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
@ -74,7 +74,7 @@ export const registerKmipSpecRouter = async (server: FastifyZodProvider) => {
schema: {
description: "KMIP endpoint for creating managed objects",
body: z.object({
algorithm: z.nativeEnum(SymmetricEncryption)
algorithm: z.nativeEnum(SymmetricKeyAlgorithm)
}),
response: {
200: KmsKeysSchema
@ -433,7 +433,7 @@ export const registerKmipSpecRouter = async (server: FastifyZodProvider) => {
body: z.object({
key: z.string(),
name: z.string(),
algorithm: z.nativeEnum(SymmetricEncryption)
algorithm: z.nativeEnum(SymmetricKeyAlgorithm)
}),
response: {
200: z.object({

View File

@ -12,7 +12,7 @@ import RedisStore from "connect-redis";
import { z } from "zod";
import { OidcConfigsSchema } from "@app/db/schemas";
import { OIDCConfigurationType } from "@app/ee/services/oidc/oidc-config-types";
import { OIDCConfigurationType, OIDCJWTSignatureAlgorithm } from "@app/ee/services/oidc/oidc-config-types";
import { getConfig } from "@app/lib/config/env";
import { authRateLimit, readLimit, writeLimit } from "@app/server/config/rateLimiter";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
@ -30,7 +30,8 @@ const SanitizedOidcConfigSchema = OidcConfigsSchema.pick({
orgId: true,
isActive: true,
allowedEmailDomains: true,
manageGroupMemberships: true
manageGroupMemberships: true,
jwtSignatureAlgorithm: true
});
export const registerOidcRouter = async (server: FastifyZodProvider) => {
@ -136,11 +137,12 @@ export const registerOidcRouter = async (server: FastifyZodProvider) => {
url: "/login/error",
method: "GET",
handler: async (req, res) => {
const failureMessage = req.session.get<any>("messages");
await req.session.destroy();
return res.status(500).send({
error: "Authentication error",
details: req.query
details: failureMessage ?? req.query
});
}
});
@ -169,7 +171,8 @@ export const registerOidcRouter = async (server: FastifyZodProvider) => {
isActive: true,
orgId: true,
allowedEmailDomains: true,
manageGroupMemberships: true
manageGroupMemberships: true,
jwtSignatureAlgorithm: true
}).extend({
clientId: z.string(),
clientSecret: z.string()
@ -224,7 +227,8 @@ export const registerOidcRouter = async (server: FastifyZodProvider) => {
clientId: z.string().trim(),
clientSecret: z.string().trim(),
isActive: z.boolean(),
manageGroupMemberships: z.boolean().optional()
manageGroupMemberships: z.boolean().optional(),
jwtSignatureAlgorithm: z.nativeEnum(OIDCJWTSignatureAlgorithm).optional()
})
.partial()
.merge(z.object({ orgSlug: z.string() })),
@ -291,7 +295,11 @@ export const registerOidcRouter = async (server: FastifyZodProvider) => {
clientSecret: z.string().trim(),
isActive: z.boolean(),
orgSlug: z.string().trim(),
manageGroupMemberships: z.boolean().optional().default(false)
manageGroupMemberships: z.boolean().optional().default(false),
jwtSignatureAlgorithm: z
.nativeEnum(OIDCJWTSignatureAlgorithm)
.optional()
.default(OIDCJWTSignatureAlgorithm.RS256)
})
.superRefine((data, ctx) => {
if (data.configurationType === OIDCConfigurationType.CUSTOM) {

View File

@ -1,7 +1,7 @@
import { packRules } from "@casl/ability/extra";
import { z } from "zod";
import { ProjectMembershipRole, ProjectMembershipsSchema, ProjectRolesSchema } from "@app/db/schemas";
import { ProjectMembershipRole, ProjectRolesSchema } from "@app/db/schemas";
import {
backfillPermissionV1SchemaToV2Schema,
ProjectPermissionV1Schema
@ -245,13 +245,22 @@ export const registerProjectRoleRouter = async (server: FastifyZodProvider) => {
response: {
200: z.object({
data: z.object({
membership: ProjectMembershipsSchema.extend({
membership: z.object({
id: z.string(),
roles: z
.object({
role: z.string()
})
.array()
}),
assumedPrivilegeDetails: z
.object({
actorId: z.string(),
actorType: z.string(),
actorName: z.string(),
actorEmail: z.string().optional()
})
.optional(),
permissions: z.any().array()
})
})
@ -259,14 +268,20 @@ export const registerProjectRoleRouter = async (server: FastifyZodProvider) => {
},
onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => {
const { permissions, membership } = await server.services.projectRole.getUserPermission(
const { permissions, membership, assumedPrivilegeDetails } = await server.services.projectRole.getUserPermission(
req.permission.id,
req.params.projectId,
req.permission.authMethod,
req.permission.orgId
);
return { data: { permissions, membership } };
return {
data: {
permissions,
membership,
assumedPrivilegeDetails
}
};
}
});
};

View File

@ -2,7 +2,7 @@ import { z } from "zod";
import { AuditLogsSchema, SecretSnapshotsSchema } from "@app/db/schemas";
import { EventType, UserAgentType } from "@app/ee/services/audit-log/audit-log-types";
import { AUDIT_LOGS, PROJECTS } from "@app/lib/api-docs";
import { ApiDocsTags, AUDIT_LOGS, PROJECTS } from "@app/lib/api-docs";
import { getLastMidnightDateISO, removeTrailingSlash } from "@app/lib/fn";
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
@ -17,6 +17,8 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
rateLimit: readLimit
},
schema: {
hide: false,
tags: [ApiDocsTags.Projects],
description: "Return project secret snapshots ids",
security: [
{

View File

@ -5,7 +5,7 @@ import { EventType } from "@app/ee/services/audit-log/audit-log-types";
import { ProjectPermissionV2Schema } from "@app/ee/services/permission/project-permission";
import { ProjectTemplateDefaultEnvironments } from "@app/ee/services/project-template/project-template-constants";
import { isInfisicalProjectTemplate } from "@app/ee/services/project-template/project-template-fns";
import { ProjectTemplates } from "@app/lib/api-docs";
import { ApiDocsTags, ProjectTemplates } from "@app/lib/api-docs";
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
import { slugSchema } from "@app/server/lib/schemas";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
@ -101,6 +101,8 @@ export const registerProjectTemplateRouter = async (server: FastifyZodProvider)
rateLimit: readLimit
},
schema: {
hide: false,
tags: [ApiDocsTags.ProjectTemplates],
description: "List project templates for the current organization.",
response: {
200: z.object({
@ -137,6 +139,8 @@ export const registerProjectTemplateRouter = async (server: FastifyZodProvider)
rateLimit: readLimit
},
schema: {
hide: false,
tags: [ApiDocsTags.ProjectTemplates],
description: "Get a project template by ID.",
params: z.object({
templateId: z.string().uuid()
@ -176,6 +180,8 @@ export const registerProjectTemplateRouter = async (server: FastifyZodProvider)
rateLimit: writeLimit
},
schema: {
hide: false,
tags: [ApiDocsTags.ProjectTemplates],
description: "Create a project template.",
body: z.object({
name: slugSchema({ field: "name" })
@ -219,6 +225,8 @@ export const registerProjectTemplateRouter = async (server: FastifyZodProvider)
rateLimit: writeLimit
},
schema: {
hide: false,
tags: [ApiDocsTags.ProjectTemplates],
description: "Update a project template.",
params: z.object({ templateId: z.string().uuid().describe(ProjectTemplates.UPDATE.templateId) }),
body: z.object({
@ -269,6 +277,8 @@ export const registerProjectTemplateRouter = async (server: FastifyZodProvider)
rateLimit: writeLimit
},
schema: {
hide: false,
tags: [ApiDocsTags.ProjectTemplates],
description: "Delete a project template.",
params: z.object({ templateId: z.string().uuid().describe(ProjectTemplates.DELETE.templateId) }),

View File

@ -223,12 +223,18 @@ export const registerSamlRouter = async (server: FastifyZodProvider) => {
samlConfigId: z.string().trim()
})
},
preValidation: passport.authenticate("saml", {
session: false,
failureFlash: true,
failureRedirect: "/login/provider/error"
// this is due to zod type difference
}) as any,
preValidation: passport.authenticate(
"saml",
{
session: false
},
async (req, res, err, user) => {
if (err) {
throw new BadRequestError({ message: `Saml authentication failed. ${err?.message}`, error: err });
}
req.passportUser = user as { isUserCompleted: boolean; providerAuthToken: string };
}
) as any, // this is due to zod type difference
handler: (req, res) => {
if (req.passportUser.isUserCompleted) {
return res.redirect(

View File

@ -277,8 +277,10 @@ export const registerSecretApprovalRequestRouter = async (server: FastifyZodProv
reviewers: approvalRequestUser.extend({ status: z.string(), comment: z.string().optional() }).array(),
secretPath: z.string(),
commits: secretRawSchema
.omit({ _id: true, environment: true, workspace: true, type: true, version: true })
.omit({ _id: true, environment: true, workspace: true, type: true, version: true, secretValue: true })
.extend({
secretValue: z.string().optional(),
isRotatedSecret: z.boolean().optional(),
op: z.string(),
tags: SanitizedTagSchema.array().optional(),
secretMetadata: ResourceMetadataSchema.nullish(),

View File

@ -23,7 +23,8 @@ export const registerSecretRotationProviderRouter = async (server: FastifyZodPro
title: z.string(),
image: z.string().optional(),
description: z.string().optional(),
template: z.any()
template: z.any(),
isDeprecated: z.boolean().optional()
})
.array()
})

View File

@ -1,7 +1,7 @@
import { z } from "zod";
import { SecretSnapshotsSchema } from "@app/db/schemas";
import { PROJECTS } from "@app/lib/api-docs";
import { ApiDocsTags, PROJECTS } from "@app/lib/api-docs";
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { SanitizedTagSchema, secretRawSchema } from "@app/server/routes/sanitizedSchemas";
@ -33,7 +33,8 @@ export const registerSnapshotRouter = async (server: FastifyZodProvider) => {
.extend({
secretValueHidden: z.boolean(),
secretId: z.string(),
tags: SanitizedTagSchema.array()
tags: SanitizedTagSchema.array(),
isRotatedSecret: z.boolean().optional()
})
.array(),
folderVersion: z.object({ id: z.string(), name: z.string() }).array(),
@ -64,6 +65,8 @@ export const registerSnapshotRouter = async (server: FastifyZodProvider) => {
rateLimit: writeLimit
},
schema: {
hide: false,
tags: [ApiDocsTags.Projects],
description: "Roll back project secrets to those captured in a secret snapshot version.",
security: [
{

View File

@ -1,14 +1,15 @@
import { z } from "zod";
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
import { normalizeSshPrivateKey } from "@app/ee/services/ssh/ssh-certificate-authority-fns";
import { sanitizedSshCa } from "@app/ee/services/ssh/ssh-certificate-authority-schema";
import { SshCaStatus } from "@app/ee/services/ssh/ssh-certificate-authority-types";
import { SshCaKeySource, SshCaStatus } from "@app/ee/services/ssh/ssh-certificate-authority-types";
import { SshCertKeyAlgorithm } from "@app/ee/services/ssh-certificate/ssh-certificate-types";
import { sanitizedSshCertificateTemplate } from "@app/ee/services/ssh-certificate-template/ssh-certificate-template-schema";
import { SSH_CERTIFICATE_AUTHORITIES } from "@app/lib/api-docs";
import { ApiDocsTags, SSH_CERTIFICATE_AUTHORITIES } from "@app/lib/api-docs";
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type";
import { CertKeyAlgorithm } from "@app/services/certificate/certificate-types";
export const registerSshCaRouter = async (server: FastifyZodProvider) => {
server.route({
@ -19,15 +20,37 @@ export const registerSshCaRouter = async (server: FastifyZodProvider) => {
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
schema: {
hide: false,
tags: [ApiDocsTags.SshCertificateAuthorities],
description: "Create SSH CA",
body: z.object({
projectId: z.string().describe(SSH_CERTIFICATE_AUTHORITIES.CREATE.projectId),
friendlyName: z.string().describe(SSH_CERTIFICATE_AUTHORITIES.CREATE.friendlyName),
keyAlgorithm: z
.nativeEnum(CertKeyAlgorithm)
.default(CertKeyAlgorithm.RSA_2048)
.describe(SSH_CERTIFICATE_AUTHORITIES.CREATE.keyAlgorithm)
}),
body: z
.object({
projectId: z.string().describe(SSH_CERTIFICATE_AUTHORITIES.CREATE.projectId),
friendlyName: z.string().describe(SSH_CERTIFICATE_AUTHORITIES.CREATE.friendlyName),
keyAlgorithm: z
.nativeEnum(SshCertKeyAlgorithm)
.default(SshCertKeyAlgorithm.ED25519)
.describe(SSH_CERTIFICATE_AUTHORITIES.CREATE.keyAlgorithm),
publicKey: z.string().trim().optional().describe(SSH_CERTIFICATE_AUTHORITIES.CREATE.publicKey),
privateKey: z
.string()
.trim()
.optional()
.transform((val) => (val ? normalizeSshPrivateKey(val) : undefined))
.describe(SSH_CERTIFICATE_AUTHORITIES.CREATE.privateKey),
keySource: z
.nativeEnum(SshCaKeySource)
.default(SshCaKeySource.INTERNAL)
.describe(SSH_CERTIFICATE_AUTHORITIES.CREATE.keySource)
})
.refine((data) => data.keySource === SshCaKeySource.INTERNAL || (!!data.publicKey && !!data.privateKey), {
message: "publicKey and privateKey are required when keySource is external",
path: ["publicKey"]
})
.refine((data) => data.keySource === SshCaKeySource.EXTERNAL || !!data.keyAlgorithm, {
message: "keyAlgorithm is required when keySource is internal",
path: ["keyAlgorithm"]
}),
response: {
200: z.object({
ca: sanitizedSshCa.extend({
@ -71,6 +94,8 @@ export const registerSshCaRouter = async (server: FastifyZodProvider) => {
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
schema: {
hide: false,
tags: [ApiDocsTags.SshCertificateAuthorities],
description: "Get SSH CA",
params: z.object({
sshCaId: z.string().trim().describe(SSH_CERTIFICATE_AUTHORITIES.GET.sshCaId)
@ -117,6 +142,8 @@ export const registerSshCaRouter = async (server: FastifyZodProvider) => {
rateLimit: readLimit
},
schema: {
hide: false,
tags: [ApiDocsTags.SshCertificateAuthorities],
description: "Get public key of SSH CA",
params: z.object({
sshCaId: z.string().trim().describe(SSH_CERTIFICATE_AUTHORITIES.GET_PUBLIC_KEY.sshCaId)
@ -142,6 +169,8 @@ export const registerSshCaRouter = async (server: FastifyZodProvider) => {
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
schema: {
hide: false,
tags: [ApiDocsTags.SshCertificateAuthorities],
description: "Update SSH CA",
params: z.object({
sshCaId: z.string().trim().describe(SSH_CERTIFICATE_AUTHORITIES.UPDATE.sshCaId)
@ -195,6 +224,8 @@ export const registerSshCaRouter = async (server: FastifyZodProvider) => {
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
schema: {
hide: false,
tags: [ApiDocsTags.SshCertificateAuthorities],
description: "Delete SSH CA",
params: z.object({
sshCaId: z.string().trim().describe(SSH_CERTIFICATE_AUTHORITIES.DELETE.sshCaId)
@ -240,6 +271,8 @@ export const registerSshCaRouter = async (server: FastifyZodProvider) => {
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
schema: {
hide: false,
tags: [ApiDocsTags.SshCertificateAuthorities],
description: "Get list of certificate templates for the SSH CA",
params: z.object({
sshCaId: z.string().trim().describe(SSH_CERTIFICATE_AUTHORITIES.GET_CERTIFICATE_TEMPLATES.sshCaId)

View File

@ -2,13 +2,13 @@ import { z } from "zod";
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
import { SshCertType } from "@app/ee/services/ssh/ssh-certificate-authority-types";
import { SSH_CERTIFICATE_AUTHORITIES } from "@app/lib/api-docs";
import { SshCertKeyAlgorithm } from "@app/ee/services/ssh-certificate/ssh-certificate-types";
import { ApiDocsTags, SSH_CERTIFICATE_AUTHORITIES } from "@app/lib/api-docs";
import { ms } from "@app/lib/ms";
import { writeLimit } from "@app/server/config/rateLimiter";
import { getTelemetryDistinctId } from "@app/server/lib/telemetry";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type";
import { CertKeyAlgorithm } from "@app/services/certificate/certificate-types";
import { PostHogEventTypes } from "@app/services/telemetry/telemetry-types";
export const registerSshCertRouter = async (server: FastifyZodProvider) => {
@ -20,6 +20,8 @@ export const registerSshCertRouter = async (server: FastifyZodProvider) => {
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
schema: {
hide: false,
tags: [ApiDocsTags.SshCertificates],
description: "Sign SSH public key",
body: z.object({
certificateTemplateId: z
@ -100,6 +102,8 @@ export const registerSshCertRouter = async (server: FastifyZodProvider) => {
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
schema: {
hide: false,
tags: [ApiDocsTags.SshCertificates],
description: "Issue SSH credentials (certificate + key)",
body: z.object({
certificateTemplateId: z
@ -108,8 +112,8 @@ export const registerSshCertRouter = async (server: FastifyZodProvider) => {
.min(1)
.describe(SSH_CERTIFICATE_AUTHORITIES.ISSUE_SSH_CREDENTIALS.certificateTemplateId),
keyAlgorithm: z
.nativeEnum(CertKeyAlgorithm)
.default(CertKeyAlgorithm.RSA_2048)
.nativeEnum(SshCertKeyAlgorithm)
.default(SshCertKeyAlgorithm.ED25519)
.describe(SSH_CERTIFICATE_AUTHORITIES.ISSUE_SSH_CREDENTIALS.keyAlgorithm),
certType: z
.nativeEnum(SshCertType)
@ -133,7 +137,7 @@ export const registerSshCertRouter = async (server: FastifyZodProvider) => {
privateKey: z.string().describe(SSH_CERTIFICATE_AUTHORITIES.ISSUE_SSH_CREDENTIALS.privateKey),
publicKey: z.string().describe(SSH_CERTIFICATE_AUTHORITIES.ISSUE_SSH_CREDENTIALS.publicKey),
keyAlgorithm: z
.nativeEnum(CertKeyAlgorithm)
.nativeEnum(SshCertKeyAlgorithm)
.describe(SSH_CERTIFICATE_AUTHORITIES.ISSUE_SSH_CREDENTIALS.keyAlgorithm)
})
}

View File

@ -8,7 +8,7 @@ import {
isValidHostPattern,
isValidUserPattern
} from "@app/ee/services/ssh-certificate-template/ssh-certificate-template-validators";
import { SSH_CERTIFICATE_TEMPLATES } from "@app/lib/api-docs";
import { ApiDocsTags, SSH_CERTIFICATE_TEMPLATES } from "@app/lib/api-docs";
import { ms } from "@app/lib/ms";
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
@ -22,6 +22,8 @@ export const registerSshCertificateTemplateRouter = async (server: FastifyZodPro
rateLimit: readLimit
},
schema: {
hide: false,
tags: [ApiDocsTags.SshCertificateTemplates],
params: z.object({
certificateTemplateId: z.string().describe(SSH_CERTIFICATE_TEMPLATES.GET.certificateTemplateId)
}),
@ -61,6 +63,8 @@ export const registerSshCertificateTemplateRouter = async (server: FastifyZodPro
rateLimit: writeLimit
},
schema: {
hide: false,
tags: [ApiDocsTags.SshCertificateTemplates],
body: z
.object({
sshCaId: z.string().describe(SSH_CERTIFICATE_TEMPLATES.CREATE.sshCaId),
@ -92,8 +96,8 @@ export const registerSshCertificateTemplateRouter = async (server: FastifyZodPro
allowHostCertificates: z.boolean().describe(SSH_CERTIFICATE_TEMPLATES.CREATE.allowHostCertificates),
allowCustomKeyIds: z.boolean().describe(SSH_CERTIFICATE_TEMPLATES.CREATE.allowCustomKeyIds)
})
.refine((data) => ms(data.maxTTL) > ms(data.ttl), {
message: "Max TLL must be greater than TTL",
.refine((data) => ms(data.maxTTL) >= ms(data.ttl), {
message: "Max TLL must be greater than or equal to TTL",
path: ["maxTTL"]
}),
response: {
@ -141,6 +145,8 @@ export const registerSshCertificateTemplateRouter = async (server: FastifyZodPro
rateLimit: writeLimit
},
schema: {
hide: false,
tags: [ApiDocsTags.SshCertificateTemplates],
body: z.object({
status: z.nativeEnum(SshCertTemplateStatus).optional(),
name: z
@ -224,6 +230,8 @@ export const registerSshCertificateTemplateRouter = async (server: FastifyZodPro
rateLimit: writeLimit
},
schema: {
hide: false,
tags: [ApiDocsTags.SshCertificateTemplates],
params: z.object({
certificateTemplateId: z.string().describe(SSH_CERTIFICATE_TEMPLATES.DELETE.certificateTemplateId)
}),

View File

@ -0,0 +1,451 @@
import { z } from "zod";
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
import { SshCertKeyAlgorithm } from "@app/ee/services/ssh-certificate/ssh-certificate-types";
import { loginMappingSchema, sanitizedSshHost } from "@app/ee/services/ssh-host/ssh-host-schema";
import { isValidHostname } from "@app/ee/services/ssh-host/ssh-host-validators";
import { SSH_HOSTS } from "@app/lib/api-docs";
import { ms } from "@app/lib/ms";
import { publicSshCaLimit, readLimit, writeLimit } from "@app/server/config/rateLimiter";
import { slugSchema } from "@app/server/lib/schemas";
import { getTelemetryDistinctId } from "@app/server/lib/telemetry";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type";
import { PostHogEventTypes } from "@app/services/telemetry/telemetry-types";
export const registerSshHostRouter = async (server: FastifyZodProvider) => {
server.route({
method: "GET",
url: "/",
config: {
rateLimit: readLimit
},
schema: {
response: {
200: z.array(
sanitizedSshHost.extend({
loginMappings: z.array(loginMappingSchema)
})
)
}
},
onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => {
const hosts = await server.services.sshHost.listSshHosts({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId
});
return hosts;
}
});
server.route({
method: "GET",
url: "/:sshHostId",
config: {
rateLimit: readLimit
},
schema: {
params: z.object({
sshHostId: z.string().describe(SSH_HOSTS.GET.sshHostId)
}),
response: {
200: sanitizedSshHost.extend({
loginMappings: z.array(loginMappingSchema)
})
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const host = await server.services.sshHost.getSshHost({
sshHostId: req.params.sshHostId,
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
projectId: host.projectId,
event: {
type: EventType.GET_SSH_HOST,
metadata: {
sshHostId: host.id,
hostname: host.hostname
}
}
});
return host;
}
});
server.route({
method: "POST",
url: "/",
config: {
rateLimit: writeLimit
},
schema: {
description: "Add an SSH Host",
body: z.object({
projectId: z.string().describe(SSH_HOSTS.CREATE.projectId),
hostname: z
.string()
.min(1)
.trim()
.refine((v) => isValidHostname(v), {
message: "Hostname must be a valid hostname"
})
.describe(SSH_HOSTS.CREATE.hostname),
alias: slugSchema({ min: 0, max: 64, field: "alias" }).describe(SSH_HOSTS.CREATE.alias).default(""),
userCertTtl: z
.string()
.refine((val) => ms(val) > 0, "TTL must be a positive number")
.default("8h")
.describe(SSH_HOSTS.CREATE.userCertTtl),
hostCertTtl: z
.string()
.refine((val) => ms(val) > 0, "TTL must be a positive number")
.default("1y")
.describe(SSH_HOSTS.CREATE.hostCertTtl),
loginMappings: z.array(loginMappingSchema).default([]).describe(SSH_HOSTS.CREATE.loginMappings),
userSshCaId: z.string().describe(SSH_HOSTS.CREATE.userSshCaId).optional(),
hostSshCaId: z.string().describe(SSH_HOSTS.CREATE.hostSshCaId).optional()
}),
response: {
200: sanitizedSshHost.extend({
loginMappings: z.array(loginMappingSchema)
})
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const host = await server.services.sshHost.createSshHost({
...req.body,
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
projectId: host.projectId,
event: {
type: EventType.CREATE_SSH_HOST,
metadata: {
sshHostId: host.id,
hostname: host.hostname,
alias: host.alias ?? null,
userCertTtl: host.userCertTtl,
hostCertTtl: host.hostCertTtl,
loginMappings: host.loginMappings,
userSshCaId: host.userSshCaId,
hostSshCaId: host.hostSshCaId
}
}
});
return host;
}
});
server.route({
method: "PATCH",
url: "/:sshHostId",
config: {
rateLimit: writeLimit
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
schema: {
description: "Update SSH Host",
params: z.object({
sshHostId: z.string().trim().describe(SSH_HOSTS.UPDATE.sshHostId)
}),
body: z.object({
hostname: z
.string()
.trim()
.min(1)
.refine((v) => isValidHostname(v), {
message: "Hostname must be a valid hostname"
})
.optional()
.describe(SSH_HOSTS.UPDATE.hostname),
alias: slugSchema({ min: 0, max: 64, field: "alias" }).describe(SSH_HOSTS.UPDATE.alias).optional(),
userCertTtl: z
.string()
.refine((val) => ms(val) > 0, "TTL must be a positive number")
.optional()
.describe(SSH_HOSTS.UPDATE.userCertTtl),
hostCertTtl: z
.string()
.refine((val) => ms(val) > 0, "TTL must be a positive number")
.optional()
.describe(SSH_HOSTS.UPDATE.hostCertTtl),
loginMappings: z.array(loginMappingSchema).optional().describe(SSH_HOSTS.UPDATE.loginMappings)
}),
response: {
200: sanitizedSshHost.extend({
loginMappings: z.array(loginMappingSchema)
})
}
},
handler: async (req) => {
const host = await server.services.sshHost.updateSshHost({
sshHostId: req.params.sshHostId,
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
...req.body
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
projectId: host.projectId,
event: {
type: EventType.UPDATE_SSH_HOST,
metadata: {
sshHostId: host.id,
hostname: host.hostname,
alias: host.alias,
userCertTtl: host.userCertTtl,
hostCertTtl: host.hostCertTtl,
loginMappings: host.loginMappings,
userSshCaId: host.userSshCaId,
hostSshCaId: host.hostSshCaId
}
}
});
return host;
}
});
server.route({
method: "DELETE",
url: "/:sshHostId",
config: {
rateLimit: writeLimit
},
schema: {
params: z.object({
sshHostId: z.string().describe(SSH_HOSTS.DELETE.sshHostId)
}),
response: {
200: sanitizedSshHost.extend({
loginMappings: z.array(loginMappingSchema)
})
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const host = await server.services.sshHost.deleteSshHost({
sshHostId: req.params.sshHostId,
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
projectId: host.projectId,
event: {
type: EventType.DELETE_SSH_HOST,
metadata: {
sshHostId: host.id,
hostname: host.hostname
}
}
});
return host;
}
});
server.route({
method: "POST",
url: "/:sshHostId/issue-user-cert",
config: {
rateLimit: writeLimit
},
onRequest: verifyAuth([AuthMode.JWT]),
schema: {
description: "Issue SSH certificate for user",
params: z.object({
sshHostId: z.string().describe(SSH_HOSTS.ISSUE_SSH_CREDENTIALS.sshHostId)
}),
body: z.object({
loginUser: z.string().describe(SSH_HOSTS.ISSUE_SSH_CREDENTIALS.loginUser)
}),
response: {
200: z.object({
serialNumber: z.string().describe(SSH_HOSTS.ISSUE_SSH_CREDENTIALS.serialNumber),
signedKey: z.string().describe(SSH_HOSTS.ISSUE_SSH_CREDENTIALS.signedKey),
privateKey: z.string().describe(SSH_HOSTS.ISSUE_SSH_CREDENTIALS.privateKey),
publicKey: z.string().describe(SSH_HOSTS.ISSUE_SSH_CREDENTIALS.publicKey),
keyAlgorithm: z.nativeEnum(SshCertKeyAlgorithm).describe(SSH_HOSTS.ISSUE_SSH_CREDENTIALS.keyAlgorithm)
})
}
},
handler: async (req) => {
const { serialNumber, signedPublicKey, privateKey, publicKey, keyAlgorithm, host, principals } =
await server.services.sshHost.issueSshHostUserCert({
sshHostId: req.params.sshHostId,
loginUser: req.body.loginUser,
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
orgId: req.permission.orgId,
event: {
type: EventType.ISSUE_SSH_HOST_USER_CERT,
metadata: {
sshHostId: req.params.sshHostId,
hostname: host.hostname,
loginUser: req.body.loginUser,
principals,
ttl: host.userCertTtl
}
}
});
await server.services.telemetry.sendPostHogEvents({
event: PostHogEventTypes.IssueSshHostUserCert,
distinctId: getTelemetryDistinctId(req),
properties: {
sshHostId: req.params.sshHostId,
hostname: host.hostname,
principals,
...req.auditLogInfo
}
});
return {
serialNumber,
signedKey: signedPublicKey,
privateKey,
publicKey,
keyAlgorithm
};
}
});
server.route({
method: "POST",
url: "/:sshHostId/issue-host-cert",
config: {
rateLimit: writeLimit
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
schema: {
description: "Issue SSH certificate for host",
params: z.object({
sshHostId: z.string().describe(SSH_HOSTS.ISSUE_HOST_CERT.sshHostId)
}),
body: z.object({
publicKey: z.string().describe(SSH_HOSTS.ISSUE_HOST_CERT.publicKey)
}),
response: {
200: z.object({
serialNumber: z.string().describe(SSH_HOSTS.ISSUE_HOST_CERT.serialNumber),
signedKey: z.string().describe(SSH_HOSTS.ISSUE_HOST_CERT.signedKey)
})
}
},
handler: async (req) => {
const { host, principals, serialNumber, signedPublicKey } = await server.services.sshHost.issueSshHostHostCert({
sshHostId: req.params.sshHostId,
publicKey: req.body.publicKey,
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
orgId: req.permission.orgId,
event: {
type: EventType.ISSUE_SSH_HOST_HOST_CERT,
metadata: {
sshHostId: req.params.sshHostId,
hostname: host.hostname,
principals,
serialNumber,
ttl: host.hostCertTtl
}
}
});
await server.services.telemetry.sendPostHogEvents({
event: PostHogEventTypes.IssueSshHostHostCert,
distinctId: getTelemetryDistinctId(req),
properties: {
sshHostId: req.params.sshHostId,
hostname: host.hostname,
principals,
...req.auditLogInfo
}
});
return {
serialNumber,
signedKey: signedPublicKey
};
}
});
server.route({
method: "GET",
url: "/:sshHostId/user-ca-public-key",
config: {
rateLimit: publicSshCaLimit
},
schema: {
description: "Get public key of the user SSH CA linked to the host",
params: z.object({
sshHostId: z.string().trim().describe(SSH_HOSTS.GET_USER_CA_PUBLIC_KEY.sshHostId)
}),
response: {
200: z.string().describe(SSH_HOSTS.GET_USER_CA_PUBLIC_KEY.publicKey)
}
},
handler: async (req) => {
const publicKey = await server.services.sshHost.getSshHostUserCaPk(req.params.sshHostId);
return publicKey;
}
});
server.route({
method: "GET",
url: "/:sshHostId/host-ca-public-key",
config: {
rateLimit: publicSshCaLimit
},
schema: {
description: "Get public key of the host SSH CA linked to the host",
params: z.object({
sshHostId: z.string().trim().describe(SSH_HOSTS.GET_HOST_CA_PUBLIC_KEY.sshHostId)
}),
response: {
200: z.string().describe(SSH_HOSTS.GET_HOST_CA_PUBLIC_KEY.publicKey)
}
},
handler: async (req) => {
const publicKey = await server.services.sshHost.getSshHostHostCaPk(req.params.sshHostId);
return publicKey;
}
});
};

View File

@ -4,7 +4,7 @@ import { z } from "zod";
import { IdentityProjectAdditionalPrivilegeTemporaryMode } from "@app/ee/services/identity-project-additional-privilege-v2/identity-project-additional-privilege-v2-types";
import { checkForInvalidPermissionCombination } from "@app/ee/services/permission/permission-fns";
import { ProjectPermissionV2Schema } from "@app/ee/services/permission/project-permission";
import { IDENTITY_ADDITIONAL_PRIVILEGE_V2 } from "@app/lib/api-docs";
import { ApiDocsTags, IDENTITY_ADDITIONAL_PRIVILEGE_V2 } from "@app/lib/api-docs";
import { ms } from "@app/lib/ms";
import { alphaNumericNanoId } from "@app/lib/nanoid";
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
@ -21,6 +21,8 @@ export const registerIdentityProjectAdditionalPrivilegeRouter = async (server: F
rateLimit: writeLimit
},
schema: {
hide: false,
tags: [ApiDocsTags.IdentitySpecificPrivilegesV2],
description: "Add an additional privilege for identity.",
security: [
{
@ -84,6 +86,8 @@ export const registerIdentityProjectAdditionalPrivilegeRouter = async (server: F
rateLimit: writeLimit
},
schema: {
hide: false,
tags: [ApiDocsTags.IdentitySpecificPrivilegesV2],
description: "Update a specific identity privilege.",
security: [
{
@ -148,6 +152,8 @@ export const registerIdentityProjectAdditionalPrivilegeRouter = async (server: F
rateLimit: writeLimit
},
schema: {
hide: false,
tags: [ApiDocsTags.IdentitySpecificPrivilegesV2],
description: "Delete the specified identity privilege.",
security: [
{
@ -183,6 +189,8 @@ export const registerIdentityProjectAdditionalPrivilegeRouter = async (server: F
rateLimit: readLimit
},
schema: {
hide: false,
tags: [ApiDocsTags.IdentitySpecificPrivilegesV2],
description: "Retrieve details of a specific privilege by id.",
security: [
{
@ -218,6 +226,8 @@ export const registerIdentityProjectAdditionalPrivilegeRouter = async (server: F
rateLimit: readLimit
},
schema: {
hide: false,
tags: [ApiDocsTags.IdentitySpecificPrivilegesV2],
description: "Retrieve details of a specific privilege by slug.",
security: [
{
@ -258,6 +268,8 @@ export const registerIdentityProjectAdditionalPrivilegeRouter = async (server: F
rateLimit: readLimit
},
schema: {
hide: false,
tags: [ApiDocsTags.IdentitySpecificPrivilegesV2],
description: "List privileges for the specified identity by project.",
security: [
{

View File

@ -1,3 +1,8 @@
import {
registerSecretRotationV2Router,
SECRET_ROTATION_REGISTER_ROUTER_MAP
} from "@app/ee/routes/v2/secret-rotation-v2-routers";
import { registerIdentityProjectAdditionalPrivilegeRouter } from "./identity-project-additional-privilege-router";
import { registerProjectRoleRouter } from "./project-role-router";
@ -13,4 +18,17 @@ export const registerV2EERoutes = async (server: FastifyZodProvider) => {
await server.register(registerIdentityProjectAdditionalPrivilegeRouter, {
prefix: "/identity-project-additional-privilege"
});
await server.register(
async (secretRotationV2Router) => {
// register generic secret rotation endpoints
await secretRotationV2Router.register(registerSecretRotationV2Router);
// register service specific secret rotation endpoints (secret-rotations/postgres-credentials, etc.)
for await (const [type, router] of Object.entries(SECRET_ROTATION_REGISTER_ROUTER_MAP)) {
await secretRotationV2Router.register(router, { prefix: `/${type}` });
}
},
{ prefix: "/secret-rotations" }
);
};

View File

@ -4,7 +4,7 @@ import { z } from "zod";
import { ProjectMembershipRole, ProjectRolesSchema } from "@app/db/schemas";
import { checkForInvalidPermissionCombination } from "@app/ee/services/permission/permission-fns";
import { ProjectPermissionV2Schema } from "@app/ee/services/permission/project-permission";
import { PROJECT_ROLE } from "@app/lib/api-docs";
import { ApiDocsTags, PROJECT_ROLE } from "@app/lib/api-docs";
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
import { slugSchema } from "@app/server/lib/schemas";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
@ -20,6 +20,8 @@ export const registerProjectRoleRouter = async (server: FastifyZodProvider) => {
rateLimit: writeLimit
},
schema: {
hide: false,
tags: [ApiDocsTags.ProjectRoles],
description: "Create a project role",
security: [
{
@ -75,6 +77,8 @@ export const registerProjectRoleRouter = async (server: FastifyZodProvider) => {
rateLimit: writeLimit
},
schema: {
hide: false,
tags: [ApiDocsTags.ProjectRoles],
description: "Update a project role",
security: [
{
@ -130,6 +134,8 @@ export const registerProjectRoleRouter = async (server: FastifyZodProvider) => {
rateLimit: writeLimit
},
schema: {
hide: false,
tags: [ApiDocsTags.ProjectRoles],
description: "Delete a project role",
security: [
{
@ -166,6 +172,8 @@ export const registerProjectRoleRouter = async (server: FastifyZodProvider) => {
rateLimit: readLimit
},
schema: {
hide: false,
tags: [ApiDocsTags.ProjectRoles],
description: "List project role",
security: [
{
@ -204,6 +212,8 @@ export const registerProjectRoleRouter = async (server: FastifyZodProvider) => {
rateLimit: readLimit
},
schema: {
hide: false,
tags: [ApiDocsTags.ProjectRoles],
params: z.object({
projectId: z.string().trim().describe(PROJECT_ROLE.GET_ROLE_BY_SLUG.projectId),
roleSlug: z.string().trim().describe(PROJECT_ROLE.GET_ROLE_BY_SLUG.roleSlug)

View File

@ -0,0 +1,19 @@
import {
Auth0ClientSecretRotationGeneratedCredentialsSchema,
Auth0ClientSecretRotationSchema,
CreateAuth0ClientSecretRotationSchema,
UpdateAuth0ClientSecretRotationSchema
} from "@app/ee/services/secret-rotation-v2/auth0-client-secret";
import { SecretRotation } from "@app/ee/services/secret-rotation-v2/secret-rotation-v2-enums";
import { registerSecretRotationEndpoints } from "./secret-rotation-v2-endpoints";
export const registerAuth0ClientSecretRotationRouter = async (server: FastifyZodProvider) =>
registerSecretRotationEndpoints({
type: SecretRotation.Auth0ClientSecret,
server,
responseSchema: Auth0ClientSecretRotationSchema,
createSchema: CreateAuth0ClientSecretRotationSchema,
updateSchema: UpdateAuth0ClientSecretRotationSchema,
generatedCredentialsSchema: Auth0ClientSecretRotationGeneratedCredentialsSchema
});

View File

@ -0,0 +1,19 @@
import {
AwsIamUserSecretRotationGeneratedCredentialsSchema,
AwsIamUserSecretRotationSchema,
CreateAwsIamUserSecretRotationSchema,
UpdateAwsIamUserSecretRotationSchema
} from "@app/ee/services/secret-rotation-v2/aws-iam-user-secret";
import { SecretRotation } from "@app/ee/services/secret-rotation-v2/secret-rotation-v2-enums";
import { registerSecretRotationEndpoints } from "./secret-rotation-v2-endpoints";
export const registerAwsIamUserSecretRotationRouter = async (server: FastifyZodProvider) =>
registerSecretRotationEndpoints({
type: SecretRotation.AwsIamUserSecret,
server,
responseSchema: AwsIamUserSecretRotationSchema,
createSchema: CreateAwsIamUserSecretRotationSchema,
updateSchema: UpdateAwsIamUserSecretRotationSchema,
generatedCredentialsSchema: AwsIamUserSecretRotationGeneratedCredentialsSchema
});

View File

@ -0,0 +1,20 @@
import { SecretRotation } from "@app/ee/services/secret-rotation-v2/secret-rotation-v2-enums";
import { registerAuth0ClientSecretRotationRouter } from "./auth0-client-secret-rotation-router";
import { registerAwsIamUserSecretRotationRouter } from "./aws-iam-user-secret-rotation-router";
import { registerLdapPasswordRotationRouter } from "./ldap-password-rotation-router";
import { registerMsSqlCredentialsRotationRouter } from "./mssql-credentials-rotation-router";
import { registerPostgresCredentialsRotationRouter } from "./postgres-credentials-rotation-router";
export * from "./secret-rotation-v2-router";
export const SECRET_ROTATION_REGISTER_ROUTER_MAP: Record<
SecretRotation,
(server: FastifyZodProvider) => Promise<void>
> = {
[SecretRotation.PostgresCredentials]: registerPostgresCredentialsRotationRouter,
[SecretRotation.MsSqlCredentials]: registerMsSqlCredentialsRotationRouter,
[SecretRotation.Auth0ClientSecret]: registerAuth0ClientSecretRotationRouter,
[SecretRotation.LdapPassword]: registerLdapPasswordRotationRouter,
[SecretRotation.AwsIamUserSecret]: registerAwsIamUserSecretRotationRouter
};

View File

@ -0,0 +1,19 @@
import {
CreateLdapPasswordRotationSchema,
LdapPasswordRotationGeneratedCredentialsSchema,
LdapPasswordRotationSchema,
UpdateLdapPasswordRotationSchema
} from "@app/ee/services/secret-rotation-v2/ldap-password";
import { SecretRotation } from "@app/ee/services/secret-rotation-v2/secret-rotation-v2-enums";
import { registerSecretRotationEndpoints } from "./secret-rotation-v2-endpoints";
export const registerLdapPasswordRotationRouter = async (server: FastifyZodProvider) =>
registerSecretRotationEndpoints({
type: SecretRotation.LdapPassword,
server,
responseSchema: LdapPasswordRotationSchema,
createSchema: CreateLdapPasswordRotationSchema,
updateSchema: UpdateLdapPasswordRotationSchema,
generatedCredentialsSchema: LdapPasswordRotationGeneratedCredentialsSchema
});

View File

@ -0,0 +1,19 @@
import {
CreateMsSqlCredentialsRotationSchema,
MsSqlCredentialsRotationSchema,
UpdateMsSqlCredentialsRotationSchema
} from "@app/ee/services/secret-rotation-v2/mssql-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 registerMsSqlCredentialsRotationRouter = async (server: FastifyZodProvider) =>
registerSecretRotationEndpoints({
type: SecretRotation.MsSqlCredentials,
server,
responseSchema: MsSqlCredentialsRotationSchema,
createSchema: CreateMsSqlCredentialsRotationSchema,
updateSchema: UpdateMsSqlCredentialsRotationSchema,
generatedCredentialsSchema: SqlCredentialsRotationGeneratedCredentialsSchema
});

View File

@ -0,0 +1,19 @@
import {
CreatePostgresCredentialsRotationSchema,
PostgresCredentialsRotationSchema,
UpdatePostgresCredentialsRotationSchema
} from "@app/ee/services/secret-rotation-v2/postgres-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 registerPostgresCredentialsRotationRouter = async (server: FastifyZodProvider) =>
registerSecretRotationEndpoints({
type: SecretRotation.PostgresCredentials,
server,
responseSchema: PostgresCredentialsRotationSchema,
createSchema: CreatePostgresCredentialsRotationSchema,
updateSchema: UpdatePostgresCredentialsRotationSchema,
generatedCredentialsSchema: SqlCredentialsRotationGeneratedCredentialsSchema
});

View File

@ -0,0 +1,445 @@
import { z } from "zod";
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
import { SecretRotation } from "@app/ee/services/secret-rotation-v2/secret-rotation-v2-enums";
import { SECRET_ROTATION_NAME_MAP } from "@app/ee/services/secret-rotation-v2/secret-rotation-v2-maps";
import {
TRotateAtUtc,
TSecretRotationV2,
TSecretRotationV2GeneratedCredentials,
TSecretRotationV2Input
} from "@app/ee/services/secret-rotation-v2/secret-rotation-v2-types";
import { ApiDocsTags, SecretRotations } from "@app/lib/api-docs";
import { startsWithVowel } from "@app/lib/fn";
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type";
export const registerSecretRotationEndpoints = <
T extends TSecretRotationV2,
I extends TSecretRotationV2Input,
C extends TSecretRotationV2GeneratedCredentials
>({
server,
type,
createSchema,
updateSchema,
responseSchema,
generatedCredentialsSchema
}: {
type: SecretRotation;
server: FastifyZodProvider;
createSchema: z.ZodType<{
name: string;
environment: string;
secretPath: string;
projectId: string;
connectionId: string;
parameters: I["parameters"];
secretsMapping: I["secretsMapping"];
description?: string | null;
isAutoRotationEnabled?: boolean;
rotationInterval: number;
rotateAtUtc?: TRotateAtUtc;
}>;
updateSchema: z.ZodType<{
connectionId?: string;
name?: string;
environment?: string;
secretPath?: string;
parameters?: I["parameters"];
secretsMapping?: I["secretsMapping"];
description?: string | null;
isAutoRotationEnabled?: boolean;
rotationInterval?: number;
rotateAtUtc?: TRotateAtUtc;
}>;
responseSchema: z.ZodTypeAny;
generatedCredentialsSchema: z.ZodTypeAny;
}) => {
const rotationType = SECRET_ROTATION_NAME_MAP[type];
server.route({
method: "GET",
url: `/`,
config: {
rateLimit: readLimit
},
schema: {
hide: false,
tags: [ApiDocsTags.SecretRotations],
description: `List the ${rotationType} Rotations for the specified project.`,
querystring: z.object({
projectId: z.string().trim().min(1, "Project ID required").describe(SecretRotations.LIST(type).projectId)
}),
response: {
200: z.object({ secretRotations: responseSchema.array() })
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const {
query: { projectId }
} = req;
const secretRotations = (await server.services.secretRotationV2.listSecretRotationsByProjectId(
{ projectId, type },
req.permission
)) as T[];
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
projectId,
event: {
type: EventType.GET_SECRET_ROTATIONS,
metadata: {
type,
count: secretRotations.length,
rotationIds: secretRotations.map((rotation) => rotation.id)
}
}
});
return { secretRotations };
}
});
server.route({
method: "GET",
url: "/:rotationId",
config: {
rateLimit: readLimit
},
schema: {
hide: false,
tags: [ApiDocsTags.SecretRotations],
description: `Get the specified ${rotationType} Rotation by ID.`,
params: z.object({
rotationId: z.string().uuid().describe(SecretRotations.GET_BY_ID(type).rotationId)
}),
response: {
200: z.object({ secretRotation: responseSchema })
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const { rotationId } = req.params;
const secretRotation = (await server.services.secretRotationV2.findSecretRotationById(
{ rotationId, type },
req.permission
)) as T;
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
projectId: secretRotation.projectId,
event: {
type: EventType.GET_SECRET_ROTATION,
metadata: {
rotationId,
type,
secretPath: secretRotation.folder.path,
environment: secretRotation.environment.slug
}
}
});
return { secretRotation };
}
});
server.route({
method: "GET",
url: `/rotation-name/:rotationName`,
config: {
rateLimit: readLimit
},
schema: {
hide: false,
tags: [ApiDocsTags.SecretRotations],
description: `Get the specified ${rotationType} Rotation by name, secret path, environment and project ID.`,
params: z.object({
rotationName: z
.string()
.trim()
.min(1, "Rotation name required")
.describe(SecretRotations.GET_BY_NAME(type).rotationName)
}),
querystring: z.object({
projectId: z
.string()
.trim()
.min(1, "Project ID required")
.describe(SecretRotations.GET_BY_NAME(type).projectId),
secretPath: z
.string()
.trim()
.min(1, "Secret path required")
.describe(SecretRotations.GET_BY_NAME(type).secretPath),
environment: z
.string()
.trim()
.min(1, "Environment required")
.describe(SecretRotations.GET_BY_NAME(type).environment)
}),
response: {
200: z.object({ secretRotation: responseSchema })
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const { rotationName } = req.params;
const { projectId, secretPath, environment } = req.query;
const secretRotation = (await server.services.secretRotationV2.findSecretRotationByName(
{ rotationName, projectId, type, secretPath, environment },
req.permission
)) as T;
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
projectId,
event: {
type: EventType.GET_SECRET_ROTATION,
metadata: {
rotationId: secretRotation.id,
type,
secretPath,
environment
}
}
});
return { secretRotation };
}
});
server.route({
method: "POST",
url: "/",
config: {
rateLimit: writeLimit
},
schema: {
hide: false,
tags: [ApiDocsTags.SecretRotations],
description: `Create ${
startsWithVowel(rotationType) ? "an" : "a"
} ${rotationType} Rotation for the specified project.`,
body: createSchema,
response: {
200: z.object({ secretRotation: responseSchema })
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const secretRotation = (await server.services.secretRotationV2.createSecretRotation(
{ ...req.body, type },
req.permission
)) as T;
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
projectId: secretRotation.projectId,
event: {
type: EventType.CREATE_SECRET_ROTATION,
metadata: {
rotationId: secretRotation.id,
type,
...req.body
}
}
});
return { secretRotation };
}
});
server.route({
method: "PATCH",
url: "/:rotationId",
config: {
rateLimit: writeLimit
},
schema: {
hide: false,
tags: [ApiDocsTags.SecretRotations],
description: `Update the specified ${rotationType} Rotation.`,
params: z.object({
rotationId: z.string().uuid().describe(SecretRotations.UPDATE(type).rotationId)
}),
body: updateSchema,
response: {
200: z.object({ secretRotation: responseSchema })
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const { rotationId } = req.params;
const secretRotation = (await server.services.secretRotationV2.updateSecretRotation(
{ ...req.body, rotationId, type },
req.permission
)) as T;
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
projectId: secretRotation.projectId,
event: {
type: EventType.UPDATE_SECRET_ROTATION,
metadata: {
rotationId,
type,
...req.body
}
}
});
return { secretRotation };
}
});
server.route({
method: "DELETE",
url: `/:rotationId`,
config: {
rateLimit: writeLimit
},
schema: {
hide: false,
tags: [ApiDocsTags.SecretRotations],
description: `Delete the specified ${rotationType} Rotation.`,
params: z.object({
rotationId: z.string().uuid().describe(SecretRotations.DELETE(type).rotationId)
}),
querystring: z.object({
deleteSecrets: z
.enum(["true", "false"])
.transform((value) => value === "true")
.describe(SecretRotations.DELETE(type).deleteSecrets),
revokeGeneratedCredentials: z
.enum(["true", "false"])
.transform((value) => value === "true")
.describe(SecretRotations.DELETE(type).revokeGeneratedCredentials)
}),
response: {
200: z.object({ secretRotation: responseSchema })
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const { rotationId } = req.params;
const { deleteSecrets, revokeGeneratedCredentials } = req.query;
const secretRotation = (await server.services.secretRotationV2.deleteSecretRotation(
{ type, rotationId, deleteSecrets, revokeGeneratedCredentials },
req.permission
)) as T;
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
projectId: secretRotation.projectId,
event: {
type: EventType.DELETE_SECRET_ROTATION,
metadata: {
type,
rotationId,
deleteSecrets,
revokeGeneratedCredentials
}
}
});
return { secretRotation };
}
});
server.route({
method: "GET",
url: "/:rotationId/generated-credentials",
config: {
rateLimit: readLimit
},
schema: {
hide: false,
tags: [ApiDocsTags.SecretRotations],
description: `Get the generated credentials for the specified ${rotationType} Rotation.`,
params: z.object({
rotationId: z.string().uuid().describe(SecretRotations.GET_GENERATED_CREDENTIALS_BY_ID(type).rotationId)
}),
response: {
200: z.object({
generatedCredentials: generatedCredentialsSchema,
activeIndex: z.number(),
rotationId: z.string().uuid(),
type: z.literal(type)
})
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const { rotationId } = req.params;
const {
generatedCredentials,
secretRotation: { activeIndex, projectId, folder, environment }
} = await server.services.secretRotationV2.findSecretRotationGeneratedCredentialsById(
{
rotationId,
type
},
req.permission
);
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
projectId,
event: {
type: EventType.GET_SECRET_ROTATION_GENERATED_CREDENTIALS,
metadata: {
type,
rotationId,
secretPath: folder.path,
environment: environment.slug
}
}
});
return { generatedCredentials: generatedCredentials as C, activeIndex, rotationId, type };
}
});
server.route({
method: "POST",
url: "/:rotationId/rotate-secrets",
config: {
rateLimit: writeLimit
},
schema: {
hide: false,
tags: [ApiDocsTags.SecretRotations],
description: `Rotate the generated credentials for the specified ${rotationType} Rotation.`,
params: z.object({
rotationId: z.string().uuid().describe(SecretRotations.ROTATE(type).rotationId)
}),
response: {
200: z.object({ secretRotation: responseSchema })
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const { rotationId } = req.params;
const secretRotation = (await server.services.secretRotationV2.rotateSecretRotation(
{
rotationId,
type,
auditLogInfo: req.auditLogInfo
},
req.permission
)) as T;
return { secretRotation };
}
});
};

View File

@ -0,0 +1,91 @@
import { z } from "zod";
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
import { Auth0ClientSecretRotationListItemSchema } from "@app/ee/services/secret-rotation-v2/auth0-client-secret";
import { AwsIamUserSecretRotationListItemSchema } from "@app/ee/services/secret-rotation-v2/aws-iam-user-secret";
import { LdapPasswordRotationListItemSchema } from "@app/ee/services/secret-rotation-v2/ldap-password";
import { MsSqlCredentialsRotationListItemSchema } from "@app/ee/services/secret-rotation-v2/mssql-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 { ApiDocsTags, SecretRotations } from "@app/lib/api-docs";
import { readLimit } from "@app/server/config/rateLimiter";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type";
const SecretRotationV2OptionsSchema = z.discriminatedUnion("type", [
PostgresCredentialsRotationListItemSchema,
MsSqlCredentialsRotationListItemSchema,
Auth0ClientSecretRotationListItemSchema,
LdapPasswordRotationListItemSchema,
AwsIamUserSecretRotationListItemSchema
]);
export const registerSecretRotationV2Router = async (server: FastifyZodProvider) => {
server.route({
method: "GET",
url: "/options",
config: {
rateLimit: readLimit
},
schema: {
hide: false,
tags: [ApiDocsTags.SecretRotations],
description: "List the available Secret Rotation Options.",
response: {
200: z.object({
secretRotationOptions: SecretRotationV2OptionsSchema.array()
})
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: () => {
const secretRotationOptions = server.services.secretRotationV2.listSecretRotationOptions();
return { secretRotationOptions };
}
});
server.route({
method: "GET",
url: "/",
config: {
rateLimit: readLimit
},
schema: {
hide: false,
tags: [ApiDocsTags.SecretRotations],
description: "List all the Secret Rotations for the specified project.",
querystring: z.object({
projectId: z.string().trim().min(1, "Project ID required").describe(SecretRotations.LIST().projectId)
}),
response: {
200: z.object({ secretRotations: SecretRotationV2Schema.array() })
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const {
query: { projectId },
permission
} = req;
const secretRotations = await server.services.secretRotationV2.listSecretRotationsByProjectId(
{ projectId },
permission
);
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
projectId,
event: {
type: EventType.GET_SECRET_ROTATIONS,
metadata: {
rotationIds: secretRotations.map((sync) => sync.id),
count: secretRotations.length
}
}
});
return { secretRotations };
}
});
};

View File

@ -94,7 +94,8 @@ export const accessApprovalRequestServiceFactory = ({
actor,
actorOrgId,
actorAuthMethod,
projectSlug
projectSlug,
note
}: TCreateAccessApprovalRequestDTO) => {
const cfg = getConfig();
const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId);
@ -209,7 +210,8 @@ export const accessApprovalRequestServiceFactory = ({
requestedByUserId: actorId,
temporaryRange: temporaryRange || null,
permissions: JSON.stringify(requestedPermissions),
isTemporary
isTemporary,
note: note || null
},
tx
);
@ -232,7 +234,8 @@ export const accessApprovalRequestServiceFactory = ({
secretPath,
environment: envSlug,
permissions: accessTypes,
approvalUrl
approvalUrl,
note
}
}
});
@ -252,7 +255,8 @@ export const accessApprovalRequestServiceFactory = ({
secretPath,
environment: envSlug,
permissions: accessTypes,
approvalUrl
approvalUrl,
note
},
template: SmtpTemplates.AccessApprovalRequest
});

View File

@ -24,6 +24,7 @@ export type TCreateAccessApprovalRequestDTO = {
permissions: unknown;
isTemporary: boolean;
temporaryRange?: string;
note?: string;
} & Omit<TProjectPermission, "projectId">;
export type TListApprovalRequestsDTO = {

View File

@ -0,0 +1,101 @@
import { ForbiddenError } from "@casl/ability";
import jwt from "jsonwebtoken";
import { ActionProjectType } from "@app/db/schemas";
import { getConfig } from "@app/lib/config/env";
import { ForbiddenRequestError, NotFoundError } from "@app/lib/errors";
import { ActorType } from "@app/services/auth/auth-type";
import { TProjectDALFactory } from "@app/services/project/project-dal";
import { TPermissionServiceFactory } from "../permission/permission-service";
import {
ProjectPermissionIdentityActions,
ProjectPermissionMemberActions,
ProjectPermissionSub
} from "../permission/project-permission";
import { TAssumeProjectPrivilegeDTO } from "./assume-privilege-types";
type TAssumePrivilegeServiceFactoryDep = {
projectDAL: Pick<TProjectDALFactory, "findById">;
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission">;
};
export type TAssumePrivilegeServiceFactory = ReturnType<typeof assumePrivilegeServiceFactory>;
export const assumePrivilegeServiceFactory = ({ projectDAL, permissionService }: TAssumePrivilegeServiceFactoryDep) => {
const assumeProjectPrivileges = async ({
targetActorType,
targetActorId,
projectId,
actorPermissionDetails,
tokenVersionId
}: TAssumeProjectPrivilegeDTO) => {
const project = await projectDAL.findById(projectId);
if (!project) throw new NotFoundError({ message: `Project with ID '${projectId}' not found` });
const { permission } = await permissionService.getProjectPermission({
actor: actorPermissionDetails.type,
actorId: actorPermissionDetails.id,
projectId,
actorAuthMethod: actorPermissionDetails.authMethod,
actorOrgId: actorPermissionDetails.orgId,
actionProjectType: ActionProjectType.Any
});
if (targetActorType === ActorType.USER) {
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionMemberActions.AssumePrivileges,
ProjectPermissionSub.Member
);
} else {
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionIdentityActions.AssumePrivileges,
ProjectPermissionSub.Identity
);
}
// check entity is part of project
await permissionService.getProjectPermission({
actor: targetActorType,
actorId: targetActorId,
projectId,
actorAuthMethod: actorPermissionDetails.authMethod,
actorOrgId: actorPermissionDetails.orgId,
actionProjectType: ActionProjectType.Any
});
const appCfg = getConfig();
const assumePrivilegesToken = jwt.sign(
{
tokenVersionId,
actorType: targetActorType,
actorId: targetActorId,
projectId,
requesterId: actorPermissionDetails.id
},
appCfg.AUTH_SECRET,
{ expiresIn: "1hr" }
);
return { actorType: targetActorType, actorId: targetActorId, projectId, assumePrivilegesToken };
};
const verifyAssumePrivilegeToken = (token: string, tokenVersionId: string) => {
const appCfg = getConfig();
const decodedToken = jwt.verify(token, appCfg.AUTH_SECRET) as {
tokenVersionId: string;
projectId: string;
requesterId: string;
actorType: ActorType;
actorId: string;
};
if (decodedToken.tokenVersionId !== tokenVersionId) {
throw new ForbiddenRequestError({ message: "Invalid token version" });
}
return decodedToken;
};
return {
assumeProjectPrivileges,
verifyAssumePrivilegeToken
};
};

View File

@ -0,0 +1,10 @@
import { OrgServiceActor } from "@app/lib/types";
import { ActorType } from "@app/services/auth/auth-type";
export type TAssumeProjectPrivilegeDTO = {
targetActorType: ActorType.USER | ActorType.IDENTITY;
targetActorId: string;
projectId: string;
tokenVersionId: string;
actorPermissionDetails: OrgServiceActor;
};

View File

@ -2,9 +2,18 @@ import {
TCreateProjectTemplateDTO,
TUpdateProjectTemplateDTO
} from "@app/ee/services/project-template/project-template-types";
import { SecretRotation, SecretRotationStatus } from "@app/ee/services/secret-rotation-v2/secret-rotation-v2-enums";
import {
TCreateSecretRotationV2DTO,
TDeleteSecretRotationV2DTO,
TSecretRotationV2Raw,
TUpdateSecretRotationV2DTO
} from "@app/ee/services/secret-rotation-v2/secret-rotation-v2-types";
import { SshCaStatus, SshCertType } from "@app/ee/services/ssh/ssh-certificate-authority-types";
import { SshCertKeyAlgorithm } from "@app/ee/services/ssh-certificate/ssh-certificate-types";
import { SshCertTemplateStatus } from "@app/ee/services/ssh-certificate-template/ssh-certificate-template-types";
import { SymmetricEncryption } from "@app/lib/crypto/cipher";
import { SymmetricKeyAlgorithm } from "@app/lib/crypto/cipher";
import { AsymmetricKeyAlgorithm, SigningAlgorithm } from "@app/lib/crypto/sign/types";
import { TProjectPermission } from "@app/lib/types";
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
import { TCreateAppConnectionDTO, TUpdateAppConnectionDTO } from "@app/services/app-connection/app-connection-types";
@ -56,6 +65,8 @@ export type TCreateAuditLogDTO = {
projectId?: string;
} & BaseAuthData;
export type AuditLogInfo = Pick<TCreateAuditLogDTO, "userAgent" | "userAgentType" | "ipAddress" | "actor">;
interface BaseAuthData {
ipAddress?: string;
userAgent?: string;
@ -180,6 +191,12 @@ export enum EventType {
UPDATE_SSH_CERTIFICATE_TEMPLATE = "update-ssh-certificate-template",
DELETE_SSH_CERTIFICATE_TEMPLATE = "delete-ssh-certificate-template",
GET_SSH_CERTIFICATE_TEMPLATE = "get-ssh-certificate-template",
CREATE_SSH_HOST = "create-ssh-host",
UPDATE_SSH_HOST = "update-ssh-host",
DELETE_SSH_HOST = "delete-ssh-host",
GET_SSH_HOST = "get-ssh-host",
ISSUE_SSH_HOST_USER_CERT = "issue-ssh-host-user-cert",
ISSUE_SSH_HOST_HOST_CERT = "issue-ssh-host-host-cert",
CREATE_CA = "create-certificate-authority",
GET_CA = "get-certificate-authority",
UPDATE_CA = "update-certificate-authority",
@ -217,6 +234,7 @@ export enum EventType {
GET_PROJECT_KMS_BACKUP = "get-project-kms-backup",
LOAD_PROJECT_KMS_BACKUP = "load-project-kms-backup",
ORG_ADMIN_ACCESS_PROJECT = "org-admin-accessed-project",
ORG_ADMIN_BYPASS_SSO = "org-admin-bypassed-sso",
CREATE_CERTIFICATE_TEMPLATE = "create-certificate-template",
UPDATE_CERTIFICATE_TEMPLATE = "update-certificate-template",
DELETE_CERTIFICATE_TEMPLATE = "delete-certificate-template",
@ -231,6 +249,8 @@ export enum EventType {
DELETE_SLACK_INTEGRATION = "delete-slack-integration",
GET_PROJECT_SLACK_CONFIG = "get-project-slack-config",
UPDATE_PROJECT_SLACK_CONFIG = "update-project-slack-config",
GET_PROJECT_SSH_CONFIG = "get-project-ssh-config",
UPDATE_PROJECT_SSH_CONFIG = "update-project-ssh-config",
INTEGRATION_SYNCED = "integration-synced",
CREATE_CMEK = "create-cmek",
UPDATE_CMEK = "update-cmek",
@ -239,6 +259,11 @@ export enum EventType {
GET_CMEK = "get-cmek",
CMEK_ENCRYPT = "cmek-encrypt",
CMEK_DECRYPT = "cmek-decrypt",
CMEK_SIGN = "cmek-sign",
CMEK_VERIFY = "cmek-verify",
CMEK_LIST_SIGNING_ALGORITHMS = "cmek-list-signing-algorithms",
CMEK_GET_PUBLIC_KEY = "cmek-get-public-key",
UPDATE_EXTERNAL_GROUP_ORG_ROLE_MAPPINGS = "update-external-group-org-role-mapping",
GET_EXTERNAL_GROUP_ORG_ROLE_MAPPINGS = "get-external-group-org-role-mapping",
GET_PROJECT_TEMPLATES = "get-project-templates",
@ -287,7 +312,17 @@ export enum EventType {
KMIP_OPERATION_LOCATE = "kmip-operation-locate",
KMIP_OPERATION_REGISTER = "kmip-operation-register",
PROJECT_ACCESS_REQUEST = "project-access-request"
GET_SECRET_ROTATIONS = "get-secret-rotations",
GET_SECRET_ROTATION = "get-secret-rotation",
GET_SECRET_ROTATION_GENERATED_CREDENTIALS = "get-secret-rotation-generated-credentials",
CREATE_SECRET_ROTATION = "create-secret-rotation",
UPDATE_SECRET_ROTATION = "update-secret-rotation",
DELETE_SECRET_ROTATION = "delete-secret-rotation",
SECRET_ROTATION_ROTATE_SECRETS = "secret-rotation-rotate-secrets",
PROJECT_ACCESS_REQUEST = "project-access-request",
PROJECT_ASSUME_PRIVILEGE_SESSION_START = "project-assume-privileges-session-start",
PROJECT_ASSUME_PRIVILEGE_SESSION_END = "project-assume-privileges-session-end"
}
export const filterableSecretEvents: EventType[] = [
@ -1360,7 +1395,7 @@ interface IssueSshCreds {
type: EventType.ISSUE_SSH_CREDS;
metadata: {
certificateTemplateId: string;
keyAlgorithm: CertKeyAlgorithm;
keyAlgorithm: SshCertKeyAlgorithm;
certType: SshCertType;
principals: string[];
ttl: string;
@ -1456,6 +1491,82 @@ interface DeleteSshCertificateTemplate {
};
}
interface CreateSshHost {
type: EventType.CREATE_SSH_HOST;
metadata: {
sshHostId: string;
hostname: string;
alias: string | null;
userCertTtl: string;
hostCertTtl: string;
loginMappings: {
loginUser: string;
allowedPrincipals: {
usernames: string[];
};
}[];
userSshCaId: string;
hostSshCaId: string;
};
}
interface UpdateSshHost {
type: EventType.UPDATE_SSH_HOST;
metadata: {
sshHostId: string;
hostname?: string;
alias?: string | null;
userCertTtl?: string;
hostCertTtl?: string;
loginMappings?: {
loginUser: string;
allowedPrincipals: {
usernames: string[];
};
}[];
userSshCaId?: string;
hostSshCaId?: string;
};
}
interface DeleteSshHost {
type: EventType.DELETE_SSH_HOST;
metadata: {
sshHostId: string;
hostname: string;
};
}
interface GetSshHost {
type: EventType.GET_SSH_HOST;
metadata: {
sshHostId: string;
hostname: string;
};
}
interface IssueSshHostUserCert {
type: EventType.ISSUE_SSH_HOST_USER_CERT;
metadata: {
sshHostId: string;
hostname: string;
loginUser: string;
principals: string[];
ttl: string;
};
}
interface IssueSshHostHostCert {
type: EventType.ISSUE_SSH_HOST_HOST_CERT;
metadata: {
sshHostId: string;
hostname: string;
serialNumber: string;
principals: string[];
ttl: string;
};
}
interface CreateCa {
type: EventType.CREATE_CA;
metadata: {
@ -1803,6 +1914,11 @@ interface OrgAdminAccessProjectEvent {
}; // no metadata yet
}
interface OrgAdminBypassSSOEvent {
type: EventType.ORG_ADMIN_BYPASS_SSO;
metadata: Record<string, string>; // no metadata yet
}
interface CreateCertificateTemplateEstConfig {
type: EventType.CREATE_CERTIFICATE_TEMPLATE_EST_CONFIG;
metadata: {
@ -1882,6 +1998,25 @@ interface GetProjectSlackConfig {
id: string;
};
}
interface GetProjectSshConfig {
type: EventType.GET_PROJECT_SSH_CONFIG;
metadata: {
id: string;
projectId: string;
};
}
interface UpdateProjectSshConfig {
type: EventType.UPDATE_PROJECT_SSH_CONFIG;
metadata: {
id: string;
projectId: string;
defaultUserSshCaId?: string | null;
defaultHostSshCaId?: string | null;
};
}
interface IntegrationSyncedEvent {
type: EventType.INTEGRATION_SYNCED;
metadata: {
@ -1899,7 +2034,7 @@ interface CreateCmekEvent {
keyId: string;
name: string;
description?: string;
encryptionAlgorithm: SymmetricEncryption;
encryptionAlgorithm: SymmetricKeyAlgorithm | AsymmetricKeyAlgorithm;
};
}
@ -1947,6 +2082,39 @@ interface CmekDecryptEvent {
};
}
interface CmekSignEvent {
type: EventType.CMEK_SIGN;
metadata: {
keyId: string;
signingAlgorithm: SigningAlgorithm;
signature: string;
};
}
interface CmekVerifyEvent {
type: EventType.CMEK_VERIFY;
metadata: {
keyId: string;
signingAlgorithm: SigningAlgorithm;
signature: string;
signatureValid: boolean;
};
}
interface CmekListSigningAlgorithmsEvent {
type: EventType.CMEK_LIST_SIGNING_ALGORITHMS;
metadata: {
keyId: string;
};
}
interface CmekGetPublicKeyEvent {
type: EventType.CMEK_GET_PUBLIC_KEY;
metadata: {
keyId: string;
};
}
interface GetExternalGroupOrgRoleMappingsEvent {
type: EventType.GET_EXTERNAL_GROUP_ORG_ROLE_MAPPINGS;
metadata?: Record<string, never>; // not needed, based off orgId
@ -2288,6 +2456,29 @@ interface ProjectAccessRequestEvent {
};
}
interface ProjectAssumePrivilegesEvent {
type: EventType.PROJECT_ASSUME_PRIVILEGE_SESSION_START;
metadata: {
projectId: string;
requesterId: string;
requesterEmail: string;
targetActorType: ActorType;
targetActorId: string;
duration: string;
};
}
interface ProjectAssumePrivilegesExitEvent {
type: EventType.PROJECT_ASSUME_PRIVILEGE_SESSION_END;
metadata: {
projectId: string;
requesterId: string;
requesterEmail: string;
targetActorType: ActorType;
targetActorId: string;
};
}
interface SetupKmipEvent {
type: EventType.SETUP_KMIP;
metadata: {
@ -2313,6 +2504,63 @@ interface RegisterKmipServerEvent {
};
}
interface GetSecretRotationsEvent {
type: EventType.GET_SECRET_ROTATIONS;
metadata: {
type?: SecretRotation;
count: number;
rotationIds: string[];
secretPath?: string;
environment?: string;
};
}
interface GetSecretRotationEvent {
type: EventType.GET_SECRET_ROTATION;
metadata: {
type: SecretRotation;
rotationId: string;
secretPath: string;
environment: string;
};
}
interface GetSecretRotationCredentialsEvent {
type: EventType.GET_SECRET_ROTATION_GENERATED_CREDENTIALS;
metadata: {
type: SecretRotation;
rotationId: string;
secretPath: string;
environment: string;
};
}
interface CreateSecretRotationEvent {
type: EventType.CREATE_SECRET_ROTATION;
metadata: Omit<TCreateSecretRotationV2DTO, "projectId"> & { rotationId: string };
}
interface UpdateSecretRotationEvent {
type: EventType.UPDATE_SECRET_ROTATION;
metadata: TUpdateSecretRotationV2DTO;
}
interface DeleteSecretRotationEvent {
type: EventType.DELETE_SECRET_ROTATION;
metadata: TDeleteSecretRotationV2DTO;
}
interface RotateSecretRotationEvent {
type: EventType.SECRET_ROTATION_ROTATE_SECRETS;
metadata: Pick<TSecretRotationV2Raw, "parameters" | "secretsMapping" | "type" | "connectionId" | "folderId"> & {
status: SecretRotationStatus;
rotationId: string;
jobId?: string | undefined;
occurredAt: Date;
message?: string | null | undefined;
};
}
export type Event =
| GetSecretsEvent
| GetSecretEvent
@ -2419,6 +2667,12 @@ export type Event =
| UpdateSshCertificateTemplate
| GetSshCertificateTemplate
| DeleteSshCertificateTemplate
| CreateSshHost
| UpdateSshHost
| DeleteSshHost
| GetSshHost
| IssueSshHostUserCert
| IssueSshHostHostCert
| CreateCa
| GetCa
| UpdateCa
@ -2456,6 +2710,7 @@ export type Event =
| GetProjectKmsBackupEvent
| LoadProjectKmsBackupEvent
| OrgAdminAccessProjectEvent
| OrgAdminBypassSSOEvent
| CreateCertificateTemplate
| UpdateCertificateTemplate
| GetCertificateTemplate
@ -2470,6 +2725,8 @@ export type Event =
| GetSlackIntegration
| UpdateProjectSlackConfig
| GetProjectSlackConfig
| GetProjectSshConfig
| UpdateProjectSshConfig
| IntegrationSyncedEvent
| CreateCmekEvent
| UpdateCmekEvent
@ -2478,6 +2735,10 @@ export type Event =
| GetCmeksEvent
| CmekEncryptEvent
| CmekDecryptEvent
| CmekSignEvent
| CmekVerifyEvent
| CmekListSigningAlgorithmsEvent
| CmekGetPublicKeyEvent
| GetExternalGroupOrgRoleMappingsEvent
| UpdateExternalGroupOrgRoleMappingsEvent
| GetProjectTemplatesEvent
@ -2523,5 +2784,14 @@ export type Event =
| KmipOperationLocateEvent
| KmipOperationRegisterEvent
| ProjectAccessRequestEvent
| ProjectAssumePrivilegesEvent
| ProjectAssumePrivilegesExitEvent
| CreateSecretRequestEvent
| SecretApprovalRequestReview;
| SecretApprovalRequestReview
| GetSecretRotationsEvent
| GetSecretRotationEvent
| GetSecretRotationCredentialsEvent
| CreateSecretRotationEvent
| UpdateSecretRotationEvent
| DeleteSecretRotationEvent
| RotateSecretRotationEvent;

View File

@ -78,10 +78,6 @@ export const dynamicSecretLeaseServiceFactory = ({
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionDynamicSecretActions.Lease,
subject(ProjectPermissionSub.DynamicSecrets, { environment: environmentSlug, secretPath: path })
);
const plan = await licenseService.getPlan(actorOrgId);
if (!plan?.dynamicSecret) {
@ -102,6 +98,15 @@ export const dynamicSecretLeaseServiceFactory = ({
message: `Dynamic secret with name '${name}' in folder with path '${path}' not found`
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionDynamicSecretActions.Lease,
subject(ProjectPermissionSub.DynamicSecrets, {
environment: environmentSlug,
secretPath: path,
metadata: dynamicSecretCfg.metadata
})
);
const totalLeasesTaken = await dynamicSecretLeaseDAL.countLeasesForDynamicSecret(dynamicSecretCfg.id);
if (totalLeasesTaken >= appCfg.MAX_LEASE_LIMIT)
throw new BadRequestError({ message: `Max lease limit reached. Limit: ${appCfg.MAX_LEASE_LIMIT}` });
@ -125,7 +130,17 @@ export const dynamicSecretLeaseServiceFactory = ({
if (expireAt > maxExpiryDate) throw new BadRequestError({ message: "TTL cannot be larger than max TTL" });
}
const { entityId, data } = await selectedProvider.create(decryptedStoredInput, expireAt.getTime());
let result;
try {
result = await selectedProvider.create(decryptedStoredInput, expireAt.getTime());
} catch (error: unknown) {
if (error && typeof error === "object" && error !== null && "sqlMessage" in error) {
throw new BadRequestError({ message: error.sqlMessage as string });
}
throw error;
}
const { entityId, data } = result;
const dynamicSecretLease = await dynamicSecretLeaseDAL.create({
expireAt,
version: 1,
@ -159,10 +174,6 @@ export const dynamicSecretLeaseServiceFactory = ({
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionDynamicSecretActions.Lease,
subject(ProjectPermissionSub.DynamicSecrets, { environment: environmentSlug, secretPath: path })
);
const { decryptor: secretManagerDecryptor } = await kmsService.createCipherPairWithDataKey({
type: KmsDataKey.SecretManager,
@ -187,7 +198,25 @@ export const dynamicSecretLeaseServiceFactory = ({
throw new NotFoundError({ message: `Dynamic secret lease with ID '${leaseId}' not found` });
}
const dynamicSecretCfg = dynamicSecretLease.dynamicSecret;
const dynamicSecretCfg = await dynamicSecretDAL.findOne({
id: dynamicSecretLease.dynamicSecretId,
folderId: folder.id
});
if (!dynamicSecretCfg)
throw new NotFoundError({
message: `Dynamic secret with ID '${dynamicSecretLease.dynamicSecretId}' not found`
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionDynamicSecretActions.Lease,
subject(ProjectPermissionSub.DynamicSecrets, {
environment: environmentSlug,
secretPath: path,
metadata: dynamicSecretCfg.metadata
})
);
const selectedProvider = dynamicSecretProviders[dynamicSecretCfg.type as DynamicSecretProviders];
const decryptedStoredInput = JSON.parse(
secretManagerDecryptor({ cipherTextBlob: Buffer.from(dynamicSecretCfg.encryptedInput) }).toString()
@ -239,10 +268,6 @@ export const dynamicSecretLeaseServiceFactory = ({
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionDynamicSecretActions.Lease,
subject(ProjectPermissionSub.DynamicSecrets, { environment: environmentSlug, secretPath: path })
);
const { decryptor: secretManagerDecryptor } = await kmsService.createCipherPairWithDataKey({
type: KmsDataKey.SecretManager,
@ -259,7 +284,25 @@ export const dynamicSecretLeaseServiceFactory = ({
if (!dynamicSecretLease || dynamicSecretLease.dynamicSecret.folderId !== folder.id)
throw new NotFoundError({ message: `Dynamic secret lease with ID '${leaseId}' not found` });
const dynamicSecretCfg = dynamicSecretLease.dynamicSecret;
const dynamicSecretCfg = await dynamicSecretDAL.findOne({
id: dynamicSecretLease.dynamicSecretId,
folderId: folder.id
});
if (!dynamicSecretCfg)
throw new NotFoundError({
message: `Dynamic secret with ID '${dynamicSecretLease.dynamicSecretId}' not found`
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionDynamicSecretActions.Lease,
subject(ProjectPermissionSub.DynamicSecrets, {
environment: environmentSlug,
secretPath: path,
metadata: dynamicSecretCfg.metadata
})
);
const selectedProvider = dynamicSecretProviders[dynamicSecretCfg.type as DynamicSecretProviders];
const decryptedStoredInput = JSON.parse(
secretManagerDecryptor({ cipherTextBlob: Buffer.from(dynamicSecretCfg.encryptedInput) }).toString()
@ -309,10 +352,6 @@ export const dynamicSecretLeaseServiceFactory = ({
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionDynamicSecretActions.Lease,
subject(ProjectPermissionSub.DynamicSecrets, { environment: environmentSlug, secretPath: path })
);
const folder = await folderDAL.findBySecretPath(projectId, environmentSlug, path);
if (!folder)
@ -326,6 +365,15 @@ export const dynamicSecretLeaseServiceFactory = ({
message: `Dynamic secret with name '${name}' in folder with path '${path}' not found`
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionDynamicSecretActions.Lease,
subject(ProjectPermissionSub.DynamicSecrets, {
environment: environmentSlug,
secretPath: path,
metadata: dynamicSecretCfg.metadata
})
);
const dynamicSecretLeases = await dynamicSecretLeaseDAL.find({ dynamicSecretId: dynamicSecretCfg.id });
return dynamicSecretLeases;
};
@ -352,10 +400,6 @@ export const dynamicSecretLeaseServiceFactory = ({
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionDynamicSecretActions.Lease,
subject(ProjectPermissionSub.DynamicSecrets, { environment: environmentSlug, secretPath: path })
);
const folder = await folderDAL.findBySecretPath(projectId, environmentSlug, path);
if (!folder) throw new NotFoundError({ message: `Folder with path '${path}' not found` });
@ -364,6 +408,25 @@ export const dynamicSecretLeaseServiceFactory = ({
if (!dynamicSecretLease)
throw new NotFoundError({ message: `Dynamic secret lease with ID '${leaseId}' not found` });
const dynamicSecretCfg = await dynamicSecretDAL.findOne({
id: dynamicSecretLease.dynamicSecretId,
folderId: folder.id
});
if (!dynamicSecretCfg)
throw new NotFoundError({
message: `Dynamic secret with ID '${dynamicSecretLease.dynamicSecretId}' not found`
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionDynamicSecretActions.Lease,
subject(ProjectPermissionSub.DynamicSecrets, {
environment: environmentSlug,
secretPath: path,
metadata: dynamicSecretCfg.metadata
})
);
return dynamicSecretLease;
};

View File

@ -1,9 +1,17 @@
import { Knex } from "knex";
import { TDbClient } from "@app/db";
import { TableName } from "@app/db/schemas";
import { TableName, TDynamicSecrets } from "@app/db/schemas";
import { DatabaseError } from "@app/lib/errors";
import { ormify, selectAllTableCols } from "@app/lib/knex";
import {
buildFindFilter,
ormify,
prependTableNameToFindFilter,
selectAllTableCols,
sqlNestRelationships,
TFindFilter,
TFindOpt
} from "@app/lib/knex";
import { OrderByDirection } from "@app/lib/types";
import { SecretsOrderBy } from "@app/services/secret/secret-types";
@ -12,6 +20,86 @@ export type TDynamicSecretDALFactory = ReturnType<typeof dynamicSecretDALFactory
export const dynamicSecretDALFactory = (db: TDbClient) => {
const orm = ormify(db, TableName.DynamicSecret);
const findOne = async (filter: TFindFilter<TDynamicSecrets>, tx?: Knex) => {
const query = (tx || db.replicaNode())(TableName.DynamicSecret)
.leftJoin(
TableName.ResourceMetadata,
`${TableName.ResourceMetadata}.dynamicSecretId`,
`${TableName.DynamicSecret}.id`
)
.select(selectAllTableCols(TableName.DynamicSecret))
.select(
db.ref("id").withSchema(TableName.ResourceMetadata).as("metadataId"),
db.ref("key").withSchema(TableName.ResourceMetadata).as("metadataKey"),
db.ref("value").withSchema(TableName.ResourceMetadata).as("metadataValue")
)
.where(prependTableNameToFindFilter(TableName.DynamicSecret, filter));
const docs = sqlNestRelationships({
data: await query,
key: "id",
parentMapper: (el) => el,
childrenMapper: [
{
key: "metadataId",
label: "metadata" as const,
mapper: ({ metadataKey, metadataValue, metadataId }) => ({
id: metadataId,
key: metadataKey,
value: metadataValue
})
}
]
});
return docs[0];
};
const findWithMetadata = async (
filter: TFindFilter<TDynamicSecrets>,
{ offset, limit, sort, tx }: TFindOpt<TDynamicSecrets> = {}
) => {
const query = (tx || db.replicaNode())(TableName.DynamicSecret)
.leftJoin(
TableName.ResourceMetadata,
`${TableName.ResourceMetadata}.dynamicSecretId`,
`${TableName.DynamicSecret}.id`
)
.select(selectAllTableCols(TableName.DynamicSecret))
.select(
db.ref("id").withSchema(TableName.ResourceMetadata).as("metadataId"),
db.ref("key").withSchema(TableName.ResourceMetadata).as("metadataKey"),
db.ref("value").withSchema(TableName.ResourceMetadata).as("metadataValue")
)
// eslint-disable-next-line @typescript-eslint/no-misused-promises
.where(buildFindFilter(filter));
if (limit) void query.limit(limit);
if (offset) void query.offset(offset);
if (sort) {
void query.orderBy(sort.map(([column, order, nulls]) => ({ column: column as string, order, nulls })));
}
const docs = sqlNestRelationships({
data: await query,
key: "id",
parentMapper: (el) => el,
childrenMapper: [
{
key: "metadataId",
label: "metadata" as const,
mapper: ({ metadataKey, metadataValue, metadataId }) => ({
id: metadataId,
key: metadataKey,
value: metadataValue
})
}
]
});
return docs;
};
// find dynamic secrets for multiple environments (folder IDs are cross env, thus need to rank for pagination)
const listDynamicSecretsByFolderIds = async (
{
@ -39,18 +127,27 @@ export const dynamicSecretDALFactory = (db: TDbClient) => {
void bd.whereILike(`${TableName.DynamicSecret}.name`, `%${search}%`);
}
})
.leftJoin(
TableName.ResourceMetadata,
`${TableName.ResourceMetadata}.dynamicSecretId`,
`${TableName.DynamicSecret}.id`
)
.leftJoin(TableName.SecretFolder, `${TableName.SecretFolder}.id`, `${TableName.DynamicSecret}.folderId`)
.leftJoin(TableName.Environment, `${TableName.SecretFolder}.envId`, `${TableName.Environment}.id`)
.select(
selectAllTableCols(TableName.DynamicSecret),
db.ref("slug").withSchema(TableName.Environment).as("environment"),
db.raw(`DENSE_RANK() OVER (ORDER BY ${TableName.DynamicSecret}."name" ${orderDirection}) as rank`)
db.raw(`DENSE_RANK() OVER (ORDER BY ${TableName.DynamicSecret}."name" ${orderDirection}) as rank`),
db.ref("id").withSchema(TableName.ResourceMetadata).as("metadataId"),
db.ref("key").withSchema(TableName.ResourceMetadata).as("metadataKey"),
db.ref("value").withSchema(TableName.ResourceMetadata).as("metadataValue")
)
.orderBy(`${TableName.DynamicSecret}.${orderBy}`, orderDirection);
let queryWithLimit;
if (limit) {
const rankOffset = offset + 1;
return await (tx || db)
queryWithLimit = (tx || db.replicaNode())
.with("w", query)
.select("*")
.from<Awaited<typeof query>[number]>("w")
@ -58,7 +155,22 @@ export const dynamicSecretDALFactory = (db: TDbClient) => {
.andWhere("w.rank", "<", rankOffset + limit);
}
const dynamicSecrets = await query;
const dynamicSecrets = sqlNestRelationships({
data: await (queryWithLimit || query),
key: "id",
parentMapper: (el) => el,
childrenMapper: [
{
key: "metadataId",
label: "metadata" as const,
mapper: ({ metadataKey, metadataValue, metadataId }) => ({
id: metadataId,
key: metadataKey,
value: metadataValue
})
}
]
});
return dynamicSecrets;
} catch (error) {
@ -66,5 +178,5 @@ export const dynamicSecretDALFactory = (db: TDbClient) => {
}
};
return { ...orm, listDynamicSecretsByFolderIds };
return { ...orm, listDynamicSecretsByFolderIds, findOne, findWithMetadata };
};

View File

@ -8,11 +8,13 @@ import { getDbConnectionHost } from "@app/lib/knex";
export const verifyHostInputValidity = async (host: string, isGateway = false) => {
const appCfg = getConfig();
// if (appCfg.NODE_ENV === "development") return ["host.docker.internal"]; // incase you want to remove this check in dev
if (appCfg.isDevelopmentMode) return [host];
const reservedHosts = [appCfg.DB_HOST || getDbConnectionHost(appCfg.DB_CONNECTION_URI)].concat(
(appCfg.DB_READ_REPLICAS || []).map((el) => getDbConnectionHost(el.DB_CONNECTION_URI)),
getDbConnectionHost(appCfg.REDIS_URL)
getDbConnectionHost(appCfg.REDIS_URL),
getDbConnectionHost(appCfg.AUDIT_LOGS_DB_CONNECTION_URI)
);
// get host db ip
@ -40,7 +42,7 @@ export const verifyHostInputValidity = async (host: string, isGateway = false) =
inputHostIps.push(...resolvedIps);
}
if (!isGateway) {
if (!isGateway && !(appCfg.DYNAMIC_SECRET_ALLOW_INTERNAL_IP || appCfg.ALLOW_INTERNAL_IP_CONNECTIONS)) {
const isInternalIp = inputHostIps.some((el) => isPrivateIp(el));
if (isInternalIp) throw new BadRequestError({ message: "Invalid db host" });
}

View File

@ -12,6 +12,7 @@ import { OrderByDirection, OrgServiceActor } from "@app/lib/types";
import { TKmsServiceFactory } from "@app/services/kms/kms-service";
import { KmsDataKey } from "@app/services/kms/kms-types";
import { TProjectDALFactory } from "@app/services/project/project-dal";
import { TResourceMetadataDALFactory } from "@app/services/resource-metadata/resource-metadata-dal";
import { TSecretFolderDALFactory } from "@app/services/secret-folder/secret-folder-dal";
import { TDynamicSecretLeaseDALFactory } from "../dynamic-secret-lease/dynamic-secret-lease-dal";
@ -46,6 +47,7 @@ type TDynamicSecretServiceFactoryDep = {
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission">;
kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey">;
projectGatewayDAL: Pick<TProjectGatewayDALFactory, "findOne">;
resourceMetadataDAL: Pick<TResourceMetadataDALFactory, "insertMany" | "delete">;
};
export type TDynamicSecretServiceFactory = ReturnType<typeof dynamicSecretServiceFactory>;
@ -60,7 +62,8 @@ export const dynamicSecretServiceFactory = ({
dynamicSecretQueueService,
projectDAL,
kmsService,
projectGatewayDAL
projectGatewayDAL,
resourceMetadataDAL
}: TDynamicSecretServiceFactoryDep) => {
const create = async ({
path,
@ -73,7 +76,8 @@ export const dynamicSecretServiceFactory = ({
projectSlug,
actorOrgId,
defaultTTL,
actorAuthMethod
actorAuthMethod,
metadata
}: TCreateDynamicSecretDTO) => {
const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId);
if (!project) throw new NotFoundError({ message: `Project with slug '${projectSlug}' not found` });
@ -87,9 +91,10 @@ export const dynamicSecretServiceFactory = ({
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionDynamicSecretActions.CreateRootCredential,
subject(ProjectPermissionSub.DynamicSecrets, { environment: environmentSlug, secretPath: path })
subject(ProjectPermissionSub.DynamicSecrets, { environment: environmentSlug, secretPath: path, metadata })
);
const plan = await licenseService.getPlan(actorOrgId);
@ -131,16 +136,36 @@ export const dynamicSecretServiceFactory = ({
projectId
});
const dynamicSecretCfg = await dynamicSecretDAL.create({
type: provider.type,
version: 1,
encryptedInput: secretManagerEncryptor({ plainText: Buffer.from(JSON.stringify(inputs)) }).cipherTextBlob,
maxTTL,
defaultTTL,
folderId: folder.id,
name,
projectGatewayId: selectedGatewayId
const dynamicSecretCfg = await dynamicSecretDAL.transaction(async (tx) => {
const cfg = await dynamicSecretDAL.create(
{
type: provider.type,
version: 1,
encryptedInput: secretManagerEncryptor({ plainText: Buffer.from(JSON.stringify(inputs)) }).cipherTextBlob,
maxTTL,
defaultTTL,
folderId: folder.id,
name,
projectGatewayId: selectedGatewayId
},
tx
);
if (metadata) {
await resourceMetadataDAL.insertMany(
metadata.map(({ key, value }) => ({
key,
value,
dynamicSecretId: cfg.id,
orgId: actorOrgId
})),
tx
);
}
return cfg;
});
return dynamicSecretCfg;
};
@ -156,7 +181,8 @@ export const dynamicSecretServiceFactory = ({
actorId,
newName,
actorOrgId,
actorAuthMethod
actorAuthMethod,
metadata
}: TUpdateDynamicSecretDTO) => {
const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId);
if (!project) throw new NotFoundError({ message: `Project with slug '${projectSlug}' not found` });
@ -171,10 +197,6 @@ export const dynamicSecretServiceFactory = ({
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionDynamicSecretActions.EditRootCredential,
subject(ProjectPermissionSub.DynamicSecrets, { environment: environmentSlug, secretPath: path })
);
const plan = await licenseService.getPlan(actorOrgId);
if (!plan?.dynamicSecret) {
@ -193,6 +215,27 @@ export const dynamicSecretServiceFactory = ({
message: `Dynamic secret with name '${name}' in folder '${folder.path}' not found`
});
}
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionDynamicSecretActions.EditRootCredential,
subject(ProjectPermissionSub.DynamicSecrets, {
environment: environmentSlug,
secretPath: path,
metadata: dynamicSecretCfg.metadata
})
);
if (metadata) {
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionDynamicSecretActions.EditRootCredential,
subject(ProjectPermissionSub.DynamicSecrets, {
environment: environmentSlug,
secretPath: path,
metadata
})
);
}
if (newName) {
const existingDynamicSecret = await dynamicSecretDAL.findOne({ name: newName, folderId: folder.id });
if (existingDynamicSecret)
@ -231,14 +274,41 @@ export const dynamicSecretServiceFactory = ({
const isConnected = await selectedProvider.validateConnection(newInput);
if (!isConnected) throw new BadRequestError({ message: "Provider connection failed" });
const updatedDynamicCfg = await dynamicSecretDAL.updateById(dynamicSecretCfg.id, {
encryptedInput: secretManagerEncryptor({ plainText: Buffer.from(JSON.stringify(updatedInput)) }).cipherTextBlob,
maxTTL,
defaultTTL,
name: newName ?? name,
status: null,
statusDetails: null,
projectGatewayId: selectedGatewayId
const updatedDynamicCfg = await dynamicSecretDAL.transaction(async (tx) => {
const cfg = await dynamicSecretDAL.updateById(
dynamicSecretCfg.id,
{
encryptedInput: secretManagerEncryptor({ plainText: Buffer.from(JSON.stringify(updatedInput)) })
.cipherTextBlob,
maxTTL,
defaultTTL,
name: newName ?? name,
status: null,
projectGatewayId: selectedGatewayId
},
tx
);
if (metadata) {
await resourceMetadataDAL.delete(
{
dynamicSecretId: cfg.id
},
tx
);
await resourceMetadataDAL.insertMany(
metadata.map(({ key, value }) => ({
key,
value,
dynamicSecretId: cfg.id,
orgId: actorOrgId
})),
tx
);
}
return cfg;
});
return updatedDynamicCfg;
@ -268,10 +338,6 @@ export const dynamicSecretServiceFactory = ({
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionDynamicSecretActions.DeleteRootCredential,
subject(ProjectPermissionSub.DynamicSecrets, { environment: environmentSlug, secretPath: path })
);
const folder = await folderDAL.findBySecretPath(projectId, environmentSlug, path);
if (!folder)
@ -282,6 +348,15 @@ export const dynamicSecretServiceFactory = ({
throw new NotFoundError({ message: `Dynamic secret with name '${name}' in folder '${folder.path}' not found` });
}
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionDynamicSecretActions.DeleteRootCredential,
subject(ProjectPermissionSub.DynamicSecrets, {
environment: environmentSlug,
secretPath: path,
metadata: dynamicSecretCfg.metadata
})
);
const leases = await dynamicSecretLeaseDAL.find({ dynamicSecretId: dynamicSecretCfg.id });
// when not forced we check with the external system to first remove the things
// we introduce a forced concept because consider the external lease got deleted by some other external like a human or another system
@ -329,14 +404,6 @@ export const dynamicSecretServiceFactory = ({
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionDynamicSecretActions.ReadRootCredential,
subject(ProjectPermissionSub.DynamicSecrets, { environment: environmentSlug, secretPath: path })
);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionDynamicSecretActions.EditRootCredential,
subject(ProjectPermissionSub.DynamicSecrets, { environment: environmentSlug, secretPath: path })
);
const folder = await folderDAL.findBySecretPath(projectId, environmentSlug, path);
if (!folder)
@ -346,6 +413,25 @@ export const dynamicSecretServiceFactory = ({
if (!dynamicSecretCfg) {
throw new NotFoundError({ message: `Dynamic secret with name '${name} in folder '${path}' not found` });
}
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionDynamicSecretActions.ReadRootCredential,
subject(ProjectPermissionSub.DynamicSecrets, {
environment: environmentSlug,
secretPath: path,
metadata: dynamicSecretCfg.metadata
})
);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionDynamicSecretActions.EditRootCredential,
subject(ProjectPermissionSub.DynamicSecrets, {
environment: environmentSlug,
secretPath: path,
metadata: dynamicSecretCfg.metadata
})
);
const { decryptor: secretManagerDecryptor } = await kmsService.createCipherPairWithDataKey({
type: KmsDataKey.SecretManager,
projectId
@ -356,6 +442,7 @@ export const dynamicSecretServiceFactory = ({
) as object;
const selectedProvider = dynamicSecretProviders[dynamicSecretCfg.type as DynamicSecretProviders];
const providerInputs = (await selectedProvider.validateProviderInputs(decryptedStoredInput)) as object;
return { ...dynamicSecretCfg, inputs: providerInputs };
};
@ -426,7 +513,7 @@ export const dynamicSecretServiceFactory = ({
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionDynamicSecretActions.ReadRootCredential,
subject(ProjectPermissionSub.DynamicSecrets, { environment: environmentSlug, secretPath: path })
ProjectPermissionSub.DynamicSecrets
);
const folder = await folderDAL.findBySecretPath(projectId, environmentSlug, path);
@ -473,16 +560,12 @@ export const dynamicSecretServiceFactory = ({
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionDynamicSecretActions.ReadRootCredential,
subject(ProjectPermissionSub.DynamicSecrets, { environment: environmentSlug, secretPath: path })
);
const folder = await folderDAL.findBySecretPath(projectId, environmentSlug, path);
if (!folder)
throw new NotFoundError({ message: `Folder with path '${path}' in environment '${environmentSlug}' not found` });
const dynamicSecretCfg = await dynamicSecretDAL.find(
const dynamicSecretCfg = await dynamicSecretDAL.findWithMetadata(
{ folderId: folder.id, $search: search ? { name: `%${search}%` } : undefined },
{
limit,
@ -490,7 +573,17 @@ export const dynamicSecretServiceFactory = ({
sort: orderBy ? [[orderBy, orderDirection]] : undefined
}
);
return dynamicSecretCfg;
return dynamicSecretCfg.filter((dynamicSecret) => {
return permission.can(
ProjectPermissionDynamicSecretActions.ReadRootCredential,
subject(ProjectPermissionSub.DynamicSecrets, {
environment: environmentSlug,
secretPath: path,
metadata: dynamicSecret.metadata
})
);
});
};
const listDynamicSecretsByFolderIds = async (
@ -542,24 +635,14 @@ export const dynamicSecretServiceFactory = ({
isInternal,
...params
}: TListDynamicSecretsMultiEnvDTO) => {
if (!isInternal) {
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
// verify user has access to each env in request
environmentSlugs.forEach((environmentSlug) =>
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionDynamicSecretActions.ReadRootCredential,
subject(ProjectPermissionSub.DynamicSecrets, { environment: environmentSlug, secretPath: path })
)
);
}
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
const folders = await folderDAL.findBySecretPathMultiEnv(projectId, environmentSlugs, path);
if (!folders.length)
@ -572,7 +655,16 @@ export const dynamicSecretServiceFactory = ({
...params
});
return dynamicSecretCfg;
return dynamicSecretCfg.filter((dynamicSecret) => {
return permission.can(
ProjectPermissionDynamicSecretActions.ReadRootCredential,
subject(ProjectPermissionSub.DynamicSecrets, {
environment: dynamicSecret.environment,
secretPath: path,
metadata: dynamicSecret.metadata
})
);
});
};
const fetchAzureEntraIdUsers = async ({

View File

@ -1,6 +1,7 @@
import { z } from "zod";
import { OrderByDirection, TProjectPermission } from "@app/lib/types";
import { ResourceMetadataDTO } from "@app/services/resource-metadata/resource-metadata-schema";
import { SecretsOrderBy } from "@app/services/secret/secret-types";
import { DynamicSecretProviderSchema } from "./providers/models";
@ -20,6 +21,7 @@ export type TCreateDynamicSecretDTO = {
environmentSlug: string;
name: string;
projectSlug: string;
metadata?: ResourceMetadataDTO;
} & Omit<TProjectPermission, "projectId">;
export type TUpdateDynamicSecretDTO = {
@ -31,6 +33,7 @@ export type TUpdateDynamicSecretDTO = {
environmentSlug: string;
inputs?: TProvider["inputs"];
projectSlug: string;
metadata?: ResourceMetadataDTO;
} & Omit<TProjectPermission, "projectId">;
export type TDeleteDynamicSecretDTO = {

View File

@ -2,6 +2,7 @@ import handlebars from "handlebars";
import ldapjs from "ldapjs";
import ldif from "ldif";
import { customAlphabet } from "nanoid";
import RE2 from "re2";
import { z } from "zod";
import { BadRequestError } from "@app/lib/errors";
@ -194,7 +195,8 @@ export const LdapProvider = (): TDynamicProviderFns => {
const client = await $getClient(providerInputs);
if (providerInputs.credentialType === LdapCredentialType.Static) {
const dnMatch = providerInputs.rotationLdif.match(/^dn:\s*(.+)/m);
const dnRegex = new RE2("^dn:\\s*(.+)", "m");
const dnMatch = dnRegex.exec(providerInputs.rotationLdif);
if (dnMatch) {
const username = dnMatch[1];
@ -238,7 +240,8 @@ export const LdapProvider = (): TDynamicProviderFns => {
const client = await $getClient(providerInputs);
if (providerInputs.credentialType === LdapCredentialType.Static) {
const dnMatch = providerInputs.rotationLdif.match(/^dn:\s*(.+)/m);
const dnRegex = new RE2("^dn:\\s*(.+)", "m");
const dnMatch = dnRegex.exec(providerInputs.rotationLdif);
if (dnMatch) {
const username = dnMatch[1];

View File

@ -7,7 +7,7 @@ import { BadRequestError, InternalServerError, NotFoundError } from "@app/lib/er
import { alphaNumericNanoId } from "@app/lib/nanoid";
import { TKmsKeyDALFactory } from "@app/services/kms/kms-key-dal";
import { TKmsServiceFactory } from "@app/services/kms/kms-service";
import { KmsDataKey } from "@app/services/kms/kms-types";
import { KmsDataKey, KmsKeyUsage } from "@app/services/kms/kms-types";
import { TLicenseServiceFactory } from "../license/license-service";
import { OrgPermissionActions, OrgPermissionSubjects } from "../permission/org-permission";
@ -83,18 +83,26 @@ export const externalKmsServiceFactory = ({
throw error;
});
// if missing kms key this generate a new kms key id and returns new provider input
const newProviderInput = await externalKms.generateInputKmsKey();
sanitizedProviderInput = JSON.stringify(newProviderInput);
try {
// if missing kms key this generate a new kms key id and returns new provider input
const newProviderInput = await externalKms.generateInputKmsKey();
sanitizedProviderInput = JSON.stringify(newProviderInput);
await externalKms.validateConnection();
await externalKms.validateConnection();
} finally {
await externalKms.cleanup();
}
}
break;
case KmsProviders.Gcp:
{
const externalKms = await GcpKmsProviderFactory({ inputs: provider.inputs });
await externalKms.validateConnection();
sanitizedProviderInput = JSON.stringify(provider.inputs);
try {
await externalKms.validateConnection();
sanitizedProviderInput = JSON.stringify(provider.inputs);
} finally {
await externalKms.cleanup();
}
}
break;
default:
@ -115,6 +123,7 @@ export const externalKmsServiceFactory = ({
{
isReserved: false,
description,
keyUsage: KmsKeyUsage.ENCRYPT_DECRYPT,
name: kmsName,
orgId: actorOrgId
},
@ -185,8 +194,12 @@ export const externalKmsServiceFactory = ({
);
const updatedProviderInput = { ...decryptedProviderInput, ...provider.inputs };
const externalKms = await AwsKmsProviderFactory({ inputs: updatedProviderInput });
await externalKms.validateConnection();
sanitizedProviderInput = JSON.stringify(updatedProviderInput);
try {
await externalKms.validateConnection();
sanitizedProviderInput = JSON.stringify(updatedProviderInput);
} finally {
await externalKms.cleanup();
}
}
break;
case KmsProviders.Gcp:
@ -196,8 +209,12 @@ export const externalKmsServiceFactory = ({
);
const updatedProviderInput = { ...decryptedProviderInput, ...provider.inputs };
const externalKms = await GcpKmsProviderFactory({ inputs: updatedProviderInput });
await externalKms.validateConnection();
sanitizedProviderInput = JSON.stringify(updatedProviderInput);
try {
await externalKms.validateConnection();
sanitizedProviderInput = JSON.stringify(updatedProviderInput);
} finally {
await externalKms.cleanup();
}
}
break;
default:
@ -367,7 +384,11 @@ export const externalKmsServiceFactory = ({
const fetchGcpKeys = async ({ credential, gcpRegion }: Pick<TExternalKmsGcpSchema, "credential" | "gcpRegion">) => {
const externalKms = await GcpKmsProviderFactory({ inputs: { credential, gcpRegion, keyName: "" } });
return externalKms.getKeysList();
try {
return await externalKms.getKeysList();
} finally {
await externalKms.cleanup();
}
};
return {

View File

@ -102,10 +102,19 @@ export const AwsKmsProviderFactory = async ({ inputs }: AwsKmsProviderArgs): Pro
return { data: Buffer.from(decryptionCommand.Plaintext) };
};
const cleanup = async () => {
try {
awsClient.destroy();
} catch (error) {
throw new Error("Failed to cleanup AWS KMS client", { cause: error });
}
};
return {
generateInputKmsKey,
validateConnection,
encrypt,
decrypt
decrypt,
cleanup
};
};

View File

@ -45,6 +45,14 @@ export const GcpKmsProviderFactory = async ({ inputs }: GcpKmsProviderArgs): Pro
}
};
const cleanup = async () => {
try {
await gcpKmsClient.close();
} catch (error) {
throw new Error("Failed to cleanup GCP KMS client", { cause: error });
}
};
// Used when adding the KMS to fetch the list of keys in specified region
const getKeysList = async () => {
try {
@ -92,7 +100,7 @@ export const GcpKmsProviderFactory = async ({ inputs }: GcpKmsProviderArgs): Pro
plaintext: data
});
if (!encryptedText[0].ciphertext) throw new Error("encryption failed");
return { encryptedBlob: Buffer.from(encryptedText[0].ciphertext) };
return { encryptedBlob: Buffer.from(encryptedText[0].ciphertext as Uint8Array) };
};
const decrypt = async (encryptedBlob: Buffer) => {
@ -101,13 +109,14 @@ export const GcpKmsProviderFactory = async ({ inputs }: GcpKmsProviderArgs): Pro
ciphertext: encryptedBlob
});
if (!decryptedText[0].plaintext) throw new Error("decryption failed");
return { data: Buffer.from(decryptedText[0].plaintext) };
return { data: Buffer.from(decryptedText[0].plaintext as Uint8Array) };
};
return {
validateConnection,
getKeysList,
encrypt,
decrypt
decrypt,
cleanup
};
};

View File

@ -98,4 +98,5 @@ export type TExternalKmsProviderFns = {
validateConnection: () => Promise<boolean>;
encrypt: (data: Buffer) => Promise<{ encryptedBlob: Buffer }>;
decrypt: (encryptedBlob: Buffer) => Promise<{ data: Buffer }>;
cleanup: () => Promise<void>;
};

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