mirror of
https://github.com/Infisical/infisical.git
synced 2025-08-24 20:43:19 +00:00
Compare commits
291 Commits
daniel/kms
...
misc/updat
Author | SHA1 | Date | |
---|---|---|---|
|
9c611daada | ||
|
71edb08942 | ||
|
89d8261a43 | ||
|
a2b2b07185 | ||
|
76864ababa | ||
|
d17d40ebd9 | ||
|
07df6803a5 | ||
|
a09d0e8948 | ||
|
ee598560ec | ||
|
c629705c9c | ||
|
be10f6e52a | ||
|
40c5ff0ad6 | ||
|
8ecb5ca7bc | ||
|
ab6a2b7dbb | ||
|
81bfc04e7c | ||
|
a757fceaed | ||
|
ce8e18f620 | ||
|
d09c964647 | ||
|
eeddbde600 | ||
|
859b643e43 | ||
|
91f71e0ef6 | ||
|
4e9e31eeb7 | ||
|
f6bc99b964 | ||
|
679eb9dffc | ||
|
0754ae3aaf | ||
|
519a0c1bdf | ||
|
e9d8979cf4 | ||
|
486d975fa0 | ||
|
42c49949b4 | ||
|
aea44088db | ||
|
e584c9ea95 | ||
|
428c60880a | ||
|
2179b9a4d7 | ||
|
1921763fa8 | ||
|
5408859a18 | ||
|
8dfc0cfbe0 | ||
|
060199e58c | ||
|
3b9b17f8d5 | ||
|
6addde2650 | ||
|
a6b3be72a9 | ||
|
394bd6755f | ||
|
c21873ac4b | ||
|
64b8c1a2de | ||
|
de443c5ea1 | ||
|
a3b7df4e6b | ||
|
a4b648ad95 | ||
|
04a8931cf6 | ||
|
ab0b8c0f10 | ||
|
258836a605 | ||
|
0b31d7f860 | ||
|
5c91d380b8 | ||
|
b908893a68 | ||
|
4d0275e589 | ||
|
6ca7a990f3 | ||
|
befd77eec2 | ||
|
1d44774913 | ||
|
984552eea9 | ||
|
b6a957a30d | ||
|
2f4efad8ae | ||
|
16c476d78c | ||
|
68c549f1c6 | ||
|
0610416677 | ||
|
4a37dc9cb7 | ||
|
7e432a4297 | ||
|
794fc9c2a2 | ||
|
d4e5d2c7ed | ||
|
0c2e0bb0f9 | ||
|
e2a414ffff | ||
|
0ca3c2bb68 | ||
|
083581b51a | ||
|
40e976133c | ||
|
ad2f002822 | ||
|
8842dfe5d1 | ||
|
b1eea4ae9c | ||
|
a8e0a8aca3 | ||
|
b37058d0e2 | ||
|
334a05d5f1 | ||
|
12c813928c | ||
|
521fef6fca | ||
|
8f8236c445 | ||
|
3cf5c534ff | ||
|
2b03c295f9 | ||
|
4fc7a52941 | ||
|
0ded2e51ba | ||
|
0d2b3adec7 | ||
|
e695203c05 | ||
|
f9d76aae5d | ||
|
1c280759d1 | ||
|
4562f57b54 | ||
|
6005dce44d | ||
|
bf85df7e36 | ||
|
f7f7d2d528 | ||
|
57342cf2a0 | ||
|
d530604b51 | ||
|
229c7c0dcf | ||
|
6a79830e01 | ||
|
722067f86c | ||
|
86bb2659b5 | ||
|
dc59f226b6 | ||
|
cd9792822b | ||
|
9175c1dffa | ||
|
210f1dc2a2 | ||
|
7851bb8710 | ||
|
f6e802c017 | ||
|
d28c87ee67 | ||
|
b6e6a3c6be | ||
|
54927454bf | ||
|
b9070a8fa3 | ||
|
1ce06891a5 | ||
|
3a8154eddc | ||
|
95b6676976 | ||
|
15c0834d56 | ||
|
1e4dfd0c7c | ||
|
34b7d28e2f | ||
|
245a348517 | ||
|
e0fc582e2e | ||
|
68ef897b6a | ||
|
1b060e76de | ||
|
9f7599b2a1 | ||
|
edd415aed8 | ||
|
c816cbc9a9 | ||
|
416811d594 | ||
|
80a9d2bba9 | ||
|
f5e34ea59e | ||
|
9cbe70a6f3 | ||
|
f49fb534ab | ||
|
6eea4c8364 | ||
|
1e206ee441 | ||
|
bec3cec040 | ||
|
85c1a1081e | ||
|
d1122886fd | ||
|
3757f190f0 | ||
|
fec55bc9f8 | ||
|
a285a14fff | ||
|
9ec7d0d03e | ||
|
d5246c2891 | ||
|
dcb7215b7d | ||
|
c0f383ce1d | ||
|
0dcb223f80 | ||
|
877485b45a | ||
|
f9f098af86 | ||
|
6a5748150a | ||
|
3ef053f255 | ||
|
ed914d49ee | ||
|
8f7a652741 | ||
|
e43f583eb6 | ||
|
717c947e53 | ||
|
8ad334b3ab | ||
|
c7e707f20a | ||
|
d13e685a81 | ||
|
9849a5f136 | ||
|
26773a1444 | ||
|
3ea450e94a | ||
|
7d0574087c | ||
|
46755f724c | ||
|
e12f4ad253 | ||
|
36916704be | ||
|
5dbded60f4 | ||
|
a80d5f10e5 | ||
|
0faa8f4bb0 | ||
|
365b4b975e | ||
|
fbf634f7da | ||
|
47bb3c10fa | ||
|
1f3e7da3b7 | ||
|
81396f6b51 | ||
|
63279280fd | ||
|
66fbcc6806 | ||
|
a6f280197b | ||
|
346d2f213e | ||
|
f2d9593660 | ||
|
219964a242 | ||
|
9f1ac77afa | ||
|
240f558231 | ||
|
f3b3df1010 | ||
|
1fd6cd4787 | ||
|
a7d715ed08 | ||
|
a758503f40 | ||
|
550cb2b5ec | ||
|
75cb259c51 | ||
|
be2c5a9e57 | ||
|
a077a9d6f2 | ||
|
296493484f | ||
|
835b2fba9c | ||
|
92bc9d48af | ||
|
a9c1c197f7 | ||
|
5bd7dd4d65 | ||
|
8e2cfe2c03 | ||
|
0bb107d61d | ||
|
82c7dad6c8 | ||
|
83df0850ce | ||
|
ae43435509 | ||
|
7811178261 | ||
|
b21b0b340b | ||
|
fdbb930940 | ||
|
9e56790886 | ||
|
e08c5f265e | ||
|
e7a55d8a27 | ||
|
35b8adb0f6 | ||
|
d161be1170 | ||
|
aabf933756 | ||
|
5d44d58ff4 | ||
|
1268bc1238 | ||
|
07e4bc8eed | ||
|
69ef7fdf3b | ||
|
ff294dab8d | ||
|
a01a9f3f77 | ||
|
c99440ba81 | ||
|
6d5a6f42e0 | ||
|
235be96ded | ||
|
d0a642a63a | ||
|
30471bfcad | ||
|
cf84dde0fa | ||
|
0c027fdc43 | ||
|
727a6a7701 | ||
|
98bb5d7aa7 | ||
|
7f1f9e7fd0 | ||
|
b06eeb0d40 | ||
|
eedffffc38 | ||
|
5d366687a5 | ||
|
4720914839 | ||
|
9f487ad026 | ||
|
c70b9e665e | ||
|
d460e96052 | ||
|
e475774910 | ||
|
98f742a807 | ||
|
66f1967f88 | ||
|
da6cf85c8d | ||
|
e8b6eb0573 | ||
|
03ad5c5db0 | ||
|
e81c49500b | ||
|
2a28d74bde | ||
|
d4ac4f8d8f | ||
|
aedc6e16ad | ||
|
1ec7c67212 | ||
|
ff0ff622a6 | ||
|
511becabd8 | ||
|
f0229c5ecf | ||
|
8d711af23b | ||
|
7bd61d88fc | ||
|
a9a16c9bd1 | ||
|
929434d17f | ||
|
c47d76a6c7 | ||
|
e959ed7fab | ||
|
4e4b1b689b | ||
|
ee2e2246da | ||
|
e30d400afa | ||
|
024ed0c0d8 | ||
|
e99e360339 | ||
|
f35cd2d6a6 | ||
|
b259428075 | ||
|
f54a10f626 | ||
|
63a3ce2dba | ||
|
9aabc3ced7 | ||
|
fe9ec6b030 | ||
|
bef55043f7 | ||
|
0323d152da | ||
|
b6566943c6 | ||
|
f345801bd6 | ||
|
4160009913 | ||
|
d5065af7e9 | ||
|
68e88ddef8 | ||
|
a2909b8030 | ||
|
8987938642 | ||
|
3f00359459 | ||
|
a5b5b90ca1 | ||
|
fd0a00023b | ||
|
dd112b3850 | ||
|
c01c58fdcb | ||
|
4bba207552 | ||
|
8563eb850b | ||
|
4225bf6e0e | ||
|
fab385fdd9 | ||
|
a204629bef | ||
|
50679ba29d | ||
|
f5fa57d6c5 | ||
|
6088ae09ab | ||
|
0de15bf70c | ||
|
b5d229a7c5 | ||
|
92084ccd47 | ||
|
418ac20f91 | ||
|
e30a05e3e8 | ||
|
ce7798c48b | ||
|
6ce1c4e19e | ||
|
f08de1599d | ||
|
7d4f223174 | ||
|
ef47d0056f | ||
|
ccd7b0062e | ||
|
c403ffa9f6 | ||
|
1184ea1b11 | ||
|
7d97a76ecc | ||
|
a889f92528 |
27
.github/workflows/release_helm_gateway.yaml
vendored
Normal file
27
.github/workflows/release_helm_gateway.yaml
vendored
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
name: Release Gateway Helm Chart
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
release-helm:
|
||||||
|
name: Release Helm Chart
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Install Helm
|
||||||
|
uses: azure/setup-helm@v3
|
||||||
|
with:
|
||||||
|
version: v3.10.0
|
||||||
|
|
||||||
|
- name: Install python
|
||||||
|
uses: actions/setup-python@v4
|
||||||
|
|
||||||
|
- name: Install Cloudsmith CLI
|
||||||
|
run: pip install --upgrade cloudsmith-cli
|
||||||
|
|
||||||
|
- name: Build and push helm package to CloudSmith
|
||||||
|
run: cd helm-charts && sh upload-gateway-cloudsmith.sh
|
||||||
|
env:
|
||||||
|
CLOUDSMITH_API_KEY: ${{ secrets.CLOUDSMITH_API_KEY }}
|
@@ -24,3 +24,7 @@ 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
|
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:281
|
||||||
docs/documentation/platform/kms/overview.mdx:generic-api-key:344
|
docs/documentation/platform/kms/overview.mdx:generic-api-key:344
|
||||||
|
frontend/src/pages/secret-manager/OverviewPage/components/SecretOverviewTableRow/SecretOverviewTableRow.tsx:generic-api-key:85
|
||||||
|
docs/cli/commands/user.mdx:generic-api-key:51
|
||||||
|
frontend/src/pages/secret-manager/OverviewPage/components/SecretOverviewTableRow/SecretOverviewTableRow.tsx:generic-api-key:76
|
||||||
|
docs/integrations/app-connections/hashicorp-vault.mdx:generic-api-key:188
|
||||||
|
@@ -133,8 +133,8 @@ RUN apt-get update && apt-get install -y \
|
|||||||
RUN printf "[FreeTDS]\nDescription = FreeTDS Driver\nDriver = /usr/lib/x86_64-linux-gnu/odbc/libtdsodbc.so\nSetup = /usr/lib/x86_64-linux-gnu/odbc/libtdsS.so\nFileUsage = 1\n" > /etc/odbcinst.ini
|
RUN printf "[FreeTDS]\nDescription = FreeTDS Driver\nDriver = /usr/lib/x86_64-linux-gnu/odbc/libtdsodbc.so\nSetup = /usr/lib/x86_64-linux-gnu/odbc/libtdsS.so\nFileUsage = 1\n" > /etc/odbcinst.ini
|
||||||
|
|
||||||
# Install Infisical CLI
|
# Install Infisical CLI
|
||||||
RUN curl -1sLf 'https://dl.cloudsmith.io/public/infisical/infisical-cli/setup.deb.sh' | bash \
|
RUN curl -1sLf 'https://artifacts-cli.infisical.com/setup.deb.sh' | bash \
|
||||||
&& apt-get update && apt-get install -y infisical=0.31.1 \
|
&& apt-get update && apt-get install -y infisical=0.41.2 \
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
RUN groupadd -r -g 1001 nodejs && useradd -r -u 1001 -g nodejs non-root-user
|
RUN groupadd -r -g 1001 nodejs && useradd -r -u 1001 -g nodejs non-root-user
|
||||||
@@ -171,6 +171,7 @@ ENV NODE_ENV production
|
|||||||
ENV STANDALONE_BUILD true
|
ENV STANDALONE_BUILD true
|
||||||
ENV STANDALONE_MODE true
|
ENV STANDALONE_MODE true
|
||||||
ENV ChrystokiConfigurationPath=/usr/safenet/lunaclient/
|
ENV ChrystokiConfigurationPath=/usr/safenet/lunaclient/
|
||||||
|
ENV NODE_OPTIONS="--max-old-space-size=1024"
|
||||||
|
|
||||||
WORKDIR /backend
|
WORKDIR /backend
|
||||||
|
|
||||||
|
@@ -127,8 +127,8 @@ RUN apt-get update && apt-get install -y \
|
|||||||
&& rm -rf /var/lib/apt/lists/*
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
# Install Infisical CLI
|
# Install Infisical CLI
|
||||||
RUN curl -1sLf 'https://dl.cloudsmith.io/public/infisical/infisical-cli/setup.deb.sh' | bash \
|
RUN curl -1sLf 'https://artifacts-cli.infisical.com/setup.deb.sh' | bash \
|
||||||
&& apt-get update && apt-get install -y infisical=0.31.1 \
|
&& apt-get update && apt-get install -y infisical=0.41.2 \
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
WORKDIR /
|
WORKDIR /
|
||||||
@@ -168,6 +168,7 @@ ENV HTTPS_ENABLED false
|
|||||||
ENV NODE_ENV production
|
ENV NODE_ENV production
|
||||||
ENV STANDALONE_BUILD true
|
ENV STANDALONE_BUILD true
|
||||||
ENV STANDALONE_MODE true
|
ENV STANDALONE_MODE true
|
||||||
|
ENV NODE_OPTIONS="--max-old-space-size=1024"
|
||||||
|
|
||||||
WORKDIR /backend
|
WORKDIR /backend
|
||||||
|
|
||||||
|
@@ -69,6 +69,15 @@ module.exports = {
|
|||||||
["^\\."]
|
["^\\."]
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
],
|
||||||
|
"import/extensions": [
|
||||||
|
"error",
|
||||||
|
"ignorePackages",
|
||||||
|
{
|
||||||
|
"": "never", // this is required to get the .tsx to work...
|
||||||
|
ts: "never",
|
||||||
|
tsx: "never"
|
||||||
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@@ -54,8 +54,8 @@ COPY --from=build /app .
|
|||||||
|
|
||||||
# Install Infisical CLI
|
# Install Infisical CLI
|
||||||
RUN apt-get install -y curl bash && \
|
RUN apt-get install -y curl bash && \
|
||||||
curl -1sLf 'https://dl.cloudsmith.io/public/infisical/infisical-cli/setup.deb.sh' | bash && \
|
curl -1sLf 'https://artifacts-cli.infisical.com/setup.deb.sh' | bash && \
|
||||||
apt-get update && apt-get install -y infisical=0.8.1 git
|
apt-get update && apt-get install -y infisical=0.41.2 git
|
||||||
|
|
||||||
HEALTHCHECK --interval=10s --timeout=3s --start-period=10s \
|
HEALTHCHECK --interval=10s --timeout=3s --start-period=10s \
|
||||||
CMD node healthcheck.js
|
CMD node healthcheck.js
|
||||||
|
@@ -55,9 +55,9 @@ RUN mkdir -p /etc/softhsm2/tokens && \
|
|||||||
# ? App setup
|
# ? App setup
|
||||||
|
|
||||||
# Install Infisical CLI
|
# Install Infisical CLI
|
||||||
RUN curl -1sLf 'https://dl.cloudsmith.io/public/infisical/infisical-cli/setup.deb.sh' | bash && \
|
RUN curl -1sLf 'https://artifacts-cli.infisical.com/setup.deb.sh' | bash && \
|
||||||
apt-get update && \
|
apt-get update && \
|
||||||
apt-get install -y infisical=0.8.1
|
apt-get install -y infisical=0.41.2
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
|
@@ -64,9 +64,9 @@ RUN wget https://www.openssl.org/source/openssl-3.1.2.tar.gz \
|
|||||||
# ? App setup
|
# ? App setup
|
||||||
|
|
||||||
# Install Infisical CLI
|
# Install Infisical CLI
|
||||||
RUN curl -1sLf 'https://dl.cloudsmith.io/public/infisical/infisical-cli/setup.deb.sh' | bash && \
|
RUN curl -1sLf 'https://artifacts-cli.infisical.com/setup.deb.sh' | bash && \
|
||||||
apt-get update && \
|
apt-get update && \
|
||||||
apt-get install -y infisical=0.8.1
|
apt-get install -y infisical=0.41.2
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
|
@@ -1,4 +1,8 @@
|
|||||||
|
import RE2 from "re2";
|
||||||
|
|
||||||
import { TKeyStoreFactory } from "@app/keystore/keystore";
|
import { TKeyStoreFactory } from "@app/keystore/keystore";
|
||||||
|
import { applyJitter } from "@app/lib/dates";
|
||||||
|
import { delay as delayMs } from "@app/lib/delay";
|
||||||
import { Lock } from "@app/lib/red-lock";
|
import { Lock } from "@app/lib/red-lock";
|
||||||
|
|
||||||
export const mockKeyStore = (): TKeyStoreFactory => {
|
export const mockKeyStore = (): TKeyStoreFactory => {
|
||||||
@@ -18,6 +22,27 @@ export const mockKeyStore = (): TKeyStoreFactory => {
|
|||||||
delete store[key];
|
delete store[key];
|
||||||
return 1;
|
return 1;
|
||||||
},
|
},
|
||||||
|
deleteItems: async ({ pattern, batchSize = 500, delay = 1500, jitter = 200 }) => {
|
||||||
|
const regex = new RE2(`^${pattern.replace(/[-[\]/{}()+?.\\^$|]/g, "\\$&").replace(/\*/g, ".*")}$`);
|
||||||
|
let totalDeleted = 0;
|
||||||
|
const keys = Object.keys(store);
|
||||||
|
|
||||||
|
for (let i = 0; i < keys.length; i += batchSize) {
|
||||||
|
const batch = keys.slice(i, i + batchSize);
|
||||||
|
|
||||||
|
for (const key of batch) {
|
||||||
|
if (regex.test(key)) {
|
||||||
|
delete store[key];
|
||||||
|
totalDeleted += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-await-in-loop
|
||||||
|
await delayMs(Math.max(0, applyJitter(delay, jitter)));
|
||||||
|
}
|
||||||
|
|
||||||
|
return totalDeleted;
|
||||||
|
},
|
||||||
getItem: async (key) => {
|
getItem: async (key) => {
|
||||||
const value = store[key];
|
const value = store[key];
|
||||||
if (typeof value === "string") {
|
if (typeof value === "string") {
|
||||||
|
3530
backend/package-lock.json
generated
3530
backend/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -72,7 +72,8 @@
|
|||||||
"seed:new": "tsx ./scripts/create-seed-file.ts",
|
"seed:new": "tsx ./scripts/create-seed-file.ts",
|
||||||
"seed": "knex --knexfile ./dist/db/knexfile.ts --client pg seed:run",
|
"seed": "knex --knexfile ./dist/db/knexfile.ts --client pg seed:run",
|
||||||
"seed-dev": "knex --knexfile ./src/db/knexfile.ts --client pg seed:run",
|
"seed-dev": "knex --knexfile ./src/db/knexfile.ts --client pg seed:run",
|
||||||
"db:reset": "npm run migration:rollback -- --all && npm run migration:latest"
|
"db:reset": "npm run migration:rollback -- --all && npm run migration:latest",
|
||||||
|
"email:dev": "email dev --dir src/services/smtp/emails"
|
||||||
},
|
},
|
||||||
"keywords": [],
|
"keywords": [],
|
||||||
"author": "",
|
"author": "",
|
||||||
@@ -96,6 +97,7 @@
|
|||||||
"@types/picomatch": "^2.3.3",
|
"@types/picomatch": "^2.3.3",
|
||||||
"@types/pkcs11js": "^1.0.4",
|
"@types/pkcs11js": "^1.0.4",
|
||||||
"@types/prompt-sync": "^4.2.3",
|
"@types/prompt-sync": "^4.2.3",
|
||||||
|
"@types/react": "^19.1.2",
|
||||||
"@types/resolve": "^1.20.6",
|
"@types/resolve": "^1.20.6",
|
||||||
"@types/safe-regex": "^1.1.6",
|
"@types/safe-regex": "^1.1.6",
|
||||||
"@types/sjcl": "^1.0.34",
|
"@types/sjcl": "^1.0.34",
|
||||||
@@ -115,6 +117,7 @@
|
|||||||
"nodemon": "^3.0.2",
|
"nodemon": "^3.0.2",
|
||||||
"pino-pretty": "^10.2.3",
|
"pino-pretty": "^10.2.3",
|
||||||
"prompt-sync": "^4.2.0",
|
"prompt-sync": "^4.2.0",
|
||||||
|
"react-email": "4.0.7",
|
||||||
"rimraf": "^5.0.5",
|
"rimraf": "^5.0.5",
|
||||||
"ts-node": "^10.9.2",
|
"ts-node": "^10.9.2",
|
||||||
"tsc-alias": "^1.8.8",
|
"tsc-alias": "^1.8.8",
|
||||||
@@ -149,7 +152,8 @@
|
|||||||
"@infisical/quic": "^1.0.8",
|
"@infisical/quic": "^1.0.8",
|
||||||
"@node-saml/passport-saml": "^5.0.1",
|
"@node-saml/passport-saml": "^5.0.1",
|
||||||
"@octokit/auth-app": "^7.1.1",
|
"@octokit/auth-app": "^7.1.1",
|
||||||
"@octokit/plugin-paginate-graphql": "^5.2.4",
|
"@octokit/core": "^5.2.1",
|
||||||
|
"@octokit/plugin-paginate-graphql": "^4.0.1",
|
||||||
"@octokit/plugin-retry": "^5.0.5",
|
"@octokit/plugin-retry": "^5.0.5",
|
||||||
"@octokit/rest": "^20.0.2",
|
"@octokit/rest": "^20.0.2",
|
||||||
"@octokit/webhooks-types": "^7.3.1",
|
"@octokit/webhooks-types": "^7.3.1",
|
||||||
@@ -164,6 +168,7 @@
|
|||||||
"@opentelemetry/semantic-conventions": "^1.27.0",
|
"@opentelemetry/semantic-conventions": "^1.27.0",
|
||||||
"@peculiar/asn1-schema": "^2.3.8",
|
"@peculiar/asn1-schema": "^2.3.8",
|
||||||
"@peculiar/x509": "^1.12.1",
|
"@peculiar/x509": "^1.12.1",
|
||||||
|
"@react-email/components": "0.0.36",
|
||||||
"@serdnam/pino-cloudwatch-transport": "^1.0.4",
|
"@serdnam/pino-cloudwatch-transport": "^1.0.4",
|
||||||
"@sindresorhus/slugify": "1.1.0",
|
"@sindresorhus/slugify": "1.1.0",
|
||||||
"@slack/oauth": "^3.0.2",
|
"@slack/oauth": "^3.0.2",
|
||||||
@@ -175,6 +180,7 @@
|
|||||||
"axios": "^1.6.7",
|
"axios": "^1.6.7",
|
||||||
"axios-retry": "^4.0.0",
|
"axios-retry": "^4.0.0",
|
||||||
"bcrypt": "^5.1.1",
|
"bcrypt": "^5.1.1",
|
||||||
|
"botbuilder": "^4.23.2",
|
||||||
"bullmq": "^5.4.2",
|
"bullmq": "^5.4.2",
|
||||||
"cassandra-driver": "^4.7.2",
|
"cassandra-driver": "^4.7.2",
|
||||||
"connect-redis": "^7.1.1",
|
"connect-redis": "^7.1.1",
|
||||||
@@ -222,6 +228,8 @@
|
|||||||
"posthog-node": "^3.6.2",
|
"posthog-node": "^3.6.2",
|
||||||
"probot": "^13.3.8",
|
"probot": "^13.3.8",
|
||||||
"re2": "^1.21.4",
|
"re2": "^1.21.4",
|
||||||
|
"react": "19.1.0",
|
||||||
|
"react-dom": "19.1.0",
|
||||||
"safe-regex": "^2.1.1",
|
"safe-regex": "^2.1.1",
|
||||||
"scim-patch": "^0.8.3",
|
"scim-patch": "^0.8.3",
|
||||||
"scim2-parse-filter": "^0.2.10",
|
"scim2-parse-filter": "^0.2.10",
|
||||||
@@ -233,6 +241,6 @@
|
|||||||
"tweetnacl-util": "^0.15.1",
|
"tweetnacl-util": "^0.15.1",
|
||||||
"uuid": "^9.0.1",
|
"uuid": "^9.0.1",
|
||||||
"zod": "^3.22.4",
|
"zod": "^3.22.4",
|
||||||
"zod-to-json-schema": "^3.22.4"
|
"zod-to-json-schema": "^3.24.5"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
18
backend/src/@types/fastify.d.ts
vendored
18
backend/src/@types/fastify.d.ts
vendored
@@ -41,6 +41,7 @@ import { TSecretSnapshotServiceFactory } from "@app/ee/services/secret-snapshot/
|
|||||||
import { TSshCertificateAuthorityServiceFactory } from "@app/ee/services/ssh/ssh-certificate-authority-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 { TSshCertificateTemplateServiceFactory } from "@app/ee/services/ssh-certificate-template/ssh-certificate-template-service";
|
||||||
import { TSshHostServiceFactory } from "@app/ee/services/ssh-host/ssh-host-service";
|
import { TSshHostServiceFactory } from "@app/ee/services/ssh-host/ssh-host-service";
|
||||||
|
import { TSshHostGroupServiceFactory } from "@app/ee/services/ssh-host-group/ssh-host-group-service";
|
||||||
import { TTrustedIpServiceFactory } from "@app/ee/services/trusted-ip/trusted-ip-service";
|
import { TTrustedIpServiceFactory } from "@app/ee/services/trusted-ip/trusted-ip-service";
|
||||||
import { TAuthMode } from "@app/server/plugins/auth/inject-identity";
|
import { TAuthMode } from "@app/server/plugins/auth/inject-identity";
|
||||||
import { TApiKeyServiceFactory } from "@app/services/api-key/api-key-service";
|
import { TApiKeyServiceFactory } from "@app/services/api-key/api-key-service";
|
||||||
@@ -65,12 +66,15 @@ import { TIdentityAzureAuthServiceFactory } from "@app/services/identity-azure-a
|
|||||||
import { TIdentityGcpAuthServiceFactory } from "@app/services/identity-gcp-auth/identity-gcp-auth-service";
|
import { TIdentityGcpAuthServiceFactory } from "@app/services/identity-gcp-auth/identity-gcp-auth-service";
|
||||||
import { TIdentityJwtAuthServiceFactory } from "@app/services/identity-jwt-auth/identity-jwt-auth-service";
|
import { TIdentityJwtAuthServiceFactory } from "@app/services/identity-jwt-auth/identity-jwt-auth-service";
|
||||||
import { TIdentityKubernetesAuthServiceFactory } from "@app/services/identity-kubernetes-auth/identity-kubernetes-auth-service";
|
import { TIdentityKubernetesAuthServiceFactory } from "@app/services/identity-kubernetes-auth/identity-kubernetes-auth-service";
|
||||||
|
import { TIdentityLdapAuthServiceFactory } from "@app/services/identity-ldap-auth/identity-ldap-auth-service";
|
||||||
|
import { TAllowedFields } from "@app/services/identity-ldap-auth/identity-ldap-auth-types";
|
||||||
import { TIdentityOidcAuthServiceFactory } from "@app/services/identity-oidc-auth/identity-oidc-auth-service";
|
import { TIdentityOidcAuthServiceFactory } from "@app/services/identity-oidc-auth/identity-oidc-auth-service";
|
||||||
import { TIdentityProjectServiceFactory } from "@app/services/identity-project/identity-project-service";
|
import { TIdentityProjectServiceFactory } from "@app/services/identity-project/identity-project-service";
|
||||||
import { TIdentityTokenAuthServiceFactory } from "@app/services/identity-token-auth/identity-token-auth-service";
|
import { TIdentityTokenAuthServiceFactory } from "@app/services/identity-token-auth/identity-token-auth-service";
|
||||||
import { TIdentityUaServiceFactory } from "@app/services/identity-ua/identity-ua-service";
|
import { TIdentityUaServiceFactory } from "@app/services/identity-ua/identity-ua-service";
|
||||||
import { TIntegrationServiceFactory } from "@app/services/integration/integration-service";
|
import { TIntegrationServiceFactory } from "@app/services/integration/integration-service";
|
||||||
import { TIntegrationAuthServiceFactory } from "@app/services/integration-auth/integration-auth-service";
|
import { TIntegrationAuthServiceFactory } from "@app/services/integration-auth/integration-auth-service";
|
||||||
|
import { TMicrosoftTeamsServiceFactory } from "@app/services/microsoft-teams/microsoft-teams-service";
|
||||||
import { TOrgRoleServiceFactory } from "@app/services/org/org-role-service";
|
import { TOrgRoleServiceFactory } from "@app/services/org/org-role-service";
|
||||||
import { TOrgServiceFactory } from "@app/services/org/org-service";
|
import { TOrgServiceFactory } from "@app/services/org/org-service";
|
||||||
import { TOrgAdminServiceFactory } from "@app/services/org-admin/org-admin-service";
|
import { TOrgAdminServiceFactory } from "@app/services/org-admin/org-admin-service";
|
||||||
@@ -144,6 +148,13 @@ declare module "fastify" {
|
|||||||
providerAuthToken: string;
|
providerAuthToken: string;
|
||||||
externalProviderAccessToken?: string;
|
externalProviderAccessToken?: string;
|
||||||
};
|
};
|
||||||
|
passportMachineIdentity: {
|
||||||
|
identityId: string;
|
||||||
|
user: {
|
||||||
|
uid: string;
|
||||||
|
mail?: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
kmipUser: {
|
kmipUser: {
|
||||||
projectId: string;
|
projectId: string;
|
||||||
clientId: string;
|
clientId: string;
|
||||||
@@ -151,7 +162,9 @@ declare module "fastify" {
|
|||||||
};
|
};
|
||||||
auditLogInfo: Pick<TCreateAuditLogDTO, "userAgent" | "userAgentType" | "ipAddress" | "actor">;
|
auditLogInfo: Pick<TCreateAuditLogDTO, "userAgent" | "userAgentType" | "ipAddress" | "actor">;
|
||||||
ssoConfig: Awaited<ReturnType<TSamlConfigServiceFactory["getSaml"]>>;
|
ssoConfig: Awaited<ReturnType<TSamlConfigServiceFactory["getSaml"]>>;
|
||||||
ldapConfig: Awaited<ReturnType<TLdapConfigServiceFactory["getLdapCfg"]>>;
|
ldapConfig: Awaited<ReturnType<TLdapConfigServiceFactory["getLdapCfg"]>> & {
|
||||||
|
allowedFields?: TAllowedFields[];
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
interface FastifyInstance {
|
interface FastifyInstance {
|
||||||
@@ -197,6 +210,7 @@ declare module "fastify" {
|
|||||||
identityAzureAuth: TIdentityAzureAuthServiceFactory;
|
identityAzureAuth: TIdentityAzureAuthServiceFactory;
|
||||||
identityOidcAuth: TIdentityOidcAuthServiceFactory;
|
identityOidcAuth: TIdentityOidcAuthServiceFactory;
|
||||||
identityJwtAuth: TIdentityJwtAuthServiceFactory;
|
identityJwtAuth: TIdentityJwtAuthServiceFactory;
|
||||||
|
identityLdapAuth: TIdentityLdapAuthServiceFactory;
|
||||||
accessApprovalPolicy: TAccessApprovalPolicyServiceFactory;
|
accessApprovalPolicy: TAccessApprovalPolicyServiceFactory;
|
||||||
accessApprovalRequest: TAccessApprovalRequestServiceFactory;
|
accessApprovalRequest: TAccessApprovalRequestServiceFactory;
|
||||||
secretApprovalPolicy: TSecretApprovalPolicyServiceFactory;
|
secretApprovalPolicy: TSecretApprovalPolicyServiceFactory;
|
||||||
@@ -213,6 +227,7 @@ declare module "fastify" {
|
|||||||
sshCertificateAuthority: TSshCertificateAuthorityServiceFactory;
|
sshCertificateAuthority: TSshCertificateAuthorityServiceFactory;
|
||||||
sshCertificateTemplate: TSshCertificateTemplateServiceFactory;
|
sshCertificateTemplate: TSshCertificateTemplateServiceFactory;
|
||||||
sshHost: TSshHostServiceFactory;
|
sshHost: TSshHostServiceFactory;
|
||||||
|
sshHostGroup: TSshHostGroupServiceFactory;
|
||||||
certificateAuthority: TCertificateAuthorityServiceFactory;
|
certificateAuthority: TCertificateAuthorityServiceFactory;
|
||||||
certificateAuthorityCrl: TCertificateAuthorityCrlServiceFactory;
|
certificateAuthorityCrl: TCertificateAuthorityCrlServiceFactory;
|
||||||
certificateEst: TCertificateEstServiceFactory;
|
certificateEst: TCertificateEstServiceFactory;
|
||||||
@@ -246,6 +261,7 @@ declare module "fastify" {
|
|||||||
kmipOperation: TKmipOperationServiceFactory;
|
kmipOperation: TKmipOperationServiceFactory;
|
||||||
gateway: TGatewayServiceFactory;
|
gateway: TGatewayServiceFactory;
|
||||||
secretRotationV2: TSecretRotationV2ServiceFactory;
|
secretRotationV2: TSecretRotationV2ServiceFactory;
|
||||||
|
microsoftTeams: TMicrosoftTeamsServiceFactory;
|
||||||
assumePrivileges: TAssumePrivilegeServiceFactory;
|
assumePrivileges: TAssumePrivilegeServiceFactory;
|
||||||
githubOrgSync: TGithubOrgSyncServiceFactory;
|
githubOrgSync: TGithubOrgSyncServiceFactory;
|
||||||
};
|
};
|
||||||
|
46
backend/src/@types/knex.d.ts
vendored
46
backend/src/@types/knex.d.ts
vendored
@@ -386,6 +386,12 @@ import {
|
|||||||
TSshCertificateTemplates,
|
TSshCertificateTemplates,
|
||||||
TSshCertificateTemplatesInsert,
|
TSshCertificateTemplatesInsert,
|
||||||
TSshCertificateTemplatesUpdate,
|
TSshCertificateTemplatesUpdate,
|
||||||
|
TSshHostGroupMemberships,
|
||||||
|
TSshHostGroupMembershipsInsert,
|
||||||
|
TSshHostGroupMembershipsUpdate,
|
||||||
|
TSshHostGroups,
|
||||||
|
TSshHostGroupsInsert,
|
||||||
|
TSshHostGroupsUpdate,
|
||||||
TSshHostLoginUserMappings,
|
TSshHostLoginUserMappings,
|
||||||
TSshHostLoginUserMappingsInsert,
|
TSshHostLoginUserMappingsInsert,
|
||||||
TSshHostLoginUserMappingsUpdate,
|
TSshHostLoginUserMappingsUpdate,
|
||||||
@@ -426,6 +432,21 @@ import {
|
|||||||
TWorkflowIntegrationsInsert,
|
TWorkflowIntegrationsInsert,
|
||||||
TWorkflowIntegrationsUpdate
|
TWorkflowIntegrationsUpdate
|
||||||
} from "@app/db/schemas";
|
} from "@app/db/schemas";
|
||||||
|
import {
|
||||||
|
TIdentityLdapAuths,
|
||||||
|
TIdentityLdapAuthsInsert,
|
||||||
|
TIdentityLdapAuthsUpdate
|
||||||
|
} from "@app/db/schemas/identity-ldap-auths";
|
||||||
|
import {
|
||||||
|
TMicrosoftTeamsIntegrations,
|
||||||
|
TMicrosoftTeamsIntegrationsInsert,
|
||||||
|
TMicrosoftTeamsIntegrationsUpdate
|
||||||
|
} from "@app/db/schemas/microsoft-teams-integrations";
|
||||||
|
import {
|
||||||
|
TProjectMicrosoftTeamsConfigs,
|
||||||
|
TProjectMicrosoftTeamsConfigsInsert,
|
||||||
|
TProjectMicrosoftTeamsConfigsUpdate
|
||||||
|
} from "@app/db/schemas/project-microsoft-teams-configs";
|
||||||
import {
|
import {
|
||||||
TSecretReminderRecipients,
|
TSecretReminderRecipients,
|
||||||
TSecretReminderRecipientsInsert,
|
TSecretReminderRecipientsInsert,
|
||||||
@@ -445,6 +466,16 @@ declare module "knex/types/tables" {
|
|||||||
interface Tables {
|
interface Tables {
|
||||||
[TableName.Users]: KnexOriginal.CompositeTableType<TUsers, TUsersInsert, TUsersUpdate>;
|
[TableName.Users]: KnexOriginal.CompositeTableType<TUsers, TUsersInsert, TUsersUpdate>;
|
||||||
[TableName.Groups]: KnexOriginal.CompositeTableType<TGroups, TGroupsInsert, TGroupsUpdate>;
|
[TableName.Groups]: KnexOriginal.CompositeTableType<TGroups, TGroupsInsert, TGroupsUpdate>;
|
||||||
|
[TableName.SshHostGroup]: KnexOriginal.CompositeTableType<
|
||||||
|
TSshHostGroups,
|
||||||
|
TSshHostGroupsInsert,
|
||||||
|
TSshHostGroupsUpdate
|
||||||
|
>;
|
||||||
|
[TableName.SshHostGroupMembership]: KnexOriginal.CompositeTableType<
|
||||||
|
TSshHostGroupMemberships,
|
||||||
|
TSshHostGroupMembershipsInsert,
|
||||||
|
TSshHostGroupMembershipsUpdate
|
||||||
|
>;
|
||||||
[TableName.SshHost]: KnexOriginal.CompositeTableType<TSshHosts, TSshHostsInsert, TSshHostsUpdate>;
|
[TableName.SshHost]: KnexOriginal.CompositeTableType<TSshHosts, TSshHostsInsert, TSshHostsUpdate>;
|
||||||
[TableName.SshCertificateAuthority]: KnexOriginal.CompositeTableType<
|
[TableName.SshCertificateAuthority]: KnexOriginal.CompositeTableType<
|
||||||
TSshCertificateAuthorities,
|
TSshCertificateAuthorities,
|
||||||
@@ -709,6 +740,11 @@ declare module "knex/types/tables" {
|
|||||||
TIdentityJwtAuthsInsert,
|
TIdentityJwtAuthsInsert,
|
||||||
TIdentityJwtAuthsUpdate
|
TIdentityJwtAuthsUpdate
|
||||||
>;
|
>;
|
||||||
|
[TableName.IdentityLdapAuth]: KnexOriginal.CompositeTableType<
|
||||||
|
TIdentityLdapAuths,
|
||||||
|
TIdentityLdapAuthsInsert,
|
||||||
|
TIdentityLdapAuthsUpdate
|
||||||
|
>;
|
||||||
[TableName.IdentityUaClientSecret]: KnexOriginal.CompositeTableType<
|
[TableName.IdentityUaClientSecret]: KnexOriginal.CompositeTableType<
|
||||||
TIdentityUaClientSecrets,
|
TIdentityUaClientSecrets,
|
||||||
TIdentityUaClientSecretsInsert,
|
TIdentityUaClientSecretsInsert,
|
||||||
@@ -1002,6 +1038,16 @@ declare module "knex/types/tables" {
|
|||||||
TSecretRotationV2SecretMappingsInsert,
|
TSecretRotationV2SecretMappingsInsert,
|
||||||
TSecretRotationV2SecretMappingsUpdate
|
TSecretRotationV2SecretMappingsUpdate
|
||||||
>;
|
>;
|
||||||
|
[TableName.MicrosoftTeamsIntegrations]: KnexOriginal.CompositeTableType<
|
||||||
|
TMicrosoftTeamsIntegrations,
|
||||||
|
TMicrosoftTeamsIntegrationsInsert,
|
||||||
|
TMicrosoftTeamsIntegrationsUpdate
|
||||||
|
>;
|
||||||
|
[TableName.ProjectMicrosoftTeamsConfigs]: KnexOriginal.CompositeTableType<
|
||||||
|
TProjectMicrosoftTeamsConfigs,
|
||||||
|
TProjectMicrosoftTeamsConfigsInsert,
|
||||||
|
TProjectMicrosoftTeamsConfigsUpdate
|
||||||
|
>;
|
||||||
[TableName.SecretReminderRecipients]: KnexOriginal.CompositeTableType<
|
[TableName.SecretReminderRecipients]: KnexOriginal.CompositeTableType<
|
||||||
TSecretReminderRecipients,
|
TSecretReminderRecipients,
|
||||||
TSecretReminderRecipientsInsert,
|
TSecretReminderRecipientsInsert,
|
||||||
|
@@ -0,0 +1,130 @@
|
|||||||
|
import { Knex } from "knex";
|
||||||
|
|
||||||
|
import { TableName } from "../schemas";
|
||||||
|
import { createOnUpdateTrigger, dropOnUpdateTrigger } from "../utils";
|
||||||
|
|
||||||
|
export async function up(knex: Knex): Promise<void> {
|
||||||
|
const superAdminHasEncryptedMicrosoftTeamsClientIdColumn = await knex.schema.hasColumn(
|
||||||
|
TableName.SuperAdmin,
|
||||||
|
"encryptedMicrosoftTeamsAppId"
|
||||||
|
);
|
||||||
|
const superAdminHasEncryptedMicrosoftTeamsClientSecret = await knex.schema.hasColumn(
|
||||||
|
TableName.SuperAdmin,
|
||||||
|
"encryptedMicrosoftTeamsClientSecret"
|
||||||
|
);
|
||||||
|
const superAdminHasEncryptedMicrosoftTeamsBotId = await knex.schema.hasColumn(
|
||||||
|
TableName.SuperAdmin,
|
||||||
|
"encryptedMicrosoftTeamsBotId"
|
||||||
|
);
|
||||||
|
|
||||||
|
if (
|
||||||
|
!superAdminHasEncryptedMicrosoftTeamsClientIdColumn ||
|
||||||
|
!superAdminHasEncryptedMicrosoftTeamsClientSecret ||
|
||||||
|
!superAdminHasEncryptedMicrosoftTeamsBotId
|
||||||
|
) {
|
||||||
|
await knex.schema.alterTable(TableName.SuperAdmin, (table) => {
|
||||||
|
if (!superAdminHasEncryptedMicrosoftTeamsClientIdColumn) {
|
||||||
|
table.binary("encryptedMicrosoftTeamsAppId").nullable();
|
||||||
|
}
|
||||||
|
if (!superAdminHasEncryptedMicrosoftTeamsClientSecret) {
|
||||||
|
table.binary("encryptedMicrosoftTeamsClientSecret").nullable();
|
||||||
|
}
|
||||||
|
if (!superAdminHasEncryptedMicrosoftTeamsBotId) {
|
||||||
|
table.binary("encryptedMicrosoftTeamsBotId").nullable();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(await knex.schema.hasColumn(TableName.WorkflowIntegrations, "status"))) {
|
||||||
|
await knex.schema.alterTable(TableName.WorkflowIntegrations, (table) => {
|
||||||
|
table.enu("status", ["pending", "installed", "failed"]).notNullable().defaultTo("installed"); // defaults to installed so we can have backwards compatibility with existing workflow integrations
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(await knex.schema.hasTable(TableName.MicrosoftTeamsIntegrations))) {
|
||||||
|
await knex.schema.createTable(TableName.MicrosoftTeamsIntegrations, (table) => {
|
||||||
|
table.uuid("id", { primaryKey: true }).notNullable();
|
||||||
|
table.foreign("id").references("id").inTable(TableName.WorkflowIntegrations).onDelete("CASCADE"); // the ID itself is the workflow integration ID
|
||||||
|
|
||||||
|
table.string("internalTeamsAppId").nullable();
|
||||||
|
table.string("tenantId").notNullable();
|
||||||
|
table.binary("encryptedAccessToken").nullable();
|
||||||
|
table.binary("encryptedBotAccessToken").nullable();
|
||||||
|
|
||||||
|
table.timestamp("accessTokenExpiresAt").nullable();
|
||||||
|
table.timestamp("botAccessTokenExpiresAt").nullable();
|
||||||
|
|
||||||
|
table.timestamps(true, true, true);
|
||||||
|
});
|
||||||
|
|
||||||
|
await createOnUpdateTrigger(knex, TableName.MicrosoftTeamsIntegrations);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(await knex.schema.hasTable(TableName.ProjectMicrosoftTeamsConfigs))) {
|
||||||
|
await knex.schema.createTable(TableName.ProjectMicrosoftTeamsConfigs, (tb) => {
|
||||||
|
tb.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
|
||||||
|
tb.string("projectId").notNullable().unique();
|
||||||
|
tb.foreign("projectId").references("id").inTable(TableName.Project).onDelete("CASCADE");
|
||||||
|
tb.uuid("microsoftTeamsIntegrationId").notNullable();
|
||||||
|
tb.foreign("microsoftTeamsIntegrationId")
|
||||||
|
.references("id")
|
||||||
|
.inTable(TableName.MicrosoftTeamsIntegrations)
|
||||||
|
.onDelete("CASCADE");
|
||||||
|
tb.boolean("isAccessRequestNotificationEnabled").notNullable().defaultTo(false);
|
||||||
|
tb.boolean("isSecretRequestNotificationEnabled").notNullable().defaultTo(false);
|
||||||
|
|
||||||
|
tb.jsonb("accessRequestChannels").notNullable(); // {teamId: string, channelIds: string[]}
|
||||||
|
tb.jsonb("secretRequestChannels").notNullable(); // {teamId: string, channelIds: string[]}
|
||||||
|
tb.timestamps(true, true, true);
|
||||||
|
});
|
||||||
|
|
||||||
|
await createOnUpdateTrigger(knex, TableName.ProjectMicrosoftTeamsConfigs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function down(knex: Knex): Promise<void> {
|
||||||
|
const hasEncryptedMicrosoftTeamsClientIdColumn = await knex.schema.hasColumn(
|
||||||
|
TableName.SuperAdmin,
|
||||||
|
"encryptedMicrosoftTeamsAppId"
|
||||||
|
);
|
||||||
|
const hasEncryptedMicrosoftTeamsClientSecret = await knex.schema.hasColumn(
|
||||||
|
TableName.SuperAdmin,
|
||||||
|
"encryptedMicrosoftTeamsClientSecret"
|
||||||
|
);
|
||||||
|
const hasEncryptedMicrosoftTeamsBotId = await knex.schema.hasColumn(
|
||||||
|
TableName.SuperAdmin,
|
||||||
|
"encryptedMicrosoftTeamsBotId"
|
||||||
|
);
|
||||||
|
|
||||||
|
if (
|
||||||
|
hasEncryptedMicrosoftTeamsClientIdColumn ||
|
||||||
|
hasEncryptedMicrosoftTeamsClientSecret ||
|
||||||
|
hasEncryptedMicrosoftTeamsBotId
|
||||||
|
) {
|
||||||
|
await knex.schema.alterTable(TableName.SuperAdmin, (table) => {
|
||||||
|
if (hasEncryptedMicrosoftTeamsClientIdColumn) {
|
||||||
|
table.dropColumn("encryptedMicrosoftTeamsAppId");
|
||||||
|
}
|
||||||
|
if (hasEncryptedMicrosoftTeamsClientSecret) {
|
||||||
|
table.dropColumn("encryptedMicrosoftTeamsClientSecret");
|
||||||
|
}
|
||||||
|
if (hasEncryptedMicrosoftTeamsBotId) {
|
||||||
|
table.dropColumn("encryptedMicrosoftTeamsBotId");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (await knex.schema.hasColumn(TableName.WorkflowIntegrations, "status")) {
|
||||||
|
await knex.schema.alterTable(TableName.WorkflowIntegrations, (table) => {
|
||||||
|
table.dropColumn("status");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (await knex.schema.hasTable(TableName.ProjectMicrosoftTeamsConfigs)) {
|
||||||
|
await knex.schema.dropTableIfExists(TableName.ProjectMicrosoftTeamsConfigs);
|
||||||
|
await dropOnUpdateTrigger(knex, TableName.ProjectMicrosoftTeamsConfigs);
|
||||||
|
}
|
||||||
|
if (await knex.schema.hasTable(TableName.MicrosoftTeamsIntegrations)) {
|
||||||
|
await knex.schema.dropTableIfExists(TableName.MicrosoftTeamsIntegrations);
|
||||||
|
await dropOnUpdateTrigger(knex, TableName.MicrosoftTeamsIntegrations);
|
||||||
|
}
|
||||||
|
}
|
@@ -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");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
55
backend/src/db/migrations/20250428173025_ssh-host-groups.ts
Normal file
55
backend/src/db/migrations/20250428173025_ssh-host-groups.ts
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
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.SshHostGroup))) {
|
||||||
|
await knex.schema.createTable(TableName.SshHostGroup, (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("name").notNullable();
|
||||||
|
t.unique(["projectId", "name"]);
|
||||||
|
});
|
||||||
|
await createOnUpdateTrigger(knex, TableName.SshHostGroup);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(await knex.schema.hasTable(TableName.SshHostGroupMembership))) {
|
||||||
|
await knex.schema.createTable(TableName.SshHostGroupMembership, (t) => {
|
||||||
|
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
|
||||||
|
t.timestamps(true, true, true);
|
||||||
|
t.uuid("sshHostGroupId").notNullable();
|
||||||
|
t.foreign("sshHostGroupId").references("id").inTable(TableName.SshHostGroup).onDelete("CASCADE");
|
||||||
|
t.uuid("sshHostId").notNullable();
|
||||||
|
t.foreign("sshHostId").references("id").inTable(TableName.SshHost).onDelete("CASCADE");
|
||||||
|
t.unique(["sshHostGroupId", "sshHostId"]);
|
||||||
|
});
|
||||||
|
await createOnUpdateTrigger(knex, TableName.SshHostGroupMembership);
|
||||||
|
}
|
||||||
|
|
||||||
|
const hasGroupColumn = await knex.schema.hasColumn(TableName.SshHostLoginUser, "sshHostGroupId");
|
||||||
|
if (!hasGroupColumn) {
|
||||||
|
await knex.schema.alterTable(TableName.SshHostLoginUser, (t) => {
|
||||||
|
t.uuid("sshHostGroupId").nullable();
|
||||||
|
t.foreign("sshHostGroupId").references("id").inTable(TableName.SshHostGroup).onDelete("CASCADE");
|
||||||
|
t.uuid("sshHostId").nullable().alter();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function down(knex: Knex): Promise<void> {
|
||||||
|
const hasGroupColumn = await knex.schema.hasColumn(TableName.SshHostLoginUser, "sshHostGroupId");
|
||||||
|
if (hasGroupColumn) {
|
||||||
|
await knex.schema.alterTable(TableName.SshHostLoginUser, (t) => {
|
||||||
|
t.dropColumn("sshHostGroupId");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
await knex.schema.dropTableIfExists(TableName.SshHostGroupMembership);
|
||||||
|
await dropOnUpdateTrigger(knex, TableName.SshHostGroupMembership);
|
||||||
|
|
||||||
|
await knex.schema.dropTableIfExists(TableName.SshHostGroup);
|
||||||
|
await dropOnUpdateTrigger(knex, TableName.SshHostGroup);
|
||||||
|
}
|
@@ -0,0 +1,33 @@
|
|||||||
|
import { Knex } from "knex";
|
||||||
|
|
||||||
|
import { TableName } from "../schemas";
|
||||||
|
|
||||||
|
export async function up(knex: Knex): Promise<void> {
|
||||||
|
if (!(await knex.schema.hasColumn(TableName.CertificateBody, "encryptedCertificateChain"))) {
|
||||||
|
await knex.schema.alterTable(TableName.CertificateBody, (t) => {
|
||||||
|
t.binary("encryptedCertificateChain").nullable();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(await knex.schema.hasTable(TableName.CertificateSecret))) {
|
||||||
|
await knex.schema.createTable(TableName.CertificateSecret, (t) => {
|
||||||
|
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
|
||||||
|
t.timestamps(true, true, true);
|
||||||
|
t.uuid("certId").notNullable().unique();
|
||||||
|
t.foreign("certId").references("id").inTable(TableName.Certificate).onDelete("CASCADE");
|
||||||
|
t.binary("encryptedPrivateKey").notNullable();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function down(knex: Knex): Promise<void> {
|
||||||
|
if (await knex.schema.hasTable(TableName.CertificateSecret)) {
|
||||||
|
await knex.schema.dropTable(TableName.CertificateSecret);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (await knex.schema.hasColumn(TableName.CertificateBody, "encryptedCertificateChain")) {
|
||||||
|
await knex.schema.alterTable(TableName.CertificateBody, (t) => {
|
||||||
|
t.dropColumn("encryptedCertificateChain");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,22 @@
|
|||||||
|
import { Knex } from "knex";
|
||||||
|
|
||||||
|
import { TableName } from "../schemas";
|
||||||
|
|
||||||
|
export async function up(knex: Knex): Promise<void> {
|
||||||
|
if (!(await knex.schema.hasColumn(TableName.SshHostLoginUserMapping, "groupId"))) {
|
||||||
|
await knex.schema.alterTable(TableName.SshHostLoginUserMapping, (t) => {
|
||||||
|
t.uuid("groupId").nullable();
|
||||||
|
t.foreign("groupId").references("id").inTable(TableName.Groups).onDelete("CASCADE");
|
||||||
|
t.unique(["sshHostLoginUserId", "groupId"]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function down(knex: Knex): Promise<void> {
|
||||||
|
if (await knex.schema.hasColumn(TableName.SshHostLoginUserMapping, "groupId")) {
|
||||||
|
await knex.schema.alterTable(TableName.SshHostLoginUserMapping, (t) => {
|
||||||
|
t.dropUnique(["sshHostLoginUserId", "groupId"]);
|
||||||
|
t.dropColumn("groupId");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,22 @@
|
|||||||
|
import { Knex } from "knex";
|
||||||
|
|
||||||
|
import { ProjectType, TableName } from "../schemas";
|
||||||
|
|
||||||
|
export async function up(knex: Knex): Promise<void> {
|
||||||
|
if (!(await knex.schema.hasColumn(TableName.ProjectTemplates, "type"))) {
|
||||||
|
await knex.schema.alterTable(TableName.ProjectTemplates, (t) => {
|
||||||
|
// defaulting to sm for migration to set existing, new ones will always be specified on creation
|
||||||
|
t.string("type").defaultTo(ProjectType.SecretManager).notNullable();
|
||||||
|
t.jsonb("environments").nullable().alter();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function down(knex: Knex): Promise<void> {
|
||||||
|
if (await knex.schema.hasColumn(TableName.ProjectTemplates, "type")) {
|
||||||
|
await knex.schema.alterTable(TableName.ProjectTemplates, (t) => {
|
||||||
|
t.dropColumn("type");
|
||||||
|
// not reverting nullable environments
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,39 @@
|
|||||||
|
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.IdentityLdapAuth))) {
|
||||||
|
await knex.schema.createTable(TableName.IdentityLdapAuth, (t) => {
|
||||||
|
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
|
||||||
|
|
||||||
|
t.bigInteger("accessTokenTTL").defaultTo(7200).notNullable();
|
||||||
|
t.bigInteger("accessTokenMaxTTL").defaultTo(7200).notNullable();
|
||||||
|
t.bigInteger("accessTokenNumUsesLimit").defaultTo(0).notNullable();
|
||||||
|
t.jsonb("accessTokenTrustedIps").notNullable();
|
||||||
|
|
||||||
|
t.uuid("identityId").notNullable().unique();
|
||||||
|
t.foreign("identityId").references("id").inTable(TableName.Identity).onDelete("CASCADE");
|
||||||
|
|
||||||
|
t.binary("encryptedBindDN").notNullable();
|
||||||
|
t.binary("encryptedBindPass").notNullable();
|
||||||
|
t.binary("encryptedLdapCaCertificate").nullable();
|
||||||
|
|
||||||
|
t.string("url").notNullable();
|
||||||
|
t.string("searchBase").notNullable();
|
||||||
|
t.string("searchFilter").notNullable();
|
||||||
|
|
||||||
|
t.jsonb("allowedFields").nullable();
|
||||||
|
|
||||||
|
t.timestamps(true, true, true);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
await createOnUpdateTrigger(knex, TableName.IdentityLdapAuth);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function down(knex: Knex): Promise<void> {
|
||||||
|
await knex.schema.dropTableIfExists(TableName.IdentityLdapAuth);
|
||||||
|
await dropOnUpdateTrigger(knex, TableName.IdentityLdapAuth);
|
||||||
|
}
|
@@ -14,7 +14,8 @@ export const CertificateBodiesSchema = z.object({
|
|||||||
createdAt: z.date(),
|
createdAt: z.date(),
|
||||||
updatedAt: z.date(),
|
updatedAt: z.date(),
|
||||||
certId: z.string().uuid(),
|
certId: z.string().uuid(),
|
||||||
encryptedCertificate: zodBuffer
|
encryptedCertificate: zodBuffer,
|
||||||
|
encryptedCertificateChain: zodBuffer.nullable().optional()
|
||||||
});
|
});
|
||||||
|
|
||||||
export type TCertificateBodies = z.infer<typeof CertificateBodiesSchema>;
|
export type TCertificateBodies = z.infer<typeof CertificateBodiesSchema>;
|
||||||
|
@@ -5,6 +5,8 @@
|
|||||||
|
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { zodBuffer } from "@app/lib/zod";
|
||||||
|
|
||||||
import { TImmutableDBKeys } from "./models";
|
import { TImmutableDBKeys } from "./models";
|
||||||
|
|
||||||
export const CertificateSecretsSchema = z.object({
|
export const CertificateSecretsSchema = z.object({
|
||||||
@@ -12,8 +14,7 @@ export const CertificateSecretsSchema = z.object({
|
|||||||
createdAt: z.date(),
|
createdAt: z.date(),
|
||||||
updatedAt: z.date(),
|
updatedAt: z.date(),
|
||||||
certId: z.string().uuid(),
|
certId: z.string().uuid(),
|
||||||
pk: z.string(),
|
encryptedPrivateKey: zodBuffer
|
||||||
sk: z.string()
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export type TCertificateSecrets = z.infer<typeof CertificateSecretsSchema>;
|
export type TCertificateSecrets = z.infer<typeof CertificateSecretsSchema>;
|
||||||
|
32
backend/src/db/schemas/identity-ldap-auths.ts
Normal file
32
backend/src/db/schemas/identity-ldap-auths.ts
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
// 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 IdentityLdapAuthsSchema = z.object({
|
||||||
|
id: z.string().uuid(),
|
||||||
|
accessTokenTTL: z.coerce.number().default(7200),
|
||||||
|
accessTokenMaxTTL: z.coerce.number().default(7200),
|
||||||
|
accessTokenNumUsesLimit: z.coerce.number().default(0),
|
||||||
|
accessTokenTrustedIps: z.unknown(),
|
||||||
|
identityId: z.string().uuid(),
|
||||||
|
encryptedBindDN: zodBuffer,
|
||||||
|
encryptedBindPass: zodBuffer,
|
||||||
|
encryptedLdapCaCertificate: zodBuffer.nullable().optional(),
|
||||||
|
url: z.string(),
|
||||||
|
searchBase: z.string(),
|
||||||
|
searchFilter: z.string(),
|
||||||
|
allowedFields: z.unknown().nullable().optional(),
|
||||||
|
createdAt: z.date(),
|
||||||
|
updatedAt: z.date()
|
||||||
|
});
|
||||||
|
|
||||||
|
export type TIdentityLdapAuths = z.infer<typeof IdentityLdapAuthsSchema>;
|
||||||
|
export type TIdentityLdapAuthsInsert = Omit<z.input<typeof IdentityLdapAuthsSchema>, TImmutableDBKeys>;
|
||||||
|
export type TIdentityLdapAuthsUpdate = Partial<Omit<z.input<typeof IdentityLdapAuthsSchema>, TImmutableDBKeys>>;
|
@@ -58,6 +58,7 @@ export * from "./kms-keys";
|
|||||||
export * from "./kms-root-config";
|
export * from "./kms-root-config";
|
||||||
export * from "./ldap-configs";
|
export * from "./ldap-configs";
|
||||||
export * from "./ldap-group-maps";
|
export * from "./ldap-group-maps";
|
||||||
|
export * from "./microsoft-teams-integrations";
|
||||||
export * from "./models";
|
export * from "./models";
|
||||||
export * from "./oidc-configs";
|
export * from "./oidc-configs";
|
||||||
export * from "./org-bots";
|
export * from "./org-bots";
|
||||||
@@ -127,6 +128,8 @@ export * from "./ssh-certificate-authority-secrets";
|
|||||||
export * from "./ssh-certificate-bodies";
|
export * from "./ssh-certificate-bodies";
|
||||||
export * from "./ssh-certificate-templates";
|
export * from "./ssh-certificate-templates";
|
||||||
export * from "./ssh-certificates";
|
export * from "./ssh-certificates";
|
||||||
|
export * from "./ssh-host-group-memberships";
|
||||||
|
export * from "./ssh-host-groups";
|
||||||
export * from "./ssh-host-login-user-mappings";
|
export * from "./ssh-host-login-user-mappings";
|
||||||
export * from "./ssh-host-login-users";
|
export * from "./ssh-host-login-users";
|
||||||
export * from "./ssh-hosts";
|
export * from "./ssh-hosts";
|
||||||
|
31
backend/src/db/schemas/microsoft-teams-integrations.ts
Normal file
31
backend/src/db/schemas/microsoft-teams-integrations.ts
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
// 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 MicrosoftTeamsIntegrationsSchema = z.object({
|
||||||
|
id: z.string().uuid(),
|
||||||
|
internalTeamsAppId: z.string().nullable().optional(),
|
||||||
|
tenantId: z.string(),
|
||||||
|
encryptedAccessToken: zodBuffer.nullable().optional(),
|
||||||
|
encryptedBotAccessToken: zodBuffer.nullable().optional(),
|
||||||
|
accessTokenExpiresAt: z.date().nullable().optional(),
|
||||||
|
botAccessTokenExpiresAt: z.date().nullable().optional(),
|
||||||
|
createdAt: z.date(),
|
||||||
|
updatedAt: z.date()
|
||||||
|
});
|
||||||
|
|
||||||
|
export type TMicrosoftTeamsIntegrations = z.infer<typeof MicrosoftTeamsIntegrationsSchema>;
|
||||||
|
export type TMicrosoftTeamsIntegrationsInsert = Omit<
|
||||||
|
z.input<typeof MicrosoftTeamsIntegrationsSchema>,
|
||||||
|
TImmutableDBKeys
|
||||||
|
>;
|
||||||
|
export type TMicrosoftTeamsIntegrationsUpdate = Partial<
|
||||||
|
Omit<z.input<typeof MicrosoftTeamsIntegrationsSchema>, TImmutableDBKeys>
|
||||||
|
>;
|
@@ -2,6 +2,8 @@ import { z } from "zod";
|
|||||||
|
|
||||||
export enum TableName {
|
export enum TableName {
|
||||||
Users = "users",
|
Users = "users",
|
||||||
|
SshHostGroup = "ssh_host_groups",
|
||||||
|
SshHostGroupMembership = "ssh_host_group_memberships",
|
||||||
SshHost = "ssh_hosts",
|
SshHost = "ssh_hosts",
|
||||||
SshHostLoginUser = "ssh_host_login_users",
|
SshHostLoginUser = "ssh_host_login_users",
|
||||||
SshHostLoginUserMapping = "ssh_host_login_user_mappings",
|
SshHostLoginUserMapping = "ssh_host_login_user_mappings",
|
||||||
@@ -78,6 +80,7 @@ export enum TableName {
|
|||||||
IdentityAwsAuth = "identity_aws_auths",
|
IdentityAwsAuth = "identity_aws_auths",
|
||||||
IdentityOidcAuth = "identity_oidc_auths",
|
IdentityOidcAuth = "identity_oidc_auths",
|
||||||
IdentityJwtAuth = "identity_jwt_auths",
|
IdentityJwtAuth = "identity_jwt_auths",
|
||||||
|
IdentityLdapAuth = "identity_ldap_auths",
|
||||||
IdentityOrgMembership = "identity_org_memberships",
|
IdentityOrgMembership = "identity_org_memberships",
|
||||||
IdentityProjectMembership = "identity_project_memberships",
|
IdentityProjectMembership = "identity_project_memberships",
|
||||||
IdentityProjectMembershipRole = "identity_project_membership_role",
|
IdentityProjectMembershipRole = "identity_project_membership_role",
|
||||||
@@ -147,6 +150,8 @@ export enum TableName {
|
|||||||
KmipClientCertificates = "kmip_client_certificates",
|
KmipClientCertificates = "kmip_client_certificates",
|
||||||
SecretRotationV2 = "secret_rotations_v2",
|
SecretRotationV2 = "secret_rotations_v2",
|
||||||
SecretRotationV2SecretMapping = "secret_rotation_v2_secret_mappings",
|
SecretRotationV2SecretMapping = "secret_rotation_v2_secret_mappings",
|
||||||
|
MicrosoftTeamsIntegrations = "microsoft_teams_integrations",
|
||||||
|
ProjectMicrosoftTeamsConfigs = "project_microsoft_teams_configs",
|
||||||
SecretReminderRecipients = "secret_reminder_recipients",
|
SecretReminderRecipients = "secret_reminder_recipients",
|
||||||
GithubOrgSyncConfig = "github_org_sync_configs"
|
GithubOrgSyncConfig = "github_org_sync_configs"
|
||||||
}
|
}
|
||||||
@@ -181,11 +186,16 @@ export enum OrgMembershipStatus {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export enum ProjectMembershipRole {
|
export enum ProjectMembershipRole {
|
||||||
|
// general
|
||||||
Admin = "admin",
|
Admin = "admin",
|
||||||
Member = "member",
|
Member = "member",
|
||||||
Custom = "custom",
|
Custom = "custom",
|
||||||
Viewer = "viewer",
|
Viewer = "viewer",
|
||||||
NoAccess = "no-access"
|
NoAccess = "no-access",
|
||||||
|
// ssh
|
||||||
|
SshHostBootstrapper = "ssh-host-bootstrapper",
|
||||||
|
// kms
|
||||||
|
KmsCryptographicOperator = "cryptographic-operator"
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum SecretEncryptionAlgo {
|
export enum SecretEncryptionAlgo {
|
||||||
@@ -223,7 +233,8 @@ export enum IdentityAuthMethod {
|
|||||||
AWS_AUTH = "aws-auth",
|
AWS_AUTH = "aws-auth",
|
||||||
AZURE_AUTH = "azure-auth",
|
AZURE_AUTH = "azure-auth",
|
||||||
OIDC_AUTH = "oidc-auth",
|
OIDC_AUTH = "oidc-auth",
|
||||||
JWT_AUTH = "jwt-auth"
|
JWT_AUTH = "jwt-auth",
|
||||||
|
LDAP_AUTH = "ldap-auth"
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum ProjectType {
|
export enum ProjectType {
|
||||||
|
@@ -23,12 +23,12 @@ export const OrganizationsSchema = z.object({
|
|||||||
defaultMembershipRole: z.string().default("member"),
|
defaultMembershipRole: z.string().default("member"),
|
||||||
enforceMfa: z.boolean().default(false),
|
enforceMfa: z.boolean().default(false),
|
||||||
selectedMfaMethod: z.string().nullable().optional(),
|
selectedMfaMethod: z.string().nullable().optional(),
|
||||||
secretShareSendToAnyone: z.boolean().default(true).nullable().optional(),
|
|
||||||
allowSecretSharingOutsideOrganization: z.boolean().default(true).nullable().optional(),
|
allowSecretSharingOutsideOrganization: z.boolean().default(true).nullable().optional(),
|
||||||
shouldUseNewPrivilegeSystem: z.boolean().default(true),
|
shouldUseNewPrivilegeSystem: z.boolean().default(true),
|
||||||
privilegeUpgradeInitiatedByUsername: z.string().nullable().optional(),
|
privilegeUpgradeInitiatedByUsername: z.string().nullable().optional(),
|
||||||
privilegeUpgradeInitiatedAt: z.date().nullable().optional(),
|
privilegeUpgradeInitiatedAt: z.date().nullable().optional(),
|
||||||
bypassOrgAuthEnabled: z.boolean().default(false)
|
bypassOrgAuthEnabled: z.boolean().default(false),
|
||||||
|
userTokenExpiration: z.string().nullable().optional()
|
||||||
});
|
});
|
||||||
|
|
||||||
export type TOrganizations = z.infer<typeof OrganizationsSchema>;
|
export type TOrganizations = z.infer<typeof OrganizationsSchema>;
|
||||||
|
29
backend/src/db/schemas/project-microsoft-teams-configs.ts
Normal file
29
backend/src/db/schemas/project-microsoft-teams-configs.ts
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
// 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 ProjectMicrosoftTeamsConfigsSchema = z.object({
|
||||||
|
id: z.string().uuid(),
|
||||||
|
projectId: z.string(),
|
||||||
|
microsoftTeamsIntegrationId: z.string().uuid(),
|
||||||
|
isAccessRequestNotificationEnabled: z.boolean().default(false),
|
||||||
|
isSecretRequestNotificationEnabled: z.boolean().default(false),
|
||||||
|
accessRequestChannels: z.unknown(),
|
||||||
|
secretRequestChannels: z.unknown(),
|
||||||
|
createdAt: z.date(),
|
||||||
|
updatedAt: z.date()
|
||||||
|
});
|
||||||
|
|
||||||
|
export type TProjectMicrosoftTeamsConfigs = z.infer<typeof ProjectMicrosoftTeamsConfigsSchema>;
|
||||||
|
export type TProjectMicrosoftTeamsConfigsInsert = Omit<
|
||||||
|
z.input<typeof ProjectMicrosoftTeamsConfigsSchema>,
|
||||||
|
TImmutableDBKeys
|
||||||
|
>;
|
||||||
|
export type TProjectMicrosoftTeamsConfigsUpdate = Partial<
|
||||||
|
Omit<z.input<typeof ProjectMicrosoftTeamsConfigsSchema>, TImmutableDBKeys>
|
||||||
|
>;
|
@@ -12,10 +12,11 @@ export const ProjectTemplatesSchema = z.object({
|
|||||||
name: z.string(),
|
name: z.string(),
|
||||||
description: z.string().nullable().optional(),
|
description: z.string().nullable().optional(),
|
||||||
roles: z.unknown(),
|
roles: z.unknown(),
|
||||||
environments: z.unknown(),
|
environments: z.unknown().nullable().optional(),
|
||||||
orgId: z.string().uuid(),
|
orgId: z.string().uuid(),
|
||||||
createdAt: z.date(),
|
createdAt: z.date(),
|
||||||
updatedAt: z.date()
|
updatedAt: z.date(),
|
||||||
|
type: z.string().default("secret-manager")
|
||||||
});
|
});
|
||||||
|
|
||||||
export type TProjectTemplates = z.infer<typeof ProjectTemplatesSchema>;
|
export type TProjectTemplates = z.infer<typeof ProjectTemplatesSchema>;
|
||||||
|
@@ -27,7 +27,7 @@ export const ProjectsSchema = z.object({
|
|||||||
description: z.string().nullable().optional(),
|
description: z.string().nullable().optional(),
|
||||||
type: z.string(),
|
type: z.string(),
|
||||||
enforceCapitalization: z.boolean().default(false),
|
enforceCapitalization: z.boolean().default(false),
|
||||||
hasDeleteProtection: z.boolean().default(true).nullable().optional()
|
hasDeleteProtection: z.boolean().default(false).nullable().optional()
|
||||||
});
|
});
|
||||||
|
|
||||||
export type TProjects = z.infer<typeof ProjectsSchema>;
|
export type TProjects = z.infer<typeof ProjectsSchema>;
|
||||||
|
22
backend/src/db/schemas/ssh-host-group-memberships.ts
Normal file
22
backend/src/db/schemas/ssh-host-group-memberships.ts
Normal 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 SshHostGroupMembershipsSchema = z.object({
|
||||||
|
id: z.string().uuid(),
|
||||||
|
createdAt: z.date(),
|
||||||
|
updatedAt: z.date(),
|
||||||
|
sshHostGroupId: z.string().uuid(),
|
||||||
|
sshHostId: z.string().uuid()
|
||||||
|
});
|
||||||
|
|
||||||
|
export type TSshHostGroupMemberships = z.infer<typeof SshHostGroupMembershipsSchema>;
|
||||||
|
export type TSshHostGroupMembershipsInsert = Omit<z.input<typeof SshHostGroupMembershipsSchema>, TImmutableDBKeys>;
|
||||||
|
export type TSshHostGroupMembershipsUpdate = Partial<
|
||||||
|
Omit<z.input<typeof SshHostGroupMembershipsSchema>, TImmutableDBKeys>
|
||||||
|
>;
|
20
backend/src/db/schemas/ssh-host-groups.ts
Normal file
20
backend/src/db/schemas/ssh-host-groups.ts
Normal 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 SshHostGroupsSchema = z.object({
|
||||||
|
id: z.string().uuid(),
|
||||||
|
createdAt: z.date(),
|
||||||
|
updatedAt: z.date(),
|
||||||
|
projectId: z.string(),
|
||||||
|
name: z.string()
|
||||||
|
});
|
||||||
|
|
||||||
|
export type TSshHostGroups = z.infer<typeof SshHostGroupsSchema>;
|
||||||
|
export type TSshHostGroupsInsert = Omit<z.input<typeof SshHostGroupsSchema>, TImmutableDBKeys>;
|
||||||
|
export type TSshHostGroupsUpdate = Partial<Omit<z.input<typeof SshHostGroupsSchema>, TImmutableDBKeys>>;
|
@@ -12,7 +12,8 @@ export const SshHostLoginUserMappingsSchema = z.object({
|
|||||||
createdAt: z.date(),
|
createdAt: z.date(),
|
||||||
updatedAt: z.date(),
|
updatedAt: z.date(),
|
||||||
sshHostLoginUserId: z.string().uuid(),
|
sshHostLoginUserId: z.string().uuid(),
|
||||||
userId: z.string().uuid().nullable().optional()
|
userId: z.string().uuid().nullable().optional(),
|
||||||
|
groupId: z.string().uuid().nullable().optional()
|
||||||
});
|
});
|
||||||
|
|
||||||
export type TSshHostLoginUserMappings = z.infer<typeof SshHostLoginUserMappingsSchema>;
|
export type TSshHostLoginUserMappings = z.infer<typeof SshHostLoginUserMappingsSchema>;
|
||||||
|
@@ -11,8 +11,9 @@ export const SshHostLoginUsersSchema = z.object({
|
|||||||
id: z.string().uuid(),
|
id: z.string().uuid(),
|
||||||
createdAt: z.date(),
|
createdAt: z.date(),
|
||||||
updatedAt: z.date(),
|
updatedAt: z.date(),
|
||||||
sshHostId: z.string().uuid(),
|
sshHostId: z.string().uuid().nullable().optional(),
|
||||||
loginUser: z.string()
|
loginUser: z.string(),
|
||||||
|
sshHostGroupId: z.string().uuid().nullable().optional()
|
||||||
});
|
});
|
||||||
|
|
||||||
export type TSshHostLoginUsers = z.infer<typeof SshHostLoginUsersSchema>;
|
export type TSshHostLoginUsers = z.infer<typeof SshHostLoginUsersSchema>;
|
||||||
|
@@ -26,7 +26,10 @@ export const SuperAdminSchema = z.object({
|
|||||||
encryptedSlackClientSecret: zodBuffer.nullable().optional(),
|
encryptedSlackClientSecret: zodBuffer.nullable().optional(),
|
||||||
authConsentContent: z.string().nullable().optional(),
|
authConsentContent: z.string().nullable().optional(),
|
||||||
pageFrameContent: z.string().nullable().optional(),
|
pageFrameContent: z.string().nullable().optional(),
|
||||||
adminIdentityIds: z.string().array().nullable().optional()
|
adminIdentityIds: z.string().array().nullable().optional(),
|
||||||
|
encryptedMicrosoftTeamsAppId: zodBuffer.nullable().optional(),
|
||||||
|
encryptedMicrosoftTeamsClientSecret: zodBuffer.nullable().optional(),
|
||||||
|
encryptedMicrosoftTeamsBotId: zodBuffer.nullable().optional()
|
||||||
});
|
});
|
||||||
|
|
||||||
export type TSuperAdmin = z.infer<typeof SuperAdminSchema>;
|
export type TSuperAdmin = z.infer<typeof SuperAdminSchema>;
|
||||||
|
@@ -14,7 +14,8 @@ export const WorkflowIntegrationsSchema = z.object({
|
|||||||
orgId: z.string().uuid(),
|
orgId: z.string().uuid(),
|
||||||
description: z.string().nullable().optional(),
|
description: z.string().nullable().optional(),
|
||||||
createdAt: z.date(),
|
createdAt: z.date(),
|
||||||
updatedAt: z.date()
|
updatedAt: z.date(),
|
||||||
|
status: z.string().default("installed")
|
||||||
});
|
});
|
||||||
|
|
||||||
export type TWorkflowIntegrations = z.infer<typeof WorkflowIntegrationsSchema>;
|
export type TWorkflowIntegrations = z.infer<typeof WorkflowIntegrationsSchema>;
|
||||||
|
@@ -2,6 +2,7 @@ import { z } from "zod";
|
|||||||
|
|
||||||
import { AccessApprovalRequestsReviewersSchema, AccessApprovalRequestsSchema, UsersSchema } from "@app/db/schemas";
|
import { AccessApprovalRequestsReviewersSchema, AccessApprovalRequestsSchema, UsersSchema } from "@app/db/schemas";
|
||||||
import { ApprovalStatus } from "@app/ee/services/access-approval-request/access-approval-request-types";
|
import { ApprovalStatus } from "@app/ee/services/access-approval-request/access-approval-request-types";
|
||||||
|
import { writeLimit } from "@app/server/config/rateLimiter";
|
||||||
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||||
import { AuthMode } from "@app/services/auth/auth-type";
|
import { AuthMode } from "@app/services/auth/auth-type";
|
||||||
|
|
||||||
@@ -18,6 +19,9 @@ export const registerAccessApprovalRequestRouter = async (server: FastifyZodProv
|
|||||||
server.route({
|
server.route({
|
||||||
url: "/",
|
url: "/",
|
||||||
method: "POST",
|
method: "POST",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
schema: {
|
schema: {
|
||||||
body: z.object({
|
body: z.object({
|
||||||
permissions: z.any().array(),
|
permissions: z.any().array(),
|
||||||
|
@@ -34,6 +34,7 @@ import { registerSnapshotRouter } from "./snapshot-router";
|
|||||||
import { registerSshCaRouter } from "./ssh-certificate-authority-router";
|
import { registerSshCaRouter } from "./ssh-certificate-authority-router";
|
||||||
import { registerSshCertRouter } from "./ssh-certificate-router";
|
import { registerSshCertRouter } from "./ssh-certificate-router";
|
||||||
import { registerSshCertificateTemplateRouter } from "./ssh-certificate-template-router";
|
import { registerSshCertificateTemplateRouter } from "./ssh-certificate-template-router";
|
||||||
|
import { registerSshHostGroupRouter } from "./ssh-host-group-router";
|
||||||
import { registerSshHostRouter } from "./ssh-host-router";
|
import { registerSshHostRouter } from "./ssh-host-router";
|
||||||
import { registerTrustedIpRouter } from "./trusted-ip-router";
|
import { registerTrustedIpRouter } from "./trusted-ip-router";
|
||||||
import { registerUserAdditionalPrivilegeRouter } from "./user-additional-privilege-router";
|
import { registerUserAdditionalPrivilegeRouter } from "./user-additional-privilege-router";
|
||||||
@@ -88,6 +89,7 @@ export const registerV1EERoutes = async (server: FastifyZodProvider) => {
|
|||||||
await sshRouter.register(registerSshCertRouter, { prefix: "/certificates" });
|
await sshRouter.register(registerSshCertRouter, { prefix: "/certificates" });
|
||||||
await sshRouter.register(registerSshCertificateTemplateRouter, { prefix: "/certificate-templates" });
|
await sshRouter.register(registerSshCertificateTemplateRouter, { prefix: "/certificate-templates" });
|
||||||
await sshRouter.register(registerSshHostRouter, { prefix: "/hosts" });
|
await sshRouter.register(registerSshHostRouter, { prefix: "/hosts" });
|
||||||
|
await sshRouter.register(registerSshHostGroupRouter, { prefix: "/host-groups" });
|
||||||
},
|
},
|
||||||
{ prefix: "/ssh" }
|
{ prefix: "/ssh" }
|
||||||
);
|
);
|
||||||
|
@@ -98,6 +98,9 @@ export const registerLdapRouter = async (server: FastifyZodProvider) => {
|
|||||||
server.route({
|
server.route({
|
||||||
url: "/login",
|
url: "/login",
|
||||||
method: "POST",
|
method: "POST",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
schema: {
|
schema: {
|
||||||
body: z.object({
|
body: z.object({
|
||||||
organizationSlug: z.string().trim()
|
organizationSlug: z.string().trim()
|
||||||
|
@@ -1,9 +1,8 @@
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import { ProjectMembershipRole, ProjectTemplatesSchema } from "@app/db/schemas";
|
import { ProjectMembershipRole, ProjectTemplatesSchema, ProjectType } from "@app/db/schemas";
|
||||||
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
|
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
|
||||||
import { ProjectPermissionV2Schema } from "@app/ee/services/permission/project-permission";
|
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 { isInfisicalProjectTemplate } from "@app/ee/services/project-template/project-template-fns";
|
||||||
import { ApiDocsTags, ProjectTemplates } from "@app/lib/api-docs";
|
import { ApiDocsTags, ProjectTemplates } from "@app/lib/api-docs";
|
||||||
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
||||||
@@ -35,6 +34,7 @@ const SanitizedProjectTemplateSchema = ProjectTemplatesSchema.extend({
|
|||||||
position: z.number().min(1)
|
position: z.number().min(1)
|
||||||
})
|
})
|
||||||
.array()
|
.array()
|
||||||
|
.nullable()
|
||||||
});
|
});
|
||||||
|
|
||||||
const ProjectTemplateRolesSchema = z
|
const ProjectTemplateRolesSchema = z
|
||||||
@@ -104,6 +104,9 @@ export const registerProjectTemplateRouter = async (server: FastifyZodProvider)
|
|||||||
hide: false,
|
hide: false,
|
||||||
tags: [ApiDocsTags.ProjectTemplates],
|
tags: [ApiDocsTags.ProjectTemplates],
|
||||||
description: "List project templates for the current organization.",
|
description: "List project templates for the current organization.",
|
||||||
|
querystring: z.object({
|
||||||
|
type: z.nativeEnum(ProjectType).optional().describe(ProjectTemplates.LIST.type)
|
||||||
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
projectTemplates: SanitizedProjectTemplateSchema.array()
|
projectTemplates: SanitizedProjectTemplateSchema.array()
|
||||||
@@ -112,7 +115,8 @@ export const registerProjectTemplateRouter = async (server: FastifyZodProvider)
|
|||||||
},
|
},
|
||||||
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
handler: async (req) => {
|
handler: async (req) => {
|
||||||
const projectTemplates = await server.services.projectTemplate.listProjectTemplatesByOrg(req.permission);
|
const { type } = req.query;
|
||||||
|
const projectTemplates = await server.services.projectTemplate.listProjectTemplatesByOrg(req.permission, type);
|
||||||
|
|
||||||
const auditTemplates = projectTemplates.filter((template) => !isInfisicalProjectTemplate(template.name));
|
const auditTemplates = projectTemplates.filter((template) => !isInfisicalProjectTemplate(template.name));
|
||||||
|
|
||||||
@@ -184,6 +188,7 @@ export const registerProjectTemplateRouter = async (server: FastifyZodProvider)
|
|||||||
tags: [ApiDocsTags.ProjectTemplates],
|
tags: [ApiDocsTags.ProjectTemplates],
|
||||||
description: "Create a project template.",
|
description: "Create a project template.",
|
||||||
body: z.object({
|
body: z.object({
|
||||||
|
type: z.nativeEnum(ProjectType).describe(ProjectTemplates.CREATE.type),
|
||||||
name: slugSchema({ field: "name" })
|
name: slugSchema({ field: "name" })
|
||||||
.refine((val) => !isInfisicalProjectTemplate(val), {
|
.refine((val) => !isInfisicalProjectTemplate(val), {
|
||||||
message: `The requested project template name is reserved.`
|
message: `The requested project template name is reserved.`
|
||||||
@@ -191,9 +196,7 @@ export const registerProjectTemplateRouter = async (server: FastifyZodProvider)
|
|||||||
.describe(ProjectTemplates.CREATE.name),
|
.describe(ProjectTemplates.CREATE.name),
|
||||||
description: z.string().max(256).trim().optional().describe(ProjectTemplates.CREATE.description),
|
description: z.string().max(256).trim().optional().describe(ProjectTemplates.CREATE.description),
|
||||||
roles: ProjectTemplateRolesSchema.default([]).describe(ProjectTemplates.CREATE.roles),
|
roles: ProjectTemplateRolesSchema.default([]).describe(ProjectTemplates.CREATE.roles),
|
||||||
environments: ProjectTemplateEnvironmentsSchema.default(ProjectTemplateDefaultEnvironments).describe(
|
environments: ProjectTemplateEnvironmentsSchema.describe(ProjectTemplates.CREATE.environments).optional()
|
||||||
ProjectTemplates.CREATE.environments
|
|
||||||
)
|
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
|
@@ -166,6 +166,9 @@ export const registerSamlRouter = async (server: FastifyZodProvider) => {
|
|||||||
server.route({
|
server.route({
|
||||||
url: "/redirect/saml2/organizations/:orgSlug",
|
url: "/redirect/saml2/organizations/:orgSlug",
|
||||||
method: "GET",
|
method: "GET",
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
schema: {
|
schema: {
|
||||||
params: z.object({
|
params: z.object({
|
||||||
orgSlug: z.string().trim()
|
orgSlug: z.string().trim()
|
||||||
@@ -192,6 +195,9 @@ export const registerSamlRouter = async (server: FastifyZodProvider) => {
|
|||||||
server.route({
|
server.route({
|
||||||
url: "/redirect/saml2/:samlConfigId",
|
url: "/redirect/saml2/:samlConfigId",
|
||||||
method: "GET",
|
method: "GET",
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
schema: {
|
schema: {
|
||||||
params: z.object({
|
params: z.object({
|
||||||
samlConfigId: z.string().trim()
|
samlConfigId: z.string().trim()
|
||||||
@@ -218,6 +224,9 @@ export const registerSamlRouter = async (server: FastifyZodProvider) => {
|
|||||||
server.route({
|
server.route({
|
||||||
url: "/saml2/:samlConfigId",
|
url: "/saml2/:samlConfigId",
|
||||||
method: "POST",
|
method: "POST",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
schema: {
|
schema: {
|
||||||
params: z.object({
|
params: z.object({
|
||||||
samlConfigId: z.string().trim()
|
samlConfigId: z.string().trim()
|
||||||
|
@@ -196,6 +196,9 @@ export const registerScimRouter = async (server: FastifyZodProvider) => {
|
|||||||
server.route({
|
server.route({
|
||||||
url: "/Users",
|
url: "/Users",
|
||||||
method: "POST",
|
method: "POST",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
schema: {
|
schema: {
|
||||||
body: z.object({
|
body: z.object({
|
||||||
schemas: z.array(z.string()),
|
schemas: z.array(z.string()),
|
||||||
|
@@ -1,11 +1,11 @@
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import { GitAppOrgSchema, SecretScanningGitRisksSchema } from "@app/db/schemas";
|
import { GitAppOrgSchema, SecretScanningGitRisksSchema } from "@app/db/schemas";
|
||||||
|
import { canUseSecretScanning } from "@app/ee/services/secret-scanning/secret-scanning-fns";
|
||||||
import {
|
import {
|
||||||
SecretScanningResolvedStatus,
|
SecretScanningResolvedStatus,
|
||||||
SecretScanningRiskStatus
|
SecretScanningRiskStatus
|
||||||
} from "@app/ee/services/secret-scanning/secret-scanning-types";
|
} from "@app/ee/services/secret-scanning/secret-scanning-types";
|
||||||
import { getConfig } from "@app/lib/config/env";
|
|
||||||
import { BadRequestError } from "@app/lib/errors";
|
import { BadRequestError } from "@app/lib/errors";
|
||||||
import { OrderByDirection } from "@app/lib/types";
|
import { OrderByDirection } from "@app/lib/types";
|
||||||
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
||||||
@@ -23,14 +23,14 @@ export const registerSecretScanningRouter = async (server: FastifyZodProvider) =
|
|||||||
body: z.object({ organizationId: z.string().trim() }),
|
body: z.object({ organizationId: z.string().trim() }),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
sessionId: z.string()
|
sessionId: z.string(),
|
||||||
|
gitAppSlug: z.string()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onRequest: verifyAuth([AuthMode.JWT]),
|
onRequest: verifyAuth([AuthMode.JWT]),
|
||||||
handler: async (req) => {
|
handler: async (req) => {
|
||||||
const appCfg = getConfig();
|
if (!canUseSecretScanning(req.auth.orgId)) {
|
||||||
if (!appCfg.SECRET_SCANNING_ORG_WHITELIST?.includes(req.auth.orgId)) {
|
|
||||||
throw new BadRequestError({
|
throw new BadRequestError({
|
||||||
message: "Secret scanning is temporarily unavailable."
|
message: "Secret scanning is temporarily unavailable."
|
||||||
});
|
});
|
||||||
|
360
backend/src/ee/routes/v1/ssh-host-group-router.ts
Normal file
360
backend/src/ee/routes/v1/ssh-host-group-router.ts
Normal file
@@ -0,0 +1,360 @@
|
|||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
|
||||||
|
import { loginMappingSchema, sanitizedSshHost } from "@app/ee/services/ssh-host/ssh-host-schema";
|
||||||
|
import { sanitizedSshHostGroup } from "@app/ee/services/ssh-host-group/ssh-host-group-schema";
|
||||||
|
import { EHostGroupMembershipFilter } from "@app/ee/services/ssh-host-group/ssh-host-group-types";
|
||||||
|
import { ApiDocsTags, SSH_HOST_GROUPS } 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";
|
||||||
|
import { AuthMode } from "@app/services/auth/auth-type";
|
||||||
|
|
||||||
|
export const registerSshHostGroupRouter = async (server: FastifyZodProvider) => {
|
||||||
|
server.route({
|
||||||
|
method: "GET",
|
||||||
|
url: "/:sshHostGroupId",
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
hide: false,
|
||||||
|
tags: [ApiDocsTags.SshHostGroups],
|
||||||
|
description: "Get SSH Host Group",
|
||||||
|
params: z.object({
|
||||||
|
sshHostGroupId: z.string().describe(SSH_HOST_GROUPS.GET.sshHostGroupId)
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: sanitizedSshHostGroup.extend({
|
||||||
|
loginMappings: z.array(loginMappingSchema)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const sshHostGroup = await server.services.sshHostGroup.getSshHostGroup({
|
||||||
|
sshHostGroupId: req.params.sshHostGroupId,
|
||||||
|
actor: req.permission.type,
|
||||||
|
actorId: req.permission.id,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
|
actorOrgId: req.permission.orgId
|
||||||
|
});
|
||||||
|
|
||||||
|
await server.services.auditLog.createAuditLog({
|
||||||
|
...req.auditLogInfo,
|
||||||
|
projectId: sshHostGroup.projectId,
|
||||||
|
event: {
|
||||||
|
type: EventType.GET_SSH_HOST_GROUP,
|
||||||
|
metadata: {
|
||||||
|
sshHostGroupId: sshHostGroup.id,
|
||||||
|
name: sshHostGroup.name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return sshHostGroup;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: "POST",
|
||||||
|
url: "/",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
hide: false,
|
||||||
|
tags: [ApiDocsTags.SshHostGroups],
|
||||||
|
description: "Create SSH Host Group",
|
||||||
|
body: z.object({
|
||||||
|
projectId: z.string().describe(SSH_HOST_GROUPS.CREATE.projectId),
|
||||||
|
name: slugSchema({ min: 1, max: 64, field: "name" }).describe(SSH_HOST_GROUPS.CREATE.name),
|
||||||
|
loginMappings: z.array(loginMappingSchema).default([]).describe(SSH_HOST_GROUPS.CREATE.loginMappings)
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: sanitizedSshHostGroup.extend({
|
||||||
|
loginMappings: z.array(loginMappingSchema)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const sshHostGroup = await server.services.sshHostGroup.createSshHostGroup({
|
||||||
|
...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: sshHostGroup.projectId,
|
||||||
|
event: {
|
||||||
|
type: EventType.CREATE_SSH_HOST_GROUP,
|
||||||
|
metadata: {
|
||||||
|
sshHostGroupId: sshHostGroup.id,
|
||||||
|
name: sshHostGroup.name,
|
||||||
|
loginMappings: sshHostGroup.loginMappings
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return sshHostGroup;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: "PATCH",
|
||||||
|
url: "/:sshHostGroupId",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
|
schema: {
|
||||||
|
hide: false,
|
||||||
|
tags: [ApiDocsTags.SshHostGroups],
|
||||||
|
description: "Update SSH Host Group",
|
||||||
|
params: z.object({
|
||||||
|
sshHostGroupId: z.string().trim().describe(SSH_HOST_GROUPS.UPDATE.sshHostGroupId)
|
||||||
|
}),
|
||||||
|
body: z.object({
|
||||||
|
name: slugSchema({ min: 1, max: 64, field: "name" }).describe(SSH_HOST_GROUPS.UPDATE.name).optional(),
|
||||||
|
loginMappings: z.array(loginMappingSchema).optional().describe(SSH_HOST_GROUPS.UPDATE.loginMappings)
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: sanitizedSshHostGroup.extend({
|
||||||
|
loginMappings: z.array(loginMappingSchema)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handler: async (req) => {
|
||||||
|
const sshHostGroup = await server.services.sshHostGroup.updateSshHostGroup({
|
||||||
|
sshHostGroupId: req.params.sshHostGroupId,
|
||||||
|
...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: sshHostGroup.projectId,
|
||||||
|
event: {
|
||||||
|
type: EventType.UPDATE_SSH_HOST_GROUP,
|
||||||
|
metadata: {
|
||||||
|
sshHostGroupId: sshHostGroup.id,
|
||||||
|
name: sshHostGroup.name,
|
||||||
|
loginMappings: sshHostGroup.loginMappings
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return sshHostGroup;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: "DELETE",
|
||||||
|
url: "/:sshHostGroupId",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
hide: false,
|
||||||
|
tags: [ApiDocsTags.SshHostGroups],
|
||||||
|
description: "Delete SSH Host Group",
|
||||||
|
params: z.object({
|
||||||
|
sshHostGroupId: z.string().describe(SSH_HOST_GROUPS.DELETE.sshHostGroupId)
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: sanitizedSshHostGroup.extend({
|
||||||
|
loginMappings: z.array(loginMappingSchema)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const sshHostGroup = await server.services.sshHostGroup.deleteSshHostGroup({
|
||||||
|
sshHostGroupId: req.params.sshHostGroupId,
|
||||||
|
actor: req.permission.type,
|
||||||
|
actorId: req.permission.id,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
|
actorOrgId: req.permission.orgId
|
||||||
|
});
|
||||||
|
|
||||||
|
await server.services.auditLog.createAuditLog({
|
||||||
|
...req.auditLogInfo,
|
||||||
|
projectId: sshHostGroup.projectId,
|
||||||
|
event: {
|
||||||
|
type: EventType.DELETE_SSH_HOST_GROUP,
|
||||||
|
metadata: {
|
||||||
|
sshHostGroupId: sshHostGroup.id,
|
||||||
|
name: sshHostGroup.name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return sshHostGroup;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: "GET",
|
||||||
|
url: "/:sshHostGroupId/hosts",
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
hide: false,
|
||||||
|
tags: [ApiDocsTags.SshHostGroups],
|
||||||
|
description: "Get SSH Hosts in a Host Group",
|
||||||
|
params: z.object({
|
||||||
|
sshHostGroupId: z.string().describe(SSH_HOST_GROUPS.GET.sshHostGroupId)
|
||||||
|
}),
|
||||||
|
querystring: z.object({
|
||||||
|
filter: z.nativeEnum(EHostGroupMembershipFilter).optional().describe(SSH_HOST_GROUPS.GET.filter)
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
hosts: sanitizedSshHost
|
||||||
|
.pick({
|
||||||
|
id: true,
|
||||||
|
hostname: true,
|
||||||
|
alias: true
|
||||||
|
})
|
||||||
|
.merge(
|
||||||
|
z.object({
|
||||||
|
isPartOfGroup: z.boolean(),
|
||||||
|
joinedGroupAt: z.date().nullable()
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.array(),
|
||||||
|
totalCount: z.number()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const { sshHostGroup, hosts, totalCount } = await server.services.sshHostGroup.listSshHostGroupHosts({
|
||||||
|
sshHostGroupId: req.params.sshHostGroupId,
|
||||||
|
actor: req.permission.type,
|
||||||
|
actorId: req.permission.id,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
|
actorOrgId: req.permission.orgId,
|
||||||
|
...req.query
|
||||||
|
});
|
||||||
|
|
||||||
|
await server.services.auditLog.createAuditLog({
|
||||||
|
...req.auditLogInfo,
|
||||||
|
projectId: sshHostGroup.projectId,
|
||||||
|
event: {
|
||||||
|
type: EventType.GET_SSH_HOST_GROUP_HOSTS,
|
||||||
|
metadata: {
|
||||||
|
sshHostGroupId: req.params.sshHostGroupId,
|
||||||
|
name: sshHostGroup.name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return { hosts, totalCount };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: "POST",
|
||||||
|
url: "/:sshHostGroupId/hosts/:hostId",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
hide: false,
|
||||||
|
tags: [ApiDocsTags.SshHostGroups],
|
||||||
|
description: "Add an SSH Host to a Host Group",
|
||||||
|
params: z.object({
|
||||||
|
sshHostGroupId: z.string().describe(SSH_HOST_GROUPS.ADD_HOST.sshHostGroupId),
|
||||||
|
hostId: z.string().describe(SSH_HOST_GROUPS.ADD_HOST.hostId)
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: sanitizedSshHost.extend({
|
||||||
|
loginMappings: z.array(loginMappingSchema)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const { sshHostGroup, sshHost } = await server.services.sshHostGroup.addHostToSshHostGroup({
|
||||||
|
sshHostGroupId: req.params.sshHostGroupId,
|
||||||
|
hostId: req.params.hostId,
|
||||||
|
actor: req.permission.type,
|
||||||
|
actorId: req.permission.id,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
|
actorOrgId: req.permission.orgId
|
||||||
|
});
|
||||||
|
|
||||||
|
await server.services.auditLog.createAuditLog({
|
||||||
|
...req.auditLogInfo,
|
||||||
|
projectId: sshHost.projectId,
|
||||||
|
event: {
|
||||||
|
type: EventType.ADD_HOST_TO_SSH_HOST_GROUP,
|
||||||
|
metadata: {
|
||||||
|
sshHostGroupId: sshHostGroup.id,
|
||||||
|
sshHostId: sshHost.id,
|
||||||
|
hostname: sshHost.hostname
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return sshHost;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: "DELETE",
|
||||||
|
url: "/:sshHostGroupId/hosts/:hostId",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
hide: false,
|
||||||
|
tags: [ApiDocsTags.SshHostGroups],
|
||||||
|
description: "Remove an SSH Host from a Host Group",
|
||||||
|
params: z.object({
|
||||||
|
sshHostGroupId: z.string().describe(SSH_HOST_GROUPS.DELETE_HOST.sshHostGroupId),
|
||||||
|
hostId: z.string().describe(SSH_HOST_GROUPS.DELETE_HOST.hostId)
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: sanitizedSshHost.extend({
|
||||||
|
loginMappings: z.array(loginMappingSchema)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const { sshHostGroup, sshHost } = await server.services.sshHostGroup.removeHostFromSshHostGroup({
|
||||||
|
sshHostGroupId: req.params.sshHostGroupId,
|
||||||
|
hostId: req.params.hostId,
|
||||||
|
actor: req.permission.type,
|
||||||
|
actorId: req.permission.id,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
|
actorOrgId: req.permission.orgId
|
||||||
|
});
|
||||||
|
|
||||||
|
await server.services.auditLog.createAuditLog({
|
||||||
|
...req.auditLogInfo,
|
||||||
|
projectId: sshHost.projectId,
|
||||||
|
event: {
|
||||||
|
type: EventType.REMOVE_HOST_FROM_SSH_HOST_GROUP,
|
||||||
|
metadata: {
|
||||||
|
sshHostGroupId: sshHostGroup.id,
|
||||||
|
sshHostId: sshHost.id,
|
||||||
|
hostname: sshHost.hostname
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return sshHost;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
@@ -3,8 +3,9 @@ import { z } from "zod";
|
|||||||
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
|
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
|
||||||
import { SshCertKeyAlgorithm } from "@app/ee/services/ssh-certificate/ssh-certificate-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 { loginMappingSchema, sanitizedSshHost } from "@app/ee/services/ssh-host/ssh-host-schema";
|
||||||
|
import { LoginMappingSource } from "@app/ee/services/ssh-host/ssh-host-types";
|
||||||
import { isValidHostname } from "@app/ee/services/ssh-host/ssh-host-validators";
|
import { isValidHostname } from "@app/ee/services/ssh-host/ssh-host-validators";
|
||||||
import { SSH_HOSTS } from "@app/lib/api-docs";
|
import { ApiDocsTags, SSH_HOSTS } from "@app/lib/api-docs";
|
||||||
import { ms } from "@app/lib/ms";
|
import { ms } from "@app/lib/ms";
|
||||||
import { publicSshCaLimit, readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
import { publicSshCaLimit, readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
||||||
import { slugSchema } from "@app/server/lib/schemas";
|
import { slugSchema } from "@app/server/lib/schemas";
|
||||||
@@ -21,10 +22,16 @@ export const registerSshHostRouter = async (server: FastifyZodProvider) => {
|
|||||||
rateLimit: readLimit
|
rateLimit: readLimit
|
||||||
},
|
},
|
||||||
schema: {
|
schema: {
|
||||||
|
hide: false,
|
||||||
|
tags: [ApiDocsTags.SshHosts],
|
||||||
response: {
|
response: {
|
||||||
200: z.array(
|
200: z.array(
|
||||||
sanitizedSshHost.extend({
|
sanitizedSshHost.extend({
|
||||||
loginMappings: z.array(loginMappingSchema)
|
loginMappings: loginMappingSchema
|
||||||
|
.extend({
|
||||||
|
source: z.nativeEnum(LoginMappingSource)
|
||||||
|
})
|
||||||
|
.array()
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -49,12 +56,18 @@ export const registerSshHostRouter = async (server: FastifyZodProvider) => {
|
|||||||
rateLimit: readLimit
|
rateLimit: readLimit
|
||||||
},
|
},
|
||||||
schema: {
|
schema: {
|
||||||
|
hide: false,
|
||||||
|
tags: [ApiDocsTags.SshHosts],
|
||||||
params: z.object({
|
params: z.object({
|
||||||
sshHostId: z.string().describe(SSH_HOSTS.GET.sshHostId)
|
sshHostId: z.string().describe(SSH_HOSTS.GET.sshHostId)
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: sanitizedSshHost.extend({
|
200: sanitizedSshHost.extend({
|
||||||
loginMappings: z.array(loginMappingSchema)
|
loginMappings: loginMappingSchema
|
||||||
|
.extend({
|
||||||
|
source: z.nativeEnum(LoginMappingSource)
|
||||||
|
})
|
||||||
|
.array()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -91,7 +104,9 @@ export const registerSshHostRouter = async (server: FastifyZodProvider) => {
|
|||||||
rateLimit: writeLimit
|
rateLimit: writeLimit
|
||||||
},
|
},
|
||||||
schema: {
|
schema: {
|
||||||
description: "Add an SSH Host",
|
hide: false,
|
||||||
|
tags: [ApiDocsTags.SshHosts],
|
||||||
|
description: "Register SSH Host",
|
||||||
body: z.object({
|
body: z.object({
|
||||||
projectId: z.string().describe(SSH_HOSTS.CREATE.projectId),
|
projectId: z.string().describe(SSH_HOSTS.CREATE.projectId),
|
||||||
hostname: z
|
hostname: z
|
||||||
@@ -119,7 +134,11 @@ export const registerSshHostRouter = async (server: FastifyZodProvider) => {
|
|||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: sanitizedSshHost.extend({
|
200: sanitizedSshHost.extend({
|
||||||
loginMappings: z.array(loginMappingSchema)
|
loginMappings: loginMappingSchema
|
||||||
|
.extend({
|
||||||
|
source: z.nativeEnum(LoginMappingSource)
|
||||||
|
})
|
||||||
|
.array()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -163,6 +182,8 @@ export const registerSshHostRouter = async (server: FastifyZodProvider) => {
|
|||||||
},
|
},
|
||||||
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
schema: {
|
schema: {
|
||||||
|
hide: false,
|
||||||
|
tags: [ApiDocsTags.SshHosts],
|
||||||
description: "Update SSH Host",
|
description: "Update SSH Host",
|
||||||
params: z.object({
|
params: z.object({
|
||||||
sshHostId: z.string().trim().describe(SSH_HOSTS.UPDATE.sshHostId)
|
sshHostId: z.string().trim().describe(SSH_HOSTS.UPDATE.sshHostId)
|
||||||
@@ -192,7 +213,11 @@ export const registerSshHostRouter = async (server: FastifyZodProvider) => {
|
|||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: sanitizedSshHost.extend({
|
200: sanitizedSshHost.extend({
|
||||||
loginMappings: z.array(loginMappingSchema)
|
loginMappings: loginMappingSchema
|
||||||
|
.extend({
|
||||||
|
source: z.nativeEnum(LoginMappingSource)
|
||||||
|
})
|
||||||
|
.array()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -235,12 +260,19 @@ export const registerSshHostRouter = async (server: FastifyZodProvider) => {
|
|||||||
rateLimit: writeLimit
|
rateLimit: writeLimit
|
||||||
},
|
},
|
||||||
schema: {
|
schema: {
|
||||||
|
hide: false,
|
||||||
|
tags: [ApiDocsTags.SshHosts],
|
||||||
|
description: "Delete SSH Host",
|
||||||
params: z.object({
|
params: z.object({
|
||||||
sshHostId: z.string().describe(SSH_HOSTS.DELETE.sshHostId)
|
sshHostId: z.string().describe(SSH_HOSTS.DELETE.sshHostId)
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: sanitizedSshHost.extend({
|
200: sanitizedSshHost.extend({
|
||||||
loginMappings: z.array(loginMappingSchema)
|
loginMappings: loginMappingSchema
|
||||||
|
.extend({
|
||||||
|
source: z.nativeEnum(LoginMappingSource)
|
||||||
|
})
|
||||||
|
.array()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -278,6 +310,8 @@ export const registerSshHostRouter = async (server: FastifyZodProvider) => {
|
|||||||
},
|
},
|
||||||
onRequest: verifyAuth([AuthMode.JWT]),
|
onRequest: verifyAuth([AuthMode.JWT]),
|
||||||
schema: {
|
schema: {
|
||||||
|
hide: false,
|
||||||
|
tags: [ApiDocsTags.SshHosts],
|
||||||
description: "Issue SSH certificate for user",
|
description: "Issue SSH certificate for user",
|
||||||
params: z.object({
|
params: z.object({
|
||||||
sshHostId: z.string().describe(SSH_HOSTS.ISSUE_SSH_CREDENTIALS.sshHostId)
|
sshHostId: z.string().describe(SSH_HOSTS.ISSUE_SSH_CREDENTIALS.sshHostId)
|
||||||
@@ -350,6 +384,8 @@ export const registerSshHostRouter = async (server: FastifyZodProvider) => {
|
|||||||
},
|
},
|
||||||
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
schema: {
|
schema: {
|
||||||
|
hide: false,
|
||||||
|
tags: [ApiDocsTags.SshHosts],
|
||||||
description: "Issue SSH certificate for host",
|
description: "Issue SSH certificate for host",
|
||||||
params: z.object({
|
params: z.object({
|
||||||
sshHostId: z.string().describe(SSH_HOSTS.ISSUE_HOST_CERT.sshHostId)
|
sshHostId: z.string().describe(SSH_HOSTS.ISSUE_HOST_CERT.sshHostId)
|
||||||
@@ -414,6 +450,8 @@ export const registerSshHostRouter = async (server: FastifyZodProvider) => {
|
|||||||
rateLimit: publicSshCaLimit
|
rateLimit: publicSshCaLimit
|
||||||
},
|
},
|
||||||
schema: {
|
schema: {
|
||||||
|
hide: false,
|
||||||
|
tags: [ApiDocsTags.SshHosts],
|
||||||
description: "Get public key of the user SSH CA linked to the host",
|
description: "Get public key of the user SSH CA linked to the host",
|
||||||
params: z.object({
|
params: z.object({
|
||||||
sshHostId: z.string().trim().describe(SSH_HOSTS.GET_USER_CA_PUBLIC_KEY.sshHostId)
|
sshHostId: z.string().trim().describe(SSH_HOSTS.GET_USER_CA_PUBLIC_KEY.sshHostId)
|
||||||
@@ -435,6 +473,8 @@ export const registerSshHostRouter = async (server: FastifyZodProvider) => {
|
|||||||
rateLimit: publicSshCaLimit
|
rateLimit: publicSshCaLimit
|
||||||
},
|
},
|
||||||
schema: {
|
schema: {
|
||||||
|
hide: false,
|
||||||
|
tags: [ApiDocsTags.SshHosts],
|
||||||
description: "Get public key of the host SSH CA linked to the host",
|
description: "Get public key of the host SSH CA linked to the host",
|
||||||
params: z.object({
|
params: z.object({
|
||||||
sshHostId: z.string().trim().describe(SSH_HOSTS.GET_HOST_CA_PUBLIC_KEY.sshHostId)
|
sshHostId: z.string().trim().describe(SSH_HOSTS.GET_HOST_CA_PUBLIC_KEY.sshHostId)
|
||||||
|
@@ -0,0 +1,19 @@
|
|||||||
|
import {
|
||||||
|
AzureClientSecretRotationGeneratedCredentialsSchema,
|
||||||
|
AzureClientSecretRotationSchema,
|
||||||
|
CreateAzureClientSecretRotationSchema,
|
||||||
|
UpdateAzureClientSecretRotationSchema
|
||||||
|
} from "@app/ee/services/secret-rotation-v2/azure-client-secret";
|
||||||
|
import { SecretRotation } from "@app/ee/services/secret-rotation-v2/secret-rotation-v2-enums";
|
||||||
|
|
||||||
|
import { registerSecretRotationEndpoints } from "./secret-rotation-v2-endpoints";
|
||||||
|
|
||||||
|
export const registerAzureClientSecretRotationRouter = async (server: FastifyZodProvider) =>
|
||||||
|
registerSecretRotationEndpoints({
|
||||||
|
type: SecretRotation.AzureClientSecret,
|
||||||
|
server,
|
||||||
|
responseSchema: AzureClientSecretRotationSchema,
|
||||||
|
createSchema: CreateAzureClientSecretRotationSchema,
|
||||||
|
updateSchema: UpdateAzureClientSecretRotationSchema,
|
||||||
|
generatedCredentialsSchema: AzureClientSecretRotationGeneratedCredentialsSchema
|
||||||
|
});
|
@@ -2,6 +2,7 @@ import { SecretRotation } from "@app/ee/services/secret-rotation-v2/secret-rotat
|
|||||||
|
|
||||||
import { registerAuth0ClientSecretRotationRouter } from "./auth0-client-secret-rotation-router";
|
import { registerAuth0ClientSecretRotationRouter } from "./auth0-client-secret-rotation-router";
|
||||||
import { registerAwsIamUserSecretRotationRouter } from "./aws-iam-user-secret-rotation-router";
|
import { registerAwsIamUserSecretRotationRouter } from "./aws-iam-user-secret-rotation-router";
|
||||||
|
import { registerAzureClientSecretRotationRouter } from "./azure-client-secret-rotation-router";
|
||||||
import { registerLdapPasswordRotationRouter } from "./ldap-password-rotation-router";
|
import { registerLdapPasswordRotationRouter } from "./ldap-password-rotation-router";
|
||||||
import { registerMsSqlCredentialsRotationRouter } from "./mssql-credentials-rotation-router";
|
import { registerMsSqlCredentialsRotationRouter } from "./mssql-credentials-rotation-router";
|
||||||
import { registerPostgresCredentialsRotationRouter } from "./postgres-credentials-rotation-router";
|
import { registerPostgresCredentialsRotationRouter } from "./postgres-credentials-rotation-router";
|
||||||
@@ -15,6 +16,7 @@ export const SECRET_ROTATION_REGISTER_ROUTER_MAP: Record<
|
|||||||
[SecretRotation.PostgresCredentials]: registerPostgresCredentialsRotationRouter,
|
[SecretRotation.PostgresCredentials]: registerPostgresCredentialsRotationRouter,
|
||||||
[SecretRotation.MsSqlCredentials]: registerMsSqlCredentialsRotationRouter,
|
[SecretRotation.MsSqlCredentials]: registerMsSqlCredentialsRotationRouter,
|
||||||
[SecretRotation.Auth0ClientSecret]: registerAuth0ClientSecretRotationRouter,
|
[SecretRotation.Auth0ClientSecret]: registerAuth0ClientSecretRotationRouter,
|
||||||
[SecretRotation.LdapPassword]: registerLdapPasswordRotationRouter,
|
[SecretRotation.AzureClientSecret]: registerAzureClientSecretRotationRouter,
|
||||||
[SecretRotation.AwsIamUserSecret]: registerAwsIamUserSecretRotationRouter
|
[SecretRotation.AwsIamUserSecret]: registerAwsIamUserSecretRotationRouter,
|
||||||
|
[SecretRotation.LdapPassword]: registerLdapPasswordRotationRouter
|
||||||
};
|
};
|
||||||
|
@@ -3,6 +3,7 @@ import { z } from "zod";
|
|||||||
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
|
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
|
||||||
import { Auth0ClientSecretRotationListItemSchema } from "@app/ee/services/secret-rotation-v2/auth0-client-secret";
|
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 { AwsIamUserSecretRotationListItemSchema } from "@app/ee/services/secret-rotation-v2/aws-iam-user-secret";
|
||||||
|
import { AzureClientSecretRotationListItemSchema } from "@app/ee/services/secret-rotation-v2/azure-client-secret";
|
||||||
import { LdapPasswordRotationListItemSchema } from "@app/ee/services/secret-rotation-v2/ldap-password";
|
import { LdapPasswordRotationListItemSchema } from "@app/ee/services/secret-rotation-v2/ldap-password";
|
||||||
import { MsSqlCredentialsRotationListItemSchema } from "@app/ee/services/secret-rotation-v2/mssql-credentials";
|
import { MsSqlCredentialsRotationListItemSchema } from "@app/ee/services/secret-rotation-v2/mssql-credentials";
|
||||||
import { PostgresCredentialsRotationListItemSchema } from "@app/ee/services/secret-rotation-v2/postgres-credentials";
|
import { PostgresCredentialsRotationListItemSchema } from "@app/ee/services/secret-rotation-v2/postgres-credentials";
|
||||||
@@ -16,8 +17,9 @@ const SecretRotationV2OptionsSchema = z.discriminatedUnion("type", [
|
|||||||
PostgresCredentialsRotationListItemSchema,
|
PostgresCredentialsRotationListItemSchema,
|
||||||
MsSqlCredentialsRotationListItemSchema,
|
MsSqlCredentialsRotationListItemSchema,
|
||||||
Auth0ClientSecretRotationListItemSchema,
|
Auth0ClientSecretRotationListItemSchema,
|
||||||
LdapPasswordRotationListItemSchema,
|
AzureClientSecretRotationListItemSchema,
|
||||||
AwsIamUserSecretRotationListItemSchema
|
AwsIamUserSecretRotationListItemSchema,
|
||||||
|
LdapPasswordRotationListItemSchema
|
||||||
]);
|
]);
|
||||||
|
|
||||||
export const registerSecretRotationV2Router = async (server: FastifyZodProvider) => {
|
export const registerSecretRotationV2Router = async (server: FastifyZodProvider) => {
|
||||||
|
@@ -6,13 +6,15 @@ import { getConfig } from "@app/lib/config/env";
|
|||||||
import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors";
|
import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors";
|
||||||
import { ms } from "@app/lib/ms";
|
import { ms } from "@app/lib/ms";
|
||||||
import { alphaNumericNanoId } from "@app/lib/nanoid";
|
import { alphaNumericNanoId } from "@app/lib/nanoid";
|
||||||
|
import { triggerWorkflowIntegrationNotification } from "@app/lib/workflow-integrations/trigger-notification";
|
||||||
|
import { TriggerFeature } from "@app/lib/workflow-integrations/types";
|
||||||
import { TKmsServiceFactory } from "@app/services/kms/kms-service";
|
import { TKmsServiceFactory } from "@app/services/kms/kms-service";
|
||||||
|
import { TMicrosoftTeamsServiceFactory } from "@app/services/microsoft-teams/microsoft-teams-service";
|
||||||
|
import { TProjectMicrosoftTeamsConfigDALFactory } from "@app/services/microsoft-teams/project-microsoft-teams-config-dal";
|
||||||
import { TProjectDALFactory } from "@app/services/project/project-dal";
|
import { TProjectDALFactory } from "@app/services/project/project-dal";
|
||||||
import { TProjectEnvDALFactory } from "@app/services/project-env/project-env-dal";
|
import { TProjectEnvDALFactory } from "@app/services/project-env/project-env-dal";
|
||||||
import { TProjectMembershipDALFactory } from "@app/services/project-membership/project-membership-dal";
|
import { TProjectMembershipDALFactory } from "@app/services/project-membership/project-membership-dal";
|
||||||
import { TProjectSlackConfigDALFactory } from "@app/services/slack/project-slack-config-dal";
|
import { TProjectSlackConfigDALFactory } from "@app/services/slack/project-slack-config-dal";
|
||||||
import { triggerSlackNotification } from "@app/services/slack/slack-fns";
|
|
||||||
import { SlackTriggerFeature } from "@app/services/slack/slack-types";
|
|
||||||
import { SmtpTemplates, TSmtpService } from "@app/services/smtp/smtp-service";
|
import { SmtpTemplates, TSmtpService } from "@app/services/smtp/smtp-service";
|
||||||
import { TUserDALFactory } from "@app/services/user/user-dal";
|
import { TUserDALFactory } from "@app/services/user/user-dal";
|
||||||
|
|
||||||
@@ -67,6 +69,8 @@ type TSecretApprovalRequestServiceFactoryDep = {
|
|||||||
>;
|
>;
|
||||||
kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey">;
|
kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey">;
|
||||||
projectSlackConfigDAL: Pick<TProjectSlackConfigDALFactory, "getIntegrationDetailsByProject">;
|
projectSlackConfigDAL: Pick<TProjectSlackConfigDALFactory, "getIntegrationDetailsByProject">;
|
||||||
|
microsoftTeamsService: Pick<TMicrosoftTeamsServiceFactory, "sendNotification">;
|
||||||
|
projectMicrosoftTeamsConfigDAL: Pick<TProjectMicrosoftTeamsConfigDALFactory, "getIntegrationDetailsByProject">;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TAccessApprovalRequestServiceFactory = ReturnType<typeof accessApprovalRequestServiceFactory>;
|
export type TAccessApprovalRequestServiceFactory = ReturnType<typeof accessApprovalRequestServiceFactory>;
|
||||||
@@ -84,6 +88,8 @@ export const accessApprovalRequestServiceFactory = ({
|
|||||||
smtpService,
|
smtpService,
|
||||||
userDAL,
|
userDAL,
|
||||||
kmsService,
|
kmsService,
|
||||||
|
microsoftTeamsService,
|
||||||
|
projectMicrosoftTeamsConfigDAL,
|
||||||
projectSlackConfigDAL
|
projectSlackConfigDAL
|
||||||
}: TSecretApprovalRequestServiceFactoryDep) => {
|
}: TSecretApprovalRequestServiceFactoryDep) => {
|
||||||
const createAccessApprovalRequest = async ({
|
const createAccessApprovalRequest = async ({
|
||||||
@@ -219,24 +225,30 @@ export const accessApprovalRequestServiceFactory = ({
|
|||||||
const requesterFullName = `${requestedByUser.firstName} ${requestedByUser.lastName}`;
|
const requesterFullName = `${requestedByUser.firstName} ${requestedByUser.lastName}`;
|
||||||
const approvalUrl = `${cfg.SITE_URL}/secret-manager/${project.id}/approval`;
|
const approvalUrl = `${cfg.SITE_URL}/secret-manager/${project.id}/approval`;
|
||||||
|
|
||||||
await triggerSlackNotification({
|
await triggerWorkflowIntegrationNotification({
|
||||||
projectId: project.id,
|
input: {
|
||||||
projectSlackConfigDAL,
|
notification: {
|
||||||
projectDAL,
|
type: TriggerFeature.ACCESS_REQUEST,
|
||||||
kmsService,
|
payload: {
|
||||||
notification: {
|
projectName: project.name,
|
||||||
type: SlackTriggerFeature.ACCESS_REQUEST,
|
requesterFullName,
|
||||||
payload: {
|
isTemporary,
|
||||||
projectName: project.name,
|
requesterEmail: requestedByUser.email as string,
|
||||||
requesterFullName,
|
secretPath,
|
||||||
isTemporary,
|
environment: envSlug,
|
||||||
requesterEmail: requestedByUser.email as string,
|
permissions: accessTypes,
|
||||||
secretPath,
|
approvalUrl,
|
||||||
environment: envSlug,
|
note
|
||||||
permissions: accessTypes,
|
}
|
||||||
approvalUrl,
|
},
|
||||||
note
|
projectId: project.id
|
||||||
}
|
},
|
||||||
|
dependencies: {
|
||||||
|
projectDAL,
|
||||||
|
projectSlackConfigDAL,
|
||||||
|
kmsService,
|
||||||
|
microsoftTeamsService,
|
||||||
|
projectMicrosoftTeamsConfigDAL
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@@ -12,6 +12,7 @@ import {
|
|||||||
import { SshCaStatus, SshCertType } from "@app/ee/services/ssh/ssh-certificate-authority-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 { SshCertKeyAlgorithm } from "@app/ee/services/ssh-certificate/ssh-certificate-types";
|
||||||
import { SshCertTemplateStatus } from "@app/ee/services/ssh-certificate-template/ssh-certificate-template-types";
|
import { SshCertTemplateStatus } from "@app/ee/services/ssh-certificate-template/ssh-certificate-template-types";
|
||||||
|
import { TLoginMapping } from "@app/ee/services/ssh-host/ssh-host-types";
|
||||||
import { SymmetricKeyAlgorithm } from "@app/lib/crypto/cipher";
|
import { SymmetricKeyAlgorithm } from "@app/lib/crypto/cipher";
|
||||||
import { AsymmetricKeyAlgorithm, SigningAlgorithm } from "@app/lib/crypto/sign/types";
|
import { AsymmetricKeyAlgorithm, SigningAlgorithm } from "@app/lib/crypto/sign/types";
|
||||||
import { TProjectPermission } from "@app/lib/types";
|
import { TProjectPermission } from "@app/lib/types";
|
||||||
@@ -21,6 +22,7 @@ import { ActorType } from "@app/services/auth/auth-type";
|
|||||||
import { CertKeyAlgorithm } from "@app/services/certificate/certificate-types";
|
import { CertKeyAlgorithm } from "@app/services/certificate/certificate-types";
|
||||||
import { CaStatus } from "@app/services/certificate-authority/certificate-authority-types";
|
import { CaStatus } from "@app/services/certificate-authority/certificate-authority-types";
|
||||||
import { TIdentityTrustedIp } from "@app/services/identity/identity-types";
|
import { TIdentityTrustedIp } from "@app/services/identity/identity-types";
|
||||||
|
import { TAllowedFields } from "@app/services/identity-ldap-auth/identity-ldap-auth-types";
|
||||||
import { PkiItemType } from "@app/services/pki-collection/pki-collection-types";
|
import { PkiItemType } from "@app/services/pki-collection/pki-collection-types";
|
||||||
import { SecretSync, SecretSyncImportBehavior } from "@app/services/secret-sync/secret-sync-enums";
|
import { SecretSync, SecretSyncImportBehavior } from "@app/services/secret-sync/secret-sync-enums";
|
||||||
import {
|
import {
|
||||||
@@ -29,6 +31,7 @@ import {
|
|||||||
TSecretSyncRaw,
|
TSecretSyncRaw,
|
||||||
TUpdateSecretSyncDTO
|
TUpdateSecretSyncDTO
|
||||||
} from "@app/services/secret-sync/secret-sync-types";
|
} from "@app/services/secret-sync/secret-sync-types";
|
||||||
|
import { WorkflowIntegration } from "@app/services/workflow-integration/workflow-integration-types";
|
||||||
|
|
||||||
import { KmipPermission } from "../kmip/kmip-enum";
|
import { KmipPermission } from "../kmip/kmip-enum";
|
||||||
import { ApprovalStatus } from "../secret-approval-request/secret-approval-request-types";
|
import { ApprovalStatus } from "../secret-approval-request/secret-approval-request-types";
|
||||||
@@ -117,44 +120,60 @@ export enum EventType {
|
|||||||
CREATE_TOKEN_IDENTITY_TOKEN_AUTH = "create-token-identity-token-auth",
|
CREATE_TOKEN_IDENTITY_TOKEN_AUTH = "create-token-identity-token-auth",
|
||||||
UPDATE_TOKEN_IDENTITY_TOKEN_AUTH = "update-token-identity-token-auth",
|
UPDATE_TOKEN_IDENTITY_TOKEN_AUTH = "update-token-identity-token-auth",
|
||||||
GET_TOKENS_IDENTITY_TOKEN_AUTH = "get-tokens-identity-token-auth",
|
GET_TOKENS_IDENTITY_TOKEN_AUTH = "get-tokens-identity-token-auth",
|
||||||
|
|
||||||
ADD_IDENTITY_TOKEN_AUTH = "add-identity-token-auth",
|
ADD_IDENTITY_TOKEN_AUTH = "add-identity-token-auth",
|
||||||
UPDATE_IDENTITY_TOKEN_AUTH = "update-identity-token-auth",
|
UPDATE_IDENTITY_TOKEN_AUTH = "update-identity-token-auth",
|
||||||
GET_IDENTITY_TOKEN_AUTH = "get-identity-token-auth",
|
GET_IDENTITY_TOKEN_AUTH = "get-identity-token-auth",
|
||||||
REVOKE_IDENTITY_TOKEN_AUTH = "revoke-identity-token-auth",
|
REVOKE_IDENTITY_TOKEN_AUTH = "revoke-identity-token-auth",
|
||||||
|
|
||||||
LOGIN_IDENTITY_KUBERNETES_AUTH = "login-identity-kubernetes-auth",
|
LOGIN_IDENTITY_KUBERNETES_AUTH = "login-identity-kubernetes-auth",
|
||||||
ADD_IDENTITY_KUBERNETES_AUTH = "add-identity-kubernetes-auth",
|
ADD_IDENTITY_KUBERNETES_AUTH = "add-identity-kubernetes-auth",
|
||||||
UPDATE_IDENTITY_KUBENETES_AUTH = "update-identity-kubernetes-auth",
|
UPDATE_IDENTITY_KUBENETES_AUTH = "update-identity-kubernetes-auth",
|
||||||
GET_IDENTITY_KUBERNETES_AUTH = "get-identity-kubernetes-auth",
|
GET_IDENTITY_KUBERNETES_AUTH = "get-identity-kubernetes-auth",
|
||||||
REVOKE_IDENTITY_KUBERNETES_AUTH = "revoke-identity-kubernetes-auth",
|
REVOKE_IDENTITY_KUBERNETES_AUTH = "revoke-identity-kubernetes-auth",
|
||||||
|
|
||||||
LOGIN_IDENTITY_OIDC_AUTH = "login-identity-oidc-auth",
|
LOGIN_IDENTITY_OIDC_AUTH = "login-identity-oidc-auth",
|
||||||
ADD_IDENTITY_OIDC_AUTH = "add-identity-oidc-auth",
|
ADD_IDENTITY_OIDC_AUTH = "add-identity-oidc-auth",
|
||||||
UPDATE_IDENTITY_OIDC_AUTH = "update-identity-oidc-auth",
|
UPDATE_IDENTITY_OIDC_AUTH = "update-identity-oidc-auth",
|
||||||
GET_IDENTITY_OIDC_AUTH = "get-identity-oidc-auth",
|
GET_IDENTITY_OIDC_AUTH = "get-identity-oidc-auth",
|
||||||
REVOKE_IDENTITY_OIDC_AUTH = "revoke-identity-oidc-auth",
|
REVOKE_IDENTITY_OIDC_AUTH = "revoke-identity-oidc-auth",
|
||||||
|
|
||||||
LOGIN_IDENTITY_JWT_AUTH = "login-identity-jwt-auth",
|
LOGIN_IDENTITY_JWT_AUTH = "login-identity-jwt-auth",
|
||||||
ADD_IDENTITY_JWT_AUTH = "add-identity-jwt-auth",
|
ADD_IDENTITY_JWT_AUTH = "add-identity-jwt-auth",
|
||||||
UPDATE_IDENTITY_JWT_AUTH = "update-identity-jwt-auth",
|
UPDATE_IDENTITY_JWT_AUTH = "update-identity-jwt-auth",
|
||||||
GET_IDENTITY_JWT_AUTH = "get-identity-jwt-auth",
|
GET_IDENTITY_JWT_AUTH = "get-identity-jwt-auth",
|
||||||
REVOKE_IDENTITY_JWT_AUTH = "revoke-identity-jwt-auth",
|
REVOKE_IDENTITY_JWT_AUTH = "revoke-identity-jwt-auth",
|
||||||
|
|
||||||
CREATE_IDENTITY_UNIVERSAL_AUTH_CLIENT_SECRET = "create-identity-universal-auth-client-secret",
|
CREATE_IDENTITY_UNIVERSAL_AUTH_CLIENT_SECRET = "create-identity-universal-auth-client-secret",
|
||||||
REVOKE_IDENTITY_UNIVERSAL_AUTH_CLIENT_SECRET = "revoke-identity-universal-auth-client-secret",
|
REVOKE_IDENTITY_UNIVERSAL_AUTH_CLIENT_SECRET = "revoke-identity-universal-auth-client-secret",
|
||||||
|
|
||||||
GET_IDENTITY_UNIVERSAL_AUTH_CLIENT_SECRETS = "get-identity-universal-auth-client-secret",
|
GET_IDENTITY_UNIVERSAL_AUTH_CLIENT_SECRETS = "get-identity-universal-auth-client-secret",
|
||||||
GET_IDENTITY_UNIVERSAL_AUTH_CLIENT_SECRET_BY_ID = "get-identity-universal-auth-client-secret-by-id",
|
GET_IDENTITY_UNIVERSAL_AUTH_CLIENT_SECRET_BY_ID = "get-identity-universal-auth-client-secret-by-id",
|
||||||
|
|
||||||
LOGIN_IDENTITY_GCP_AUTH = "login-identity-gcp-auth",
|
LOGIN_IDENTITY_GCP_AUTH = "login-identity-gcp-auth",
|
||||||
ADD_IDENTITY_GCP_AUTH = "add-identity-gcp-auth",
|
ADD_IDENTITY_GCP_AUTH = "add-identity-gcp-auth",
|
||||||
UPDATE_IDENTITY_GCP_AUTH = "update-identity-gcp-auth",
|
UPDATE_IDENTITY_GCP_AUTH = "update-identity-gcp-auth",
|
||||||
REVOKE_IDENTITY_GCP_AUTH = "revoke-identity-gcp-auth",
|
REVOKE_IDENTITY_GCP_AUTH = "revoke-identity-gcp-auth",
|
||||||
GET_IDENTITY_GCP_AUTH = "get-identity-gcp-auth",
|
GET_IDENTITY_GCP_AUTH = "get-identity-gcp-auth",
|
||||||
|
|
||||||
LOGIN_IDENTITY_AWS_AUTH = "login-identity-aws-auth",
|
LOGIN_IDENTITY_AWS_AUTH = "login-identity-aws-auth",
|
||||||
ADD_IDENTITY_AWS_AUTH = "add-identity-aws-auth",
|
ADD_IDENTITY_AWS_AUTH = "add-identity-aws-auth",
|
||||||
UPDATE_IDENTITY_AWS_AUTH = "update-identity-aws-auth",
|
UPDATE_IDENTITY_AWS_AUTH = "update-identity-aws-auth",
|
||||||
REVOKE_IDENTITY_AWS_AUTH = "revoke-identity-aws-auth",
|
REVOKE_IDENTITY_AWS_AUTH = "revoke-identity-aws-auth",
|
||||||
GET_IDENTITY_AWS_AUTH = "get-identity-aws-auth",
|
GET_IDENTITY_AWS_AUTH = "get-identity-aws-auth",
|
||||||
|
|
||||||
LOGIN_IDENTITY_AZURE_AUTH = "login-identity-azure-auth",
|
LOGIN_IDENTITY_AZURE_AUTH = "login-identity-azure-auth",
|
||||||
ADD_IDENTITY_AZURE_AUTH = "add-identity-azure-auth",
|
ADD_IDENTITY_AZURE_AUTH = "add-identity-azure-auth",
|
||||||
UPDATE_IDENTITY_AZURE_AUTH = "update-identity-azure-auth",
|
UPDATE_IDENTITY_AZURE_AUTH = "update-identity-azure-auth",
|
||||||
GET_IDENTITY_AZURE_AUTH = "get-identity-azure-auth",
|
GET_IDENTITY_AZURE_AUTH = "get-identity-azure-auth",
|
||||||
REVOKE_IDENTITY_AZURE_AUTH = "revoke-identity-azure-auth",
|
REVOKE_IDENTITY_AZURE_AUTH = "revoke-identity-azure-auth",
|
||||||
|
|
||||||
|
LOGIN_IDENTITY_LDAP_AUTH = "login-identity-ldap-auth",
|
||||||
|
ADD_IDENTITY_LDAP_AUTH = "add-identity-ldap-auth",
|
||||||
|
UPDATE_IDENTITY_LDAP_AUTH = "update-identity-ldap-auth",
|
||||||
|
GET_IDENTITY_LDAP_AUTH = "get-identity-ldap-auth",
|
||||||
|
REVOKE_IDENTITY_LDAP_AUTH = "revoke-identity-ldap-auth",
|
||||||
|
|
||||||
CREATE_ENVIRONMENT = "create-environment",
|
CREATE_ENVIRONMENT = "create-environment",
|
||||||
UPDATE_ENVIRONMENT = "update-environment",
|
UPDATE_ENVIRONMENT = "update-environment",
|
||||||
DELETE_ENVIRONMENT = "delete-environment",
|
DELETE_ENVIRONMENT = "delete-environment",
|
||||||
@@ -191,12 +210,19 @@ export enum EventType {
|
|||||||
UPDATE_SSH_CERTIFICATE_TEMPLATE = "update-ssh-certificate-template",
|
UPDATE_SSH_CERTIFICATE_TEMPLATE = "update-ssh-certificate-template",
|
||||||
DELETE_SSH_CERTIFICATE_TEMPLATE = "delete-ssh-certificate-template",
|
DELETE_SSH_CERTIFICATE_TEMPLATE = "delete-ssh-certificate-template",
|
||||||
GET_SSH_CERTIFICATE_TEMPLATE = "get-ssh-certificate-template",
|
GET_SSH_CERTIFICATE_TEMPLATE = "get-ssh-certificate-template",
|
||||||
|
GET_SSH_HOST = "get-ssh-host",
|
||||||
CREATE_SSH_HOST = "create-ssh-host",
|
CREATE_SSH_HOST = "create-ssh-host",
|
||||||
UPDATE_SSH_HOST = "update-ssh-host",
|
UPDATE_SSH_HOST = "update-ssh-host",
|
||||||
DELETE_SSH_HOST = "delete-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_USER_CERT = "issue-ssh-host-user-cert",
|
||||||
ISSUE_SSH_HOST_HOST_CERT = "issue-ssh-host-host-cert",
|
ISSUE_SSH_HOST_HOST_CERT = "issue-ssh-host-host-cert",
|
||||||
|
GET_SSH_HOST_GROUP = "get-ssh-host-group",
|
||||||
|
CREATE_SSH_HOST_GROUP = "create-ssh-host-group",
|
||||||
|
UPDATE_SSH_HOST_GROUP = "update-ssh-host-group",
|
||||||
|
DELETE_SSH_HOST_GROUP = "delete-ssh-host-group",
|
||||||
|
GET_SSH_HOST_GROUP_HOSTS = "get-ssh-host-group-hosts",
|
||||||
|
ADD_HOST_TO_SSH_HOST_GROUP = "add-host-to-ssh-host-group",
|
||||||
|
REMOVE_HOST_FROM_SSH_HOST_GROUP = "remove-host-from-ssh-host-group",
|
||||||
CREATE_CA = "create-certificate-authority",
|
CREATE_CA = "create-certificate-authority",
|
||||||
GET_CA = "get-certificate-authority",
|
GET_CA = "get-certificate-authority",
|
||||||
UPDATE_CA = "update-certificate-authority",
|
UPDATE_CA = "update-certificate-authority",
|
||||||
@@ -215,6 +241,8 @@ export enum EventType {
|
|||||||
DELETE_CERT = "delete-cert",
|
DELETE_CERT = "delete-cert",
|
||||||
REVOKE_CERT = "revoke-cert",
|
REVOKE_CERT = "revoke-cert",
|
||||||
GET_CERT_BODY = "get-cert-body",
|
GET_CERT_BODY = "get-cert-body",
|
||||||
|
GET_CERT_PRIVATE_KEY = "get-cert-private-key",
|
||||||
|
GET_CERT_BUNDLE = "get-cert-bundle",
|
||||||
CREATE_PKI_ALERT = "create-pki-alert",
|
CREATE_PKI_ALERT = "create-pki-alert",
|
||||||
GET_PKI_ALERT = "get-pki-alert",
|
GET_PKI_ALERT = "get-pki-alert",
|
||||||
UPDATE_PKI_ALERT = "update-pki-alert",
|
UPDATE_PKI_ALERT = "update-pki-alert",
|
||||||
@@ -244,11 +272,14 @@ export enum EventType {
|
|||||||
GET_CERTIFICATE_TEMPLATE_EST_CONFIG = "get-certificate-template-est-config",
|
GET_CERTIFICATE_TEMPLATE_EST_CONFIG = "get-certificate-template-est-config",
|
||||||
ATTEMPT_CREATE_SLACK_INTEGRATION = "attempt-create-slack-integration",
|
ATTEMPT_CREATE_SLACK_INTEGRATION = "attempt-create-slack-integration",
|
||||||
ATTEMPT_REINSTALL_SLACK_INTEGRATION = "attempt-reinstall-slack-integration",
|
ATTEMPT_REINSTALL_SLACK_INTEGRATION = "attempt-reinstall-slack-integration",
|
||||||
|
GET_PROJECT_SLACK_CONFIG = "get-project-slack-config",
|
||||||
|
UPDATE_PROJECT_SLACK_CONFIG = "update-project-slack-config",
|
||||||
GET_SLACK_INTEGRATION = "get-slack-integration",
|
GET_SLACK_INTEGRATION = "get-slack-integration",
|
||||||
UPDATE_SLACK_INTEGRATION = "update-slack-integration",
|
UPDATE_SLACK_INTEGRATION = "update-slack-integration",
|
||||||
DELETE_SLACK_INTEGRATION = "delete-slack-integration",
|
DELETE_SLACK_INTEGRATION = "delete-slack-integration",
|
||||||
GET_PROJECT_SLACK_CONFIG = "get-project-slack-config",
|
GET_PROJECT_WORKFLOW_INTEGRATION_CONFIG = "get-project-workflow-integration-config",
|
||||||
UPDATE_PROJECT_SLACK_CONFIG = "update-project-slack-config",
|
UPDATE_PROJECT_WORKFLOW_INTEGRATION_CONFIG = "update-project-workflow-integration-config",
|
||||||
|
|
||||||
GET_PROJECT_SSH_CONFIG = "get-project-ssh-config",
|
GET_PROJECT_SSH_CONFIG = "get-project-ssh-config",
|
||||||
UPDATE_PROJECT_SSH_CONFIG = "update-project-ssh-config",
|
UPDATE_PROJECT_SSH_CONFIG = "update-project-ssh-config",
|
||||||
INTEGRATION_SYNCED = "integration-synced",
|
INTEGRATION_SYNCED = "integration-synced",
|
||||||
@@ -321,6 +352,15 @@ export enum EventType {
|
|||||||
SECRET_ROTATION_ROTATE_SECRETS = "secret-rotation-rotate-secrets",
|
SECRET_ROTATION_ROTATE_SECRETS = "secret-rotation-rotate-secrets",
|
||||||
|
|
||||||
PROJECT_ACCESS_REQUEST = "project-access-request",
|
PROJECT_ACCESS_REQUEST = "project-access-request",
|
||||||
|
|
||||||
|
MICROSOFT_TEAMS_WORKFLOW_INTEGRATION_CREATE = "microsoft-teams-workflow-integration-create",
|
||||||
|
MICROSOFT_TEAMS_WORKFLOW_INTEGRATION_DELETE = "microsoft-teams-workflow-integration-delete",
|
||||||
|
MICROSOFT_TEAMS_WORKFLOW_INTEGRATION_UPDATE = "microsoft-teams-workflow-integration-update",
|
||||||
|
MICROSOFT_TEAMS_WORKFLOW_INTEGRATION_CHECK_INSTALLATION_STATUS = "microsoft-teams-workflow-integration-check-installation-status",
|
||||||
|
MICROSOFT_TEAMS_WORKFLOW_INTEGRATION_GET_TEAMS = "microsoft-teams-workflow-integration-get-teams",
|
||||||
|
MICROSOFT_TEAMS_WORKFLOW_INTEGRATION_GET = "microsoft-teams-workflow-integration-get",
|
||||||
|
MICROSOFT_TEAMS_WORKFLOW_INTEGRATION_LIST = "microsoft-teams-workflow-integration-list",
|
||||||
|
|
||||||
PROJECT_ASSUME_PRIVILEGE_SESSION_START = "project-assume-privileges-session-start",
|
PROJECT_ASSUME_PRIVILEGE_SESSION_START = "project-assume-privileges-session-start",
|
||||||
PROJECT_ASSUME_PRIVILEGE_SESSION_END = "project-assume-privileges-session-end"
|
PROJECT_ASSUME_PRIVILEGE_SESSION_END = "project-assume-privileges-session-end"
|
||||||
}
|
}
|
||||||
@@ -1011,6 +1051,55 @@ interface GetIdentityAzureAuthEvent {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface LoginIdentityLdapAuthEvent {
|
||||||
|
type: EventType.LOGIN_IDENTITY_LDAP_AUTH;
|
||||||
|
metadata: {
|
||||||
|
identityId: string;
|
||||||
|
ldapUsername: string;
|
||||||
|
ldapEmail?: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
interface AddIdentityLdapAuthEvent {
|
||||||
|
type: EventType.ADD_IDENTITY_LDAP_AUTH;
|
||||||
|
metadata: {
|
||||||
|
identityId: string;
|
||||||
|
accessTokenTTL?: number;
|
||||||
|
accessTokenMaxTTL?: number;
|
||||||
|
accessTokenNumUsesLimit?: number;
|
||||||
|
accessTokenTrustedIps?: Array<TIdentityTrustedIp>;
|
||||||
|
allowedFields?: TAllowedFields[];
|
||||||
|
url: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
interface UpdateIdentityLdapAuthEvent {
|
||||||
|
type: EventType.UPDATE_IDENTITY_LDAP_AUTH;
|
||||||
|
metadata: {
|
||||||
|
identityId: string;
|
||||||
|
accessTokenTTL?: number;
|
||||||
|
accessTokenMaxTTL?: number;
|
||||||
|
accessTokenNumUsesLimit?: number;
|
||||||
|
accessTokenTrustedIps?: Array<TIdentityTrustedIp>;
|
||||||
|
allowedFields?: TAllowedFields[];
|
||||||
|
url?: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
interface GetIdentityLdapAuthEvent {
|
||||||
|
type: EventType.GET_IDENTITY_LDAP_AUTH;
|
||||||
|
metadata: {
|
||||||
|
identityId: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
interface RevokeIdentityLdapAuthEvent {
|
||||||
|
type: EventType.REVOKE_IDENTITY_LDAP_AUTH;
|
||||||
|
metadata: {
|
||||||
|
identityId: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
interface LoginIdentityOidcAuthEvent {
|
interface LoginIdentityOidcAuthEvent {
|
||||||
type: EventType.LOGIN_IDENTITY_OIDC_AUTH;
|
type: EventType.LOGIN_IDENTITY_OIDC_AUTH;
|
||||||
metadata: {
|
metadata: {
|
||||||
@@ -1499,12 +1588,7 @@ interface CreateSshHost {
|
|||||||
alias: string | null;
|
alias: string | null;
|
||||||
userCertTtl: string;
|
userCertTtl: string;
|
||||||
hostCertTtl: string;
|
hostCertTtl: string;
|
||||||
loginMappings: {
|
loginMappings: TLoginMapping[];
|
||||||
loginUser: string;
|
|
||||||
allowedPrincipals: {
|
|
||||||
usernames: string[];
|
|
||||||
};
|
|
||||||
}[];
|
|
||||||
userSshCaId: string;
|
userSshCaId: string;
|
||||||
hostSshCaId: string;
|
hostSshCaId: string;
|
||||||
};
|
};
|
||||||
@@ -1518,12 +1602,7 @@ interface UpdateSshHost {
|
|||||||
alias?: string | null;
|
alias?: string | null;
|
||||||
userCertTtl?: string;
|
userCertTtl?: string;
|
||||||
hostCertTtl?: string;
|
hostCertTtl?: string;
|
||||||
loginMappings?: {
|
loginMappings?: TLoginMapping[];
|
||||||
loginUser: string;
|
|
||||||
allowedPrincipals: {
|
|
||||||
usernames: string[];
|
|
||||||
};
|
|
||||||
}[];
|
|
||||||
userSshCaId?: string;
|
userSshCaId?: string;
|
||||||
hostSshCaId?: string;
|
hostSshCaId?: string;
|
||||||
};
|
};
|
||||||
@@ -1567,6 +1646,66 @@ interface IssueSshHostHostCert {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface GetSshHostGroupEvent {
|
||||||
|
type: EventType.GET_SSH_HOST_GROUP;
|
||||||
|
metadata: {
|
||||||
|
sshHostGroupId: string;
|
||||||
|
name: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
interface CreateSshHostGroupEvent {
|
||||||
|
type: EventType.CREATE_SSH_HOST_GROUP;
|
||||||
|
metadata: {
|
||||||
|
sshHostGroupId: string;
|
||||||
|
name: string;
|
||||||
|
loginMappings: TLoginMapping[];
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
interface UpdateSshHostGroupEvent {
|
||||||
|
type: EventType.UPDATE_SSH_HOST_GROUP;
|
||||||
|
metadata: {
|
||||||
|
sshHostGroupId: string;
|
||||||
|
name?: string;
|
||||||
|
loginMappings?: TLoginMapping[];
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
interface DeleteSshHostGroupEvent {
|
||||||
|
type: EventType.DELETE_SSH_HOST_GROUP;
|
||||||
|
metadata: {
|
||||||
|
sshHostGroupId: string;
|
||||||
|
name: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
interface GetSshHostGroupHostsEvent {
|
||||||
|
type: EventType.GET_SSH_HOST_GROUP_HOSTS;
|
||||||
|
metadata: {
|
||||||
|
sshHostGroupId: string;
|
||||||
|
name: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
interface AddHostToSshHostGroupEvent {
|
||||||
|
type: EventType.ADD_HOST_TO_SSH_HOST_GROUP;
|
||||||
|
metadata: {
|
||||||
|
sshHostGroupId: string;
|
||||||
|
sshHostId: string;
|
||||||
|
hostname: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
interface RemoveHostFromSshHostGroupEvent {
|
||||||
|
type: EventType.REMOVE_HOST_FROM_SSH_HOST_GROUP;
|
||||||
|
metadata: {
|
||||||
|
sshHostGroupId: string;
|
||||||
|
sshHostId: string;
|
||||||
|
hostname: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
interface CreateCa {
|
interface CreateCa {
|
||||||
type: EventType.CREATE_CA;
|
type: EventType.CREATE_CA;
|
||||||
metadata: {
|
metadata: {
|
||||||
@@ -1719,6 +1858,24 @@ interface GetCertBody {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface GetCertPrivateKey {
|
||||||
|
type: EventType.GET_CERT_PRIVATE_KEY;
|
||||||
|
metadata: {
|
||||||
|
certId: string;
|
||||||
|
cn: string;
|
||||||
|
serialNumber: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
interface GetCertBundle {
|
||||||
|
type: EventType.GET_CERT_BUNDLE;
|
||||||
|
metadata: {
|
||||||
|
certId: string;
|
||||||
|
cn: string;
|
||||||
|
serialNumber: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
interface CreatePkiAlert {
|
interface CreatePkiAlert {
|
||||||
type: EventType.CREATE_PKI_ALERT;
|
type: EventType.CREATE_PKI_ALERT;
|
||||||
metadata: {
|
metadata: {
|
||||||
@@ -1980,22 +2137,24 @@ interface GetSlackIntegration {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
interface UpdateProjectSlackConfig {
|
interface UpdateProjectWorkflowIntegrationConfig {
|
||||||
type: EventType.UPDATE_PROJECT_SLACK_CONFIG;
|
type: EventType.UPDATE_PROJECT_WORKFLOW_INTEGRATION_CONFIG;
|
||||||
metadata: {
|
metadata: {
|
||||||
id: string;
|
id: string;
|
||||||
slackIntegrationId: string;
|
integrationId: string;
|
||||||
|
integration: WorkflowIntegration;
|
||||||
isAccessRequestNotificationEnabled: boolean;
|
isAccessRequestNotificationEnabled: boolean;
|
||||||
accessRequestChannels: string;
|
accessRequestChannels?: string | { teamId: string; channelIds: string[] };
|
||||||
isSecretRequestNotificationEnabled: boolean;
|
isSecretRequestNotificationEnabled: boolean;
|
||||||
secretRequestChannels: string;
|
secretRequestChannels?: string | { teamId: string; channelIds: string[] };
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
interface GetProjectSlackConfig {
|
interface GetProjectWorkflowIntegrationConfig {
|
||||||
type: EventType.GET_PROJECT_SLACK_CONFIG;
|
type: EventType.GET_PROJECT_WORKFLOW_INTEGRATION_CONFIG;
|
||||||
metadata: {
|
metadata: {
|
||||||
id: string;
|
id: string;
|
||||||
|
integration: WorkflowIntegration;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2561,6 +2720,66 @@ interface RotateSecretRotationEvent {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface MicrosoftTeamsWorkflowIntegrationCreateEvent {
|
||||||
|
type: EventType.MICROSOFT_TEAMS_WORKFLOW_INTEGRATION_CREATE;
|
||||||
|
metadata: {
|
||||||
|
tenantId: string;
|
||||||
|
slug: string;
|
||||||
|
description?: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
interface MicrosoftTeamsWorkflowIntegrationDeleteEvent {
|
||||||
|
type: EventType.MICROSOFT_TEAMS_WORKFLOW_INTEGRATION_DELETE;
|
||||||
|
metadata: {
|
||||||
|
tenantId: string;
|
||||||
|
id: string;
|
||||||
|
slug: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
interface MicrosoftTeamsWorkflowIntegrationCheckInstallationStatusEvent {
|
||||||
|
type: EventType.MICROSOFT_TEAMS_WORKFLOW_INTEGRATION_CHECK_INSTALLATION_STATUS;
|
||||||
|
metadata: {
|
||||||
|
tenantId: string;
|
||||||
|
slug: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
interface MicrosoftTeamsWorkflowIntegrationGetTeamsEvent {
|
||||||
|
type: EventType.MICROSOFT_TEAMS_WORKFLOW_INTEGRATION_GET_TEAMS;
|
||||||
|
metadata: {
|
||||||
|
tenantId: string;
|
||||||
|
slug: string;
|
||||||
|
id: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
interface MicrosoftTeamsWorkflowIntegrationGetEvent {
|
||||||
|
type: EventType.MICROSOFT_TEAMS_WORKFLOW_INTEGRATION_GET;
|
||||||
|
metadata: {
|
||||||
|
tenantId: string;
|
||||||
|
slug: string;
|
||||||
|
id: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
interface MicrosoftTeamsWorkflowIntegrationListEvent {
|
||||||
|
type: EventType.MICROSOFT_TEAMS_WORKFLOW_INTEGRATION_LIST;
|
||||||
|
metadata: Record<string, string>;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface MicrosoftTeamsWorkflowIntegrationUpdateEvent {
|
||||||
|
type: EventType.MICROSOFT_TEAMS_WORKFLOW_INTEGRATION_UPDATE;
|
||||||
|
metadata: {
|
||||||
|
tenantId: string;
|
||||||
|
slug: string;
|
||||||
|
id: string;
|
||||||
|
newSlug?: string;
|
||||||
|
newDescription?: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export type Event =
|
export type Event =
|
||||||
| GetSecretsEvent
|
| GetSecretsEvent
|
||||||
| GetSecretEvent
|
| GetSecretEvent
|
||||||
@@ -2632,6 +2851,11 @@ export type Event =
|
|||||||
| UpdateIdentityJwtAuthEvent
|
| UpdateIdentityJwtAuthEvent
|
||||||
| GetIdentityJwtAuthEvent
|
| GetIdentityJwtAuthEvent
|
||||||
| DeleteIdentityJwtAuthEvent
|
| DeleteIdentityJwtAuthEvent
|
||||||
|
| LoginIdentityLdapAuthEvent
|
||||||
|
| AddIdentityLdapAuthEvent
|
||||||
|
| UpdateIdentityLdapAuthEvent
|
||||||
|
| GetIdentityLdapAuthEvent
|
||||||
|
| RevokeIdentityLdapAuthEvent
|
||||||
| CreateEnvironmentEvent
|
| CreateEnvironmentEvent
|
||||||
| GetEnvironmentEvent
|
| GetEnvironmentEvent
|
||||||
| UpdateEnvironmentEvent
|
| UpdateEnvironmentEvent
|
||||||
@@ -2691,6 +2915,8 @@ export type Event =
|
|||||||
| DeleteCert
|
| DeleteCert
|
||||||
| RevokeCert
|
| RevokeCert
|
||||||
| GetCertBody
|
| GetCertBody
|
||||||
|
| GetCertPrivateKey
|
||||||
|
| GetCertBundle
|
||||||
| CreatePkiAlert
|
| CreatePkiAlert
|
||||||
| GetPkiAlert
|
| GetPkiAlert
|
||||||
| UpdatePkiAlert
|
| UpdatePkiAlert
|
||||||
@@ -2723,8 +2949,8 @@ export type Event =
|
|||||||
| UpdateSlackIntegration
|
| UpdateSlackIntegration
|
||||||
| DeleteSlackIntegration
|
| DeleteSlackIntegration
|
||||||
| GetSlackIntegration
|
| GetSlackIntegration
|
||||||
| UpdateProjectSlackConfig
|
| UpdateProjectWorkflowIntegrationConfig
|
||||||
| GetProjectSlackConfig
|
| GetProjectWorkflowIntegrationConfig
|
||||||
| GetProjectSshConfig
|
| GetProjectSshConfig
|
||||||
| UpdateProjectSshConfig
|
| UpdateProjectSshConfig
|
||||||
| IntegrationSyncedEvent
|
| IntegrationSyncedEvent
|
||||||
@@ -2753,6 +2979,13 @@ export type Event =
|
|||||||
| CreateAppConnectionEvent
|
| CreateAppConnectionEvent
|
||||||
| UpdateAppConnectionEvent
|
| UpdateAppConnectionEvent
|
||||||
| DeleteAppConnectionEvent
|
| DeleteAppConnectionEvent
|
||||||
|
| GetSshHostGroupEvent
|
||||||
|
| CreateSshHostGroupEvent
|
||||||
|
| UpdateSshHostGroupEvent
|
||||||
|
| DeleteSshHostGroupEvent
|
||||||
|
| GetSshHostGroupHostsEvent
|
||||||
|
| AddHostToSshHostGroupEvent
|
||||||
|
| RemoveHostFromSshHostGroupEvent
|
||||||
| CreateSharedSecretEvent
|
| CreateSharedSecretEvent
|
||||||
| DeleteSharedSecretEvent
|
| DeleteSharedSecretEvent
|
||||||
| ReadSharedSecretEvent
|
| ReadSharedSecretEvent
|
||||||
@@ -2794,4 +3027,11 @@ export type Event =
|
|||||||
| CreateSecretRotationEvent
|
| CreateSecretRotationEvent
|
||||||
| UpdateSecretRotationEvent
|
| UpdateSecretRotationEvent
|
||||||
| DeleteSecretRotationEvent
|
| DeleteSecretRotationEvent
|
||||||
| RotateSecretRotationEvent;
|
| RotateSecretRotationEvent
|
||||||
|
| MicrosoftTeamsWorkflowIntegrationCreateEvent
|
||||||
|
| MicrosoftTeamsWorkflowIntegrationDeleteEvent
|
||||||
|
| MicrosoftTeamsWorkflowIntegrationCheckInstallationStatusEvent
|
||||||
|
| MicrosoftTeamsWorkflowIntegrationGetTeamsEvent
|
||||||
|
| MicrosoftTeamsWorkflowIntegrationGetEvent
|
||||||
|
| MicrosoftTeamsWorkflowIntegrationListEvent
|
||||||
|
| MicrosoftTeamsWorkflowIntegrationUpdateEvent;
|
||||||
|
@@ -24,8 +24,16 @@ export const verifyHostInputValidity = async (host: string, isGateway = false) =
|
|||||||
if (net.isIPv4(el)) {
|
if (net.isIPv4(el)) {
|
||||||
exclusiveIps.push(el);
|
exclusiveIps.push(el);
|
||||||
} else {
|
} else {
|
||||||
const resolvedIps = await dns.resolve4(el);
|
try {
|
||||||
exclusiveIps.push(...resolvedIps);
|
const resolvedIps = await dns.resolve4(el);
|
||||||
|
exclusiveIps.push(...resolvedIps);
|
||||||
|
} catch (error) {
|
||||||
|
// only try lookup if not found
|
||||||
|
if ((error as { code: string })?.code !== "ENOTFOUND") throw error;
|
||||||
|
|
||||||
|
const resolvedIps = (await dns.lookup(el, { all: true, family: 4 })).map(({ address }) => address);
|
||||||
|
exclusiveIps.push(...resolvedIps);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -38,8 +46,16 @@ export const verifyHostInputValidity = async (host: string, isGateway = false) =
|
|||||||
if (normalizedHost === "localhost" || normalizedHost === "host.docker.internal") {
|
if (normalizedHost === "localhost" || normalizedHost === "host.docker.internal") {
|
||||||
throw new BadRequestError({ message: "Invalid db host" });
|
throw new BadRequestError({ message: "Invalid db host" });
|
||||||
}
|
}
|
||||||
const resolvedIps = await dns.resolve4(host);
|
try {
|
||||||
inputHostIps.push(...resolvedIps);
|
const resolvedIps = await dns.resolve4(host);
|
||||||
|
inputHostIps.push(...resolvedIps);
|
||||||
|
} catch (error) {
|
||||||
|
// only try lookup if not found
|
||||||
|
if ((error as { code: string })?.code !== "ENOTFOUND") throw error;
|
||||||
|
|
||||||
|
const resolvedIps = (await dns.lookup(host, { all: true, family: 4 })).map(({ address }) => address);
|
||||||
|
inputHostIps.push(...resolvedIps);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isGateway && !(appCfg.DYNAMIC_SECRET_ALLOW_INTERNAL_IP || appCfg.ALLOW_INTERNAL_IP_CONNECTIONS)) {
|
if (!isGateway && !(appCfg.DYNAMIC_SECRET_ALLOW_INTERNAL_IP || appCfg.ALLOW_INTERNAL_IP_CONNECTIONS)) {
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
import { ForbiddenError } from "@casl/ability";
|
import { ForbiddenError } from "@casl/ability";
|
||||||
import { Octokit } from "@octokit/core";
|
import { Octokit } from "@octokit/core";
|
||||||
import { paginateGraphQL } from "@octokit/plugin-paginate-graphql";
|
import { paginateGraphql } from "@octokit/plugin-paginate-graphql";
|
||||||
import { Octokit as OctokitRest } from "@octokit/rest";
|
import { Octokit as OctokitRest } from "@octokit/rest";
|
||||||
|
|
||||||
import { OrgMembershipRole } from "@app/db/schemas";
|
import { OrgMembershipRole } from "@app/db/schemas";
|
||||||
@@ -18,7 +18,7 @@ import { TPermissionServiceFactory } from "../permission/permission-service";
|
|||||||
import { TGithubOrgSyncDALFactory } from "./github-org-sync-dal";
|
import { TGithubOrgSyncDALFactory } from "./github-org-sync-dal";
|
||||||
import { TCreateGithubOrgSyncDTO, TDeleteGithubOrgSyncDTO, TUpdateGithubOrgSyncDTO } from "./github-org-sync-types";
|
import { TCreateGithubOrgSyncDTO, TDeleteGithubOrgSyncDTO, TUpdateGithubOrgSyncDTO } from "./github-org-sync-types";
|
||||||
|
|
||||||
const OctokitWithPlugin = Octokit.plugin(paginateGraphQL);
|
const OctokitWithPlugin = Octokit.plugin(paginateGraphql);
|
||||||
|
|
||||||
type TGithubOrgSyncServiceFactoryDep = {
|
type TGithubOrgSyncServiceFactoryDep = {
|
||||||
githubOrgSyncDAL: TGithubOrgSyncDALFactory;
|
githubOrgSyncDAL: TGithubOrgSyncDALFactory;
|
||||||
|
@@ -153,7 +153,19 @@ export const groupDALFactory = (db: TDbClient) => {
|
|||||||
totalCount: Number(members?.[0]?.total_count ?? 0)
|
totalCount: Number(members?.[0]?.total_count ?? 0)
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw new DatabaseError({ error, name: "Find all org members" });
|
throw new DatabaseError({ error, name: "Find all user group members" });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const findGroupsByProjectId = async (projectId: string, tx?: Knex) => {
|
||||||
|
try {
|
||||||
|
const docs = await (tx || db.replicaNode())(TableName.Groups)
|
||||||
|
.join(TableName.GroupProjectMembership, `${TableName.Groups}.id`, `${TableName.GroupProjectMembership}.groupId`)
|
||||||
|
.where(`${TableName.GroupProjectMembership}.projectId`, projectId)
|
||||||
|
.select(selectAllTableCols(TableName.Groups));
|
||||||
|
return docs;
|
||||||
|
} catch (error) {
|
||||||
|
throw new DatabaseError({ error, name: "Find groups by project id" });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -161,6 +173,7 @@ export const groupDALFactory = (db: TDbClient) => {
|
|||||||
findGroups,
|
findGroups,
|
||||||
findByOrgId,
|
findByOrgId,
|
||||||
findAllGroupPossibleMembers,
|
findAllGroupPossibleMembers,
|
||||||
|
findGroupsByProjectId,
|
||||||
...groupOrm
|
...groupOrm
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@@ -176,7 +176,8 @@ export const userGroupMembershipDALFactory = (db: TDbClient) => {
|
|||||||
db.ref("name").withSchema(TableName.Groups).as("groupName"),
|
db.ref("name").withSchema(TableName.Groups).as("groupName"),
|
||||||
db.ref("id").withSchema(TableName.OrgMembership).as("orgMembershipId"),
|
db.ref("id").withSchema(TableName.OrgMembership).as("orgMembershipId"),
|
||||||
db.ref("firstName").withSchema(TableName.Users).as("firstName"),
|
db.ref("firstName").withSchema(TableName.Users).as("firstName"),
|
||||||
db.ref("lastName").withSchema(TableName.Users).as("lastName")
|
db.ref("lastName").withSchema(TableName.Users).as("lastName"),
|
||||||
|
db.ref("slug").withSchema(TableName.Groups).as("groupSlug")
|
||||||
);
|
);
|
||||||
|
|
||||||
return docs;
|
return docs;
|
||||||
|
@@ -14,6 +14,11 @@ export type TLDAPConfig = {
|
|||||||
caCert: string;
|
caCert: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type TTestLDAPConfigDTO = Omit<
|
||||||
|
TLDAPConfig,
|
||||||
|
"organization" | "id" | "groupSearchBase" | "groupSearchFilter" | "isActive" | "uniqueUserAttribute" | "searchBase"
|
||||||
|
>;
|
||||||
|
|
||||||
export type TCreateLdapCfgDTO = {
|
export type TCreateLdapCfgDTO = {
|
||||||
orgId: string;
|
orgId: string;
|
||||||
isActive: boolean;
|
isActive: boolean;
|
||||||
|
@@ -2,15 +2,14 @@ import ldapjs from "ldapjs";
|
|||||||
|
|
||||||
import { logger } from "@app/lib/logger";
|
import { logger } from "@app/lib/logger";
|
||||||
|
|
||||||
import { TLDAPConfig } from "./ldap-config-types";
|
import { TLDAPConfig, TTestLDAPConfigDTO } from "./ldap-config-types";
|
||||||
|
|
||||||
export const isValidLdapFilter = (filter: string) => {
|
export const isValidLdapFilter = (filter: string) => {
|
||||||
try {
|
try {
|
||||||
ldapjs.parseFilter(filter);
|
ldapjs.parseFilter(filter);
|
||||||
return true;
|
return true;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error("Invalid LDAP filter");
|
logger.error(error, "Invalid LDAP filter");
|
||||||
logger.error(error);
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -20,7 +19,7 @@ export const isValidLdapFilter = (filter: string) => {
|
|||||||
* @param ldapConfig - The LDAP configuration to test
|
* @param ldapConfig - The LDAP configuration to test
|
||||||
* @returns {Boolean} isConnected - Whether or not the connection was successful
|
* @returns {Boolean} isConnected - Whether or not the connection was successful
|
||||||
*/
|
*/
|
||||||
export const testLDAPConfig = async (ldapConfig: TLDAPConfig): Promise<boolean> => {
|
export const testLDAPConfig = async (ldapConfig: TTestLDAPConfigDTO): Promise<boolean> => {
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
const ldapClient = ldapjs.createClient({
|
const ldapClient = ldapjs.createClient({
|
||||||
url: ldapConfig.url,
|
url: ldapConfig.url,
|
||||||
|
@@ -28,7 +28,8 @@ export const getDefaultOnPremFeatures = () => {
|
|||||||
has_used_trial: true,
|
has_used_trial: true,
|
||||||
secretApproval: true,
|
secretApproval: true,
|
||||||
secretRotation: true,
|
secretRotation: true,
|
||||||
caCrl: false
|
caCrl: false,
|
||||||
|
sshHostGroups: false
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@@ -10,6 +10,7 @@ export const BillingPlanRows = {
|
|||||||
CustomAlerts: { name: "Custom alerts", field: "customAlerts" },
|
CustomAlerts: { name: "Custom alerts", field: "customAlerts" },
|
||||||
AuditLogs: { name: "Audit logs", field: "auditLogs" },
|
AuditLogs: { name: "Audit logs", field: "auditLogs" },
|
||||||
SamlSSO: { name: "SAML SSO", field: "samlSSO" },
|
SamlSSO: { name: "SAML SSO", field: "samlSSO" },
|
||||||
|
SshHostGroups: { name: "SSH Host Groups", field: "sshHostGroups" },
|
||||||
Hsm: { name: "Hardware Security Module (HSM)", field: "hsm" },
|
Hsm: { name: "Hardware Security Module (HSM)", field: "hsm" },
|
||||||
OidcSSO: { name: "OIDC SSO", field: "oidcSSO" },
|
OidcSSO: { name: "OIDC SSO", field: "oidcSSO" },
|
||||||
SecretApproval: { name: "Secret approvals", field: "secretApproval" },
|
SecretApproval: { name: "Secret approvals", field: "secretApproval" },
|
||||||
|
@@ -53,7 +53,8 @@ export const getDefaultOnPremFeatures = (): TFeatureSet => ({
|
|||||||
enforceMfa: false,
|
enforceMfa: false,
|
||||||
projectTemplates: false,
|
projectTemplates: false,
|
||||||
kmip: false,
|
kmip: false,
|
||||||
gateway: false
|
gateway: false,
|
||||||
|
sshHostGroups: false
|
||||||
});
|
});
|
||||||
|
|
||||||
export const setupLicenseRequestWithStore = (baseURL: string, refreshUrl: string, licenseKey: string) => {
|
export const setupLicenseRequestWithStore = (baseURL: string, refreshUrl: string, licenseKey: string) => {
|
||||||
|
@@ -71,6 +71,7 @@ export type TFeatureSet = {
|
|||||||
projectTemplates: false;
|
projectTemplates: false;
|
||||||
kmip: false;
|
kmip: false;
|
||||||
gateway: false;
|
gateway: false;
|
||||||
|
sshHostGroups: false;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TOrgPlansTableDTO = {
|
export type TOrgPlansTableDTO = {
|
||||||
|
448
backend/src/ee/services/permission/default-roles.ts
Normal file
448
backend/src/ee/services/permission/default-roles.ts
Normal file
@@ -0,0 +1,448 @@
|
|||||||
|
import { AbilityBuilder, createMongoAbility, MongoAbility } from "@casl/ability";
|
||||||
|
|
||||||
|
import {
|
||||||
|
ProjectPermissionActions,
|
||||||
|
ProjectPermissionCertificateActions,
|
||||||
|
ProjectPermissionCmekActions,
|
||||||
|
ProjectPermissionDynamicSecretActions,
|
||||||
|
ProjectPermissionGroupActions,
|
||||||
|
ProjectPermissionIdentityActions,
|
||||||
|
ProjectPermissionKmipActions,
|
||||||
|
ProjectPermissionMemberActions,
|
||||||
|
ProjectPermissionSecretActions,
|
||||||
|
ProjectPermissionSecretRotationActions,
|
||||||
|
ProjectPermissionSecretSyncActions,
|
||||||
|
ProjectPermissionSet,
|
||||||
|
ProjectPermissionSshHostActions,
|
||||||
|
ProjectPermissionSub
|
||||||
|
} from "@app/ee/services/permission/project-permission";
|
||||||
|
|
||||||
|
const buildAdminPermissionRules = () => {
|
||||||
|
const { can, rules } = new AbilityBuilder<MongoAbility<ProjectPermissionSet>>(createMongoAbility);
|
||||||
|
|
||||||
|
// Admins get full access to everything
|
||||||
|
[
|
||||||
|
ProjectPermissionSub.SecretFolders,
|
||||||
|
ProjectPermissionSub.SecretImports,
|
||||||
|
ProjectPermissionSub.SecretApproval,
|
||||||
|
ProjectPermissionSub.Role,
|
||||||
|
ProjectPermissionSub.Integrations,
|
||||||
|
ProjectPermissionSub.Webhooks,
|
||||||
|
ProjectPermissionSub.ServiceTokens,
|
||||||
|
ProjectPermissionSub.Settings,
|
||||||
|
ProjectPermissionSub.Environments,
|
||||||
|
ProjectPermissionSub.Tags,
|
||||||
|
ProjectPermissionSub.AuditLogs,
|
||||||
|
ProjectPermissionSub.IpAllowList,
|
||||||
|
ProjectPermissionSub.CertificateAuthorities,
|
||||||
|
ProjectPermissionSub.CertificateTemplates,
|
||||||
|
ProjectPermissionSub.PkiAlerts,
|
||||||
|
ProjectPermissionSub.PkiCollections,
|
||||||
|
ProjectPermissionSub.SshCertificateAuthorities,
|
||||||
|
ProjectPermissionSub.SshCertificates,
|
||||||
|
ProjectPermissionSub.SshCertificateTemplates,
|
||||||
|
ProjectPermissionSub.SshHostGroups
|
||||||
|
].forEach((el) => {
|
||||||
|
can(
|
||||||
|
[
|
||||||
|
ProjectPermissionActions.Read,
|
||||||
|
ProjectPermissionActions.Edit,
|
||||||
|
ProjectPermissionActions.Create,
|
||||||
|
ProjectPermissionActions.Delete
|
||||||
|
],
|
||||||
|
el
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
can(
|
||||||
|
[
|
||||||
|
ProjectPermissionCertificateActions.Read,
|
||||||
|
ProjectPermissionCertificateActions.Edit,
|
||||||
|
ProjectPermissionCertificateActions.Create,
|
||||||
|
ProjectPermissionCertificateActions.Delete,
|
||||||
|
ProjectPermissionCertificateActions.ReadPrivateKey
|
||||||
|
],
|
||||||
|
ProjectPermissionSub.Certificates
|
||||||
|
);
|
||||||
|
|
||||||
|
can(
|
||||||
|
[
|
||||||
|
ProjectPermissionSshHostActions.Edit,
|
||||||
|
ProjectPermissionSshHostActions.Read,
|
||||||
|
ProjectPermissionSshHostActions.Create,
|
||||||
|
ProjectPermissionSshHostActions.Delete,
|
||||||
|
ProjectPermissionSshHostActions.IssueHostCert
|
||||||
|
],
|
||||||
|
ProjectPermissionSub.SshHosts
|
||||||
|
);
|
||||||
|
|
||||||
|
can(
|
||||||
|
[
|
||||||
|
ProjectPermissionMemberActions.Create,
|
||||||
|
ProjectPermissionMemberActions.Edit,
|
||||||
|
ProjectPermissionMemberActions.Delete,
|
||||||
|
ProjectPermissionMemberActions.Read,
|
||||||
|
ProjectPermissionMemberActions.GrantPrivileges,
|
||||||
|
ProjectPermissionMemberActions.AssumePrivileges
|
||||||
|
],
|
||||||
|
ProjectPermissionSub.Member
|
||||||
|
);
|
||||||
|
|
||||||
|
can(
|
||||||
|
[
|
||||||
|
ProjectPermissionGroupActions.Create,
|
||||||
|
ProjectPermissionGroupActions.Edit,
|
||||||
|
ProjectPermissionGroupActions.Delete,
|
||||||
|
ProjectPermissionGroupActions.Read,
|
||||||
|
ProjectPermissionGroupActions.GrantPrivileges
|
||||||
|
],
|
||||||
|
ProjectPermissionSub.Groups
|
||||||
|
);
|
||||||
|
|
||||||
|
can(
|
||||||
|
[
|
||||||
|
ProjectPermissionIdentityActions.Create,
|
||||||
|
ProjectPermissionIdentityActions.Edit,
|
||||||
|
ProjectPermissionIdentityActions.Delete,
|
||||||
|
ProjectPermissionIdentityActions.Read,
|
||||||
|
ProjectPermissionIdentityActions.GrantPrivileges,
|
||||||
|
ProjectPermissionIdentityActions.AssumePrivileges
|
||||||
|
],
|
||||||
|
ProjectPermissionSub.Identity
|
||||||
|
);
|
||||||
|
|
||||||
|
can(
|
||||||
|
[
|
||||||
|
ProjectPermissionSecretActions.DescribeAndReadValue,
|
||||||
|
ProjectPermissionSecretActions.DescribeSecret,
|
||||||
|
ProjectPermissionSecretActions.ReadValue,
|
||||||
|
ProjectPermissionSecretActions.Create,
|
||||||
|
ProjectPermissionSecretActions.Edit,
|
||||||
|
ProjectPermissionSecretActions.Delete
|
||||||
|
],
|
||||||
|
ProjectPermissionSub.Secrets
|
||||||
|
);
|
||||||
|
|
||||||
|
can(
|
||||||
|
[
|
||||||
|
ProjectPermissionDynamicSecretActions.ReadRootCredential,
|
||||||
|
ProjectPermissionDynamicSecretActions.EditRootCredential,
|
||||||
|
ProjectPermissionDynamicSecretActions.CreateRootCredential,
|
||||||
|
ProjectPermissionDynamicSecretActions.DeleteRootCredential,
|
||||||
|
ProjectPermissionDynamicSecretActions.Lease
|
||||||
|
],
|
||||||
|
ProjectPermissionSub.DynamicSecrets
|
||||||
|
);
|
||||||
|
|
||||||
|
can([ProjectPermissionActions.Edit, ProjectPermissionActions.Delete], ProjectPermissionSub.Project);
|
||||||
|
can([ProjectPermissionActions.Read, ProjectPermissionActions.Create], ProjectPermissionSub.SecretRollback);
|
||||||
|
can([ProjectPermissionActions.Edit], ProjectPermissionSub.Kms);
|
||||||
|
can(
|
||||||
|
[
|
||||||
|
ProjectPermissionCmekActions.Create,
|
||||||
|
ProjectPermissionCmekActions.Edit,
|
||||||
|
ProjectPermissionCmekActions.Delete,
|
||||||
|
ProjectPermissionCmekActions.Read,
|
||||||
|
ProjectPermissionCmekActions.Encrypt,
|
||||||
|
ProjectPermissionCmekActions.Decrypt,
|
||||||
|
ProjectPermissionCmekActions.Sign,
|
||||||
|
ProjectPermissionCmekActions.Verify
|
||||||
|
],
|
||||||
|
ProjectPermissionSub.Cmek
|
||||||
|
);
|
||||||
|
can(
|
||||||
|
[
|
||||||
|
ProjectPermissionSecretSyncActions.Create,
|
||||||
|
ProjectPermissionSecretSyncActions.Edit,
|
||||||
|
ProjectPermissionSecretSyncActions.Delete,
|
||||||
|
ProjectPermissionSecretSyncActions.Read,
|
||||||
|
ProjectPermissionSecretSyncActions.SyncSecrets,
|
||||||
|
ProjectPermissionSecretSyncActions.ImportSecrets,
|
||||||
|
ProjectPermissionSecretSyncActions.RemoveSecrets
|
||||||
|
],
|
||||||
|
ProjectPermissionSub.SecretSyncs
|
||||||
|
);
|
||||||
|
|
||||||
|
can(
|
||||||
|
[
|
||||||
|
ProjectPermissionKmipActions.CreateClients,
|
||||||
|
ProjectPermissionKmipActions.UpdateClients,
|
||||||
|
ProjectPermissionKmipActions.DeleteClients,
|
||||||
|
ProjectPermissionKmipActions.ReadClients,
|
||||||
|
ProjectPermissionKmipActions.GenerateClientCertificates
|
||||||
|
],
|
||||||
|
ProjectPermissionSub.Kmip
|
||||||
|
);
|
||||||
|
|
||||||
|
can(
|
||||||
|
[
|
||||||
|
ProjectPermissionSecretRotationActions.Create,
|
||||||
|
ProjectPermissionSecretRotationActions.Edit,
|
||||||
|
ProjectPermissionSecretRotationActions.Delete,
|
||||||
|
ProjectPermissionSecretRotationActions.Read,
|
||||||
|
ProjectPermissionSecretRotationActions.ReadGeneratedCredentials,
|
||||||
|
ProjectPermissionSecretRotationActions.RotateSecrets
|
||||||
|
],
|
||||||
|
ProjectPermissionSub.SecretRotation
|
||||||
|
);
|
||||||
|
|
||||||
|
return rules;
|
||||||
|
};
|
||||||
|
|
||||||
|
const buildMemberPermissionRules = () => {
|
||||||
|
const { can, rules } = new AbilityBuilder<MongoAbility<ProjectPermissionSet>>(createMongoAbility);
|
||||||
|
|
||||||
|
can(
|
||||||
|
[
|
||||||
|
ProjectPermissionSecretActions.DescribeAndReadValue,
|
||||||
|
ProjectPermissionSecretActions.DescribeSecret,
|
||||||
|
ProjectPermissionSecretActions.ReadValue,
|
||||||
|
ProjectPermissionSecretActions.Edit,
|
||||||
|
ProjectPermissionSecretActions.Create,
|
||||||
|
ProjectPermissionSecretActions.Delete
|
||||||
|
],
|
||||||
|
ProjectPermissionSub.Secrets
|
||||||
|
);
|
||||||
|
can(
|
||||||
|
[
|
||||||
|
ProjectPermissionActions.Read,
|
||||||
|
ProjectPermissionActions.Edit,
|
||||||
|
ProjectPermissionActions.Create,
|
||||||
|
ProjectPermissionActions.Delete
|
||||||
|
],
|
||||||
|
ProjectPermissionSub.SecretFolders
|
||||||
|
);
|
||||||
|
can(
|
||||||
|
[
|
||||||
|
ProjectPermissionDynamicSecretActions.ReadRootCredential,
|
||||||
|
ProjectPermissionDynamicSecretActions.EditRootCredential,
|
||||||
|
ProjectPermissionDynamicSecretActions.CreateRootCredential,
|
||||||
|
ProjectPermissionDynamicSecretActions.DeleteRootCredential,
|
||||||
|
ProjectPermissionDynamicSecretActions.Lease
|
||||||
|
],
|
||||||
|
ProjectPermissionSub.DynamicSecrets
|
||||||
|
);
|
||||||
|
can(
|
||||||
|
[
|
||||||
|
ProjectPermissionActions.Read,
|
||||||
|
ProjectPermissionActions.Edit,
|
||||||
|
ProjectPermissionActions.Create,
|
||||||
|
ProjectPermissionActions.Delete
|
||||||
|
],
|
||||||
|
ProjectPermissionSub.SecretImports
|
||||||
|
);
|
||||||
|
|
||||||
|
can([ProjectPermissionActions.Read], ProjectPermissionSub.SecretApproval);
|
||||||
|
can([ProjectPermissionSecretRotationActions.Read], ProjectPermissionSub.SecretRotation);
|
||||||
|
|
||||||
|
can([ProjectPermissionActions.Read, ProjectPermissionActions.Create], ProjectPermissionSub.SecretRollback);
|
||||||
|
|
||||||
|
can([ProjectPermissionMemberActions.Read, ProjectPermissionMemberActions.Create], ProjectPermissionSub.Member);
|
||||||
|
|
||||||
|
can([ProjectPermissionGroupActions.Read], ProjectPermissionSub.Groups);
|
||||||
|
|
||||||
|
can(
|
||||||
|
[
|
||||||
|
ProjectPermissionActions.Read,
|
||||||
|
ProjectPermissionActions.Edit,
|
||||||
|
ProjectPermissionActions.Create,
|
||||||
|
ProjectPermissionActions.Delete
|
||||||
|
],
|
||||||
|
ProjectPermissionSub.Integrations
|
||||||
|
);
|
||||||
|
|
||||||
|
can(
|
||||||
|
[
|
||||||
|
ProjectPermissionActions.Read,
|
||||||
|
ProjectPermissionActions.Edit,
|
||||||
|
ProjectPermissionActions.Create,
|
||||||
|
ProjectPermissionActions.Delete
|
||||||
|
],
|
||||||
|
ProjectPermissionSub.Webhooks
|
||||||
|
);
|
||||||
|
|
||||||
|
can(
|
||||||
|
[
|
||||||
|
ProjectPermissionIdentityActions.Read,
|
||||||
|
ProjectPermissionIdentityActions.Edit,
|
||||||
|
ProjectPermissionIdentityActions.Create,
|
||||||
|
ProjectPermissionIdentityActions.Delete
|
||||||
|
],
|
||||||
|
ProjectPermissionSub.Identity
|
||||||
|
);
|
||||||
|
|
||||||
|
can(
|
||||||
|
[
|
||||||
|
ProjectPermissionActions.Read,
|
||||||
|
ProjectPermissionActions.Edit,
|
||||||
|
ProjectPermissionActions.Create,
|
||||||
|
ProjectPermissionActions.Delete
|
||||||
|
],
|
||||||
|
ProjectPermissionSub.ServiceTokens
|
||||||
|
);
|
||||||
|
|
||||||
|
can(
|
||||||
|
[
|
||||||
|
ProjectPermissionActions.Read,
|
||||||
|
ProjectPermissionActions.Edit,
|
||||||
|
ProjectPermissionActions.Create,
|
||||||
|
ProjectPermissionActions.Delete
|
||||||
|
],
|
||||||
|
ProjectPermissionSub.Settings
|
||||||
|
);
|
||||||
|
|
||||||
|
can(
|
||||||
|
[
|
||||||
|
ProjectPermissionActions.Read,
|
||||||
|
ProjectPermissionActions.Edit,
|
||||||
|
ProjectPermissionActions.Create,
|
||||||
|
ProjectPermissionActions.Delete
|
||||||
|
],
|
||||||
|
ProjectPermissionSub.Environments
|
||||||
|
);
|
||||||
|
|
||||||
|
can(
|
||||||
|
[
|
||||||
|
ProjectPermissionActions.Read,
|
||||||
|
ProjectPermissionActions.Edit,
|
||||||
|
ProjectPermissionActions.Create,
|
||||||
|
ProjectPermissionActions.Delete
|
||||||
|
],
|
||||||
|
ProjectPermissionSub.Tags
|
||||||
|
);
|
||||||
|
|
||||||
|
can([ProjectPermissionActions.Read], ProjectPermissionSub.Role);
|
||||||
|
can([ProjectPermissionActions.Read], ProjectPermissionSub.AuditLogs);
|
||||||
|
can([ProjectPermissionActions.Read], ProjectPermissionSub.IpAllowList);
|
||||||
|
|
||||||
|
// double check if all CRUD are needed for CA and Certificates
|
||||||
|
can([ProjectPermissionActions.Read], ProjectPermissionSub.CertificateAuthorities);
|
||||||
|
|
||||||
|
can(
|
||||||
|
[
|
||||||
|
ProjectPermissionCertificateActions.Read,
|
||||||
|
ProjectPermissionCertificateActions.Edit,
|
||||||
|
ProjectPermissionCertificateActions.Create,
|
||||||
|
ProjectPermissionCertificateActions.Delete
|
||||||
|
],
|
||||||
|
ProjectPermissionSub.Certificates
|
||||||
|
);
|
||||||
|
|
||||||
|
can([ProjectPermissionActions.Read], ProjectPermissionSub.CertificateTemplates);
|
||||||
|
|
||||||
|
can([ProjectPermissionActions.Read], ProjectPermissionSub.PkiAlerts);
|
||||||
|
can([ProjectPermissionActions.Read], ProjectPermissionSub.PkiCollections);
|
||||||
|
|
||||||
|
can([ProjectPermissionActions.Read], ProjectPermissionSub.SshCertificates);
|
||||||
|
can([ProjectPermissionActions.Create], ProjectPermissionSub.SshCertificates);
|
||||||
|
can([ProjectPermissionActions.Read], ProjectPermissionSub.SshCertificateTemplates);
|
||||||
|
|
||||||
|
can([ProjectPermissionSshHostActions.Read], ProjectPermissionSub.SshHosts);
|
||||||
|
|
||||||
|
can(
|
||||||
|
[
|
||||||
|
ProjectPermissionCmekActions.Create,
|
||||||
|
ProjectPermissionCmekActions.Edit,
|
||||||
|
ProjectPermissionCmekActions.Delete,
|
||||||
|
ProjectPermissionCmekActions.Read,
|
||||||
|
ProjectPermissionCmekActions.Encrypt,
|
||||||
|
ProjectPermissionCmekActions.Decrypt,
|
||||||
|
ProjectPermissionCmekActions.Sign,
|
||||||
|
ProjectPermissionCmekActions.Verify
|
||||||
|
],
|
||||||
|
ProjectPermissionSub.Cmek
|
||||||
|
);
|
||||||
|
|
||||||
|
can(
|
||||||
|
[
|
||||||
|
ProjectPermissionSecretSyncActions.Create,
|
||||||
|
ProjectPermissionSecretSyncActions.Edit,
|
||||||
|
ProjectPermissionSecretSyncActions.Delete,
|
||||||
|
ProjectPermissionSecretSyncActions.Read,
|
||||||
|
ProjectPermissionSecretSyncActions.SyncSecrets,
|
||||||
|
ProjectPermissionSecretSyncActions.ImportSecrets,
|
||||||
|
ProjectPermissionSecretSyncActions.RemoveSecrets
|
||||||
|
],
|
||||||
|
ProjectPermissionSub.SecretSyncs
|
||||||
|
);
|
||||||
|
|
||||||
|
return rules;
|
||||||
|
};
|
||||||
|
|
||||||
|
const buildViewerPermissionRules = () => {
|
||||||
|
const { can, rules } = new AbilityBuilder<MongoAbility<ProjectPermissionSet>>(createMongoAbility);
|
||||||
|
|
||||||
|
can(ProjectPermissionSecretActions.DescribeAndReadValue, ProjectPermissionSub.Secrets);
|
||||||
|
can(ProjectPermissionSecretActions.DescribeSecret, ProjectPermissionSub.Secrets);
|
||||||
|
can(ProjectPermissionSecretActions.ReadValue, ProjectPermissionSub.Secrets);
|
||||||
|
can(ProjectPermissionActions.Read, ProjectPermissionSub.SecretFolders);
|
||||||
|
can(ProjectPermissionDynamicSecretActions.ReadRootCredential, ProjectPermissionSub.DynamicSecrets);
|
||||||
|
can(ProjectPermissionActions.Read, ProjectPermissionSub.SecretImports);
|
||||||
|
can(ProjectPermissionActions.Read, ProjectPermissionSub.SecretApproval);
|
||||||
|
can(ProjectPermissionActions.Read, ProjectPermissionSub.SecretRollback);
|
||||||
|
can(ProjectPermissionSecretRotationActions.Read, ProjectPermissionSub.SecretRotation);
|
||||||
|
can(ProjectPermissionMemberActions.Read, ProjectPermissionSub.Member);
|
||||||
|
can(ProjectPermissionGroupActions.Read, ProjectPermissionSub.Groups);
|
||||||
|
can(ProjectPermissionActions.Read, ProjectPermissionSub.Role);
|
||||||
|
can(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations);
|
||||||
|
can(ProjectPermissionActions.Read, ProjectPermissionSub.Webhooks);
|
||||||
|
can(ProjectPermissionIdentityActions.Read, ProjectPermissionSub.Identity);
|
||||||
|
can(ProjectPermissionActions.Read, ProjectPermissionSub.ServiceTokens);
|
||||||
|
can(ProjectPermissionActions.Read, ProjectPermissionSub.Settings);
|
||||||
|
can(ProjectPermissionActions.Read, ProjectPermissionSub.Environments);
|
||||||
|
can(ProjectPermissionActions.Read, ProjectPermissionSub.Tags);
|
||||||
|
can(ProjectPermissionActions.Read, ProjectPermissionSub.AuditLogs);
|
||||||
|
can(ProjectPermissionActions.Read, ProjectPermissionSub.IpAllowList);
|
||||||
|
can(ProjectPermissionActions.Read, ProjectPermissionSub.CertificateAuthorities);
|
||||||
|
can(ProjectPermissionCertificateActions.Read, ProjectPermissionSub.Certificates);
|
||||||
|
can(ProjectPermissionCmekActions.Read, ProjectPermissionSub.Cmek);
|
||||||
|
can(ProjectPermissionActions.Read, ProjectPermissionSub.SshCertificates);
|
||||||
|
can(ProjectPermissionActions.Read, ProjectPermissionSub.SshCertificateTemplates);
|
||||||
|
can(ProjectPermissionSecretSyncActions.Read, ProjectPermissionSub.SecretSyncs);
|
||||||
|
|
||||||
|
return rules;
|
||||||
|
};
|
||||||
|
|
||||||
|
const buildNoAccessProjectPermission = () => {
|
||||||
|
const { rules } = new AbilityBuilder<MongoAbility<ProjectPermissionSet>>(createMongoAbility);
|
||||||
|
return rules;
|
||||||
|
};
|
||||||
|
|
||||||
|
const buildSshHostBootstrapPermissionRules = () => {
|
||||||
|
const { can, rules } = new AbilityBuilder<MongoAbility<ProjectPermissionSet>>(createMongoAbility);
|
||||||
|
|
||||||
|
can(
|
||||||
|
[ProjectPermissionSshHostActions.Create, ProjectPermissionSshHostActions.IssueHostCert],
|
||||||
|
ProjectPermissionSub.SshHosts
|
||||||
|
);
|
||||||
|
|
||||||
|
return rules;
|
||||||
|
};
|
||||||
|
|
||||||
|
const buildCryptographicOperatorPermissionRules = () => {
|
||||||
|
const { can, rules } = new AbilityBuilder<MongoAbility<ProjectPermissionSet>>(createMongoAbility);
|
||||||
|
|
||||||
|
can(
|
||||||
|
[
|
||||||
|
ProjectPermissionCmekActions.Encrypt,
|
||||||
|
ProjectPermissionCmekActions.Decrypt,
|
||||||
|
ProjectPermissionCmekActions.Sign,
|
||||||
|
ProjectPermissionCmekActions.Verify
|
||||||
|
],
|
||||||
|
ProjectPermissionSub.Cmek
|
||||||
|
);
|
||||||
|
|
||||||
|
return rules;
|
||||||
|
};
|
||||||
|
|
||||||
|
// General
|
||||||
|
export const projectAdminPermissions = buildAdminPermissionRules();
|
||||||
|
export const projectMemberPermissions = buildMemberPermissionRules();
|
||||||
|
export const projectViewerPermission = buildViewerPermissionRules();
|
||||||
|
export const projectNoAccessPermissions = buildNoAccessProjectPermission();
|
||||||
|
|
||||||
|
// SSH
|
||||||
|
export const sshHostBootstrapPermissions = buildSshHostBootstrapPermissionRules();
|
||||||
|
|
||||||
|
// KMS
|
||||||
|
export const cryptographicOperatorPermissions = buildCryptographicOperatorPermissionRules();
|
@@ -132,7 +132,7 @@ export const permissionDALFactory = (db: TDbClient) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const getProjectGroupPermissions = async (projectId: string) => {
|
const getProjectGroupPermissions = async (projectId: string, filterGroupId?: string) => {
|
||||||
try {
|
try {
|
||||||
const docs = await db
|
const docs = await db
|
||||||
.replicaNode()(TableName.GroupProjectMembership)
|
.replicaNode()(TableName.GroupProjectMembership)
|
||||||
@@ -148,6 +148,11 @@ export const permissionDALFactory = (db: TDbClient) => {
|
|||||||
`groupCustomRoles.id`
|
`groupCustomRoles.id`
|
||||||
)
|
)
|
||||||
.where(`${TableName.GroupProjectMembership}.projectId`, "=", projectId)
|
.where(`${TableName.GroupProjectMembership}.projectId`, "=", projectId)
|
||||||
|
.where((bd) => {
|
||||||
|
if (filterGroupId) {
|
||||||
|
void bd.where(`${TableName.GroupProjectMembership}.groupId`, "=", filterGroupId);
|
||||||
|
}
|
||||||
|
})
|
||||||
.select(
|
.select(
|
||||||
db.ref("id").withSchema(TableName.GroupProjectMembership).as("membershipId"),
|
db.ref("id").withSchema(TableName.GroupProjectMembership).as("membershipId"),
|
||||||
db.ref("id").withSchema(TableName.Groups).as("groupId"),
|
db.ref("id").withSchema(TableName.Groups).as("groupId"),
|
||||||
|
@@ -12,6 +12,14 @@ import {
|
|||||||
TIdentityProjectMemberships,
|
TIdentityProjectMemberships,
|
||||||
TProjectMemberships
|
TProjectMemberships
|
||||||
} from "@app/db/schemas";
|
} from "@app/db/schemas";
|
||||||
|
import {
|
||||||
|
cryptographicOperatorPermissions,
|
||||||
|
projectAdminPermissions,
|
||||||
|
projectMemberPermissions,
|
||||||
|
projectNoAccessPermissions,
|
||||||
|
projectViewerPermission,
|
||||||
|
sshHostBootstrapPermissions
|
||||||
|
} from "@app/ee/services/permission/default-roles";
|
||||||
import { conditionsMatcher } from "@app/lib/casl";
|
import { conditionsMatcher } from "@app/lib/casl";
|
||||||
import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors";
|
import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors";
|
||||||
import { objectify } from "@app/lib/fn";
|
import { objectify } from "@app/lib/fn";
|
||||||
@@ -32,14 +40,7 @@ import {
|
|||||||
TGetServiceTokenProjectPermissionArg,
|
TGetServiceTokenProjectPermissionArg,
|
||||||
TGetUserProjectPermissionArg
|
TGetUserProjectPermissionArg
|
||||||
} from "./permission-service-types";
|
} from "./permission-service-types";
|
||||||
import {
|
import { buildServiceTokenProjectPermission, ProjectPermissionSet } from "./project-permission";
|
||||||
buildServiceTokenProjectPermission,
|
|
||||||
projectAdminPermissions,
|
|
||||||
projectMemberPermissions,
|
|
||||||
projectNoAccessPermissions,
|
|
||||||
ProjectPermissionSet,
|
|
||||||
projectViewerPermission
|
|
||||||
} from "./project-permission";
|
|
||||||
|
|
||||||
type TPermissionServiceFactoryDep = {
|
type TPermissionServiceFactoryDep = {
|
||||||
orgRoleDAL: Pick<TOrgRoleDALFactory, "findOne">;
|
orgRoleDAL: Pick<TOrgRoleDALFactory, "findOne">;
|
||||||
@@ -95,6 +96,10 @@ export const permissionServiceFactory = ({
|
|||||||
return projectViewerPermission;
|
return projectViewerPermission;
|
||||||
case ProjectMembershipRole.NoAccess:
|
case ProjectMembershipRole.NoAccess:
|
||||||
return projectNoAccessPermissions;
|
return projectNoAccessPermissions;
|
||||||
|
case ProjectMembershipRole.SshHostBootstrapper:
|
||||||
|
return sshHostBootstrapPermissions;
|
||||||
|
case ProjectMembershipRole.KmsCryptographicOperator:
|
||||||
|
return cryptographicOperatorPermissions;
|
||||||
case ProjectMembershipRole.Custom: {
|
case ProjectMembershipRole.Custom: {
|
||||||
return unpackRules<RawRuleOf<MongoAbility<ProjectPermissionSet>>>(
|
return unpackRules<RawRuleOf<MongoAbility<ProjectPermissionSet>>>(
|
||||||
permissions as PackRule<RawRuleOf<MongoAbility<ProjectPermissionSet>>>[]
|
permissions as PackRule<RawRuleOf<MongoAbility<ProjectPermissionSet>>>[]
|
||||||
@@ -625,6 +630,34 @@ export const permissionServiceFactory = ({
|
|||||||
return { permission };
|
return { permission };
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const checkGroupProjectPermission = async ({
|
||||||
|
groupId,
|
||||||
|
projectId,
|
||||||
|
checkPermissions
|
||||||
|
}: {
|
||||||
|
groupId: string;
|
||||||
|
projectId: string;
|
||||||
|
checkPermissions: ProjectPermissionSet;
|
||||||
|
}) => {
|
||||||
|
const rawGroupProjectPermissions = await permissionDAL.getProjectGroupPermissions(projectId, groupId);
|
||||||
|
const groupPermissions = rawGroupProjectPermissions.map((groupProjectPermission) => {
|
||||||
|
const rolePermissions =
|
||||||
|
groupProjectPermission.roles?.map(({ role, permissions }) => ({ role, permissions })) || [];
|
||||||
|
const rules = buildProjectPermissionRules(rolePermissions);
|
||||||
|
const permission = createMongoAbility<ProjectPermissionSet>(rules, {
|
||||||
|
conditionsMatcher
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
permission,
|
||||||
|
id: groupProjectPermission.groupId,
|
||||||
|
name: groupProjectPermission.username,
|
||||||
|
membershipId: groupProjectPermission.id
|
||||||
|
};
|
||||||
|
});
|
||||||
|
return groupPermissions.some((groupPermission) => groupPermission.permission.can(...checkPermissions));
|
||||||
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
getUserOrgPermission,
|
getUserOrgPermission,
|
||||||
getOrgPermission,
|
getOrgPermission,
|
||||||
@@ -634,6 +667,7 @@ export const permissionServiceFactory = ({
|
|||||||
getOrgPermissionByRole,
|
getOrgPermissionByRole,
|
||||||
getProjectPermissionByRole,
|
getProjectPermissionByRole,
|
||||||
buildOrgPermission,
|
buildOrgPermission,
|
||||||
buildProjectPermissionRules
|
buildProjectPermissionRules,
|
||||||
|
checkGroupProjectPermission
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@@ -17,6 +17,14 @@ export enum ProjectPermissionActions {
|
|||||||
Delete = "delete"
|
Delete = "delete"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum ProjectPermissionCertificateActions {
|
||||||
|
Read = "read",
|
||||||
|
Create = "create",
|
||||||
|
Edit = "edit",
|
||||||
|
Delete = "delete",
|
||||||
|
ReadPrivateKey = "read-private-key"
|
||||||
|
}
|
||||||
|
|
||||||
export enum ProjectPermissionSecretActions {
|
export enum ProjectPermissionSecretActions {
|
||||||
DescribeAndReadValue = "read",
|
DescribeAndReadValue = "read",
|
||||||
DescribeSecret = "describeSecret",
|
DescribeSecret = "describeSecret",
|
||||||
@@ -134,6 +142,7 @@ export enum ProjectPermissionSub {
|
|||||||
SshCertificates = "ssh-certificates",
|
SshCertificates = "ssh-certificates",
|
||||||
SshCertificateTemplates = "ssh-certificate-templates",
|
SshCertificateTemplates = "ssh-certificate-templates",
|
||||||
SshHosts = "ssh-hosts",
|
SshHosts = "ssh-hosts",
|
||||||
|
SshHostGroups = "ssh-host-groups",
|
||||||
PkiAlerts = "pki-alerts",
|
PkiAlerts = "pki-alerts",
|
||||||
PkiCollections = "pki-collections",
|
PkiCollections = "pki-collections",
|
||||||
Kms = "kms",
|
Kms = "kms",
|
||||||
@@ -231,7 +240,7 @@ export type ProjectPermissionSet =
|
|||||||
ProjectPermissionSub.Identity | (ForcedSubject<ProjectPermissionSub.Identity> & IdentityManagementSubjectFields)
|
ProjectPermissionSub.Identity | (ForcedSubject<ProjectPermissionSub.Identity> & IdentityManagementSubjectFields)
|
||||||
]
|
]
|
||||||
| [ProjectPermissionActions, ProjectPermissionSub.CertificateAuthorities]
|
| [ProjectPermissionActions, ProjectPermissionSub.CertificateAuthorities]
|
||||||
| [ProjectPermissionActions, ProjectPermissionSub.Certificates]
|
| [ProjectPermissionCertificateActions, ProjectPermissionSub.Certificates]
|
||||||
| [ProjectPermissionActions, ProjectPermissionSub.CertificateTemplates]
|
| [ProjectPermissionActions, ProjectPermissionSub.CertificateTemplates]
|
||||||
| [ProjectPermissionActions, ProjectPermissionSub.SshCertificateAuthorities]
|
| [ProjectPermissionActions, ProjectPermissionSub.SshCertificateAuthorities]
|
||||||
| [ProjectPermissionActions, ProjectPermissionSub.SshCertificates]
|
| [ProjectPermissionActions, ProjectPermissionSub.SshCertificates]
|
||||||
@@ -240,6 +249,7 @@ export type ProjectPermissionSet =
|
|||||||
ProjectPermissionSshHostActions,
|
ProjectPermissionSshHostActions,
|
||||||
ProjectPermissionSub.SshHosts | (ForcedSubject<ProjectPermissionSub.SshHosts> & SshHostSubjectFields)
|
ProjectPermissionSub.SshHosts | (ForcedSubject<ProjectPermissionSub.SshHosts> & SshHostSubjectFields)
|
||||||
]
|
]
|
||||||
|
| [ProjectPermissionActions, ProjectPermissionSub.SshHostGroups]
|
||||||
| [ProjectPermissionActions, ProjectPermissionSub.PkiAlerts]
|
| [ProjectPermissionActions, ProjectPermissionSub.PkiAlerts]
|
||||||
| [ProjectPermissionActions, ProjectPermissionSub.PkiCollections]
|
| [ProjectPermissionActions, ProjectPermissionSub.PkiCollections]
|
||||||
| [ProjectPermissionSecretSyncActions, ProjectPermissionSub.SecretSyncs]
|
| [ProjectPermissionSecretSyncActions, ProjectPermissionSub.SecretSyncs]
|
||||||
@@ -476,7 +486,7 @@ const GeneralPermissionSchema = [
|
|||||||
}),
|
}),
|
||||||
z.object({
|
z.object({
|
||||||
subject: z.literal(ProjectPermissionSub.Certificates).describe("The entity this permission pertains to."),
|
subject: z.literal(ProjectPermissionSub.Certificates).describe("The entity this permission pertains to."),
|
||||||
action: CASL_ACTION_SCHEMA_NATIVE_ENUM(ProjectPermissionActions).describe(
|
action: CASL_ACTION_SCHEMA_NATIVE_ENUM(ProjectPermissionCertificateActions).describe(
|
||||||
"Describe what action an entity can take."
|
"Describe what action an entity can take."
|
||||||
)
|
)
|
||||||
}),
|
}),
|
||||||
@@ -508,6 +518,12 @@ const GeneralPermissionSchema = [
|
|||||||
"Describe what action an entity can take."
|
"Describe what action an entity can take."
|
||||||
)
|
)
|
||||||
}),
|
}),
|
||||||
|
z.object({
|
||||||
|
subject: z.literal(ProjectPermissionSub.SshHostGroups).describe("The entity this permission pertains to."),
|
||||||
|
action: CASL_ACTION_SCHEMA_NATIVE_ENUM(ProjectPermissionActions).describe(
|
||||||
|
"Describe what action an entity can take."
|
||||||
|
)
|
||||||
|
}),
|
||||||
z.object({
|
z.object({
|
||||||
subject: z.literal(ProjectPermissionSub.PkiAlerts).describe("The entity this permission pertains to."),
|
subject: z.literal(ProjectPermissionSub.PkiAlerts).describe("The entity this permission pertains to."),
|
||||||
action: CASL_ACTION_SCHEMA_NATIVE_ENUM(ProjectPermissionActions).describe(
|
action: CASL_ACTION_SCHEMA_NATIVE_ENUM(ProjectPermissionActions).describe(
|
||||||
@@ -662,392 +678,6 @@ export const ProjectPermissionV2Schema = z.discriminatedUnion("subject", [
|
|||||||
|
|
||||||
export type TProjectPermissionV2Schema = z.infer<typeof ProjectPermissionV2Schema>;
|
export type TProjectPermissionV2Schema = z.infer<typeof ProjectPermissionV2Schema>;
|
||||||
|
|
||||||
const buildAdminPermissionRules = () => {
|
|
||||||
const { can, rules } = new AbilityBuilder<MongoAbility<ProjectPermissionSet>>(createMongoAbility);
|
|
||||||
|
|
||||||
// Admins get full access to everything
|
|
||||||
[
|
|
||||||
ProjectPermissionSub.SecretFolders,
|
|
||||||
ProjectPermissionSub.SecretImports,
|
|
||||||
ProjectPermissionSub.SecretApproval,
|
|
||||||
ProjectPermissionSub.Role,
|
|
||||||
ProjectPermissionSub.Integrations,
|
|
||||||
ProjectPermissionSub.Webhooks,
|
|
||||||
ProjectPermissionSub.ServiceTokens,
|
|
||||||
ProjectPermissionSub.Settings,
|
|
||||||
ProjectPermissionSub.Environments,
|
|
||||||
ProjectPermissionSub.Tags,
|
|
||||||
ProjectPermissionSub.AuditLogs,
|
|
||||||
ProjectPermissionSub.IpAllowList,
|
|
||||||
ProjectPermissionSub.CertificateAuthorities,
|
|
||||||
ProjectPermissionSub.Certificates,
|
|
||||||
ProjectPermissionSub.CertificateTemplates,
|
|
||||||
ProjectPermissionSub.PkiAlerts,
|
|
||||||
ProjectPermissionSub.PkiCollections,
|
|
||||||
ProjectPermissionSub.SshCertificateAuthorities,
|
|
||||||
ProjectPermissionSub.SshCertificates,
|
|
||||||
ProjectPermissionSub.SshCertificateTemplates
|
|
||||||
].forEach((el) => {
|
|
||||||
can(
|
|
||||||
[
|
|
||||||
ProjectPermissionActions.Read,
|
|
||||||
ProjectPermissionActions.Edit,
|
|
||||||
ProjectPermissionActions.Create,
|
|
||||||
ProjectPermissionActions.Delete
|
|
||||||
],
|
|
||||||
el
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
can(
|
|
||||||
[
|
|
||||||
ProjectPermissionSshHostActions.Edit,
|
|
||||||
ProjectPermissionSshHostActions.Read,
|
|
||||||
ProjectPermissionSshHostActions.Create,
|
|
||||||
ProjectPermissionSshHostActions.Delete,
|
|
||||||
ProjectPermissionSshHostActions.IssueHostCert
|
|
||||||
],
|
|
||||||
ProjectPermissionSub.SshHosts
|
|
||||||
);
|
|
||||||
|
|
||||||
can(
|
|
||||||
[
|
|
||||||
ProjectPermissionMemberActions.Create,
|
|
||||||
ProjectPermissionMemberActions.Edit,
|
|
||||||
ProjectPermissionMemberActions.Delete,
|
|
||||||
ProjectPermissionMemberActions.Read,
|
|
||||||
ProjectPermissionMemberActions.GrantPrivileges,
|
|
||||||
ProjectPermissionMemberActions.AssumePrivileges
|
|
||||||
],
|
|
||||||
ProjectPermissionSub.Member
|
|
||||||
);
|
|
||||||
|
|
||||||
can(
|
|
||||||
[
|
|
||||||
ProjectPermissionGroupActions.Create,
|
|
||||||
ProjectPermissionGroupActions.Edit,
|
|
||||||
ProjectPermissionGroupActions.Delete,
|
|
||||||
ProjectPermissionGroupActions.Read,
|
|
||||||
ProjectPermissionGroupActions.GrantPrivileges
|
|
||||||
],
|
|
||||||
ProjectPermissionSub.Groups
|
|
||||||
);
|
|
||||||
|
|
||||||
can(
|
|
||||||
[
|
|
||||||
ProjectPermissionIdentityActions.Create,
|
|
||||||
ProjectPermissionIdentityActions.Edit,
|
|
||||||
ProjectPermissionIdentityActions.Delete,
|
|
||||||
ProjectPermissionIdentityActions.Read,
|
|
||||||
ProjectPermissionIdentityActions.GrantPrivileges,
|
|
||||||
ProjectPermissionIdentityActions.AssumePrivileges
|
|
||||||
],
|
|
||||||
ProjectPermissionSub.Identity
|
|
||||||
);
|
|
||||||
|
|
||||||
can(
|
|
||||||
[
|
|
||||||
ProjectPermissionSecretActions.DescribeAndReadValue,
|
|
||||||
ProjectPermissionSecretActions.DescribeSecret,
|
|
||||||
ProjectPermissionSecretActions.ReadValue,
|
|
||||||
ProjectPermissionSecretActions.Create,
|
|
||||||
ProjectPermissionSecretActions.Edit,
|
|
||||||
ProjectPermissionSecretActions.Delete
|
|
||||||
],
|
|
||||||
ProjectPermissionSub.Secrets
|
|
||||||
);
|
|
||||||
|
|
||||||
can(
|
|
||||||
[
|
|
||||||
ProjectPermissionDynamicSecretActions.ReadRootCredential,
|
|
||||||
ProjectPermissionDynamicSecretActions.EditRootCredential,
|
|
||||||
ProjectPermissionDynamicSecretActions.CreateRootCredential,
|
|
||||||
ProjectPermissionDynamicSecretActions.DeleteRootCredential,
|
|
||||||
ProjectPermissionDynamicSecretActions.Lease
|
|
||||||
],
|
|
||||||
ProjectPermissionSub.DynamicSecrets
|
|
||||||
);
|
|
||||||
|
|
||||||
can([ProjectPermissionActions.Edit, ProjectPermissionActions.Delete], ProjectPermissionSub.Project);
|
|
||||||
can([ProjectPermissionActions.Read, ProjectPermissionActions.Create], ProjectPermissionSub.SecretRollback);
|
|
||||||
can([ProjectPermissionActions.Edit], ProjectPermissionSub.Kms);
|
|
||||||
can(
|
|
||||||
[
|
|
||||||
ProjectPermissionCmekActions.Create,
|
|
||||||
ProjectPermissionCmekActions.Edit,
|
|
||||||
ProjectPermissionCmekActions.Delete,
|
|
||||||
ProjectPermissionCmekActions.Read,
|
|
||||||
ProjectPermissionCmekActions.Encrypt,
|
|
||||||
ProjectPermissionCmekActions.Decrypt,
|
|
||||||
ProjectPermissionCmekActions.Sign,
|
|
||||||
ProjectPermissionCmekActions.Verify
|
|
||||||
],
|
|
||||||
ProjectPermissionSub.Cmek
|
|
||||||
);
|
|
||||||
can(
|
|
||||||
[
|
|
||||||
ProjectPermissionSecretSyncActions.Create,
|
|
||||||
ProjectPermissionSecretSyncActions.Edit,
|
|
||||||
ProjectPermissionSecretSyncActions.Delete,
|
|
||||||
ProjectPermissionSecretSyncActions.Read,
|
|
||||||
ProjectPermissionSecretSyncActions.SyncSecrets,
|
|
||||||
ProjectPermissionSecretSyncActions.ImportSecrets,
|
|
||||||
ProjectPermissionSecretSyncActions.RemoveSecrets
|
|
||||||
],
|
|
||||||
ProjectPermissionSub.SecretSyncs
|
|
||||||
);
|
|
||||||
|
|
||||||
can(
|
|
||||||
[
|
|
||||||
ProjectPermissionKmipActions.CreateClients,
|
|
||||||
ProjectPermissionKmipActions.UpdateClients,
|
|
||||||
ProjectPermissionKmipActions.DeleteClients,
|
|
||||||
ProjectPermissionKmipActions.ReadClients,
|
|
||||||
ProjectPermissionKmipActions.GenerateClientCertificates
|
|
||||||
],
|
|
||||||
ProjectPermissionSub.Kmip
|
|
||||||
);
|
|
||||||
|
|
||||||
can(
|
|
||||||
[
|
|
||||||
ProjectPermissionSecretRotationActions.Create,
|
|
||||||
ProjectPermissionSecretRotationActions.Edit,
|
|
||||||
ProjectPermissionSecretRotationActions.Delete,
|
|
||||||
ProjectPermissionSecretRotationActions.Read,
|
|
||||||
ProjectPermissionSecretRotationActions.ReadGeneratedCredentials,
|
|
||||||
ProjectPermissionSecretRotationActions.RotateSecrets
|
|
||||||
],
|
|
||||||
ProjectPermissionSub.SecretRotation
|
|
||||||
);
|
|
||||||
|
|
||||||
return rules;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const projectAdminPermissions = buildAdminPermissionRules();
|
|
||||||
|
|
||||||
const buildMemberPermissionRules = () => {
|
|
||||||
const { can, rules } = new AbilityBuilder<MongoAbility<ProjectPermissionSet>>(createMongoAbility);
|
|
||||||
|
|
||||||
can(
|
|
||||||
[
|
|
||||||
ProjectPermissionSecretActions.DescribeAndReadValue,
|
|
||||||
ProjectPermissionSecretActions.DescribeSecret,
|
|
||||||
ProjectPermissionSecretActions.ReadValue,
|
|
||||||
ProjectPermissionSecretActions.Edit,
|
|
||||||
ProjectPermissionSecretActions.Create,
|
|
||||||
ProjectPermissionSecretActions.Delete
|
|
||||||
],
|
|
||||||
ProjectPermissionSub.Secrets
|
|
||||||
);
|
|
||||||
can(
|
|
||||||
[
|
|
||||||
ProjectPermissionActions.Read,
|
|
||||||
ProjectPermissionActions.Edit,
|
|
||||||
ProjectPermissionActions.Create,
|
|
||||||
ProjectPermissionActions.Delete
|
|
||||||
],
|
|
||||||
ProjectPermissionSub.SecretFolders
|
|
||||||
);
|
|
||||||
can(
|
|
||||||
[
|
|
||||||
ProjectPermissionDynamicSecretActions.ReadRootCredential,
|
|
||||||
ProjectPermissionDynamicSecretActions.EditRootCredential,
|
|
||||||
ProjectPermissionDynamicSecretActions.CreateRootCredential,
|
|
||||||
ProjectPermissionDynamicSecretActions.DeleteRootCredential,
|
|
||||||
ProjectPermissionDynamicSecretActions.Lease
|
|
||||||
],
|
|
||||||
ProjectPermissionSub.DynamicSecrets
|
|
||||||
);
|
|
||||||
can(
|
|
||||||
[
|
|
||||||
ProjectPermissionActions.Read,
|
|
||||||
ProjectPermissionActions.Edit,
|
|
||||||
ProjectPermissionActions.Create,
|
|
||||||
ProjectPermissionActions.Delete
|
|
||||||
],
|
|
||||||
ProjectPermissionSub.SecretImports
|
|
||||||
);
|
|
||||||
|
|
||||||
can([ProjectPermissionActions.Read], ProjectPermissionSub.SecretApproval);
|
|
||||||
can([ProjectPermissionSecretRotationActions.Read], ProjectPermissionSub.SecretRotation);
|
|
||||||
|
|
||||||
can([ProjectPermissionActions.Read, ProjectPermissionActions.Create], ProjectPermissionSub.SecretRollback);
|
|
||||||
|
|
||||||
can([ProjectPermissionMemberActions.Read, ProjectPermissionMemberActions.Create], ProjectPermissionSub.Member);
|
|
||||||
|
|
||||||
can([ProjectPermissionGroupActions.Read], ProjectPermissionSub.Groups);
|
|
||||||
|
|
||||||
can(
|
|
||||||
[
|
|
||||||
ProjectPermissionActions.Read,
|
|
||||||
ProjectPermissionActions.Edit,
|
|
||||||
ProjectPermissionActions.Create,
|
|
||||||
ProjectPermissionActions.Delete
|
|
||||||
],
|
|
||||||
ProjectPermissionSub.Integrations
|
|
||||||
);
|
|
||||||
|
|
||||||
can(
|
|
||||||
[
|
|
||||||
ProjectPermissionActions.Read,
|
|
||||||
ProjectPermissionActions.Edit,
|
|
||||||
ProjectPermissionActions.Create,
|
|
||||||
ProjectPermissionActions.Delete
|
|
||||||
],
|
|
||||||
ProjectPermissionSub.Webhooks
|
|
||||||
);
|
|
||||||
|
|
||||||
can(
|
|
||||||
[
|
|
||||||
ProjectPermissionIdentityActions.Read,
|
|
||||||
ProjectPermissionIdentityActions.Edit,
|
|
||||||
ProjectPermissionIdentityActions.Create,
|
|
||||||
ProjectPermissionIdentityActions.Delete
|
|
||||||
],
|
|
||||||
ProjectPermissionSub.Identity
|
|
||||||
);
|
|
||||||
|
|
||||||
can(
|
|
||||||
[
|
|
||||||
ProjectPermissionActions.Read,
|
|
||||||
ProjectPermissionActions.Edit,
|
|
||||||
ProjectPermissionActions.Create,
|
|
||||||
ProjectPermissionActions.Delete
|
|
||||||
],
|
|
||||||
ProjectPermissionSub.ServiceTokens
|
|
||||||
);
|
|
||||||
|
|
||||||
can(
|
|
||||||
[
|
|
||||||
ProjectPermissionActions.Read,
|
|
||||||
ProjectPermissionActions.Edit,
|
|
||||||
ProjectPermissionActions.Create,
|
|
||||||
ProjectPermissionActions.Delete
|
|
||||||
],
|
|
||||||
ProjectPermissionSub.Settings
|
|
||||||
);
|
|
||||||
|
|
||||||
can(
|
|
||||||
[
|
|
||||||
ProjectPermissionActions.Read,
|
|
||||||
ProjectPermissionActions.Edit,
|
|
||||||
ProjectPermissionActions.Create,
|
|
||||||
ProjectPermissionActions.Delete
|
|
||||||
],
|
|
||||||
ProjectPermissionSub.Environments
|
|
||||||
);
|
|
||||||
|
|
||||||
can(
|
|
||||||
[
|
|
||||||
ProjectPermissionActions.Read,
|
|
||||||
ProjectPermissionActions.Edit,
|
|
||||||
ProjectPermissionActions.Create,
|
|
||||||
ProjectPermissionActions.Delete
|
|
||||||
],
|
|
||||||
ProjectPermissionSub.Tags
|
|
||||||
);
|
|
||||||
|
|
||||||
can([ProjectPermissionActions.Read], ProjectPermissionSub.Role);
|
|
||||||
can([ProjectPermissionActions.Read], ProjectPermissionSub.AuditLogs);
|
|
||||||
can([ProjectPermissionActions.Read], ProjectPermissionSub.IpAllowList);
|
|
||||||
|
|
||||||
// double check if all CRUD are needed for CA and Certificates
|
|
||||||
can([ProjectPermissionActions.Read], ProjectPermissionSub.CertificateAuthorities);
|
|
||||||
|
|
||||||
can(
|
|
||||||
[
|
|
||||||
ProjectPermissionActions.Read,
|
|
||||||
ProjectPermissionActions.Edit,
|
|
||||||
ProjectPermissionActions.Create,
|
|
||||||
ProjectPermissionActions.Delete
|
|
||||||
],
|
|
||||||
ProjectPermissionSub.Certificates
|
|
||||||
);
|
|
||||||
|
|
||||||
can([ProjectPermissionActions.Read], ProjectPermissionSub.CertificateTemplates);
|
|
||||||
|
|
||||||
can([ProjectPermissionActions.Read], ProjectPermissionSub.PkiAlerts);
|
|
||||||
can([ProjectPermissionActions.Read], ProjectPermissionSub.PkiCollections);
|
|
||||||
|
|
||||||
can([ProjectPermissionActions.Read], ProjectPermissionSub.SshCertificates);
|
|
||||||
can([ProjectPermissionActions.Create], ProjectPermissionSub.SshCertificates);
|
|
||||||
can([ProjectPermissionActions.Read], ProjectPermissionSub.SshCertificateTemplates);
|
|
||||||
|
|
||||||
can([ProjectPermissionSshHostActions.Read], ProjectPermissionSub.SshHosts);
|
|
||||||
|
|
||||||
can(
|
|
||||||
[
|
|
||||||
ProjectPermissionCmekActions.Create,
|
|
||||||
ProjectPermissionCmekActions.Edit,
|
|
||||||
ProjectPermissionCmekActions.Delete,
|
|
||||||
ProjectPermissionCmekActions.Read,
|
|
||||||
ProjectPermissionCmekActions.Encrypt,
|
|
||||||
ProjectPermissionCmekActions.Decrypt,
|
|
||||||
ProjectPermissionCmekActions.Sign,
|
|
||||||
ProjectPermissionCmekActions.Verify
|
|
||||||
],
|
|
||||||
ProjectPermissionSub.Cmek
|
|
||||||
);
|
|
||||||
|
|
||||||
can(
|
|
||||||
[
|
|
||||||
ProjectPermissionSecretSyncActions.Create,
|
|
||||||
ProjectPermissionSecretSyncActions.Edit,
|
|
||||||
ProjectPermissionSecretSyncActions.Delete,
|
|
||||||
ProjectPermissionSecretSyncActions.Read,
|
|
||||||
ProjectPermissionSecretSyncActions.SyncSecrets,
|
|
||||||
ProjectPermissionSecretSyncActions.ImportSecrets,
|
|
||||||
ProjectPermissionSecretSyncActions.RemoveSecrets
|
|
||||||
],
|
|
||||||
ProjectPermissionSub.SecretSyncs
|
|
||||||
);
|
|
||||||
|
|
||||||
return rules;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const projectMemberPermissions = buildMemberPermissionRules();
|
|
||||||
|
|
||||||
const buildViewerPermissionRules = () => {
|
|
||||||
const { can, rules } = new AbilityBuilder<MongoAbility<ProjectPermissionSet>>(createMongoAbility);
|
|
||||||
|
|
||||||
can(ProjectPermissionSecretActions.DescribeAndReadValue, ProjectPermissionSub.Secrets);
|
|
||||||
can(ProjectPermissionSecretActions.DescribeSecret, ProjectPermissionSub.Secrets);
|
|
||||||
can(ProjectPermissionSecretActions.ReadValue, ProjectPermissionSub.Secrets);
|
|
||||||
can(ProjectPermissionActions.Read, ProjectPermissionSub.SecretFolders);
|
|
||||||
can(ProjectPermissionDynamicSecretActions.ReadRootCredential, ProjectPermissionSub.DynamicSecrets);
|
|
||||||
can(ProjectPermissionActions.Read, ProjectPermissionSub.SecretImports);
|
|
||||||
can(ProjectPermissionActions.Read, ProjectPermissionSub.SecretApproval);
|
|
||||||
can(ProjectPermissionActions.Read, ProjectPermissionSub.SecretRollback);
|
|
||||||
can(ProjectPermissionSecretRotationActions.Read, ProjectPermissionSub.SecretRotation);
|
|
||||||
can(ProjectPermissionMemberActions.Read, ProjectPermissionSub.Member);
|
|
||||||
can(ProjectPermissionGroupActions.Read, ProjectPermissionSub.Groups);
|
|
||||||
can(ProjectPermissionActions.Read, ProjectPermissionSub.Role);
|
|
||||||
can(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations);
|
|
||||||
can(ProjectPermissionActions.Read, ProjectPermissionSub.Webhooks);
|
|
||||||
can(ProjectPermissionIdentityActions.Read, ProjectPermissionSub.Identity);
|
|
||||||
can(ProjectPermissionActions.Read, ProjectPermissionSub.ServiceTokens);
|
|
||||||
can(ProjectPermissionActions.Read, ProjectPermissionSub.Settings);
|
|
||||||
can(ProjectPermissionActions.Read, ProjectPermissionSub.Environments);
|
|
||||||
can(ProjectPermissionActions.Read, ProjectPermissionSub.Tags);
|
|
||||||
can(ProjectPermissionActions.Read, ProjectPermissionSub.AuditLogs);
|
|
||||||
can(ProjectPermissionActions.Read, ProjectPermissionSub.IpAllowList);
|
|
||||||
can(ProjectPermissionActions.Read, ProjectPermissionSub.CertificateAuthorities);
|
|
||||||
can(ProjectPermissionActions.Read, ProjectPermissionSub.Certificates);
|
|
||||||
can(ProjectPermissionCmekActions.Read, ProjectPermissionSub.Cmek);
|
|
||||||
can(ProjectPermissionActions.Read, ProjectPermissionSub.SshCertificates);
|
|
||||||
can(ProjectPermissionActions.Read, ProjectPermissionSub.SshCertificateTemplates);
|
|
||||||
can(ProjectPermissionSecretSyncActions.Read, ProjectPermissionSub.SecretSyncs);
|
|
||||||
|
|
||||||
return rules;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const projectViewerPermission = buildViewerPermissionRules();
|
|
||||||
|
|
||||||
const buildNoAccessProjectPermission = () => {
|
|
||||||
const { rules } = new AbilityBuilder<MongoAbility<ProjectPermissionSet>>(createMongoAbility);
|
|
||||||
return rules;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const buildServiceTokenProjectPermission = (
|
export const buildServiceTokenProjectPermission = (
|
||||||
scopes: Array<{ secretPath: string; environment: string }>,
|
scopes: Array<{ secretPath: string; environment: string }>,
|
||||||
permission: string[]
|
permission: string[]
|
||||||
@@ -1089,8 +719,6 @@ export const buildServiceTokenProjectPermission = (
|
|||||||
return build({ conditionsMatcher });
|
return build({ conditionsMatcher });
|
||||||
};
|
};
|
||||||
|
|
||||||
export const projectNoAccessPermissions = buildNoAccessProjectPermission();
|
|
||||||
|
|
||||||
/* eslint-disable */
|
/* eslint-disable */
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -1,22 +1,27 @@
|
|||||||
import { ProjectTemplateDefaultEnvironments } from "@app/ee/services/project-template/project-template-constants";
|
import { ProjectType } from "@app/db/schemas";
|
||||||
import {
|
import {
|
||||||
InfisicalProjectTemplate,
|
InfisicalProjectTemplate,
|
||||||
TUnpackedPermission
|
TUnpackedPermission
|
||||||
} from "@app/ee/services/project-template/project-template-types";
|
} from "@app/ee/services/project-template/project-template-types";
|
||||||
import { getPredefinedRoles } from "@app/services/project-role/project-role-fns";
|
import { getPredefinedRoles } from "@app/services/project-role/project-role-fns";
|
||||||
|
|
||||||
export const getDefaultProjectTemplate = (orgId: string) => ({
|
import { ProjectTemplateDefaultEnvironments } from "./project-template-constants";
|
||||||
|
|
||||||
|
export const getDefaultProjectTemplate = (orgId: string, type: ProjectType) => ({
|
||||||
id: "b11b49a9-09a9-4443-916a-4246f9ff2c69", // random ID to appease zod
|
id: "b11b49a9-09a9-4443-916a-4246f9ff2c69", // random ID to appease zod
|
||||||
|
type,
|
||||||
name: InfisicalProjectTemplate.Default,
|
name: InfisicalProjectTemplate.Default,
|
||||||
createdAt: new Date(),
|
createdAt: new Date(),
|
||||||
updatedAt: new Date(),
|
updatedAt: new Date(),
|
||||||
description: "Infisical's default project template",
|
description: `Infisical's ${type} default project template`,
|
||||||
environments: ProjectTemplateDefaultEnvironments,
|
environments: type === ProjectType.SecretManager ? ProjectTemplateDefaultEnvironments : null,
|
||||||
roles: [...getPredefinedRoles("project-template")].map(({ name, slug, permissions }) => ({
|
roles: [...getPredefinedRoles({ projectId: "project-template", projectType: type })].map(
|
||||||
name,
|
({ name, slug, permissions }) => ({
|
||||||
slug,
|
name,
|
||||||
permissions: permissions as TUnpackedPermission[]
|
slug,
|
||||||
})),
|
permissions: permissions as TUnpackedPermission[]
|
||||||
|
})
|
||||||
|
),
|
||||||
orgId
|
orgId
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@@ -1,10 +1,11 @@
|
|||||||
import { ForbiddenError } from "@casl/ability";
|
import { ForbiddenError } from "@casl/ability";
|
||||||
import { packRules } from "@casl/ability/extra";
|
import { packRules } from "@casl/ability/extra";
|
||||||
|
|
||||||
import { TProjectTemplates } from "@app/db/schemas";
|
import { ProjectType, TProjectTemplates } from "@app/db/schemas";
|
||||||
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
|
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
|
||||||
import { OrgPermissionActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission";
|
import { OrgPermissionActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission";
|
||||||
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
||||||
|
import { ProjectTemplateDefaultEnvironments } from "@app/ee/services/project-template/project-template-constants";
|
||||||
import { getDefaultProjectTemplate } from "@app/ee/services/project-template/project-template-fns";
|
import { getDefaultProjectTemplate } from "@app/ee/services/project-template/project-template-fns";
|
||||||
import {
|
import {
|
||||||
TCreateProjectTemplateDTO,
|
TCreateProjectTemplateDTO,
|
||||||
@@ -32,11 +33,13 @@ const $unpackProjectTemplate = ({ roles, environments, ...rest }: TProjectTempla
|
|||||||
...rest,
|
...rest,
|
||||||
environments: environments as TProjectTemplateEnvironment[],
|
environments: environments as TProjectTemplateEnvironment[],
|
||||||
roles: [
|
roles: [
|
||||||
...getPredefinedRoles("project-template").map(({ name, slug, permissions }) => ({
|
...getPredefinedRoles({ projectId: "project-template", projectType: rest.type as ProjectType }).map(
|
||||||
name,
|
({ name, slug, permissions }) => ({
|
||||||
slug,
|
name,
|
||||||
permissions: permissions as TUnpackedPermission[]
|
slug,
|
||||||
})),
|
permissions: permissions as TUnpackedPermission[]
|
||||||
|
})
|
||||||
|
),
|
||||||
...(roles as TProjectTemplateRole[]).map((role) => ({
|
...(roles as TProjectTemplateRole[]).map((role) => ({
|
||||||
...role,
|
...role,
|
||||||
permissions: unpackPermissions(role.permissions)
|
permissions: unpackPermissions(role.permissions)
|
||||||
@@ -49,7 +52,7 @@ export const projectTemplateServiceFactory = ({
|
|||||||
permissionService,
|
permissionService,
|
||||||
projectTemplateDAL
|
projectTemplateDAL
|
||||||
}: TProjectTemplatesServiceFactoryDep) => {
|
}: TProjectTemplatesServiceFactoryDep) => {
|
||||||
const listProjectTemplatesByOrg = async (actor: OrgServiceActor) => {
|
const listProjectTemplatesByOrg = async (actor: OrgServiceActor, type?: ProjectType) => {
|
||||||
const plan = await licenseService.getPlan(actor.orgId);
|
const plan = await licenseService.getPlan(actor.orgId);
|
||||||
|
|
||||||
if (!plan.projectTemplates)
|
if (!plan.projectTemplates)
|
||||||
@@ -68,11 +71,14 @@ export const projectTemplateServiceFactory = ({
|
|||||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.ProjectTemplates);
|
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.ProjectTemplates);
|
||||||
|
|
||||||
const projectTemplates = await projectTemplateDAL.find({
|
const projectTemplates = await projectTemplateDAL.find({
|
||||||
orgId: actor.orgId
|
orgId: actor.orgId,
|
||||||
|
...(type ? { type } : {})
|
||||||
});
|
});
|
||||||
|
|
||||||
return [
|
return [
|
||||||
getDefaultProjectTemplate(actor.orgId),
|
...(type
|
||||||
|
? [getDefaultProjectTemplate(actor.orgId, type)]
|
||||||
|
: Object.values(ProjectType).map((projectType) => getDefaultProjectTemplate(actor.orgId, projectType))),
|
||||||
...projectTemplates.map((template) => $unpackProjectTemplate(template))
|
...projectTemplates.map((template) => $unpackProjectTemplate(template))
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
@@ -134,7 +140,7 @@ export const projectTemplateServiceFactory = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const createProjectTemplate = async (
|
const createProjectTemplate = async (
|
||||||
{ roles, environments, ...params }: TCreateProjectTemplateDTO,
|
{ roles, environments, type, ...params }: TCreateProjectTemplateDTO,
|
||||||
actor: OrgServiceActor
|
actor: OrgServiceActor
|
||||||
) => {
|
) => {
|
||||||
const plan = await licenseService.getPlan(actor.orgId);
|
const plan = await licenseService.getPlan(actor.orgId);
|
||||||
@@ -154,6 +160,17 @@ export const projectTemplateServiceFactory = ({
|
|||||||
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.ProjectTemplates);
|
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.ProjectTemplates);
|
||||||
|
|
||||||
|
if (environments && type !== ProjectType.SecretManager) {
|
||||||
|
throw new BadRequestError({ message: "Cannot configure environments for non-SecretManager project templates" });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (environments && plan.environmentLimit !== null && environments.length > plan.environmentLimit) {
|
||||||
|
throw new BadRequestError({
|
||||||
|
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
|
||||||
|
message: `Failed to create project template due to environment count exceeding your current limit of ${plan.environmentLimit}. Contact Infisical to increase limit.`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const isConflictingName = Boolean(
|
const isConflictingName = Boolean(
|
||||||
await projectTemplateDAL.findOne({
|
await projectTemplateDAL.findOne({
|
||||||
name: params.name,
|
name: params.name,
|
||||||
@@ -169,8 +186,10 @@ export const projectTemplateServiceFactory = ({
|
|||||||
const projectTemplate = await projectTemplateDAL.create({
|
const projectTemplate = await projectTemplateDAL.create({
|
||||||
...params,
|
...params,
|
||||||
roles: JSON.stringify(roles.map((role) => ({ ...role, permissions: packRules(role.permissions) }))),
|
roles: JSON.stringify(roles.map((role) => ({ ...role, permissions: packRules(role.permissions) }))),
|
||||||
environments: JSON.stringify(environments),
|
environments:
|
||||||
orgId: actor.orgId
|
type === ProjectType.SecretManager ? JSON.stringify(environments ?? ProjectTemplateDefaultEnvironments) : null,
|
||||||
|
orgId: actor.orgId,
|
||||||
|
type
|
||||||
});
|
});
|
||||||
|
|
||||||
return $unpackProjectTemplate(projectTemplate);
|
return $unpackProjectTemplate(projectTemplate);
|
||||||
@@ -202,6 +221,19 @@ export const projectTemplateServiceFactory = ({
|
|||||||
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.ProjectTemplates);
|
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.ProjectTemplates);
|
||||||
|
|
||||||
|
if (projectTemplate.type !== ProjectType.SecretManager && environments)
|
||||||
|
throw new BadRequestError({ message: "Cannot configure environments for non-SecretManager project templates" });
|
||||||
|
|
||||||
|
if (projectTemplate.type === ProjectType.SecretManager && environments === null)
|
||||||
|
throw new BadRequestError({ message: "Environments cannot be removed for SecretManager project templates" });
|
||||||
|
|
||||||
|
if (environments && plan.environmentLimit !== null && environments.length > plan.environmentLimit) {
|
||||||
|
throw new BadRequestError({
|
||||||
|
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
|
||||||
|
message: `Failed to update project template due to environment count exceeding your current limit of ${plan.environmentLimit}. Contact Infisical to increase limit.`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if (params.name && projectTemplate.name !== params.name) {
|
if (params.name && projectTemplate.name !== params.name) {
|
||||||
const isConflictingName = Boolean(
|
const isConflictingName = Boolean(
|
||||||
await projectTemplateDAL.findOne({
|
await projectTemplateDAL.findOne({
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import { TProjectEnvironments } from "@app/db/schemas";
|
import { ProjectType, TProjectEnvironments } from "@app/db/schemas";
|
||||||
import { TProjectPermissionV2Schema } from "@app/ee/services/permission/project-permission";
|
import { TProjectPermissionV2Schema } from "@app/ee/services/permission/project-permission";
|
||||||
import { UnpackedPermissionSchema } from "@app/server/routes/sanitizedSchema/permission";
|
import { UnpackedPermissionSchema } from "@app/server/routes/sanitizedSchema/permission";
|
||||||
|
|
||||||
@@ -15,8 +15,9 @@ export type TProjectTemplateRole = {
|
|||||||
export type TCreateProjectTemplateDTO = {
|
export type TCreateProjectTemplateDTO = {
|
||||||
name: string;
|
name: string;
|
||||||
description?: string;
|
description?: string;
|
||||||
|
type: ProjectType;
|
||||||
roles: TProjectTemplateRole[];
|
roles: TProjectTemplateRole[];
|
||||||
environments: TProjectTemplateEnvironment[];
|
environments?: TProjectTemplateEnvironment[] | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TUpdateProjectTemplateDTO = Partial<TCreateProjectTemplateDTO>;
|
export type TUpdateProjectTemplateDTO = Partial<TCreateProjectTemplateDTO>;
|
||||||
|
@@ -334,7 +334,7 @@ export const secretApprovalRequestDALFactory = (db: TDbClient) => {
|
|||||||
db.ref("secretId").withSchema(TableName.SecretApprovalRequestSecret).as("commitSecretId"),
|
db.ref("secretId").withSchema(TableName.SecretApprovalRequestSecret).as("commitSecretId"),
|
||||||
db.ref("id").withSchema(TableName.SecretApprovalRequestSecret).as("commitId"),
|
db.ref("id").withSchema(TableName.SecretApprovalRequestSecret).as("commitId"),
|
||||||
db.raw(
|
db.raw(
|
||||||
`DENSE_RANK() OVER (partition by ${TableName.Environment}."projectId" ORDER BY ${TableName.SecretApprovalRequest}."id" DESC) as rank`
|
`DENSE_RANK() OVER (PARTITION BY ${TableName.Environment}."projectId" ORDER BY ${TableName.SecretApprovalRequest}."createdAt" DESC) as rank`
|
||||||
),
|
),
|
||||||
db.ref("secretPath").withSchema(TableName.SecretApprovalPolicy).as("policySecretPath"),
|
db.ref("secretPath").withSchema(TableName.SecretApprovalPolicy).as("policySecretPath"),
|
||||||
db.ref("enforcementLevel").withSchema(TableName.SecretApprovalPolicy).as("policyEnforcementLevel"),
|
db.ref("enforcementLevel").withSchema(TableName.SecretApprovalPolicy).as("policyEnforcementLevel"),
|
||||||
@@ -483,7 +483,7 @@ export const secretApprovalRequestDALFactory = (db: TDbClient) => {
|
|||||||
db.ref("secretId").withSchema(TableName.SecretApprovalRequestSecretV2).as("commitSecretId"),
|
db.ref("secretId").withSchema(TableName.SecretApprovalRequestSecretV2).as("commitSecretId"),
|
||||||
db.ref("id").withSchema(TableName.SecretApprovalRequestSecretV2).as("commitId"),
|
db.ref("id").withSchema(TableName.SecretApprovalRequestSecretV2).as("commitId"),
|
||||||
db.raw(
|
db.raw(
|
||||||
`DENSE_RANK() OVER (partition by ${TableName.Environment}."projectId" ORDER BY ${TableName.SecretApprovalRequest}."id" DESC) as rank`
|
`DENSE_RANK() OVER (PARTITION BY ${TableName.Environment}."projectId" ORDER BY ${TableName.SecretApprovalRequest}."createdAt" DESC) as rank`
|
||||||
),
|
),
|
||||||
db.ref("secretPath").withSchema(TableName.SecretApprovalPolicy).as("policySecretPath"),
|
db.ref("secretPath").withSchema(TableName.SecretApprovalPolicy).as("policySecretPath"),
|
||||||
db.ref("allowedSelfApprovals").withSchema(TableName.SecretApprovalPolicy).as("policyAllowedSelfApprovals"),
|
db.ref("allowedSelfApprovals").withSchema(TableName.SecretApprovalPolicy).as("policyAllowedSelfApprovals"),
|
||||||
|
@@ -17,9 +17,13 @@ import { groupBy, pick, unique } from "@app/lib/fn";
|
|||||||
import { setKnexStringValue } from "@app/lib/knex";
|
import { setKnexStringValue } from "@app/lib/knex";
|
||||||
import { alphaNumericNanoId } from "@app/lib/nanoid";
|
import { alphaNumericNanoId } from "@app/lib/nanoid";
|
||||||
import { EnforcementLevel } from "@app/lib/types";
|
import { EnforcementLevel } from "@app/lib/types";
|
||||||
|
import { triggerWorkflowIntegrationNotification } from "@app/lib/workflow-integrations/trigger-notification";
|
||||||
|
import { TriggerFeature } from "@app/lib/workflow-integrations/types";
|
||||||
import { ActorType } from "@app/services/auth/auth-type";
|
import { ActorType } from "@app/services/auth/auth-type";
|
||||||
import { TKmsServiceFactory } from "@app/services/kms/kms-service";
|
import { TKmsServiceFactory } from "@app/services/kms/kms-service";
|
||||||
import { KmsDataKey } from "@app/services/kms/kms-types";
|
import { KmsDataKey } from "@app/services/kms/kms-types";
|
||||||
|
import { TMicrosoftTeamsServiceFactory } from "@app/services/microsoft-teams/microsoft-teams-service";
|
||||||
|
import { TProjectMicrosoftTeamsConfigDALFactory } from "@app/services/microsoft-teams/project-microsoft-teams-config-dal";
|
||||||
import { TProjectDALFactory } from "@app/services/project/project-dal";
|
import { TProjectDALFactory } from "@app/services/project/project-dal";
|
||||||
import { TProjectBotServiceFactory } from "@app/services/project-bot/project-bot-service";
|
import { TProjectBotServiceFactory } from "@app/services/project-bot/project-bot-service";
|
||||||
import { TProjectEnvDALFactory } from "@app/services/project-env/project-env-dal";
|
import { TProjectEnvDALFactory } from "@app/services/project-env/project-env-dal";
|
||||||
@@ -52,8 +56,6 @@ import {
|
|||||||
import { TSecretVersionV2DALFactory } from "@app/services/secret-v2-bridge/secret-version-dal";
|
import { TSecretVersionV2DALFactory } from "@app/services/secret-v2-bridge/secret-version-dal";
|
||||||
import { TSecretVersionV2TagDALFactory } from "@app/services/secret-v2-bridge/secret-version-tag-dal";
|
import { TSecretVersionV2TagDALFactory } from "@app/services/secret-v2-bridge/secret-version-tag-dal";
|
||||||
import { TProjectSlackConfigDALFactory } from "@app/services/slack/project-slack-config-dal";
|
import { TProjectSlackConfigDALFactory } from "@app/services/slack/project-slack-config-dal";
|
||||||
import { triggerSlackNotification } from "@app/services/slack/slack-fns";
|
|
||||||
import { SlackTriggerFeature } from "@app/services/slack/slack-types";
|
|
||||||
import { SmtpTemplates, TSmtpService } from "@app/services/smtp/smtp-service";
|
import { SmtpTemplates, TSmtpService } from "@app/services/smtp/smtp-service";
|
||||||
import { TUserDALFactory } from "@app/services/user/user-dal";
|
import { TUserDALFactory } from "@app/services/user/user-dal";
|
||||||
|
|
||||||
@@ -126,6 +128,8 @@ type TSecretApprovalRequestServiceFactoryDep = {
|
|||||||
secretApprovalPolicyDAL: Pick<TSecretApprovalPolicyDALFactory, "findById">;
|
secretApprovalPolicyDAL: Pick<TSecretApprovalPolicyDALFactory, "findById">;
|
||||||
projectSlackConfigDAL: Pick<TProjectSlackConfigDALFactory, "getIntegrationDetailsByProject">;
|
projectSlackConfigDAL: Pick<TProjectSlackConfigDALFactory, "getIntegrationDetailsByProject">;
|
||||||
licenseService: Pick<TLicenseServiceFactory, "getPlan">;
|
licenseService: Pick<TLicenseServiceFactory, "getPlan">;
|
||||||
|
projectMicrosoftTeamsConfigDAL: Pick<TProjectMicrosoftTeamsConfigDALFactory, "getIntegrationDetailsByProject">;
|
||||||
|
microsoftTeamsService: Pick<TMicrosoftTeamsServiceFactory, "sendNotification">;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TSecretApprovalRequestServiceFactory = ReturnType<typeof secretApprovalRequestServiceFactory>;
|
export type TSecretApprovalRequestServiceFactory = ReturnType<typeof secretApprovalRequestServiceFactory>;
|
||||||
@@ -155,7 +159,9 @@ export const secretApprovalRequestServiceFactory = ({
|
|||||||
secretVersionTagV2BridgeDAL,
|
secretVersionTagV2BridgeDAL,
|
||||||
licenseService,
|
licenseService,
|
||||||
projectSlackConfigDAL,
|
projectSlackConfigDAL,
|
||||||
resourceMetadataDAL
|
resourceMetadataDAL,
|
||||||
|
projectMicrosoftTeamsConfigDAL,
|
||||||
|
microsoftTeamsService
|
||||||
}: TSecretApprovalRequestServiceFactoryDep) => {
|
}: TSecretApprovalRequestServiceFactoryDep) => {
|
||||||
const requestCount = async ({ projectId, actor, actorId, actorOrgId, actorAuthMethod }: TApprovalRequestCountDTO) => {
|
const requestCount = async ({ projectId, actor, actorId, actorOrgId, actorAuthMethod }: TApprovalRequestCountDTO) => {
|
||||||
if (actor === ActorType.SERVICE) throw new BadRequestError({ message: "Cannot use service token" });
|
if (actor === ActorType.SERVICE) throw new BadRequestError({ message: "Cannot use service token" });
|
||||||
@@ -1171,21 +1177,28 @@ export const secretApprovalRequestServiceFactory = ({
|
|||||||
|
|
||||||
const env = await projectEnvDAL.findOne({ id: policy.envId });
|
const env = await projectEnvDAL.findOne({ id: policy.envId });
|
||||||
const user = await userDAL.findById(secretApprovalRequest.committerUserId);
|
const user = await userDAL.findById(secretApprovalRequest.committerUserId);
|
||||||
await triggerSlackNotification({
|
|
||||||
projectId,
|
await triggerWorkflowIntegrationNotification({
|
||||||
projectDAL,
|
input: {
|
||||||
kmsService,
|
projectId,
|
||||||
projectSlackConfigDAL,
|
notification: {
|
||||||
notification: {
|
type: TriggerFeature.SECRET_APPROVAL,
|
||||||
type: SlackTriggerFeature.SECRET_APPROVAL,
|
payload: {
|
||||||
payload: {
|
userEmail: user.email as string,
|
||||||
userEmail: user.email as string,
|
environment: env.name,
|
||||||
environment: env.name,
|
secretPath,
|
||||||
secretPath,
|
projectId,
|
||||||
projectId,
|
requestId: secretApprovalRequest.id,
|
||||||
requestId: secretApprovalRequest.id,
|
secretKeys: [...new Set(Object.values(data).flatMap((arr) => arr?.map((item) => item.secretName) ?? []))]
|
||||||
secretKeys: [...new Set(Object.values(data).flatMap((arr) => arr?.map((item) => item.secretName) ?? []))]
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
dependencies: {
|
||||||
|
projectDAL,
|
||||||
|
projectSlackConfigDAL,
|
||||||
|
kmsService,
|
||||||
|
projectMicrosoftTeamsConfigDAL,
|
||||||
|
microsoftTeamsService
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1503,21 +1516,28 @@ export const secretApprovalRequestServiceFactory = ({
|
|||||||
|
|
||||||
const user = await userDAL.findById(secretApprovalRequest.committerUserId);
|
const user = await userDAL.findById(secretApprovalRequest.committerUserId);
|
||||||
const env = await projectEnvDAL.findOne({ id: policy.envId });
|
const env = await projectEnvDAL.findOne({ id: policy.envId });
|
||||||
await triggerSlackNotification({
|
|
||||||
projectId,
|
await triggerWorkflowIntegrationNotification({
|
||||||
projectDAL,
|
input: {
|
||||||
kmsService,
|
projectId,
|
||||||
projectSlackConfigDAL,
|
notification: {
|
||||||
notification: {
|
type: TriggerFeature.SECRET_APPROVAL,
|
||||||
type: SlackTriggerFeature.SECRET_APPROVAL,
|
payload: {
|
||||||
payload: {
|
userEmail: user.email as string,
|
||||||
userEmail: user.email as string,
|
environment: env.name,
|
||||||
environment: env.name,
|
secretPath,
|
||||||
secretPath,
|
projectId,
|
||||||
projectId,
|
requestId: secretApprovalRequest.id,
|
||||||
requestId: secretApprovalRequest.id,
|
secretKeys: [...new Set(Object.values(data).flatMap((arr) => arr?.map((item) => item.secretKey) ?? []))]
|
||||||
secretKeys: [...new Set(Object.values(data).flatMap((arr) => arr?.map((item) => item.secretKey) ?? []))]
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
dependencies: {
|
||||||
|
projectDAL,
|
||||||
|
kmsService,
|
||||||
|
projectSlackConfigDAL,
|
||||||
|
microsoftTeamsService,
|
||||||
|
projectMicrosoftTeamsConfigDAL
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@@ -0,0 +1,15 @@
|
|||||||
|
import { SecretRotation } from "@app/ee/services/secret-rotation-v2/secret-rotation-v2-enums";
|
||||||
|
import { TSecretRotationV2ListItem } from "@app/ee/services/secret-rotation-v2/secret-rotation-v2-types";
|
||||||
|
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
|
||||||
|
|
||||||
|
export const AZURE_CLIENT_SECRET_ROTATION_LIST_OPTION: TSecretRotationV2ListItem = {
|
||||||
|
name: "Azure Client Secret",
|
||||||
|
type: SecretRotation.AzureClientSecret,
|
||||||
|
connection: AppConnection.AzureClientSecrets,
|
||||||
|
template: {
|
||||||
|
secretsMapping: {
|
||||||
|
clientId: "AZURE_CLIENT_ID",
|
||||||
|
clientSecret: "AZURE_CLIENT_SECRET"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
@@ -0,0 +1,202 @@
|
|||||||
|
/* eslint-disable no-await-in-loop */
|
||||||
|
import { AxiosError } from "axios";
|
||||||
|
|
||||||
|
import {
|
||||||
|
AzureAddPasswordResponse,
|
||||||
|
TAzureClientSecretRotationGeneratedCredentials,
|
||||||
|
TAzureClientSecretRotationWithConnection
|
||||||
|
} from "@app/ee/services/secret-rotation-v2/azure-client-secret/azure-client-secret-rotation-types";
|
||||||
|
import {
|
||||||
|
TRotationFactory,
|
||||||
|
TRotationFactoryGetSecretsPayload,
|
||||||
|
TRotationFactoryIssueCredentials,
|
||||||
|
TRotationFactoryRevokeCredentials,
|
||||||
|
TRotationFactoryRotateCredentials
|
||||||
|
} from "@app/ee/services/secret-rotation-v2/secret-rotation-v2-types";
|
||||||
|
import { request } from "@app/lib/config/request";
|
||||||
|
import { BadRequestError } from "@app/lib/errors";
|
||||||
|
import { getAzureConnectionAccessToken } from "@app/services/app-connection/azure-client-secrets";
|
||||||
|
|
||||||
|
const GRAPH_API_BASE = "https://graph.microsoft.com/v1.0";
|
||||||
|
|
||||||
|
type AzureErrorResponse = { error: { message: string } };
|
||||||
|
|
||||||
|
const sleep = async () =>
|
||||||
|
new Promise((resolve) => {
|
||||||
|
setTimeout(resolve, 1000);
|
||||||
|
});
|
||||||
|
|
||||||
|
export const azureClientSecretRotationFactory: TRotationFactory<
|
||||||
|
TAzureClientSecretRotationWithConnection,
|
||||||
|
TAzureClientSecretRotationGeneratedCredentials
|
||||||
|
> = (secretRotation, appConnectionDAL, kmsService) => {
|
||||||
|
const {
|
||||||
|
connection,
|
||||||
|
parameters: { objectId, clientId: clientIdParam },
|
||||||
|
secretsMapping
|
||||||
|
} = secretRotation;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new client secret for the Azure app.
|
||||||
|
*/
|
||||||
|
const $rotateClientSecret = async () => {
|
||||||
|
const accessToken = await getAzureConnectionAccessToken(connection.id, appConnectionDAL, kmsService);
|
||||||
|
const endpoint = `${GRAPH_API_BASE}/applications/${objectId}/addPassword`;
|
||||||
|
|
||||||
|
const now = new Date();
|
||||||
|
const formattedDate = `${String(now.getMonth() + 1).padStart(2, "0")}-${String(now.getDate()).padStart(
|
||||||
|
2,
|
||||||
|
"0"
|
||||||
|
)}-${now.getFullYear()}`;
|
||||||
|
|
||||||
|
const endDateTime = new Date();
|
||||||
|
endDateTime.setFullYear(now.getFullYear() + 5);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const { data } = await request.post<AzureAddPasswordResponse>(
|
||||||
|
endpoint,
|
||||||
|
{
|
||||||
|
passwordCredential: {
|
||||||
|
displayName: `Infisical Rotated Secret (${formattedDate})`,
|
||||||
|
endDateTime: endDateTime.toISOString()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${accessToken}`,
|
||||||
|
"Content-Type": "application/json"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!data?.secretText || !data?.keyId) {
|
||||||
|
throw new Error("Invalid response from Azure: missing secretText or keyId.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
clientSecret: data.secretText,
|
||||||
|
keyId: data.keyId,
|
||||||
|
clientId: clientIdParam
|
||||||
|
};
|
||||||
|
} catch (error: unknown) {
|
||||||
|
if (error instanceof AxiosError) {
|
||||||
|
let message;
|
||||||
|
if (
|
||||||
|
error.response?.data &&
|
||||||
|
typeof error.response.data === "object" &&
|
||||||
|
"error" in error.response.data &&
|
||||||
|
typeof (error.response.data as AzureErrorResponse).error.message === "string"
|
||||||
|
) {
|
||||||
|
message = (error.response.data as AzureErrorResponse).error.message;
|
||||||
|
}
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: `Failed to add client secret to Azure app ${objectId}: ${
|
||||||
|
message || error.message || "Unknown error"
|
||||||
|
}`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: "Unable to validate connection: verify credentials"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Revokes a client secret from the Azure app using its keyId.
|
||||||
|
*/
|
||||||
|
const revokeCredential = async (keyId: string) => {
|
||||||
|
const accessToken = await getAzureConnectionAccessToken(connection.id, appConnectionDAL, kmsService);
|
||||||
|
const endpoint = `${GRAPH_API_BASE}/applications/${objectId}/removePassword`;
|
||||||
|
|
||||||
|
try {
|
||||||
|
await request.post(
|
||||||
|
endpoint,
|
||||||
|
{ keyId },
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${accessToken}`,
|
||||||
|
"Content-Type": "application/json"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
} catch (error: unknown) {
|
||||||
|
if (error instanceof AxiosError) {
|
||||||
|
let message;
|
||||||
|
if (
|
||||||
|
error.response?.data &&
|
||||||
|
typeof error.response.data === "object" &&
|
||||||
|
"error" in error.response.data &&
|
||||||
|
typeof (error.response.data as AzureErrorResponse).error.message === "string"
|
||||||
|
) {
|
||||||
|
message = (error.response.data as AzureErrorResponse).error.message;
|
||||||
|
}
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: `Failed to remove client secret with keyId ${keyId} from app ${objectId}: ${
|
||||||
|
message || error.message || "Unknown error"
|
||||||
|
}`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: "Unable to validate connection: verify credentials"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Issues a new set of credentials.
|
||||||
|
*/
|
||||||
|
const issueCredentials: TRotationFactoryIssueCredentials<TAzureClientSecretRotationGeneratedCredentials> = async (
|
||||||
|
callback
|
||||||
|
) => {
|
||||||
|
const credentials = await $rotateClientSecret();
|
||||||
|
return callback(credentials);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Revokes a list of credentials.
|
||||||
|
*/
|
||||||
|
const revokeCredentials: TRotationFactoryRevokeCredentials<TAzureClientSecretRotationGeneratedCredentials> = async (
|
||||||
|
credentials,
|
||||||
|
callback
|
||||||
|
) => {
|
||||||
|
if (!credentials?.length) return callback();
|
||||||
|
|
||||||
|
for (const { keyId } of credentials) {
|
||||||
|
await revokeCredential(keyId);
|
||||||
|
await sleep();
|
||||||
|
}
|
||||||
|
return callback();
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rotates credentials by issuing new ones and revoking the old.
|
||||||
|
*/
|
||||||
|
const rotateCredentials: TRotationFactoryRotateCredentials<TAzureClientSecretRotationGeneratedCredentials> = async (
|
||||||
|
oldCredentials,
|
||||||
|
callback
|
||||||
|
) => {
|
||||||
|
const newCredentials = await $rotateClientSecret();
|
||||||
|
if (oldCredentials?.keyId) {
|
||||||
|
await revokeCredential(oldCredentials.keyId);
|
||||||
|
}
|
||||||
|
|
||||||
|
return callback(newCredentials);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maps the generated credentials into the secret payload format.
|
||||||
|
*/
|
||||||
|
const getSecretsPayload: TRotationFactoryGetSecretsPayload<TAzureClientSecretRotationGeneratedCredentials> = ({
|
||||||
|
clientSecret
|
||||||
|
}) => [
|
||||||
|
{ key: secretsMapping.clientSecret, value: clientSecret },
|
||||||
|
{ key: secretsMapping.clientId, value: clientIdParam }
|
||||||
|
];
|
||||||
|
|
||||||
|
return {
|
||||||
|
issueCredentials,
|
||||||
|
revokeCredentials,
|
||||||
|
rotateCredentials,
|
||||||
|
getSecretsPayload
|
||||||
|
};
|
||||||
|
};
|
@@ -0,0 +1,74 @@
|
|||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { SecretRotation } from "@app/ee/services/secret-rotation-v2/secret-rotation-v2-enums";
|
||||||
|
import {
|
||||||
|
BaseCreateSecretRotationSchema,
|
||||||
|
BaseSecretRotationSchema,
|
||||||
|
BaseUpdateSecretRotationSchema
|
||||||
|
} from "@app/ee/services/secret-rotation-v2/secret-rotation-v2-schemas";
|
||||||
|
import { SecretRotations } from "@app/lib/api-docs";
|
||||||
|
import { SecretNameSchema } from "@app/server/lib/schemas";
|
||||||
|
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
|
||||||
|
|
||||||
|
export const AzureClientSecretRotationGeneratedCredentialsSchema = z
|
||||||
|
.object({
|
||||||
|
clientId: z.string(),
|
||||||
|
clientSecret: z.string(),
|
||||||
|
keyId: z.string()
|
||||||
|
})
|
||||||
|
.array()
|
||||||
|
.min(1)
|
||||||
|
.max(2);
|
||||||
|
|
||||||
|
const AzureClientSecretRotationParametersSchema = z.object({
|
||||||
|
objectId: z
|
||||||
|
.string()
|
||||||
|
.trim()
|
||||||
|
.min(1, "Object ID Required")
|
||||||
|
.describe(SecretRotations.PARAMETERS.AZURE_CLIENT_SECRET.objectId),
|
||||||
|
appName: z.string().trim().describe(SecretRotations.PARAMETERS.AZURE_CLIENT_SECRET.appName).optional(),
|
||||||
|
clientId: z
|
||||||
|
.string()
|
||||||
|
.trim()
|
||||||
|
.min(1, "Client ID Required")
|
||||||
|
.describe(SecretRotations.PARAMETERS.AZURE_CLIENT_SECRET.clientId)
|
||||||
|
});
|
||||||
|
|
||||||
|
const AzureClientSecretRotationSecretsMappingSchema = z.object({
|
||||||
|
clientId: SecretNameSchema.describe(SecretRotations.SECRETS_MAPPING.AZURE_CLIENT_SECRET.clientId),
|
||||||
|
clientSecret: SecretNameSchema.describe(SecretRotations.SECRETS_MAPPING.AZURE_CLIENT_SECRET.clientSecret)
|
||||||
|
});
|
||||||
|
|
||||||
|
export const AzureClientSecretRotationTemplateSchema = z.object({
|
||||||
|
secretsMapping: z.object({
|
||||||
|
clientId: z.string(),
|
||||||
|
clientSecret: z.string()
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
export const AzureClientSecretRotationSchema = BaseSecretRotationSchema(SecretRotation.AzureClientSecret).extend({
|
||||||
|
type: z.literal(SecretRotation.AzureClientSecret),
|
||||||
|
parameters: AzureClientSecretRotationParametersSchema,
|
||||||
|
secretsMapping: AzureClientSecretRotationSecretsMappingSchema
|
||||||
|
});
|
||||||
|
|
||||||
|
export const CreateAzureClientSecretRotationSchema = BaseCreateSecretRotationSchema(
|
||||||
|
SecretRotation.AzureClientSecret
|
||||||
|
).extend({
|
||||||
|
parameters: AzureClientSecretRotationParametersSchema,
|
||||||
|
secretsMapping: AzureClientSecretRotationSecretsMappingSchema
|
||||||
|
});
|
||||||
|
|
||||||
|
export const UpdateAzureClientSecretRotationSchema = BaseUpdateSecretRotationSchema(
|
||||||
|
SecretRotation.AzureClientSecret
|
||||||
|
).extend({
|
||||||
|
parameters: AzureClientSecretRotationParametersSchema.optional(),
|
||||||
|
secretsMapping: AzureClientSecretRotationSecretsMappingSchema.optional()
|
||||||
|
});
|
||||||
|
|
||||||
|
export const AzureClientSecretRotationListItemSchema = z.object({
|
||||||
|
name: z.literal("Azure Client Secret"),
|
||||||
|
connection: z.literal(AppConnection.AzureClientSecrets),
|
||||||
|
type: z.literal(SecretRotation.AzureClientSecret),
|
||||||
|
template: AzureClientSecretRotationTemplateSchema
|
||||||
|
});
|
@@ -0,0 +1,41 @@
|
|||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { TAzureClientSecretsConnection } from "@app/services/app-connection/azure-client-secrets";
|
||||||
|
|
||||||
|
import {
|
||||||
|
AzureClientSecretRotationGeneratedCredentialsSchema,
|
||||||
|
AzureClientSecretRotationListItemSchema,
|
||||||
|
AzureClientSecretRotationSchema,
|
||||||
|
CreateAzureClientSecretRotationSchema
|
||||||
|
} from "./azure-client-secret-rotation-schemas";
|
||||||
|
|
||||||
|
export type TAzureClientSecretRotation = z.infer<typeof AzureClientSecretRotationSchema>;
|
||||||
|
|
||||||
|
export type TAzureClientSecretRotationInput = z.infer<typeof CreateAzureClientSecretRotationSchema>;
|
||||||
|
|
||||||
|
export type TAzureClientSecretRotationListItem = z.infer<typeof AzureClientSecretRotationListItemSchema>;
|
||||||
|
|
||||||
|
export type TAzureClientSecretRotationWithConnection = TAzureClientSecretRotation & {
|
||||||
|
connection: TAzureClientSecretsConnection;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TAzureClientSecretRotationGeneratedCredentials = z.infer<
|
||||||
|
typeof AzureClientSecretRotationGeneratedCredentialsSchema
|
||||||
|
>;
|
||||||
|
|
||||||
|
export interface TAzureClientSecretRotationParameters {
|
||||||
|
appId: string;
|
||||||
|
keyId?: string;
|
||||||
|
displayName?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TAzureClientSecretRotationSecretsMapping {
|
||||||
|
appId: string;
|
||||||
|
clientSecret: string;
|
||||||
|
keyId: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AzureAddPasswordResponse {
|
||||||
|
secretText: string;
|
||||||
|
keyId: string;
|
||||||
|
}
|
@@ -0,0 +1,3 @@
|
|||||||
|
export * from "./azure-client-secret-rotation-constants";
|
||||||
|
export * from "./azure-client-secret-rotation-schemas";
|
||||||
|
export * from "./azure-client-secret-rotation-types";
|
@@ -2,8 +2,9 @@ export enum SecretRotation {
|
|||||||
PostgresCredentials = "postgres-credentials",
|
PostgresCredentials = "postgres-credentials",
|
||||||
MsSqlCredentials = "mssql-credentials",
|
MsSqlCredentials = "mssql-credentials",
|
||||||
Auth0ClientSecret = "auth0-client-secret",
|
Auth0ClientSecret = "auth0-client-secret",
|
||||||
LdapPassword = "ldap-password",
|
AzureClientSecret = "azure-client-secret",
|
||||||
AwsIamUserSecret = "aws-iam-user-secret"
|
AwsIamUserSecret = "aws-iam-user-secret",
|
||||||
|
LdapPassword = "ldap-password"
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum SecretRotationStatus {
|
export enum SecretRotationStatus {
|
||||||
|
@@ -5,6 +5,7 @@ import { KmsDataKey } from "@app/services/kms/kms-types";
|
|||||||
|
|
||||||
import { AUTH0_CLIENT_SECRET_ROTATION_LIST_OPTION } from "./auth0-client-secret";
|
import { AUTH0_CLIENT_SECRET_ROTATION_LIST_OPTION } from "./auth0-client-secret";
|
||||||
import { AWS_IAM_USER_SECRET_ROTATION_LIST_OPTION } from "./aws-iam-user-secret";
|
import { AWS_IAM_USER_SECRET_ROTATION_LIST_OPTION } from "./aws-iam-user-secret";
|
||||||
|
import { AZURE_CLIENT_SECRET_ROTATION_LIST_OPTION } from "./azure-client-secret";
|
||||||
import { LDAP_PASSWORD_ROTATION_LIST_OPTION } from "./ldap-password";
|
import { LDAP_PASSWORD_ROTATION_LIST_OPTION } from "./ldap-password";
|
||||||
import { MSSQL_CREDENTIALS_ROTATION_LIST_OPTION } from "./mssql-credentials";
|
import { MSSQL_CREDENTIALS_ROTATION_LIST_OPTION } from "./mssql-credentials";
|
||||||
import { POSTGRES_CREDENTIALS_ROTATION_LIST_OPTION } from "./postgres-credentials";
|
import { POSTGRES_CREDENTIALS_ROTATION_LIST_OPTION } from "./postgres-credentials";
|
||||||
@@ -21,8 +22,9 @@ const SECRET_ROTATION_LIST_OPTIONS: Record<SecretRotation, TSecretRotationV2List
|
|||||||
[SecretRotation.PostgresCredentials]: POSTGRES_CREDENTIALS_ROTATION_LIST_OPTION,
|
[SecretRotation.PostgresCredentials]: POSTGRES_CREDENTIALS_ROTATION_LIST_OPTION,
|
||||||
[SecretRotation.MsSqlCredentials]: MSSQL_CREDENTIALS_ROTATION_LIST_OPTION,
|
[SecretRotation.MsSqlCredentials]: MSSQL_CREDENTIALS_ROTATION_LIST_OPTION,
|
||||||
[SecretRotation.Auth0ClientSecret]: AUTH0_CLIENT_SECRET_ROTATION_LIST_OPTION,
|
[SecretRotation.Auth0ClientSecret]: AUTH0_CLIENT_SECRET_ROTATION_LIST_OPTION,
|
||||||
[SecretRotation.LdapPassword]: LDAP_PASSWORD_ROTATION_LIST_OPTION,
|
[SecretRotation.AzureClientSecret]: AZURE_CLIENT_SECRET_ROTATION_LIST_OPTION,
|
||||||
[SecretRotation.AwsIamUserSecret]: AWS_IAM_USER_SECRET_ROTATION_LIST_OPTION
|
[SecretRotation.AwsIamUserSecret]: AWS_IAM_USER_SECRET_ROTATION_LIST_OPTION,
|
||||||
|
[SecretRotation.LdapPassword]: LDAP_PASSWORD_ROTATION_LIST_OPTION
|
||||||
};
|
};
|
||||||
|
|
||||||
export const listSecretRotationOptions = () => {
|
export const listSecretRotationOptions = () => {
|
||||||
@@ -217,7 +219,7 @@ export const parseRotationErrorMessage = (err: unknown): string => {
|
|||||||
if (err instanceof AxiosError) {
|
if (err instanceof AxiosError) {
|
||||||
errorMessage += err?.response?.data
|
errorMessage += err?.response?.data
|
||||||
? JSON.stringify(err?.response?.data)
|
? JSON.stringify(err?.response?.data)
|
||||||
: err?.message ?? "An unknown error occurred.";
|
: (err?.message ?? "An unknown error occurred.");
|
||||||
} else {
|
} else {
|
||||||
errorMessage += (err as Error)?.message || "An unknown error occurred.";
|
errorMessage += (err as Error)?.message || "An unknown error occurred.";
|
||||||
}
|
}
|
||||||
|
@@ -5,14 +5,16 @@ export const SECRET_ROTATION_NAME_MAP: Record<SecretRotation, string> = {
|
|||||||
[SecretRotation.PostgresCredentials]: "PostgreSQL Credentials",
|
[SecretRotation.PostgresCredentials]: "PostgreSQL Credentials",
|
||||||
[SecretRotation.MsSqlCredentials]: "Microsoft SQL Server Credentials",
|
[SecretRotation.MsSqlCredentials]: "Microsoft SQL Server Credentials",
|
||||||
[SecretRotation.Auth0ClientSecret]: "Auth0 Client Secret",
|
[SecretRotation.Auth0ClientSecret]: "Auth0 Client Secret",
|
||||||
[SecretRotation.LdapPassword]: "LDAP Password",
|
[SecretRotation.AzureClientSecret]: "Azure Client Secret",
|
||||||
[SecretRotation.AwsIamUserSecret]: "AWS IAM User Secret"
|
[SecretRotation.AwsIamUserSecret]: "AWS IAM User Secret",
|
||||||
|
[SecretRotation.LdapPassword]: "LDAP Password"
|
||||||
};
|
};
|
||||||
|
|
||||||
export const SECRET_ROTATION_CONNECTION_MAP: Record<SecretRotation, AppConnection> = {
|
export const SECRET_ROTATION_CONNECTION_MAP: Record<SecretRotation, AppConnection> = {
|
||||||
[SecretRotation.PostgresCredentials]: AppConnection.Postgres,
|
[SecretRotation.PostgresCredentials]: AppConnection.Postgres,
|
||||||
[SecretRotation.MsSqlCredentials]: AppConnection.MsSql,
|
[SecretRotation.MsSqlCredentials]: AppConnection.MsSql,
|
||||||
[SecretRotation.Auth0ClientSecret]: AppConnection.Auth0,
|
[SecretRotation.Auth0ClientSecret]: AppConnection.Auth0,
|
||||||
[SecretRotation.LdapPassword]: AppConnection.LDAP,
|
[SecretRotation.AzureClientSecret]: AppConnection.AzureClientSecrets,
|
||||||
[SecretRotation.AwsIamUserSecret]: AppConnection.AWS
|
[SecretRotation.AwsIamUserSecret]: AppConnection.AWS,
|
||||||
|
[SecretRotation.LdapPassword]: AppConnection.LDAP
|
||||||
};
|
};
|
||||||
|
@@ -20,7 +20,7 @@ export const BaseSecretRotationSchema = (type: SecretRotation) =>
|
|||||||
// unique to provider
|
// unique to provider
|
||||||
type: true,
|
type: true,
|
||||||
parameters: true,
|
parameters: true,
|
||||||
secretMappings: true
|
secretsMapping: true
|
||||||
}).extend({
|
}).extend({
|
||||||
connection: z.object({
|
connection: z.object({
|
||||||
app: z.literal(SECRET_ROTATION_CONNECTION_MAP[type]),
|
app: z.literal(SECRET_ROTATION_CONNECTION_MAP[type]),
|
||||||
|
@@ -14,6 +14,7 @@ import {
|
|||||||
ProjectPermissionSub
|
ProjectPermissionSub
|
||||||
} from "@app/ee/services/permission/project-permission";
|
} from "@app/ee/services/permission/project-permission";
|
||||||
import { auth0ClientSecretRotationFactory } from "@app/ee/services/secret-rotation-v2/auth0-client-secret/auth0-client-secret-rotation-fns";
|
import { auth0ClientSecretRotationFactory } from "@app/ee/services/secret-rotation-v2/auth0-client-secret/auth0-client-secret-rotation-fns";
|
||||||
|
import { azureClientSecretRotationFactory } from "@app/ee/services/secret-rotation-v2/azure-client-secret/azure-client-secret-rotation-fns";
|
||||||
import { ldapPasswordRotationFactory } from "@app/ee/services/secret-rotation-v2/ldap-password/ldap-password-rotation-fns";
|
import { ldapPasswordRotationFactory } from "@app/ee/services/secret-rotation-v2/ldap-password/ldap-password-rotation-fns";
|
||||||
import { SecretRotation, SecretRotationStatus } from "@app/ee/services/secret-rotation-v2/secret-rotation-v2-enums";
|
import { SecretRotation, SecretRotationStatus } from "@app/ee/services/secret-rotation-v2/secret-rotation-v2-enums";
|
||||||
import {
|
import {
|
||||||
@@ -102,7 +103,7 @@ export type TSecretRotationV2ServiceFactoryDep = {
|
|||||||
secretQueueService: Pick<TSecretQueueFactory, "syncSecrets" | "removeSecretReminder">;
|
secretQueueService: Pick<TSecretQueueFactory, "syncSecrets" | "removeSecretReminder">;
|
||||||
snapshotService: Pick<TSecretSnapshotServiceFactory, "performSnapshot">;
|
snapshotService: Pick<TSecretSnapshotServiceFactory, "performSnapshot">;
|
||||||
queueService: Pick<TQueueServiceFactory, "queuePg">;
|
queueService: Pick<TQueueServiceFactory, "queuePg">;
|
||||||
appConnectionDAL: Pick<TAppConnectionDALFactory, "updateById">;
|
appConnectionDAL: Pick<TAppConnectionDALFactory, "findById" | "update" | "updateById">;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TSecretRotationV2ServiceFactory = ReturnType<typeof secretRotationV2ServiceFactory>;
|
export type TSecretRotationV2ServiceFactory = ReturnType<typeof secretRotationV2ServiceFactory>;
|
||||||
@@ -117,8 +118,9 @@ const SECRET_ROTATION_FACTORY_MAP: Record<SecretRotation, TRotationFactoryImplem
|
|||||||
[SecretRotation.PostgresCredentials]: sqlCredentialsRotationFactory as TRotationFactoryImplementation,
|
[SecretRotation.PostgresCredentials]: sqlCredentialsRotationFactory as TRotationFactoryImplementation,
|
||||||
[SecretRotation.MsSqlCredentials]: sqlCredentialsRotationFactory as TRotationFactoryImplementation,
|
[SecretRotation.MsSqlCredentials]: sqlCredentialsRotationFactory as TRotationFactoryImplementation,
|
||||||
[SecretRotation.Auth0ClientSecret]: auth0ClientSecretRotationFactory as TRotationFactoryImplementation,
|
[SecretRotation.Auth0ClientSecret]: auth0ClientSecretRotationFactory as TRotationFactoryImplementation,
|
||||||
[SecretRotation.LdapPassword]: ldapPasswordRotationFactory as TRotationFactoryImplementation,
|
[SecretRotation.AzureClientSecret]: azureClientSecretRotationFactory as TRotationFactoryImplementation,
|
||||||
[SecretRotation.AwsIamUserSecret]: awsIamUserSecretRotationFactory as TRotationFactoryImplementation
|
[SecretRotation.AwsIamUserSecret]: awsIamUserSecretRotationFactory as TRotationFactoryImplementation,
|
||||||
|
[SecretRotation.LdapPassword]: ldapPasswordRotationFactory as TRotationFactoryImplementation
|
||||||
};
|
};
|
||||||
|
|
||||||
export const secretRotationV2ServiceFactory = ({
|
export const secretRotationV2ServiceFactory = ({
|
||||||
@@ -447,7 +449,8 @@ export const secretRotationV2ServiceFactory = ({
|
|||||||
{
|
{
|
||||||
parameters: payload.parameters,
|
parameters: payload.parameters,
|
||||||
secretsMapping,
|
secretsMapping,
|
||||||
connection
|
connection,
|
||||||
|
rotationInterval: payload.rotationInterval
|
||||||
} as TSecretRotationV2WithConnection,
|
} as TSecretRotationV2WithConnection,
|
||||||
appConnectionDAL,
|
appConnectionDAL,
|
||||||
kmsService
|
kmsService
|
||||||
|
@@ -19,6 +19,13 @@ import {
|
|||||||
TAwsIamUserSecretRotationListItem,
|
TAwsIamUserSecretRotationListItem,
|
||||||
TAwsIamUserSecretRotationWithConnection
|
TAwsIamUserSecretRotationWithConnection
|
||||||
} from "./aws-iam-user-secret";
|
} from "./aws-iam-user-secret";
|
||||||
|
import {
|
||||||
|
TAzureClientSecretRotation,
|
||||||
|
TAzureClientSecretRotationGeneratedCredentials,
|
||||||
|
TAzureClientSecretRotationInput,
|
||||||
|
TAzureClientSecretRotationListItem,
|
||||||
|
TAzureClientSecretRotationWithConnection
|
||||||
|
} from "./azure-client-secret";
|
||||||
import {
|
import {
|
||||||
TLdapPasswordRotation,
|
TLdapPasswordRotation,
|
||||||
TLdapPasswordRotationGeneratedCredentials,
|
TLdapPasswordRotationGeneratedCredentials,
|
||||||
@@ -45,6 +52,7 @@ export type TSecretRotationV2 =
|
|||||||
| TPostgresCredentialsRotation
|
| TPostgresCredentialsRotation
|
||||||
| TMsSqlCredentialsRotation
|
| TMsSqlCredentialsRotation
|
||||||
| TAuth0ClientSecretRotation
|
| TAuth0ClientSecretRotation
|
||||||
|
| TAzureClientSecretRotation
|
||||||
| TLdapPasswordRotation
|
| TLdapPasswordRotation
|
||||||
| TAwsIamUserSecretRotation;
|
| TAwsIamUserSecretRotation;
|
||||||
|
|
||||||
@@ -52,12 +60,14 @@ export type TSecretRotationV2WithConnection =
|
|||||||
| TPostgresCredentialsRotationWithConnection
|
| TPostgresCredentialsRotationWithConnection
|
||||||
| TMsSqlCredentialsRotationWithConnection
|
| TMsSqlCredentialsRotationWithConnection
|
||||||
| TAuth0ClientSecretRotationWithConnection
|
| TAuth0ClientSecretRotationWithConnection
|
||||||
|
| TAzureClientSecretRotationWithConnection
|
||||||
| TLdapPasswordRotationWithConnection
|
| TLdapPasswordRotationWithConnection
|
||||||
| TAwsIamUserSecretRotationWithConnection;
|
| TAwsIamUserSecretRotationWithConnection;
|
||||||
|
|
||||||
export type TSecretRotationV2GeneratedCredentials =
|
export type TSecretRotationV2GeneratedCredentials =
|
||||||
| TSqlCredentialsRotationGeneratedCredentials
|
| TSqlCredentialsRotationGeneratedCredentials
|
||||||
| TAuth0ClientSecretRotationGeneratedCredentials
|
| TAuth0ClientSecretRotationGeneratedCredentials
|
||||||
|
| TAzureClientSecretRotationGeneratedCredentials
|
||||||
| TLdapPasswordRotationGeneratedCredentials
|
| TLdapPasswordRotationGeneratedCredentials
|
||||||
| TAwsIamUserSecretRotationGeneratedCredentials;
|
| TAwsIamUserSecretRotationGeneratedCredentials;
|
||||||
|
|
||||||
@@ -65,6 +75,7 @@ export type TSecretRotationV2Input =
|
|||||||
| TPostgresCredentialsRotationInput
|
| TPostgresCredentialsRotationInput
|
||||||
| TMsSqlCredentialsRotationInput
|
| TMsSqlCredentialsRotationInput
|
||||||
| TAuth0ClientSecretRotationInput
|
| TAuth0ClientSecretRotationInput
|
||||||
|
| TAzureClientSecretRotationInput
|
||||||
| TLdapPasswordRotationInput
|
| TLdapPasswordRotationInput
|
||||||
| TAwsIamUserSecretRotationInput;
|
| TAwsIamUserSecretRotationInput;
|
||||||
|
|
||||||
@@ -72,6 +83,7 @@ export type TSecretRotationV2ListItem =
|
|||||||
| TPostgresCredentialsRotationListItem
|
| TPostgresCredentialsRotationListItem
|
||||||
| TMsSqlCredentialsRotationListItem
|
| TMsSqlCredentialsRotationListItem
|
||||||
| TAuth0ClientSecretRotationListItem
|
| TAuth0ClientSecretRotationListItem
|
||||||
|
| TAzureClientSecretRotationListItem
|
||||||
| TLdapPasswordRotationListItem
|
| TLdapPasswordRotationListItem
|
||||||
| TAwsIamUserSecretRotationListItem;
|
| TAwsIamUserSecretRotationListItem;
|
||||||
|
|
||||||
@@ -197,7 +209,7 @@ export type TRotationFactory<
|
|||||||
C extends TSecretRotationV2GeneratedCredentials
|
C extends TSecretRotationV2GeneratedCredentials
|
||||||
> = (
|
> = (
|
||||||
secretRotation: T,
|
secretRotation: T,
|
||||||
appConnectionDAL: Pick<TAppConnectionDALFactory, "updateById">,
|
appConnectionDAL: Pick<TAppConnectionDALFactory, "findById" | "update" | "updateById">,
|
||||||
kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey">
|
kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey">
|
||||||
) => {
|
) => {
|
||||||
issueCredentials: TRotationFactoryIssueCredentials<C>;
|
issueCredentials: TRotationFactoryIssueCredentials<C>;
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import { Auth0ClientSecretRotationSchema } from "@app/ee/services/secret-rotation-v2/auth0-client-secret";
|
import { Auth0ClientSecretRotationSchema } from "@app/ee/services/secret-rotation-v2/auth0-client-secret";
|
||||||
|
import { AzureClientSecretRotationSchema } from "@app/ee/services/secret-rotation-v2/azure-client-secret";
|
||||||
import { LdapPasswordRotationSchema } from "@app/ee/services/secret-rotation-v2/ldap-password";
|
import { LdapPasswordRotationSchema } from "@app/ee/services/secret-rotation-v2/ldap-password";
|
||||||
import { MsSqlCredentialsRotationSchema } from "@app/ee/services/secret-rotation-v2/mssql-credentials";
|
import { MsSqlCredentialsRotationSchema } from "@app/ee/services/secret-rotation-v2/mssql-credentials";
|
||||||
import { PostgresCredentialsRotationSchema } from "@app/ee/services/secret-rotation-v2/postgres-credentials";
|
import { PostgresCredentialsRotationSchema } from "@app/ee/services/secret-rotation-v2/postgres-credentials";
|
||||||
@@ -11,6 +12,7 @@ export const SecretRotationV2Schema = z.discriminatedUnion("type", [
|
|||||||
PostgresCredentialsRotationSchema,
|
PostgresCredentialsRotationSchema,
|
||||||
MsSqlCredentialsRotationSchema,
|
MsSqlCredentialsRotationSchema,
|
||||||
Auth0ClientSecretRotationSchema,
|
Auth0ClientSecretRotationSchema,
|
||||||
|
AzureClientSecretRotationSchema,
|
||||||
LdapPasswordRotationSchema,
|
LdapPasswordRotationSchema,
|
||||||
AwsIamUserSecretRotationSchema
|
AwsIamUserSecretRotationSchema
|
||||||
]);
|
]);
|
||||||
|
@@ -0,0 +1,11 @@
|
|||||||
|
import { getConfig } from "@app/lib/config/env";
|
||||||
|
|
||||||
|
export const canUseSecretScanning = (orgId: string) => {
|
||||||
|
const appCfg = getConfig();
|
||||||
|
|
||||||
|
if (!appCfg.isCloud) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return appCfg.SECRET_SCANNING_ORG_WHITELIST?.includes(orgId);
|
||||||
|
};
|
@@ -12,6 +12,7 @@ import { NotFoundError } from "@app/lib/errors";
|
|||||||
import { TGitAppDALFactory } from "./git-app-dal";
|
import { TGitAppDALFactory } from "./git-app-dal";
|
||||||
import { TGitAppInstallSessionDALFactory } from "./git-app-install-session-dal";
|
import { TGitAppInstallSessionDALFactory } from "./git-app-install-session-dal";
|
||||||
import { TSecretScanningDALFactory } from "./secret-scanning-dal";
|
import { TSecretScanningDALFactory } from "./secret-scanning-dal";
|
||||||
|
import { canUseSecretScanning } from "./secret-scanning-fns";
|
||||||
import { TSecretScanningQueueFactory } from "./secret-scanning-queue";
|
import { TSecretScanningQueueFactory } from "./secret-scanning-queue";
|
||||||
import {
|
import {
|
||||||
SecretScanningRiskStatus,
|
SecretScanningRiskStatus,
|
||||||
@@ -47,12 +48,14 @@ export const secretScanningServiceFactory = ({
|
|||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId
|
||||||
}: TInstallAppSessionDTO) => {
|
}: TInstallAppSessionDTO) => {
|
||||||
|
const appCfg = getConfig();
|
||||||
|
|
||||||
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
|
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
|
||||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.SecretScanning);
|
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.SecretScanning);
|
||||||
|
|
||||||
const sessionId = crypto.randomBytes(16).toString("hex");
|
const sessionId = crypto.randomBytes(16).toString("hex");
|
||||||
await gitAppInstallSessionDAL.upsert({ orgId, sessionId, userId: actorId });
|
await gitAppInstallSessionDAL.upsert({ orgId, sessionId, userId: actorId });
|
||||||
return { sessionId };
|
return { sessionId, gitAppSlug: appCfg.SECRET_SCANNING_GIT_APP_SLUG };
|
||||||
};
|
};
|
||||||
|
|
||||||
const linkInstallationToOrg = async ({
|
const linkInstallationToOrg = async ({
|
||||||
@@ -91,7 +94,8 @@ export const secretScanningServiceFactory = ({
|
|||||||
const {
|
const {
|
||||||
data: { repositories }
|
data: { repositories }
|
||||||
} = await octokit.apps.listReposAccessibleToInstallation();
|
} = await octokit.apps.listReposAccessibleToInstallation();
|
||||||
if (appCfg.SECRET_SCANNING_ORG_WHITELIST?.includes(actorOrgId)) {
|
|
||||||
|
if (canUseSecretScanning(actorOrgId)) {
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
repositories.map(({ id, full_name }) =>
|
repositories.map(({ id, full_name }) =>
|
||||||
secretScanningQueue.startFullRepoScan({
|
secretScanningQueue.startFullRepoScan({
|
||||||
@@ -102,6 +106,7 @@ export const secretScanningServiceFactory = ({
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return { installatedApp };
|
return { installatedApp };
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -164,7 +169,6 @@ export const secretScanningServiceFactory = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleRepoPushEvent = async (payload: WebhookEventMap["push"]) => {
|
const handleRepoPushEvent = async (payload: WebhookEventMap["push"]) => {
|
||||||
const appCfg = getConfig();
|
|
||||||
const { commits, repository, installation, pusher } = payload;
|
const { commits, repository, installation, pusher } = payload;
|
||||||
if (!commits || !repository || !installation || !pusher) {
|
if (!commits || !repository || !installation || !pusher) {
|
||||||
return;
|
return;
|
||||||
@@ -175,7 +179,7 @@ export const secretScanningServiceFactory = ({
|
|||||||
});
|
});
|
||||||
if (!installationLink) return;
|
if (!installationLink) return;
|
||||||
|
|
||||||
if (appCfg.SECRET_SCANNING_ORG_WHITELIST?.includes(installationLink.orgId)) {
|
if (canUseSecretScanning(installationLink.orgId)) {
|
||||||
await secretScanningQueue.startPushEventScan({
|
await secretScanningQueue.startPushEventScan({
|
||||||
commits,
|
commits,
|
||||||
pusher: { name: pusher.name, email: pusher.email },
|
pusher: { name: pusher.name, email: pusher.email },
|
||||||
|
231
backend/src/ee/services/ssh-host-group/ssh-host-group-dal.ts
Normal file
231
backend/src/ee/services/ssh-host-group/ssh-host-group-dal.ts
Normal file
@@ -0,0 +1,231 @@
|
|||||||
|
import { Knex } from "knex";
|
||||||
|
|
||||||
|
import { TDbClient } from "@app/db";
|
||||||
|
import { TableName } from "@app/db/schemas";
|
||||||
|
import { BadRequestError, DatabaseError } from "@app/lib/errors";
|
||||||
|
import { groupBy, unique } from "@app/lib/fn";
|
||||||
|
import { ormify } from "@app/lib/knex";
|
||||||
|
|
||||||
|
import { EHostGroupMembershipFilter } from "./ssh-host-group-types";
|
||||||
|
|
||||||
|
export type TSshHostGroupDALFactory = ReturnType<typeof sshHostGroupDALFactory>;
|
||||||
|
|
||||||
|
export const sshHostGroupDALFactory = (db: TDbClient) => {
|
||||||
|
const sshHostGroupOrm = ormify(db, TableName.SshHostGroup);
|
||||||
|
|
||||||
|
const findSshHostGroupsWithLoginMappings = async (projectId: string, tx?: Knex) => {
|
||||||
|
try {
|
||||||
|
// First, get all the SSH host groups with their login mappings
|
||||||
|
const rows = await (tx || db.replicaNode())(TableName.SshHostGroup)
|
||||||
|
.leftJoin(
|
||||||
|
TableName.SshHostLoginUser,
|
||||||
|
`${TableName.SshHostGroup}.id`,
|
||||||
|
`${TableName.SshHostLoginUser}.sshHostGroupId`
|
||||||
|
)
|
||||||
|
.leftJoin(
|
||||||
|
TableName.SshHostLoginUserMapping,
|
||||||
|
`${TableName.SshHostLoginUser}.id`,
|
||||||
|
`${TableName.SshHostLoginUserMapping}.sshHostLoginUserId`
|
||||||
|
)
|
||||||
|
.leftJoin(TableName.Users, `${TableName.SshHostLoginUserMapping}.userId`, `${TableName.Users}.id`)
|
||||||
|
.leftJoin(TableName.Groups, `${TableName.SshHostLoginUserMapping}.groupId`, `${TableName.Groups}.id`)
|
||||||
|
.where(`${TableName.SshHostGroup}.projectId`, projectId)
|
||||||
|
.select(
|
||||||
|
db.ref("id").withSchema(TableName.SshHostGroup).as("sshHostGroupId"),
|
||||||
|
db.ref("projectId").withSchema(TableName.SshHostGroup),
|
||||||
|
db.ref("name").withSchema(TableName.SshHostGroup),
|
||||||
|
db.ref("loginUser").withSchema(TableName.SshHostLoginUser),
|
||||||
|
db.ref("username").withSchema(TableName.Users),
|
||||||
|
db.ref("userId").withSchema(TableName.SshHostLoginUserMapping),
|
||||||
|
db.ref("slug").withSchema(TableName.Groups).as("groupSlug")
|
||||||
|
)
|
||||||
|
.orderBy(`${TableName.SshHostGroup}.updatedAt`, "desc");
|
||||||
|
|
||||||
|
const hostsGrouped = groupBy(rows, (r) => r.sshHostGroupId);
|
||||||
|
|
||||||
|
const hostGroupIds = Object.keys(hostsGrouped);
|
||||||
|
|
||||||
|
type HostCountRow = {
|
||||||
|
sshHostGroupId: string;
|
||||||
|
host_count: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
const hostCountsQuery = (await (tx ||
|
||||||
|
db
|
||||||
|
.replicaNode()(TableName.SshHostGroupMembership)
|
||||||
|
.select(`${TableName.SshHostGroupMembership}.sshHostGroupId`, db.raw(`count(*) as host_count`))
|
||||||
|
.whereIn(`${TableName.SshHostGroupMembership}.sshHostGroupId`, hostGroupIds)
|
||||||
|
.groupBy(`${TableName.SshHostGroupMembership}.sshHostGroupId`))) as HostCountRow[];
|
||||||
|
|
||||||
|
const hostCountsMap = hostCountsQuery.reduce<Record<string, number>>((acc, { sshHostGroupId, host_count }) => {
|
||||||
|
acc[sshHostGroupId] = Number(host_count);
|
||||||
|
return acc;
|
||||||
|
}, {});
|
||||||
|
|
||||||
|
return Object.values(hostsGrouped).map((hostRows) => {
|
||||||
|
const { sshHostGroupId, name } = hostRows[0];
|
||||||
|
const loginMappingGrouped = groupBy(
|
||||||
|
hostRows.filter((r) => r.loginUser),
|
||||||
|
(r) => r.loginUser
|
||||||
|
);
|
||||||
|
const loginMappings = Object.entries(loginMappingGrouped).map(([loginUser, entries]) => ({
|
||||||
|
loginUser,
|
||||||
|
allowedPrincipals: {
|
||||||
|
usernames: unique(entries.map((e) => e.username)).filter(Boolean),
|
||||||
|
groups: unique(entries.map((e) => e.groupSlug)).filter(Boolean)
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
return {
|
||||||
|
id: sshHostGroupId,
|
||||||
|
projectId,
|
||||||
|
name,
|
||||||
|
loginMappings,
|
||||||
|
hostCount: hostCountsMap[sshHostGroupId] ?? 0
|
||||||
|
};
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
throw new DatabaseError({ error, name: `${TableName.SshHostGroup}: FindSshHostGroupsWithLoginMappings` });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const findSshHostGroupByIdWithLoginMappings = async (sshHostGroupId: string, tx?: Knex) => {
|
||||||
|
try {
|
||||||
|
const rows = await (tx || db.replicaNode())(TableName.SshHostGroup)
|
||||||
|
.leftJoin(
|
||||||
|
TableName.SshHostLoginUser,
|
||||||
|
`${TableName.SshHostGroup}.id`,
|
||||||
|
`${TableName.SshHostLoginUser}.sshHostGroupId`
|
||||||
|
)
|
||||||
|
.leftJoin(
|
||||||
|
TableName.SshHostLoginUserMapping,
|
||||||
|
`${TableName.SshHostLoginUser}.id`,
|
||||||
|
`${TableName.SshHostLoginUserMapping}.sshHostLoginUserId`
|
||||||
|
)
|
||||||
|
.leftJoin(TableName.Users, `${TableName.SshHostLoginUserMapping}.userId`, `${TableName.Users}.id`)
|
||||||
|
.leftJoin(TableName.Groups, `${TableName.SshHostLoginUserMapping}.groupId`, `${TableName.Groups}.id`)
|
||||||
|
.where(`${TableName.SshHostGroup}.id`, sshHostGroupId)
|
||||||
|
.select(
|
||||||
|
db.ref("id").withSchema(TableName.SshHostGroup).as("sshHostGroupId"),
|
||||||
|
db.ref("projectId").withSchema(TableName.SshHostGroup),
|
||||||
|
db.ref("name").withSchema(TableName.SshHostGroup),
|
||||||
|
db.ref("loginUser").withSchema(TableName.SshHostLoginUser),
|
||||||
|
db.ref("username").withSchema(TableName.Users),
|
||||||
|
db.ref("userId").withSchema(TableName.SshHostLoginUserMapping),
|
||||||
|
db.ref("slug").withSchema(TableName.Groups).as("groupSlug")
|
||||||
|
);
|
||||||
|
|
||||||
|
if (rows.length === 0) return null;
|
||||||
|
|
||||||
|
const { sshHostGroupId: id, projectId, name } = rows[0];
|
||||||
|
|
||||||
|
const loginMappingGrouped = groupBy(
|
||||||
|
rows.filter((r) => r.loginUser),
|
||||||
|
(r) => r.loginUser
|
||||||
|
);
|
||||||
|
|
||||||
|
const loginMappings = Object.entries(loginMappingGrouped).map(([loginUser, entries]) => ({
|
||||||
|
loginUser,
|
||||||
|
allowedPrincipals: {
|
||||||
|
usernames: unique(entries.map((e) => e.username)).filter(Boolean),
|
||||||
|
groups: unique(entries.map((e) => e.groupSlug)).filter(Boolean)
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
return {
|
||||||
|
id,
|
||||||
|
projectId,
|
||||||
|
name,
|
||||||
|
loginMappings
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
throw new DatabaseError({ error, name: `${TableName.SshHostGroup}: FindSshHostGroupByIdWithLoginMappings` });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const findAllSshHostsInGroup = async ({
|
||||||
|
sshHostGroupId,
|
||||||
|
offset = 0,
|
||||||
|
limit,
|
||||||
|
filter
|
||||||
|
}: {
|
||||||
|
sshHostGroupId: string;
|
||||||
|
offset?: number;
|
||||||
|
limit?: number;
|
||||||
|
filter?: EHostGroupMembershipFilter;
|
||||||
|
}) => {
|
||||||
|
try {
|
||||||
|
const sshHostGroup = await db
|
||||||
|
.replicaNode()(TableName.SshHostGroup)
|
||||||
|
.where(`${TableName.SshHostGroup}.id`, sshHostGroupId)
|
||||||
|
.select("projectId")
|
||||||
|
.first();
|
||||||
|
|
||||||
|
if (!sshHostGroup) {
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: `SSH host group with ID ${sshHostGroupId} not found`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const query = db
|
||||||
|
.replicaNode()(TableName.SshHost)
|
||||||
|
.where(`${TableName.SshHost}.projectId`, sshHostGroup.projectId)
|
||||||
|
.leftJoin(TableName.SshHostGroupMembership, (bd) => {
|
||||||
|
bd.on(`${TableName.SshHostGroupMembership}.sshHostId`, "=", `${TableName.SshHost}.id`).andOn(
|
||||||
|
`${TableName.SshHostGroupMembership}.sshHostGroupId`,
|
||||||
|
"=",
|
||||||
|
db.raw("?", [sshHostGroupId])
|
||||||
|
);
|
||||||
|
})
|
||||||
|
.select(
|
||||||
|
db.ref("id").withSchema(TableName.SshHost),
|
||||||
|
db.ref("hostname").withSchema(TableName.SshHost),
|
||||||
|
db.ref("alias").withSchema(TableName.SshHost),
|
||||||
|
db.ref("sshHostGroupId").withSchema(TableName.SshHostGroupMembership),
|
||||||
|
db.ref("createdAt").withSchema(TableName.SshHostGroupMembership).as("joinedGroupAt"),
|
||||||
|
db.raw(`count(*) OVER() as total_count`)
|
||||||
|
)
|
||||||
|
.offset(offset)
|
||||||
|
.orderBy(`${TableName.SshHost}.hostname`, "asc");
|
||||||
|
|
||||||
|
if (limit) {
|
||||||
|
void query.limit(limit);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (filter) {
|
||||||
|
switch (filter) {
|
||||||
|
case EHostGroupMembershipFilter.GROUP_MEMBERS:
|
||||||
|
void query.andWhere(`${TableName.SshHostGroupMembership}.createdAt`, "is not", null);
|
||||||
|
break;
|
||||||
|
case EHostGroupMembershipFilter.NON_GROUP_MEMBERS:
|
||||||
|
void query.andWhere(`${TableName.SshHostGroupMembership}.createdAt`, "is", null);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const hosts = await query;
|
||||||
|
|
||||||
|
return {
|
||||||
|
hosts: hosts.map(({ id, hostname, alias, sshHostGroupId: memberGroupId, joinedGroupAt }) => ({
|
||||||
|
id,
|
||||||
|
hostname,
|
||||||
|
alias,
|
||||||
|
isPartOfGroup: !!memberGroupId,
|
||||||
|
joinedGroupAt
|
||||||
|
})),
|
||||||
|
// @ts-expect-error col select is raw and not strongly typed
|
||||||
|
totalCount: Number(hosts?.[0]?.total_count ?? 0)
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
throw new DatabaseError({ error, name: `${TableName.SshHostGroupMembership}: FindAllSshHostsInGroup` });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
findSshHostGroupsWithLoginMappings,
|
||||||
|
findSshHostGroupByIdWithLoginMappings,
|
||||||
|
findAllSshHostsInGroup,
|
||||||
|
...sshHostGroupOrm
|
||||||
|
};
|
||||||
|
};
|
@@ -0,0 +1,13 @@
|
|||||||
|
import { TDbClient } from "@app/db";
|
||||||
|
import { TableName } from "@app/db/schemas";
|
||||||
|
import { ormify } from "@app/lib/knex";
|
||||||
|
|
||||||
|
export type TSshHostGroupMembershipDALFactory = ReturnType<typeof sshHostGroupMembershipDALFactory>;
|
||||||
|
|
||||||
|
export const sshHostGroupMembershipDALFactory = (db: TDbClient) => {
|
||||||
|
const sshHostGroupMembershipOrm = ormify(db, TableName.SshHostGroupMembership);
|
||||||
|
|
||||||
|
return {
|
||||||
|
...sshHostGroupMembershipOrm
|
||||||
|
};
|
||||||
|
};
|
@@ -0,0 +1,7 @@
|
|||||||
|
import { SshHostGroupsSchema } from "@app/db/schemas";
|
||||||
|
|
||||||
|
export const sanitizedSshHostGroup = SshHostGroupsSchema.pick({
|
||||||
|
id: true,
|
||||||
|
projectId: true,
|
||||||
|
name: true
|
||||||
|
});
|
405
backend/src/ee/services/ssh-host-group/ssh-host-group-service.ts
Normal file
405
backend/src/ee/services/ssh-host-group/ssh-host-group-service.ts
Normal file
@@ -0,0 +1,405 @@
|
|||||||
|
import { ForbiddenError } from "@casl/ability";
|
||||||
|
|
||||||
|
import { ActionProjectType } from "@app/db/schemas";
|
||||||
|
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
||||||
|
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
|
||||||
|
import { TSshHostDALFactory } from "@app/ee/services/ssh-host/ssh-host-dal";
|
||||||
|
import { TSshHostLoginUserMappingDALFactory } from "@app/ee/services/ssh-host/ssh-host-login-user-mapping-dal";
|
||||||
|
import { TSshHostLoginUserDALFactory } from "@app/ee/services/ssh-host/ssh-login-user-dal";
|
||||||
|
import { TSshHostGroupDALFactory } from "@app/ee/services/ssh-host-group/ssh-host-group-dal";
|
||||||
|
import { TSshHostGroupMembershipDALFactory } from "@app/ee/services/ssh-host-group/ssh-host-group-membership-dal";
|
||||||
|
import { BadRequestError, NotFoundError } from "@app/lib/errors";
|
||||||
|
import { TProjectDALFactory } from "@app/services/project/project-dal";
|
||||||
|
import { TUserDALFactory } from "@app/services/user/user-dal";
|
||||||
|
|
||||||
|
import { TGroupDALFactory } from "../group/group-dal";
|
||||||
|
import { TLicenseServiceFactory } from "../license/license-service";
|
||||||
|
import { createSshLoginMappings } from "../ssh-host/ssh-host-fns";
|
||||||
|
import {
|
||||||
|
TAddHostToSshHostGroupDTO,
|
||||||
|
TCreateSshHostGroupDTO,
|
||||||
|
TDeleteSshHostGroupDTO,
|
||||||
|
TGetSshHostGroupDTO,
|
||||||
|
TListSshHostGroupHostsDTO,
|
||||||
|
TRemoveHostFromSshHostGroupDTO,
|
||||||
|
TUpdateSshHostGroupDTO
|
||||||
|
} from "./ssh-host-group-types";
|
||||||
|
|
||||||
|
type TSshHostGroupServiceFactoryDep = {
|
||||||
|
projectDAL: Pick<TProjectDALFactory, "findById" | "find">;
|
||||||
|
sshHostDAL: Pick<TSshHostDALFactory, "findSshHostByIdWithLoginMappings">;
|
||||||
|
sshHostGroupDAL: Pick<
|
||||||
|
TSshHostGroupDALFactory,
|
||||||
|
| "create"
|
||||||
|
| "updateById"
|
||||||
|
| "findById"
|
||||||
|
| "deleteById"
|
||||||
|
| "transaction"
|
||||||
|
| "findSshHostGroupByIdWithLoginMappings"
|
||||||
|
| "findAllSshHostsInGroup"
|
||||||
|
| "findOne"
|
||||||
|
| "find"
|
||||||
|
>;
|
||||||
|
sshHostGroupMembershipDAL: Pick<TSshHostGroupMembershipDALFactory, "create" | "deleteById" | "findOne">;
|
||||||
|
sshHostLoginUserDAL: Pick<TSshHostLoginUserDALFactory, "create" | "transaction" | "delete">;
|
||||||
|
sshHostLoginUserMappingDAL: Pick<TSshHostLoginUserMappingDALFactory, "insertMany">;
|
||||||
|
userDAL: Pick<TUserDALFactory, "find">;
|
||||||
|
permissionService: Pick<
|
||||||
|
TPermissionServiceFactory,
|
||||||
|
"getProjectPermission" | "getUserProjectPermission" | "checkGroupProjectPermission"
|
||||||
|
>;
|
||||||
|
licenseService: Pick<TLicenseServiceFactory, "getPlan">;
|
||||||
|
groupDAL: Pick<TGroupDALFactory, "findGroupsByProjectId">;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TSshHostGroupServiceFactory = ReturnType<typeof sshHostGroupServiceFactory>;
|
||||||
|
|
||||||
|
export const sshHostGroupServiceFactory = ({
|
||||||
|
projectDAL,
|
||||||
|
sshHostDAL,
|
||||||
|
sshHostGroupDAL,
|
||||||
|
sshHostGroupMembershipDAL,
|
||||||
|
sshHostLoginUserDAL,
|
||||||
|
sshHostLoginUserMappingDAL,
|
||||||
|
userDAL,
|
||||||
|
permissionService,
|
||||||
|
licenseService,
|
||||||
|
groupDAL
|
||||||
|
}: TSshHostGroupServiceFactoryDep) => {
|
||||||
|
const createSshHostGroup = async ({
|
||||||
|
projectId,
|
||||||
|
name,
|
||||||
|
loginMappings,
|
||||||
|
actorId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actor,
|
||||||
|
actorOrgId
|
||||||
|
}: TCreateSshHostGroupDTO) => {
|
||||||
|
const { permission } = await permissionService.getProjectPermission({
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
projectId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId,
|
||||||
|
actionProjectType: ActionProjectType.SSH
|
||||||
|
});
|
||||||
|
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Create, ProjectPermissionSub.SshHostGroups);
|
||||||
|
|
||||||
|
const plan = await licenseService.getPlan(actorOrgId);
|
||||||
|
if (!plan.sshHostGroups)
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: "Failed to create SSH host group due to plan restriction. Upgrade plan to create group."
|
||||||
|
});
|
||||||
|
|
||||||
|
const newSshHostGroup = await sshHostGroupDAL.transaction(async (tx) => {
|
||||||
|
// (dangtony98): room to optimize check to ensure that
|
||||||
|
// the SSH host group name is unique across the whole org
|
||||||
|
const project = await projectDAL.findById(projectId, tx);
|
||||||
|
if (!project) throw new NotFoundError({ message: `Project with ID '${projectId}' not found` });
|
||||||
|
const projects = await projectDAL.find(
|
||||||
|
{
|
||||||
|
orgId: project.orgId
|
||||||
|
},
|
||||||
|
{ tx }
|
||||||
|
);
|
||||||
|
|
||||||
|
const existingSshHostGroup = await sshHostGroupDAL.find(
|
||||||
|
{
|
||||||
|
name,
|
||||||
|
$in: {
|
||||||
|
projectId: projects.map((p) => p.id)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ tx }
|
||||||
|
);
|
||||||
|
|
||||||
|
if (existingSshHostGroup.length) {
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: `SSH host group with name '${name}' already exists in the organization`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const sshHostGroup = await sshHostGroupDAL.create(
|
||||||
|
{
|
||||||
|
projectId,
|
||||||
|
name
|
||||||
|
},
|
||||||
|
tx
|
||||||
|
);
|
||||||
|
|
||||||
|
await createSshLoginMappings({
|
||||||
|
sshHostGroupId: sshHostGroup.id,
|
||||||
|
loginMappings,
|
||||||
|
sshHostLoginUserDAL,
|
||||||
|
sshHostLoginUserMappingDAL,
|
||||||
|
groupDAL,
|
||||||
|
userDAL,
|
||||||
|
permissionService,
|
||||||
|
projectId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId,
|
||||||
|
tx
|
||||||
|
});
|
||||||
|
|
||||||
|
const newSshHostGroupWithLoginMappings = await sshHostGroupDAL.findSshHostGroupByIdWithLoginMappings(
|
||||||
|
sshHostGroup.id,
|
||||||
|
tx
|
||||||
|
);
|
||||||
|
if (!newSshHostGroupWithLoginMappings) {
|
||||||
|
throw new NotFoundError({ message: `SSH host group with ID '${sshHostGroup.id}' not found` });
|
||||||
|
}
|
||||||
|
|
||||||
|
return newSshHostGroupWithLoginMappings;
|
||||||
|
});
|
||||||
|
|
||||||
|
return newSshHostGroup;
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateSshHostGroup = async ({
|
||||||
|
sshHostGroupId,
|
||||||
|
name,
|
||||||
|
loginMappings,
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId
|
||||||
|
}: TUpdateSshHostGroupDTO) => {
|
||||||
|
const sshHostGroup = await sshHostGroupDAL.findById(sshHostGroupId);
|
||||||
|
if (!sshHostGroup) throw new NotFoundError({ message: `SSH host group with ID '${sshHostGroupId}' not found` });
|
||||||
|
|
||||||
|
const { permission } = await permissionService.getProjectPermission({
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
projectId: sshHostGroup.projectId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId,
|
||||||
|
actionProjectType: ActionProjectType.SSH
|
||||||
|
});
|
||||||
|
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.SshHostGroups);
|
||||||
|
|
||||||
|
const plan = await licenseService.getPlan(actorOrgId);
|
||||||
|
if (!plan.sshHostGroups)
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: "Failed to update SSH host group due to plan restriction. Upgrade plan to update group."
|
||||||
|
});
|
||||||
|
|
||||||
|
const updatedSshHostGroup = await sshHostGroupDAL.transaction(async (tx) => {
|
||||||
|
await sshHostGroupDAL.updateById(
|
||||||
|
sshHostGroupId,
|
||||||
|
{
|
||||||
|
name
|
||||||
|
},
|
||||||
|
tx
|
||||||
|
);
|
||||||
|
if (loginMappings) {
|
||||||
|
await sshHostLoginUserDAL.delete({ sshHostGroupId: sshHostGroup.id }, tx);
|
||||||
|
if (loginMappings.length) {
|
||||||
|
await createSshLoginMappings({
|
||||||
|
sshHostGroupId: sshHostGroup.id,
|
||||||
|
loginMappings,
|
||||||
|
sshHostLoginUserDAL,
|
||||||
|
sshHostLoginUserMappingDAL,
|
||||||
|
groupDAL,
|
||||||
|
userDAL,
|
||||||
|
permissionService,
|
||||||
|
projectId: sshHostGroup.projectId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId,
|
||||||
|
tx
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const updatedSshHostGroupWithLoginMappings = await sshHostGroupDAL.findSshHostGroupByIdWithLoginMappings(
|
||||||
|
sshHostGroup.id,
|
||||||
|
tx
|
||||||
|
);
|
||||||
|
if (!updatedSshHostGroupWithLoginMappings) {
|
||||||
|
throw new NotFoundError({ message: `SSH host group with ID '${sshHostGroup.id}' not found` });
|
||||||
|
}
|
||||||
|
|
||||||
|
return updatedSshHostGroupWithLoginMappings;
|
||||||
|
});
|
||||||
|
|
||||||
|
return updatedSshHostGroup;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getSshHostGroup = async ({
|
||||||
|
sshHostGroupId,
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId
|
||||||
|
}: TGetSshHostGroupDTO) => {
|
||||||
|
const sshHostGroup = await sshHostGroupDAL.findSshHostGroupByIdWithLoginMappings(sshHostGroupId);
|
||||||
|
if (!sshHostGroup) throw new NotFoundError({ message: `SSH host group with ID '${sshHostGroupId}' not found` });
|
||||||
|
|
||||||
|
const { permission } = await permissionService.getProjectPermission({
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
projectId: sshHostGroup.projectId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId,
|
||||||
|
actionProjectType: ActionProjectType.SSH
|
||||||
|
});
|
||||||
|
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.SshHostGroups);
|
||||||
|
|
||||||
|
return sshHostGroup;
|
||||||
|
};
|
||||||
|
|
||||||
|
const deleteSshHostGroup = async ({
|
||||||
|
sshHostGroupId,
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId
|
||||||
|
}: TDeleteSshHostGroupDTO) => {
|
||||||
|
const sshHostGroup = await sshHostGroupDAL.findSshHostGroupByIdWithLoginMappings(sshHostGroupId);
|
||||||
|
if (!sshHostGroup) throw new NotFoundError({ message: `SSH host group with ID '${sshHostGroupId}' not found` });
|
||||||
|
|
||||||
|
const { permission } = await permissionService.getProjectPermission({
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
projectId: sshHostGroup.projectId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId,
|
||||||
|
actionProjectType: ActionProjectType.SSH
|
||||||
|
});
|
||||||
|
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Delete, ProjectPermissionSub.SshHostGroups);
|
||||||
|
|
||||||
|
await sshHostGroupDAL.deleteById(sshHostGroupId);
|
||||||
|
|
||||||
|
return sshHostGroup;
|
||||||
|
};
|
||||||
|
|
||||||
|
const listSshHostGroupHosts = async ({
|
||||||
|
sshHostGroupId,
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId,
|
||||||
|
filter
|
||||||
|
}: TListSshHostGroupHostsDTO) => {
|
||||||
|
const sshHostGroup = await sshHostGroupDAL.findSshHostGroupByIdWithLoginMappings(sshHostGroupId);
|
||||||
|
if (!sshHostGroup) throw new NotFoundError({ message: `SSH host group with ID '${sshHostGroupId}' not found` });
|
||||||
|
|
||||||
|
const { permission } = await permissionService.getProjectPermission({
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
projectId: sshHostGroup.projectId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId,
|
||||||
|
actionProjectType: ActionProjectType.SSH
|
||||||
|
});
|
||||||
|
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.SshHostGroups);
|
||||||
|
|
||||||
|
const { hosts, totalCount } = await sshHostGroupDAL.findAllSshHostsInGroup({ sshHostGroupId, filter });
|
||||||
|
return { sshHostGroup, hosts, totalCount };
|
||||||
|
};
|
||||||
|
|
||||||
|
const addHostToSshHostGroup = async ({
|
||||||
|
sshHostGroupId,
|
||||||
|
hostId,
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId
|
||||||
|
}: TAddHostToSshHostGroupDTO) => {
|
||||||
|
const sshHostGroup = await sshHostGroupDAL.findSshHostGroupByIdWithLoginMappings(sshHostGroupId);
|
||||||
|
if (!sshHostGroup) throw new NotFoundError({ message: `SSH host group with ID '${sshHostGroupId}' not found` });
|
||||||
|
|
||||||
|
const sshHost = await sshHostDAL.findSshHostByIdWithLoginMappings(hostId);
|
||||||
|
if (!sshHost) {
|
||||||
|
throw new NotFoundError({
|
||||||
|
message: `SSH host with ID ${hostId} not found`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sshHostGroup.projectId !== sshHost.projectId) {
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: `SSH host with ID ${hostId} not found in project ${sshHostGroup.projectId}`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const { permission } = await permissionService.getProjectPermission({
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
projectId: sshHostGroup.projectId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId,
|
||||||
|
actionProjectType: ActionProjectType.SSH
|
||||||
|
});
|
||||||
|
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.SshHostGroups);
|
||||||
|
|
||||||
|
await sshHostGroupMembershipDAL.create({ sshHostGroupId, sshHostId: hostId });
|
||||||
|
|
||||||
|
return { sshHostGroup, sshHost };
|
||||||
|
};
|
||||||
|
|
||||||
|
const removeHostFromSshHostGroup = async ({
|
||||||
|
sshHostGroupId,
|
||||||
|
hostId,
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId
|
||||||
|
}: TRemoveHostFromSshHostGroupDTO) => {
|
||||||
|
const sshHostGroup = await sshHostGroupDAL.findSshHostGroupByIdWithLoginMappings(sshHostGroupId);
|
||||||
|
if (!sshHostGroup) throw new NotFoundError({ message: `SSH host group with ID '${sshHostGroupId}' not found` });
|
||||||
|
|
||||||
|
const sshHost = await sshHostDAL.findSshHostByIdWithLoginMappings(hostId);
|
||||||
|
if (!sshHost) {
|
||||||
|
throw new NotFoundError({
|
||||||
|
message: `SSH host with ID ${hostId} not found`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sshHostGroup.projectId !== sshHost.projectId) {
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: `SSH host with ID ${hostId} not found in project ${sshHostGroup.projectId}`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const { permission } = await permissionService.getProjectPermission({
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
projectId: sshHostGroup.projectId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId,
|
||||||
|
actionProjectType: ActionProjectType.SSH
|
||||||
|
});
|
||||||
|
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.SshHostGroups);
|
||||||
|
|
||||||
|
const sshHostGroupMembership = await sshHostGroupMembershipDAL.findOne({
|
||||||
|
sshHostGroupId,
|
||||||
|
sshHostId: hostId
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!sshHostGroupMembership) {
|
||||||
|
throw new NotFoundError({
|
||||||
|
message: `SSH host with ID ${hostId} not found in SSH host group with ID ${sshHostGroupId}`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
await sshHostGroupMembershipDAL.deleteById(sshHostGroupMembership.id);
|
||||||
|
|
||||||
|
return { sshHostGroup, sshHost };
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
createSshHostGroup,
|
||||||
|
getSshHostGroup,
|
||||||
|
deleteSshHostGroup,
|
||||||
|
updateSshHostGroup,
|
||||||
|
listSshHostGroupHosts,
|
||||||
|
addHostToSshHostGroup,
|
||||||
|
removeHostFromSshHostGroup
|
||||||
|
};
|
||||||
|
};
|
@@ -0,0 +1,41 @@
|
|||||||
|
import { TLoginMapping } from "@app/ee/services/ssh-host/ssh-host-types";
|
||||||
|
import { TProjectPermission } from "@app/lib/types";
|
||||||
|
|
||||||
|
export type TCreateSshHostGroupDTO = {
|
||||||
|
name: string;
|
||||||
|
loginMappings: TLoginMapping[];
|
||||||
|
} & TProjectPermission;
|
||||||
|
|
||||||
|
export type TUpdateSshHostGroupDTO = {
|
||||||
|
sshHostGroupId: string;
|
||||||
|
name?: string;
|
||||||
|
loginMappings?: TLoginMapping[];
|
||||||
|
} & Omit<TProjectPermission, "projectId">;
|
||||||
|
|
||||||
|
export type TGetSshHostGroupDTO = {
|
||||||
|
sshHostGroupId: string;
|
||||||
|
} & Omit<TProjectPermission, "projectId">;
|
||||||
|
|
||||||
|
export type TDeleteSshHostGroupDTO = {
|
||||||
|
sshHostGroupId: string;
|
||||||
|
} & Omit<TProjectPermission, "projectId">;
|
||||||
|
|
||||||
|
export type TListSshHostGroupHostsDTO = {
|
||||||
|
sshHostGroupId: string;
|
||||||
|
filter?: EHostGroupMembershipFilter;
|
||||||
|
} & Omit<TProjectPermission, "projectId">;
|
||||||
|
|
||||||
|
export type TAddHostToSshHostGroupDTO = {
|
||||||
|
sshHostGroupId: string;
|
||||||
|
hostId: string;
|
||||||
|
} & Omit<TProjectPermission, "projectId">;
|
||||||
|
|
||||||
|
export type TRemoveHostFromSshHostGroupDTO = {
|
||||||
|
sshHostGroupId: string;
|
||||||
|
hostId: string;
|
||||||
|
} & Omit<TProjectPermission, "projectId">;
|
||||||
|
|
||||||
|
export enum EHostGroupMembershipFilter {
|
||||||
|
GROUP_MEMBERS = "group-members",
|
||||||
|
NON_GROUP_MEMBERS = "non-group-members"
|
||||||
|
}
|
@@ -6,6 +6,8 @@ import { DatabaseError } from "@app/lib/errors";
|
|||||||
import { groupBy, unique } from "@app/lib/fn";
|
import { groupBy, unique } from "@app/lib/fn";
|
||||||
import { ormify } from "@app/lib/knex";
|
import { ormify } from "@app/lib/knex";
|
||||||
|
|
||||||
|
import { LoginMappingSource } from "./ssh-host-types";
|
||||||
|
|
||||||
export type TSshHostDALFactory = ReturnType<typeof sshHostDALFactory>;
|
export type TSshHostDALFactory = ReturnType<typeof sshHostDALFactory>;
|
||||||
|
|
||||||
export const sshHostDALFactory = (db: TDbClient) => {
|
export const sshHostDALFactory = (db: TDbClient) => {
|
||||||
@@ -13,13 +15,16 @@ export const sshHostDALFactory = (db: TDbClient) => {
|
|||||||
|
|
||||||
const findUserAccessibleSshHosts = async (projectIds: string[], userId: string, tx?: Knex) => {
|
const findUserAccessibleSshHosts = async (projectIds: string[], userId: string, tx?: Knex) => {
|
||||||
try {
|
try {
|
||||||
const user = await (tx || db.replicaNode())(TableName.Users).where({ id: userId }).select("username").first();
|
const knex = tx || db.replicaNode();
|
||||||
|
|
||||||
|
const user = await knex(TableName.Users).where({ id: userId }).select("username").first();
|
||||||
|
|
||||||
if (!user) {
|
if (!user) {
|
||||||
throw new DatabaseError({ name: `${TableName.Users}: UserNotFound`, error: new Error("User not found") });
|
throw new DatabaseError({ name: `${TableName.Users}: UserNotFound`, error: new Error("User not found") });
|
||||||
}
|
}
|
||||||
|
|
||||||
const rows = await (tx || db.replicaNode())(TableName.SshHost)
|
// get hosts where user has direct login mappings
|
||||||
|
const directHostRows = await knex(TableName.SshHost)
|
||||||
.leftJoin(TableName.SshHostLoginUser, `${TableName.SshHost}.id`, `${TableName.SshHostLoginUser}.sshHostId`)
|
.leftJoin(TableName.SshHostLoginUser, `${TableName.SshHost}.id`, `${TableName.SshHostLoginUser}.sshHostId`)
|
||||||
.leftJoin(
|
.leftJoin(
|
||||||
TableName.SshHostLoginUserMapping,
|
TableName.SshHostLoginUserMapping,
|
||||||
@@ -27,8 +32,17 @@ export const sshHostDALFactory = (db: TDbClient) => {
|
|||||||
`${TableName.SshHostLoginUserMapping}.sshHostLoginUserId`
|
`${TableName.SshHostLoginUserMapping}.sshHostLoginUserId`
|
||||||
)
|
)
|
||||||
.leftJoin(TableName.Users, `${TableName.Users}.id`, `${TableName.SshHostLoginUserMapping}.userId`)
|
.leftJoin(TableName.Users, `${TableName.Users}.id`, `${TableName.SshHostLoginUserMapping}.userId`)
|
||||||
|
.leftJoin(
|
||||||
|
TableName.UserGroupMembership,
|
||||||
|
`${TableName.UserGroupMembership}.groupId`,
|
||||||
|
`${TableName.SshHostLoginUserMapping}.groupId`
|
||||||
|
)
|
||||||
.whereIn(`${TableName.SshHost}.projectId`, projectIds)
|
.whereIn(`${TableName.SshHost}.projectId`, projectIds)
|
||||||
.andWhere(`${TableName.SshHostLoginUserMapping}.userId`, userId)
|
.andWhere((bd) => {
|
||||||
|
void bd
|
||||||
|
.where(`${TableName.SshHostLoginUserMapping}.userId`, userId)
|
||||||
|
.orWhere(`${TableName.UserGroupMembership}.userId`, userId);
|
||||||
|
})
|
||||||
.select(
|
.select(
|
||||||
db.ref("id").withSchema(TableName.SshHost).as("sshHostId"),
|
db.ref("id").withSchema(TableName.SshHost).as("sshHostId"),
|
||||||
db.ref("projectId").withSchema(TableName.SshHost),
|
db.ref("projectId").withSchema(TableName.SshHost),
|
||||||
@@ -37,26 +51,79 @@ export const sshHostDALFactory = (db: TDbClient) => {
|
|||||||
db.ref("userCertTtl").withSchema(TableName.SshHost),
|
db.ref("userCertTtl").withSchema(TableName.SshHost),
|
||||||
db.ref("hostCertTtl").withSchema(TableName.SshHost),
|
db.ref("hostCertTtl").withSchema(TableName.SshHost),
|
||||||
db.ref("loginUser").withSchema(TableName.SshHostLoginUser),
|
db.ref("loginUser").withSchema(TableName.SshHostLoginUser),
|
||||||
db.ref("username").withSchema(TableName.Users),
|
|
||||||
db.ref("userId").withSchema(TableName.SshHostLoginUserMapping),
|
|
||||||
db.ref("userSshCaId").withSchema(TableName.SshHost),
|
db.ref("userSshCaId").withSchema(TableName.SshHost),
|
||||||
db.ref("hostSshCaId").withSchema(TableName.SshHost)
|
db.ref("hostSshCaId").withSchema(TableName.SshHost)
|
||||||
)
|
);
|
||||||
.orderBy(`${TableName.SshHost}.updatedAt`, "desc");
|
|
||||||
|
|
||||||
const grouped = groupBy(rows, (r) => r.sshHostId);
|
// get hosts where user has login mappings via host groups
|
||||||
return Object.values(grouped).map((hostRows) => {
|
const groupHostRows = await knex(TableName.SshHostGroupMembership)
|
||||||
|
.join(
|
||||||
|
TableName.SshHostLoginUser,
|
||||||
|
`${TableName.SshHostGroupMembership}.sshHostGroupId`,
|
||||||
|
`${TableName.SshHostLoginUser}.sshHostGroupId`
|
||||||
|
)
|
||||||
|
.leftJoin(
|
||||||
|
TableName.SshHostLoginUserMapping,
|
||||||
|
`${TableName.SshHostLoginUser}.id`,
|
||||||
|
`${TableName.SshHostLoginUserMapping}.sshHostLoginUserId`
|
||||||
|
)
|
||||||
|
.join(TableName.SshHost, `${TableName.SshHostGroupMembership}.sshHostId`, `${TableName.SshHost}.id`)
|
||||||
|
.leftJoin(
|
||||||
|
TableName.UserGroupMembership,
|
||||||
|
`${TableName.UserGroupMembership}.groupId`,
|
||||||
|
`${TableName.SshHostLoginUserMapping}.groupId`
|
||||||
|
)
|
||||||
|
.whereIn(`${TableName.SshHost}.projectId`, projectIds)
|
||||||
|
.andWhere((bd) => {
|
||||||
|
void bd
|
||||||
|
.where(`${TableName.SshHostLoginUserMapping}.userId`, userId)
|
||||||
|
.orWhere(`${TableName.UserGroupMembership}.userId`, userId);
|
||||||
|
})
|
||||||
|
.select(
|
||||||
|
db.ref("id").withSchema(TableName.SshHost).as("sshHostId"),
|
||||||
|
db.ref("projectId").withSchema(TableName.SshHost),
|
||||||
|
db.ref("hostname").withSchema(TableName.SshHost),
|
||||||
|
db.ref("alias").withSchema(TableName.SshHost),
|
||||||
|
db.ref("userCertTtl").withSchema(TableName.SshHost),
|
||||||
|
db.ref("hostCertTtl").withSchema(TableName.SshHost),
|
||||||
|
db.ref("loginUser").withSchema(TableName.SshHostLoginUser),
|
||||||
|
db.ref("userSshCaId").withSchema(TableName.SshHost),
|
||||||
|
db.ref("hostSshCaId").withSchema(TableName.SshHost)
|
||||||
|
);
|
||||||
|
|
||||||
|
const directHostRowsWithSource = directHostRows.map((row) => ({
|
||||||
|
...row,
|
||||||
|
source: LoginMappingSource.HOST
|
||||||
|
}));
|
||||||
|
|
||||||
|
const groupHostRowsWithSource = groupHostRows.map((row) => ({
|
||||||
|
...row,
|
||||||
|
source: LoginMappingSource.HOST_GROUP
|
||||||
|
}));
|
||||||
|
|
||||||
|
const mergedRows = [...directHostRowsWithSource, ...groupHostRowsWithSource];
|
||||||
|
|
||||||
|
const hostsGrouped = groupBy(mergedRows, (r) => r.sshHostId);
|
||||||
|
|
||||||
|
return Object.values(hostsGrouped).map((hostRows) => {
|
||||||
const { sshHostId, hostname, alias, userCertTtl, hostCertTtl, userSshCaId, hostSshCaId, projectId } =
|
const { sshHostId, hostname, alias, userCertTtl, hostCertTtl, userSshCaId, hostSshCaId, projectId } =
|
||||||
hostRows[0];
|
hostRows[0];
|
||||||
|
|
||||||
const loginMappingGrouped = groupBy(hostRows, (r) => r.loginUser);
|
const loginMappingGrouped = groupBy(hostRows, (r) => r.loginUser);
|
||||||
|
const loginMappings = Object.entries(loginMappingGrouped).map(([loginUser, mappings]) => {
|
||||||
|
// Prefer HOST source over HOST_GROUP
|
||||||
|
const preferredMapping =
|
||||||
|
mappings.find((m) => m.source === LoginMappingSource.HOST) ||
|
||||||
|
mappings.find((m) => m.source === LoginMappingSource.HOST_GROUP);
|
||||||
|
|
||||||
const loginMappings = Object.entries(loginMappingGrouped).map(([loginUser]) => ({
|
return {
|
||||||
loginUser,
|
loginUser,
|
||||||
allowedPrincipals: {
|
allowedPrincipals: {
|
||||||
usernames: [user.username]
|
usernames: [user.username]
|
||||||
}
|
},
|
||||||
}));
|
source: preferredMapping!.source
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id: sshHostId,
|
id: sshHostId,
|
||||||
@@ -85,6 +152,7 @@ export const sshHostDALFactory = (db: TDbClient) => {
|
|||||||
`${TableName.SshHostLoginUserMapping}.sshHostLoginUserId`
|
`${TableName.SshHostLoginUserMapping}.sshHostLoginUserId`
|
||||||
)
|
)
|
||||||
.leftJoin(TableName.Users, `${TableName.SshHostLoginUserMapping}.userId`, `${TableName.Users}.id`)
|
.leftJoin(TableName.Users, `${TableName.SshHostLoginUserMapping}.userId`, `${TableName.Users}.id`)
|
||||||
|
.leftJoin(TableName.Groups, `${TableName.SshHostLoginUserMapping}.groupId`, `${TableName.Groups}.id`)
|
||||||
.where(`${TableName.SshHost}.projectId`, projectId)
|
.where(`${TableName.SshHost}.projectId`, projectId)
|
||||||
.select(
|
.select(
|
||||||
db.ref("id").withSchema(TableName.SshHost).as("sshHostId"),
|
db.ref("id").withSchema(TableName.SshHost).as("sshHostId"),
|
||||||
@@ -96,25 +164,67 @@ export const sshHostDALFactory = (db: TDbClient) => {
|
|||||||
db.ref("loginUser").withSchema(TableName.SshHostLoginUser),
|
db.ref("loginUser").withSchema(TableName.SshHostLoginUser),
|
||||||
db.ref("username").withSchema(TableName.Users),
|
db.ref("username").withSchema(TableName.Users),
|
||||||
db.ref("userId").withSchema(TableName.SshHostLoginUserMapping),
|
db.ref("userId").withSchema(TableName.SshHostLoginUserMapping),
|
||||||
|
db.ref("slug").withSchema(TableName.Groups).as("groupSlug"),
|
||||||
db.ref("userSshCaId").withSchema(TableName.SshHost),
|
db.ref("userSshCaId").withSchema(TableName.SshHost),
|
||||||
db.ref("hostSshCaId").withSchema(TableName.SshHost)
|
db.ref("hostSshCaId").withSchema(TableName.SshHost)
|
||||||
)
|
)
|
||||||
.orderBy(`${TableName.SshHost}.updatedAt`, "desc");
|
.orderBy(`${TableName.SshHost}.updatedAt`, "desc");
|
||||||
|
|
||||||
|
// process login mappings inherited from groups that hosts are part of
|
||||||
|
const hostIds = unique(rows.map((r) => r.sshHostId)).filter(Boolean);
|
||||||
|
const groupRows = await (tx || db.replicaNode())(TableName.SshHostGroupMembership)
|
||||||
|
.join(
|
||||||
|
TableName.SshHostLoginUser,
|
||||||
|
`${TableName.SshHostGroupMembership}.sshHostGroupId`,
|
||||||
|
`${TableName.SshHostLoginUser}.sshHostGroupId`
|
||||||
|
)
|
||||||
|
.leftJoin(
|
||||||
|
TableName.SshHostLoginUserMapping,
|
||||||
|
`${TableName.SshHostLoginUser}.id`,
|
||||||
|
`${TableName.SshHostLoginUserMapping}.sshHostLoginUserId`
|
||||||
|
)
|
||||||
|
.leftJoin(TableName.Users, `${TableName.SshHostLoginUserMapping}.userId`, `${TableName.Users}.id`)
|
||||||
|
.leftJoin(TableName.Groups, `${TableName.SshHostLoginUserMapping}.groupId`, `${TableName.Groups}.id`)
|
||||||
|
.select(
|
||||||
|
db.ref("sshHostId").withSchema(TableName.SshHostGroupMembership),
|
||||||
|
db.ref("loginUser").withSchema(TableName.SshHostLoginUser),
|
||||||
|
db.ref("username").withSchema(TableName.Users),
|
||||||
|
db.ref("slug").withSchema(TableName.Groups).as("groupSlug")
|
||||||
|
)
|
||||||
|
.whereIn(`${TableName.SshHostGroupMembership}.sshHostId`, hostIds);
|
||||||
|
|
||||||
|
const groupedGroupMappings = groupBy(groupRows, (r) => r.sshHostId);
|
||||||
|
|
||||||
const hostsGrouped = groupBy(rows, (r) => r.sshHostId);
|
const hostsGrouped = groupBy(rows, (r) => r.sshHostId);
|
||||||
return Object.values(hostsGrouped).map((hostRows) => {
|
return Object.values(hostsGrouped).map((hostRows) => {
|
||||||
const { sshHostId, hostname, alias, userCertTtl, hostCertTtl, userSshCaId, hostSshCaId } = hostRows[0];
|
const { sshHostId, hostname, alias, userCertTtl, hostCertTtl, userSshCaId, hostSshCaId } = hostRows[0];
|
||||||
|
|
||||||
|
// direct login mappings
|
||||||
const loginMappingGrouped = groupBy(
|
const loginMappingGrouped = groupBy(
|
||||||
hostRows.filter((r) => r.loginUser),
|
hostRows.filter((r) => r.loginUser),
|
||||||
(r) => r.loginUser
|
(r) => r.loginUser
|
||||||
);
|
);
|
||||||
|
|
||||||
const loginMappings = Object.entries(loginMappingGrouped).map(([loginUser, entries]) => ({
|
const directMappings = Object.entries(loginMappingGrouped).map(([loginUser, entries]) => ({
|
||||||
loginUser,
|
loginUser,
|
||||||
allowedPrincipals: {
|
allowedPrincipals: {
|
||||||
usernames: unique(entries.map((e) => e.username)).filter(Boolean)
|
usernames: unique(entries.map((e) => e.username)).filter(Boolean),
|
||||||
}
|
groups: unique(entries.map((e) => e.groupSlug)).filter(Boolean)
|
||||||
|
},
|
||||||
|
source: LoginMappingSource.HOST
|
||||||
|
}));
|
||||||
|
|
||||||
|
// group-inherited login mappings
|
||||||
|
const inheritedGroupRows = groupedGroupMappings[sshHostId] || [];
|
||||||
|
const inheritedGrouped = groupBy(inheritedGroupRows, (r) => r.loginUser);
|
||||||
|
|
||||||
|
const groupMappings = Object.entries(inheritedGrouped).map(([loginUser, entries]) => ({
|
||||||
|
loginUser,
|
||||||
|
allowedPrincipals: {
|
||||||
|
usernames: unique(entries.map((e) => e.username)).filter(Boolean),
|
||||||
|
groups: unique(entries.map((e) => e.groupSlug)).filter(Boolean)
|
||||||
|
},
|
||||||
|
source: LoginMappingSource.HOST_GROUP
|
||||||
}));
|
}));
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -124,7 +234,7 @@ export const sshHostDALFactory = (db: TDbClient) => {
|
|||||||
projectId,
|
projectId,
|
||||||
userCertTtl,
|
userCertTtl,
|
||||||
hostCertTtl,
|
hostCertTtl,
|
||||||
loginMappings,
|
loginMappings: [...directMappings, ...groupMappings],
|
||||||
userSshCaId,
|
userSshCaId,
|
||||||
hostSshCaId
|
hostSshCaId
|
||||||
};
|
};
|
||||||
@@ -144,6 +254,7 @@ export const sshHostDALFactory = (db: TDbClient) => {
|
|||||||
`${TableName.SshHostLoginUserMapping}.sshHostLoginUserId`
|
`${TableName.SshHostLoginUserMapping}.sshHostLoginUserId`
|
||||||
)
|
)
|
||||||
.leftJoin(TableName.Users, `${TableName.SshHostLoginUserMapping}.userId`, `${TableName.Users}.id`)
|
.leftJoin(TableName.Users, `${TableName.SshHostLoginUserMapping}.userId`, `${TableName.Users}.id`)
|
||||||
|
.leftJoin(TableName.Groups, `${TableName.SshHostLoginUserMapping}.groupId`, `${TableName.Groups}.id`)
|
||||||
.where(`${TableName.SshHost}.id`, sshHostId)
|
.where(`${TableName.SshHost}.id`, sshHostId)
|
||||||
.select(
|
.select(
|
||||||
db.ref("id").withSchema(TableName.SshHost).as("sshHostId"),
|
db.ref("id").withSchema(TableName.SshHost).as("sshHostId"),
|
||||||
@@ -156,23 +267,62 @@ export const sshHostDALFactory = (db: TDbClient) => {
|
|||||||
db.ref("username").withSchema(TableName.Users),
|
db.ref("username").withSchema(TableName.Users),
|
||||||
db.ref("userId").withSchema(TableName.SshHostLoginUserMapping),
|
db.ref("userId").withSchema(TableName.SshHostLoginUserMapping),
|
||||||
db.ref("userSshCaId").withSchema(TableName.SshHost),
|
db.ref("userSshCaId").withSchema(TableName.SshHost),
|
||||||
db.ref("hostSshCaId").withSchema(TableName.SshHost)
|
db.ref("hostSshCaId").withSchema(TableName.SshHost),
|
||||||
|
db.ref("slug").withSchema(TableName.Groups).as("groupSlug")
|
||||||
);
|
);
|
||||||
|
|
||||||
if (rows.length === 0) return null;
|
if (rows.length === 0) return null;
|
||||||
|
|
||||||
const { sshHostId: id, projectId, hostname, alias, userCertTtl, hostCertTtl, userSshCaId, hostSshCaId } = rows[0];
|
const { sshHostId: id, projectId, hostname, alias, userCertTtl, hostCertTtl, userSshCaId, hostSshCaId } = rows[0];
|
||||||
|
|
||||||
const loginMappingGrouped = groupBy(
|
// direct login mappings
|
||||||
|
const directGrouped = groupBy(
|
||||||
rows.filter((r) => r.loginUser),
|
rows.filter((r) => r.loginUser),
|
||||||
(r) => r.loginUser
|
(r) => r.loginUser
|
||||||
);
|
);
|
||||||
|
|
||||||
const loginMappings = Object.entries(loginMappingGrouped).map(([loginUser, entries]) => ({
|
const directMappings = Object.entries(directGrouped).map(([loginUser, entries]) => ({
|
||||||
loginUser,
|
loginUser,
|
||||||
allowedPrincipals: {
|
allowedPrincipals: {
|
||||||
usernames: unique(entries.map((e) => e.username)).filter(Boolean)
|
usernames: unique(entries.map((e) => e.username)).filter(Boolean),
|
||||||
}
|
groups: unique(entries.map((e) => e.groupSlug)).filter(Boolean)
|
||||||
|
},
|
||||||
|
source: LoginMappingSource.HOST
|
||||||
|
}));
|
||||||
|
|
||||||
|
// group login mappings
|
||||||
|
const groupRows = await (tx || db.replicaNode())(TableName.SshHostGroupMembership)
|
||||||
|
.join(
|
||||||
|
TableName.SshHostLoginUser,
|
||||||
|
`${TableName.SshHostGroupMembership}.sshHostGroupId`,
|
||||||
|
`${TableName.SshHostLoginUser}.sshHostGroupId`
|
||||||
|
)
|
||||||
|
.leftJoin(
|
||||||
|
TableName.SshHostLoginUserMapping,
|
||||||
|
`${TableName.SshHostLoginUser}.id`,
|
||||||
|
`${TableName.SshHostLoginUserMapping}.sshHostLoginUserId`
|
||||||
|
)
|
||||||
|
.leftJoin(TableName.Users, `${TableName.SshHostLoginUserMapping}.userId`, `${TableName.Users}.id`)
|
||||||
|
.leftJoin(TableName.Groups, `${TableName.SshHostLoginUserMapping}.groupId`, `${TableName.Groups}.id`)
|
||||||
|
.where(`${TableName.SshHostGroupMembership}.sshHostId`, sshHostId)
|
||||||
|
.select(
|
||||||
|
db.ref("loginUser").withSchema(TableName.SshHostLoginUser),
|
||||||
|
db.ref("username").withSchema(TableName.Users),
|
||||||
|
db.ref("slug").withSchema(TableName.Groups).as("groupSlug")
|
||||||
|
);
|
||||||
|
|
||||||
|
const groupGrouped = groupBy(
|
||||||
|
groupRows.filter((r) => r.loginUser),
|
||||||
|
(r) => r.loginUser
|
||||||
|
);
|
||||||
|
|
||||||
|
const groupMappings = Object.entries(groupGrouped).map(([loginUser, entries]) => ({
|
||||||
|
loginUser,
|
||||||
|
allowedPrincipals: {
|
||||||
|
usernames: unique(entries.map((e) => e.username)).filter(Boolean),
|
||||||
|
groups: unique(entries.map((e) => e.groupSlug)).filter(Boolean)
|
||||||
|
},
|
||||||
|
source: LoginMappingSource.HOST_GROUP
|
||||||
}));
|
}));
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -182,7 +332,7 @@ export const sshHostDALFactory = (db: TDbClient) => {
|
|||||||
alias,
|
alias,
|
||||||
userCertTtl,
|
userCertTtl,
|
||||||
hostCertTtl,
|
hostCertTtl,
|
||||||
loginMappings,
|
loginMappings: [...directMappings, ...groupMappings],
|
||||||
userSshCaId,
|
userSshCaId,
|
||||||
hostSshCaId
|
hostSshCaId
|
||||||
};
|
};
|
||||||
|
122
backend/src/ee/services/ssh-host/ssh-host-fns.ts
Normal file
122
backend/src/ee/services/ssh-host/ssh-host-fns.ts
Normal file
@@ -0,0 +1,122 @@
|
|||||||
|
import { Knex } from "knex";
|
||||||
|
|
||||||
|
import { ActionProjectType } from "@app/db/schemas";
|
||||||
|
import { BadRequestError } from "@app/lib/errors";
|
||||||
|
|
||||||
|
import { ProjectPermissionSshHostActions, ProjectPermissionSub } from "../permission/project-permission";
|
||||||
|
import { TCreateSshLoginMappingsDTO } from "./ssh-host-types";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create SSH login mappings for a given SSH host
|
||||||
|
* or SSH host group.
|
||||||
|
*/
|
||||||
|
export const createSshLoginMappings = async ({
|
||||||
|
sshHostId,
|
||||||
|
sshHostGroupId,
|
||||||
|
loginMappings,
|
||||||
|
sshHostLoginUserDAL,
|
||||||
|
sshHostLoginUserMappingDAL,
|
||||||
|
groupDAL,
|
||||||
|
userDAL,
|
||||||
|
permissionService,
|
||||||
|
projectId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId,
|
||||||
|
tx: outerTx
|
||||||
|
}: TCreateSshLoginMappingsDTO) => {
|
||||||
|
const processCreation = async (tx: Knex) => {
|
||||||
|
// (dangtony98): room to optimize
|
||||||
|
for await (const { loginUser, allowedPrincipals } of loginMappings) {
|
||||||
|
const sshHostLoginUser = await sshHostLoginUserDAL.create(
|
||||||
|
// (dangtony98): should either pass in sshHostId or sshHostGroupId but not both
|
||||||
|
{
|
||||||
|
sshHostId,
|
||||||
|
sshHostGroupId,
|
||||||
|
loginUser
|
||||||
|
},
|
||||||
|
tx
|
||||||
|
);
|
||||||
|
|
||||||
|
if (allowedPrincipals.usernames && allowedPrincipals.usernames.length > 0) {
|
||||||
|
const users = await userDAL.find(
|
||||||
|
{
|
||||||
|
$in: {
|
||||||
|
username: allowedPrincipals.usernames
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ tx }
|
||||||
|
);
|
||||||
|
|
||||||
|
const foundUsernames = new Set(users.map((u) => u.username));
|
||||||
|
|
||||||
|
for (const uname of allowedPrincipals.usernames) {
|
||||||
|
if (!foundUsernames.has(uname)) {
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: `Invalid username: ${uname}`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for await (const user of users) {
|
||||||
|
// check that each user has access to the SSH project
|
||||||
|
await permissionService.getUserProjectPermission({
|
||||||
|
userId: user.id,
|
||||||
|
projectId,
|
||||||
|
authMethod: actorAuthMethod,
|
||||||
|
userOrgId: actorOrgId,
|
||||||
|
actionProjectType: ActionProjectType.SSH
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
await sshHostLoginUserMappingDAL.insertMany(
|
||||||
|
users.map((user) => ({
|
||||||
|
sshHostLoginUserId: sshHostLoginUser.id,
|
||||||
|
userId: user.id
|
||||||
|
})),
|
||||||
|
tx
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (allowedPrincipals.groups && allowedPrincipals.groups.length > 0) {
|
||||||
|
const projectGroups = await groupDAL.findGroupsByProjectId(projectId);
|
||||||
|
const groups = projectGroups.filter((g) => allowedPrincipals.groups?.includes(g.slug));
|
||||||
|
|
||||||
|
if (groups.length !== allowedPrincipals.groups?.length) {
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: `Invalid group slugs: ${allowedPrincipals.groups
|
||||||
|
.filter((g) => !projectGroups.some((pg) => pg.slug === g))
|
||||||
|
.join(", ")}`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
for await (const group of groups) {
|
||||||
|
// check that each group has access to the SSH project and have read access to hosts
|
||||||
|
const hasPermission = await permissionService.checkGroupProjectPermission({
|
||||||
|
groupId: group.id,
|
||||||
|
projectId,
|
||||||
|
checkPermissions: [ProjectPermissionSshHostActions.Read, ProjectPermissionSub.SshHosts]
|
||||||
|
});
|
||||||
|
if (!hasPermission) {
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: `Group ${group.slug} does not have access to the SSH project`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await sshHostLoginUserMappingDAL.insertMany(
|
||||||
|
groups.map((group) => ({
|
||||||
|
sshHostLoginUserId: sshHostLoginUser.id,
|
||||||
|
groupId: group.id
|
||||||
|
})),
|
||||||
|
tx
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (outerTx) {
|
||||||
|
return processCreation(outerTx);
|
||||||
|
}
|
||||||
|
|
||||||
|
return sshHostLoginUserDAL.transaction(processCreation);
|
||||||
|
};
|
@@ -15,7 +15,24 @@ export const sanitizedSshHost = SshHostsSchema.pick({
|
|||||||
|
|
||||||
export const loginMappingSchema = z.object({
|
export const loginMappingSchema = z.object({
|
||||||
loginUser: z.string().trim(),
|
loginUser: z.string().trim(),
|
||||||
allowedPrincipals: z.object({
|
allowedPrincipals: z
|
||||||
usernames: z.array(z.string().trim()).transform((usernames) => Array.from(new Set(usernames)))
|
.object({
|
||||||
})
|
usernames: z
|
||||||
|
.array(z.string().trim())
|
||||||
|
.transform((usernames) => Array.from(new Set(usernames)))
|
||||||
|
.optional(),
|
||||||
|
groups: z
|
||||||
|
.array(z.string().trim())
|
||||||
|
.transform((groups) => Array.from(new Set(groups)))
|
||||||
|
.optional()
|
||||||
|
})
|
||||||
|
.refine(
|
||||||
|
(data) => {
|
||||||
|
return (data.usernames && data.usernames.length > 0) || (data.groups && data.groups.length > 0);
|
||||||
|
},
|
||||||
|
{
|
||||||
|
message: "At least one username or group must be provided",
|
||||||
|
path: ["allowedPrincipals"]
|
||||||
|
}
|
||||||
|
)
|
||||||
});
|
});
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
import { ForbiddenError, subject } from "@casl/ability";
|
import { ForbiddenError, subject } from "@casl/ability";
|
||||||
|
|
||||||
import { ActionProjectType, ProjectType } from "@app/db/schemas";
|
import { ActionProjectType, ProjectType } from "@app/db/schemas";
|
||||||
|
import { TGroupDALFactory } from "@app/ee/services/group/group-dal";
|
||||||
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
||||||
import { ProjectPermissionSshHostActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
|
import { ProjectPermissionSshHostActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
|
||||||
import { TSshCertificateAuthorityDALFactory } from "@app/ee/services/ssh/ssh-certificate-authority-dal";
|
import { TSshCertificateAuthorityDALFactory } from "@app/ee/services/ssh/ssh-certificate-authority-dal";
|
||||||
@@ -19,6 +20,7 @@ import { TProjectDALFactory } from "@app/services/project/project-dal";
|
|||||||
import { TProjectSshConfigDALFactory } from "@app/services/project/project-ssh-config-dal";
|
import { TProjectSshConfigDALFactory } from "@app/services/project/project-ssh-config-dal";
|
||||||
import { TUserDALFactory } from "@app/services/user/user-dal";
|
import { TUserDALFactory } from "@app/services/user/user-dal";
|
||||||
|
|
||||||
|
import { TUserGroupMembershipDALFactory } from "../group/user-group-membership-dal";
|
||||||
import {
|
import {
|
||||||
convertActorToPrincipals,
|
convertActorToPrincipals,
|
||||||
createSshCert,
|
createSshCert,
|
||||||
@@ -26,6 +28,7 @@ import {
|
|||||||
getSshPublicKey
|
getSshPublicKey
|
||||||
} from "../ssh/ssh-certificate-authority-fns";
|
} from "../ssh/ssh-certificate-authority-fns";
|
||||||
import { SshCertType } from "../ssh/ssh-certificate-authority-types";
|
import { SshCertType } from "../ssh/ssh-certificate-authority-types";
|
||||||
|
import { createSshLoginMappings } from "./ssh-host-fns";
|
||||||
import {
|
import {
|
||||||
TCreateSshHostDTO,
|
TCreateSshHostDTO,
|
||||||
TDeleteSshHostDTO,
|
TDeleteSshHostDTO,
|
||||||
@@ -38,12 +41,14 @@ import {
|
|||||||
|
|
||||||
type TSshHostServiceFactoryDep = {
|
type TSshHostServiceFactoryDep = {
|
||||||
userDAL: Pick<TUserDALFactory, "findById" | "find">;
|
userDAL: Pick<TUserDALFactory, "findById" | "find">;
|
||||||
|
groupDAL: Pick<TGroupDALFactory, "findGroupsByProjectId">;
|
||||||
projectDAL: Pick<TProjectDALFactory, "find">;
|
projectDAL: Pick<TProjectDALFactory, "find">;
|
||||||
projectSshConfigDAL: Pick<TProjectSshConfigDALFactory, "findOne">;
|
projectSshConfigDAL: Pick<TProjectSshConfigDALFactory, "findOne">;
|
||||||
sshCertificateAuthorityDAL: Pick<TSshCertificateAuthorityDALFactory, "findOne">;
|
sshCertificateAuthorityDAL: Pick<TSshCertificateAuthorityDALFactory, "findOne">;
|
||||||
sshCertificateAuthoritySecretDAL: Pick<TSshCertificateAuthoritySecretDALFactory, "findOne">;
|
sshCertificateAuthoritySecretDAL: Pick<TSshCertificateAuthoritySecretDALFactory, "findOne">;
|
||||||
sshCertificateDAL: Pick<TSshCertificateDALFactory, "create" | "transaction">;
|
sshCertificateDAL: Pick<TSshCertificateDALFactory, "create" | "transaction">;
|
||||||
sshCertificateBodyDAL: Pick<TSshCertificateBodyDALFactory, "create">;
|
sshCertificateBodyDAL: Pick<TSshCertificateBodyDALFactory, "create">;
|
||||||
|
userGroupMembershipDAL: Pick<TUserGroupMembershipDALFactory, "findGroupMembershipsByUserIdInOrg">;
|
||||||
sshHostDAL: Pick<
|
sshHostDAL: Pick<
|
||||||
TSshHostDALFactory,
|
TSshHostDALFactory,
|
||||||
| "transaction"
|
| "transaction"
|
||||||
@@ -57,7 +62,10 @@ type TSshHostServiceFactoryDep = {
|
|||||||
>;
|
>;
|
||||||
sshHostLoginUserDAL: TSshHostLoginUserDALFactory;
|
sshHostLoginUserDAL: TSshHostLoginUserDALFactory;
|
||||||
sshHostLoginUserMappingDAL: TSshHostLoginUserMappingDALFactory;
|
sshHostLoginUserMappingDAL: TSshHostLoginUserMappingDALFactory;
|
||||||
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission" | "getUserProjectPermission">;
|
permissionService: Pick<
|
||||||
|
TPermissionServiceFactory,
|
||||||
|
"getProjectPermission" | "getUserProjectPermission" | "checkGroupProjectPermission"
|
||||||
|
>;
|
||||||
kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey">;
|
kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey">;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -65,6 +73,8 @@ export type TSshHostServiceFactory = ReturnType<typeof sshHostServiceFactory>;
|
|||||||
|
|
||||||
export const sshHostServiceFactory = ({
|
export const sshHostServiceFactory = ({
|
||||||
userDAL,
|
userDAL,
|
||||||
|
userGroupMembershipDAL,
|
||||||
|
groupDAL,
|
||||||
projectDAL,
|
projectDAL,
|
||||||
projectSshConfigDAL,
|
projectSshConfigDAL,
|
||||||
sshCertificateAuthorityDAL,
|
sshCertificateAuthorityDAL,
|
||||||
@@ -202,56 +212,19 @@ export const sshHostServiceFactory = ({
|
|||||||
tx
|
tx
|
||||||
);
|
);
|
||||||
|
|
||||||
// (dangtony98): room to optimize
|
await createSshLoginMappings({
|
||||||
for await (const { loginUser, allowedPrincipals } of loginMappings) {
|
sshHostId: host.id,
|
||||||
const sshHostLoginUser = await sshHostLoginUserDAL.create(
|
loginMappings,
|
||||||
{
|
sshHostLoginUserDAL,
|
||||||
sshHostId: host.id,
|
sshHostLoginUserMappingDAL,
|
||||||
loginUser
|
groupDAL,
|
||||||
},
|
userDAL,
|
||||||
tx
|
permissionService,
|
||||||
);
|
projectId,
|
||||||
|
actorAuthMethod,
|
||||||
if (allowedPrincipals.usernames.length > 0) {
|
actorOrgId,
|
||||||
const users = await userDAL.find(
|
tx
|
||||||
{
|
});
|
||||||
$in: {
|
|
||||||
username: allowedPrincipals.usernames
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{ tx }
|
|
||||||
);
|
|
||||||
|
|
||||||
const foundUsernames = new Set(users.map((u) => u.username));
|
|
||||||
|
|
||||||
for (const uname of allowedPrincipals.usernames) {
|
|
||||||
if (!foundUsernames.has(uname)) {
|
|
||||||
throw new BadRequestError({
|
|
||||||
message: `Invalid username: ${uname}`
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for await (const user of users) {
|
|
||||||
// check that each user has access to the SSH project
|
|
||||||
await permissionService.getUserProjectPermission({
|
|
||||||
userId: user.id,
|
|
||||||
projectId,
|
|
||||||
authMethod: actorAuthMethod,
|
|
||||||
userOrgId: actorOrgId,
|
|
||||||
actionProjectType: ActionProjectType.SSH
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
await sshHostLoginUserMappingDAL.insertMany(
|
|
||||||
users.map((user) => ({
|
|
||||||
sshHostLoginUserId: sshHostLoginUser.id,
|
|
||||||
userId: user.id
|
|
||||||
})),
|
|
||||||
tx
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const newSshHostWithLoginMappings = await sshHostDAL.findSshHostByIdWithLoginMappings(host.id, tx);
|
const newSshHostWithLoginMappings = await sshHostDAL.findSshHostByIdWithLoginMappings(host.id, tx);
|
||||||
if (!newSshHostWithLoginMappings) {
|
if (!newSshHostWithLoginMappings) {
|
||||||
@@ -310,54 +283,19 @@ export const sshHostServiceFactory = ({
|
|||||||
if (loginMappings) {
|
if (loginMappings) {
|
||||||
await sshHostLoginUserDAL.delete({ sshHostId: host.id }, tx);
|
await sshHostLoginUserDAL.delete({ sshHostId: host.id }, tx);
|
||||||
if (loginMappings.length) {
|
if (loginMappings.length) {
|
||||||
for await (const { loginUser, allowedPrincipals } of loginMappings) {
|
await createSshLoginMappings({
|
||||||
const sshHostLoginUser = await sshHostLoginUserDAL.create(
|
sshHostId: host.id,
|
||||||
{
|
loginMappings,
|
||||||
sshHostId: host.id,
|
sshHostLoginUserDAL,
|
||||||
loginUser
|
sshHostLoginUserMappingDAL,
|
||||||
},
|
groupDAL,
|
||||||
tx
|
userDAL,
|
||||||
);
|
permissionService,
|
||||||
|
projectId: host.projectId,
|
||||||
if (allowedPrincipals.usernames.length > 0) {
|
actorAuthMethod,
|
||||||
const users = await userDAL.find(
|
actorOrgId,
|
||||||
{
|
tx
|
||||||
$in: {
|
});
|
||||||
username: allowedPrincipals.usernames
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{ tx }
|
|
||||||
);
|
|
||||||
|
|
||||||
const foundUsernames = new Set(users.map((u) => u.username));
|
|
||||||
|
|
||||||
for (const uname of allowedPrincipals.usernames) {
|
|
||||||
if (!foundUsernames.has(uname)) {
|
|
||||||
throw new BadRequestError({
|
|
||||||
message: `Invalid username: ${uname}`
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for await (const user of users) {
|
|
||||||
await permissionService.getUserProjectPermission({
|
|
||||||
userId: user.id,
|
|
||||||
projectId: host.projectId,
|
|
||||||
authMethod: actorAuthMethod,
|
|
||||||
userOrgId: actorOrgId,
|
|
||||||
actionProjectType: ActionProjectType.SSH
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
await sshHostLoginUserMappingDAL.insertMany(
|
|
||||||
users.map((user) => ({
|
|
||||||
sshHostLoginUserId: sshHostLoginUser.id,
|
|
||||||
userId: user.id
|
|
||||||
})),
|
|
||||||
tx
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -460,10 +398,14 @@ export const sshHostServiceFactory = ({
|
|||||||
userDAL
|
userDAL
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const userGroups = await userGroupMembershipDAL.findGroupMembershipsByUserIdInOrg(actorId, actorOrgId);
|
||||||
|
const userGroupSlugs = userGroups.map((g) => g.groupSlug);
|
||||||
|
|
||||||
const mapping = host.loginMappings.find(
|
const mapping = host.loginMappings.find(
|
||||||
(m) =>
|
(m) =>
|
||||||
m.loginUser === loginUser &&
|
m.loginUser === loginUser &&
|
||||||
m.allowedPrincipals.usernames.some((allowed) => internalPrincipals.includes(allowed))
|
(m.allowedPrincipals.usernames?.some((allowed) => internalPrincipals.includes(allowed)) ||
|
||||||
|
m.allowedPrincipals.groups?.some((allowed) => userGroupSlugs.includes(allowed)))
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!mapping) {
|
if (!mapping) {
|
||||||
|
@@ -1,18 +1,35 @@
|
|||||||
|
import { Knex } from "knex";
|
||||||
|
|
||||||
|
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
||||||
|
import { TSshHostLoginUserMappingDALFactory } from "@app/ee/services/ssh-host/ssh-host-login-user-mapping-dal";
|
||||||
|
import { TSshHostLoginUserDALFactory } from "@app/ee/services/ssh-host/ssh-login-user-dal";
|
||||||
import { TProjectPermission } from "@app/lib/types";
|
import { TProjectPermission } from "@app/lib/types";
|
||||||
|
import { ActorAuthMethod } from "@app/services/auth/auth-type";
|
||||||
|
import { TUserDALFactory } from "@app/services/user/user-dal";
|
||||||
|
|
||||||
|
import { TGroupDALFactory } from "../group/group-dal";
|
||||||
|
|
||||||
export type TListSshHostsDTO = Omit<TProjectPermission, "projectId">;
|
export type TListSshHostsDTO = Omit<TProjectPermission, "projectId">;
|
||||||
|
|
||||||
|
export type TLoginMapping = {
|
||||||
|
loginUser: string;
|
||||||
|
allowedPrincipals: {
|
||||||
|
usernames?: string[];
|
||||||
|
groups?: string[];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export enum LoginMappingSource {
|
||||||
|
HOST = "host",
|
||||||
|
HOST_GROUP = "hostGroup"
|
||||||
|
}
|
||||||
|
|
||||||
export type TCreateSshHostDTO = {
|
export type TCreateSshHostDTO = {
|
||||||
hostname: string;
|
hostname: string;
|
||||||
alias?: string;
|
alias?: string;
|
||||||
userCertTtl: string;
|
userCertTtl: string;
|
||||||
hostCertTtl: string;
|
hostCertTtl: string;
|
||||||
loginMappings: {
|
loginMappings: TLoginMapping[];
|
||||||
loginUser: string;
|
|
||||||
allowedPrincipals: {
|
|
||||||
usernames: string[];
|
|
||||||
};
|
|
||||||
}[];
|
|
||||||
userSshCaId?: string;
|
userSshCaId?: string;
|
||||||
hostSshCaId?: string;
|
hostSshCaId?: string;
|
||||||
} & TProjectPermission;
|
} & TProjectPermission;
|
||||||
@@ -23,12 +40,7 @@ export type TUpdateSshHostDTO = {
|
|||||||
alias?: string;
|
alias?: string;
|
||||||
userCertTtl?: string;
|
userCertTtl?: string;
|
||||||
hostCertTtl?: string;
|
hostCertTtl?: string;
|
||||||
loginMappings?: {
|
loginMappings?: TLoginMapping[];
|
||||||
loginUser: string;
|
|
||||||
allowedPrincipals: {
|
|
||||||
usernames: string[];
|
|
||||||
};
|
|
||||||
}[];
|
|
||||||
} & Omit<TProjectPermission, "projectId">;
|
} & Omit<TProjectPermission, "projectId">;
|
||||||
|
|
||||||
export type TGetSshHostDTO = {
|
export type TGetSshHostDTO = {
|
||||||
@@ -48,3 +60,20 @@ export type TIssueSshHostHostCertDTO = {
|
|||||||
sshHostId: string;
|
sshHostId: string;
|
||||||
publicKey: string;
|
publicKey: string;
|
||||||
} & Omit<TProjectPermission, "projectId">;
|
} & Omit<TProjectPermission, "projectId">;
|
||||||
|
|
||||||
|
type BaseCreateSshLoginMappingsDTO = {
|
||||||
|
loginMappings: TLoginMapping[];
|
||||||
|
sshHostLoginUserDAL: Pick<TSshHostLoginUserDALFactory, "create" | "transaction">;
|
||||||
|
sshHostLoginUserMappingDAL: Pick<TSshHostLoginUserMappingDALFactory, "insertMany">;
|
||||||
|
userDAL: Pick<TUserDALFactory, "find">;
|
||||||
|
permissionService: Pick<TPermissionServiceFactory, "getUserProjectPermission" | "checkGroupProjectPermission">;
|
||||||
|
groupDAL: Pick<TGroupDALFactory, "findGroupsByProjectId">;
|
||||||
|
projectId: string;
|
||||||
|
actorAuthMethod: ActorAuthMethod;
|
||||||
|
actorOrgId: string;
|
||||||
|
tx?: Knex;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TCreateSshLoginMappingsDTO =
|
||||||
|
| (BaseCreateSshLoginMappingsDTO & { sshHostId: string; sshHostGroupId?: undefined })
|
||||||
|
| (BaseCreateSshLoginMappingsDTO & { sshHostGroupId: string; sshHostId?: undefined });
|
||||||
|
@@ -282,7 +282,7 @@ export const sshCertificateAuthorityServiceFactory = ({
|
|||||||
|
|
||||||
// set [keyId] depending on if [allowCustomKeyIds] is true or false
|
// set [keyId] depending on if [allowCustomKeyIds] is true or false
|
||||||
const keyId = sshCertificateTemplate.allowCustomKeyIds
|
const keyId = sshCertificateTemplate.allowCustomKeyIds
|
||||||
? requestedKeyId ?? `${actor}-${actorId}`
|
? (requestedKeyId ?? `${actor}-${actorId}`)
|
||||||
: `${actor}-${actorId}`;
|
: `${actor}-${actorId}`;
|
||||||
|
|
||||||
const sshCaSecret = await sshCertificateAuthoritySecretDAL.findOne({ sshCaId: sshCertificateTemplate.sshCaId });
|
const sshCaSecret = await sshCertificateAuthoritySecretDAL.findOne({ sshCaId: sshCertificateTemplate.sshCaId });
|
||||||
@@ -404,7 +404,7 @@ export const sshCertificateAuthorityServiceFactory = ({
|
|||||||
|
|
||||||
// set [keyId] depending on if [allowCustomKeyIds] is true or false
|
// set [keyId] depending on if [allowCustomKeyIds] is true or false
|
||||||
const keyId = sshCertificateTemplate.allowCustomKeyIds
|
const keyId = sshCertificateTemplate.allowCustomKeyIds
|
||||||
? requestedKeyId ?? `${actor}-${actorId}`
|
? (requestedKeyId ?? `${actor}-${actorId}`)
|
||||||
: `${actor}-${actorId}`;
|
: `${actor}-${actorId}`;
|
||||||
|
|
||||||
const sshCaSecret = await sshCertificateAuthoritySecretDAL.findOne({ sshCaId: sshCertificateTemplate.sshCaId });
|
const sshCaSecret = await sshCertificateAuthoritySecretDAL.findOne({ sshCaId: sshCertificateTemplate.sshCaId });
|
||||||
|
@@ -1,6 +1,8 @@
|
|||||||
import { Redis } from "ioredis";
|
import { Redis } from "ioredis";
|
||||||
|
|
||||||
import { pgAdvisoryLockHashText } from "@app/lib/crypto/hashtext";
|
import { pgAdvisoryLockHashText } from "@app/lib/crypto/hashtext";
|
||||||
|
import { applyJitter } from "@app/lib/dates";
|
||||||
|
import { delay as delayMs } from "@app/lib/delay";
|
||||||
import { Redlock, Settings } from "@app/lib/red-lock";
|
import { Redlock, Settings } from "@app/lib/red-lock";
|
||||||
|
|
||||||
export const PgSqlLock = {
|
export const PgSqlLock = {
|
||||||
@@ -48,6 +50,13 @@ export const KeyStoreTtls = {
|
|||||||
AccessTokenStatusUpdateInSeconds: 120
|
AccessTokenStatusUpdateInSeconds: 120
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type TDeleteItems = {
|
||||||
|
pattern: string;
|
||||||
|
batchSize?: number;
|
||||||
|
delay?: number;
|
||||||
|
jitter?: number;
|
||||||
|
};
|
||||||
|
|
||||||
type TWaitTillReady = {
|
type TWaitTillReady = {
|
||||||
key: string;
|
key: string;
|
||||||
waitingCb?: () => void;
|
waitingCb?: () => void;
|
||||||
@@ -75,6 +84,35 @@ export const keyStoreFactory = (redisUrl: string) => {
|
|||||||
|
|
||||||
const deleteItem = async (key: string) => redis.del(key);
|
const deleteItem = async (key: string) => redis.del(key);
|
||||||
|
|
||||||
|
const deleteItems = async ({ pattern, batchSize = 500, delay = 1500, jitter = 200 }: TDeleteItems) => {
|
||||||
|
let cursor = "0";
|
||||||
|
let totalDeleted = 0;
|
||||||
|
|
||||||
|
do {
|
||||||
|
// Await in loop is needed so that Redis is not overwhelmed
|
||||||
|
// eslint-disable-next-line no-await-in-loop
|
||||||
|
const [nextCursor, keys] = await redis.scan(cursor, "MATCH", pattern, "COUNT", 1000); // Count should be 1000 - 5000 for prod loads
|
||||||
|
cursor = nextCursor;
|
||||||
|
|
||||||
|
for (let i = 0; i < keys.length; i += batchSize) {
|
||||||
|
const batch = keys.slice(i, i + batchSize);
|
||||||
|
const pipeline = redis.pipeline();
|
||||||
|
for (const key of batch) {
|
||||||
|
pipeline.unlink(key);
|
||||||
|
}
|
||||||
|
// eslint-disable-next-line no-await-in-loop
|
||||||
|
await pipeline.exec();
|
||||||
|
totalDeleted += batch.length;
|
||||||
|
console.log("BATCH DONE");
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-await-in-loop
|
||||||
|
await delayMs(Math.max(0, applyJitter(delay, jitter)));
|
||||||
|
}
|
||||||
|
} while (cursor !== "0");
|
||||||
|
|
||||||
|
return totalDeleted;
|
||||||
|
};
|
||||||
|
|
||||||
const incrementBy = async (key: string, value: number) => redis.incrby(key, value);
|
const incrementBy = async (key: string, value: number) => redis.incrby(key, value);
|
||||||
|
|
||||||
const setExpiry = async (key: string, expiryInSeconds: number) => redis.expire(key, expiryInSeconds);
|
const setExpiry = async (key: string, expiryInSeconds: number) => redis.expire(key, expiryInSeconds);
|
||||||
@@ -94,7 +132,7 @@ export const keyStoreFactory = (redisUrl: string) => {
|
|||||||
// eslint-disable-next-line
|
// eslint-disable-next-line
|
||||||
await new Promise((resolve) => {
|
await new Promise((resolve) => {
|
||||||
waitingCb?.();
|
waitingCb?.();
|
||||||
setTimeout(resolve, Math.max(0, delay + Math.floor((Math.random() * 2 - 1) * jitter)));
|
setTimeout(resolve, Math.max(0, applyJitter(delay, jitter)));
|
||||||
});
|
});
|
||||||
attempts += 1;
|
attempts += 1;
|
||||||
// eslint-disable-next-line
|
// eslint-disable-next-line
|
||||||
@@ -108,6 +146,7 @@ export const keyStoreFactory = (redisUrl: string) => {
|
|||||||
setExpiry,
|
setExpiry,
|
||||||
setItemWithExpiry,
|
setItemWithExpiry,
|
||||||
deleteItem,
|
deleteItem,
|
||||||
|
deleteItems,
|
||||||
incrementBy,
|
incrementBy,
|
||||||
acquireLock(resources: string[], duration: number, settings?: Partial<Settings>) {
|
acquireLock(resources: string[], duration: number, settings?: Partial<Settings>) {
|
||||||
return redisLock.acquire(resources, duration, settings);
|
return redisLock.acquire(resources, duration, settings);
|
||||||
|
@@ -1,3 +1,7 @@
|
|||||||
|
import RE2 from "re2";
|
||||||
|
|
||||||
|
import { applyJitter } from "@app/lib/dates";
|
||||||
|
import { delay as delayMs } from "@app/lib/delay";
|
||||||
import { Lock } from "@app/lib/red-lock";
|
import { Lock } from "@app/lib/red-lock";
|
||||||
|
|
||||||
import { TKeyStoreFactory } from "./keystore";
|
import { TKeyStoreFactory } from "./keystore";
|
||||||
@@ -19,6 +23,27 @@ export const inMemoryKeyStore = (): TKeyStoreFactory => {
|
|||||||
delete store[key];
|
delete store[key];
|
||||||
return 1;
|
return 1;
|
||||||
},
|
},
|
||||||
|
deleteItems: async ({ pattern, batchSize = 500, delay = 1500, jitter = 200 }) => {
|
||||||
|
const regex = new RE2(`^${pattern.replace(/[-[\]/{}()+?.\\^$|]/g, "\\$&").replace(/\*/g, ".*")}$`);
|
||||||
|
let totalDeleted = 0;
|
||||||
|
const keys = Object.keys(store);
|
||||||
|
|
||||||
|
for (let i = 0; i < keys.length; i += batchSize) {
|
||||||
|
const batch = keys.slice(i, i + batchSize);
|
||||||
|
|
||||||
|
for (const key of batch) {
|
||||||
|
if (regex.test(key)) {
|
||||||
|
delete store[key];
|
||||||
|
totalDeleted += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-await-in-loop
|
||||||
|
await delayMs(Math.max(0, applyJitter(delay, jitter)));
|
||||||
|
}
|
||||||
|
|
||||||
|
return totalDeleted;
|
||||||
|
},
|
||||||
getItem: async (key) => {
|
getItem: async (key) => {
|
||||||
const value = store[key];
|
const value = store[key];
|
||||||
if (typeof value === "string") {
|
if (typeof value === "string") {
|
||||||
|
@@ -18,6 +18,7 @@ export enum ApiDocsTags {
|
|||||||
KubernetesAuth = "Kubernetes Auth",
|
KubernetesAuth = "Kubernetes Auth",
|
||||||
JwtAuth = "JWT Auth",
|
JwtAuth = "JWT Auth",
|
||||||
OidcAuth = "OIDC Auth",
|
OidcAuth = "OIDC Auth",
|
||||||
|
LdapAuth = "LDAP Auth",
|
||||||
Groups = "Groups",
|
Groups = "Groups",
|
||||||
Organizations = "Organizations",
|
Organizations = "Organizations",
|
||||||
Projects = "Projects",
|
Projects = "Projects",
|
||||||
@@ -48,6 +49,8 @@ export enum ApiDocsTags {
|
|||||||
SshCertificates = "SSH Certificates",
|
SshCertificates = "SSH Certificates",
|
||||||
SshCertificateAuthorities = "SSH Certificate Authorities",
|
SshCertificateAuthorities = "SSH Certificate Authorities",
|
||||||
SshCertificateTemplates = "SSH Certificate Templates",
|
SshCertificateTemplates = "SSH Certificate Templates",
|
||||||
|
SshHosts = "SSH Hosts",
|
||||||
|
SshHostGroups = "SSH Host Groups",
|
||||||
KmsKeys = "KMS Keys",
|
KmsKeys = "KMS Keys",
|
||||||
KmsEncryption = "KMS Encryption",
|
KmsEncryption = "KMS Encryption",
|
||||||
KmsSigning = "KMS Signing"
|
KmsSigning = "KMS Signing"
|
||||||
@@ -182,6 +185,49 @@ export const UNIVERSAL_AUTH = {
|
|||||||
}
|
}
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
|
export const LDAP_AUTH = {
|
||||||
|
LOGIN: {
|
||||||
|
identityId: "The ID of the identity to login.",
|
||||||
|
username: "The username of the LDAP user to login.",
|
||||||
|
password: "The password of the LDAP user to login."
|
||||||
|
},
|
||||||
|
ATTACH: {
|
||||||
|
identityId: "The ID of the identity to attach the configuration onto.",
|
||||||
|
url: "The URL of the LDAP server.",
|
||||||
|
allowedFields:
|
||||||
|
"The comma-separated array of key/value pairs of required fields that the LDAP entry must have in order to authenticate.",
|
||||||
|
searchBase: "The base DN to search for the LDAP user.",
|
||||||
|
searchFilter: "The filter to use to search for the LDAP user.",
|
||||||
|
bindDN: "The DN of the user to bind to the LDAP server.",
|
||||||
|
bindPass: "The password of the user to bind to the LDAP server.",
|
||||||
|
ldapCaCertificate: "The PEM-encoded CA certificate for the LDAP server.",
|
||||||
|
accessTokenTTL: "The lifetime for an access token in seconds.",
|
||||||
|
accessTokenMaxTTL: "The maximum lifetime for an access token in seconds.",
|
||||||
|
accessTokenNumUsesLimit: "The maximum number of times that an access token can be used.",
|
||||||
|
accessTokenTrustedIps: "The IPs or CIDR ranges that access tokens can be used from."
|
||||||
|
},
|
||||||
|
UPDATE: {
|
||||||
|
identityId: "The ID of the identity to update the configuration for.",
|
||||||
|
url: "The new URL of the LDAP server.",
|
||||||
|
allowedFields: "The comma-separated list of allowed fields to return from the LDAP user.",
|
||||||
|
searchBase: "The new base DN to search for the LDAP user.",
|
||||||
|
searchFilter: "The new filter to use to search for the LDAP user.",
|
||||||
|
bindDN: "The new DN of the user to bind to the LDAP server.",
|
||||||
|
bindPass: "The new password of the user to bind to the LDAP server.",
|
||||||
|
ldapCaCertificate: "The new PEM-encoded CA certificate for the LDAP server.",
|
||||||
|
accessTokenTTL: "The new lifetime for an access token in seconds.",
|
||||||
|
accessTokenMaxTTL: "The new maximum lifetime for an access token in seconds.",
|
||||||
|
accessTokenNumUsesLimit: "The new maximum number of times that an access token can be used.",
|
||||||
|
accessTokenTrustedIps: "The new IPs or CIDR ranges that access tokens can be used from."
|
||||||
|
},
|
||||||
|
RETRIEVE: {
|
||||||
|
identityId: "The ID of the identity to retrieve the configuration for."
|
||||||
|
},
|
||||||
|
REVOKE: {
|
||||||
|
identityId: "The ID of the identity to revoke the configuration for."
|
||||||
|
}
|
||||||
|
} as const;
|
||||||
|
|
||||||
export const AWS_AUTH = {
|
export const AWS_AUTH = {
|
||||||
LOGIN: {
|
LOGIN: {
|
||||||
identityId: "The ID of the identity to login.",
|
identityId: "The ID of the identity to login.",
|
||||||
@@ -568,6 +614,9 @@ export const PROJECTS = {
|
|||||||
LIST_SSH_HOSTS: {
|
LIST_SSH_HOSTS: {
|
||||||
projectId: "The ID of the project to list SSH hosts for."
|
projectId: "The ID of the project to list SSH hosts for."
|
||||||
},
|
},
|
||||||
|
LIST_SSH_HOST_GROUPS: {
|
||||||
|
projectId: "The ID of the project to list SSH host groups for."
|
||||||
|
},
|
||||||
LIST_SSH_CERTIFICATES: {
|
LIST_SSH_CERTIFICATES: {
|
||||||
projectId: "The ID of the project to list SSH certificates for.",
|
projectId: "The ID of the project to list SSH certificates for.",
|
||||||
offset: "The offset to start from. If you enter 10, it will start from the 10th SSH certificate.",
|
offset: "The offset to start from. If you enter 10, it will start from the 10th SSH certificate.",
|
||||||
@@ -1382,6 +1431,40 @@ export const SSH_CERTIFICATE_TEMPLATES = {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const SSH_HOST_GROUPS = {
|
||||||
|
GET: {
|
||||||
|
sshHostGroupId: "The ID of the SSH host group to get.",
|
||||||
|
filter: "The filter to apply to the SSH hosts in the SSH host group."
|
||||||
|
},
|
||||||
|
CREATE: {
|
||||||
|
projectId: "The ID of the project to create the SSH host group in.",
|
||||||
|
name: "The name of the SSH host group.",
|
||||||
|
loginMappings:
|
||||||
|
"A list of default login mappings to include on each host in the SSH host group. Each login mapping contains a login user and a list of corresponding allowed principals being usernames of users in the Infisical SSH project."
|
||||||
|
},
|
||||||
|
UPDATE: {
|
||||||
|
sshHostGroupId: "The ID of the SSH host group to update.",
|
||||||
|
name: "The name of the SSH host group to update to.",
|
||||||
|
loginMappings:
|
||||||
|
"A list of default login mappings to include on each host in the SSH host group. Each login mapping contains a login user and a list of corresponding allowed principals being usernames of users in the Infisical SSH project."
|
||||||
|
},
|
||||||
|
DELETE: {
|
||||||
|
sshHostGroupId: "The ID of the SSH host group to delete."
|
||||||
|
},
|
||||||
|
LIST_HOSTS: {
|
||||||
|
offset: "The offset to start from. If you enter 10, it will start from the 10th host",
|
||||||
|
limit: "The number of hosts to return."
|
||||||
|
},
|
||||||
|
ADD_HOST: {
|
||||||
|
sshHostGroupId: "The ID of the SSH host group to add the host to.",
|
||||||
|
hostId: "The ID of the SSH host to add to the SSH host group."
|
||||||
|
},
|
||||||
|
DELETE_HOST: {
|
||||||
|
sshHostGroupId: "The ID of the SSH host group to delete the host from.",
|
||||||
|
hostId: "The ID of the SSH host to delete from the SSH host group."
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
export const SSH_HOSTS = {
|
export const SSH_HOSTS = {
|
||||||
GET: {
|
GET: {
|
||||||
sshHostId: "The ID of the SSH host to get."
|
sshHostId: "The ID of the SSH host to get."
|
||||||
@@ -1395,7 +1478,7 @@ export const SSH_HOSTS = {
|
|||||||
loginUser: "A login user on the remote machine (e.g. 'ec2-user', 'deploy', 'admin')",
|
loginUser: "A login user on the remote machine (e.g. 'ec2-user', 'deploy', 'admin')",
|
||||||
allowedPrincipals: "A list of allowed principals that can log in as the login user.",
|
allowedPrincipals: "A list of allowed principals that can log in as the login user.",
|
||||||
loginMappings:
|
loginMappings:
|
||||||
"A list of login mappings for the SSH host. Each login mapping contains a login user and a list of corresponding allowed principals being usernames of users in the Infisical SSH project.",
|
"A list of login mappings for the SSH host. Each login mapping contains a login user and a list of corresponding allowed principals being usernames of users or groups slugs in the Infisical SSH project.",
|
||||||
userSshCaId:
|
userSshCaId:
|
||||||
"The ID of the SSH CA to use for user certificates. If not specified, the default user SSH CA will be used if it exists.",
|
"The ID of the SSH CA to use for user certificates. If not specified, the default user SSH CA will be used if it exists.",
|
||||||
hostSshCaId:
|
hostSshCaId:
|
||||||
@@ -1410,7 +1493,7 @@ export const SSH_HOSTS = {
|
|||||||
loginUser: "A login user on the remote machine (e.g. 'ec2-user', 'deploy', 'admin')",
|
loginUser: "A login user on the remote machine (e.g. 'ec2-user', 'deploy', 'admin')",
|
||||||
allowedPrincipals: "A list of allowed principals that can log in as the login user.",
|
allowedPrincipals: "A list of allowed principals that can log in as the login user.",
|
||||||
loginMappings:
|
loginMappings:
|
||||||
"A list of login mappings for the SSH host. Each login mapping contains a login user and a list of corresponding allowed principals being usernames of users in the Infisical SSH project."
|
"A list of login mappings for the SSH host. Each login mapping contains a login user and a list of corresponding allowed principals being usernames of users or groups slugs in the Infisical SSH project."
|
||||||
},
|
},
|
||||||
DELETE: {
|
DELETE: {
|
||||||
sshHostId: "The ID of the SSH host to delete."
|
sshHostId: "The ID of the SSH host to delete."
|
||||||
@@ -1580,7 +1663,8 @@ export const CERTIFICATES = {
|
|||||||
serialNumber: "The serial number of the certificate to get the certificate body and certificate chain for.",
|
serialNumber: "The serial number of the certificate to get the certificate body and certificate chain for.",
|
||||||
certificate: "The certificate body of the certificate.",
|
certificate: "The certificate body of the certificate.",
|
||||||
certificateChain: "The certificate chain of the certificate.",
|
certificateChain: "The certificate chain of the certificate.",
|
||||||
serialNumberRes: "The serial number of the certificate."
|
serialNumberRes: "The serial number of the certificate.",
|
||||||
|
privateKey: "The private key of the certificate."
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -1782,8 +1866,12 @@ export const KMS = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const ProjectTemplates = {
|
export const ProjectTemplates = {
|
||||||
|
LIST: {
|
||||||
|
type: "The type of project template to list."
|
||||||
|
},
|
||||||
CREATE: {
|
CREATE: {
|
||||||
name: "The name of the project template to be created. Must be slug-friendly.",
|
name: "The name of the project template to be created. Must be slug-friendly.",
|
||||||
|
type: "The type of project template to be created.",
|
||||||
description: "An optional description of the project template.",
|
description: "An optional description of the project template.",
|
||||||
roles: "The roles to be created when the template is applied to a project.",
|
roles: "The roles to be created when the template is applied to a project.",
|
||||||
environments: "The environments to be created when the template is applied to a project."
|
environments: "The environments to be created when the template is applied to a project."
|
||||||
@@ -1862,6 +1950,13 @@ export const AppConnections = {
|
|||||||
instanceUrl: "The Windmill instance URL to connect with (defaults to https://app.windmill.dev).",
|
instanceUrl: "The Windmill instance URL to connect with (defaults to https://app.windmill.dev).",
|
||||||
accessToken: "The access token to use to connect with Windmill."
|
accessToken: "The access token to use to connect with Windmill."
|
||||||
},
|
},
|
||||||
|
HC_VAULT: {
|
||||||
|
instanceUrl: "The Hashicrop Vault instance URL to connect with.",
|
||||||
|
namespace: "The Hashicrop Vault namespace to connect with.",
|
||||||
|
accessToken: "The access token used to connect with Hashicorp Vault.",
|
||||||
|
roleId: "The Role ID used to connect with Hashicorp Vault.",
|
||||||
|
secretId: "The Secret ID used to connect with Hashicorp Vault."
|
||||||
|
},
|
||||||
LDAP: {
|
LDAP: {
|
||||||
provider: "The type of LDAP provider. Determines provider-specific behaviors.",
|
provider: "The type of LDAP provider. Determines provider-specific behaviors.",
|
||||||
url: "The LDAP/LDAPS URL to connect to (e.g., 'ldap://domain-or-ip:389' or 'ldaps://domain-or-ip:636').",
|
url: "The LDAP/LDAPS URL to connect to (e.g., 'ldap://domain-or-ip:389' or 'ldaps://domain-or-ip:636').",
|
||||||
@@ -1875,6 +1970,10 @@ export const AppConnections = {
|
|||||||
TEAMCITY: {
|
TEAMCITY: {
|
||||||
instanceUrl: "The TeamCity instance URL to connect with.",
|
instanceUrl: "The TeamCity instance URL to connect with.",
|
||||||
accessToken: "The access token to use to connect with TeamCity."
|
accessToken: "The access token to use to connect with TeamCity."
|
||||||
|
},
|
||||||
|
AZURE_CLIENT_SECRETS: {
|
||||||
|
code: "The OAuth code to use to connect with Azure Client Secrets.",
|
||||||
|
tenantId: "The Tenant ID to use to connect with Azure Client Secrets."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -2015,6 +2114,10 @@ export const SecretSyncs = {
|
|||||||
workspace: "The Windmill workspace to sync secrets to.",
|
workspace: "The Windmill workspace to sync secrets to.",
|
||||||
path: "The Windmill workspace path to sync secrets to."
|
path: "The Windmill workspace path to sync secrets to."
|
||||||
},
|
},
|
||||||
|
HC_VAULT: {
|
||||||
|
mount: "The Hashicorp Vault Secrets Engine Mount to sync secrets to.",
|
||||||
|
path: "The Hashicorp Vault path to sync secrets to."
|
||||||
|
},
|
||||||
TEAMCITY: {
|
TEAMCITY: {
|
||||||
project: "The TeamCity project to sync secrets to.",
|
project: "The TeamCity project to sync secrets to.",
|
||||||
buildConfig: "The TeamCity build configuration to sync secrets to."
|
buildConfig: "The TeamCity build configuration to sync secrets to."
|
||||||
@@ -2083,6 +2186,11 @@ export const SecretRotations = {
|
|||||||
AUTH0_CLIENT_SECRET: {
|
AUTH0_CLIENT_SECRET: {
|
||||||
clientId: "The client ID of the Auth0 Application to rotate the client secret for."
|
clientId: "The client ID of the Auth0 Application to rotate the client secret for."
|
||||||
},
|
},
|
||||||
|
AZURE_CLIENT_SECRET: {
|
||||||
|
objectId: "The ID of the Azure Application to rotate the client secret for.",
|
||||||
|
appName: "The name of the Azure Application to rotate the client secret for.",
|
||||||
|
clientId: "The client ID of the Azure Application to rotate the client secret for."
|
||||||
|
},
|
||||||
LDAP_PASSWORD: {
|
LDAP_PASSWORD: {
|
||||||
dn: "The Distinguished Name (DN) of the principal to rotate the password for."
|
dn: "The Distinguished Name (DN) of the principal to rotate the password for."
|
||||||
},
|
},
|
||||||
@@ -2113,6 +2221,10 @@ export const SecretRotations = {
|
|||||||
clientId: "The name of the secret that the client ID will be mapped to.",
|
clientId: "The name of the secret that the client ID will be mapped to.",
|
||||||
clientSecret: "The name of the secret that the rotated client secret will be mapped to."
|
clientSecret: "The name of the secret that the rotated client secret will be mapped to."
|
||||||
},
|
},
|
||||||
|
AZURE_CLIENT_SECRET: {
|
||||||
|
clientId: "The name of the secret that the client ID will be mapped to.",
|
||||||
|
clientSecret: "The name of the secret that the rotated client secret will be mapped to."
|
||||||
|
},
|
||||||
LDAP_PASSWORD: {
|
LDAP_PASSWORD: {
|
||||||
dn: "The name of the secret that the Distinguished Name (DN) of the principal will be mapped to.",
|
dn: "The name of the secret that the Distinguished Name (DN) of the principal will be mapped to.",
|
||||||
password: "The name of the secret that the rotated password will be mapped to."
|
password: "The name of the secret that the rotated password will be mapped to."
|
||||||
|
@@ -146,6 +146,7 @@ const envSchema = z
|
|||||||
SECRET_SCANNING_GIT_APP_ID: zpStr(z.string().optional()),
|
SECRET_SCANNING_GIT_APP_ID: zpStr(z.string().optional()),
|
||||||
SECRET_SCANNING_PRIVATE_KEY: zpStr(z.string().optional()),
|
SECRET_SCANNING_PRIVATE_KEY: zpStr(z.string().optional()),
|
||||||
SECRET_SCANNING_ORG_WHITELIST: zpStr(z.string().optional()),
|
SECRET_SCANNING_ORG_WHITELIST: zpStr(z.string().optional()),
|
||||||
|
SECRET_SCANNING_GIT_APP_SLUG: zpStr(z.string().default("infisical-radar")),
|
||||||
// LICENSE
|
// LICENSE
|
||||||
LICENSE_SERVER_URL: zpStr(z.string().optional().default("https://portal.infisical.com")),
|
LICENSE_SERVER_URL: zpStr(z.string().optional().default("https://portal.infisical.com")),
|
||||||
LICENSE_SERVER_KEY: zpStr(z.string().optional()),
|
LICENSE_SERVER_KEY: zpStr(z.string().optional()),
|
||||||
|
4
backend/src/lib/delay/index.ts
Normal file
4
backend/src/lib/delay/index.ts
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
export const delay = (ms: number) =>
|
||||||
|
new Promise<void>((resolve) => {
|
||||||
|
setTimeout(resolve, ms);
|
||||||
|
});
|
@@ -6,4 +6,5 @@ export * from "./array";
|
|||||||
export * from "./dates";
|
export * from "./dates";
|
||||||
export * from "./object";
|
export * from "./object";
|
||||||
export * from "./string";
|
export * from "./string";
|
||||||
|
export * from "./time";
|
||||||
export * from "./undefined";
|
export * from "./undefined";
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user