mirror of
https://github.com/Infisical/infisical.git
synced 2025-07-10 11:43:04 +00:00
Compare commits
325 Commits
overview-u
...
project-ov
Author | SHA1 | Date | |
---|---|---|---|
b467619341 | |||
0f20758df2 | |||
f636cc678b | |||
ff8ad14e1b | |||
d683d3adb3 | |||
d9b8cd1204 | |||
27b5e2aa68 | |||
692121445d | |||
9a940dce64 | |||
7e523546b3 | |||
814d6e2709 | |||
c0b296ccd5 | |||
da82cfdf6b | |||
92147b5398 | |||
526e184bd9 | |||
9943312063 | |||
c2cefb2b0c | |||
7571c9b426 | |||
bf707667b5 | |||
d2e6743f22 | |||
9e896563ed | |||
64744d042d | |||
2648ac1c90 | |||
22ae1aeee4 | |||
cd13733621 | |||
0191eb48f3 | |||
9d39910152 | |||
c5a8786d1c | |||
9137fa4ca5 | |||
84687c0558 | |||
78da7ec343 | |||
a678ebb4ac | |||
83dd38db49 | |||
00d4ae9fbd | |||
218338e5d2 | |||
456107fbf3 | |||
2003f5b671 | |||
d2c6bcc7a7 | |||
06bd593b60 | |||
aea43c0a8e | |||
06f5af1200 | |||
f903e5b3d4 | |||
c6f8915d3f | |||
65b1354ef1 | |||
cda8579ca4 | |||
5badb811e1 | |||
7f8b489724 | |||
8723a16913 | |||
b4593a2e11 | |||
1b1acdcb0b | |||
1bbf78e295 | |||
a8f08730a1 | |||
9af9050aa2 | |||
3b767a4deb | |||
18f5f5d04e | |||
6a6f08fc4d | |||
cc564119e0 | |||
189b0dd5ee | |||
9cbef2c07b | |||
9a960a85cd | |||
2a9e31d305 | |||
fb2f1731dd | |||
42648a134c | |||
defb66ce65 | |||
a3d06fdf1b | |||
9049c441d6 | |||
51ecc9dfa0 | |||
13c9879fb6 | |||
8c6b903204 | |||
23b20ebdab | |||
37d490ede3 | |||
edecfb1f62 | |||
ae35a863bc | |||
73025f5094 | |||
82634983ce | |||
af2f3017b7 | |||
a8f0eceeb9 | |||
36ff5e054b | |||
eff73f1810 | |||
68357b5669 | |||
03c2e93bea | |||
8c1f3837e7 | |||
7b47d91cc1 | |||
c37afaa050 | |||
811920f8bb | |||
7b295c5a21 | |||
527a727c1c | |||
0139064aaa | |||
a3859170fe | |||
62ad82f7b1 | |||
02b97cbf5b | |||
8a65343f79 | |||
cf6181eb73 | |||
984ffd2a53 | |||
a1c44bd7a2 | |||
d7860e2491 | |||
db33349f49 | |||
7ab67db84d | |||
e14bb6b901 | |||
3a17281e37 | |||
91d6d5d07b | |||
ac7b23da45 | |||
1fdc82e494 | |||
3daae6f965 | |||
833963af0c | |||
aa560b8199 | |||
a215b99b3c | |||
fbd9ecd980 | |||
3b839d4826 | |||
b52ec37f76 | |||
5709afe0d3 | |||
566a243520 | |||
147c21ab9f | |||
abfe185a5b | |||
f62eb9f8a2 | |||
ec60080e27 | |||
9fdc56bd6c | |||
9163da291e | |||
f6c10683a5 | |||
307e6900ee | |||
bb59bb1868 | |||
139f880be1 | |||
69157cb912 | |||
44eb761d5b | |||
f6002d81b3 | |||
af240bd58c | |||
414de3c4d0 | |||
1a7b810bad | |||
0379ba4eb1 | |||
c2ce1aa5aa | |||
c8e155f0ca | |||
5ced43574d | |||
19ff045d2e | |||
4784f47a72 | |||
abbf541c9f | |||
28a27daf29 | |||
fcdd121a58 | |||
5bfd92bf8d | |||
83f0a500bd | |||
325d277021 | |||
45af2c0b49 | |||
9ca71f663a | |||
e5c7aba745 | |||
cada75bd0c | |||
a37689eeca | |||
ba57899a56 | |||
38c9242e5b | |||
8dafa75aa2 | |||
aea61bae38 | |||
37a10d1435 | |||
a64c2173e7 | |||
ec0603a464 | |||
bf8d60fcdc | |||
b47846a780 | |||
ea403b0393 | |||
9ab89fdef6 | |||
dea22ab844 | |||
8bdf294a34 | |||
0b2c967e63 | |||
c89876aa10 | |||
76b3aab4c0 | |||
944319b9b6 | |||
ac6f79815a | |||
6734bf245f | |||
b32584ce73 | |||
3e41b359c5 | |||
2352bca03e | |||
9f3236b47d | |||
01c5f516f8 | |||
74067751a6 | |||
fa7318eeb1 | |||
fb9c580e53 | |||
1bfdbb7314 | |||
6b3279cbe5 | |||
48ac6b4aff | |||
b0c1c9ce26 | |||
d82d22a198 | |||
c66510f473 | |||
09cdd5ec91 | |||
e028b4e26d | |||
b8f7ffbf53 | |||
0d97fc27c7 | |||
098c1d840b | |||
cce2a54265 | |||
d1033cb324 | |||
7134e1dc66 | |||
8aa26b77ed | |||
4b06880320 | |||
124cd9f812 | |||
d531d069d1 | |||
522a5d477d | |||
d2f0db669a | |||
4dd78d745b | |||
4fef5c305d | |||
e5bbc46b0f | |||
30f3543850 | |||
114915f913 | |||
b5801af9a8 | |||
20366a8c07 | |||
60a4c72a5d | |||
447e28511c | |||
650ed656e3 | |||
13d2cbd8b0 | |||
abfc5736fd | |||
54ac450b63 | |||
3871fa552c | |||
9c72ee7f10 | |||
22e8617661 | |||
2f29a513cc | |||
cb6c28ac26 | |||
d3833c33b3 | |||
978a3e5828 | |||
27bf91e58f | |||
f2c3c76c60 | |||
85023916e4 | |||
3723afe595 | |||
02afd6a8e7 | |||
14d6f6c048 | |||
929eac4350 | |||
c6074dd69a | |||
a9b26755ba | |||
033e5d3f81 | |||
90634e1913 | |||
58b61a861a | |||
3c8ec7d7fb | |||
26a59286c5 | |||
392792bb1e | |||
d79a6b8f25 | |||
217a09c97b | |||
a389ede03d | |||
10939fecc0 | |||
48f40ff938 | |||
969896e431 | |||
fd85da5739 | |||
2caf6ff94b | |||
ed7d709a70 | |||
aff97374a9 | |||
e8e90585ca | |||
abd9dbf714 | |||
89aed3640b | |||
5513ff7631 | |||
9fb7676739 | |||
6ac734d6c4 | |||
8044999785 | |||
be51e4372d | |||
460b545925 | |||
2f26c1930b | |||
68abd0f044 | |||
f3c11a0a17 | |||
f4779de051 | |||
defe7b8f0b | |||
cf3113ac89 | |||
953cc3a850 | |||
fc9ae05f89 | |||
de22a3c56b | |||
7c4baa6fd4 | |||
f285648c95 | |||
0f04890d8f | |||
61274243e2 | |||
9366428091 | |||
62482852aa | |||
cc02c00b61 | |||
2e256e4282 | |||
1b4bae6a84 | |||
1f0bcae0fc | |||
dcd21883d1 | |||
9af5a66bab | |||
d7913a75c2 | |||
205442bff5 | |||
8ab51aba12 | |||
3d1f054b87 | |||
29fedfdde5 | |||
b5317d1d75 | |||
aef3a7436f | |||
6446311b6d | |||
3e80f1907c | |||
79e62eec25 | |||
c41730c5fb | |||
aac63d3097 | |||
f0b9d3c816 | |||
ea393d144a | |||
c4c0f86598 | |||
1f7617d132 | |||
c95680b95d | |||
18f1f93b5f | |||
70ea761375 | |||
5b4790ee78 | |||
5ab2a6bb5d | |||
dcac85fe6c | |||
2f07471404 | |||
137fd5ef07 | |||
883c7835a1 | |||
e33f34ceb4 | |||
af5805a5ca | |||
bcf1c49a1b | |||
84fedf8eda | |||
97755981eb | |||
8291663802 | |||
d9aed45504 | |||
8ada11edf3 | |||
4bd62aa462 | |||
0366e58a5b | |||
9f6dca23db | |||
18e733c71f | |||
f0a95808e7 | |||
90a0d0f744 | |||
7f9c9be2c8 | |||
070982081c | |||
f462c3f85d | |||
8683693103 | |||
737fffcceb | |||
ffac24ce75 | |||
c505c5877f | |||
d4bf8a33dc | |||
6566393e21 | |||
af245b1f16 | |||
c17df7e951 | |||
4d4953e95a | |||
43e0d400f9 | |||
b80b77ec36 | |||
198e74cd88 | |||
8ed0a1de84 | |||
c305ddd463 | |||
27cb686216 | |||
e201d77a8f |
@ -107,6 +107,10 @@ INF_APP_CONNECTION_GITHUB_APP_PRIVATE_KEY=
|
||||
INF_APP_CONNECTION_GITHUB_APP_SLUG=
|
||||
INF_APP_CONNECTION_GITHUB_APP_ID=
|
||||
|
||||
#gitlab app connection
|
||||
INF_APP_CONNECTION_GITLAB_OAUTH_CLIENT_ID=
|
||||
INF_APP_CONNECTION_GITLAB_OAUTH_CLIENT_SECRET=
|
||||
|
||||
#github radar app connection
|
||||
INF_APP_CONNECTION_GITHUB_RADAR_APP_CLIENT_ID=
|
||||
INF_APP_CONNECTION_GITHUB_RADAR_APP_CLIENT_SECRET=
|
||||
|
@ -83,7 +83,7 @@ jobs:
|
||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
|
||||
goreleaser:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-latest-8-cores
|
||||
needs: [cli-integration-tests]
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
@ -19,7 +19,7 @@ WORKDIR /app
|
||||
|
||||
# Copy dependencies
|
||||
COPY --from=frontend-dependencies /app/node_modules ./node_modules
|
||||
# Copy all files
|
||||
# Copy all files
|
||||
COPY /frontend .
|
||||
|
||||
ENV NODE_ENV production
|
||||
@ -32,7 +32,7 @@ ENV VITE_INTERCOM_ID $INTERCOM_ID
|
||||
ARG INFISICAL_PLATFORM_VERSION
|
||||
ENV VITE_INFISICAL_PLATFORM_VERSION $INFISICAL_PLATFORM_VERSION
|
||||
ARG CAPTCHA_SITE_KEY
|
||||
ENV VITE_CAPTCHA_SITE_KEY $CAPTCHA_SITE_KEY
|
||||
ENV VITE_CAPTCHA_SITE_KEY $CAPTCHA_SITE_KEY
|
||||
|
||||
# Build
|
||||
RUN npm run build
|
||||
@ -134,7 +134,7 @@ RUN printf "[FreeTDS]\nDescription = FreeTDS Driver\nDriver = /usr/lib/x86_64-li
|
||||
|
||||
# Install Infisical CLI
|
||||
RUN curl -1sLf 'https://artifacts-cli.infisical.com/setup.deb.sh' | bash \
|
||||
&& apt-get update && apt-get install -y infisical=0.41.2 \
|
||||
&& apt-get update && apt-get install -y infisical=0.41.89 \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
RUN groupadd -r -g 1001 nodejs && useradd -r -u 1001 -g nodejs non-root-user
|
||||
@ -155,7 +155,7 @@ ENV INTERCOM_ID=$INTERCOM_ID
|
||||
ARG CAPTCHA_SITE_KEY
|
||||
ENV CAPTCHA_SITE_KEY=$CAPTCHA_SITE_KEY
|
||||
|
||||
WORKDIR /
|
||||
WORKDIR /
|
||||
|
||||
COPY --from=backend-runner /app /backend
|
||||
|
||||
@ -166,9 +166,9 @@ ENV INFISICAL_PLATFORM_VERSION $INFISICAL_PLATFORM_VERSION
|
||||
|
||||
ENV PORT 8080
|
||||
ENV HOST=0.0.0.0
|
||||
ENV HTTPS_ENABLED false
|
||||
ENV HTTPS_ENABLED false
|
||||
ENV NODE_ENV production
|
||||
ENV STANDALONE_BUILD true
|
||||
ENV STANDALONE_BUILD true
|
||||
ENV STANDALONE_MODE true
|
||||
ENV ChrystokiConfigurationPath=/usr/safenet/lunaclient/
|
||||
ENV NODE_OPTIONS="--max-old-space-size=1024"
|
||||
|
@ -20,7 +20,7 @@ WORKDIR /app
|
||||
|
||||
# Copy dependencies
|
||||
COPY --from=frontend-dependencies /app/node_modules ./node_modules
|
||||
# Copy all files
|
||||
# Copy all files
|
||||
COPY /frontend .
|
||||
|
||||
ENV NODE_ENV production
|
||||
@ -33,7 +33,7 @@ ENV VITE_INTERCOM_ID $INTERCOM_ID
|
||||
ARG INFISICAL_PLATFORM_VERSION
|
||||
ENV VITE_INFISICAL_PLATFORM_VERSION $INFISICAL_PLATFORM_VERSION
|
||||
ARG CAPTCHA_SITE_KEY
|
||||
ENV VITE_CAPTCHA_SITE_KEY $CAPTCHA_SITE_KEY
|
||||
ENV VITE_CAPTCHA_SITE_KEY $CAPTCHA_SITE_KEY
|
||||
|
||||
# Build
|
||||
RUN npm run build
|
||||
@ -128,7 +128,7 @@ RUN apt-get update && apt-get install -y \
|
||||
|
||||
# Install Infisical CLI
|
||||
RUN curl -1sLf 'https://artifacts-cli.infisical.com/setup.deb.sh' | bash \
|
||||
&& apt-get update && apt-get install -y infisical=0.41.2 \
|
||||
&& apt-get update && apt-get install -y infisical=0.41.89 \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
WORKDIR /
|
||||
@ -164,9 +164,9 @@ ENV INFISICAL_PLATFORM_VERSION $INFISICAL_PLATFORM_VERSION
|
||||
|
||||
ENV PORT 8080
|
||||
ENV HOST=0.0.0.0
|
||||
ENV HTTPS_ENABLED false
|
||||
ENV HTTPS_ENABLED false
|
||||
ENV NODE_ENV production
|
||||
ENV STANDALONE_BUILD true
|
||||
ENV STANDALONE_BUILD true
|
||||
ENV STANDALONE_MODE true
|
||||
ENV NODE_OPTIONS="--max-old-space-size=1024"
|
||||
|
||||
|
@ -9,7 +9,7 @@ RUN apt-get update && apt-get install -y \
|
||||
make \
|
||||
g++ \
|
||||
openssh-client \
|
||||
openssl
|
||||
openssl
|
||||
|
||||
# Install dependencies for TDS driver (required for SAP ASE dynamic secrets)
|
||||
RUN apt-get install -y \
|
||||
@ -55,10 +55,10 @@ COPY --from=build /app .
|
||||
# Install Infisical CLI
|
||||
RUN apt-get install -y curl bash && \
|
||||
curl -1sLf 'https://artifacts-cli.infisical.com/setup.deb.sh' | bash && \
|
||||
apt-get update && apt-get install -y infisical=0.41.2 git
|
||||
apt-get update && apt-get install -y infisical=0.41.89 git
|
||||
|
||||
HEALTHCHECK --interval=10s --timeout=3s --start-period=10s \
|
||||
CMD node healthcheck.js
|
||||
HEALTHCHECK --interval=10s --timeout=3s --start-period=10s \
|
||||
CMD node healthcheck.js
|
||||
|
||||
ENV HOST=0.0.0.0
|
||||
|
||||
|
@ -57,7 +57,7 @@ RUN mkdir -p /etc/softhsm2/tokens && \
|
||||
# Install Infisical CLI
|
||||
RUN curl -1sLf 'https://artifacts-cli.infisical.com/setup.deb.sh' | bash && \
|
||||
apt-get update && \
|
||||
apt-get install -y infisical=0.41.2
|
||||
apt-get install -y infisical=0.41.89
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
|
@ -52,7 +52,7 @@ RUN apt-get install -y opensc
|
||||
|
||||
RUN mkdir -p /etc/softhsm2/tokens && \
|
||||
softhsm2-util --init-token --slot 0 --label "auth-app" --pin 1234 --so-pin 0000
|
||||
|
||||
|
||||
WORKDIR /openssl-build
|
||||
RUN wget https://www.openssl.org/source/openssl-3.1.2.tar.gz \
|
||||
&& tar -xf openssl-3.1.2.tar.gz \
|
||||
@ -66,7 +66,7 @@ RUN wget https://www.openssl.org/source/openssl-3.1.2.tar.gz \
|
||||
# Install Infisical CLI
|
||||
RUN curl -1sLf 'https://artifacts-cli.infisical.com/setup.deb.sh' | bash && \
|
||||
apt-get update && \
|
||||
apt-get install -y infisical=0.41.2
|
||||
apt-get install -y infisical=0.41.89
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
|
@ -8,6 +8,9 @@ import { Lock } from "@app/lib/red-lock";
|
||||
export const mockKeyStore = (): TKeyStoreFactory => {
|
||||
const store: Record<string, string | number | Buffer> = {};
|
||||
|
||||
const getRegex = (pattern: string) =>
|
||||
new RE2(`^${pattern.replace(/[-[\]/{}()+?.\\^$|]/g, "\\$&").replace(/\*/g, ".*")}$`);
|
||||
|
||||
return {
|
||||
setItem: async (key, value) => {
|
||||
store[key] = value;
|
||||
@ -23,7 +26,7 @@ export const mockKeyStore = (): TKeyStoreFactory => {
|
||||
return 1;
|
||||
},
|
||||
deleteItems: async ({ pattern, batchSize = 500, delay = 1500, jitter = 200 }) => {
|
||||
const regex = new RE2(`^${pattern.replace(/[-[\]/{}()+?.\\^$|]/g, "\\$&").replace(/\*/g, ".*")}$`);
|
||||
const regex = getRegex(pattern);
|
||||
let totalDeleted = 0;
|
||||
const keys = Object.keys(store);
|
||||
|
||||
@ -53,6 +56,27 @@ export const mockKeyStore = (): TKeyStoreFactory => {
|
||||
incrementBy: async () => {
|
||||
return 1;
|
||||
},
|
||||
getItems: async (keys) => {
|
||||
const values = keys.map((key) => {
|
||||
const value = store[key];
|
||||
if (typeof value === "string") {
|
||||
return value;
|
||||
}
|
||||
return null;
|
||||
});
|
||||
return values;
|
||||
},
|
||||
getKeysByPattern: async (pattern) => {
|
||||
const regex = getRegex(pattern);
|
||||
const keys = Object.keys(store);
|
||||
return keys.filter((key) => regex.test(key));
|
||||
},
|
||||
deleteItemsByKeyIn: async (keys) => {
|
||||
for (const key of keys) {
|
||||
delete store[key];
|
||||
}
|
||||
return keys.length;
|
||||
},
|
||||
acquireLock: () => {
|
||||
return Promise.resolve({
|
||||
release: () => {}
|
||||
|
67
backend/package-lock.json
generated
67
backend/package-lock.json
generated
@ -30,6 +30,7 @@
|
||||
"@fastify/static": "^7.0.4",
|
||||
"@fastify/swagger": "^8.14.0",
|
||||
"@fastify/swagger-ui": "^2.1.0",
|
||||
"@gitbeaker/rest": "^42.5.0",
|
||||
"@google-cloud/kms": "^4.5.0",
|
||||
"@infisical/quic": "^1.0.8",
|
||||
"@node-saml/passport-saml": "^5.0.1",
|
||||
@ -7807,6 +7808,48 @@
|
||||
"p-limit": "^3.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@gitbeaker/core": {
|
||||
"version": "42.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@gitbeaker/core/-/core-42.5.0.tgz",
|
||||
"integrity": "sha512-rMWpOPaZi1iLiifnOIoVO57p2EmQQdfIwP4txqNyMvG4WjYP5Ez0U7jRD9Nra41x6K5kTPBZkuQcAdxVWRJcEQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@gitbeaker/requester-utils": "^42.5.0",
|
||||
"qs": "^6.12.2",
|
||||
"xcase": "^2.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.20.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@gitbeaker/requester-utils": {
|
||||
"version": "42.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@gitbeaker/requester-utils/-/requester-utils-42.5.0.tgz",
|
||||
"integrity": "sha512-HLdLS9LPBMVQumvroQg/4qkphLDtwDB+ygEsrD2u4oYCMUtXV4V1xaVqU4yTXjbTJ5sItOtdB43vYRkBcgueBw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"picomatch-browser": "^2.2.6",
|
||||
"qs": "^6.12.2",
|
||||
"rate-limiter-flexible": "^4.0.1",
|
||||
"xcase": "^2.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.20.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@gitbeaker/rest": {
|
||||
"version": "42.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@gitbeaker/rest/-/rest-42.5.0.tgz",
|
||||
"integrity": "sha512-oC5cM6jS7aFOp0luTw5mWSRuMgdxwHRLZQ/aWkI+ETMfsprR/HyxsXfljlMY/XJ/fRxTbRJiodR5Axf66WjO3w==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@gitbeaker/core": "^42.5.0",
|
||||
"@gitbeaker/requester-utils": "^42.5.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.20.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@google-cloud/kms": {
|
||||
"version": "4.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@google-cloud/kms/-/kms-4.5.0.tgz",
|
||||
@ -24628,6 +24671,18 @@
|
||||
"url": "https://github.com/sponsors/jonschlinkert"
|
||||
}
|
||||
},
|
||||
"node_modules/picomatch-browser": {
|
||||
"version": "2.2.6",
|
||||
"resolved": "https://registry.npmjs.org/picomatch-browser/-/picomatch-browser-2.2.6.tgz",
|
||||
"integrity": "sha512-0ypsOQt9D4e3hziV8O4elD9uN0z/jtUEfxVRtNaAAtXIyUx9m/SzlO020i8YNL2aL/E6blOvvHQcin6HZlFy/w==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8.6"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/jonschlinkert"
|
||||
}
|
||||
},
|
||||
"node_modules/pify": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz",
|
||||
@ -25562,6 +25617,12 @@
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/rate-limiter-flexible": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/rate-limiter-flexible/-/rate-limiter-flexible-4.0.1.tgz",
|
||||
"integrity": "sha512-2/dGHpDFpeA0+755oUkW+EKyklqLS9lu0go9pDsbhqQjZcxfRyJ6LA4JI0+HAdZ2bemD/oOjUeZQB2lCZqXQfQ==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/raw-body": {
|
||||
"version": "2.5.2",
|
||||
"resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz",
|
||||
@ -31039,6 +31100,12 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/xcase": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/xcase/-/xcase-2.0.1.tgz",
|
||||
"integrity": "sha512-UmFXIPU+9Eg3E9m/728Bii0lAIuoc+6nbrNUKaRPJOFp91ih44qqGlWtxMB6kXFrRD6po+86ksHM5XHCfk6iPw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/xml-crypto": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/xml-crypto/-/xml-crypto-6.0.1.tgz",
|
||||
|
@ -149,6 +149,7 @@
|
||||
"@fastify/static": "^7.0.4",
|
||||
"@fastify/swagger": "^8.14.0",
|
||||
"@fastify/swagger-ui": "^2.1.0",
|
||||
"@gitbeaker/rest": "^42.5.0",
|
||||
"@google-cloud/kms": "^4.5.0",
|
||||
"@infisical/quic": "^1.0.8",
|
||||
"@node-saml/passport-saml": "^5.0.1",
|
||||
|
2
backend/src/@types/fastify.d.ts
vendored
2
backend/src/@types/fastify.d.ts
vendored
@ -74,6 +74,7 @@ import { TAllowedFields } from "@app/services/identity-ldap-auth/identity-ldap-a
|
||||
import { TIdentityOciAuthServiceFactory } from "@app/services/identity-oci-auth/identity-oci-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 { TIdentityTlsCertAuthServiceFactory } from "@app/services/identity-tls-cert-auth/identity-tls-cert-auth-types";
|
||||
import { TIdentityTokenAuthServiceFactory } from "@app/services/identity-token-auth/identity-token-auth-service";
|
||||
import { TIdentityUaServiceFactory } from "@app/services/identity-ua/identity-ua-service";
|
||||
import { TIntegrationServiceFactory } from "@app/services/integration/integration-service";
|
||||
@ -218,6 +219,7 @@ declare module "fastify" {
|
||||
identityKubernetesAuth: TIdentityKubernetesAuthServiceFactory;
|
||||
identityGcpAuth: TIdentityGcpAuthServiceFactory;
|
||||
identityAliCloudAuth: TIdentityAliCloudAuthServiceFactory;
|
||||
identityTlsCertAuth: TIdentityTlsCertAuthServiceFactory;
|
||||
identityAwsAuth: TIdentityAwsAuthServiceFactory;
|
||||
identityAzureAuth: TIdentityAzureAuthServiceFactory;
|
||||
identityOciAuth: TIdentityOciAuthServiceFactory;
|
||||
|
8
backend/src/@types/knex.d.ts
vendored
8
backend/src/@types/knex.d.ts
vendored
@ -164,6 +164,9 @@ import {
|
||||
TIdentityProjectMemberships,
|
||||
TIdentityProjectMembershipsInsert,
|
||||
TIdentityProjectMembershipsUpdate,
|
||||
TIdentityTlsCertAuths,
|
||||
TIdentityTlsCertAuthsInsert,
|
||||
TIdentityTlsCertAuthsUpdate,
|
||||
TIdentityTokenAuths,
|
||||
TIdentityTokenAuthsInsert,
|
||||
TIdentityTokenAuthsUpdate,
|
||||
@ -794,6 +797,11 @@ declare module "knex/types/tables" {
|
||||
TIdentityAlicloudAuthsInsert,
|
||||
TIdentityAlicloudAuthsUpdate
|
||||
>;
|
||||
[TableName.IdentityTlsCertAuth]: KnexOriginal.CompositeTableType<
|
||||
TIdentityTlsCertAuths,
|
||||
TIdentityTlsCertAuthsInsert,
|
||||
TIdentityTlsCertAuthsUpdate
|
||||
>;
|
||||
[TableName.IdentityAwsAuth]: KnexOriginal.CompositeTableType<
|
||||
TIdentityAwsAuths,
|
||||
TIdentityAwsAuthsInsert,
|
||||
|
@ -110,7 +110,8 @@ export const initAuditLogDbConnection = ({
|
||||
},
|
||||
migrations: {
|
||||
tableName: "infisical_migrations"
|
||||
}
|
||||
},
|
||||
pool: { min: 0, max: 10 }
|
||||
});
|
||||
|
||||
// we add these overrides so that auditLogDb and the primary DB are interchangeable
|
||||
|
@ -4,6 +4,7 @@ import "ts-node/register";
|
||||
import dotenv from "dotenv";
|
||||
import type { Knex } from "knex";
|
||||
import path from "path";
|
||||
import { initLogger } from "@app/lib/logger";
|
||||
|
||||
// Update with your config settings. .
|
||||
dotenv.config({
|
||||
@ -13,6 +14,8 @@ dotenv.config({
|
||||
path: path.join(__dirname, "../../../.env")
|
||||
});
|
||||
|
||||
initLogger();
|
||||
|
||||
export default {
|
||||
development: {
|
||||
client: "postgres",
|
||||
|
@ -0,0 +1,28 @@
|
||||
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.IdentityTlsCertAuth))) {
|
||||
await knex.schema.createTable(TableName.IdentityTlsCertAuth, (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.timestamps(true, true, true);
|
||||
t.uuid("identityId").notNullable().unique();
|
||||
t.foreign("identityId").references("id").inTable(TableName.Identity).onDelete("CASCADE");
|
||||
t.string("allowedCommonNames").nullable();
|
||||
t.binary("encryptedCaCertificate").notNullable();
|
||||
});
|
||||
}
|
||||
|
||||
await createOnUpdateTrigger(knex, TableName.IdentityTlsCertAuth);
|
||||
}
|
||||
|
||||
export async function down(knex: Knex): Promise<void> {
|
||||
await knex.schema.dropTableIfExists(TableName.IdentityTlsCertAuth);
|
||||
await dropOnUpdateTrigger(knex, TableName.IdentityTlsCertAuth);
|
||||
}
|
@ -0,0 +1,41 @@
|
||||
import { Knex } from "knex";
|
||||
|
||||
import { ProjectType, TableName } from "../schemas";
|
||||
|
||||
export async function up(knex: Knex): Promise<void> {
|
||||
const hasTypeColumn = await knex.schema.hasColumn(TableName.Project, "type");
|
||||
const hasDefaultTypeColumn = await knex.schema.hasColumn(TableName.Project, "defaultProduct");
|
||||
if (hasTypeColumn && !hasDefaultTypeColumn) {
|
||||
await knex.schema.alterTable(TableName.Project, (t) => {
|
||||
t.string("type").nullable().alter();
|
||||
t.string("defaultProduct").notNullable().defaultTo(ProjectType.SecretManager);
|
||||
});
|
||||
|
||||
await knex(TableName.Project).update({
|
||||
// eslint-disable-next-line
|
||||
// @ts-ignore this is because this field is created later
|
||||
defaultProduct: knex.raw(`
|
||||
CASE
|
||||
WHEN "type" IS NULL OR "type" = '' THEN 'secret-manager'
|
||||
ELSE "type"
|
||||
END
|
||||
`)
|
||||
});
|
||||
}
|
||||
|
||||
const hasTemplateTypeColumn = await knex.schema.hasColumn(TableName.ProjectTemplates, "type");
|
||||
if (hasTemplateTypeColumn) {
|
||||
await knex.schema.alterTable(TableName.ProjectTemplates, (t) => {
|
||||
t.string("type").nullable().alter();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export async function down(knex: Knex): Promise<void> {
|
||||
const hasDefaultTypeColumn = await knex.schema.hasColumn(TableName.Project, "defaultProduct");
|
||||
if (hasDefaultTypeColumn) {
|
||||
await knex.schema.alterTable(TableName.Project, (t) => {
|
||||
t.dropColumn("defaultProduct");
|
||||
});
|
||||
}
|
||||
}
|
21
backend/src/db/migrations/20250627010508_env-overrides.ts
Normal file
21
backend/src/db/migrations/20250627010508_env-overrides.ts
Normal file
@ -0,0 +1,21 @@
|
||||
import { Knex } from "knex";
|
||||
|
||||
import { TableName } from "../schemas";
|
||||
|
||||
export async function up(knex: Knex): Promise<void> {
|
||||
const hasColumn = await knex.schema.hasColumn(TableName.SuperAdmin, "encryptedEnvOverrides");
|
||||
if (!hasColumn) {
|
||||
await knex.schema.alterTable(TableName.SuperAdmin, (t) => {
|
||||
t.binary("encryptedEnvOverrides").nullable();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export async function down(knex: Knex): Promise<void> {
|
||||
const hasColumn = await knex.schema.hasColumn(TableName.SuperAdmin, "encryptedEnvOverrides");
|
||||
if (hasColumn) {
|
||||
await knex.schema.alterTable(TableName.SuperAdmin, (t) => {
|
||||
t.dropColumn("encryptedEnvOverrides");
|
||||
});
|
||||
}
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
import { Knex } from "knex";
|
||||
|
||||
import { TableName } from "../schemas";
|
||||
|
||||
export async function up(knex: Knex): Promise<void> {
|
||||
const hasColumn = await knex.schema.hasColumn(TableName.OrgMembership, "lastInvitedAt");
|
||||
await knex.schema.alterTable(TableName.OrgMembership, (t) => {
|
||||
if (!hasColumn) {
|
||||
t.datetime("lastInvitedAt").nullable();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export async function down(knex: Knex): Promise<void> {
|
||||
const hasColumn = await knex.schema.hasColumn(TableName.OrgMembership, "lastInvitedAt");
|
||||
await knex.schema.alterTable(TableName.OrgMembership, (t) => {
|
||||
if (hasColumn) {
|
||||
t.dropColumn("lastInvitedAt");
|
||||
}
|
||||
});
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
import { Knex } from "knex";
|
||||
|
||||
import { TableName } from "../schemas";
|
||||
|
||||
export async function up(knex: Knex): Promise<void> {
|
||||
const hasColumn = await knex.schema.hasColumn(TableName.OrgMembership, "lastInvitedAt");
|
||||
if (hasColumn) {
|
||||
await knex.schema.alterTable(TableName.OrgMembership, (t) => {
|
||||
t.datetime("lastInvitedAt").nullable().defaultTo(knex.fn.now()).alter();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export async function down(knex: Knex): Promise<void> {
|
||||
const hasColumn = await knex.schema.hasColumn(TableName.OrgMembership, "lastInvitedAt");
|
||||
if (hasColumn) {
|
||||
await knex.schema.alterTable(TableName.OrgMembership, (t) => {
|
||||
t.datetime("lastInvitedAt").nullable().alter();
|
||||
});
|
||||
}
|
||||
}
|
27
backend/src/db/schemas/identity-tls-cert-auths.ts
Normal file
27
backend/src/db/schemas/identity-tls-cert-auths.ts
Normal file
@ -0,0 +1,27 @@
|
||||
// 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 IdentityTlsCertAuthsSchema = 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(),
|
||||
createdAt: z.date(),
|
||||
updatedAt: z.date(),
|
||||
identityId: z.string().uuid(),
|
||||
allowedCommonNames: z.string().nullable().optional(),
|
||||
encryptedCaCertificate: zodBuffer
|
||||
});
|
||||
|
||||
export type TIdentityTlsCertAuths = z.infer<typeof IdentityTlsCertAuthsSchema>;
|
||||
export type TIdentityTlsCertAuthsInsert = Omit<z.input<typeof IdentityTlsCertAuthsSchema>, TImmutableDBKeys>;
|
||||
export type TIdentityTlsCertAuthsUpdate = Partial<Omit<z.input<typeof IdentityTlsCertAuthsSchema>, TImmutableDBKeys>>;
|
@ -52,6 +52,7 @@ export * from "./identity-org-memberships";
|
||||
export * from "./identity-project-additional-privilege";
|
||||
export * from "./identity-project-membership-role";
|
||||
export * from "./identity-project-memberships";
|
||||
export * from "./identity-tls-cert-auths";
|
||||
export * from "./identity-token-auths";
|
||||
export * from "./identity-ua-client-secrets";
|
||||
export * from "./identity-universal-auths";
|
||||
|
@ -86,6 +86,7 @@ export enum TableName {
|
||||
IdentityOidcAuth = "identity_oidc_auths",
|
||||
IdentityJwtAuth = "identity_jwt_auths",
|
||||
IdentityLdapAuth = "identity_ldap_auths",
|
||||
IdentityTlsCertAuth = "identity_tls_cert_auths",
|
||||
IdentityOrgMembership = "identity_org_memberships",
|
||||
IdentityProjectMembership = "identity_project_memberships",
|
||||
IdentityProjectMembershipRole = "identity_project_membership_role",
|
||||
@ -251,6 +252,7 @@ export enum IdentityAuthMethod {
|
||||
ALICLOUD_AUTH = "alicloud-auth",
|
||||
AWS_AUTH = "aws-auth",
|
||||
AZURE_AUTH = "azure-auth",
|
||||
TLS_CERT_AUTH = "tls-cert-auth",
|
||||
OCI_AUTH = "oci-auth",
|
||||
OIDC_AUTH = "oidc-auth",
|
||||
JWT_AUTH = "jwt-auth",
|
||||
@ -265,16 +267,6 @@ export enum ProjectType {
|
||||
SecretScanning = "secret-scanning"
|
||||
}
|
||||
|
||||
export enum ActionProjectType {
|
||||
SecretManager = ProjectType.SecretManager,
|
||||
CertificateManager = ProjectType.CertificateManager,
|
||||
KMS = ProjectType.KMS,
|
||||
SSH = ProjectType.SSH,
|
||||
SecretScanning = ProjectType.SecretScanning,
|
||||
// project operations that happen on all types
|
||||
Any = "any"
|
||||
}
|
||||
|
||||
export enum SortDirection {
|
||||
ASC = "asc",
|
||||
DESC = "desc"
|
||||
|
@ -18,7 +18,8 @@ export const OrgMembershipsSchema = z.object({
|
||||
orgId: z.string().uuid(),
|
||||
roleId: z.string().uuid().nullable().optional(),
|
||||
projectFavorites: z.string().array().nullable().optional(),
|
||||
isActive: z.boolean().default(true)
|
||||
isActive: z.boolean().default(true),
|
||||
lastInvitedAt: z.date().nullable().optional()
|
||||
});
|
||||
|
||||
export type TOrgMemberships = z.infer<typeof OrgMembershipsSchema>;
|
||||
|
@ -16,7 +16,7 @@ export const ProjectTemplatesSchema = z.object({
|
||||
orgId: z.string().uuid(),
|
||||
createdAt: z.date(),
|
||||
updatedAt: z.date(),
|
||||
type: z.string().default("secret-manager")
|
||||
type: z.string().nullable().optional()
|
||||
});
|
||||
|
||||
export type TProjectTemplates = z.infer<typeof ProjectTemplatesSchema>;
|
||||
|
@ -25,11 +25,12 @@ export const ProjectsSchema = z.object({
|
||||
kmsSecretManagerKeyId: z.string().uuid().nullable().optional(),
|
||||
kmsSecretManagerEncryptedDataKey: zodBuffer.nullable().optional(),
|
||||
description: z.string().nullable().optional(),
|
||||
type: z.string(),
|
||||
type: z.string().nullable().optional(),
|
||||
enforceCapitalization: z.boolean().default(false),
|
||||
hasDeleteProtection: z.boolean().default(false).nullable().optional(),
|
||||
secretSharing: z.boolean().default(true),
|
||||
showSnapshotsLegacy: z.boolean().default(false)
|
||||
showSnapshotsLegacy: z.boolean().default(false),
|
||||
defaultProduct: z.string().default("secret-manager")
|
||||
});
|
||||
|
||||
export type TProjects = z.infer<typeof ProjectsSchema>;
|
||||
|
@ -34,7 +34,8 @@ export const SuperAdminSchema = z.object({
|
||||
encryptedGitHubAppConnectionClientSecret: zodBuffer.nullable().optional(),
|
||||
encryptedGitHubAppConnectionSlug: zodBuffer.nullable().optional(),
|
||||
encryptedGitHubAppConnectionId: zodBuffer.nullable().optional(),
|
||||
encryptedGitHubAppConnectionPrivateKey: zodBuffer.nullable().optional()
|
||||
encryptedGitHubAppConnectionPrivateKey: zodBuffer.nullable().optional(),
|
||||
encryptedEnvOverrides: zodBuffer.nullable().optional()
|
||||
});
|
||||
|
||||
export type TSuperAdmin = z.infer<typeof SuperAdminSchema>;
|
||||
|
@ -60,7 +60,8 @@ export const registerAccessApprovalRequestRouter = async (server: FastifyZodProv
|
||||
method: "GET",
|
||||
schema: {
|
||||
querystring: z.object({
|
||||
projectSlug: z.string().trim()
|
||||
projectSlug: z.string().trim(),
|
||||
policyId: z.string().trim().optional()
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
@ -73,6 +74,7 @@ export const registerAccessApprovalRequestRouter = async (server: FastifyZodProv
|
||||
handler: async (req) => {
|
||||
const { count } = await server.services.accessApprovalRequest.getCount({
|
||||
projectSlug: req.query.projectSlug,
|
||||
policyId: req.query.policyId,
|
||||
actor: req.permission.type,
|
||||
actorId: req.permission.id,
|
||||
actorOrgId: req.permission.orgId,
|
||||
|
@ -17,6 +17,7 @@ import { z } from "zod";
|
||||
import { LdapGroupMapsSchema } from "@app/db/schemas";
|
||||
import { TLDAPConfig } from "@app/ee/services/ldap-config/ldap-config-types";
|
||||
import { isValidLdapFilter, searchGroups } from "@app/ee/services/ldap-config/ldap-fns";
|
||||
import { ApiDocsTags, LdapSso } from "@app/lib/api-docs";
|
||||
import { getConfig } from "@app/lib/config/env";
|
||||
import { BadRequestError } from "@app/lib/errors";
|
||||
import { logger } from "@app/lib/logger";
|
||||
@ -132,10 +133,18 @@ export const registerLdapRouter = async (server: FastifyZodProvider) => {
|
||||
config: {
|
||||
rateLimit: readLimit
|
||||
},
|
||||
onRequest: verifyAuth([AuthMode.JWT]),
|
||||
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||
schema: {
|
||||
hide: false,
|
||||
tags: [ApiDocsTags.LdapSso],
|
||||
description: "Get LDAP config",
|
||||
security: [
|
||||
{
|
||||
bearerAuth: []
|
||||
}
|
||||
],
|
||||
querystring: z.object({
|
||||
organizationId: z.string().trim()
|
||||
organizationId: z.string().trim().describe(LdapSso.GET_CONFIG.organizationId)
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
@ -172,23 +181,32 @@ export const registerLdapRouter = async (server: FastifyZodProvider) => {
|
||||
config: {
|
||||
rateLimit: writeLimit
|
||||
},
|
||||
onRequest: verifyAuth([AuthMode.JWT]),
|
||||
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||
schema: {
|
||||
hide: false,
|
||||
tags: [ApiDocsTags.LdapSso],
|
||||
description: "Create LDAP config",
|
||||
security: [
|
||||
{
|
||||
bearerAuth: []
|
||||
}
|
||||
],
|
||||
body: z.object({
|
||||
organizationId: z.string().trim(),
|
||||
isActive: z.boolean(),
|
||||
url: z.string().trim(),
|
||||
bindDN: z.string().trim(),
|
||||
bindPass: z.string().trim(),
|
||||
uniqueUserAttribute: z.string().trim().default("uidNumber"),
|
||||
searchBase: z.string().trim(),
|
||||
searchFilter: z.string().trim().default("(uid={{username}})"),
|
||||
groupSearchBase: z.string().trim(),
|
||||
organizationId: z.string().trim().describe(LdapSso.CREATE_CONFIG.organizationId),
|
||||
isActive: z.boolean().describe(LdapSso.CREATE_CONFIG.isActive),
|
||||
url: z.string().trim().describe(LdapSso.CREATE_CONFIG.url),
|
||||
bindDN: z.string().trim().describe(LdapSso.CREATE_CONFIG.bindDN),
|
||||
bindPass: z.string().trim().describe(LdapSso.CREATE_CONFIG.bindPass),
|
||||
uniqueUserAttribute: z.string().trim().default("uidNumber").describe(LdapSso.CREATE_CONFIG.uniqueUserAttribute),
|
||||
searchBase: z.string().trim().describe(LdapSso.CREATE_CONFIG.searchBase),
|
||||
searchFilter: z.string().trim().default("(uid={{username}})").describe(LdapSso.CREATE_CONFIG.searchFilter),
|
||||
groupSearchBase: z.string().trim().describe(LdapSso.CREATE_CONFIG.groupSearchBase),
|
||||
groupSearchFilter: z
|
||||
.string()
|
||||
.trim()
|
||||
.default("(|(memberUid={{.Username}})(member={{.UserDN}})(uniqueMember={{.UserDN}}))"),
|
||||
caCert: z.string().trim().default("")
|
||||
.default("(|(memberUid={{.Username}})(member={{.UserDN}})(uniqueMember={{.UserDN}}))")
|
||||
.describe(LdapSso.CREATE_CONFIG.groupSearchFilter),
|
||||
caCert: z.string().trim().default("").describe(LdapSso.CREATE_CONFIG.caCert)
|
||||
}),
|
||||
response: {
|
||||
200: SanitizedLdapConfigSchema
|
||||
@ -214,23 +232,31 @@ export const registerLdapRouter = async (server: FastifyZodProvider) => {
|
||||
config: {
|
||||
rateLimit: writeLimit
|
||||
},
|
||||
onRequest: verifyAuth([AuthMode.JWT]),
|
||||
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||
schema: {
|
||||
hide: false,
|
||||
tags: [ApiDocsTags.LdapSso],
|
||||
description: "Update LDAP config",
|
||||
security: [
|
||||
{
|
||||
bearerAuth: []
|
||||
}
|
||||
],
|
||||
body: z
|
||||
.object({
|
||||
isActive: z.boolean(),
|
||||
url: z.string().trim(),
|
||||
bindDN: z.string().trim(),
|
||||
bindPass: z.string().trim(),
|
||||
uniqueUserAttribute: z.string().trim(),
|
||||
searchBase: z.string().trim(),
|
||||
searchFilter: z.string().trim(),
|
||||
groupSearchBase: z.string().trim(),
|
||||
groupSearchFilter: z.string().trim(),
|
||||
caCert: z.string().trim()
|
||||
isActive: z.boolean().describe(LdapSso.UPDATE_CONFIG.isActive),
|
||||
url: z.string().trim().describe(LdapSso.UPDATE_CONFIG.url),
|
||||
bindDN: z.string().trim().describe(LdapSso.UPDATE_CONFIG.bindDN),
|
||||
bindPass: z.string().trim().describe(LdapSso.UPDATE_CONFIG.bindPass),
|
||||
uniqueUserAttribute: z.string().trim().describe(LdapSso.UPDATE_CONFIG.uniqueUserAttribute),
|
||||
searchBase: z.string().trim().describe(LdapSso.UPDATE_CONFIG.searchBase),
|
||||
searchFilter: z.string().trim().describe(LdapSso.UPDATE_CONFIG.searchFilter),
|
||||
groupSearchBase: z.string().trim().describe(LdapSso.UPDATE_CONFIG.groupSearchBase),
|
||||
groupSearchFilter: z.string().trim().describe(LdapSso.UPDATE_CONFIG.groupSearchFilter),
|
||||
caCert: z.string().trim().describe(LdapSso.UPDATE_CONFIG.caCert)
|
||||
})
|
||||
.partial()
|
||||
.merge(z.object({ organizationId: z.string() })),
|
||||
.merge(z.object({ organizationId: z.string().trim().describe(LdapSso.UPDATE_CONFIG.organizationId) })),
|
||||
response: {
|
||||
200: SanitizedLdapConfigSchema
|
||||
}
|
||||
|
@ -13,6 +13,7 @@ import { z } from "zod";
|
||||
|
||||
import { OidcConfigsSchema } from "@app/db/schemas";
|
||||
import { OIDCConfigurationType, OIDCJWTSignatureAlgorithm } from "@app/ee/services/oidc/oidc-config-types";
|
||||
import { ApiDocsTags, OidcSSo } from "@app/lib/api-docs";
|
||||
import { getConfig } from "@app/lib/config/env";
|
||||
import { authRateLimit, readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
||||
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||
@ -153,10 +154,18 @@ export const registerOidcRouter = async (server: FastifyZodProvider) => {
|
||||
config: {
|
||||
rateLimit: readLimit
|
||||
},
|
||||
onRequest: verifyAuth([AuthMode.JWT]),
|
||||
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||
schema: {
|
||||
hide: false,
|
||||
tags: [ApiDocsTags.OidcSso],
|
||||
description: "Get OIDC config",
|
||||
security: [
|
||||
{
|
||||
bearerAuth: []
|
||||
}
|
||||
],
|
||||
querystring: z.object({
|
||||
orgSlug: z.string().trim()
|
||||
organizationId: z.string().trim().describe(OidcSSo.GET_CONFIG.organizationId)
|
||||
}),
|
||||
response: {
|
||||
200: SanitizedOidcConfigSchema.pick({
|
||||
@ -180,9 +189,8 @@ export const registerOidcRouter = async (server: FastifyZodProvider) => {
|
||||
}
|
||||
},
|
||||
handler: async (req) => {
|
||||
const { orgSlug } = req.query;
|
||||
const oidc = await server.services.oidc.getOidc({
|
||||
orgSlug,
|
||||
organizationId: req.query.organizationId,
|
||||
type: "external",
|
||||
actor: req.permission.type,
|
||||
actorId: req.permission.id,
|
||||
@ -200,8 +208,16 @@ export const registerOidcRouter = async (server: FastifyZodProvider) => {
|
||||
config: {
|
||||
rateLimit: writeLimit
|
||||
},
|
||||
onRequest: verifyAuth([AuthMode.JWT]),
|
||||
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||
schema: {
|
||||
hide: false,
|
||||
tags: [ApiDocsTags.OidcSso],
|
||||
description: "Update OIDC config",
|
||||
security: [
|
||||
{
|
||||
bearerAuth: []
|
||||
}
|
||||
],
|
||||
body: z
|
||||
.object({
|
||||
allowedEmailDomains: z
|
||||
@ -216,22 +232,26 @@ export const registerOidcRouter = async (server: FastifyZodProvider) => {
|
||||
.split(",")
|
||||
.map((id) => id.trim())
|
||||
.join(", ");
|
||||
}),
|
||||
discoveryURL: z.string().trim(),
|
||||
configurationType: z.nativeEnum(OIDCConfigurationType),
|
||||
issuer: z.string().trim(),
|
||||
authorizationEndpoint: z.string().trim(),
|
||||
jwksUri: z.string().trim(),
|
||||
tokenEndpoint: z.string().trim(),
|
||||
userinfoEndpoint: z.string().trim(),
|
||||
clientId: z.string().trim(),
|
||||
clientSecret: z.string().trim(),
|
||||
isActive: z.boolean(),
|
||||
manageGroupMemberships: z.boolean().optional(),
|
||||
jwtSignatureAlgorithm: z.nativeEnum(OIDCJWTSignatureAlgorithm).optional()
|
||||
})
|
||||
.describe(OidcSSo.UPDATE_CONFIG.allowedEmailDomains),
|
||||
discoveryURL: z.string().trim().describe(OidcSSo.UPDATE_CONFIG.discoveryURL),
|
||||
configurationType: z.nativeEnum(OIDCConfigurationType).describe(OidcSSo.UPDATE_CONFIG.configurationType),
|
||||
issuer: z.string().trim().describe(OidcSSo.UPDATE_CONFIG.issuer),
|
||||
authorizationEndpoint: z.string().trim().describe(OidcSSo.UPDATE_CONFIG.authorizationEndpoint),
|
||||
jwksUri: z.string().trim().describe(OidcSSo.UPDATE_CONFIG.jwksUri),
|
||||
tokenEndpoint: z.string().trim().describe(OidcSSo.UPDATE_CONFIG.tokenEndpoint),
|
||||
userinfoEndpoint: z.string().trim().describe(OidcSSo.UPDATE_CONFIG.userinfoEndpoint),
|
||||
clientId: z.string().trim().describe(OidcSSo.UPDATE_CONFIG.clientId),
|
||||
clientSecret: z.string().trim().describe(OidcSSo.UPDATE_CONFIG.clientSecret),
|
||||
isActive: z.boolean().describe(OidcSSo.UPDATE_CONFIG.isActive),
|
||||
manageGroupMemberships: z.boolean().optional().describe(OidcSSo.UPDATE_CONFIG.manageGroupMemberships),
|
||||
jwtSignatureAlgorithm: z
|
||||
.nativeEnum(OIDCJWTSignatureAlgorithm)
|
||||
.optional()
|
||||
.describe(OidcSSo.UPDATE_CONFIG.jwtSignatureAlgorithm)
|
||||
})
|
||||
.partial()
|
||||
.merge(z.object({ orgSlug: z.string() })),
|
||||
.merge(z.object({ organizationId: z.string().describe(OidcSSo.UPDATE_CONFIG.organizationId) })),
|
||||
response: {
|
||||
200: SanitizedOidcConfigSchema.pick({
|
||||
id: true,
|
||||
@ -267,8 +287,16 @@ export const registerOidcRouter = async (server: FastifyZodProvider) => {
|
||||
config: {
|
||||
rateLimit: writeLimit
|
||||
},
|
||||
onRequest: verifyAuth([AuthMode.JWT]),
|
||||
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||
schema: {
|
||||
hide: false,
|
||||
tags: [ApiDocsTags.OidcSso],
|
||||
description: "Create OIDC config",
|
||||
security: [
|
||||
{
|
||||
bearerAuth: []
|
||||
}
|
||||
],
|
||||
body: z
|
||||
.object({
|
||||
allowedEmailDomains: z
|
||||
@ -283,23 +311,34 @@ export const registerOidcRouter = async (server: FastifyZodProvider) => {
|
||||
.split(",")
|
||||
.map((id) => id.trim())
|
||||
.join(", ");
|
||||
}),
|
||||
configurationType: z.nativeEnum(OIDCConfigurationType),
|
||||
issuer: z.string().trim().optional().default(""),
|
||||
discoveryURL: z.string().trim().optional().default(""),
|
||||
authorizationEndpoint: z.string().trim().optional().default(""),
|
||||
jwksUri: z.string().trim().optional().default(""),
|
||||
tokenEndpoint: z.string().trim().optional().default(""),
|
||||
userinfoEndpoint: z.string().trim().optional().default(""),
|
||||
clientId: z.string().trim(),
|
||||
clientSecret: z.string().trim(),
|
||||
isActive: z.boolean(),
|
||||
orgSlug: z.string().trim(),
|
||||
manageGroupMemberships: z.boolean().optional().default(false),
|
||||
})
|
||||
.describe(OidcSSo.CREATE_CONFIG.allowedEmailDomains),
|
||||
configurationType: z.nativeEnum(OIDCConfigurationType).describe(OidcSSo.CREATE_CONFIG.configurationType),
|
||||
issuer: z.string().trim().optional().default("").describe(OidcSSo.CREATE_CONFIG.issuer),
|
||||
discoveryURL: z.string().trim().optional().default("").describe(OidcSSo.CREATE_CONFIG.discoveryURL),
|
||||
authorizationEndpoint: z
|
||||
.string()
|
||||
.trim()
|
||||
.optional()
|
||||
.default("")
|
||||
.describe(OidcSSo.CREATE_CONFIG.authorizationEndpoint),
|
||||
jwksUri: z.string().trim().optional().default("").describe(OidcSSo.CREATE_CONFIG.jwksUri),
|
||||
tokenEndpoint: z.string().trim().optional().default("").describe(OidcSSo.CREATE_CONFIG.tokenEndpoint),
|
||||
userinfoEndpoint: z.string().trim().optional().default("").describe(OidcSSo.CREATE_CONFIG.userinfoEndpoint),
|
||||
clientId: z.string().trim().describe(OidcSSo.CREATE_CONFIG.clientId),
|
||||
clientSecret: z.string().trim().describe(OidcSSo.CREATE_CONFIG.clientSecret),
|
||||
isActive: z.boolean().describe(OidcSSo.CREATE_CONFIG.isActive),
|
||||
organizationId: z.string().trim().describe(OidcSSo.CREATE_CONFIG.organizationId),
|
||||
manageGroupMemberships: z
|
||||
.boolean()
|
||||
.optional()
|
||||
.default(false)
|
||||
.describe(OidcSSo.CREATE_CONFIG.manageGroupMemberships),
|
||||
jwtSignatureAlgorithm: z
|
||||
.nativeEnum(OIDCJWTSignatureAlgorithm)
|
||||
.optional()
|
||||
.default(OIDCJWTSignatureAlgorithm.RS256)
|
||||
.describe(OidcSSo.CREATE_CONFIG.jwtSignatureAlgorithm)
|
||||
})
|
||||
.superRefine((data, ctx) => {
|
||||
if (data.configurationType === OIDCConfigurationType.CUSTOM) {
|
||||
|
@ -111,15 +111,38 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
|
||||
params: z.object({
|
||||
workspaceId: z.string().trim().describe(AUDIT_LOGS.EXPORT.projectId)
|
||||
}),
|
||||
querystring: z.object({
|
||||
eventType: z.nativeEnum(EventType).optional().describe(AUDIT_LOGS.EXPORT.eventType),
|
||||
userAgentType: z.nativeEnum(UserAgentType).optional().describe(AUDIT_LOGS.EXPORT.userAgentType),
|
||||
startDate: z.string().datetime().optional().describe(AUDIT_LOGS.EXPORT.startDate),
|
||||
endDate: z.string().datetime().optional().describe(AUDIT_LOGS.EXPORT.endDate),
|
||||
offset: z.coerce.number().default(0).describe(AUDIT_LOGS.EXPORT.offset),
|
||||
limit: z.coerce.number().default(20).describe(AUDIT_LOGS.EXPORT.limit),
|
||||
actor: z.string().optional().describe(AUDIT_LOGS.EXPORT.actor)
|
||||
}),
|
||||
querystring: z
|
||||
.object({
|
||||
eventType: z.nativeEnum(EventType).optional().describe(AUDIT_LOGS.EXPORT.eventType),
|
||||
userAgentType: z.nativeEnum(UserAgentType).optional().describe(AUDIT_LOGS.EXPORT.userAgentType),
|
||||
startDate: z.string().datetime().optional().describe(AUDIT_LOGS.EXPORT.startDate),
|
||||
endDate: z.string().datetime().optional().describe(AUDIT_LOGS.EXPORT.endDate),
|
||||
offset: z.coerce.number().default(0).describe(AUDIT_LOGS.EXPORT.offset),
|
||||
limit: z.coerce.number().max(1000).default(20).describe(AUDIT_LOGS.EXPORT.limit),
|
||||
actor: z.string().optional().describe(AUDIT_LOGS.EXPORT.actor)
|
||||
})
|
||||
.superRefine((el, ctx) => {
|
||||
if (el.endDate && el.startDate) {
|
||||
const startDate = new Date(el.startDate);
|
||||
const endDate = new Date(el.endDate);
|
||||
const maxAllowedDate = new Date(startDate);
|
||||
maxAllowedDate.setMonth(maxAllowedDate.getMonth() + 3);
|
||||
if (endDate < startDate) {
|
||||
ctx.addIssue({
|
||||
code: z.ZodIssueCode.custom,
|
||||
path: ["endDate"],
|
||||
message: "End date cannot be before start date"
|
||||
});
|
||||
}
|
||||
if (endDate > maxAllowedDate) {
|
||||
ctx.addIssue({
|
||||
code: z.ZodIssueCode.custom,
|
||||
path: ["endDate"],
|
||||
message: "Dates must be within 3 months"
|
||||
});
|
||||
}
|
||||
}
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
auditLogs: AuditLogsSchema.omit({
|
||||
@ -161,7 +184,7 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
|
||||
filter: {
|
||||
...req.query,
|
||||
projectId: req.params.workspaceId,
|
||||
endDate: req.query.endDate,
|
||||
endDate: req.query.endDate || new Date().toISOString(),
|
||||
startDate: req.query.startDate || getLastMidnightDateISO(),
|
||||
auditLogActorId: req.query.actor,
|
||||
eventType: req.query.eventType ? [req.query.eventType] : undefined
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { z } from "zod";
|
||||
|
||||
import { ProjectMembershipRole, ProjectTemplatesSchema, ProjectType } from "@app/db/schemas";
|
||||
import { ProjectMembershipRole, ProjectTemplatesSchema } from "@app/db/schemas";
|
||||
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
|
||||
import { ProjectPermissionV2Schema } from "@app/ee/services/permission/project-permission";
|
||||
import { isInfisicalProjectTemplate } from "@app/ee/services/project-template/project-template-fns";
|
||||
@ -104,9 +104,6 @@ export const registerProjectTemplateRouter = async (server: FastifyZodProvider)
|
||||
hide: false,
|
||||
tags: [ApiDocsTags.ProjectTemplates],
|
||||
description: "List project templates for the current organization.",
|
||||
querystring: z.object({
|
||||
type: z.nativeEnum(ProjectType).optional().describe(ProjectTemplates.LIST.type)
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
projectTemplates: SanitizedProjectTemplateSchema.array()
|
||||
@ -115,8 +112,7 @@ export const registerProjectTemplateRouter = async (server: FastifyZodProvider)
|
||||
},
|
||||
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||
handler: async (req) => {
|
||||
const { type } = req.query;
|
||||
const projectTemplates = await server.services.projectTemplate.listProjectTemplatesByOrg(req.permission, type);
|
||||
const projectTemplates = await server.services.projectTemplate.listProjectTemplatesByOrg(req.permission);
|
||||
|
||||
const auditTemplates = projectTemplates.filter((template) => !isInfisicalProjectTemplate(template.name));
|
||||
|
||||
@ -188,7 +184,6 @@ export const registerProjectTemplateRouter = async (server: FastifyZodProvider)
|
||||
tags: [ApiDocsTags.ProjectTemplates],
|
||||
description: "Create a project template.",
|
||||
body: z.object({
|
||||
type: z.nativeEnum(ProjectType).describe(ProjectTemplates.CREATE.type),
|
||||
name: slugSchema({ field: "name" })
|
||||
.refine((val) => !isInfisicalProjectTemplate(val), {
|
||||
message: `The requested project template name is reserved.`
|
||||
@ -284,7 +279,6 @@ export const registerProjectTemplateRouter = async (server: FastifyZodProvider)
|
||||
tags: [ApiDocsTags.ProjectTemplates],
|
||||
description: "Delete a project template.",
|
||||
params: z.object({ templateId: z.string().uuid().describe(ProjectTemplates.DELETE.templateId) }),
|
||||
|
||||
response: {
|
||||
200: z.object({
|
||||
projectTemplate: SanitizedProjectTemplateSchema
|
||||
|
@ -13,6 +13,7 @@ import { FastifyRequest } from "fastify";
|
||||
import { z } from "zod";
|
||||
|
||||
import { SamlProviders, TGetSamlCfgDTO } from "@app/ee/services/saml-config/saml-config-types";
|
||||
import { ApiDocsTags, SamlSso } from "@app/lib/api-docs";
|
||||
import { getConfig } from "@app/lib/config/env";
|
||||
import { BadRequestError } from "@app/lib/errors";
|
||||
import { logger } from "@app/lib/logger";
|
||||
@ -149,8 +150,8 @@ export const registerSamlRouter = async (server: FastifyZodProvider) => {
|
||||
firstName,
|
||||
lastName: lastName as string,
|
||||
relayState: (req.body as { RelayState?: string }).RelayState,
|
||||
authProvider: (req as unknown as FastifyRequest).ssoConfig?.authProvider as string,
|
||||
orgId: (req as unknown as FastifyRequest).ssoConfig?.orgId as string,
|
||||
authProvider: (req as unknown as FastifyRequest).ssoConfig?.authProvider,
|
||||
orgId: (req as unknown as FastifyRequest).ssoConfig?.orgId,
|
||||
metadata: userMetadata
|
||||
});
|
||||
cb(null, { isUserCompleted, providerAuthToken });
|
||||
@ -262,25 +263,31 @@ export const registerSamlRouter = async (server: FastifyZodProvider) => {
|
||||
config: {
|
||||
rateLimit: readLimit
|
||||
},
|
||||
onRequest: verifyAuth([AuthMode.JWT]),
|
||||
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||
schema: {
|
||||
hide: false,
|
||||
tags: [ApiDocsTags.SamlSso],
|
||||
description: "Get SAML config",
|
||||
security: [
|
||||
{
|
||||
bearerAuth: []
|
||||
}
|
||||
],
|
||||
querystring: z.object({
|
||||
organizationId: z.string().trim()
|
||||
organizationId: z.string().trim().describe(SamlSso.GET_CONFIG.organizationId)
|
||||
}),
|
||||
response: {
|
||||
200: z
|
||||
.object({
|
||||
id: z.string(),
|
||||
organization: z.string(),
|
||||
orgId: z.string(),
|
||||
authProvider: z.string(),
|
||||
isActive: z.boolean(),
|
||||
entryPoint: z.string(),
|
||||
issuer: z.string(),
|
||||
cert: z.string(),
|
||||
lastUsed: z.date().nullable().optional()
|
||||
})
|
||||
.optional()
|
||||
200: z.object({
|
||||
id: z.string(),
|
||||
organization: z.string(),
|
||||
orgId: z.string(),
|
||||
authProvider: z.string(),
|
||||
isActive: z.boolean(),
|
||||
entryPoint: z.string(),
|
||||
issuer: z.string(),
|
||||
cert: z.string(),
|
||||
lastUsed: z.date().nullable().optional()
|
||||
})
|
||||
}
|
||||
},
|
||||
handler: async (req) => {
|
||||
@ -302,15 +309,23 @@ export const registerSamlRouter = async (server: FastifyZodProvider) => {
|
||||
config: {
|
||||
rateLimit: writeLimit
|
||||
},
|
||||
onRequest: verifyAuth([AuthMode.JWT]),
|
||||
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||
schema: {
|
||||
hide: false,
|
||||
tags: [ApiDocsTags.SamlSso],
|
||||
description: "Create SAML config",
|
||||
security: [
|
||||
{
|
||||
bearerAuth: []
|
||||
}
|
||||
],
|
||||
body: z.object({
|
||||
organizationId: z.string(),
|
||||
authProvider: z.nativeEnum(SamlProviders),
|
||||
isActive: z.boolean(),
|
||||
entryPoint: z.string(),
|
||||
issuer: z.string(),
|
||||
cert: z.string()
|
||||
organizationId: z.string().trim().describe(SamlSso.CREATE_CONFIG.organizationId),
|
||||
authProvider: z.nativeEnum(SamlProviders).describe(SamlSso.CREATE_CONFIG.authProvider),
|
||||
isActive: z.boolean().describe(SamlSso.CREATE_CONFIG.isActive),
|
||||
entryPoint: z.string().trim().describe(SamlSso.CREATE_CONFIG.entryPoint),
|
||||
issuer: z.string().trim().describe(SamlSso.CREATE_CONFIG.issuer),
|
||||
cert: z.string().trim().describe(SamlSso.CREATE_CONFIG.cert)
|
||||
}),
|
||||
response: {
|
||||
200: SanitizedSamlConfigSchema
|
||||
@ -341,18 +356,26 @@ export const registerSamlRouter = async (server: FastifyZodProvider) => {
|
||||
config: {
|
||||
rateLimit: writeLimit
|
||||
},
|
||||
onRequest: verifyAuth([AuthMode.JWT]),
|
||||
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||
schema: {
|
||||
hide: false,
|
||||
tags: [ApiDocsTags.SamlSso],
|
||||
description: "Update SAML config",
|
||||
security: [
|
||||
{
|
||||
bearerAuth: []
|
||||
}
|
||||
],
|
||||
body: z
|
||||
.object({
|
||||
authProvider: z.nativeEnum(SamlProviders),
|
||||
isActive: z.boolean(),
|
||||
entryPoint: z.string(),
|
||||
issuer: z.string(),
|
||||
cert: z.string()
|
||||
authProvider: z.nativeEnum(SamlProviders).describe(SamlSso.UPDATE_CONFIG.authProvider),
|
||||
isActive: z.boolean().describe(SamlSso.UPDATE_CONFIG.isActive),
|
||||
entryPoint: z.string().trim().describe(SamlSso.UPDATE_CONFIG.entryPoint),
|
||||
issuer: z.string().trim().describe(SamlSso.UPDATE_CONFIG.issuer),
|
||||
cert: z.string().trim().describe(SamlSso.UPDATE_CONFIG.cert)
|
||||
})
|
||||
.partial()
|
||||
.merge(z.object({ organizationId: z.string() })),
|
||||
.merge(z.object({ organizationId: z.string().trim().describe(SamlSso.UPDATE_CONFIG.organizationId) })),
|
||||
response: {
|
||||
200: SanitizedSamlConfigSchema
|
||||
}
|
||||
|
@ -94,7 +94,8 @@ export const registerSecretApprovalRequestRouter = async (server: FastifyZodProv
|
||||
},
|
||||
schema: {
|
||||
querystring: z.object({
|
||||
workspaceId: z.string().trim()
|
||||
workspaceId: z.string().trim(),
|
||||
policyId: z.string().trim().optional()
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
@ -112,7 +113,8 @@ export const registerSecretApprovalRequestRouter = async (server: FastifyZodProv
|
||||
actorId: req.permission.id,
|
||||
actorAuthMethod: req.permission.authMethod,
|
||||
actorOrgId: req.permission.orgId,
|
||||
projectId: req.query.workspaceId
|
||||
projectId: req.query.workspaceId,
|
||||
policyId: req.query.policyId
|
||||
});
|
||||
return { approvals };
|
||||
}
|
||||
@ -139,14 +141,39 @@ export const registerSecretApprovalRequestRouter = async (server: FastifyZodProv
|
||||
},
|
||||
onRequest: verifyAuth([AuthMode.JWT]),
|
||||
handler: async (req) => {
|
||||
const { approval } = await server.services.secretApprovalRequest.mergeSecretApprovalRequest({
|
||||
actorId: req.permission.id,
|
||||
actor: req.permission.type,
|
||||
actorAuthMethod: req.permission.authMethod,
|
||||
actorOrgId: req.permission.orgId,
|
||||
approvalId: req.params.id,
|
||||
bypassReason: req.body.bypassReason
|
||||
const { approval, projectId, secretMutationEvents } =
|
||||
await server.services.secretApprovalRequest.mergeSecretApprovalRequest({
|
||||
actorId: req.permission.id,
|
||||
actor: req.permission.type,
|
||||
actorAuthMethod: req.permission.authMethod,
|
||||
actorOrgId: req.permission.orgId,
|
||||
approvalId: req.params.id,
|
||||
bypassReason: req.body.bypassReason
|
||||
});
|
||||
|
||||
await server.services.auditLog.createAuditLog({
|
||||
...req.auditLogInfo,
|
||||
orgId: req.permission.orgId,
|
||||
projectId,
|
||||
event: {
|
||||
type: EventType.SECRET_APPROVAL_MERGED,
|
||||
metadata: {
|
||||
mergedBy: req.permission.id,
|
||||
secretApprovalRequestSlug: approval.slug,
|
||||
secretApprovalRequestId: approval.id
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
for await (const event of secretMutationEvents) {
|
||||
await server.services.auditLog.createAuditLog({
|
||||
...req.auditLogInfo,
|
||||
orgId: req.permission.orgId,
|
||||
projectId,
|
||||
event
|
||||
});
|
||||
}
|
||||
|
||||
return { approval };
|
||||
}
|
||||
});
|
||||
|
@ -80,6 +80,7 @@ export const registerSshCertRouter = async (server: FastifyZodProvider) => {
|
||||
await server.services.telemetry.sendPostHogEvents({
|
||||
event: PostHogEventTypes.SignSshKey,
|
||||
distinctId: getTelemetryDistinctId(req),
|
||||
organizationId: req.permission.orgId,
|
||||
properties: {
|
||||
certificateTemplateId: req.body.certificateTemplateId,
|
||||
principals: req.body.principals,
|
||||
@ -171,6 +172,7 @@ export const registerSshCertRouter = async (server: FastifyZodProvider) => {
|
||||
await server.services.telemetry.sendPostHogEvents({
|
||||
event: PostHogEventTypes.IssueSshCreds,
|
||||
distinctId: getTelemetryDistinctId(req),
|
||||
organizationId: req.permission.orgId,
|
||||
properties: {
|
||||
certificateTemplateId: req.body.certificateTemplateId,
|
||||
principals: req.body.principals,
|
||||
|
@ -358,6 +358,7 @@ export const registerSshHostRouter = async (server: FastifyZodProvider) => {
|
||||
await server.services.telemetry.sendPostHogEvents({
|
||||
event: PostHogEventTypes.IssueSshHostUserCert,
|
||||
distinctId: getTelemetryDistinctId(req),
|
||||
organizationId: req.permission.orgId,
|
||||
properties: {
|
||||
sshHostId: req.params.sshHostId,
|
||||
hostname: host.hostname,
|
||||
@ -427,6 +428,7 @@ export const registerSshHostRouter = async (server: FastifyZodProvider) => {
|
||||
|
||||
await server.services.telemetry.sendPostHogEvents({
|
||||
event: PostHogEventTypes.IssueSshHostHostCert,
|
||||
organizationId: req.permission.orgId,
|
||||
distinctId: getTelemetryDistinctId(req),
|
||||
properties: {
|
||||
sshHostId: req.params.sshHostId,
|
||||
|
@ -0,0 +1,16 @@
|
||||
import { registerSecretScanningEndpoints } from "@app/ee/routes/v2/secret-scanning-v2-routers/secret-scanning-v2-endpoints";
|
||||
import {
|
||||
BitbucketDataSourceSchema,
|
||||
CreateBitbucketDataSourceSchema,
|
||||
UpdateBitbucketDataSourceSchema
|
||||
} from "@app/ee/services/secret-scanning-v2/bitbucket";
|
||||
import { SecretScanningDataSource } from "@app/ee/services/secret-scanning-v2/secret-scanning-v2-enums";
|
||||
|
||||
export const registerBitbucketSecretScanningRouter = async (server: FastifyZodProvider) =>
|
||||
registerSecretScanningEndpoints({
|
||||
type: SecretScanningDataSource.Bitbucket,
|
||||
server,
|
||||
responseSchema: BitbucketDataSourceSchema,
|
||||
createSchema: CreateBitbucketDataSourceSchema,
|
||||
updateSchema: UpdateBitbucketDataSourceSchema
|
||||
});
|
@ -1,5 +1,6 @@
|
||||
import { SecretScanningDataSource } from "@app/ee/services/secret-scanning-v2/secret-scanning-v2-enums";
|
||||
|
||||
import { registerBitbucketSecretScanningRouter } from "./bitbucket-secret-scanning-router";
|
||||
import { registerGitHubSecretScanningRouter } from "./github-secret-scanning-router";
|
||||
|
||||
export * from "./secret-scanning-v2-router";
|
||||
@ -8,5 +9,6 @@ export const SECRET_SCANNING_REGISTER_ROUTER_MAP: Record<
|
||||
SecretScanningDataSource,
|
||||
(server: FastifyZodProvider) => Promise<void>
|
||||
> = {
|
||||
[SecretScanningDataSource.GitHub]: registerGitHubSecretScanningRouter
|
||||
[SecretScanningDataSource.GitHub]: registerGitHubSecretScanningRouter,
|
||||
[SecretScanningDataSource.Bitbucket]: registerBitbucketSecretScanningRouter
|
||||
};
|
||||
|
@ -2,6 +2,7 @@ import { z } from "zod";
|
||||
|
||||
import { SecretScanningConfigsSchema } from "@app/db/schemas";
|
||||
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
|
||||
import { BitbucketDataSourceListItemSchema } from "@app/ee/services/secret-scanning-v2/bitbucket";
|
||||
import { GitHubDataSourceListItemSchema } from "@app/ee/services/secret-scanning-v2/github";
|
||||
import {
|
||||
SecretScanningFindingStatus,
|
||||
@ -21,7 +22,10 @@ import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
||||
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||
import { AuthMode } from "@app/services/auth/auth-type";
|
||||
|
||||
const SecretScanningDataSourceOptionsSchema = z.discriminatedUnion("type", [GitHubDataSourceListItemSchema]);
|
||||
const SecretScanningDataSourceOptionsSchema = z.discriminatedUnion("type", [
|
||||
GitHubDataSourceListItemSchema,
|
||||
BitbucketDataSourceListItemSchema
|
||||
]);
|
||||
|
||||
export const registerSecretScanningV2Router = async (server: FastifyZodProvider) => {
|
||||
server.route({
|
||||
|
@ -1,6 +1,5 @@
|
||||
import { ForbiddenError } from "@casl/ability";
|
||||
|
||||
import { ActionProjectType } from "@app/db/schemas";
|
||||
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
|
||||
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
|
||||
import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors";
|
||||
@ -97,8 +96,7 @@ export const accessApprovalPolicyServiceFactory = ({
|
||||
actorId,
|
||||
projectId: project.id,
|
||||
actorAuthMethod,
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.SecretManager
|
||||
actorOrgId
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
@ -248,8 +246,7 @@ export const accessApprovalPolicyServiceFactory = ({
|
||||
actorId,
|
||||
projectId: project.id,
|
||||
actorAuthMethod,
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.SecretManager
|
||||
actorOrgId
|
||||
});
|
||||
|
||||
const accessApprovalPolicies = await accessApprovalPolicyDAL.find({ projectId: project.id, deletedAt: null });
|
||||
@ -301,8 +298,7 @@ export const accessApprovalPolicyServiceFactory = ({
|
||||
actorId,
|
||||
projectId: accessApprovalPolicy.projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.SecretManager
|
||||
actorOrgId
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.SecretApproval);
|
||||
@ -498,8 +494,7 @@ export const accessApprovalPolicyServiceFactory = ({
|
||||
actorId,
|
||||
projectId: policy.projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.SecretManager
|
||||
actorOrgId
|
||||
});
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionActions.Delete,
|
||||
@ -549,8 +544,7 @@ export const accessApprovalPolicyServiceFactory = ({
|
||||
actorId,
|
||||
projectId: project.id,
|
||||
actorAuthMethod,
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.SecretManager
|
||||
actorOrgId
|
||||
});
|
||||
if (!membership) {
|
||||
throw new ForbiddenRequestError({ message: "You are not a member of this project" });
|
||||
@ -589,8 +583,7 @@ export const accessApprovalPolicyServiceFactory = ({
|
||||
actorId,
|
||||
projectId: policy.projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.SecretManager
|
||||
actorOrgId
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.SecretApproval);
|
||||
|
@ -220,7 +220,7 @@ export interface TAccessApprovalRequestDALFactory extends Omit<TOrmify<TableName
|
||||
bypassers: string[];
|
||||
}[]
|
||||
>;
|
||||
getCount: ({ projectId }: { projectId: string }) => Promise<{
|
||||
getCount: ({ projectId }: { projectId: string; policyId?: string }) => Promise<{
|
||||
pendingCount: number;
|
||||
finalizedCount: number;
|
||||
}>;
|
||||
@ -702,7 +702,7 @@ export const accessApprovalRequestDALFactory = (db: TDbClient): TAccessApprovalR
|
||||
}
|
||||
};
|
||||
|
||||
const getCount: TAccessApprovalRequestDALFactory["getCount"] = async ({ projectId }) => {
|
||||
const getCount: TAccessApprovalRequestDALFactory["getCount"] = async ({ projectId, policyId }) => {
|
||||
try {
|
||||
const accessRequests = await db
|
||||
.replicaNode()(TableName.AccessApprovalRequest)
|
||||
@ -723,8 +723,10 @@ export const accessApprovalRequestDALFactory = (db: TDbClient): TAccessApprovalR
|
||||
`${TableName.AccessApprovalRequest}.id`,
|
||||
`${TableName.AccessApprovalRequestReviewer}.requestId`
|
||||
)
|
||||
|
||||
.where(`${TableName.Environment}.projectId`, projectId)
|
||||
.where((qb) => {
|
||||
if (policyId) void qb.where(`${TableName.AccessApprovalPolicy}.id`, policyId);
|
||||
})
|
||||
.select(selectAllTableCols(TableName.AccessApprovalRequest))
|
||||
.select(db.ref("status").withSchema(TableName.AccessApprovalRequestReviewer).as("reviewerStatus"))
|
||||
.select(db.ref("reviewerUserId").withSchema(TableName.AccessApprovalRequestReviewer).as("reviewerUserId"))
|
||||
|
@ -1,7 +1,7 @@
|
||||
import slugify from "@sindresorhus/slugify";
|
||||
import msFn from "ms";
|
||||
|
||||
import { ActionProjectType, ProjectMembershipRole } from "@app/db/schemas";
|
||||
import { ProjectMembershipRole } from "@app/db/schemas";
|
||||
import { getConfig } from "@app/lib/config/env";
|
||||
import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors";
|
||||
import { groupBy } from "@app/lib/fn";
|
||||
@ -107,8 +107,7 @@ export const accessApprovalRequestServiceFactory = ({
|
||||
actorId,
|
||||
projectId: project.id,
|
||||
actorAuthMethod,
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.SecretManager
|
||||
actorOrgId
|
||||
});
|
||||
if (!membership) {
|
||||
throw new ForbiddenRequestError({ message: "You are not a member of this project" });
|
||||
@ -217,7 +216,7 @@ export const accessApprovalRequestServiceFactory = ({
|
||||
);
|
||||
|
||||
const requesterFullName = `${requestedByUser.firstName} ${requestedByUser.lastName}`;
|
||||
const approvalUrl = `${cfg.SITE_URL}/secret-manager/${project.id}/approval`;
|
||||
const approvalUrl = `${cfg.SITE_URL}/projects/${project.id}/secret-manager/approval`;
|
||||
|
||||
await triggerWorkflowIntegrationNotification({
|
||||
input: {
|
||||
@ -290,8 +289,7 @@ export const accessApprovalRequestServiceFactory = ({
|
||||
actorId,
|
||||
projectId: project.id,
|
||||
actorAuthMethod,
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.SecretManager
|
||||
actorOrgId
|
||||
});
|
||||
if (!membership) {
|
||||
throw new ForbiddenRequestError({ message: "You are not a member of this project" });
|
||||
@ -337,8 +335,7 @@ export const accessApprovalRequestServiceFactory = ({
|
||||
actorId,
|
||||
projectId: accessApprovalRequest.projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.SecretManager
|
||||
actorOrgId
|
||||
});
|
||||
|
||||
if (!membership) {
|
||||
@ -350,6 +347,12 @@ export const accessApprovalRequestServiceFactory = ({
|
||||
const canBypass = !policy.bypassers.length || policy.bypassers.some((bypasser) => bypasser.userId === actorId);
|
||||
const cannotBypassUnderSoftEnforcement = !(isSoftEnforcement && canBypass);
|
||||
|
||||
// Calculate break glass attempt before sequence checks
|
||||
const isBreakGlassApprovalAttempt =
|
||||
policy.enforcementLevel === EnforcementLevel.Soft &&
|
||||
actorId === accessApprovalRequest.requestedByUserId &&
|
||||
status === ApprovalStatus.APPROVED;
|
||||
|
||||
const isApprover = policy.approvers.find((approver) => approver.userId === actorId);
|
||||
// If user is (not an approver OR cant self approve) AND can't bypass policy
|
||||
if ((!isApprover || (!policy.allowedSelfApprovals && isSelfApproval)) && cannotBypassUnderSoftEnforcement) {
|
||||
@ -409,15 +412,14 @@ export const accessApprovalRequestServiceFactory = ({
|
||||
const isApproverOfTheSequence = policy.approvers.find(
|
||||
(el) => el.sequence === presentSequence.step && el.userId === actorId
|
||||
);
|
||||
if (!isApproverOfTheSequence) throw new BadRequestError({ message: "You are not reviewer in this step" });
|
||||
|
||||
// Only throw if actor is not the approver and not bypassing
|
||||
if (!isApproverOfTheSequence && !isBreakGlassApprovalAttempt) {
|
||||
throw new BadRequestError({ message: "You are not a reviewer in this step" });
|
||||
}
|
||||
}
|
||||
|
||||
const reviewStatus = await accessApprovalRequestReviewerDAL.transaction(async (tx) => {
|
||||
const isBreakGlassApprovalAttempt =
|
||||
policy.enforcementLevel === EnforcementLevel.Soft &&
|
||||
actorId === accessApprovalRequest.requestedByUserId &&
|
||||
status === ApprovalStatus.APPROVED;
|
||||
|
||||
let reviewForThisActorProcessing: {
|
||||
id: string;
|
||||
requestId: string;
|
||||
@ -543,7 +545,7 @@ export const accessApprovalRequestServiceFactory = ({
|
||||
bypassReason: bypassReason || "No reason provided",
|
||||
secretPath: policy.secretPath || "/",
|
||||
environment,
|
||||
approvalUrl: `${cfg.SITE_URL}/secret-manager/${project.id}/approval`,
|
||||
approvalUrl: `${cfg.SITE_URL}/projects/${project.id}/secret-manager/approval`,
|
||||
requestType: "access"
|
||||
},
|
||||
template: SmtpTemplates.AccessSecretRequestBypassed
|
||||
@ -560,6 +562,7 @@ export const accessApprovalRequestServiceFactory = ({
|
||||
|
||||
const getCount: TAccessApprovalRequestServiceFactory["getCount"] = async ({
|
||||
projectSlug,
|
||||
policyId,
|
||||
actor,
|
||||
actorAuthMethod,
|
||||
actorId,
|
||||
@ -573,14 +576,13 @@ export const accessApprovalRequestServiceFactory = ({
|
||||
actorId,
|
||||
projectId: project.id,
|
||||
actorAuthMethod,
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.SecretManager
|
||||
actorOrgId
|
||||
});
|
||||
if (!membership) {
|
||||
throw new ForbiddenRequestError({ message: "You are not a member of this project" });
|
||||
}
|
||||
|
||||
const count = await accessApprovalRequestDAL.getCount({ projectId: project.id });
|
||||
const count = await accessApprovalRequestDAL.getCount({ projectId: project.id, policyId });
|
||||
|
||||
return { count };
|
||||
};
|
||||
|
@ -12,6 +12,7 @@ export type TVerifyPermission = {
|
||||
|
||||
export type TGetAccessRequestCountDTO = {
|
||||
projectSlug: string;
|
||||
policyId?: string;
|
||||
} & Omit<TProjectPermission, "projectId">;
|
||||
|
||||
export type TReviewAccessRequestDTO = {
|
||||
|
@ -1,7 +1,6 @@
|
||||
import { ForbiddenError } from "@casl/ability";
|
||||
import jwt from "jsonwebtoken";
|
||||
|
||||
import { ActionProjectType } from "@app/db/schemas";
|
||||
import { getConfig } from "@app/lib/config/env";
|
||||
import { ForbiddenRequestError, NotFoundError } from "@app/lib/errors";
|
||||
import { ActorType } from "@app/services/auth/auth-type";
|
||||
@ -38,8 +37,7 @@ export const assumePrivilegeServiceFactory = ({
|
||||
actorId: actorPermissionDetails.id,
|
||||
projectId,
|
||||
actorAuthMethod: actorPermissionDetails.authMethod,
|
||||
actorOrgId: actorPermissionDetails.orgId,
|
||||
actionProjectType: ActionProjectType.Any
|
||||
actorOrgId: actorPermissionDetails.orgId
|
||||
});
|
||||
|
||||
if (targetActorType === ActorType.USER) {
|
||||
@ -60,8 +58,7 @@ export const assumePrivilegeServiceFactory = ({
|
||||
actorId: targetActorId,
|
||||
projectId,
|
||||
actorAuthMethod: actorPermissionDetails.authMethod,
|
||||
actorOrgId: actorPermissionDetails.orgId,
|
||||
actionProjectType: ActionProjectType.Any
|
||||
actorOrgId: actorPermissionDetails.orgId
|
||||
});
|
||||
|
||||
const appCfg = getConfig();
|
||||
|
@ -0,0 +1,21 @@
|
||||
export function providerSpecificPayload(url: string) {
|
||||
const { hostname } = new URL(url);
|
||||
|
||||
const payload: Record<string, string> = {};
|
||||
|
||||
switch (hostname) {
|
||||
case "http-intake.logs.datadoghq.com":
|
||||
case "http-intake.logs.us3.datadoghq.com":
|
||||
case "http-intake.logs.us5.datadoghq.com":
|
||||
case "http-intake.logs.datadoghq.eu":
|
||||
case "http-intake.logs.ap1.datadoghq.com":
|
||||
case "http-intake.logs.ddog-gov.com":
|
||||
payload.ddsource = "infisical";
|
||||
payload.service = "audit-logs";
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return payload;
|
||||
}
|
@ -13,6 +13,7 @@ import { TLicenseServiceFactory } from "../license/license-service";
|
||||
import { OrgPermissionActions, OrgPermissionSubjects } from "../permission/org-permission";
|
||||
import { TPermissionServiceFactory } from "../permission/permission-service-types";
|
||||
import { TAuditLogStreamDALFactory } from "./audit-log-stream-dal";
|
||||
import { providerSpecificPayload } from "./audit-log-stream-fns";
|
||||
import { LogStreamHeaders, TAuditLogStreamServiceFactory } from "./audit-log-stream-types";
|
||||
|
||||
type TAuditLogStreamServiceFactoryDep = {
|
||||
@ -69,10 +70,11 @@ export const auditLogStreamServiceFactory = ({
|
||||
headers.forEach(({ key, value }) => {
|
||||
streamHeaders[key] = value;
|
||||
});
|
||||
|
||||
await request
|
||||
.post(
|
||||
url,
|
||||
{ ping: "ok" },
|
||||
{ ...providerSpecificPayload(url), ping: "ok" },
|
||||
{
|
||||
headers: streamHeaders,
|
||||
// request timeout
|
||||
@ -137,7 +139,7 @@ export const auditLogStreamServiceFactory = ({
|
||||
await request
|
||||
.post(
|
||||
url || logStream.url,
|
||||
{ ping: "ok" },
|
||||
{ ...providerSpecificPayload(url || logStream.url), ping: "ok" },
|
||||
{
|
||||
headers: streamHeaders,
|
||||
// request timeout
|
||||
|
@ -30,10 +30,10 @@ type TFindQuery = {
|
||||
actor?: string;
|
||||
projectId?: string;
|
||||
environment?: string;
|
||||
orgId?: string;
|
||||
orgId: string;
|
||||
eventType?: string;
|
||||
startDate?: string;
|
||||
endDate?: string;
|
||||
startDate: string;
|
||||
endDate: string;
|
||||
userAgentType?: string;
|
||||
limit?: number;
|
||||
offset?: number;
|
||||
@ -61,18 +61,15 @@ export const auditLogDALFactory = (db: TDbClient) => {
|
||||
},
|
||||
tx
|
||||
) => {
|
||||
if (!orgId && !projectId) {
|
||||
throw new Error("Either orgId or projectId must be provided");
|
||||
}
|
||||
|
||||
try {
|
||||
// Find statements
|
||||
const sqlQuery = (tx || db.replicaNode())(TableName.AuditLog)
|
||||
.where(`${TableName.AuditLog}.orgId`, orgId)
|
||||
.whereRaw(`"${TableName.AuditLog}"."createdAt" >= ?::timestamptz`, [startDate])
|
||||
.andWhereRaw(`"${TableName.AuditLog}"."createdAt" < ?::timestamptz`, [endDate])
|
||||
// eslint-disable-next-line func-names
|
||||
.where(function () {
|
||||
if (orgId) {
|
||||
void this.where(`${TableName.AuditLog}.orgId`, orgId);
|
||||
} else if (projectId) {
|
||||
if (projectId) {
|
||||
void this.where(`${TableName.AuditLog}.projectId`, projectId);
|
||||
}
|
||||
});
|
||||
@ -135,14 +132,6 @@ export const auditLogDALFactory = (db: TDbClient) => {
|
||||
void sqlQuery.whereIn("eventType", eventType);
|
||||
}
|
||||
|
||||
// Filter by date range
|
||||
if (startDate) {
|
||||
void sqlQuery.whereRaw(`"${TableName.AuditLog}"."createdAt" >= ?::timestamptz`, [startDate]);
|
||||
}
|
||||
if (endDate) {
|
||||
void sqlQuery.whereRaw(`"${TableName.AuditLog}"."createdAt" <= ?::timestamptz`, [endDate]);
|
||||
}
|
||||
|
||||
// we timeout long running queries to prevent DB resource issues (2 minutes)
|
||||
const docs = await sqlQuery.timeout(1000 * 120);
|
||||
|
||||
@ -174,6 +163,8 @@ export const auditLogDALFactory = (db: TDbClient) => {
|
||||
try {
|
||||
const findExpiredLogSubQuery = (tx || db)(TableName.AuditLog)
|
||||
.where("expiresAt", "<", today)
|
||||
.where("createdAt", "<", today) // to use audit log partition
|
||||
.orderBy(`${TableName.AuditLog}.createdAt`, "desc")
|
||||
.select("id")
|
||||
.limit(AUDIT_LOG_PRUNE_BATCH_SIZE);
|
||||
|
||||
|
@ -1,13 +1,15 @@
|
||||
import { RawAxiosRequestHeaders } from "axios";
|
||||
import { AxiosError, RawAxiosRequestHeaders } from "axios";
|
||||
|
||||
import { SecretKeyEncoding } from "@app/db/schemas";
|
||||
import { getConfig } from "@app/lib/config/env";
|
||||
import { request } from "@app/lib/config/request";
|
||||
import { infisicalSymmetricDecrypt } from "@app/lib/crypto/encryption";
|
||||
import { logger } from "@app/lib/logger";
|
||||
import { QueueJobs, QueueName, TQueueServiceFactory } from "@app/queue";
|
||||
import { TProjectDALFactory } from "@app/services/project/project-dal";
|
||||
|
||||
import { TAuditLogStreamDALFactory } from "../audit-log-stream/audit-log-stream-dal";
|
||||
import { providerSpecificPayload } from "../audit-log-stream/audit-log-stream-fns";
|
||||
import { LogStreamHeaders } from "../audit-log-stream/audit-log-stream-types";
|
||||
import { TLicenseServiceFactory } from "../license/license-service";
|
||||
import { TAuditLogDALFactory } from "./audit-log-dal";
|
||||
@ -128,13 +130,25 @@ export const auditLogQueueServiceFactory = async ({
|
||||
headers[key] = value;
|
||||
});
|
||||
|
||||
return request.post(url, auditLog, {
|
||||
headers,
|
||||
// request timeout
|
||||
timeout: AUDIT_LOG_STREAM_TIMEOUT,
|
||||
// connection timeout
|
||||
signal: AbortSignal.timeout(AUDIT_LOG_STREAM_TIMEOUT)
|
||||
});
|
||||
try {
|
||||
const response = await request.post(
|
||||
url,
|
||||
{ ...providerSpecificPayload(url), ...auditLog },
|
||||
{
|
||||
headers,
|
||||
// request timeout
|
||||
timeout: AUDIT_LOG_STREAM_TIMEOUT,
|
||||
// connection timeout
|
||||
signal: AbortSignal.timeout(AUDIT_LOG_STREAM_TIMEOUT)
|
||||
}
|
||||
);
|
||||
return response;
|
||||
} catch (error) {
|
||||
logger.error(
|
||||
`Failed to stream audit log [url=${url}] for org [orgId=${orgId}] [error=${(error as AxiosError).message}]`
|
||||
);
|
||||
return error;
|
||||
}
|
||||
}
|
||||
)
|
||||
);
|
||||
@ -218,13 +232,25 @@ export const auditLogQueueServiceFactory = async ({
|
||||
headers[key] = value;
|
||||
});
|
||||
|
||||
return request.post(url, auditLog, {
|
||||
headers,
|
||||
// request timeout
|
||||
timeout: AUDIT_LOG_STREAM_TIMEOUT,
|
||||
// connection timeout
|
||||
signal: AbortSignal.timeout(AUDIT_LOG_STREAM_TIMEOUT)
|
||||
});
|
||||
try {
|
||||
const response = await request.post(
|
||||
url,
|
||||
{ ...providerSpecificPayload(url), ...auditLog },
|
||||
{
|
||||
headers,
|
||||
// request timeout
|
||||
timeout: AUDIT_LOG_STREAM_TIMEOUT,
|
||||
// connection timeout
|
||||
signal: AbortSignal.timeout(AUDIT_LOG_STREAM_TIMEOUT)
|
||||
}
|
||||
);
|
||||
return response;
|
||||
} catch (error) {
|
||||
logger.error(
|
||||
`Failed to stream audit log [url=${url}] for org [orgId=${orgId}] [error=${(error as AxiosError).message}]`
|
||||
);
|
||||
return error;
|
||||
}
|
||||
}
|
||||
)
|
||||
);
|
||||
|
@ -1,7 +1,6 @@
|
||||
import { ForbiddenError } from "@casl/ability";
|
||||
import { requestContext } from "@fastify/request-context";
|
||||
|
||||
import { ActionProjectType } from "@app/db/schemas";
|
||||
import { getConfig } from "@app/lib/config/env";
|
||||
import { BadRequestError } from "@app/lib/errors";
|
||||
import { ActorType } from "@app/services/auth/auth-type";
|
||||
@ -38,8 +37,7 @@ export const auditLogServiceFactory = ({
|
||||
actorId,
|
||||
projectId: filter.projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.Any
|
||||
actorOrgId
|
||||
});
|
||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.AuditLogs);
|
||||
} else {
|
||||
@ -69,7 +67,8 @@ export const auditLogServiceFactory = ({
|
||||
secretPath: filter.secretPath,
|
||||
secretKey: filter.secretKey,
|
||||
environment: filter.environment,
|
||||
...(filter.projectId ? { projectId: filter.projectId } : { orgId: actorOrgId })
|
||||
orgId: actorOrgId,
|
||||
...(filter.projectId ? { projectId: filter.projectId } : {})
|
||||
});
|
||||
|
||||
return auditLogs.map(({ eventType: logEventType, actor: eActor, actorMetadata, eventMetadata, ...el }) => ({
|
||||
|
@ -56,8 +56,8 @@ export type TListProjectAuditLogDTO = {
|
||||
eventType?: EventType[];
|
||||
offset?: number;
|
||||
limit: number;
|
||||
endDate?: string;
|
||||
startDate?: string;
|
||||
endDate: string;
|
||||
startDate: string;
|
||||
projectId?: string;
|
||||
environment?: string;
|
||||
auditLogActorId?: string;
|
||||
@ -116,6 +116,15 @@ interface BaseAuthData {
|
||||
userAgentType?: UserAgentType;
|
||||
}
|
||||
|
||||
export enum SecretApprovalEvent {
|
||||
Create = "create",
|
||||
Update = "update",
|
||||
Delete = "delete",
|
||||
CreateMany = "create-many",
|
||||
UpdateMany = "update-many",
|
||||
DeleteMany = "delete-many"
|
||||
}
|
||||
|
||||
export enum UserAgentType {
|
||||
WEB = "web",
|
||||
CLI = "cli",
|
||||
@ -202,6 +211,12 @@ export enum EventType {
|
||||
REVOKE_IDENTITY_ALICLOUD_AUTH = "revoke-identity-alicloud-auth",
|
||||
GET_IDENTITY_ALICLOUD_AUTH = "get-identity-alicloud-auth",
|
||||
|
||||
LOGIN_IDENTITY_TLS_CERT_AUTH = "login-identity-tls-cert-auth",
|
||||
ADD_IDENTITY_TLS_CERT_AUTH = "add-identity-tls-cert-auth",
|
||||
UPDATE_IDENTITY_TLS_CERT_AUTH = "update-identity-tls-cert-auth",
|
||||
REVOKE_IDENTITY_TLS_CERT_AUTH = "revoke-identity-tls-cert-auth",
|
||||
GET_IDENTITY_TLS_CERT_AUTH = "get-identity-tls-cert-auth",
|
||||
|
||||
LOGIN_IDENTITY_AWS_AUTH = "login-identity-aws-auth",
|
||||
ADD_IDENTITY_AWS_AUTH = "add-identity-aws-auth",
|
||||
UPDATE_IDENTITY_AWS_AUTH = "update-identity-aws-auth",
|
||||
@ -1141,6 +1156,53 @@ interface GetIdentityAliCloudAuthEvent {
|
||||
};
|
||||
}
|
||||
|
||||
interface LoginIdentityTlsCertAuthEvent {
|
||||
type: EventType.LOGIN_IDENTITY_TLS_CERT_AUTH;
|
||||
metadata: {
|
||||
identityId: string;
|
||||
identityTlsCertAuthId: string;
|
||||
identityAccessTokenId: string;
|
||||
};
|
||||
}
|
||||
|
||||
interface AddIdentityTlsCertAuthEvent {
|
||||
type: EventType.ADD_IDENTITY_TLS_CERT_AUTH;
|
||||
metadata: {
|
||||
identityId: string;
|
||||
allowedCommonNames: string | null | undefined;
|
||||
accessTokenTTL: number;
|
||||
accessTokenMaxTTL: number;
|
||||
accessTokenNumUsesLimit: number;
|
||||
accessTokenTrustedIps: Array<TIdentityTrustedIp>;
|
||||
};
|
||||
}
|
||||
|
||||
interface DeleteIdentityTlsCertAuthEvent {
|
||||
type: EventType.REVOKE_IDENTITY_TLS_CERT_AUTH;
|
||||
metadata: {
|
||||
identityId: string;
|
||||
};
|
||||
}
|
||||
|
||||
interface UpdateIdentityTlsCertAuthEvent {
|
||||
type: EventType.UPDATE_IDENTITY_TLS_CERT_AUTH;
|
||||
metadata: {
|
||||
identityId: string;
|
||||
allowedCommonNames: string | null | undefined;
|
||||
accessTokenTTL?: number;
|
||||
accessTokenMaxTTL?: number;
|
||||
accessTokenNumUsesLimit?: number;
|
||||
accessTokenTrustedIps?: Array<TIdentityTrustedIp>;
|
||||
};
|
||||
}
|
||||
|
||||
interface GetIdentityTlsCertAuthEvent {
|
||||
type: EventType.GET_IDENTITY_TLS_CERT_AUTH;
|
||||
metadata: {
|
||||
identityId: string;
|
||||
};
|
||||
}
|
||||
|
||||
interface LoginIdentityOciAuthEvent {
|
||||
type: EventType.LOGIN_IDENTITY_OCI_AUTH;
|
||||
metadata: {
|
||||
@ -1652,6 +1714,17 @@ interface SecretApprovalRequest {
|
||||
committedBy: string;
|
||||
secretApprovalRequestSlug: string;
|
||||
secretApprovalRequestId: string;
|
||||
eventType: SecretApprovalEvent;
|
||||
secretKey?: string;
|
||||
secretId?: string;
|
||||
secrets?: {
|
||||
secretKey?: string;
|
||||
secretId?: string;
|
||||
environment?: string;
|
||||
secretPath?: string;
|
||||
}[];
|
||||
environment: string;
|
||||
secretPath: string;
|
||||
};
|
||||
}
|
||||
|
||||
@ -3358,6 +3431,11 @@ export type Event =
|
||||
| UpdateIdentityAliCloudAuthEvent
|
||||
| GetIdentityAliCloudAuthEvent
|
||||
| DeleteIdentityAliCloudAuthEvent
|
||||
| LoginIdentityTlsCertAuthEvent
|
||||
| AddIdentityTlsCertAuthEvent
|
||||
| UpdateIdentityTlsCertAuthEvent
|
||||
| GetIdentityTlsCertAuthEvent
|
||||
| DeleteIdentityTlsCertAuthEvent
|
||||
| LoginIdentityOciAuthEvent
|
||||
| AddIdentityOciAuthEvent
|
||||
| UpdateIdentityOciAuthEvent
|
||||
|
@ -1,7 +1,6 @@
|
||||
import { ForbiddenError } from "@casl/ability";
|
||||
import * as x509 from "@peculiar/x509";
|
||||
|
||||
import { ActionProjectType } from "@app/db/schemas";
|
||||
import { TCertificateAuthorityCrlDALFactory } from "@app/ee/services/certificate-authority-crl/certificate-authority-crl-dal";
|
||||
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
|
||||
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
|
||||
@ -78,8 +77,7 @@ export const certificateAuthorityCrlServiceFactory = ({
|
||||
actorId,
|
||||
projectId: ca.projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.CertificateManager
|
||||
actorOrgId
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
|
@ -1,7 +1,6 @@
|
||||
import { ForbiddenError, subject } from "@casl/ability";
|
||||
import RE2 from "re2";
|
||||
|
||||
import { ActionProjectType } from "@app/db/schemas";
|
||||
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
|
||||
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
|
||||
import {
|
||||
@ -85,8 +84,7 @@ export const dynamicSecretLeaseServiceFactory = ({
|
||||
actorId,
|
||||
projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.SecretManager
|
||||
actorOrgId
|
||||
});
|
||||
|
||||
const plan = await licenseService.getPlan(actorOrgId);
|
||||
@ -202,8 +200,7 @@ export const dynamicSecretLeaseServiceFactory = ({
|
||||
actorId,
|
||||
projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.SecretManager
|
||||
actorOrgId
|
||||
});
|
||||
|
||||
const { decryptor: secretManagerDecryptor } = await kmsService.createCipherPairWithDataKey({
|
||||
@ -300,8 +297,7 @@ export const dynamicSecretLeaseServiceFactory = ({
|
||||
actorId,
|
||||
projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.SecretManager
|
||||
actorOrgId
|
||||
});
|
||||
|
||||
const { decryptor: secretManagerDecryptor } = await kmsService.createCipherPairWithDataKey({
|
||||
@ -389,8 +385,7 @@ export const dynamicSecretLeaseServiceFactory = ({
|
||||
actorId,
|
||||
projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.SecretManager
|
||||
actorOrgId
|
||||
});
|
||||
|
||||
const folder = await folderDAL.findBySecretPath(projectId, environmentSlug, path);
|
||||
@ -437,8 +432,7 @@ export const dynamicSecretLeaseServiceFactory = ({
|
||||
actorId,
|
||||
projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.SecretManager
|
||||
actorOrgId
|
||||
});
|
||||
|
||||
const folder = await folderDAL.findBySecretPath(projectId, environmentSlug, path);
|
||||
|
@ -1,6 +1,5 @@
|
||||
import { ForbiddenError, subject } from "@casl/ability";
|
||||
|
||||
import { ActionProjectType } from "@app/db/schemas";
|
||||
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
|
||||
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
|
||||
import {
|
||||
@ -78,8 +77,7 @@ export const dynamicSecretServiceFactory = ({
|
||||
actorId,
|
||||
projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.SecretManager
|
||||
actorOrgId
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
@ -202,8 +200,7 @@ export const dynamicSecretServiceFactory = ({
|
||||
actorId,
|
||||
projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.SecretManager
|
||||
actorOrgId
|
||||
});
|
||||
|
||||
const plan = await licenseService.getPlan(actorOrgId);
|
||||
@ -354,8 +351,7 @@ export const dynamicSecretServiceFactory = ({
|
||||
actorId,
|
||||
projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.SecretManager
|
||||
actorOrgId
|
||||
});
|
||||
|
||||
const folder = await folderDAL.findBySecretPath(projectId, environmentSlug, path);
|
||||
@ -420,8 +416,7 @@ export const dynamicSecretServiceFactory = ({
|
||||
actorId,
|
||||
projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.SecretManager
|
||||
actorOrgId
|
||||
});
|
||||
|
||||
const folder = await folderDAL.findBySecretPath(projectId, environmentSlug, path);
|
||||
@ -485,8 +480,7 @@ export const dynamicSecretServiceFactory = ({
|
||||
actorId,
|
||||
projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.SecretManager
|
||||
actorOrgId
|
||||
});
|
||||
|
||||
// verify user has access to each env in request
|
||||
@ -529,8 +523,7 @@ export const dynamicSecretServiceFactory = ({
|
||||
actorId,
|
||||
projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.SecretManager
|
||||
actorOrgId
|
||||
});
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionDynamicSecretActions.ReadRootCredential,
|
||||
@ -578,8 +571,7 @@ export const dynamicSecretServiceFactory = ({
|
||||
actorId,
|
||||
projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.SecretManager
|
||||
actorOrgId
|
||||
});
|
||||
|
||||
const folder = await folderDAL.findBySecretPath(projectId, environmentSlug, path);
|
||||
@ -616,8 +608,7 @@ export const dynamicSecretServiceFactory = ({
|
||||
actorId: actor.id,
|
||||
projectId,
|
||||
actorAuthMethod: actor.authMethod,
|
||||
actorOrgId: actor.orgId,
|
||||
actionProjectType: ActionProjectType.SecretManager
|
||||
actorOrgId: actor.orgId
|
||||
});
|
||||
|
||||
const userAccessibleFolderMappings = folderMappings.filter(({ path, environment }) =>
|
||||
@ -661,8 +652,7 @@ export const dynamicSecretServiceFactory = ({
|
||||
actorId,
|
||||
projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.SecretManager
|
||||
actorOrgId
|
||||
});
|
||||
|
||||
const folders = await folderDAL.findBySecretPathMultiEnv(projectId, environmentSlugs, path);
|
||||
|
@ -1,5 +1,5 @@
|
||||
import axios from "axios";
|
||||
import * as jwt from "jsonwebtoken";
|
||||
import jwt from "jsonwebtoken";
|
||||
|
||||
import { BadRequestError, InternalServerError } from "@app/lib/errors";
|
||||
import { alphaNumericNanoId } from "@app/lib/nanoid";
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { ForbiddenError, subject } from "@casl/ability";
|
||||
import { packRules } from "@casl/ability/extra";
|
||||
|
||||
import { ActionProjectType, TableName } from "@app/db/schemas";
|
||||
import { TableName } from "@app/db/schemas";
|
||||
import { BadRequestError, NotFoundError, PermissionBoundaryError } from "@app/lib/errors";
|
||||
import { ms } from "@app/lib/ms";
|
||||
import { validateHandlebarTemplate } from "@app/lib/template/validate-handlebars";
|
||||
@ -61,8 +61,7 @@ export const identityProjectAdditionalPrivilegeV2ServiceFactory = ({
|
||||
actorId,
|
||||
projectId: identityProjectMembership.projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.Any
|
||||
actorOrgId
|
||||
});
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionIdentityActions.Edit,
|
||||
@ -73,8 +72,7 @@ export const identityProjectAdditionalPrivilegeV2ServiceFactory = ({
|
||||
actorId: identityId,
|
||||
projectId: identityProjectMembership.projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.Any
|
||||
actorOrgId
|
||||
});
|
||||
|
||||
// we need to validate that the privilege given is not higher than the assigning users permission
|
||||
@ -160,8 +158,7 @@ export const identityProjectAdditionalPrivilegeV2ServiceFactory = ({
|
||||
actorId,
|
||||
projectId: identityProjectMembership.projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.Any
|
||||
actorOrgId
|
||||
});
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionIdentityActions.Edit,
|
||||
@ -172,8 +169,7 @@ export const identityProjectAdditionalPrivilegeV2ServiceFactory = ({
|
||||
actorId: identityProjectMembership.identityId,
|
||||
projectId: identityProjectMembership.projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.Any
|
||||
actorOrgId
|
||||
});
|
||||
|
||||
// we need to validate that the privilege given is not higher than the assigning users permission
|
||||
@ -260,8 +256,7 @@ export const identityProjectAdditionalPrivilegeV2ServiceFactory = ({
|
||||
actorId,
|
||||
projectId: identityProjectMembership.projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.Any
|
||||
actorOrgId
|
||||
});
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionIdentityActions.Edit,
|
||||
@ -272,8 +267,7 @@ export const identityProjectAdditionalPrivilegeV2ServiceFactory = ({
|
||||
actorId: identityProjectMembership.identityId,
|
||||
projectId: identityProjectMembership.projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.Any
|
||||
actorOrgId
|
||||
});
|
||||
const permissionBoundary = validatePrivilegeChangeOperation(
|
||||
membership.shouldUseNewPrivilegeSystem,
|
||||
@ -321,8 +315,7 @@ export const identityProjectAdditionalPrivilegeV2ServiceFactory = ({
|
||||
actorId,
|
||||
projectId: identityProjectMembership.projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.Any
|
||||
actorOrgId
|
||||
});
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionIdentityActions.Read,
|
||||
@ -356,8 +349,7 @@ export const identityProjectAdditionalPrivilegeV2ServiceFactory = ({
|
||||
actorId,
|
||||
projectId: identityProjectMembership.projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.Any
|
||||
actorOrgId
|
||||
});
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionIdentityActions.Read,
|
||||
@ -392,8 +384,7 @@ export const identityProjectAdditionalPrivilegeV2ServiceFactory = ({
|
||||
actorId,
|
||||
projectId: identityProjectMembership.projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.Any
|
||||
actorOrgId
|
||||
});
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionIdentityActions.Read,
|
||||
|
@ -1,7 +1,6 @@
|
||||
import { ForbiddenError, MongoAbility, RawRuleOf, subject } from "@casl/ability";
|
||||
import { PackRule, packRules, unpackRules } from "@casl/ability/extra";
|
||||
|
||||
import { ActionProjectType } from "@app/db/schemas";
|
||||
import { BadRequestError, NotFoundError, PermissionBoundaryError } from "@app/lib/errors";
|
||||
import { ms } from "@app/lib/ms";
|
||||
import { validateHandlebarTemplate } from "@app/lib/template/validate-handlebars";
|
||||
@ -73,8 +72,7 @@ export const identityProjectAdditionalPrivilegeServiceFactory = ({
|
||||
actorId,
|
||||
projectId: identityProjectMembership.projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.Any
|
||||
actorOrgId
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
@ -87,8 +85,7 @@ export const identityProjectAdditionalPrivilegeServiceFactory = ({
|
||||
actorId: identityId,
|
||||
projectId: identityProjectMembership.projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.Any
|
||||
actorOrgId
|
||||
});
|
||||
|
||||
// we need to validate that the privilege given is not higher than the assigning users permission
|
||||
@ -175,8 +172,7 @@ export const identityProjectAdditionalPrivilegeServiceFactory = ({
|
||||
actorId,
|
||||
projectId: identityProjectMembership.projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.Any
|
||||
actorOrgId
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
@ -189,8 +185,7 @@ export const identityProjectAdditionalPrivilegeServiceFactory = ({
|
||||
actorId: identityProjectMembership.identityId,
|
||||
projectId: identityProjectMembership.projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.Any
|
||||
actorOrgId
|
||||
});
|
||||
|
||||
// we need to validate that the privilege given is not higher than the assigning users permission
|
||||
@ -293,8 +288,7 @@ export const identityProjectAdditionalPrivilegeServiceFactory = ({
|
||||
actorId,
|
||||
projectId: identityProjectMembership.projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.Any
|
||||
actorOrgId
|
||||
});
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionIdentityActions.Edit,
|
||||
@ -306,8 +300,7 @@ export const identityProjectAdditionalPrivilegeServiceFactory = ({
|
||||
actorId: identityProjectMembership.identityId,
|
||||
projectId: identityProjectMembership.projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.Any
|
||||
actorOrgId
|
||||
});
|
||||
const permissionBoundary = validatePrivilegeChangeOperation(
|
||||
membership.shouldUseNewPrivilegeSystem,
|
||||
@ -366,8 +359,7 @@ export const identityProjectAdditionalPrivilegeServiceFactory = ({
|
||||
actorId,
|
||||
projectId: identityProjectMembership.projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.Any
|
||||
actorOrgId
|
||||
});
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionIdentityActions.Read,
|
||||
@ -409,8 +401,7 @@ export const identityProjectAdditionalPrivilegeServiceFactory = ({
|
||||
actorId,
|
||||
projectId: identityProjectMembership.projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.Any
|
||||
actorOrgId
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
|
@ -24,7 +24,7 @@ type TKmipOperationServiceFactoryDep = {
|
||||
kmsService: TKmsServiceFactory;
|
||||
kmsDAL: TKmsKeyDALFactory;
|
||||
kmipClientDAL: TKmipClientDALFactory;
|
||||
projectDAL: Pick<TProjectDALFactory, "getProjectFromSplitId" | "findById">;
|
||||
projectDAL: Pick<TProjectDALFactory, "findById">;
|
||||
permissionService: Pick<TPermissionServiceFactory, "getOrgPermission">;
|
||||
};
|
||||
|
||||
|
@ -2,10 +2,10 @@ import { ForbiddenError } from "@casl/ability";
|
||||
import * as x509 from "@peculiar/x509";
|
||||
import crypto, { KeyObject } from "crypto";
|
||||
|
||||
import { ActionProjectType } from "@app/db/schemas";
|
||||
import { BadRequestError, InternalServerError, NotFoundError } from "@app/lib/errors";
|
||||
import { isValidIp } from "@app/lib/ip";
|
||||
import { ms } from "@app/lib/ms";
|
||||
import { OrgServiceActor } from "@app/lib/types";
|
||||
import { isFQDN } from "@app/lib/validator/validate-url";
|
||||
import { constructPemChainFromCerts } from "@app/services/certificate/certificate-fns";
|
||||
import { CertExtendedKeyUsage, CertKeyAlgorithm, CertKeyUsage } from "@app/services/certificate/certificate-types";
|
||||
@ -73,8 +73,7 @@ export const kmipServiceFactory = ({
|
||||
actorId,
|
||||
projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.KMS
|
||||
actorOrgId
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
@ -127,8 +126,7 @@ export const kmipServiceFactory = ({
|
||||
actorId,
|
||||
projectId: kmipClient.projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.KMS
|
||||
actorOrgId
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
@ -159,8 +157,7 @@ export const kmipServiceFactory = ({
|
||||
actorId,
|
||||
projectId: kmipClient.projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.KMS
|
||||
actorOrgId
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
@ -193,8 +190,7 @@ export const kmipServiceFactory = ({
|
||||
actorId,
|
||||
projectId: kmipClient.projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.KMS
|
||||
actorOrgId
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionKmipActions.ReadClients, ProjectPermissionSub.Kmip);
|
||||
@ -215,8 +211,7 @@ export const kmipServiceFactory = ({
|
||||
actorId,
|
||||
projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.KMS
|
||||
actorOrgId
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionKmipActions.ReadClients, ProjectPermissionSub.Kmip);
|
||||
@ -252,8 +247,7 @@ export const kmipServiceFactory = ({
|
||||
actorId,
|
||||
projectId: kmipClient.projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.KMS
|
||||
actorOrgId
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
@ -802,6 +796,26 @@ export const kmipServiceFactory = ({
|
||||
};
|
||||
};
|
||||
|
||||
const getProjectClientCount = async (projectId: string, actor: OrgServiceActor) => {
|
||||
// Anyone in the project should be able to get count.
|
||||
await permissionService.getProjectPermission({
|
||||
actor: actor.type,
|
||||
actorId: actor.id,
|
||||
projectId,
|
||||
actorAuthMethod: actor.authMethod,
|
||||
actorOrgId: actor.orgId
|
||||
});
|
||||
|
||||
const clients = await kmipClientDAL.find(
|
||||
{
|
||||
projectId
|
||||
},
|
||||
{ count: true }
|
||||
);
|
||||
|
||||
return Number(clients?.[0]?.count ?? 0);
|
||||
};
|
||||
|
||||
return {
|
||||
createKmipClient,
|
||||
updateKmipClient,
|
||||
@ -813,6 +827,7 @@ export const kmipServiceFactory = ({
|
||||
generateOrgKmipServerCertificate,
|
||||
getOrgKmip,
|
||||
getServerCertificateBySerialNumber,
|
||||
registerServer
|
||||
registerServer,
|
||||
getProjectClientCount
|
||||
};
|
||||
};
|
||||
|
@ -107,34 +107,26 @@ export const oidcConfigServiceFactory = ({
|
||||
kmsService
|
||||
}: TOidcConfigServiceFactoryDep) => {
|
||||
const getOidc = async (dto: TGetOidcCfgDTO) => {
|
||||
const org = await orgDAL.findOne({ slug: dto.orgSlug });
|
||||
if (!org) {
|
||||
const oidcCfg = await oidcConfigDAL.findOne({
|
||||
orgId: dto.organizationId
|
||||
});
|
||||
if (!oidcCfg) {
|
||||
throw new NotFoundError({
|
||||
message: `Organization with slug '${dto.orgSlug}' not found`,
|
||||
name: "OrgNotFound"
|
||||
message: `OIDC configuration for organization with ID '${dto.organizationId}' not found`
|
||||
});
|
||||
}
|
||||
|
||||
if (dto.type === "external") {
|
||||
const { permission } = await permissionService.getOrgPermission(
|
||||
dto.actor,
|
||||
dto.actorId,
|
||||
org.id,
|
||||
dto.organizationId,
|
||||
dto.actorAuthMethod,
|
||||
dto.actorOrgId
|
||||
);
|
||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Sso);
|
||||
}
|
||||
|
||||
const oidcCfg = await oidcConfigDAL.findOne({
|
||||
orgId: org.id
|
||||
});
|
||||
|
||||
if (!oidcCfg) {
|
||||
throw new NotFoundError({
|
||||
message: `OIDC configuration for organization with slug '${dto.orgSlug}' not found`
|
||||
});
|
||||
}
|
||||
|
||||
const { decryptor } = await kmsService.createCipherPairWithDataKey({
|
||||
type: KmsDataKey.Organization,
|
||||
orgId: oidcCfg.orgId
|
||||
@ -465,7 +457,7 @@ export const oidcConfigServiceFactory = ({
|
||||
};
|
||||
|
||||
const updateOidcCfg = async ({
|
||||
orgSlug,
|
||||
organizationId,
|
||||
allowedEmailDomains,
|
||||
configurationType,
|
||||
discoveryURL,
|
||||
@ -484,13 +476,11 @@ export const oidcConfigServiceFactory = ({
|
||||
manageGroupMemberships,
|
||||
jwtSignatureAlgorithm
|
||||
}: TUpdateOidcCfgDTO) => {
|
||||
const org = await orgDAL.findOne({
|
||||
slug: orgSlug
|
||||
});
|
||||
const org = await orgDAL.findOne({ id: organizationId });
|
||||
|
||||
if (!org) {
|
||||
throw new NotFoundError({
|
||||
message: `Organization with slug '${orgSlug}' not found`
|
||||
message: `Organization with ID '${organizationId}' not found`
|
||||
});
|
||||
}
|
||||
|
||||
@ -555,7 +545,7 @@ export const oidcConfigServiceFactory = ({
|
||||
};
|
||||
|
||||
const createOidcCfg = async ({
|
||||
orgSlug,
|
||||
organizationId,
|
||||
allowedEmailDomains,
|
||||
configurationType,
|
||||
discoveryURL,
|
||||
@ -574,12 +564,10 @@ export const oidcConfigServiceFactory = ({
|
||||
manageGroupMemberships,
|
||||
jwtSignatureAlgorithm
|
||||
}: TCreateOidcCfgDTO) => {
|
||||
const org = await orgDAL.findOne({
|
||||
slug: orgSlug
|
||||
});
|
||||
const org = await orgDAL.findOne({ id: organizationId });
|
||||
if (!org) {
|
||||
throw new NotFoundError({
|
||||
message: `Organization with slug '${orgSlug}' not found`
|
||||
message: `Organization with ID '${organizationId}' not found`
|
||||
});
|
||||
}
|
||||
|
||||
@ -639,7 +627,7 @@ export const oidcConfigServiceFactory = ({
|
||||
|
||||
const oidcCfg = await getOidc({
|
||||
type: "internal",
|
||||
orgSlug
|
||||
organizationId: org.id
|
||||
});
|
||||
|
||||
if (!oidcCfg || !oidcCfg.isActive) {
|
||||
|
@ -26,11 +26,11 @@ export type TOidcLoginDTO = {
|
||||
export type TGetOidcCfgDTO =
|
||||
| ({
|
||||
type: "external";
|
||||
orgSlug: string;
|
||||
organizationId: string;
|
||||
} & TGenericPermission)
|
||||
| {
|
||||
type: "internal";
|
||||
orgSlug: string;
|
||||
organizationId: string;
|
||||
};
|
||||
|
||||
export type TCreateOidcCfgDTO = {
|
||||
@ -45,7 +45,7 @@ export type TCreateOidcCfgDTO = {
|
||||
clientId: string;
|
||||
clientSecret: string;
|
||||
isActive: boolean;
|
||||
orgSlug: string;
|
||||
organizationId: string;
|
||||
manageGroupMemberships: boolean;
|
||||
jwtSignatureAlgorithm: OIDCJWTSignatureAlgorithm;
|
||||
} & TGenericPermission;
|
||||
@ -62,7 +62,7 @@ export type TUpdateOidcCfgDTO = Partial<{
|
||||
clientId: string;
|
||||
clientSecret: string;
|
||||
isActive: boolean;
|
||||
orgSlug: string;
|
||||
organizationId: string;
|
||||
manageGroupMemberships: boolean;
|
||||
jwtSignatureAlgorithm: OIDCJWTSignatureAlgorithm;
|
||||
}> &
|
||||
|
@ -91,7 +91,7 @@ export interface TPermissionDALFactory {
|
||||
userId: string;
|
||||
projectId: string;
|
||||
username: string;
|
||||
projectType: string;
|
||||
projectType?: string | null;
|
||||
id: string;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
@ -163,7 +163,7 @@ export interface TPermissionDALFactory {
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
orgId: string;
|
||||
projectType: string;
|
||||
projectType?: string | null;
|
||||
shouldUseNewPrivilegeSystem: boolean;
|
||||
orgAuthEnforced: boolean;
|
||||
metadata: {
|
||||
@ -201,7 +201,7 @@ export interface TPermissionDALFactory {
|
||||
userId: string;
|
||||
projectId: string;
|
||||
username: string;
|
||||
projectType: string;
|
||||
projectType?: string | null;
|
||||
id: string;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
@ -267,7 +267,7 @@ export interface TPermissionDALFactory {
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
orgId: string;
|
||||
projectType: string;
|
||||
projectType?: string | null;
|
||||
orgAuthEnforced: boolean;
|
||||
metadata: {
|
||||
id: string;
|
||||
|
@ -1,7 +1,6 @@
|
||||
import { MongoAbility, RawRuleOf } from "@casl/ability";
|
||||
import { MongoQuery } from "@ucast/mongo2js";
|
||||
|
||||
import { ActionProjectType } from "@app/db/schemas";
|
||||
import { ActorAuthMethod, ActorType } from "@app/services/auth/auth-type";
|
||||
|
||||
import { OrgPermissionSet } from "./org-permission";
|
||||
@ -21,7 +20,6 @@ export type TGetUserProjectPermissionArg = {
|
||||
userId: string;
|
||||
projectId: string;
|
||||
authMethod: ActorAuthMethod;
|
||||
actionProjectType: ActionProjectType;
|
||||
userOrgId?: string;
|
||||
};
|
||||
|
||||
@ -29,14 +27,12 @@ export type TGetIdentityProjectPermissionArg = {
|
||||
identityId: string;
|
||||
projectId: string;
|
||||
identityOrgId?: string;
|
||||
actionProjectType: ActionProjectType;
|
||||
};
|
||||
|
||||
export type TGetServiceTokenProjectPermissionArg = {
|
||||
serviceTokenId: string;
|
||||
projectId: string;
|
||||
actorOrgId?: string;
|
||||
actionProjectType: ActionProjectType;
|
||||
};
|
||||
|
||||
export type TGetProjectPermissionArg = {
|
||||
@ -45,7 +41,6 @@ export type TGetProjectPermissionArg = {
|
||||
projectId: string;
|
||||
actorAuthMethod: ActorAuthMethod;
|
||||
actorOrgId?: string;
|
||||
actionProjectType: ActionProjectType;
|
||||
};
|
||||
|
||||
export type TPermissionServiceFactory = {
|
||||
@ -143,13 +138,7 @@ export type TPermissionServiceFactory = {
|
||||
};
|
||||
}
|
||||
>;
|
||||
getUserProjectPermission: ({
|
||||
userId,
|
||||
projectId,
|
||||
authMethod,
|
||||
userOrgId,
|
||||
actionProjectType
|
||||
}: TGetUserProjectPermissionArg) => Promise<{
|
||||
getUserProjectPermission: ({ userId, projectId, authMethod, userOrgId }: TGetUserProjectPermissionArg) => Promise<{
|
||||
permission: MongoAbility<ProjectPermissionSet, MongoQuery>;
|
||||
membership: {
|
||||
id: string;
|
||||
|
@ -5,7 +5,6 @@ import { MongoQuery } from "@ucast/mongo2js";
|
||||
import handlebars from "handlebars";
|
||||
|
||||
import {
|
||||
ActionProjectType,
|
||||
OrgMembershipRole,
|
||||
ProjectMembershipRole,
|
||||
ServiceTokenScopes,
|
||||
@ -214,8 +213,7 @@ export const permissionServiceFactory = ({
|
||||
userId,
|
||||
projectId,
|
||||
authMethod,
|
||||
userOrgId,
|
||||
actionProjectType
|
||||
userOrgId
|
||||
}: TGetUserProjectPermissionArg): Promise<TProjectPermissionRT<ActorType.USER>> => {
|
||||
const userProjectPermission = await permissionDAL.getProjectPermission(userId, projectId);
|
||||
if (!userProjectPermission) throw new ForbiddenRequestError({ name: "User not a part of the specified project" });
|
||||
@ -242,12 +240,6 @@ export const permissionServiceFactory = ({
|
||||
userProjectPermission.orgRole
|
||||
);
|
||||
|
||||
if (actionProjectType !== ActionProjectType.Any && actionProjectType !== userProjectPermission.projectType) {
|
||||
throw new BadRequestError({
|
||||
message: `The project is of type ${userProjectPermission.projectType}. Operations of type ${actionProjectType} are not allowed.`
|
||||
});
|
||||
}
|
||||
|
||||
// join two permissions and pass to build the final permission set
|
||||
const rolePermissions = userProjectPermission.roles?.map(({ role, permissions }) => ({ role, permissions })) || [];
|
||||
const additionalPrivileges =
|
||||
@ -295,8 +287,7 @@ export const permissionServiceFactory = ({
|
||||
const getIdentityProjectPermission = async ({
|
||||
identityId,
|
||||
projectId,
|
||||
identityOrgId,
|
||||
actionProjectType
|
||||
identityOrgId
|
||||
}: TGetIdentityProjectPermissionArg): Promise<TProjectPermissionRT<ActorType.IDENTITY>> => {
|
||||
const identityProjectPermission = await permissionDAL.getProjectIdentityPermission(identityId, projectId);
|
||||
if (!identityProjectPermission)
|
||||
@ -316,12 +307,6 @@ export const permissionServiceFactory = ({
|
||||
throw new ForbiddenRequestError({ name: "Identity is not a member of the specified organization" });
|
||||
}
|
||||
|
||||
if (actionProjectType !== ActionProjectType.Any && actionProjectType !== identityProjectPermission.projectType) {
|
||||
throw new BadRequestError({
|
||||
message: `The project is of type ${identityProjectPermission.projectType}. Operations of type ${actionProjectType} are not allowed.`
|
||||
});
|
||||
}
|
||||
|
||||
const rolePermissions =
|
||||
identityProjectPermission.roles?.map(({ role, permissions }) => ({ role, permissions })) || [];
|
||||
const additionalPrivileges =
|
||||
@ -376,8 +361,7 @@ export const permissionServiceFactory = ({
|
||||
const getServiceTokenProjectPermission = async ({
|
||||
serviceTokenId,
|
||||
projectId,
|
||||
actorOrgId,
|
||||
actionProjectType
|
||||
actorOrgId
|
||||
}: TGetServiceTokenProjectPermissionArg) => {
|
||||
const serviceToken = await serviceTokenDAL.findById(serviceTokenId);
|
||||
if (!serviceToken) throw new NotFoundError({ message: `Service token with ID '${serviceTokenId}' not found` });
|
||||
@ -402,12 +386,6 @@ export const permissionServiceFactory = ({
|
||||
});
|
||||
}
|
||||
|
||||
if (actionProjectType !== ActionProjectType.Any && actionProjectType !== serviceTokenProject.type) {
|
||||
throw new BadRequestError({
|
||||
message: `The project is of type ${serviceTokenProject.type}. Operations of type ${actionProjectType} are not allowed.`
|
||||
});
|
||||
}
|
||||
|
||||
const scopes = ServiceTokenScopes.parse(serviceToken.scopes || []);
|
||||
return {
|
||||
permission: buildServiceTokenProjectPermission(scopes, serviceToken.permissions),
|
||||
@ -559,8 +537,7 @@ export const permissionServiceFactory = ({
|
||||
actorId: inputActorId,
|
||||
projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId,
|
||||
actionProjectType
|
||||
actorOrgId
|
||||
}: TGetProjectPermissionArg): Promise<TProjectPermissionRT<T>> => {
|
||||
let actor = inputActor;
|
||||
let actorId = inputActorId;
|
||||
@ -581,22 +558,19 @@ export const permissionServiceFactory = ({
|
||||
userId: actorId,
|
||||
projectId,
|
||||
authMethod: actorAuthMethod,
|
||||
userOrgId: actorOrgId,
|
||||
actionProjectType
|
||||
userOrgId: actorOrgId
|
||||
}) as Promise<TProjectPermissionRT<T>>;
|
||||
case ActorType.SERVICE:
|
||||
return getServiceTokenProjectPermission({
|
||||
serviceTokenId: actorId,
|
||||
projectId,
|
||||
actorOrgId,
|
||||
actionProjectType
|
||||
actorOrgId
|
||||
}) as Promise<TProjectPermissionRT<T>>;
|
||||
case ActorType.IDENTITY:
|
||||
return getIdentityProjectPermission({
|
||||
identityId: actorId,
|
||||
projectId,
|
||||
identityOrgId: actorOrgId,
|
||||
actionProjectType
|
||||
identityOrgId: actorOrgId
|
||||
}) as Promise<TProjectPermissionRT<T>>;
|
||||
default:
|
||||
throw new BadRequestError({
|
||||
|
@ -1,7 +1,6 @@
|
||||
/* eslint-disable no-await-in-loop */
|
||||
import { ForbiddenError } from "@casl/ability";
|
||||
|
||||
import { ActionProjectType } from "@app/db/schemas";
|
||||
import { ProjectPermissionCommitsActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
|
||||
import { NotFoundError } from "@app/lib/errors";
|
||||
import { logger } from "@app/lib/logger";
|
||||
@ -321,8 +320,7 @@ export const pitServiceFactory = ({
|
||||
actorId,
|
||||
projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.SecretManager
|
||||
actorOrgId
|
||||
});
|
||||
|
||||
ForbiddenError.from(userPermission).throwUnlessCan(
|
||||
|
@ -1,4 +1,3 @@
|
||||
import { ProjectType } from "@app/db/schemas";
|
||||
import {
|
||||
InfisicalProjectTemplate,
|
||||
TUnpackedPermission
|
||||
@ -7,21 +6,18 @@ import { getPredefinedRoles } from "@app/services/project-role/project-role-fns"
|
||||
|
||||
import { ProjectTemplateDefaultEnvironments } from "./project-template-constants";
|
||||
|
||||
export const getDefaultProjectTemplate = (orgId: string, type: ProjectType) => ({
|
||||
export const getDefaultProjectTemplate = (orgId: string) => ({
|
||||
id: "b11b49a9-09a9-4443-916a-4246f9ff2c69", // random ID to appease zod
|
||||
type,
|
||||
name: InfisicalProjectTemplate.Default,
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
description: `Infisical's ${type} default project template`,
|
||||
environments: type === ProjectType.SecretManager ? ProjectTemplateDefaultEnvironments : null,
|
||||
roles: [...getPredefinedRoles({ projectId: "project-template", projectType: type })].map(
|
||||
({ name, slug, permissions }) => ({
|
||||
name,
|
||||
slug,
|
||||
permissions: permissions as TUnpackedPermission[]
|
||||
})
|
||||
),
|
||||
description: `Infisical's default project template`,
|
||||
environments: ProjectTemplateDefaultEnvironments,
|
||||
roles: getPredefinedRoles({ projectId: "project-template" }) as Array<{
|
||||
name: string;
|
||||
slug: string;
|
||||
permissions: TUnpackedPermission[];
|
||||
}>,
|
||||
orgId
|
||||
});
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { ForbiddenError } from "@casl/ability";
|
||||
import { packRules } from "@casl/ability/extra";
|
||||
|
||||
import { ProjectType, TProjectTemplates } from "@app/db/schemas";
|
||||
import { TProjectTemplates } from "@app/db/schemas";
|
||||
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
|
||||
import { OrgPermissionActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission";
|
||||
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
|
||||
@ -29,13 +29,11 @@ const $unpackProjectTemplate = ({ roles, environments, ...rest }: TProjectTempla
|
||||
...rest,
|
||||
environments: environments as TProjectTemplateEnvironment[],
|
||||
roles: [
|
||||
...getPredefinedRoles({ projectId: "project-template", projectType: rest.type as ProjectType }).map(
|
||||
({ name, slug, permissions }) => ({
|
||||
name,
|
||||
slug,
|
||||
permissions: permissions as TUnpackedPermission[]
|
||||
})
|
||||
),
|
||||
...getPredefinedRoles({ projectId: "project-template" }).map(({ name, slug, permissions }) => ({
|
||||
name,
|
||||
slug,
|
||||
permissions: permissions as TUnpackedPermission[]
|
||||
})),
|
||||
...(roles as TProjectTemplateRole[]).map((role) => ({
|
||||
...role,
|
||||
permissions: unpackPermissions(role.permissions)
|
||||
@ -48,10 +46,7 @@ export const projectTemplateServiceFactory = ({
|
||||
permissionService,
|
||||
projectTemplateDAL
|
||||
}: TProjectTemplatesServiceFactoryDep): TProjectTemplateServiceFactory => {
|
||||
const listProjectTemplatesByOrg: TProjectTemplateServiceFactory["listProjectTemplatesByOrg"] = async (
|
||||
actor,
|
||||
type
|
||||
) => {
|
||||
const listProjectTemplatesByOrg: TProjectTemplateServiceFactory["listProjectTemplatesByOrg"] = async (actor) => {
|
||||
const plan = await licenseService.getPlan(actor.orgId);
|
||||
|
||||
if (!plan.projectTemplates)
|
||||
@ -70,14 +65,11 @@ export const projectTemplateServiceFactory = ({
|
||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.ProjectTemplates);
|
||||
|
||||
const projectTemplates = await projectTemplateDAL.find({
|
||||
orgId: actor.orgId,
|
||||
...(type ? { type } : {})
|
||||
orgId: actor.orgId
|
||||
});
|
||||
|
||||
return [
|
||||
...(type
|
||||
? [getDefaultProjectTemplate(actor.orgId, type)]
|
||||
: Object.values(ProjectType).map((projectType) => getDefaultProjectTemplate(actor.orgId, projectType))),
|
||||
getDefaultProjectTemplate(actor.orgId),
|
||||
...projectTemplates.map((template) => $unpackProjectTemplate(template))
|
||||
];
|
||||
};
|
||||
@ -142,7 +134,7 @@ export const projectTemplateServiceFactory = ({
|
||||
};
|
||||
|
||||
const createProjectTemplate: TProjectTemplateServiceFactory["createProjectTemplate"] = async (
|
||||
{ roles, environments, type, ...params },
|
||||
{ roles, environments, ...params },
|
||||
actor
|
||||
) => {
|
||||
const plan = await licenseService.getPlan(actor.orgId);
|
||||
@ -162,10 +154,6 @@ export const projectTemplateServiceFactory = ({
|
||||
|
||||
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
|
||||
@ -188,10 +176,8 @@ export const projectTemplateServiceFactory = ({
|
||||
const projectTemplate = await projectTemplateDAL.create({
|
||||
...params,
|
||||
roles: JSON.stringify(roles.map((role) => ({ ...role, permissions: packRules(role.permissions) }))),
|
||||
environments:
|
||||
type === ProjectType.SecretManager ? JSON.stringify(environments ?? ProjectTemplateDefaultEnvironments) : null,
|
||||
orgId: actor.orgId,
|
||||
type
|
||||
environments: environments ? JSON.stringify(environments ?? ProjectTemplateDefaultEnvironments) : null,
|
||||
orgId: actor.orgId
|
||||
});
|
||||
|
||||
return $unpackProjectTemplate(projectTemplate);
|
||||
@ -223,12 +209,6 @@ export const projectTemplateServiceFactory = ({
|
||||
|
||||
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
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { z } from "zod";
|
||||
|
||||
import { ProjectMembershipRole, ProjectType, TProjectEnvironments } from "@app/db/schemas";
|
||||
import { ProjectMembershipRole, TProjectEnvironments } from "@app/db/schemas";
|
||||
import { TProjectPermissionV2Schema } from "@app/ee/services/permission/project-permission";
|
||||
import { OrgServiceActor } from "@app/lib/types";
|
||||
import { UnpackedPermissionSchema } from "@app/server/routes/sanitizedSchema/permission";
|
||||
@ -16,7 +16,6 @@ export type TProjectTemplateRole = {
|
||||
export type TCreateProjectTemplateDTO = {
|
||||
name: string;
|
||||
description?: string;
|
||||
type: ProjectType;
|
||||
roles: TProjectTemplateRole[];
|
||||
environments?: TProjectTemplateEnvironment[] | null;
|
||||
};
|
||||
@ -30,14 +29,10 @@ export enum InfisicalProjectTemplate {
|
||||
}
|
||||
|
||||
export type TProjectTemplateServiceFactory = {
|
||||
listProjectTemplatesByOrg: (
|
||||
actor: OrgServiceActor,
|
||||
type?: ProjectType
|
||||
) => Promise<
|
||||
listProjectTemplatesByOrg: (actor: OrgServiceActor) => Promise<
|
||||
(
|
||||
| {
|
||||
id: string;
|
||||
type: ProjectType;
|
||||
name: InfisicalProjectTemplate;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
@ -74,7 +69,6 @@ export type TProjectTemplateServiceFactory = {
|
||||
name: string;
|
||||
}[];
|
||||
name: string;
|
||||
type: string;
|
||||
orgId: string;
|
||||
id: string;
|
||||
createdAt: Date;
|
||||
@ -99,7 +93,6 @@ export type TProjectTemplateServiceFactory = {
|
||||
name: string;
|
||||
}[];
|
||||
name: string;
|
||||
type: string;
|
||||
orgId: string;
|
||||
id: string;
|
||||
createdAt: Date;
|
||||
@ -123,7 +116,6 @@ export type TProjectTemplateServiceFactory = {
|
||||
name: string;
|
||||
}[];
|
||||
name: string;
|
||||
type: string;
|
||||
orgId: string;
|
||||
id: string;
|
||||
createdAt: Date;
|
||||
@ -146,7 +138,6 @@ export type TProjectTemplateServiceFactory = {
|
||||
name: string;
|
||||
}[];
|
||||
name: string;
|
||||
type: string;
|
||||
orgId: string;
|
||||
id: string;
|
||||
createdAt: Date;
|
||||
@ -170,7 +161,6 @@ export type TProjectTemplateServiceFactory = {
|
||||
name: string;
|
||||
}[];
|
||||
name: string;
|
||||
type: string;
|
||||
orgId: string;
|
||||
id: string;
|
||||
createdAt: Date;
|
||||
@ -194,7 +184,6 @@ export type TProjectTemplateServiceFactory = {
|
||||
name: string;
|
||||
}[];
|
||||
name: string;
|
||||
type: string;
|
||||
orgId: string;
|
||||
id: string;
|
||||
createdAt: Date;
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { ForbiddenError, MongoAbility, RawRuleOf } from "@casl/ability";
|
||||
import { PackRule, packRules, unpackRules } from "@casl/ability/extra";
|
||||
|
||||
import { ActionProjectType, TableName } from "@app/db/schemas";
|
||||
import { TableName } from "@app/db/schemas";
|
||||
import { BadRequestError, NotFoundError, PermissionBoundaryError } from "@app/lib/errors";
|
||||
import { ms } from "@app/lib/ms";
|
||||
import { validateHandlebarTemplate } from "@app/lib/template/validate-handlebars";
|
||||
@ -61,8 +61,7 @@ export const projectUserAdditionalPrivilegeServiceFactory = ({
|
||||
actorId,
|
||||
projectId: projectMembership.projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.Any
|
||||
actorOrgId
|
||||
});
|
||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionMemberActions.Edit, ProjectPermissionSub.Member);
|
||||
const { permission: targetUserPermission, membership } = await permissionService.getProjectPermission({
|
||||
@ -70,8 +69,7 @@ export const projectUserAdditionalPrivilegeServiceFactory = ({
|
||||
actorId: projectMembership.userId,
|
||||
projectId: projectMembership.projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.Any
|
||||
actorOrgId
|
||||
});
|
||||
|
||||
// we need to validate that the privilege given is not higher than the assigning users permission
|
||||
@ -166,8 +164,7 @@ export const projectUserAdditionalPrivilegeServiceFactory = ({
|
||||
actorId,
|
||||
projectId: projectMembership.projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.Any
|
||||
actorOrgId
|
||||
});
|
||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionMemberActions.Edit, ProjectPermissionSub.Member);
|
||||
const { permission: targetUserPermission } = await permissionService.getProjectPermission({
|
||||
@ -175,8 +172,7 @@ export const projectUserAdditionalPrivilegeServiceFactory = ({
|
||||
actorId: projectMembership.userId,
|
||||
projectId: projectMembership.projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.Any
|
||||
actorOrgId
|
||||
});
|
||||
|
||||
// we need to validate that the privilege given is not higher than the assigning users permission
|
||||
@ -276,8 +272,7 @@ export const projectUserAdditionalPrivilegeServiceFactory = ({
|
||||
actorId,
|
||||
projectId: projectMembership.projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.Any
|
||||
actorOrgId
|
||||
});
|
||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionMemberActions.Edit, ProjectPermissionSub.Member);
|
||||
|
||||
@ -322,8 +317,7 @@ export const projectUserAdditionalPrivilegeServiceFactory = ({
|
||||
actorId,
|
||||
projectId: projectMembership.projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.Any
|
||||
actorOrgId
|
||||
});
|
||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionMemberActions.Read, ProjectPermissionSub.Member);
|
||||
|
||||
@ -349,8 +343,7 @@ export const projectUserAdditionalPrivilegeServiceFactory = ({
|
||||
actorId,
|
||||
projectId: projectMembership.projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.Any
|
||||
actorOrgId
|
||||
});
|
||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionMemberActions.Read, ProjectPermissionSub.Member);
|
||||
|
||||
|
@ -148,10 +148,18 @@ export const samlConfigServiceFactory = ({
|
||||
let samlConfig: TSamlConfigs | undefined;
|
||||
if (dto.type === "org") {
|
||||
samlConfig = await samlConfigDAL.findOne({ orgId: dto.orgId });
|
||||
if (!samlConfig) return;
|
||||
if (!samlConfig) {
|
||||
throw new NotFoundError({
|
||||
message: `SAML configuration for organization with ID '${dto.orgId}' not found`
|
||||
});
|
||||
}
|
||||
} else if (dto.type === "orgSlug") {
|
||||
const org = await orgDAL.findOne({ slug: dto.orgSlug });
|
||||
if (!org) return;
|
||||
if (!org) {
|
||||
throw new NotFoundError({
|
||||
message: `Organization with slug '${dto.orgSlug}' not found`
|
||||
});
|
||||
}
|
||||
samlConfig = await samlConfigDAL.findOne({ orgId: org.id });
|
||||
} else if (dto.type === "ssoId") {
|
||||
// TODO:
|
||||
|
@ -61,20 +61,17 @@ export type TSamlLoginDTO = {
|
||||
export type TSamlConfigServiceFactory = {
|
||||
createSamlCfg: (arg: TCreateSamlCfgDTO) => Promise<TSamlConfigs>;
|
||||
updateSamlCfg: (arg: TUpdateSamlCfgDTO) => Promise<TSamlConfigs>;
|
||||
getSaml: (arg: TGetSamlCfgDTO) => Promise<
|
||||
| {
|
||||
id: string;
|
||||
organization: string;
|
||||
orgId: string;
|
||||
authProvider: string;
|
||||
isActive: boolean;
|
||||
entryPoint: string;
|
||||
issuer: string;
|
||||
cert: string;
|
||||
lastUsed: Date | null | undefined;
|
||||
}
|
||||
| undefined
|
||||
>;
|
||||
getSaml: (arg: TGetSamlCfgDTO) => Promise<{
|
||||
id: string;
|
||||
organization: string;
|
||||
orgId: string;
|
||||
authProvider: string;
|
||||
isActive: boolean;
|
||||
entryPoint: string;
|
||||
issuer: string;
|
||||
cert: string;
|
||||
lastUsed: Date | null | undefined;
|
||||
}>;
|
||||
samlLogin: (arg: TSamlLoginDTO) => Promise<{
|
||||
isUserCompleted: boolean;
|
||||
providerAuthToken: string;
|
||||
|
@ -1,7 +1,6 @@
|
||||
import { ForbiddenError } from "@casl/ability";
|
||||
import picomatch from "picomatch";
|
||||
|
||||
import { ActionProjectType } from "@app/db/schemas";
|
||||
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
|
||||
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
|
||||
import { BadRequestError, NotFoundError } from "@app/lib/errors";
|
||||
@ -91,8 +90,7 @@ export const secretApprovalPolicyServiceFactory = ({
|
||||
actorId,
|
||||
projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.SecretManager
|
||||
actorOrgId
|
||||
});
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionActions.Create,
|
||||
@ -267,8 +265,7 @@ export const secretApprovalPolicyServiceFactory = ({
|
||||
actorId,
|
||||
projectId: secretApprovalPolicy.projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.SecretManager
|
||||
actorOrgId
|
||||
});
|
||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.SecretApproval);
|
||||
|
||||
@ -423,8 +420,7 @@ export const secretApprovalPolicyServiceFactory = ({
|
||||
actorId,
|
||||
projectId: sapPolicy.projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.SecretManager
|
||||
actorOrgId
|
||||
});
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionActions.Delete,
|
||||
@ -463,8 +459,7 @@ export const secretApprovalPolicyServiceFactory = ({
|
||||
actorId,
|
||||
projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.SecretManager
|
||||
actorOrgId
|
||||
});
|
||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.SecretApproval);
|
||||
|
||||
@ -508,8 +503,7 @@ export const secretApprovalPolicyServiceFactory = ({
|
||||
actorId,
|
||||
projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.SecretManager
|
||||
actorOrgId
|
||||
});
|
||||
|
||||
return getSecretApprovalPolicy(projectId, environment, secretPath);
|
||||
@ -535,8 +529,7 @@ export const secretApprovalPolicyServiceFactory = ({
|
||||
actorId,
|
||||
projectId: sapPolicy.projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.SecretManager
|
||||
actorOrgId
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.SecretApproval);
|
||||
|
@ -290,7 +290,7 @@ export const secretApprovalRequestDALFactory = (db: TDbClient) => {
|
||||
}
|
||||
};
|
||||
|
||||
const findProjectRequestCount = async (projectId: string, userId: string, tx?: Knex) => {
|
||||
const findProjectRequestCount = async (projectId: string, userId: string, policyId?: string, tx?: Knex) => {
|
||||
try {
|
||||
const docs = await (tx || db)
|
||||
.with(
|
||||
@ -309,6 +309,9 @@ export const secretApprovalRequestDALFactory = (db: TDbClient) => {
|
||||
`${TableName.SecretApprovalPolicy}.id`
|
||||
)
|
||||
.where({ projectId })
|
||||
.where((qb) => {
|
||||
if (policyId) void qb.where(`${TableName.SecretApprovalPolicy}.id`, policyId);
|
||||
})
|
||||
.andWhere(
|
||||
(bd) =>
|
||||
void bd
|
||||
|
@ -36,7 +36,7 @@ export const sendApprovalEmailsFn = async ({
|
||||
firstName: reviewerUser.firstName,
|
||||
projectName: project.name,
|
||||
organizationName: project.organization.name,
|
||||
approvalUrl: `${cfg.SITE_URL}/secret-manager/${project.id}/approval?requestId=${secretApprovalRequest.id}`
|
||||
approvalUrl: `${cfg.SITE_URL}/projects/${project.id}/secret-manager/approval?requestId=${secretApprovalRequest.id}`
|
||||
},
|
||||
template: SmtpTemplates.SecretApprovalRequestNeedsReview
|
||||
});
|
||||
|
@ -2,7 +2,6 @@
|
||||
import { ForbiddenError, subject } from "@casl/ability";
|
||||
|
||||
import {
|
||||
ActionProjectType,
|
||||
ProjectMembershipRole,
|
||||
SecretEncryptionAlgo,
|
||||
SecretKeyEncoding,
|
||||
@ -11,6 +10,7 @@ import {
|
||||
TSecretApprovalRequestsSecretsInsert,
|
||||
TSecretApprovalRequestsSecretsV2Insert
|
||||
} from "@app/db/schemas";
|
||||
import { Event, EventType } from "@app/ee/services/audit-log/audit-log-types";
|
||||
import { getConfig } from "@app/lib/config/env";
|
||||
import { decryptSymmetric128BitHexKeyUTF8 } from "@app/lib/crypto";
|
||||
import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors";
|
||||
@ -168,7 +168,14 @@ export const secretApprovalRequestServiceFactory = ({
|
||||
microsoftTeamsService,
|
||||
folderCommitService
|
||||
}: TSecretApprovalRequestServiceFactoryDep) => {
|
||||
const requestCount = async ({ projectId, actor, actorId, actorOrgId, actorAuthMethod }: TApprovalRequestCountDTO) => {
|
||||
const requestCount = async ({
|
||||
projectId,
|
||||
policyId,
|
||||
actor,
|
||||
actorId,
|
||||
actorOrgId,
|
||||
actorAuthMethod
|
||||
}: TApprovalRequestCountDTO) => {
|
||||
if (actor === ActorType.SERVICE) throw new BadRequestError({ message: "Cannot use service token" });
|
||||
|
||||
await permissionService.getProjectPermission({
|
||||
@ -176,11 +183,10 @@ export const secretApprovalRequestServiceFactory = ({
|
||||
actorId,
|
||||
projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.SecretManager
|
||||
actorOrgId
|
||||
});
|
||||
|
||||
const count = await secretApprovalRequestDAL.findProjectRequestCount(projectId, actorId);
|
||||
const count = await secretApprovalRequestDAL.findProjectRequestCount(projectId, actorId, policyId);
|
||||
return count;
|
||||
};
|
||||
|
||||
@ -204,8 +210,7 @@ export const secretApprovalRequestServiceFactory = ({
|
||||
actorId,
|
||||
projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.SecretManager
|
||||
actorOrgId
|
||||
});
|
||||
|
||||
const { shouldUseSecretV2Bridge } = await projectBotService.getBotKey(projectId);
|
||||
@ -257,8 +262,7 @@ export const secretApprovalRequestServiceFactory = ({
|
||||
actorId,
|
||||
projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.SecretManager
|
||||
actorOrgId
|
||||
});
|
||||
if (
|
||||
!hasRole(ProjectMembershipRole.Admin) &&
|
||||
@ -407,8 +411,7 @@ export const secretApprovalRequestServiceFactory = ({
|
||||
actorId,
|
||||
projectId: secretApprovalRequest.projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.SecretManager
|
||||
actorOrgId
|
||||
});
|
||||
if (
|
||||
!hasRole(ProjectMembershipRole.Admin) &&
|
||||
@ -477,8 +480,7 @@ export const secretApprovalRequestServiceFactory = ({
|
||||
actorId,
|
||||
projectId: secretApprovalRequest.projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.SecretManager
|
||||
actorOrgId
|
||||
});
|
||||
if (
|
||||
!hasRole(ProjectMembershipRole.Admin) &&
|
||||
@ -522,7 +524,7 @@ export const secretApprovalRequestServiceFactory = ({
|
||||
});
|
||||
}
|
||||
|
||||
const { policy, folderId, projectId, bypassers } = secretApprovalRequest;
|
||||
const { policy, folderId, projectId, bypassers, environment } = secretApprovalRequest;
|
||||
if (policy.deletedAt) {
|
||||
throw new BadRequestError({
|
||||
message: "The policy associated with this secret approval request has been deleted."
|
||||
@ -534,8 +536,7 @@ export const secretApprovalRequestServiceFactory = ({
|
||||
actorId,
|
||||
projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.SecretManager
|
||||
actorOrgId
|
||||
});
|
||||
|
||||
if (
|
||||
@ -951,13 +952,118 @@ export const secretApprovalRequestServiceFactory = ({
|
||||
bypassReason,
|
||||
secretPath: policy.secretPath,
|
||||
environment: env.name,
|
||||
approvalUrl: `${cfg.SITE_URL}/secret-manager/${project.id}/approval`
|
||||
approvalUrl: `${cfg.SITE_URL}/projects/${project.id}/secret-manager/approval`
|
||||
},
|
||||
template: SmtpTemplates.AccessSecretRequestBypassed
|
||||
});
|
||||
}
|
||||
|
||||
return mergeStatus;
|
||||
const { created, updated, deleted } = mergeStatus.secrets;
|
||||
|
||||
const secretMutationEvents: Event[] = [];
|
||||
|
||||
if (created.length) {
|
||||
if (created.length > 1) {
|
||||
secretMutationEvents.push({
|
||||
type: EventType.CREATE_SECRETS,
|
||||
metadata: {
|
||||
environment,
|
||||
secretPath: folder.path,
|
||||
secrets: created.map((secret) => ({
|
||||
secretId: secret.id,
|
||||
secretVersion: 1,
|
||||
// @ts-expect-error not present on v1 secrets
|
||||
secretKey: secret.key as string,
|
||||
// @ts-expect-error not present on v1 secrets
|
||||
secretMetadata: secret.secretMetadata as ResourceMetadataDTO
|
||||
}))
|
||||
}
|
||||
});
|
||||
} else {
|
||||
const [secret] = created;
|
||||
secretMutationEvents.push({
|
||||
type: EventType.CREATE_SECRET,
|
||||
metadata: {
|
||||
environment,
|
||||
secretPath: folder.path,
|
||||
secretId: secret.id,
|
||||
secretVersion: 1,
|
||||
// @ts-expect-error not present on v1 secrets
|
||||
secretKey: secret.key as string,
|
||||
// @ts-expect-error not present on v1 secrets
|
||||
secretMetadata: secret.secretMetadata as ResourceMetadataDTO
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (updated.length) {
|
||||
if (updated.length > 1) {
|
||||
secretMutationEvents.push({
|
||||
type: EventType.UPDATE_SECRETS,
|
||||
metadata: {
|
||||
environment,
|
||||
secretPath: folder.path,
|
||||
secrets: updated.map((secret) => ({
|
||||
secretId: secret.id,
|
||||
secretVersion: secret.version,
|
||||
// @ts-expect-error not present on v1 secrets
|
||||
secretKey: secret.key as string,
|
||||
// @ts-expect-error not present on v1 secrets
|
||||
secretMetadata: secret.secretMetadata as ResourceMetadataDTO
|
||||
}))
|
||||
}
|
||||
});
|
||||
} else {
|
||||
const [secret] = updated;
|
||||
secretMutationEvents.push({
|
||||
type: EventType.UPDATE_SECRET,
|
||||
metadata: {
|
||||
environment,
|
||||
secretPath: folder.path,
|
||||
secretId: secret.id,
|
||||
secretVersion: secret.version,
|
||||
// @ts-expect-error not present on v1 secrets
|
||||
secretKey: secret.key as string,
|
||||
// @ts-expect-error not present on v1 secrets
|
||||
secretMetadata: secret.secretMetadata as ResourceMetadataDTO
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (deleted.length) {
|
||||
if (deleted.length > 1) {
|
||||
secretMutationEvents.push({
|
||||
type: EventType.DELETE_SECRETS,
|
||||
metadata: {
|
||||
environment,
|
||||
secretPath: folder.path,
|
||||
secrets: deleted.map((secret) => ({
|
||||
secretId: secret.id,
|
||||
secretVersion: secret.version,
|
||||
// @ts-expect-error not present on v1 secrets
|
||||
secretKey: secret.key as string
|
||||
}))
|
||||
}
|
||||
});
|
||||
} else {
|
||||
const [secret] = deleted;
|
||||
secretMutationEvents.push({
|
||||
type: EventType.DELETE_SECRET,
|
||||
metadata: {
|
||||
environment,
|
||||
secretPath: folder.path,
|
||||
secretId: secret.id,
|
||||
secretVersion: secret.version,
|
||||
// @ts-expect-error not present on v1 secrets
|
||||
secretKey: secret.key as string
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return { ...mergeStatus, projectId, secretMutationEvents };
|
||||
};
|
||||
|
||||
// function to save secret change to secret approval
|
||||
@ -980,8 +1086,7 @@ export const secretApprovalRequestServiceFactory = ({
|
||||
actorId,
|
||||
projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.SecretManager
|
||||
actorOrgId
|
||||
});
|
||||
|
||||
throwIfMissingSecretReadValueOrDescribePermission(permission, ProjectPermissionSecretActions.ReadValue, {
|
||||
@ -1271,8 +1376,7 @@ export const secretApprovalRequestServiceFactory = ({
|
||||
actorId,
|
||||
projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.SecretManager
|
||||
actorOrgId
|
||||
});
|
||||
const folder = await folderDAL.findBySecretPath(projectId, environment, secretPath);
|
||||
if (!folder)
|
||||
|
@ -84,7 +84,7 @@ export type TReviewRequestDTO = {
|
||||
comment?: string;
|
||||
} & Omit<TProjectPermission, "projectId">;
|
||||
|
||||
export type TApprovalRequestCountDTO = TProjectPermission;
|
||||
export type TApprovalRequestCountDTO = TProjectPermission & { policyId?: string };
|
||||
|
||||
export type TListApprovalsDTO = {
|
||||
projectId: string;
|
||||
|
@ -166,7 +166,9 @@ export const secretRotationV2QueueServiceFactory = async ({
|
||||
secretPath: folder.path,
|
||||
environment: environment.name,
|
||||
projectName: project.name,
|
||||
rotationUrl: encodeURI(`${appCfg.SITE_URL}/secret-manager/${projectId}/secrets/${environment.slug}`)
|
||||
rotationUrl: encodeURI(
|
||||
`${appCfg.SITE_URL}/projects/${projectId}/secret-manager/secrets/${environment.slug}`
|
||||
)
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
|
@ -2,7 +2,7 @@ import { ForbiddenError, subject } from "@casl/ability";
|
||||
import { Knex } from "knex";
|
||||
import isEqual from "lodash.isequal";
|
||||
|
||||
import { ActionProjectType, SecretType, TableName } from "@app/db/schemas";
|
||||
import { SecretType, TableName } from "@app/db/schemas";
|
||||
import { EventType, TAuditLogServiceFactory } from "@app/ee/services/audit-log/audit-log-types";
|
||||
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
|
||||
import { hasSecretReadValueOrDescribePermission } from "@app/ee/services/permission/permission-fns";
|
||||
@ -218,7 +218,7 @@ export const secretRotationV2ServiceFactory = ({
|
||||
actorId: actor.id,
|
||||
actorAuthMethod: actor.authMethod,
|
||||
actorOrgId: actor.orgId,
|
||||
actionProjectType: ActionProjectType.SecretManager,
|
||||
|
||||
projectId
|
||||
});
|
||||
|
||||
@ -269,7 +269,7 @@ export const secretRotationV2ServiceFactory = ({
|
||||
actorId: actor.id,
|
||||
actorAuthMethod: actor.authMethod,
|
||||
actorOrgId: actor.orgId,
|
||||
actionProjectType: ActionProjectType.SecretManager,
|
||||
|
||||
projectId
|
||||
});
|
||||
|
||||
@ -315,7 +315,7 @@ export const secretRotationV2ServiceFactory = ({
|
||||
actorId: actor.id,
|
||||
actorAuthMethod: actor.authMethod,
|
||||
actorOrgId: actor.orgId,
|
||||
actionProjectType: ActionProjectType.SecretManager,
|
||||
|
||||
projectId
|
||||
});
|
||||
|
||||
@ -380,7 +380,7 @@ export const secretRotationV2ServiceFactory = ({
|
||||
actorId: actor.id,
|
||||
actorAuthMethod: actor.authMethod,
|
||||
actorOrgId: actor.orgId,
|
||||
actionProjectType: ActionProjectType.SecretManager,
|
||||
|
||||
projectId
|
||||
});
|
||||
|
||||
@ -424,7 +424,7 @@ export const secretRotationV2ServiceFactory = ({
|
||||
actorId: actor.id,
|
||||
actorAuthMethod: actor.authMethod,
|
||||
actorOrgId: actor.orgId,
|
||||
actionProjectType: ActionProjectType.SecretManager,
|
||||
|
||||
projectId
|
||||
});
|
||||
|
||||
@ -625,7 +625,7 @@ export const secretRotationV2ServiceFactory = ({
|
||||
actorId: actor.id,
|
||||
actorAuthMethod: actor.authMethod,
|
||||
actorOrgId: actor.orgId,
|
||||
actionProjectType: ActionProjectType.SecretManager,
|
||||
|
||||
projectId
|
||||
});
|
||||
|
||||
@ -775,7 +775,7 @@ export const secretRotationV2ServiceFactory = ({
|
||||
actorId: actor.id,
|
||||
actorAuthMethod: actor.authMethod,
|
||||
actorOrgId: actor.orgId,
|
||||
actionProjectType: ActionProjectType.SecretManager,
|
||||
|
||||
projectId
|
||||
});
|
||||
|
||||
@ -1105,7 +1105,7 @@ export const secretRotationV2ServiceFactory = ({
|
||||
actorId: actor.id,
|
||||
actorAuthMethod: actor.authMethod,
|
||||
actorOrgId: actor.orgId,
|
||||
actionProjectType: ActionProjectType.SecretManager,
|
||||
|
||||
projectId
|
||||
});
|
||||
|
||||
@ -1152,7 +1152,7 @@ export const secretRotationV2ServiceFactory = ({
|
||||
actorId: actor.id,
|
||||
actorAuthMethod: actor.authMethod,
|
||||
actorOrgId: actor.orgId,
|
||||
actionProjectType: ActionProjectType.SecretManager,
|
||||
|
||||
projectId
|
||||
});
|
||||
|
||||
@ -1204,7 +1204,7 @@ export const secretRotationV2ServiceFactory = ({
|
||||
actorId: actor.id,
|
||||
actorAuthMethod: actor.authMethod,
|
||||
actorOrgId: actor.orgId,
|
||||
actionProjectType: ActionProjectType.SecretManager,
|
||||
|
||||
projectId
|
||||
});
|
||||
|
||||
@ -1320,8 +1320,7 @@ export const secretRotationV2ServiceFactory = ({
|
||||
actorId: actor.id,
|
||||
projectId,
|
||||
actorAuthMethod: actor.authMethod,
|
||||
actorOrgId: actor.orgId,
|
||||
actionProjectType: ActionProjectType.SecretManager
|
||||
actorOrgId: actor.orgId
|
||||
});
|
||||
|
||||
const permissiveFolderMappings = folderMappings.filter(({ path, environment }) =>
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { ForbiddenError, subject } from "@casl/ability";
|
||||
import Ajv from "ajv";
|
||||
|
||||
import { ActionProjectType, ProjectVersion, TableName } from "@app/db/schemas";
|
||||
import { ProjectVersion, TableName } from "@app/db/schemas";
|
||||
import { decryptSymmetric128BitHexKeyUTF8 } from "@app/lib/crypto/encryption";
|
||||
import { BadRequestError, NotFoundError } from "@app/lib/errors";
|
||||
import { TProjectPermission } from "@app/lib/types";
|
||||
@ -66,8 +66,7 @@ export const secretRotationServiceFactory = ({
|
||||
actorId,
|
||||
projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.SecretManager
|
||||
actorOrgId
|
||||
});
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionSecretRotationActions.Read,
|
||||
@ -98,8 +97,7 @@ export const secretRotationServiceFactory = ({
|
||||
actorId,
|
||||
projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.SecretManager
|
||||
actorOrgId
|
||||
});
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionSecretRotationActions.Read,
|
||||
@ -215,8 +213,7 @@ export const secretRotationServiceFactory = ({
|
||||
actorId,
|
||||
projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.SecretManager
|
||||
actorOrgId
|
||||
});
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionSecretRotationActions.Read,
|
||||
@ -264,8 +261,7 @@ export const secretRotationServiceFactory = ({
|
||||
actorId,
|
||||
projectId: project.id,
|
||||
actorAuthMethod,
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.SecretManager
|
||||
actorOrgId
|
||||
});
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionSecretRotationActions.Edit,
|
||||
@ -285,8 +281,7 @@ export const secretRotationServiceFactory = ({
|
||||
actorId,
|
||||
projectId: doc.projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.SecretManager
|
||||
actorOrgId
|
||||
});
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionSecretRotationActions.Delete,
|
||||
|
@ -0,0 +1,9 @@
|
||||
import { SecretScanningDataSource } from "@app/ee/services/secret-scanning-v2/secret-scanning-v2-enums";
|
||||
import { TSecretScanningDataSourceListItem } from "@app/ee/services/secret-scanning-v2/secret-scanning-v2-types";
|
||||
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
|
||||
|
||||
export const BITBUCKET_SECRET_SCANNING_DATA_SOURCE_LIST_OPTION: TSecretScanningDataSourceListItem = {
|
||||
name: "Bitbucket",
|
||||
type: SecretScanningDataSource.Bitbucket,
|
||||
connection: AppConnection.Bitbucket
|
||||
};
|
@ -0,0 +1,314 @@
|
||||
import { join } from "path";
|
||||
|
||||
import { scanContentAndGetFindings } from "@app/ee/services/secret-scanning/secret-scanning-queue/secret-scanning-fns";
|
||||
import { SecretMatch } from "@app/ee/services/secret-scanning/secret-scanning-queue/secret-scanning-queue-types";
|
||||
import {
|
||||
SecretScanningFindingSeverity,
|
||||
SecretScanningResource
|
||||
} from "@app/ee/services/secret-scanning-v2/secret-scanning-v2-enums";
|
||||
import {
|
||||
cloneRepository,
|
||||
convertPatchLineToFileLineNumber,
|
||||
replaceNonChangesWithNewlines
|
||||
} from "@app/ee/services/secret-scanning-v2/secret-scanning-v2-fns";
|
||||
import {
|
||||
TSecretScanningFactoryGetDiffScanFindingsPayload,
|
||||
TSecretScanningFactoryGetDiffScanResourcePayload,
|
||||
TSecretScanningFactoryGetFullScanPath,
|
||||
TSecretScanningFactoryInitialize,
|
||||
TSecretScanningFactoryListRawResources,
|
||||
TSecretScanningFactoryPostInitialization,
|
||||
TSecretScanningFactoryTeardown
|
||||
} from "@app/ee/services/secret-scanning-v2/secret-scanning-v2-types";
|
||||
import { getConfig } from "@app/lib/config/env";
|
||||
import { request } from "@app/lib/config/request";
|
||||
import { titleCaseToCamelCase } from "@app/lib/fn";
|
||||
import { logger } from "@app/lib/logger";
|
||||
import { alphaNumericNanoId } from "@app/lib/nanoid";
|
||||
import { BasicRepositoryRegex } from "@app/lib/regex";
|
||||
import {
|
||||
getBitbucketUser,
|
||||
listBitbucketRepositories,
|
||||
TBitbucketConnection
|
||||
} from "@app/services/app-connection/bitbucket";
|
||||
import { IntegrationUrls } from "@app/services/integration-auth/integration-list";
|
||||
|
||||
import {
|
||||
TBitbucketDataSourceCredentials,
|
||||
TBitbucketDataSourceInput,
|
||||
TBitbucketDataSourceWithConnection,
|
||||
TQueueBitbucketResourceDiffScan
|
||||
} from "./bitbucket-secret-scanning-types";
|
||||
|
||||
export const BitbucketSecretScanningFactory = () => {
|
||||
const initialize: TSecretScanningFactoryInitialize<
|
||||
TBitbucketDataSourceInput,
|
||||
TBitbucketConnection,
|
||||
TBitbucketDataSourceCredentials
|
||||
> = async ({ connection, payload }, callback) => {
|
||||
const cfg = getConfig();
|
||||
|
||||
const { email, apiToken } = connection.credentials;
|
||||
const authHeader = `Basic ${Buffer.from(`${email}:${apiToken}`).toString("base64")}`;
|
||||
|
||||
const { data } = await request.post<{ uuid: string }>(
|
||||
`${IntegrationUrls.BITBUCKET_API_URL}/2.0/workspaces/${encodeURIComponent(payload.config.workspaceSlug)}/hooks`,
|
||||
{
|
||||
description: "Infisical webhook for push events",
|
||||
url: `${cfg.SITE_URL}/secret-scanning/webhooks/bitbucket`,
|
||||
active: false,
|
||||
events: ["repo:push"]
|
||||
},
|
||||
{
|
||||
headers: {
|
||||
Authorization: authHeader,
|
||||
Accept: "application/json"
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
return callback({
|
||||
credentials: { webhookId: data.uuid, webhookSecret: alphaNumericNanoId(64) }
|
||||
});
|
||||
};
|
||||
|
||||
const postInitialization: TSecretScanningFactoryPostInitialization<
|
||||
TBitbucketDataSourceInput,
|
||||
TBitbucketConnection,
|
||||
TBitbucketDataSourceCredentials
|
||||
> = async ({ dataSourceId, credentials, connection, payload }) => {
|
||||
const { email, apiToken } = connection.credentials;
|
||||
const { webhookId, webhookSecret } = credentials;
|
||||
|
||||
const authHeader = `Basic ${Buffer.from(`${email}:${apiToken}`).toString("base64")}`;
|
||||
|
||||
const cfg = getConfig();
|
||||
const newWebhookUrl = `${cfg.SITE_URL}/secret-scanning/webhooks/bitbucket?dataSourceId=${dataSourceId}`;
|
||||
|
||||
await request.put(
|
||||
`${IntegrationUrls.BITBUCKET_API_URL}/2.0/workspaces/${encodeURIComponent(payload.config.workspaceSlug)}/hooks/${webhookId}`,
|
||||
{
|
||||
description: "Infisical webhook for push events",
|
||||
url: newWebhookUrl,
|
||||
active: true,
|
||||
events: ["repo:push"],
|
||||
secret: webhookSecret
|
||||
},
|
||||
{
|
||||
headers: {
|
||||
Authorization: authHeader,
|
||||
Accept: "application/json"
|
||||
}
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
const teardown: TSecretScanningFactoryTeardown<
|
||||
TBitbucketDataSourceWithConnection,
|
||||
TBitbucketDataSourceCredentials
|
||||
> = async ({ credentials, dataSource }) => {
|
||||
const {
|
||||
connection: {
|
||||
credentials: { email, apiToken }
|
||||
},
|
||||
config
|
||||
} = dataSource;
|
||||
const { webhookId } = credentials;
|
||||
|
||||
const authHeader = `Basic ${Buffer.from(`${email}:${apiToken}`).toString("base64")}`;
|
||||
|
||||
try {
|
||||
await request.delete(
|
||||
`${IntegrationUrls.BITBUCKET_API_URL}/2.0/workspaces/${config.workspaceSlug}/hooks/${webhookId}`,
|
||||
{
|
||||
headers: {
|
||||
Authorization: authHeader,
|
||||
Accept: "application/json"
|
||||
}
|
||||
}
|
||||
);
|
||||
} catch (err) {
|
||||
logger.error(`teardown: Bitbucket - Failed to call delete on webhook [webhookId=${webhookId}]`);
|
||||
}
|
||||
};
|
||||
|
||||
const listRawResources: TSecretScanningFactoryListRawResources<TBitbucketDataSourceWithConnection> = async (
|
||||
dataSource
|
||||
) => {
|
||||
const {
|
||||
connection,
|
||||
config: { includeRepos, workspaceSlug }
|
||||
} = dataSource;
|
||||
|
||||
const repos = await listBitbucketRepositories(connection, workspaceSlug);
|
||||
|
||||
const filteredRepos: typeof repos = [];
|
||||
if (includeRepos.includes("*")) {
|
||||
filteredRepos.push(...repos);
|
||||
} else {
|
||||
filteredRepos.push(...repos.filter((repo) => includeRepos.includes(repo.full_name)));
|
||||
}
|
||||
|
||||
return filteredRepos.map(({ full_name, uuid }) => ({
|
||||
name: full_name,
|
||||
externalId: uuid,
|
||||
type: SecretScanningResource.Repository
|
||||
}));
|
||||
};
|
||||
|
||||
const getFullScanPath: TSecretScanningFactoryGetFullScanPath<TBitbucketDataSourceWithConnection> = async ({
|
||||
dataSource,
|
||||
resourceName,
|
||||
tempFolder
|
||||
}) => {
|
||||
const {
|
||||
connection: {
|
||||
credentials: { apiToken, email }
|
||||
}
|
||||
} = dataSource;
|
||||
|
||||
const repoPath = join(tempFolder, "repo.git");
|
||||
|
||||
if (!BasicRepositoryRegex.test(resourceName)) {
|
||||
throw new Error("Invalid Bitbucket repository name");
|
||||
}
|
||||
|
||||
const { username } = await getBitbucketUser({ email, apiToken });
|
||||
|
||||
await cloneRepository({
|
||||
cloneUrl: `https://${encodeURIComponent(username)}:${apiToken}@bitbucket.org/${resourceName}.git`,
|
||||
repoPath
|
||||
});
|
||||
|
||||
return repoPath;
|
||||
};
|
||||
|
||||
const getDiffScanResourcePayload: TSecretScanningFactoryGetDiffScanResourcePayload<
|
||||
TQueueBitbucketResourceDiffScan["payload"]
|
||||
> = ({ repository }) => {
|
||||
return {
|
||||
name: repository.full_name,
|
||||
externalId: repository.uuid,
|
||||
type: SecretScanningResource.Repository
|
||||
};
|
||||
};
|
||||
|
||||
const getDiffScanFindingsPayload: TSecretScanningFactoryGetDiffScanFindingsPayload<
|
||||
TBitbucketDataSourceWithConnection,
|
||||
TQueueBitbucketResourceDiffScan["payload"]
|
||||
> = async ({ dataSource, payload, resourceName, configPath }) => {
|
||||
const {
|
||||
connection: {
|
||||
credentials: { apiToken, email }
|
||||
}
|
||||
} = dataSource;
|
||||
|
||||
const { push, repository } = payload;
|
||||
|
||||
const allFindings: SecretMatch[] = [];
|
||||
|
||||
const authHeader = `Basic ${Buffer.from(`${email}:${apiToken}`).toString("base64")}`;
|
||||
|
||||
for (const change of push.changes) {
|
||||
for (const commit of change.commits) {
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
const { data: diffstat } = await request.get<{
|
||||
values: {
|
||||
status: "added" | "modified" | "removed" | "renamed";
|
||||
new?: { path: string };
|
||||
old?: { path: string };
|
||||
}[];
|
||||
}>(`${IntegrationUrls.BITBUCKET_API_URL}/2.0/repositories/${repository.full_name}/diffstat/${commit.hash}`, {
|
||||
headers: {
|
||||
Authorization: authHeader,
|
||||
Accept: "application/json"
|
||||
}
|
||||
});
|
||||
|
||||
// eslint-disable-next-line no-continue
|
||||
if (!diffstat.values) continue;
|
||||
|
||||
for (const file of diffstat.values) {
|
||||
if ((file.status === "added" || file.status === "modified") && file.new?.path) {
|
||||
const filePath = file.new.path;
|
||||
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
const { data: patch } = await request.get<string>(
|
||||
`https://api.bitbucket.org/2.0/repositories/${repository.full_name}/diff/${commit.hash}`,
|
||||
{
|
||||
params: {
|
||||
path: filePath
|
||||
},
|
||||
headers: {
|
||||
Authorization: authHeader
|
||||
},
|
||||
responseType: "text"
|
||||
}
|
||||
);
|
||||
|
||||
// eslint-disable-next-line no-continue
|
||||
if (!patch) continue;
|
||||
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
const findings = await scanContentAndGetFindings(replaceNonChangesWithNewlines(`\n${patch}`), configPath);
|
||||
|
||||
const adjustedFindings = findings.map((finding) => {
|
||||
const startLine = convertPatchLineToFileLineNumber(patch, finding.StartLine);
|
||||
const endLine =
|
||||
finding.StartLine === finding.EndLine
|
||||
? startLine
|
||||
: convertPatchLineToFileLineNumber(patch, finding.EndLine);
|
||||
const startColumn = finding.StartColumn - 1; // subtract 1 for +
|
||||
const endColumn = finding.EndColumn - 1; // subtract 1 for +
|
||||
const authorName = commit.author.user?.display_name || commit.author.raw.split(" <")[0];
|
||||
const emailMatch = commit.author.raw.match(/<(.*)>/);
|
||||
const authorEmail = emailMatch?.[1] ?? "";
|
||||
|
||||
return {
|
||||
...finding,
|
||||
StartLine: startLine,
|
||||
EndLine: endLine,
|
||||
StartColumn: startColumn,
|
||||
EndColumn: endColumn,
|
||||
File: filePath,
|
||||
Commit: commit.hash,
|
||||
Author: authorName,
|
||||
Email: authorEmail,
|
||||
Message: commit.message,
|
||||
Fingerprint: `${commit.hash}:${filePath}:${finding.RuleID}:${startLine}:${startColumn}`,
|
||||
Date: commit.date,
|
||||
Link: `https://bitbucket.org/${resourceName}/src/${commit.hash}/${filePath}#lines-${startLine}`
|
||||
};
|
||||
});
|
||||
|
||||
allFindings.push(...adjustedFindings);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return allFindings.map(
|
||||
({
|
||||
// discard match and secret as we don't want to store
|
||||
Match,
|
||||
Secret,
|
||||
...finding
|
||||
}) => ({
|
||||
details: titleCaseToCamelCase(finding),
|
||||
fingerprint: finding.Fingerprint,
|
||||
severity: SecretScanningFindingSeverity.High,
|
||||
rule: finding.RuleID
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
return {
|
||||
initialize,
|
||||
postInitialization,
|
||||
listRawResources,
|
||||
getFullScanPath,
|
||||
getDiffScanResourcePayload,
|
||||
getDiffScanFindingsPayload,
|
||||
teardown
|
||||
};
|
||||
};
|
@ -0,0 +1,97 @@
|
||||
import { z } from "zod";
|
||||
|
||||
import {
|
||||
SecretScanningDataSource,
|
||||
SecretScanningResource
|
||||
} from "@app/ee/services/secret-scanning-v2/secret-scanning-v2-enums";
|
||||
import {
|
||||
BaseCreateSecretScanningDataSourceSchema,
|
||||
BaseSecretScanningDataSourceSchema,
|
||||
BaseSecretScanningFindingSchema,
|
||||
BaseUpdateSecretScanningDataSourceSchema,
|
||||
GitRepositoryScanFindingDetailsSchema
|
||||
} from "@app/ee/services/secret-scanning-v2/secret-scanning-v2-schemas";
|
||||
import { SecretScanningDataSources } from "@app/lib/api-docs";
|
||||
import { BasicRepositoryRegex } from "@app/lib/regex";
|
||||
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
|
||||
|
||||
export const BitbucketDataSourceConfigSchema = z.object({
|
||||
workspaceSlug: z
|
||||
.string()
|
||||
.min(1, "Workspace slug required")
|
||||
.max(128)
|
||||
.describe(SecretScanningDataSources.CONFIG.BITBUCKET.workspaceSlug),
|
||||
includeRepos: z
|
||||
.array(
|
||||
z
|
||||
.string()
|
||||
.min(1)
|
||||
.max(256)
|
||||
.refine((value) => value === "*" || BasicRepositoryRegex.test(value), "Invalid repository name format")
|
||||
)
|
||||
.nonempty("One or more repositories required")
|
||||
.max(100, "Cannot configure more than 100 repositories")
|
||||
.default(["*"])
|
||||
.describe(SecretScanningDataSources.CONFIG.BITBUCKET.includeRepos)
|
||||
});
|
||||
|
||||
export const BitbucketDataSourceSchema = BaseSecretScanningDataSourceSchema({
|
||||
type: SecretScanningDataSource.Bitbucket,
|
||||
isConnectionRequired: true
|
||||
})
|
||||
.extend({
|
||||
config: BitbucketDataSourceConfigSchema
|
||||
})
|
||||
.describe(
|
||||
JSON.stringify({
|
||||
title: "Bitbucket"
|
||||
})
|
||||
);
|
||||
|
||||
export const CreateBitbucketDataSourceSchema = BaseCreateSecretScanningDataSourceSchema({
|
||||
type: SecretScanningDataSource.Bitbucket,
|
||||
isConnectionRequired: true
|
||||
})
|
||||
.extend({
|
||||
config: BitbucketDataSourceConfigSchema
|
||||
})
|
||||
.describe(
|
||||
JSON.stringify({
|
||||
title: "Bitbucket"
|
||||
})
|
||||
);
|
||||
|
||||
export const UpdateBitbucketDataSourceSchema = BaseUpdateSecretScanningDataSourceSchema(
|
||||
SecretScanningDataSource.Bitbucket
|
||||
)
|
||||
.extend({
|
||||
config: BitbucketDataSourceConfigSchema.optional()
|
||||
})
|
||||
.describe(
|
||||
JSON.stringify({
|
||||
title: "Bitbucket"
|
||||
})
|
||||
);
|
||||
|
||||
export const BitbucketDataSourceListItemSchema = z
|
||||
.object({
|
||||
name: z.literal("Bitbucket"),
|
||||
connection: z.literal(AppConnection.Bitbucket),
|
||||
type: z.literal(SecretScanningDataSource.Bitbucket)
|
||||
})
|
||||
.describe(
|
||||
JSON.stringify({
|
||||
title: "Bitbucket"
|
||||
})
|
||||
);
|
||||
|
||||
export const BitbucketFindingSchema = BaseSecretScanningFindingSchema.extend({
|
||||
resourceType: z.literal(SecretScanningResource.Repository),
|
||||
dataSourceType: z.literal(SecretScanningDataSource.Bitbucket),
|
||||
details: GitRepositoryScanFindingDetailsSchema
|
||||
});
|
||||
|
||||
export const BitbucketDataSourceCredentialsSchema = z.object({
|
||||
webhookId: z.string(),
|
||||
webhookSecret: z.string()
|
||||
});
|
@ -0,0 +1,104 @@
|
||||
import crypto from "crypto";
|
||||
|
||||
import { TSecretScanningV2DALFactory } from "@app/ee/services/secret-scanning-v2/secret-scanning-v2-dal";
|
||||
import { SecretScanningDataSource } from "@app/ee/services/secret-scanning-v2/secret-scanning-v2-enums";
|
||||
import { TSecretScanningV2QueueServiceFactory } from "@app/ee/services/secret-scanning-v2/secret-scanning-v2-queue";
|
||||
import { logger } from "@app/lib/logger";
|
||||
import { TKmsServiceFactory } from "@app/services/kms/kms-service";
|
||||
import { KmsDataKey } from "@app/services/kms/kms-types";
|
||||
|
||||
import {
|
||||
TBitbucketDataSource,
|
||||
TBitbucketDataSourceCredentials,
|
||||
TBitbucketPushEvent
|
||||
} from "./bitbucket-secret-scanning-types";
|
||||
|
||||
export const bitbucketSecretScanningService = (
|
||||
secretScanningV2DAL: TSecretScanningV2DALFactory,
|
||||
secretScanningV2Queue: Pick<TSecretScanningV2QueueServiceFactory, "queueResourceDiffScan">,
|
||||
kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey">
|
||||
) => {
|
||||
const handlePushEvent = async (
|
||||
payload: TBitbucketPushEvent & { dataSourceId: string; receivedSignature: string; bodyString: string }
|
||||
) => {
|
||||
const { push, repository, bodyString, receivedSignature } = payload;
|
||||
|
||||
if (!push?.changes?.length || !repository?.workspace?.uuid) {
|
||||
logger.warn(
|
||||
`secretScanningV2PushEvent: Bitbucket - Insufficient data [changes=${
|
||||
push?.changes?.length ?? 0
|
||||
}] [repository=${repository?.name}] [workspaceUuid=${repository?.workspace?.uuid}]`
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const dataSource = (await secretScanningV2DAL.dataSources.findOne({
|
||||
id: payload.dataSourceId,
|
||||
type: SecretScanningDataSource.Bitbucket
|
||||
})) as TBitbucketDataSource | undefined;
|
||||
|
||||
if (!dataSource) {
|
||||
logger.error(
|
||||
`secretScanningV2PushEvent: Bitbucket - Could not find data source [workspaceUuid=${repository.workspace.uuid}]`
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const {
|
||||
isAutoScanEnabled,
|
||||
config: { includeRepos },
|
||||
encryptedCredentials,
|
||||
projectId
|
||||
} = dataSource;
|
||||
|
||||
if (!encryptedCredentials) {
|
||||
logger.info(
|
||||
`secretScanningV2PushEvent: Bitbucket - Could not find encrypted credentials [dataSourceId=${dataSource.id}] [workspaceUuid=${repository.workspace.uuid}]`
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const { decryptor } = await kmsService.createCipherPairWithDataKey({
|
||||
type: KmsDataKey.SecretManager,
|
||||
projectId
|
||||
});
|
||||
|
||||
const decryptedCredentials = decryptor({ cipherTextBlob: encryptedCredentials });
|
||||
|
||||
const credentials = JSON.parse(decryptedCredentials.toString()) as TBitbucketDataSourceCredentials;
|
||||
|
||||
const hmac = crypto.createHmac("sha256", credentials.webhookSecret);
|
||||
hmac.update(bodyString);
|
||||
const calculatedSignature = hmac.digest("hex");
|
||||
|
||||
if (calculatedSignature !== receivedSignature) {
|
||||
logger.error(
|
||||
`secretScanningV2PushEvent: Bitbucket - Invalid signature for webhook [dataSourceId=${dataSource.id}] [workspaceUuid=${repository.workspace.uuid}]`
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isAutoScanEnabled) {
|
||||
logger.info(
|
||||
`secretScanningV2PushEvent: Bitbucket - ignoring due to auto scan disabled [dataSourceId=${dataSource.id}] [workspaceUuid=${repository.workspace.uuid}]`
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (includeRepos.includes("*") || includeRepos.includes(repository.full_name)) {
|
||||
await secretScanningV2Queue.queueResourceDiffScan({
|
||||
dataSourceType: SecretScanningDataSource.Bitbucket,
|
||||
payload,
|
||||
dataSourceId: dataSource.id
|
||||
});
|
||||
} else {
|
||||
logger.info(
|
||||
`secretScanningV2PushEvent: Bitbucket - ignoring due to repository not being present in config [workspaceUuid=${repository.workspace.uuid}] [dataSourceId=${dataSource.id}]`
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
handlePushEvent
|
||||
};
|
||||
};
|
@ -0,0 +1,85 @@
|
||||
import { z } from "zod";
|
||||
|
||||
import { SecretScanningDataSource } from "@app/ee/services/secret-scanning-v2/secret-scanning-v2-enums";
|
||||
import { TBitbucketConnection } from "@app/services/app-connection/bitbucket";
|
||||
|
||||
import {
|
||||
BitbucketDataSourceCredentialsSchema,
|
||||
BitbucketDataSourceListItemSchema,
|
||||
BitbucketDataSourceSchema,
|
||||
BitbucketFindingSchema,
|
||||
CreateBitbucketDataSourceSchema
|
||||
} from "./bitbucket-secret-scanning-schemas";
|
||||
|
||||
export type TBitbucketDataSource = z.infer<typeof BitbucketDataSourceSchema>;
|
||||
|
||||
export type TBitbucketDataSourceInput = z.infer<typeof CreateBitbucketDataSourceSchema>;
|
||||
|
||||
export type TBitbucketDataSourceListItem = z.infer<typeof BitbucketDataSourceListItemSchema>;
|
||||
|
||||
export type TBitbucketDataSourceCredentials = z.infer<typeof BitbucketDataSourceCredentialsSchema>;
|
||||
|
||||
export type TBitbucketFinding = z.infer<typeof BitbucketFindingSchema>;
|
||||
|
||||
export type TBitbucketDataSourceWithConnection = TBitbucketDataSource & {
|
||||
connection: TBitbucketConnection;
|
||||
};
|
||||
|
||||
export type TBitbucketPushEventRepository = {
|
||||
full_name: string;
|
||||
name: string;
|
||||
workspace: {
|
||||
slug: string;
|
||||
uuid: string;
|
||||
};
|
||||
uuid: string;
|
||||
};
|
||||
|
||||
export type TBitbucketPushEventCommit = {
|
||||
hash: string;
|
||||
message: string;
|
||||
author: {
|
||||
raw: string;
|
||||
user?: {
|
||||
display_name: string;
|
||||
uuid: string;
|
||||
nickname: string;
|
||||
};
|
||||
};
|
||||
date: string;
|
||||
};
|
||||
|
||||
export type TBitbucketPushEventChange = {
|
||||
new?: {
|
||||
name: string;
|
||||
type: string;
|
||||
};
|
||||
old?: {
|
||||
name: string;
|
||||
type: string;
|
||||
};
|
||||
created: boolean;
|
||||
closed: boolean;
|
||||
forced: boolean;
|
||||
commits: TBitbucketPushEventCommit[];
|
||||
};
|
||||
|
||||
export type TBitbucketPushEvent = {
|
||||
push: {
|
||||
changes: TBitbucketPushEventChange[];
|
||||
};
|
||||
repository: TBitbucketPushEventRepository;
|
||||
actor: {
|
||||
display_name: string;
|
||||
uuid: string;
|
||||
nickname: string;
|
||||
};
|
||||
};
|
||||
|
||||
export type TQueueBitbucketResourceDiffScan = {
|
||||
dataSourceType: SecretScanningDataSource.Bitbucket;
|
||||
payload: TBitbucketPushEvent & { dataSourceId: string };
|
||||
dataSourceId: string;
|
||||
resourceId: string;
|
||||
scanId: string;
|
||||
};
|
@ -0,0 +1,3 @@
|
||||
export * from "./bitbucket-secret-scanning-constants";
|
||||
export * from "./bitbucket-secret-scanning-schemas";
|
||||
export * from "./bitbucket-secret-scanning-types";
|
@ -19,18 +19,23 @@ import {
|
||||
TSecretScanningFactoryGetFullScanPath,
|
||||
TSecretScanningFactoryInitialize,
|
||||
TSecretScanningFactoryListRawResources,
|
||||
TSecretScanningFactoryPostInitialization
|
||||
TSecretScanningFactoryPostInitialization,
|
||||
TSecretScanningFactoryTeardown
|
||||
} from "@app/ee/services/secret-scanning-v2/secret-scanning-v2-types";
|
||||
import { getConfig } from "@app/lib/config/env";
|
||||
import { BadRequestError } from "@app/lib/errors";
|
||||
import { titleCaseToCamelCase } from "@app/lib/fn";
|
||||
import { GitHubRepositoryRegex } from "@app/lib/regex";
|
||||
import { BasicRepositoryRegex } from "@app/lib/regex";
|
||||
import { listGitHubRadarRepositories, TGitHubRadarConnection } from "@app/services/app-connection/github-radar";
|
||||
|
||||
import { TGitHubDataSourceWithConnection, TQueueGitHubResourceDiffScan } from "./github-secret-scanning-types";
|
||||
import {
|
||||
TGitHubDataSourceInput,
|
||||
TGitHubDataSourceWithConnection,
|
||||
TQueueGitHubResourceDiffScan
|
||||
} from "./github-secret-scanning-types";
|
||||
|
||||
export const GitHubSecretScanningFactory = () => {
|
||||
const initialize: TSecretScanningFactoryInitialize<TGitHubRadarConnection> = async (
|
||||
const initialize: TSecretScanningFactoryInitialize<TGitHubDataSourceInput, TGitHubRadarConnection> = async (
|
||||
{ connection, secretScanningV2DAL },
|
||||
callback
|
||||
) => {
|
||||
@ -51,10 +56,17 @@ export const GitHubSecretScanningFactory = () => {
|
||||
});
|
||||
};
|
||||
|
||||
const postInitialization: TSecretScanningFactoryPostInitialization<TGitHubRadarConnection> = async () => {
|
||||
const postInitialization: TSecretScanningFactoryPostInitialization<
|
||||
TGitHubDataSourceInput,
|
||||
TGitHubRadarConnection
|
||||
> = async () => {
|
||||
// no post-initialization required
|
||||
};
|
||||
|
||||
const teardown: TSecretScanningFactoryTeardown<TGitHubDataSourceWithConnection> = async () => {
|
||||
// no termination required
|
||||
};
|
||||
|
||||
const listRawResources: TSecretScanningFactoryListRawResources<TGitHubDataSourceWithConnection> = async (
|
||||
dataSource
|
||||
) => {
|
||||
@ -107,7 +119,7 @@ export const GitHubSecretScanningFactory = () => {
|
||||
|
||||
const repoPath = join(tempFolder, "repo.git");
|
||||
|
||||
if (!GitHubRepositoryRegex.test(resourceName)) {
|
||||
if (!BasicRepositoryRegex.test(resourceName)) {
|
||||
throw new Error("Invalid GitHub repository name");
|
||||
}
|
||||
|
||||
@ -225,6 +237,7 @@ export const GitHubSecretScanningFactory = () => {
|
||||
listRawResources,
|
||||
getFullScanPath,
|
||||
getDiffScanResourcePayload,
|
||||
getDiffScanFindingsPayload
|
||||
getDiffScanFindingsPayload,
|
||||
teardown
|
||||
};
|
||||
};
|
||||
|
@ -12,7 +12,7 @@ import {
|
||||
GitRepositoryScanFindingDetailsSchema
|
||||
} from "@app/ee/services/secret-scanning-v2/secret-scanning-v2-schemas";
|
||||
import { SecretScanningDataSources } from "@app/lib/api-docs";
|
||||
import { GitHubRepositoryRegex } from "@app/lib/regex";
|
||||
import { BasicRepositoryRegex } from "@app/lib/regex";
|
||||
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
|
||||
|
||||
export const GitHubDataSourceConfigSchema = z.object({
|
||||
@ -22,7 +22,7 @@ export const GitHubDataSourceConfigSchema = z.object({
|
||||
.string()
|
||||
.min(1)
|
||||
.max(256)
|
||||
.refine((value) => value === "*" || GitHubRepositoryRegex.test(value), "Invalid repository name format")
|
||||
.refine((value) => value === "*" || BasicRepositoryRegex.test(value), "Invalid repository name format")
|
||||
)
|
||||
.nonempty("One or more repositories required")
|
||||
.max(100, "Cannot configure more than 100 repositories")
|
||||
|
@ -437,6 +437,7 @@ export const secretScanningV2DALFactory = (db: TDbClient) => {
|
||||
return {
|
||||
dataSources: {
|
||||
...dataSourceOrm,
|
||||
findRaw: dataSourceOrm.find,
|
||||
find: findDataSource,
|
||||
findById: findDataSourceById,
|
||||
findOne: findOneDataSource,
|
||||
|
@ -1,5 +1,6 @@
|
||||
export enum SecretScanningDataSource {
|
||||
GitHub = "github"
|
||||
GitHub = "github",
|
||||
Bitbucket = "bitbucket"
|
||||
}
|
||||
|
||||
export enum SecretScanningScanStatus {
|
||||
|
@ -1,19 +1,23 @@
|
||||
import { BitbucketSecretScanningFactory } from "@app/ee/services/secret-scanning-v2/bitbucket/bitbucket-secret-scanning-factory";
|
||||
import { GitHubSecretScanningFactory } from "@app/ee/services/secret-scanning-v2/github/github-secret-scanning-factory";
|
||||
|
||||
import { SecretScanningDataSource } from "./secret-scanning-v2-enums";
|
||||
import {
|
||||
TQueueSecretScanningResourceDiffScan,
|
||||
TSecretScanningDataSourceCredentials,
|
||||
TSecretScanningDataSourceInput,
|
||||
TSecretScanningDataSourceWithConnection,
|
||||
TSecretScanningFactory
|
||||
} from "./secret-scanning-v2-types";
|
||||
|
||||
type TSecretScanningFactoryImplementation = TSecretScanningFactory<
|
||||
TSecretScanningDataSourceWithConnection,
|
||||
TSecretScanningDataSourceCredentials,
|
||||
TQueueSecretScanningResourceDiffScan["payload"]
|
||||
TQueueSecretScanningResourceDiffScan["payload"],
|
||||
TSecretScanningDataSourceInput,
|
||||
TSecretScanningDataSourceCredentials
|
||||
>;
|
||||
|
||||
export const SECRET_SCANNING_FACTORY_MAP: Record<SecretScanningDataSource, TSecretScanningFactoryImplementation> = {
|
||||
[SecretScanningDataSource.GitHub]: GitHubSecretScanningFactory as TSecretScanningFactoryImplementation
|
||||
[SecretScanningDataSource.GitHub]: GitHubSecretScanningFactory as TSecretScanningFactoryImplementation,
|
||||
[SecretScanningDataSource.Bitbucket]: BitbucketSecretScanningFactory as TSecretScanningFactoryImplementation
|
||||
};
|
||||
|
@ -4,6 +4,7 @@ import RE2 from "re2";
|
||||
|
||||
import { readFindingsFile } from "@app/ee/services/secret-scanning/secret-scanning-queue/secret-scanning-fns";
|
||||
import { SecretMatch } from "@app/ee/services/secret-scanning/secret-scanning-queue/secret-scanning-queue-types";
|
||||
import { BITBUCKET_SECRET_SCANNING_DATA_SOURCE_LIST_OPTION } from "@app/ee/services/secret-scanning-v2/bitbucket";
|
||||
import { GITHUB_SECRET_SCANNING_DATA_SOURCE_LIST_OPTION } from "@app/ee/services/secret-scanning-v2/github";
|
||||
import { titleCaseToCamelCase } from "@app/lib/fn";
|
||||
|
||||
@ -11,7 +12,8 @@ import { SecretScanningDataSource, SecretScanningFindingSeverity } from "./secre
|
||||
import { TCloneRepository, TGetFindingsPayload, TSecretScanningDataSourceListItem } from "./secret-scanning-v2-types";
|
||||
|
||||
const SECRET_SCANNING_SOURCE_LIST_OPTIONS: Record<SecretScanningDataSource, TSecretScanningDataSourceListItem> = {
|
||||
[SecretScanningDataSource.GitHub]: GITHUB_SECRET_SCANNING_DATA_SOURCE_LIST_OPTION
|
||||
[SecretScanningDataSource.GitHub]: GITHUB_SECRET_SCANNING_DATA_SOURCE_LIST_OPTION,
|
||||
[SecretScanningDataSource.Bitbucket]: BITBUCKET_SECRET_SCANNING_DATA_SOURCE_LIST_OPTION
|
||||
};
|
||||
|
||||
export const listSecretScanningDataSourceOptions = () => {
|
||||
|
@ -2,13 +2,16 @@ import { SecretScanningDataSource } from "@app/ee/services/secret-scanning-v2/se
|
||||
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
|
||||
|
||||
export const SECRET_SCANNING_DATA_SOURCE_NAME_MAP: Record<SecretScanningDataSource, string> = {
|
||||
[SecretScanningDataSource.GitHub]: "GitHub"
|
||||
[SecretScanningDataSource.GitHub]: "GitHub",
|
||||
[SecretScanningDataSource.Bitbucket]: "Bitbucket"
|
||||
};
|
||||
|
||||
export const SECRET_SCANNING_DATA_SOURCE_CONNECTION_MAP: Record<SecretScanningDataSource, AppConnection> = {
|
||||
[SecretScanningDataSource.GitHub]: AppConnection.GitHubRadar
|
||||
[SecretScanningDataSource.GitHub]: AppConnection.GitHubRadar,
|
||||
[SecretScanningDataSource.Bitbucket]: AppConnection.Bitbucket
|
||||
};
|
||||
|
||||
export const AUTO_SYNC_DESCRIPTION_HELPER: Record<SecretScanningDataSource, { verb: string; noun: string }> = {
|
||||
[SecretScanningDataSource.GitHub]: { verb: "push", noun: "repositories" }
|
||||
[SecretScanningDataSource.GitHub]: { verb: "push", noun: "repositories" },
|
||||
[SecretScanningDataSource.Bitbucket]: { verb: "push", noun: "repositories" }
|
||||
};
|
||||
|
@ -318,7 +318,7 @@ export const secretScanningV2QueueServiceFactory = async ({
|
||||
},
|
||||
{
|
||||
batchSize: 1,
|
||||
workerCount: 20,
|
||||
workerCount: 2,
|
||||
pollingIntervalSeconds: 1
|
||||
}
|
||||
);
|
||||
@ -539,7 +539,7 @@ export const secretScanningV2QueueServiceFactory = async ({
|
||||
},
|
||||
{
|
||||
batchSize: 1,
|
||||
workerCount: 20,
|
||||
workerCount: 2,
|
||||
pollingIntervalSeconds: 1
|
||||
}
|
||||
);
|
||||
@ -588,7 +588,7 @@ export const secretScanningV2QueueServiceFactory = async ({
|
||||
numberOfSecrets: payload.numberOfSecrets,
|
||||
isDiffScan: payload.isDiffScan,
|
||||
url: encodeURI(
|
||||
`${appCfg.SITE_URL}/secret-scanning/${projectId}/findings?search=scanId:${payload.scanId}`
|
||||
`${appCfg.SITE_URL}/projects/${projectId}/secret-scanning/findings?search=scanId:${payload.scanId}`
|
||||
),
|
||||
timestamp
|
||||
}
|
||||
@ -599,7 +599,7 @@ export const secretScanningV2QueueServiceFactory = async ({
|
||||
timestamp,
|
||||
errorMessage: payload.errorMessage,
|
||||
url: encodeURI(
|
||||
`${appCfg.SITE_URL}/secret-scanning/${projectId}/data-sources/${dataSource.type}/${dataSource.id}`
|
||||
`${appCfg.SITE_URL}/projects/${projectId}/secret-scanning/data-sources/${dataSource.type}/${dataSource.id}`
|
||||
)
|
||||
}
|
||||
});
|
||||
@ -613,7 +613,7 @@ export const secretScanningV2QueueServiceFactory = async ({
|
||||
},
|
||||
{
|
||||
batchSize: 1,
|
||||
workerCount: 5,
|
||||
workerCount: 2,
|
||||
pollingIntervalSeconds: 1
|
||||
}
|
||||
);
|
||||
|
@ -19,8 +19,7 @@ export const BaseSecretScanningDataSourceSchema = ({
|
||||
// unique to provider
|
||||
type: true,
|
||||
connectionId: true,
|
||||
config: true,
|
||||
encryptedCredentials: true
|
||||
config: true
|
||||
}).extend({
|
||||
type: z.literal(type),
|
||||
connectionId: isConnectionRequired ? z.string().uuid() : z.null(),
|
||||
|
@ -1,7 +1,6 @@
|
||||
import { ForbiddenError } from "@casl/ability";
|
||||
import { join } from "path";
|
||||
|
||||
import { ActionProjectType } from "@app/db/schemas";
|
||||
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
|
||||
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
|
||||
import {
|
||||
@ -31,6 +30,8 @@ import {
|
||||
TFindSecretScanningDataSourceByNameDTO,
|
||||
TListSecretScanningDataSourcesByProjectId,
|
||||
TSecretScanningDataSource,
|
||||
TSecretScanningDataSourceCredentials,
|
||||
TSecretScanningDataSourceInput,
|
||||
TSecretScanningDataSourceWithConnection,
|
||||
TSecretScanningDataSourceWithDetails,
|
||||
TSecretScanningFinding,
|
||||
@ -50,6 +51,7 @@ import { TAppConnection } from "@app/services/app-connection/app-connection-type
|
||||
import { TKmsServiceFactory } from "@app/services/kms/kms-service";
|
||||
import { KmsDataKey } from "@app/services/kms/kms-types";
|
||||
|
||||
import { bitbucketSecretScanningService } from "./bitbucket/bitbucket-secret-scanning-service";
|
||||
import { TSecretScanningV2DALFactory } from "./secret-scanning-v2-dal";
|
||||
import { TSecretScanningV2QueueServiceFactory } from "./secret-scanning-v2-queue";
|
||||
|
||||
@ -92,7 +94,7 @@ export const secretScanningV2ServiceFactory = ({
|
||||
actorId: actor.id,
|
||||
actorAuthMethod: actor.authMethod,
|
||||
actorOrgId: actor.orgId,
|
||||
actionProjectType: ActionProjectType.SecretScanning,
|
||||
|
||||
projectId
|
||||
});
|
||||
|
||||
@ -154,7 +156,7 @@ export const secretScanningV2ServiceFactory = ({
|
||||
actorId: actor.id,
|
||||
actorAuthMethod: actor.authMethod,
|
||||
actorOrgId: actor.orgId,
|
||||
actionProjectType: ActionProjectType.SecretScanning,
|
||||
|
||||
projectId: dataSource.projectId
|
||||
});
|
||||
|
||||
@ -199,7 +201,7 @@ export const secretScanningV2ServiceFactory = ({
|
||||
actorId: actor.id,
|
||||
actorAuthMethod: actor.authMethod,
|
||||
actorOrgId: actor.orgId,
|
||||
actionProjectType: ActionProjectType.SecretScanning,
|
||||
|
||||
projectId
|
||||
});
|
||||
|
||||
@ -233,7 +235,7 @@ export const secretScanningV2ServiceFactory = ({
|
||||
actorId: actor.id,
|
||||
actorAuthMethod: actor.authMethod,
|
||||
actorOrgId: actor.orgId,
|
||||
actionProjectType: ActionProjectType.SecretScanning,
|
||||
|
||||
projectId: payload.projectId
|
||||
});
|
||||
|
||||
@ -257,7 +259,7 @@ export const secretScanningV2ServiceFactory = ({
|
||||
try {
|
||||
const createdDataSource = await factory.initialize(
|
||||
{
|
||||
payload,
|
||||
payload: payload as TSecretScanningDataSourceInput,
|
||||
connection: connection as TSecretScanningDataSourceWithConnection["connection"],
|
||||
secretScanningV2DAL
|
||||
},
|
||||
@ -288,7 +290,7 @@ export const secretScanningV2ServiceFactory = ({
|
||||
);
|
||||
|
||||
await factory.postInitialization({
|
||||
payload,
|
||||
payload: payload as TSecretScanningDataSourceInput,
|
||||
connection: connection as TSecretScanningDataSourceWithConnection["connection"],
|
||||
dataSourceId: dataSource.id,
|
||||
credentials
|
||||
@ -346,7 +348,7 @@ export const secretScanningV2ServiceFactory = ({
|
||||
actorId: actor.id,
|
||||
actorAuthMethod: actor.authMethod,
|
||||
actorOrgId: actor.orgId,
|
||||
actionProjectType: ActionProjectType.SecretScanning,
|
||||
|
||||
projectId: dataSource.projectId
|
||||
});
|
||||
|
||||
@ -399,7 +401,6 @@ export const secretScanningV2ServiceFactory = ({
|
||||
actorId: actor.id,
|
||||
actorAuthMethod: actor.authMethod,
|
||||
actorOrgId: actor.orgId,
|
||||
actionProjectType: ActionProjectType.SecretScanning,
|
||||
projectId: dataSource.projectId
|
||||
});
|
||||
|
||||
@ -413,7 +414,36 @@ export const secretScanningV2ServiceFactory = ({
|
||||
message: `Secret Scanning Data Source with ID "${dataSourceId}" is not configured for ${SECRET_SCANNING_DATA_SOURCE_NAME_MAP[type]}`
|
||||
});
|
||||
|
||||
// TODO: clean up webhooks
|
||||
const factory = SECRET_SCANNING_FACTORY_MAP[type]();
|
||||
|
||||
let connection: TAppConnection | null = null;
|
||||
if (dataSource.connection) {
|
||||
connection = await decryptAppConnection(dataSource.connection, kmsService);
|
||||
}
|
||||
|
||||
let credentials: TSecretScanningDataSourceCredentials | undefined;
|
||||
|
||||
if (dataSource.encryptedCredentials) {
|
||||
const { decryptor } = await kmsService.createCipherPairWithDataKey({
|
||||
type: KmsDataKey.SecretManager,
|
||||
projectId: dataSource.projectId
|
||||
});
|
||||
|
||||
credentials = JSON.parse(
|
||||
decryptor({
|
||||
cipherTextBlob: dataSource.encryptedCredentials
|
||||
}).toString()
|
||||
) as TSecretScanningDataSourceCredentials;
|
||||
}
|
||||
|
||||
await factory.teardown({
|
||||
dataSource: {
|
||||
...dataSource,
|
||||
// @ts-expect-error currently we don't have a null connection data source
|
||||
connection
|
||||
},
|
||||
credentials
|
||||
});
|
||||
|
||||
await secretScanningV2DAL.dataSources.deleteById(dataSourceId);
|
||||
|
||||
@ -444,7 +474,7 @@ export const secretScanningV2ServiceFactory = ({
|
||||
actorId: actor.id,
|
||||
actorAuthMethod: actor.authMethod,
|
||||
actorOrgId: actor.orgId,
|
||||
actionProjectType: ActionProjectType.SecretScanning,
|
||||
|
||||
projectId: dataSource.projectId
|
||||
});
|
||||
|
||||
@ -508,7 +538,7 @@ export const secretScanningV2ServiceFactory = ({
|
||||
actorId: actor.id,
|
||||
actorAuthMethod: actor.authMethod,
|
||||
actorOrgId: actor.orgId,
|
||||
actionProjectType: ActionProjectType.SecretScanning,
|
||||
|
||||
projectId: dataSource.projectId
|
||||
});
|
||||
|
||||
@ -553,7 +583,7 @@ export const secretScanningV2ServiceFactory = ({
|
||||
actorId: actor.id,
|
||||
actorAuthMethod: actor.authMethod,
|
||||
actorOrgId: actor.orgId,
|
||||
actionProjectType: ActionProjectType.SecretScanning,
|
||||
|
||||
projectId: dataSource.projectId
|
||||
});
|
||||
|
||||
@ -596,7 +626,7 @@ export const secretScanningV2ServiceFactory = ({
|
||||
actorId: actor.id,
|
||||
actorAuthMethod: actor.authMethod,
|
||||
actorOrgId: actor.orgId,
|
||||
actionProjectType: ActionProjectType.SecretScanning,
|
||||
|
||||
projectId: dataSource.projectId
|
||||
});
|
||||
|
||||
@ -639,7 +669,7 @@ export const secretScanningV2ServiceFactory = ({
|
||||
actorId: actor.id,
|
||||
actorAuthMethod: actor.authMethod,
|
||||
actorOrgId: actor.orgId,
|
||||
actionProjectType: ActionProjectType.SecretScanning,
|
||||
|
||||
projectId: dataSource.projectId
|
||||
});
|
||||
|
||||
@ -672,7 +702,7 @@ export const secretScanningV2ServiceFactory = ({
|
||||
actorId: actor.id,
|
||||
actorAuthMethod: actor.authMethod,
|
||||
actorOrgId: actor.orgId,
|
||||
actionProjectType: ActionProjectType.SecretScanning,
|
||||
|
||||
projectId
|
||||
});
|
||||
|
||||
@ -706,7 +736,7 @@ export const secretScanningV2ServiceFactory = ({
|
||||
actorId: actor.id,
|
||||
actorAuthMethod: actor.authMethod,
|
||||
actorOrgId: actor.orgId,
|
||||
actionProjectType: ActionProjectType.SecretScanning,
|
||||
|
||||
projectId
|
||||
});
|
||||
|
||||
@ -746,7 +776,7 @@ export const secretScanningV2ServiceFactory = ({
|
||||
actorId: actor.id,
|
||||
actorAuthMethod: actor.authMethod,
|
||||
actorOrgId: actor.orgId,
|
||||
actionProjectType: ActionProjectType.SecretScanning,
|
||||
|
||||
projectId: finding.projectId
|
||||
});
|
||||
|
||||
@ -777,7 +807,7 @@ export const secretScanningV2ServiceFactory = ({
|
||||
actorId: actor.id,
|
||||
actorAuthMethod: actor.authMethod,
|
||||
actorOrgId: actor.orgId,
|
||||
actionProjectType: ActionProjectType.SecretScanning,
|
||||
|
||||
projectId
|
||||
});
|
||||
|
||||
@ -812,7 +842,7 @@ export const secretScanningV2ServiceFactory = ({
|
||||
actorId: actor.id,
|
||||
actorAuthMethod: actor.authMethod,
|
||||
actorOrgId: actor.orgId,
|
||||
actionProjectType: ActionProjectType.SecretScanning,
|
||||
|
||||
projectId
|
||||
});
|
||||
|
||||
@ -851,6 +881,47 @@ export const secretScanningV2ServiceFactory = ({
|
||||
return config;
|
||||
};
|
||||
|
||||
const getProjectResourcesCount = async (projectId: string, actor: OrgServiceActor) => {
|
||||
// Anyone in the project should be able to get count.
|
||||
await permissionService.getProjectPermission({
|
||||
actor: actor.type,
|
||||
actorId: actor.id,
|
||||
projectId,
|
||||
actorAuthMethod: actor.authMethod,
|
||||
actorOrgId: actor.orgId
|
||||
});
|
||||
|
||||
const dataSources = await secretScanningV2DAL.dataSources.findRaw(
|
||||
{
|
||||
projectId
|
||||
},
|
||||
{ count: true }
|
||||
);
|
||||
|
||||
const resources = await secretScanningV2DAL.resources.find(
|
||||
{
|
||||
$in: {
|
||||
dataSourceId: dataSources.map((dataSource) => dataSource.id)
|
||||
}
|
||||
},
|
||||
{ count: true }
|
||||
);
|
||||
|
||||
const findings = await secretScanningV2DAL.findings.find(
|
||||
{
|
||||
projectId,
|
||||
status: SecretScanningFindingStatus.Unresolved
|
||||
},
|
||||
{ count: true }
|
||||
);
|
||||
|
||||
return {
|
||||
dataSourceCount: Number(dataSources?.[0]?.count ?? 0),
|
||||
resourceCount: Number(resources?.[0]?.count ?? 0),
|
||||
findingCount: Number(findings?.[0]?.count ?? 0)
|
||||
};
|
||||
};
|
||||
|
||||
return {
|
||||
listSecretScanningDataSourceOptions,
|
||||
listSecretScanningDataSourcesByProjectId,
|
||||
@ -870,6 +941,8 @@ export const secretScanningV2ServiceFactory = ({
|
||||
updateSecretScanningFindingById,
|
||||
findSecretScanningConfigByProjectId,
|
||||
upsertSecretScanningConfig,
|
||||
github: githubSecretScanningService(secretScanningV2DAL, secretScanningV2Queue)
|
||||
getProjectResourcesCount,
|
||||
github: githubSecretScanningService(secretScanningV2DAL, secretScanningV2Queue),
|
||||
bitbucket: bitbucketSecretScanningService(secretScanningV2DAL, secretScanningV2Queue, kmsService)
|
||||
};
|
||||
};
|
||||
|
@ -4,6 +4,15 @@ import {
|
||||
TSecretScanningResources,
|
||||
TSecretScanningScans
|
||||
} from "@app/db/schemas";
|
||||
import {
|
||||
TBitbucketDataSource,
|
||||
TBitbucketDataSourceCredentials,
|
||||
TBitbucketDataSourceInput,
|
||||
TBitbucketDataSourceListItem,
|
||||
TBitbucketDataSourceWithConnection,
|
||||
TBitbucketFinding,
|
||||
TQueueBitbucketResourceDiffScan
|
||||
} from "@app/ee/services/secret-scanning-v2/bitbucket";
|
||||
import {
|
||||
TGitHubDataSource,
|
||||
TGitHubDataSourceInput,
|
||||
@ -19,7 +28,7 @@ import {
|
||||
SecretScanningScanStatus
|
||||
} from "@app/ee/services/secret-scanning-v2/secret-scanning-v2-enums";
|
||||
|
||||
export type TSecretScanningDataSource = TGitHubDataSource;
|
||||
export type TSecretScanningDataSource = TGitHubDataSource | TBitbucketDataSource;
|
||||
|
||||
export type TSecretScanningDataSourceWithDetails = TSecretScanningDataSource & {
|
||||
lastScannedAt?: Date | null;
|
||||
@ -41,13 +50,17 @@ export type TSecretScanningScanWithDetails = TSecretScanningScans & {
|
||||
resourceName: string;
|
||||
};
|
||||
|
||||
export type TSecretScanningDataSourceWithConnection = TGitHubDataSourceWithConnection;
|
||||
export type TSecretScanningDataSourceWithConnection =
|
||||
| TGitHubDataSourceWithConnection
|
||||
| TBitbucketDataSourceWithConnection;
|
||||
|
||||
export type TSecretScanningDataSourceInput = TGitHubDataSourceInput;
|
||||
export type TSecretScanningDataSourceInput = TGitHubDataSourceInput | TBitbucketDataSourceInput;
|
||||
|
||||
export type TSecretScanningDataSourceListItem = TGitHubDataSourceListItem;
|
||||
export type TSecretScanningDataSourceListItem = TGitHubDataSourceListItem | TBitbucketDataSourceListItem;
|
||||
|
||||
export type TSecretScanningFinding = TGitHubFinding;
|
||||
export type TSecretScanningDataSourceCredentials = TBitbucketDataSourceCredentials | undefined;
|
||||
|
||||
export type TSecretScanningFinding = TGitHubFinding | TBitbucketFinding;
|
||||
|
||||
export type TListSecretScanningDataSourcesByProjectId = {
|
||||
projectId: string;
|
||||
@ -99,7 +112,7 @@ export type TQueueSecretScanningDataSourceFullScan = {
|
||||
scanId: string;
|
||||
};
|
||||
|
||||
export type TQueueSecretScanningResourceDiffScan = TQueueGitHubResourceDiffScan;
|
||||
export type TQueueSecretScanningResourceDiffScan = TQueueGitHubResourceDiffScan | TQueueBitbucketResourceDiffScan;
|
||||
|
||||
export type TQueueSecretScanningSendNotification = {
|
||||
dataSource: TSecretScanningDataSources;
|
||||
@ -138,11 +151,12 @@ export type TSecretScanningDataSourceRaw = NonNullable<
|
||||
>;
|
||||
|
||||
export type TSecretScanningFactoryInitialize<
|
||||
P extends TSecretScanningDataSourceInput,
|
||||
T extends TSecretScanningDataSourceWithConnection["connection"] | undefined = undefined,
|
||||
C extends TSecretScanningDataSourceCredentials = undefined
|
||||
> = (
|
||||
params: {
|
||||
payload: TCreateSecretScanningDataSourceDTO;
|
||||
payload: P;
|
||||
connection: T;
|
||||
secretScanningV2DAL: TSecretScanningV2DALFactory;
|
||||
},
|
||||
@ -150,24 +164,27 @@ export type TSecretScanningFactoryInitialize<
|
||||
) => Promise<TSecretScanningDataSourceRaw>;
|
||||
|
||||
export type TSecretScanningFactoryPostInitialization<
|
||||
P extends TSecretScanningDataSourceInput,
|
||||
T extends TSecretScanningDataSourceWithConnection["connection"] | undefined = undefined,
|
||||
C extends TSecretScanningDataSourceCredentials = undefined
|
||||
> = (params: {
|
||||
payload: TCreateSecretScanningDataSourceDTO;
|
||||
connection: T;
|
||||
credentials: C;
|
||||
dataSourceId: string;
|
||||
}) => Promise<void>;
|
||||
> = (params: { payload: P; connection: T; credentials: C; dataSourceId: string }) => Promise<void>;
|
||||
|
||||
export type TSecretScanningFactoryTeardown<
|
||||
T extends TSecretScanningDataSourceWithConnection,
|
||||
C extends TSecretScanningDataSourceCredentials = undefined
|
||||
> = (params: { dataSource: T; credentials: C }) => Promise<void>;
|
||||
|
||||
export type TSecretScanningFactory<
|
||||
T extends TSecretScanningDataSourceWithConnection,
|
||||
C extends TSecretScanningDataSourceCredentials,
|
||||
P extends TQueueSecretScanningResourceDiffScan["payload"]
|
||||
P extends TQueueSecretScanningResourceDiffScan["payload"],
|
||||
I extends TSecretScanningDataSourceInput,
|
||||
C extends TSecretScanningDataSourceCredentials | undefined = undefined
|
||||
> = () => {
|
||||
listRawResources: TSecretScanningFactoryListRawResources<T>;
|
||||
getFullScanPath: TSecretScanningFactoryGetFullScanPath<T>;
|
||||
initialize: TSecretScanningFactoryInitialize<T["connection"] | undefined, C>;
|
||||
postInitialization: TSecretScanningFactoryPostInitialization<T["connection"] | undefined, C>;
|
||||
initialize: TSecretScanningFactoryInitialize<I, T["connection"] | undefined, C>;
|
||||
postInitialization: TSecretScanningFactoryPostInitialization<I, T["connection"] | undefined, C>;
|
||||
teardown: TSecretScanningFactoryTeardown<T, C>;
|
||||
getDiffScanResourcePayload: TSecretScanningFactoryGetDiffScanResourcePayload<P>;
|
||||
getDiffScanFindingsPayload: TSecretScanningFactoryGetDiffScanFindingsPayload<T, P>;
|
||||
};
|
||||
@ -185,5 +202,3 @@ export type TUpsertSecretScanningConfigDTO = {
|
||||
projectId: string;
|
||||
content: string | null;
|
||||
};
|
||||
|
||||
export type TSecretScanningDataSourceCredentials = undefined;
|
||||
|
@ -1,7 +1,22 @@
|
||||
import { z } from "zod";
|
||||
|
||||
import { BitbucketDataSourceSchema, BitbucketFindingSchema } from "@app/ee/services/secret-scanning-v2/bitbucket";
|
||||
import { GitHubDataSourceSchema, GitHubFindingSchema } from "@app/ee/services/secret-scanning-v2/github";
|
||||
|
||||
export const SecretScanningDataSourceSchema = z.discriminatedUnion("type", [GitHubDataSourceSchema]);
|
||||
export const SecretScanningDataSourceSchema = z.discriminatedUnion("type", [
|
||||
GitHubDataSourceSchema,
|
||||
BitbucketDataSourceSchema
|
||||
]);
|
||||
|
||||
export const SecretScanningFindingSchema = z.discriminatedUnion("resourceType", [GitHubFindingSchema]);
|
||||
export const SecretScanningFindingSchema = z.discriminatedUnion("dataSourceType", [
|
||||
GitHubFindingSchema.describe(
|
||||
JSON.stringify({
|
||||
title: "GitHub"
|
||||
})
|
||||
),
|
||||
BitbucketFindingSchema.describe(
|
||||
JSON.stringify({
|
||||
title: "Bitbucket"
|
||||
})
|
||||
)
|
||||
]);
|
||||
|
@ -1,17 +1,11 @@
|
||||
import { ProbotOctokit } from "probot";
|
||||
|
||||
import { OrgMembershipRole, TableName } from "@app/db/schemas";
|
||||
import { getConfig } from "@app/lib/config/env";
|
||||
import { logger } from "@app/lib/logger";
|
||||
import { QueueJobs, QueueName, TQueueServiceFactory } from "@app/queue";
|
||||
import { InternalServerError } from "@app/lib/errors";
|
||||
import { TQueueServiceFactory } from "@app/queue";
|
||||
import { TOrgDALFactory } from "@app/services/org/org-dal";
|
||||
import { SmtpTemplates, TSmtpService } from "@app/services/smtp/smtp-service";
|
||||
import { TSmtpService } from "@app/services/smtp/smtp-service";
|
||||
import { TTelemetryServiceFactory } from "@app/services/telemetry/telemetry-service";
|
||||
import { PostHogEventTypes } from "@app/services/telemetry/telemetry-types";
|
||||
|
||||
import { TSecretScanningDALFactory } from "../secret-scanning-dal";
|
||||
import { scanContentAndGetFindings, scanFullRepoContentAndGetFindings } from "./secret-scanning-fns";
|
||||
import { SecretMatch, TScanFullRepoEventPayload, TScanPushEventPayload } from "./secret-scanning-queue-types";
|
||||
import { TScanFullRepoEventPayload, TScanPushEventPayload } from "./secret-scanning-queue-types";
|
||||
|
||||
type TSecretScanningQueueFactoryDep = {
|
||||
queueService: TQueueServiceFactory;
|
||||
@ -23,227 +17,21 @@ type TSecretScanningQueueFactoryDep = {
|
||||
|
||||
export type TSecretScanningQueueFactory = ReturnType<typeof secretScanningQueueFactory>;
|
||||
|
||||
export const secretScanningQueueFactory = ({
|
||||
queueService,
|
||||
secretScanningDAL,
|
||||
smtpService,
|
||||
telemetryService,
|
||||
orgMembershipDAL: orgMemberDAL
|
||||
}: TSecretScanningQueueFactoryDep) => {
|
||||
const startFullRepoScan = async (payload: TScanFullRepoEventPayload) => {
|
||||
await queueService.queue(QueueName.SecretFullRepoScan, QueueJobs.SecretScan, payload, {
|
||||
attempts: 3,
|
||||
backoff: {
|
||||
type: "exponential",
|
||||
delay: 5000
|
||||
},
|
||||
removeOnComplete: true,
|
||||
removeOnFail: {
|
||||
count: 20 // keep the most recent 20 jobs
|
||||
}
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
export const secretScanningQueueFactory = (_props: TSecretScanningQueueFactoryDep) => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const startFullRepoScan = async (_payload: TScanFullRepoEventPayload) => {
|
||||
throw new InternalServerError({
|
||||
message: "Secret Scanning V1 has been deprecated. Please migrate to Secret Scanning V2"
|
||||
});
|
||||
};
|
||||
|
||||
const startPushEventScan = async (payload: TScanPushEventPayload) => {
|
||||
await queueService.queue(QueueName.SecretPushEventScan, QueueJobs.SecretScan, payload, {
|
||||
attempts: 3,
|
||||
backoff: {
|
||||
type: "exponential",
|
||||
delay: 5000
|
||||
},
|
||||
removeOnComplete: true,
|
||||
removeOnFail: {
|
||||
count: 20 // keep the most recent 20 jobs
|
||||
}
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const startPushEventScan = async (_payload: TScanPushEventPayload) => {
|
||||
throw new InternalServerError({
|
||||
message: "Secret Scanning V1 has been deprecated. Please migrate to Secret Scanning V2"
|
||||
});
|
||||
};
|
||||
|
||||
const getOrgAdminEmails = async (organizationId: string) => {
|
||||
// get emails of admins
|
||||
const adminsOfWork = await orgMemberDAL.findMembership({
|
||||
[`${TableName.Organization}.id` as string]: organizationId,
|
||||
role: OrgMembershipRole.Admin
|
||||
});
|
||||
return adminsOfWork.filter((userObject) => userObject.email).map((userObject) => userObject.email as string);
|
||||
};
|
||||
|
||||
queueService.start(QueueName.SecretPushEventScan, async (job) => {
|
||||
const appCfg = getConfig();
|
||||
const { organizationId, commits, pusher, repository, installationId } = job.data;
|
||||
const [owner, repo] = repository.fullName.split("/");
|
||||
const octokit = new ProbotOctokit({
|
||||
auth: {
|
||||
appId: appCfg.SECRET_SCANNING_GIT_APP_ID,
|
||||
privateKey: appCfg.SECRET_SCANNING_PRIVATE_KEY,
|
||||
installationId
|
||||
}
|
||||
});
|
||||
const allFindingsByFingerprint: { [key: string]: SecretMatch } = {};
|
||||
|
||||
for (const commit of commits) {
|
||||
for (const filepath of [...commit.added, ...commit.modified]) {
|
||||
// eslint-disable-next-line
|
||||
const fileContentsResponse = await octokit.repos.getContent({
|
||||
owner,
|
||||
repo,
|
||||
path: filepath
|
||||
});
|
||||
|
||||
const { data } = fileContentsResponse;
|
||||
const fileContent = Buffer.from((data as { content: string }).content, "base64").toString();
|
||||
|
||||
// eslint-disable-next-line
|
||||
const findings = await scanContentAndGetFindings(`\n${fileContent}`); // extra line to count lines correctly
|
||||
|
||||
for (const finding of findings) {
|
||||
const fingerPrintWithCommitId = `${commit.id}:${filepath}:${finding.RuleID}:${finding.StartLine}`;
|
||||
const fingerPrintWithoutCommitId = `${filepath}:${finding.RuleID}:${finding.StartLine}`;
|
||||
finding.Fingerprint = fingerPrintWithCommitId;
|
||||
finding.FingerPrintWithoutCommitId = fingerPrintWithoutCommitId;
|
||||
finding.Commit = commit.id;
|
||||
finding.File = filepath;
|
||||
finding.Author = commit.author.name;
|
||||
finding.Email = commit?.author?.email ? commit?.author?.email : "";
|
||||
|
||||
allFindingsByFingerprint[fingerPrintWithCommitId] = finding;
|
||||
}
|
||||
}
|
||||
}
|
||||
await secretScanningDAL.transaction(async (tx) => {
|
||||
if (!Object.keys(allFindingsByFingerprint).length) return;
|
||||
await secretScanningDAL.upsert(
|
||||
Object.keys(allFindingsByFingerprint).map((key) => ({
|
||||
installationId,
|
||||
email: allFindingsByFingerprint[key].Email,
|
||||
author: allFindingsByFingerprint[key].Author,
|
||||
date: allFindingsByFingerprint[key].Date,
|
||||
file: allFindingsByFingerprint[key].File,
|
||||
tags: allFindingsByFingerprint[key].Tags,
|
||||
commit: allFindingsByFingerprint[key].Commit,
|
||||
ruleID: allFindingsByFingerprint[key].RuleID,
|
||||
endLine: String(allFindingsByFingerprint[key].EndLine),
|
||||
entropy: String(allFindingsByFingerprint[key].Entropy),
|
||||
message: allFindingsByFingerprint[key].Message,
|
||||
endColumn: String(allFindingsByFingerprint[key].EndColumn),
|
||||
startLine: String(allFindingsByFingerprint[key].StartLine),
|
||||
startColumn: String(allFindingsByFingerprint[key].StartColumn),
|
||||
fingerPrintWithoutCommitId: allFindingsByFingerprint[key].FingerPrintWithoutCommitId,
|
||||
description: allFindingsByFingerprint[key].Description,
|
||||
symlinkFile: allFindingsByFingerprint[key].SymlinkFile,
|
||||
orgId: organizationId,
|
||||
pusherEmail: pusher.email,
|
||||
pusherName: pusher.name,
|
||||
repositoryFullName: repository.fullName,
|
||||
repositoryId: String(repository.id),
|
||||
fingerprint: allFindingsByFingerprint[key].Fingerprint
|
||||
})),
|
||||
tx
|
||||
);
|
||||
});
|
||||
|
||||
const adminEmails = await getOrgAdminEmails(organizationId);
|
||||
if (pusher?.email) {
|
||||
adminEmails.push(pusher.email);
|
||||
}
|
||||
if (Object.keys(allFindingsByFingerprint).length) {
|
||||
await smtpService.sendMail({
|
||||
template: SmtpTemplates.SecretLeakIncident,
|
||||
subjectLine: `Incident alert: leaked secrets found in Github repository ${repository.fullName}`,
|
||||
recipients: adminEmails.filter((email) => email).map((email) => email),
|
||||
substitutions: {
|
||||
numberOfSecrets: Object.keys(allFindingsByFingerprint).length,
|
||||
pusher_email: pusher.email,
|
||||
pusher_name: pusher.name
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
await telemetryService.sendPostHogEvents({
|
||||
event: PostHogEventTypes.SecretScannerPush,
|
||||
distinctId: repository.fullName,
|
||||
properties: {
|
||||
numberOfRisks: Object.keys(allFindingsByFingerprint).length
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
queueService.start(QueueName.SecretFullRepoScan, async (job) => {
|
||||
const appCfg = getConfig();
|
||||
const { organizationId, repository, installationId } = job.data;
|
||||
const octokit = new ProbotOctokit({
|
||||
auth: {
|
||||
appId: appCfg.SECRET_SCANNING_GIT_APP_ID,
|
||||
privateKey: appCfg.SECRET_SCANNING_PRIVATE_KEY,
|
||||
installationId
|
||||
}
|
||||
});
|
||||
|
||||
const findings = await scanFullRepoContentAndGetFindings(
|
||||
// this is because of collision of octokit in probot and github
|
||||
// eslint-disable-next-line
|
||||
octokit as any,
|
||||
installationId,
|
||||
repository.fullName
|
||||
);
|
||||
await secretScanningDAL.transaction(async (tx) => {
|
||||
if (!findings.length) return;
|
||||
// eslint-disable-next-line
|
||||
await secretScanningDAL.upsert(
|
||||
findings.map((finding) => ({
|
||||
installationId,
|
||||
email: finding.Email,
|
||||
author: finding.Author,
|
||||
date: finding.Date,
|
||||
file: finding.File,
|
||||
tags: finding.Tags,
|
||||
commit: finding.Commit,
|
||||
ruleID: finding.RuleID,
|
||||
endLine: String(finding.EndLine),
|
||||
entropy: String(finding.Entropy),
|
||||
message: finding.Message,
|
||||
endColumn: String(finding.EndColumn),
|
||||
startLine: String(finding.StartLine),
|
||||
startColumn: String(finding.StartColumn),
|
||||
fingerPrintWithoutCommitId: finding.FingerPrintWithoutCommitId,
|
||||
description: finding.Description,
|
||||
symlinkFile: finding.SymlinkFile,
|
||||
orgId: organizationId,
|
||||
repositoryFullName: repository.fullName,
|
||||
repositoryId: String(repository.id),
|
||||
fingerprint: finding.Fingerprint
|
||||
})),
|
||||
tx
|
||||
);
|
||||
});
|
||||
|
||||
const adminEmails = await getOrgAdminEmails(organizationId);
|
||||
if (findings.length) {
|
||||
await smtpService.sendMail({
|
||||
template: SmtpTemplates.SecretLeakIncident,
|
||||
subjectLine: `Incident alert: leaked secrets found in Github repository ${repository.fullName}`,
|
||||
recipients: adminEmails.filter((email) => email).map((email) => email),
|
||||
substitutions: {
|
||||
numberOfSecrets: findings.length
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
await telemetryService.sendPostHogEvents({
|
||||
event: PostHogEventTypes.SecretScannerFull,
|
||||
distinctId: repository.fullName,
|
||||
properties: {
|
||||
numberOfRisks: findings.length
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
queueService.listen(QueueName.SecretPushEventScan, "failed", (job, err) => {
|
||||
logger.error(err, "Failed to secret scan on push", job?.data);
|
||||
});
|
||||
|
||||
queueService.listen(QueueName.SecretFullRepoScan, "failed", (job, err) => {
|
||||
logger.error(err, "Failed to do full repo secret scan", job?.data);
|
||||
});
|
||||
|
||||
return { startFullRepoScan, startPushEventScan };
|
||||
};
|
||||
|
@ -98,6 +98,7 @@ export const secretScanningServiceFactory = ({
|
||||
if (canUseSecretScanning(actorOrgId)) {
|
||||
await Promise.all(
|
||||
repositories.map(({ id, full_name }) =>
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-call,@typescript-eslint/no-unsafe-return,@typescript-eslint/no-unsafe-member-access
|
||||
secretScanningQueue.startFullRepoScan({
|
||||
organizationId: session.orgId,
|
||||
installationId,
|
||||
@ -180,6 +181,7 @@ export const secretScanningServiceFactory = ({
|
||||
if (!installationLink) return;
|
||||
|
||||
if (canUseSecretScanning(installationLink.orgId)) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-call,@typescript-eslint/no-unsafe-return,@typescript-eslint/no-unsafe-member-access
|
||||
await secretScanningQueue.startPushEventScan({
|
||||
commits,
|
||||
pusher: { name: pusher.name, email: pusher.email },
|
||||
|
@ -2,7 +2,7 @@
|
||||
// akhilmhdh: I did this, quite strange bug with eslint. Everything do have a type stil has this error
|
||||
import { ForbiddenError } from "@casl/ability";
|
||||
|
||||
import { ActionProjectType, TableName, TSecretTagJunctionInsert, TSecretV2TagJunctionInsert } from "@app/db/schemas";
|
||||
import { TableName, TSecretTagJunctionInsert, TSecretV2TagJunctionInsert } from "@app/db/schemas";
|
||||
import { decryptSymmetric128BitHexKeyUTF8 } from "@app/lib/crypto";
|
||||
import { InternalServerError, NotFoundError } from "@app/lib/errors";
|
||||
import { groupBy } from "@app/lib/fn";
|
||||
@ -103,8 +103,7 @@ export const secretSnapshotServiceFactory = ({
|
||||
actorId,
|
||||
projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.SecretManager
|
||||
actorOrgId
|
||||
});
|
||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.SecretRollback);
|
||||
|
||||
@ -140,8 +139,7 @@ export const secretSnapshotServiceFactory = ({
|
||||
actorId,
|
||||
projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.SecretManager
|
||||
actorOrgId
|
||||
});
|
||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.SecretRollback);
|
||||
|
||||
@ -169,8 +167,7 @@ export const secretSnapshotServiceFactory = ({
|
||||
actorId,
|
||||
projectId: snapshot.projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.SecretManager
|
||||
actorOrgId
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.SecretRollback);
|
||||
@ -390,8 +387,7 @@ export const secretSnapshotServiceFactory = ({
|
||||
actorId,
|
||||
projectId: snapshot.projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.SecretManager
|
||||
actorOrgId
|
||||
});
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionActions.Create,
|
||||
|
@ -1,6 +1,5 @@
|
||||
import { ForbiddenError } from "@casl/ability";
|
||||
|
||||
import { ActionProjectType } from "@app/db/schemas";
|
||||
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
|
||||
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
|
||||
import { BadRequestError, NotFoundError } from "@app/lib/errors";
|
||||
@ -59,8 +58,7 @@ export const sshCertificateTemplateServiceFactory = ({
|
||||
actorId,
|
||||
projectId: ca.projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.SSH
|
||||
actorOrgId
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
@ -132,8 +130,7 @@ export const sshCertificateTemplateServiceFactory = ({
|
||||
actorId,
|
||||
projectId: certTemplate.projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.SSH
|
||||
actorOrgId
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
@ -201,8 +198,7 @@ export const sshCertificateTemplateServiceFactory = ({
|
||||
actorId,
|
||||
projectId: certificateTemplate.projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.SSH
|
||||
actorOrgId
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
@ -228,8 +224,7 @@ export const sshCertificateTemplateServiceFactory = ({
|
||||
actorId,
|
||||
projectId: certTemplate.projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.SSH
|
||||
actorOrgId
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
|
@ -1,6 +1,5 @@
|
||||
import { ForbiddenError } from "@casl/ability";
|
||||
|
||||
import { ActionProjectType } from "@app/db/schemas";
|
||||
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
|
||||
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
|
||||
import { TSshHostDALFactory } from "@app/ee/services/ssh-host/ssh-host-dal";
|
||||
@ -9,6 +8,7 @@ import { TSshHostLoginUserDALFactory } from "@app/ee/services/ssh-host/ssh-login
|
||||
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 { OrgServiceActor } from "@app/lib/types";
|
||||
import { TProjectDALFactory } from "@app/services/project/project-dal";
|
||||
import { TUserDALFactory } from "@app/services/user/user-dal";
|
||||
|
||||
@ -80,8 +80,7 @@ export const sshHostGroupServiceFactory = ({
|
||||
actorId,
|
||||
projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.SSH
|
||||
actorOrgId
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Create, ProjectPermissionSub.SshHostGroups);
|
||||
@ -173,8 +172,7 @@ export const sshHostGroupServiceFactory = ({
|
||||
actorId,
|
||||
projectId: sshHostGroup.projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.SSH
|
||||
actorOrgId
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.SshHostGroups);
|
||||
@ -270,8 +268,7 @@ export const sshHostGroupServiceFactory = ({
|
||||
actorId,
|
||||
projectId: sshHostGroup.projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.SSH
|
||||
actorOrgId
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.SshHostGroups);
|
||||
@ -294,8 +291,7 @@ export const sshHostGroupServiceFactory = ({
|
||||
actorId,
|
||||
projectId: sshHostGroup.projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.SSH
|
||||
actorOrgId
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Delete, ProjectPermissionSub.SshHostGroups);
|
||||
@ -321,8 +317,7 @@ export const sshHostGroupServiceFactory = ({
|
||||
actorId,
|
||||
projectId: sshHostGroup.projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.SSH
|
||||
actorOrgId
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.SshHostGroups);
|
||||
@ -360,8 +355,7 @@ export const sshHostGroupServiceFactory = ({
|
||||
actorId,
|
||||
projectId: sshHostGroup.projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.SSH
|
||||
actorOrgId
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.SshHostGroups);
|
||||
@ -400,8 +394,7 @@ export const sshHostGroupServiceFactory = ({
|
||||
actorId,
|
||||
projectId: sshHostGroup.projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.SSH
|
||||
actorOrgId
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.SshHostGroups);
|
||||
@ -422,6 +415,26 @@ export const sshHostGroupServiceFactory = ({
|
||||
return { sshHostGroup, sshHost };
|
||||
};
|
||||
|
||||
const getProjectHostGroupCount = async (projectId: string, actor: OrgServiceActor) => {
|
||||
// Anyone in the project should be able to get count.
|
||||
await permissionService.getProjectPermission({
|
||||
actor: actor.type,
|
||||
actorId: actor.id,
|
||||
projectId,
|
||||
actorAuthMethod: actor.authMethod,
|
||||
actorOrgId: actor.orgId
|
||||
});
|
||||
|
||||
const hostGroups = await sshHostGroupDAL.find(
|
||||
{
|
||||
projectId
|
||||
},
|
||||
{ count: true }
|
||||
);
|
||||
|
||||
return Number(hostGroups?.[0]?.count ?? 0);
|
||||
};
|
||||
|
||||
return {
|
||||
createSshHostGroup,
|
||||
getSshHostGroup,
|
||||
@ -429,6 +442,7 @@ export const sshHostGroupServiceFactory = ({
|
||||
updateSshHostGroup,
|
||||
listSshHostGroupHosts,
|
||||
addHostToSshHostGroup,
|
||||
removeHostFromSshHostGroup
|
||||
removeHostFromSshHostGroup,
|
||||
getProjectHostGroupCount
|
||||
};
|
||||
};
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user