mirror of
https://github.com/Infisical/infisical.git
synced 2025-08-13 07:12:51 +00:00
Compare commits
39 Commits
secret-ove
...
fix-github
Author | SHA1 | Date | |
---|---|---|---|
|
6958f1cfbd | ||
|
b8cd836225 | ||
|
6826b1c242 | ||
|
35012fde03 | ||
|
6e14b2f793 | ||
|
5a3aa3d608 | ||
|
95b327de50 | ||
|
a3c36f82f3 | ||
|
42612da57d | ||
|
f63c07d538 | ||
|
98a08d136e | ||
|
6c74b875f3 | ||
|
793cd4c144 | ||
|
ec0be1166f | ||
|
899d01237c | ||
|
ff5dbe74fd | ||
|
24004084f2 | ||
|
0e401ece73 | ||
|
c4e1651df7 | ||
|
514c7596db | ||
|
9fbdede82c | ||
|
e519637e89 | ||
|
ba393b0498 | ||
|
4150f81d83 | ||
|
a45bba8537 | ||
|
fe7e8e7240 | ||
|
cf54365022 | ||
|
4b9e57ae61 | ||
|
eb27983990 | ||
|
fa311b032c | ||
|
71651f85fe | ||
|
d28d3449de | ||
|
4f26365c21 | ||
|
c974df104e | ||
|
e88fdc957e | ||
|
de2c1c5560 | ||
|
2cbd66e804 | ||
|
4a55ecbe12 | ||
|
1e29d550be |
189
backend/package-lock.json
generated
189
backend/package-lock.json
generated
@@ -38,6 +38,7 @@
|
||||
"@octokit/core": "^5.2.1",
|
||||
"@octokit/plugin-paginate-graphql": "^4.0.1",
|
||||
"@octokit/plugin-retry": "^5.0.5",
|
||||
"@octokit/request": "8.4.1",
|
||||
"@octokit/rest": "^20.0.2",
|
||||
"@octokit/webhooks-types": "^7.3.1",
|
||||
"@octopusdeploy/api-client": "^3.4.1",
|
||||
@@ -9777,18 +9778,6 @@
|
||||
"node": ">= 18"
|
||||
}
|
||||
},
|
||||
"node_modules/@octokit/auth-app/node_modules/@octokit/endpoint": {
|
||||
"version": "10.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-10.1.1.tgz",
|
||||
"integrity": "sha512-JYjh5rMOwXMJyUpj028cu0Gbp7qe/ihxfJMLc8VZBMMqSwLgOxDI1911gV4Enl1QSavAQNJcwmwBF9M0VvLh6Q==",
|
||||
"dependencies": {
|
||||
"@octokit/types": "^13.0.0",
|
||||
"universal-user-agent": "^7.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 18"
|
||||
}
|
||||
},
|
||||
"node_modules/@octokit/auth-app/node_modules/@octokit/openapi-types": {
|
||||
"version": "22.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-22.2.0.tgz",
|
||||
@@ -9835,11 +9824,6 @@
|
||||
"node": "14 || >=16.14"
|
||||
}
|
||||
},
|
||||
"node_modules/@octokit/auth-app/node_modules/universal-user-agent": {
|
||||
"version": "7.0.2",
|
||||
"resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-7.0.2.tgz",
|
||||
"integrity": "sha512-0JCqzSKnStlRRQfCdowvqy3cy0Dvtlb8xecj/H8JFZuCze4rwjPZQOgvFvn0Ws/usCHQFGpyr+pB9adaGwXn4Q=="
|
||||
},
|
||||
"node_modules/@octokit/auth-oauth-app": {
|
||||
"version": "8.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/auth-oauth-app/-/auth-oauth-app-8.1.1.tgz",
|
||||
@@ -9855,18 +9839,6 @@
|
||||
"node": ">= 18"
|
||||
}
|
||||
},
|
||||
"node_modules/@octokit/auth-oauth-app/node_modules/@octokit/endpoint": {
|
||||
"version": "10.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-10.1.1.tgz",
|
||||
"integrity": "sha512-JYjh5rMOwXMJyUpj028cu0Gbp7qe/ihxfJMLc8VZBMMqSwLgOxDI1911gV4Enl1QSavAQNJcwmwBF9M0VvLh6Q==",
|
||||
"dependencies": {
|
||||
"@octokit/types": "^13.0.0",
|
||||
"universal-user-agent": "^7.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 18"
|
||||
}
|
||||
},
|
||||
"node_modules/@octokit/auth-oauth-app/node_modules/@octokit/openapi-types": {
|
||||
"version": "22.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-22.2.0.tgz",
|
||||
@@ -9905,11 +9877,6 @@
|
||||
"@octokit/openapi-types": "^22.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@octokit/auth-oauth-app/node_modules/universal-user-agent": {
|
||||
"version": "7.0.2",
|
||||
"resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-7.0.2.tgz",
|
||||
"integrity": "sha512-0JCqzSKnStlRRQfCdowvqy3cy0Dvtlb8xecj/H8JFZuCze4rwjPZQOgvFvn0Ws/usCHQFGpyr+pB9adaGwXn4Q=="
|
||||
},
|
||||
"node_modules/@octokit/auth-oauth-device": {
|
||||
"version": "7.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/auth-oauth-device/-/auth-oauth-device-7.1.1.tgz",
|
||||
@@ -9924,18 +9891,6 @@
|
||||
"node": ">= 18"
|
||||
}
|
||||
},
|
||||
"node_modules/@octokit/auth-oauth-device/node_modules/@octokit/endpoint": {
|
||||
"version": "10.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-10.1.1.tgz",
|
||||
"integrity": "sha512-JYjh5rMOwXMJyUpj028cu0Gbp7qe/ihxfJMLc8VZBMMqSwLgOxDI1911gV4Enl1QSavAQNJcwmwBF9M0VvLh6Q==",
|
||||
"dependencies": {
|
||||
"@octokit/types": "^13.0.0",
|
||||
"universal-user-agent": "^7.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 18"
|
||||
}
|
||||
},
|
||||
"node_modules/@octokit/auth-oauth-device/node_modules/@octokit/openapi-types": {
|
||||
"version": "22.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-22.2.0.tgz",
|
||||
@@ -9974,11 +9929,6 @@
|
||||
"@octokit/openapi-types": "^22.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@octokit/auth-oauth-device/node_modules/universal-user-agent": {
|
||||
"version": "7.0.2",
|
||||
"resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-7.0.2.tgz",
|
||||
"integrity": "sha512-0JCqzSKnStlRRQfCdowvqy3cy0Dvtlb8xecj/H8JFZuCze4rwjPZQOgvFvn0Ws/usCHQFGpyr+pB9adaGwXn4Q=="
|
||||
},
|
||||
"node_modules/@octokit/auth-oauth-user": {
|
||||
"version": "5.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/auth-oauth-user/-/auth-oauth-user-5.1.1.tgz",
|
||||
@@ -9994,18 +9944,6 @@
|
||||
"node": ">= 18"
|
||||
}
|
||||
},
|
||||
"node_modules/@octokit/auth-oauth-user/node_modules/@octokit/endpoint": {
|
||||
"version": "10.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-10.1.1.tgz",
|
||||
"integrity": "sha512-JYjh5rMOwXMJyUpj028cu0Gbp7qe/ihxfJMLc8VZBMMqSwLgOxDI1911gV4Enl1QSavAQNJcwmwBF9M0VvLh6Q==",
|
||||
"dependencies": {
|
||||
"@octokit/types": "^13.0.0",
|
||||
"universal-user-agent": "^7.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 18"
|
||||
}
|
||||
},
|
||||
"node_modules/@octokit/auth-oauth-user/node_modules/@octokit/openapi-types": {
|
||||
"version": "22.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-22.2.0.tgz",
|
||||
@@ -10044,11 +9982,6 @@
|
||||
"@octokit/openapi-types": "^22.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@octokit/auth-oauth-user/node_modules/universal-user-agent": {
|
||||
"version": "7.0.2",
|
||||
"resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-7.0.2.tgz",
|
||||
"integrity": "sha512-0JCqzSKnStlRRQfCdowvqy3cy0Dvtlb8xecj/H8JFZuCze4rwjPZQOgvFvn0Ws/usCHQFGpyr+pB9adaGwXn4Q=="
|
||||
},
|
||||
"node_modules/@octokit/auth-token": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-4.0.0.tgz",
|
||||
@@ -10102,32 +10035,38 @@
|
||||
"@octokit/openapi-types": "^24.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@octokit/core/node_modules/universal-user-agent": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-6.0.1.tgz",
|
||||
"integrity": "sha512-yCzhz6FN2wU1NiiQRogkTQszlQSlpWaw8SvVegAc+bDxbzHgh1vX8uIe8OYyMH6DwH+sdTJsgMl36+mSMdRJIQ==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/@octokit/endpoint": {
|
||||
"version": "9.0.6",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-9.0.6.tgz",
|
||||
"integrity": "sha512-H1fNTMA57HbkFESSt3Y9+FBICv+0jFceJFPWDePYlR/iMGrwM5ph+Dd4XRQs+8X+PUFURLQgX9ChPfhJ/1uNQw==",
|
||||
"version": "10.1.4",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-10.1.4.tgz",
|
||||
"integrity": "sha512-OlYOlZIsfEVZm5HCSR8aSg02T2lbUWOsCQoPKfTXJwDzcHQBrVBGdGXb89dv2Kw2ToZaRtudp8O3ZIYoaOjKlA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@octokit/types": "^13.1.0",
|
||||
"universal-user-agent": "^6.0.0"
|
||||
"@octokit/types": "^14.0.0",
|
||||
"universal-user-agent": "^7.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 18"
|
||||
}
|
||||
},
|
||||
"node_modules/@octokit/endpoint/node_modules/@octokit/openapi-types": {
|
||||
"version": "24.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-24.2.0.tgz",
|
||||
"integrity": "sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg==",
|
||||
"version": "25.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-25.1.0.tgz",
|
||||
"integrity": "sha512-idsIggNXUKkk0+BExUn1dQ92sfysJrje03Q0bv0e+KPLrvyqZF8MnBpFz8UNfYDwB3Ie7Z0TByjWfzxt7vseaA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@octokit/endpoint/node_modules/@octokit/types": {
|
||||
"version": "13.10.0",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.10.0.tgz",
|
||||
"integrity": "sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA==",
|
||||
"version": "14.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-14.1.0.tgz",
|
||||
"integrity": "sha512-1y6DgTy8Jomcpu33N+p5w58l6xyt55Ar2I91RPiIA0xCJBXyUAhXCcmZaDWSANiha7R9a6qJJ2CRomGPZ6f46g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@octokit/openapi-types": "^24.2.0"
|
||||
"@octokit/openapi-types": "^25.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@octokit/graphql": {
|
||||
@@ -10159,6 +10098,12 @@
|
||||
"@octokit/openapi-types": "^24.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@octokit/graphql/node_modules/universal-user-agent": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-6.0.1.tgz",
|
||||
"integrity": "sha512-yCzhz6FN2wU1NiiQRogkTQszlQSlpWaw8SvVegAc+bDxbzHgh1vX8uIe8OYyMH6DwH+sdTJsgMl36+mSMdRJIQ==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/@octokit/oauth-authorization-url": {
|
||||
"version": "7.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/oauth-authorization-url/-/oauth-authorization-url-7.1.1.tgz",
|
||||
@@ -10181,18 +10126,6 @@
|
||||
"node": ">= 18"
|
||||
}
|
||||
},
|
||||
"node_modules/@octokit/oauth-methods/node_modules/@octokit/endpoint": {
|
||||
"version": "10.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-10.1.1.tgz",
|
||||
"integrity": "sha512-JYjh5rMOwXMJyUpj028cu0Gbp7qe/ihxfJMLc8VZBMMqSwLgOxDI1911gV4Enl1QSavAQNJcwmwBF9M0VvLh6Q==",
|
||||
"dependencies": {
|
||||
"@octokit/types": "^13.0.0",
|
||||
"universal-user-agent": "^7.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 18"
|
||||
}
|
||||
},
|
||||
"node_modules/@octokit/oauth-methods/node_modules/@octokit/openapi-types": {
|
||||
"version": "22.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-22.2.0.tgz",
|
||||
@@ -10231,11 +10164,6 @@
|
||||
"@octokit/openapi-types": "^22.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@octokit/oauth-methods/node_modules/universal-user-agent": {
|
||||
"version": "7.0.2",
|
||||
"resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-7.0.2.tgz",
|
||||
"integrity": "sha512-0JCqzSKnStlRRQfCdowvqy3cy0Dvtlb8xecj/H8JFZuCze4rwjPZQOgvFvn0Ws/usCHQFGpyr+pB9adaGwXn4Q=="
|
||||
},
|
||||
"node_modules/@octokit/openapi-types": {
|
||||
"version": "19.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-19.1.0.tgz",
|
||||
@@ -10376,31 +10304,54 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@octokit/request-error/node_modules/@octokit/openapi-types": {
|
||||
"version": "22.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-22.2.0.tgz",
|
||||
"integrity": "sha512-QBhVjcUa9W7Wwhm6DBFu6ZZ+1/t/oYxqc2tp81Pi41YNuJinbFRx8B133qVOrAaBbF7D/m0Et6f9/pZt9Rc+tg=="
|
||||
"version": "24.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-24.2.0.tgz",
|
||||
"integrity": "sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@octokit/request-error/node_modules/@octokit/types": {
|
||||
"version": "13.6.1",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.6.1.tgz",
|
||||
"integrity": "sha512-PHZE9Z+kWXb23Ndik8MKPirBPziOc0D2/3KH1P+6jK5nGWe96kadZuE4jev2/Jq7FvIfTlT2Ltg8Fv2x1v0a5g==",
|
||||
"version": "13.10.0",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.10.0.tgz",
|
||||
"integrity": "sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@octokit/openapi-types": "^22.2.0"
|
||||
"@octokit/openapi-types": "^24.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@octokit/request/node_modules/@octokit/endpoint": {
|
||||
"version": "9.0.6",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-9.0.6.tgz",
|
||||
"integrity": "sha512-H1fNTMA57HbkFESSt3Y9+FBICv+0jFceJFPWDePYlR/iMGrwM5ph+Dd4XRQs+8X+PUFURLQgX9ChPfhJ/1uNQw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@octokit/types": "^13.1.0",
|
||||
"universal-user-agent": "^6.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 18"
|
||||
}
|
||||
},
|
||||
"node_modules/@octokit/request/node_modules/@octokit/openapi-types": {
|
||||
"version": "22.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-22.2.0.tgz",
|
||||
"integrity": "sha512-QBhVjcUa9W7Wwhm6DBFu6ZZ+1/t/oYxqc2tp81Pi41YNuJinbFRx8B133qVOrAaBbF7D/m0Et6f9/pZt9Rc+tg=="
|
||||
"version": "24.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-24.2.0.tgz",
|
||||
"integrity": "sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@octokit/request/node_modules/@octokit/types": {
|
||||
"version": "13.6.1",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.6.1.tgz",
|
||||
"integrity": "sha512-PHZE9Z+kWXb23Ndik8MKPirBPziOc0D2/3KH1P+6jK5nGWe96kadZuE4jev2/Jq7FvIfTlT2Ltg8Fv2x1v0a5g==",
|
||||
"version": "13.10.0",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.10.0.tgz",
|
||||
"integrity": "sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@octokit/openapi-types": "^22.2.0"
|
||||
"@octokit/openapi-types": "^24.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@octokit/request/node_modules/universal-user-agent": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-6.0.1.tgz",
|
||||
"integrity": "sha512-yCzhz6FN2wU1NiiQRogkTQszlQSlpWaw8SvVegAc+bDxbzHgh1vX8uIe8OYyMH6DwH+sdTJsgMl36+mSMdRJIQ==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/@octokit/rest": {
|
||||
"version": "20.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/rest/-/rest-20.0.2.tgz",
|
||||
@@ -18288,7 +18239,8 @@
|
||||
"node_modules/fast-content-type-parse": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/fast-content-type-parse/-/fast-content-type-parse-1.1.0.tgz",
|
||||
"integrity": "sha512-fBHHqSTFLVnR61C+gltJuE5GkVQMV0S2nqUO8TJ+5Z3qAKG8vAx4FKai1s5jq/inV1+sREynIWSuQ6HgoSXpDQ=="
|
||||
"integrity": "sha512-fBHHqSTFLVnR61C+gltJuE5GkVQMV0S2nqUO8TJ+5Z3qAKG8vAx4FKai1s5jq/inV1+sREynIWSuQ6HgoSXpDQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/fast-copy": {
|
||||
"version": "3.0.1",
|
||||
@@ -24776,6 +24728,12 @@
|
||||
"jsonwebtoken": "^9.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/octokit-auth-probot/node_modules/universal-user-agent": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-6.0.1.tgz",
|
||||
"integrity": "sha512-yCzhz6FN2wU1NiiQRogkTQszlQSlpWaw8SvVegAc+bDxbzHgh1vX8uIe8OYyMH6DwH+sdTJsgMl36+mSMdRJIQ==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/odbc": {
|
||||
"version": "2.4.9",
|
||||
"resolved": "https://registry.npmjs.org/odbc/-/odbc-2.4.9.tgz",
|
||||
@@ -30705,9 +30663,10 @@
|
||||
"integrity": "sha512-G5o6f95b5BggDGuUfKDApKaCgNYy2x7OdHY0zSMF081O0EJobw+1130VONhrA7ezGSV2FNOGyM+KQpQZAr9bIQ=="
|
||||
},
|
||||
"node_modules/universal-user-agent": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-6.0.1.tgz",
|
||||
"integrity": "sha512-yCzhz6FN2wU1NiiQRogkTQszlQSlpWaw8SvVegAc+bDxbzHgh1vX8uIe8OYyMH6DwH+sdTJsgMl36+mSMdRJIQ=="
|
||||
"version": "7.0.3",
|
||||
"resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-7.0.3.tgz",
|
||||
"integrity": "sha512-TmnEAEAsBJVZM/AADELsK76llnwcf9vMKuPz8JflO1frO8Lchitr0fNaN9d+Ap0BjKtqWqd/J17qeDnXh8CL2A==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/universalify": {
|
||||
"version": "2.0.1",
|
||||
|
@@ -158,6 +158,7 @@
|
||||
"@octokit/core": "^5.2.1",
|
||||
"@octokit/plugin-paginate-graphql": "^4.0.1",
|
||||
"@octokit/plugin-retry": "^5.0.5",
|
||||
"@octokit/request": "8.4.1",
|
||||
"@octokit/rest": "^20.0.2",
|
||||
"@octokit/webhooks-types": "^7.3.1",
|
||||
"@octopusdeploy/api-client": "^3.4.1",
|
||||
|
@@ -0,0 +1,19 @@
|
||||
import { Knex } from "knex";
|
||||
|
||||
import { TableName } from "../schemas";
|
||||
|
||||
export async function up(knex: Knex): Promise<void> {
|
||||
if (!(await knex.schema.hasColumn(TableName.Reminder, "fromDate"))) {
|
||||
await knex.schema.alterTable(TableName.Reminder, (t) => {
|
||||
t.timestamp("fromDate", { useTz: true }).nullable();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export async function down(knex: Knex): Promise<void> {
|
||||
if (await knex.schema.hasColumn(TableName.Reminder, "fromDate")) {
|
||||
await knex.schema.alterTable(TableName.Reminder, (t) => {
|
||||
t.dropColumn("fromDate");
|
||||
});
|
||||
}
|
||||
}
|
@@ -14,7 +14,8 @@ export const RemindersSchema = z.object({
|
||||
repeatDays: z.number().nullable().optional(),
|
||||
nextReminderDate: z.date(),
|
||||
createdAt: z.date(),
|
||||
updatedAt: z.date()
|
||||
updatedAt: z.date(),
|
||||
fromDate: z.date().nullable().optional()
|
||||
});
|
||||
|
||||
export type TReminders = z.infer<typeof RemindersSchema>;
|
||||
|
@@ -1,8 +1,10 @@
|
||||
// weird commonjs-related error in the CI requires us to do the import like this
|
||||
import knex from "knex";
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
|
||||
import { TDbClient } from "@app/db";
|
||||
import { TableName, TAuditLogs } from "@app/db/schemas";
|
||||
import { getConfig } from "@app/lib/config/env";
|
||||
import { DatabaseError, GatewayTimeoutError } from "@app/lib/errors";
|
||||
import { ormify, selectAllTableCols, TOrmify } from "@app/lib/knex";
|
||||
import { logger } from "@app/lib/logger";
|
||||
@@ -150,43 +152,70 @@ export const auditLogDALFactory = (db: TDbClient) => {
|
||||
|
||||
// delete all audit log that have expired
|
||||
const pruneAuditLog: TAuditLogDALFactory["pruneAuditLog"] = async (tx) => {
|
||||
const AUDIT_LOG_PRUNE_BATCH_SIZE = 10000;
|
||||
const MAX_RETRY_ON_FAILURE = 3;
|
||||
const runPrune = async (dbClient: knex.Knex) => {
|
||||
const AUDIT_LOG_PRUNE_BATCH_SIZE = 10000;
|
||||
const MAX_RETRY_ON_FAILURE = 3;
|
||||
|
||||
const today = new Date();
|
||||
let deletedAuditLogIds: { id: string }[] = [];
|
||||
let numberOfRetryOnFailure = 0;
|
||||
let isRetrying = false;
|
||||
const today = new Date();
|
||||
let deletedAuditLogIds: { id: string }[] = [];
|
||||
let numberOfRetryOnFailure = 0;
|
||||
let isRetrying = false;
|
||||
|
||||
logger.info(`${QueueName.DailyResourceCleanUp}: audit log started`);
|
||||
do {
|
||||
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);
|
||||
logger.info(`${QueueName.DailyResourceCleanUp}: audit log started`);
|
||||
do {
|
||||
try {
|
||||
const findExpiredLogSubQuery = dbClient(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);
|
||||
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
deletedAuditLogIds = await (tx || db)(TableName.AuditLog)
|
||||
.whereIn("id", findExpiredLogSubQuery)
|
||||
.del()
|
||||
.returning("id");
|
||||
numberOfRetryOnFailure = 0; // reset
|
||||
} catch (error) {
|
||||
numberOfRetryOnFailure += 1;
|
||||
logger.error(error, "Failed to delete audit log on pruning");
|
||||
} finally {
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
await new Promise((resolve) => {
|
||||
setTimeout(resolve, 10); // time to breathe for db
|
||||
});
|
||||
}
|
||||
isRetrying = numberOfRetryOnFailure > 0;
|
||||
} while (deletedAuditLogIds.length > 0 || (isRetrying && numberOfRetryOnFailure < MAX_RETRY_ON_FAILURE));
|
||||
logger.info(`${QueueName.DailyResourceCleanUp}: audit log completed`);
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
deletedAuditLogIds = await dbClient(TableName.AuditLog)
|
||||
.whereIn("id", findExpiredLogSubQuery)
|
||||
.del()
|
||||
.returning("id");
|
||||
numberOfRetryOnFailure = 0; // reset
|
||||
} catch (error) {
|
||||
numberOfRetryOnFailure += 1;
|
||||
logger.error(error, "Failed to delete audit log on pruning");
|
||||
} finally {
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
await new Promise((resolve) => {
|
||||
setTimeout(resolve, 10); // time to breathe for db
|
||||
});
|
||||
}
|
||||
isRetrying = numberOfRetryOnFailure > 0;
|
||||
} while (deletedAuditLogIds.length > 0 || (isRetrying && numberOfRetryOnFailure < MAX_RETRY_ON_FAILURE));
|
||||
logger.info(`${QueueName.DailyResourceCleanUp}: audit log completed`);
|
||||
};
|
||||
|
||||
if (tx) {
|
||||
await runPrune(tx);
|
||||
} else {
|
||||
const QUERY_TIMEOUT_MS = 10 * 60 * 1000; // 10 minutes
|
||||
await db.transaction(async (trx) => {
|
||||
await trx.raw(`SET statement_timeout = ${QUERY_TIMEOUT_MS}`);
|
||||
await runPrune(trx);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return { ...auditLogOrm, pruneAuditLog, find };
|
||||
const create: TAuditLogDALFactory["create"] = async (tx) => {
|
||||
const config = getConfig();
|
||||
|
||||
if (config.DISABLE_AUDIT_LOG_STORAGE) {
|
||||
return {
|
||||
...tx,
|
||||
id: uuidv4(),
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date()
|
||||
};
|
||||
}
|
||||
|
||||
return auditLogOrm.create(tx);
|
||||
};
|
||||
|
||||
return { ...auditLogOrm, create, pruneAuditLog, find };
|
||||
};
|
||||
|
@@ -49,6 +49,7 @@ const baseSecretScanningDataSourceQuery = ({
|
||||
db.ref("encryptedCredentials").withSchema(TableName.AppConnection).as("connectionEncryptedCredentials"),
|
||||
db.ref("description").withSchema(TableName.AppConnection).as("connectionDescription"),
|
||||
db.ref("version").withSchema(TableName.AppConnection).as("connectionVersion"),
|
||||
db.ref("gatewayId").withSchema(TableName.AppConnection).as("connectionGatewayId"),
|
||||
db.ref("createdAt").withSchema(TableName.AppConnection).as("connectionCreatedAt"),
|
||||
db.ref("updatedAt").withSchema(TableName.AppConnection).as("connectionUpdatedAt"),
|
||||
db
|
||||
@@ -82,6 +83,7 @@ const expandSecretScanningDataSource = <
|
||||
connectionUpdatedAt,
|
||||
connectionVersion,
|
||||
connectionIsPlatformManagedCredentials,
|
||||
connectionGatewayId,
|
||||
...el
|
||||
} = dataSource;
|
||||
|
||||
@@ -100,7 +102,8 @@ const expandSecretScanningDataSource = <
|
||||
createdAt: connectionCreatedAt,
|
||||
updatedAt: connectionUpdatedAt,
|
||||
version: connectionVersion,
|
||||
isPlatformManagedCredentials: connectionIsPlatformManagedCredentials
|
||||
isPlatformManagedCredentials: connectionIsPlatformManagedCredentials,
|
||||
gatewayId: connectionGatewayId
|
||||
}
|
||||
: undefined
|
||||
};
|
||||
|
@@ -59,6 +59,7 @@ const envSchema = z
|
||||
AUDIT_LOGS_DB_ROOT_CERT: zpStr(
|
||||
z.string().describe("Postgres database base64-encoded CA cert for Audit logs").optional()
|
||||
),
|
||||
DISABLE_AUDIT_LOG_STORAGE: zodStrBool.default("false").optional().describe("Disable audit log storage"),
|
||||
MAX_LEASE_LIMIT: z.coerce.number().default(10000),
|
||||
DB_ROOT_CERT: zpStr(z.string().describe("Postgres database base64-encoded CA cert").optional()),
|
||||
DB_HOST: zpStr(z.string().describe("Postgres database host").optional()),
|
||||
@@ -482,6 +483,15 @@ export const overwriteSchema: {
|
||||
fields: { key: keyof TEnvConfig; description?: string }[];
|
||||
};
|
||||
} = {
|
||||
auditLogs: {
|
||||
name: "Audit Logs",
|
||||
fields: [
|
||||
{
|
||||
key: "DISABLE_AUDIT_LOG_STORAGE",
|
||||
description: "Disable audit log storage"
|
||||
}
|
||||
]
|
||||
},
|
||||
aws: {
|
||||
name: "AWS",
|
||||
fields: [
|
||||
|
@@ -2144,7 +2144,8 @@ export const registerRoutes = async (
|
||||
inviteOnlySignup: z.boolean().optional(),
|
||||
redisConfigured: z.boolean().optional(),
|
||||
secretScanningConfigured: z.boolean().optional(),
|
||||
samlDefaultOrgSlug: z.string().optional()
|
||||
samlDefaultOrgSlug: z.string().optional(),
|
||||
auditLogStorageDisabled: z.boolean().optional()
|
||||
})
|
||||
}
|
||||
},
|
||||
@@ -2171,7 +2172,8 @@ export const registerRoutes = async (
|
||||
inviteOnlySignup: Boolean(serverCfg.allowSignUp),
|
||||
redisConfigured: cfg.isRedisConfigured,
|
||||
secretScanningConfigured: cfg.isSecretScanningConfigured,
|
||||
samlDefaultOrgSlug: cfg.samlDefaultOrgSlug
|
||||
samlDefaultOrgSlug: cfg.samlDefaultOrgSlug,
|
||||
auditLogStorageDisabled: Boolean(cfg.DISABLE_AUDIT_LOG_STORAGE)
|
||||
};
|
||||
}
|
||||
});
|
||||
|
@@ -22,6 +22,7 @@ export const registerSecretReminderRouter = async (server: FastifyZodProvider) =
|
||||
message: z.string().trim().max(1024).optional(),
|
||||
repeatDays: z.number().min(1).nullable().optional(),
|
||||
nextReminderDate: z.string().datetime().nullable().optional(),
|
||||
fromDate: z.string().datetime().nullable().optional(),
|
||||
recipients: z.string().array().optional()
|
||||
})
|
||||
.refine((data) => {
|
||||
@@ -45,6 +46,7 @@ export const registerSecretReminderRouter = async (server: FastifyZodProvider) =
|
||||
message: req.body.message,
|
||||
repeatDays: req.body.repeatDays,
|
||||
nextReminderDate: req.body.nextReminderDate,
|
||||
fromDate: req.body.fromDate,
|
||||
recipients: req.body.recipients
|
||||
}
|
||||
});
|
||||
|
@@ -1,4 +1,5 @@
|
||||
import { createAppAuth } from "@octokit/auth-app";
|
||||
import { request } from "@octokit/request";
|
||||
import { AxiosError, AxiosRequestConfig, AxiosResponse } from "axios";
|
||||
import https from "https";
|
||||
import RE2 from "re2";
|
||||
@@ -12,7 +13,6 @@ import { GatewayProxyProtocol, withGatewayProxy } from "@app/lib/gateway";
|
||||
import { logger } from "@app/lib/logger";
|
||||
import { blockLocalAndPrivateIpAddresses } from "@app/lib/validator";
|
||||
import { getAppConnectionMethodName } from "@app/services/app-connection/app-connection-fns";
|
||||
import { IntegrationUrls } from "@app/services/integration-auth/integration-list";
|
||||
|
||||
import { AppConnection } from "../app-connection-enums";
|
||||
import { GitHubConnectionMethod } from "./github-connection-enums";
|
||||
@@ -30,6 +30,23 @@ export const getGitHubConnectionListItem = () => {
|
||||
};
|
||||
};
|
||||
|
||||
export const getGitHubInstanceApiUrl = async (config: {
|
||||
credentials: Pick<TGitHubConnectionConfig["credentials"], "host" | "instanceType">;
|
||||
}) => {
|
||||
const host = config.credentials.host || "github.com";
|
||||
|
||||
await blockLocalAndPrivateIpAddresses(`https://${host}`);
|
||||
|
||||
let apiBase: string;
|
||||
if (config.credentials.instanceType === "server") {
|
||||
apiBase = `${host}/api/v3`;
|
||||
} else {
|
||||
apiBase = `api.${host}`;
|
||||
}
|
||||
|
||||
return apiBase;
|
||||
};
|
||||
|
||||
export const requestWithGitHubGateway = async <T>(
|
||||
appConnection: { gatewayId?: string | null },
|
||||
gatewayService: Pick<TGatewayServiceFactory, "fnGetGatewayClientTlsByGatewayId">,
|
||||
@@ -73,7 +90,10 @@ export const requestWithGitHubGateway = async <T>(
|
||||
return await httpRequest.request(finalRequestConfig);
|
||||
} catch (error) {
|
||||
const axiosError = error as AxiosError;
|
||||
logger.error("Error during GitHub gateway request:", axiosError.message, axiosError.response?.data);
|
||||
logger.error(
|
||||
{ message: axiosError.message, data: axiosError.response?.data },
|
||||
"Error during GitHub gateway request:"
|
||||
);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
@@ -112,7 +132,10 @@ export const getGitHubAppAuthToken = async (appConnection: TGitHubConnection) =>
|
||||
const appAuth = createAppAuth({
|
||||
appId,
|
||||
privateKey: appPrivateKey,
|
||||
installationId: appConnection.credentials.installationId
|
||||
installationId: appConnection.credentials.installationId,
|
||||
request: request.defaults({
|
||||
baseUrl: `https://${await getGitHubInstanceApiUrl(appConnection)}`
|
||||
})
|
||||
});
|
||||
|
||||
const { token } = await appAuth({ type: "installation" });
|
||||
@@ -141,7 +164,7 @@ export const makePaginatedGitHubRequest = async <T, R = T[]>(
|
||||
|
||||
const token =
|
||||
method === GitHubConnectionMethod.OAuth ? credentials.accessToken : await getGitHubAppAuthToken(appConnection);
|
||||
let url: string | null = `https://api.${credentials.host || "github.com"}${path}`;
|
||||
let url: string | null = `https://${await getGitHubInstanceApiUrl(appConnection)}${path}`;
|
||||
let results: T[] = [];
|
||||
let i = 0;
|
||||
|
||||
@@ -325,6 +348,8 @@ export const validateGitHubConnectionCredentials = async (
|
||||
});
|
||||
}
|
||||
} catch (e: unknown) {
|
||||
logger.error(e, "Unable to verify GitHub connection");
|
||||
|
||||
if (e instanceof BadRequestError) {
|
||||
throw e;
|
||||
}
|
||||
@@ -355,7 +380,7 @@ export const validateGitHubConnectionCredentials = async (
|
||||
};
|
||||
}[];
|
||||
}>(config, gatewayService, {
|
||||
url: IntegrationUrls.GITHUB_USER_INSTALLATIONS.replace("api.github.com", `api.${host}`),
|
||||
url: `https://${await getGitHubInstanceApiUrl(config)}/user/installations`,
|
||||
headers: {
|
||||
Accept: "application/json",
|
||||
Authorization: `Bearer ${tokenResp.data.access_token}`,
|
||||
@@ -377,11 +402,15 @@ export const validateGitHubConnectionCredentials = async (
|
||||
switch (method) {
|
||||
case GitHubConnectionMethod.App:
|
||||
return {
|
||||
installationId: credentials.installationId
|
||||
installationId: credentials.installationId,
|
||||
instanceType: credentials.instanceType,
|
||||
host: credentials.host
|
||||
};
|
||||
case GitHubConnectionMethod.OAuth:
|
||||
return {
|
||||
accessToken: tokenResp.data.access_token
|
||||
accessToken: tokenResp.data.access_token,
|
||||
instanceType: credentials.instanceType,
|
||||
host: credentials.host
|
||||
};
|
||||
default:
|
||||
throw new InternalServerError({
|
||||
|
@@ -10,26 +10,59 @@ import {
|
||||
|
||||
import { GitHubConnectionMethod } from "./github-connection-enums";
|
||||
|
||||
export const GitHubConnectionOAuthInputCredentialsSchema = z.object({
|
||||
code: z.string().trim().min(1, "OAuth code required"),
|
||||
host: z.string().trim().optional()
|
||||
});
|
||||
export const GitHubConnectionOAuthInputCredentialsSchema = z.union([
|
||||
z.object({
|
||||
code: z.string().trim().min(1, "OAuth code required"),
|
||||
instanceType: z.literal("server"),
|
||||
host: z.string().trim().min(1, "Host is required for server instance type")
|
||||
}),
|
||||
z.object({
|
||||
code: z.string().trim().min(1, "OAuth code required"),
|
||||
instanceType: z.literal("cloud").optional(),
|
||||
host: z.string().trim().optional()
|
||||
})
|
||||
]);
|
||||
|
||||
export const GitHubConnectionAppInputCredentialsSchema = z.object({
|
||||
code: z.string().trim().min(1, "GitHub App code required"),
|
||||
installationId: z.string().min(1, "GitHub App Installation ID required"),
|
||||
host: z.string().trim().optional()
|
||||
});
|
||||
export const GitHubConnectionAppInputCredentialsSchema = z.union([
|
||||
z.object({
|
||||
code: z.string().trim().min(1, "GitHub App code required"),
|
||||
installationId: z.string().min(1, "GitHub App Installation ID required"),
|
||||
instanceType: z.literal("server"),
|
||||
host: z.string().trim().min(1, "Host is required for server instance type")
|
||||
}),
|
||||
z.object({
|
||||
code: z.string().trim().min(1, "GitHub App code required"),
|
||||
installationId: z.string().min(1, "GitHub App Installation ID required"),
|
||||
instanceType: z.literal("cloud").optional(),
|
||||
host: z.string().trim().optional()
|
||||
})
|
||||
]);
|
||||
|
||||
export const GitHubConnectionOAuthOutputCredentialsSchema = z.object({
|
||||
accessToken: z.string(),
|
||||
host: z.string().trim().optional()
|
||||
});
|
||||
export const GitHubConnectionOAuthOutputCredentialsSchema = z.union([
|
||||
z.object({
|
||||
accessToken: z.string(),
|
||||
instanceType: z.literal("server"),
|
||||
host: z.string().trim().min(1)
|
||||
}),
|
||||
z.object({
|
||||
accessToken: z.string(),
|
||||
instanceType: z.literal("cloud").optional(),
|
||||
host: z.string().trim().optional()
|
||||
})
|
||||
]);
|
||||
|
||||
export const GitHubConnectionAppOutputCredentialsSchema = z.object({
|
||||
installationId: z.string(),
|
||||
host: z.string().trim().optional()
|
||||
});
|
||||
export const GitHubConnectionAppOutputCredentialsSchema = z.union([
|
||||
z.object({
|
||||
installationId: z.string(),
|
||||
instanceType: z.literal("server"),
|
||||
host: z.string().trim().min(1)
|
||||
}),
|
||||
z.object({
|
||||
installationId: z.string(),
|
||||
instanceType: z.literal("cloud").optional(),
|
||||
host: z.string().trim().optional()
|
||||
})
|
||||
]);
|
||||
|
||||
export const ValidateGitHubConnectionCredentialsSchema = z.discriminatedUnion("method", [
|
||||
z.object({
|
||||
@@ -84,11 +117,17 @@ export const GitHubConnectionSchema = z.intersection(
|
||||
export const SanitizedGitHubConnectionSchema = z.discriminatedUnion("method", [
|
||||
BaseGitHubConnectionSchema.extend({
|
||||
method: z.literal(GitHubConnectionMethod.App),
|
||||
credentials: GitHubConnectionAppOutputCredentialsSchema.pick({})
|
||||
credentials: z.object({
|
||||
instanceType: z.union([z.literal("server"), z.literal("cloud")]).optional(),
|
||||
host: z.string().optional()
|
||||
})
|
||||
}),
|
||||
BaseGitHubConnectionSchema.extend({
|
||||
method: z.literal(GitHubConnectionMethod.OAuth),
|
||||
credentials: GitHubConnectionOAuthOutputCredentialsSchema.pick({})
|
||||
credentials: z.object({
|
||||
instanceType: z.union([z.literal("server"), z.literal("cloud")]).optional(),
|
||||
host: z.string().optional()
|
||||
})
|
||||
})
|
||||
]);
|
||||
|
||||
|
@@ -15,10 +15,15 @@ export const validateAltNameField = z
|
||||
.trim()
|
||||
.refine(
|
||||
(name) => {
|
||||
return isFQDN(name, { allow_wildcard: true }) || z.string().email().safeParse(name).success || isValidIp(name);
|
||||
return (
|
||||
isFQDN(name, { allow_wildcard: true, require_tld: false }) ||
|
||||
z.string().url().safeParse(name).success ||
|
||||
z.string().email().safeParse(name).success ||
|
||||
isValidIp(name)
|
||||
);
|
||||
},
|
||||
{
|
||||
message: "SAN must be a valid hostname, email address, or IP address"
|
||||
message: "SAN must be a valid hostname, email address, IP address or URL"
|
||||
}
|
||||
);
|
||||
|
||||
@@ -39,10 +44,15 @@ export const validateAltNamesField = z
|
||||
if (data === "") return true;
|
||||
// Split and validate each alt name
|
||||
return data.split(", ").every((name) => {
|
||||
return isFQDN(name, { allow_wildcard: true }) || z.string().email().safeParse(name).success || isValidIp(name);
|
||||
return (
|
||||
isFQDN(name, { allow_wildcard: true, require_tld: false }) ||
|
||||
z.string().url().safeParse(name).success ||
|
||||
z.string().email().safeParse(name).success ||
|
||||
isValidIp(name)
|
||||
);
|
||||
});
|
||||
},
|
||||
{
|
||||
message: "Each alt name must be a valid hostname or email address"
|
||||
message: "Each alt name must be a valid hostname, email address, IP address or URL"
|
||||
}
|
||||
);
|
||||
|
@@ -152,7 +152,7 @@ export const InternalCertificateAuthorityFns = ({
|
||||
extensions.push(extendedKeyUsagesExtension);
|
||||
}
|
||||
|
||||
let altNamesArray: { type: "email" | "dns"; value: string }[] = [];
|
||||
let altNamesArray: { type: "email" | "dns" | "ip" | "url"; value: string }[] = [];
|
||||
|
||||
if (subscriber.subjectAlternativeNames?.length) {
|
||||
altNamesArray = subscriber.subjectAlternativeNames.map((altName) => {
|
||||
@@ -160,10 +160,18 @@ export const InternalCertificateAuthorityFns = ({
|
||||
return { type: "email", value: altName };
|
||||
}
|
||||
|
||||
if (isFQDN(altName, { allow_wildcard: true })) {
|
||||
if (isFQDN(altName, { allow_wildcard: true, require_tld: false })) {
|
||||
return { type: "dns", value: altName };
|
||||
}
|
||||
|
||||
if (z.string().url().safeParse(altName).success) {
|
||||
return { type: "url", value: altName };
|
||||
}
|
||||
|
||||
if (z.string().ip().safeParse(altName).success) {
|
||||
return { type: "ip", value: altName };
|
||||
}
|
||||
|
||||
throw new BadRequestError({ message: `Invalid SAN entry: ${altName}` });
|
||||
});
|
||||
|
||||
@@ -418,7 +426,7 @@ export const InternalCertificateAuthorityFns = ({
|
||||
);
|
||||
}
|
||||
|
||||
let altNamesArray: { type: "email" | "dns"; value: string }[] = [];
|
||||
let altNamesArray: { type: "email" | "dns" | "ip" | "url"; value: string }[] = [];
|
||||
|
||||
if (altNames) {
|
||||
altNamesArray = altNames.split(",").map((altName) => {
|
||||
@@ -426,10 +434,18 @@ export const InternalCertificateAuthorityFns = ({
|
||||
return { type: "email", value: altName };
|
||||
}
|
||||
|
||||
if (isFQDN(altName, { allow_wildcard: true })) {
|
||||
if (isFQDN(altName, { allow_wildcard: true, require_tld: false })) {
|
||||
return { type: "dns", value: altName };
|
||||
}
|
||||
|
||||
if (z.string().url().safeParse(altName).success) {
|
||||
return { type: "url", value: altName };
|
||||
}
|
||||
|
||||
if (z.string().ip().safeParse(altName).success) {
|
||||
return { type: "ip", value: altName };
|
||||
}
|
||||
|
||||
throw new BadRequestError({ message: `Invalid SAN entry: ${altName}` });
|
||||
});
|
||||
|
||||
|
@@ -79,25 +79,33 @@ export const reminderServiceFactory = ({
|
||||
repeatDays,
|
||||
nextReminderDate: nextReminderDateInput,
|
||||
recipients,
|
||||
projectId
|
||||
projectId,
|
||||
fromDate: fromDateInput
|
||||
}: {
|
||||
secretId?: string;
|
||||
message?: string | null;
|
||||
repeatDays?: number | null;
|
||||
nextReminderDate?: string | null;
|
||||
recipients?: string[] | null;
|
||||
fromDate?: string | null;
|
||||
projectId: string;
|
||||
}) => {
|
||||
if (!secretId) {
|
||||
throw new BadRequestError({ message: "secretId is required" });
|
||||
}
|
||||
let nextReminderDate;
|
||||
let fromDate;
|
||||
if (nextReminderDateInput) {
|
||||
nextReminderDate = new Date(nextReminderDateInput);
|
||||
}
|
||||
|
||||
if (repeatDays && repeatDays > 0) {
|
||||
nextReminderDate = $addDays(repeatDays);
|
||||
if (repeatDays) {
|
||||
if (fromDateInput) {
|
||||
fromDate = new Date(fromDateInput);
|
||||
nextReminderDate = fromDate;
|
||||
} else {
|
||||
nextReminderDate = $addDays(repeatDays);
|
||||
}
|
||||
}
|
||||
|
||||
if (!nextReminderDate) {
|
||||
@@ -112,7 +120,8 @@ export const reminderServiceFactory = ({
|
||||
await reminderDAL.updateById(existingReminder.id, {
|
||||
message,
|
||||
repeatDays,
|
||||
nextReminderDate
|
||||
nextReminderDate,
|
||||
fromDate
|
||||
});
|
||||
reminderId = existingReminder.id;
|
||||
} else {
|
||||
@@ -121,7 +130,8 @@ export const reminderServiceFactory = ({
|
||||
secretId,
|
||||
message,
|
||||
repeatDays,
|
||||
nextReminderDate
|
||||
nextReminderDate,
|
||||
fromDate
|
||||
});
|
||||
reminderId = newReminder.id;
|
||||
}
|
||||
@@ -280,14 +290,28 @@ export const reminderServiceFactory = ({
|
||||
}
|
||||
|
||||
const processedReminders = remindersData.map(
|
||||
({ secretId, message, repeatDays, nextReminderDate: nextReminderDateInput, recipients, projectId }) => {
|
||||
({
|
||||
secretId,
|
||||
message,
|
||||
repeatDays,
|
||||
nextReminderDate: nextReminderDateInput,
|
||||
recipients,
|
||||
projectId,
|
||||
fromDate: fromDateInput
|
||||
}) => {
|
||||
let nextReminderDate;
|
||||
let fromDate;
|
||||
if (nextReminderDateInput) {
|
||||
nextReminderDate = new Date(nextReminderDateInput);
|
||||
}
|
||||
|
||||
if (repeatDays && repeatDays > 0 && !nextReminderDate) {
|
||||
nextReminderDate = $addDays(repeatDays);
|
||||
if (repeatDays && !nextReminderDate) {
|
||||
if (fromDateInput) {
|
||||
fromDate = new Date(fromDateInput);
|
||||
nextReminderDate = fromDate;
|
||||
} else {
|
||||
nextReminderDate = $addDays(repeatDays);
|
||||
}
|
||||
}
|
||||
|
||||
if (!nextReminderDate) {
|
||||
@@ -302,17 +326,19 @@ export const reminderServiceFactory = ({
|
||||
repeatDays,
|
||||
nextReminderDate,
|
||||
recipients: recipients ? [...new Set(recipients)] : [],
|
||||
projectId
|
||||
projectId,
|
||||
fromDate
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
const newReminders = await reminderDAL.insertMany(
|
||||
processedReminders.map(({ secretId, message, repeatDays, nextReminderDate }) => ({
|
||||
processedReminders.map(({ secretId, message, repeatDays, nextReminderDate, fromDate }) => ({
|
||||
secretId,
|
||||
message,
|
||||
repeatDays,
|
||||
nextReminderDate
|
||||
nextReminderDate,
|
||||
fromDate
|
||||
})),
|
||||
tx
|
||||
);
|
||||
|
@@ -8,6 +8,7 @@ export type TReminder = {
|
||||
message?: string | null;
|
||||
repeatDays?: number | null;
|
||||
nextReminderDate: Date;
|
||||
fromDate?: Date | null;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
};
|
||||
@@ -21,6 +22,7 @@ export type TCreateReminderDTO = {
|
||||
secretId?: string;
|
||||
message?: string | null;
|
||||
repeatDays?: number | null;
|
||||
fromDate?: string | null;
|
||||
nextReminderDate?: string | null;
|
||||
recipients?: string[] | null;
|
||||
};
|
||||
@@ -31,6 +33,7 @@ export type TBatchCreateReminderDTO = {
|
||||
message?: string | null;
|
||||
repeatDays?: number | null;
|
||||
nextReminderDate?: string | Date | null;
|
||||
fromDate?: Date | null;
|
||||
recipients?: string[] | null;
|
||||
projectId?: string;
|
||||
}[];
|
||||
@@ -95,6 +98,7 @@ export interface TReminderServiceFactory {
|
||||
nextReminderDate?: string | null;
|
||||
recipients?: string[] | null;
|
||||
projectId: string;
|
||||
fromDate?: string | null;
|
||||
}) => Promise<{
|
||||
id: string;
|
||||
created: boolean;
|
||||
|
@@ -3,6 +3,7 @@ import sodium from "libsodium-wrappers";
|
||||
import { TGatewayServiceFactory } from "@app/ee/services/gateway/gateway-service";
|
||||
import {
|
||||
getGitHubAppAuthToken,
|
||||
getGitHubInstanceApiUrl,
|
||||
GitHubConnectionMethod,
|
||||
makePaginatedGitHubRequest,
|
||||
requestWithGitHubGateway
|
||||
@@ -73,7 +74,7 @@ const getPublicKey = async (
|
||||
}
|
||||
|
||||
const response = await requestWithGitHubGateway<TGitHubPublicKey>(connection, gatewayService, {
|
||||
url: `https://api.${connection.credentials.host || "github.com"}${path}`,
|
||||
url: `https://${await getGitHubInstanceApiUrl(connection)}${path}`,
|
||||
method: "GET",
|
||||
headers: {
|
||||
Accept: "application/vnd.github+json",
|
||||
@@ -111,7 +112,7 @@ const deleteSecret = async (
|
||||
}
|
||||
|
||||
await requestWithGitHubGateway(connection, gatewayService, {
|
||||
url: `https://api.${connection.credentials.host || "github.com"}${path}`,
|
||||
url: `https://${await getGitHubInstanceApiUrl(connection)}${path}`,
|
||||
method: "DELETE",
|
||||
headers: {
|
||||
Accept: "application/vnd.github+json",
|
||||
@@ -157,7 +158,7 @@ const putSecret = async (
|
||||
}
|
||||
|
||||
await requestWithGitHubGateway(connection, gatewayService, {
|
||||
url: `https://api.${connection.credentials.host || "github.com"}${path}`,
|
||||
url: `https://${await getGitHubInstanceApiUrl(connection)}${path}`,
|
||||
method: "PUT",
|
||||
headers: {
|
||||
Accept: "application/vnd.github+json",
|
||||
|
@@ -30,6 +30,7 @@ const baseSecretSyncQuery = ({ filter, db, tx }: { db: TDbClient; filter?: Secre
|
||||
db.ref("encryptedCredentials").withSchema(TableName.AppConnection).as("connectionEncryptedCredentials"),
|
||||
db.ref("description").withSchema(TableName.AppConnection).as("connectionDescription"),
|
||||
db.ref("version").withSchema(TableName.AppConnection).as("connectionVersion"),
|
||||
db.ref("gatewayId").withSchema(TableName.AppConnection).as("connectionGatewayId"),
|
||||
db.ref("createdAt").withSchema(TableName.AppConnection).as("connectionCreatedAt"),
|
||||
db.ref("updatedAt").withSchema(TableName.AppConnection).as("connectionUpdatedAt"),
|
||||
db
|
||||
@@ -65,6 +66,7 @@ const expandSecretSync = (
|
||||
connectionUpdatedAt,
|
||||
connectionVersion,
|
||||
connectionIsPlatformManagedCredentials,
|
||||
connectionGatewayId,
|
||||
...el
|
||||
} = secretSync;
|
||||
|
||||
@@ -83,7 +85,8 @@ const expandSecretSync = (
|
||||
createdAt: connectionCreatedAt,
|
||||
updatedAt: connectionUpdatedAt,
|
||||
version: connectionVersion,
|
||||
isPlatformManagedCredentials: connectionIsPlatformManagedCredentials
|
||||
isPlatformManagedCredentials: connectionIsPlatformManagedCredentials,
|
||||
gatewayId: connectionGatewayId
|
||||
},
|
||||
folder: folder
|
||||
? {
|
||||
|
@@ -1,7 +1,9 @@
|
||||
import { Button, Heading, Link, Section, Text } from "@react-email/components";
|
||||
import { Heading, Section, Text } from "@react-email/components";
|
||||
import React from "react";
|
||||
|
||||
import { BaseButton } from "./BaseButton";
|
||||
import { BaseEmailWrapper, BaseEmailWrapperProps } from "./BaseEmailWrapper";
|
||||
import { BaseLink } from "./BaseLink";
|
||||
|
||||
interface AccessApprovalRequestTemplateProps extends Omit<BaseEmailWrapperProps, "title" | "preview" | "children"> {
|
||||
projectName: string;
|
||||
@@ -38,18 +40,15 @@ export const AccessApprovalRequestTemplate = ({
|
||||
<Heading className="text-black text-[18px] leading-[28px] text-center font-normal p-0 mx-0">
|
||||
You have a new access approval request pending review for the project <strong>{projectName}</strong>
|
||||
</Heading>
|
||||
<Section className="px-[24px] mt-[36px] pt-[12px] pb-[8px] border border-solid border-gray-200 rounded-md bg-gray-50">
|
||||
<Section className="px-[24px] mb-[28px] mt-[36px] pt-[12px] pb-[8px] border border-solid border-gray-200 rounded-md bg-gray-50">
|
||||
<Text className="text-black text-[14px] leading-[24px]">
|
||||
<strong>{requesterFullName}</strong> (
|
||||
<Link href={`mailto:${requesterEmail}`} className="text-slate-700 no-underline">
|
||||
{requesterEmail}
|
||||
</Link>
|
||||
) has requested {isTemporary ? "temporary" : "permanent"} access to <strong>{secretPath}</strong> in the{" "}
|
||||
<strong>{requesterFullName}</strong> (<BaseLink href={`mailto:${requesterEmail}`}>{requesterEmail}</BaseLink>)
|
||||
has requested {isTemporary ? "temporary" : "permanent"} access to <strong>{secretPath}</strong> in the{" "}
|
||||
<strong>{environment}</strong> environment.
|
||||
</Text>
|
||||
|
||||
{isTemporary && (
|
||||
<Text className="text-[14px] text-red-500 leading-[24px]">
|
||||
<Text className="text-[14px] text-red-600 leading-[24px]">
|
||||
<strong>This access will expire {expiresIn} after approval.</strong>
|
||||
</Text>
|
||||
)}
|
||||
@@ -67,13 +66,8 @@ export const AccessApprovalRequestTemplate = ({
|
||||
</Text>
|
||||
)}
|
||||
</Section>
|
||||
<Section className="text-center mt-[28px]">
|
||||
<Button
|
||||
href={approvalUrl}
|
||||
className="rounded-md p-3 px-[28px] my-[8px] text-center text-[16px] bg-[#EBF852] border-solid border border-[#d1e309] text-black font-medium"
|
||||
>
|
||||
Review Request
|
||||
</Button>
|
||||
<Section className="text-center">
|
||||
<BaseButton href={approvalUrl}>Review Request</BaseButton>
|
||||
</Section>
|
||||
</BaseEmailWrapper>
|
||||
);
|
||||
|
18
backend/src/services/smtp/emails/BaseButton.tsx
Normal file
18
backend/src/services/smtp/emails/BaseButton.tsx
Normal file
@@ -0,0 +1,18 @@
|
||||
import { Button } from "@react-email/components";
|
||||
import React from "react";
|
||||
|
||||
type Props = {
|
||||
href: string;
|
||||
children: string;
|
||||
};
|
||||
|
||||
export const BaseButton = ({ href, children }: Props) => {
|
||||
return (
|
||||
<Button
|
||||
href={href}
|
||||
className="rounded-[8px] py-[12px] px-[28px] my-[8px] text-center text-[16px] bg-[#EBF852] border-solid border border-[#d1e309] text-black font-medium"
|
||||
>
|
||||
{children}
|
||||
</Button>
|
||||
);
|
||||
};
|
@@ -16,23 +16,21 @@ export const BaseEmailWrapper = ({ title, preview, children, siteUrl }: BaseEmai
|
||||
<Body className="bg-gray-300 my-auto mx-auto font-sans px-[8px] py-[4px]">
|
||||
<Preview>{preview}</Preview>
|
||||
<Container className="bg-white rounded-xl my-[40px] mx-auto pb-[0px] max-w-[500px]">
|
||||
<Section className="border-0 border-b border-[#d1e309] border-solid bg-[#EBF852] mb-[44px] h-[10px] rounded-t-xl" />
|
||||
<Section className="px-[32px] mb-[18px]">
|
||||
<Section className="w-[48px] h-[48px] border border-solid border-gray-300 rounded-full bg-gray-100 mx-auto">
|
||||
<Img
|
||||
src={`https://infisical.com/_next/image?url=%2Fimages%2Flogo-black.png&w=64&q=75`}
|
||||
width="32"
|
||||
alt="Infisical Logo"
|
||||
className="mx-auto"
|
||||
/>
|
||||
</Section>
|
||||
<Section className="mb-[24px] px-[24px] mt-[24px]">
|
||||
<Img
|
||||
src="https://infisical.com/_next/image?url=%2Fimages%2Flogo-black.png&w=64&q=75"
|
||||
width="36"
|
||||
alt="Infisical Logo"
|
||||
className="mx-auto"
|
||||
/>
|
||||
</Section>
|
||||
<Hr className=" mb-[32px] mt-[0px] h-[1px]" />
|
||||
<Section className="px-[28px]">{children}</Section>
|
||||
<Hr className=" mt-[32px] mb-[0px] h-[1px]" />
|
||||
<Section className="px-[24px] text-center">
|
||||
<Text className="text-gray-500 text-[12px]">
|
||||
Email sent via{" "}
|
||||
<Link href={siteUrl} className="text-slate-700 no-underline">
|
||||
<Link href={siteUrl} className="text-slate-700 underline decoration-slate-700">
|
||||
Infisical
|
||||
</Link>
|
||||
</Text>
|
||||
|
15
backend/src/services/smtp/emails/BaseLink.tsx
Normal file
15
backend/src/services/smtp/emails/BaseLink.tsx
Normal file
@@ -0,0 +1,15 @@
|
||||
import { Link } from "@react-email/components";
|
||||
import React from "react";
|
||||
|
||||
type Props = {
|
||||
href: string;
|
||||
children: string;
|
||||
};
|
||||
|
||||
export const BaseLink = ({ href, children }: Props) => {
|
||||
return (
|
||||
<Link href={href} className="text-slate-700 underline decoration-slate-700">
|
||||
{children}
|
||||
</Link>
|
||||
);
|
||||
};
|
@@ -1,7 +1,8 @@
|
||||
import { Heading, Link, Section, Text } from "@react-email/components";
|
||||
import { Heading, Section, Text } from "@react-email/components";
|
||||
import React from "react";
|
||||
|
||||
import { BaseEmailWrapper, BaseEmailWrapperProps } from "./BaseEmailWrapper";
|
||||
import { BaseLink } from "./BaseLink";
|
||||
|
||||
interface EmailMfaTemplateProps extends Omit<BaseEmailWrapperProps, "title" | "preview" | "children"> {
|
||||
code: string;
|
||||
@@ -25,11 +26,7 @@ export const EmailMfaTemplate = ({ code, siteUrl, isCloud }: EmailMfaTemplatePro
|
||||
<strong>Not you?</strong>{" "}
|
||||
{isCloud ? (
|
||||
<>
|
||||
Contact us at{" "}
|
||||
<Link href="mailto:support@infisical.com" className="text-slate-700 no-underline">
|
||||
support@infisical.com
|
||||
</Link>{" "}
|
||||
immediately
|
||||
Contact us at <BaseLink href="mailto:support@infisical.com">support@infisical.com</BaseLink> immediately
|
||||
</>
|
||||
) : (
|
||||
"Contact your administrator immediately"
|
||||
|
@@ -1,7 +1,8 @@
|
||||
import { Heading, Link, Section, Text } from "@react-email/components";
|
||||
import { Heading, Section, Text } from "@react-email/components";
|
||||
import React from "react";
|
||||
|
||||
import { BaseEmailWrapper, BaseEmailWrapperProps } from "./BaseEmailWrapper";
|
||||
import { BaseLink } from "./BaseLink";
|
||||
|
||||
interface EmailVerificationTemplateProps extends Omit<BaseEmailWrapperProps, "title" | "preview" | "children"> {
|
||||
code: string;
|
||||
@@ -29,10 +30,7 @@ export const EmailVerificationTemplate = ({ code, siteUrl, isCloud }: EmailVerif
|
||||
<strong>Questions about Infisical?</strong>{" "}
|
||||
{isCloud ? (
|
||||
<>
|
||||
Email us at{" "}
|
||||
<Link href="mailto:support@infisical.com" className="text-slate-700 no-underline">
|
||||
support@infisical.com
|
||||
</Link>
|
||||
Email us at <BaseLink href="mailto:support@infisical.com">support@infisical.com</BaseLink>
|
||||
</>
|
||||
) : (
|
||||
"Contact your administrator"
|
||||
|
@@ -1,7 +1,8 @@
|
||||
import { Heading, Link, Section, Text } from "@react-email/components";
|
||||
import { Heading, Section, Text } from "@react-email/components";
|
||||
import React from "react";
|
||||
|
||||
import { BaseEmailWrapper, BaseEmailWrapperProps } from "./BaseEmailWrapper";
|
||||
import { BaseLink } from "./BaseLink";
|
||||
|
||||
interface ExternalImportFailedTemplateProps extends Omit<BaseEmailWrapperProps, "title" | "preview" | "children"> {
|
||||
error: string;
|
||||
@@ -21,12 +22,9 @@ export const ExternalImportFailedTemplate = ({ error, siteUrl, provider }: Exter
|
||||
</Text>
|
||||
<Text className="text-black text-[14px] leading-[24px]">
|
||||
If your issue persists, you can contact the Infisical team at{" "}
|
||||
<Link href="mailto:support@infisical.com" className="text-slate-700 no-underline">
|
||||
support@infisical.com
|
||||
</Link>
|
||||
.
|
||||
<BaseLink href="mailto:support@infisical.com">support@infisical.com</BaseLink>.
|
||||
</Text>
|
||||
<Text className="text-[14px] text-red-500 leading-[24px]">
|
||||
<Text className="text-[14px] text-red-600 leading-[24px]">
|
||||
<strong>Error:</strong> "{error}"
|
||||
</Text>
|
||||
</Section>
|
||||
|
@@ -1,6 +1,7 @@
|
||||
import { Button, Heading, Section, Text } from "@react-email/components";
|
||||
import { Heading, Section, Text } from "@react-email/components";
|
||||
import React from "react";
|
||||
|
||||
import { BaseButton } from "./BaseButton";
|
||||
import { BaseEmailWrapper, BaseEmailWrapperProps } from "./BaseEmailWrapper";
|
||||
|
||||
interface IntegrationSyncFailedTemplateProps extends Omit<BaseEmailWrapperProps, "title" | "preview" | "children"> {
|
||||
@@ -30,7 +31,7 @@ export const IntegrationSyncFailedTemplate = ({
|
||||
<Heading className="text-black text-[18px] leading-[28px] text-center font-normal p-0 mx-0">
|
||||
<strong>{count}</strong> integration(s) failed to sync
|
||||
</Heading>
|
||||
<Section className="px-[24px] mt-[36px] pt-[26px] pb-[4px] text-[14px] border border-solid border-gray-200 rounded-md bg-gray-50">
|
||||
<Section className="px-[24px] mb-[28px] mt-[36px] pt-[26px] pb-[4px] text-[14px] border border-solid border-gray-200 rounded-md bg-gray-50">
|
||||
<strong>Project</strong>
|
||||
<Text className="text-[14px] mt-[4px]">{projectName}</Text>
|
||||
<strong>Environment</strong>
|
||||
@@ -38,15 +39,10 @@ export const IntegrationSyncFailedTemplate = ({
|
||||
<strong>Secret Path</strong>
|
||||
<Text className="text-[14px] mt-[4px]">{secretPath}</Text>
|
||||
<strong className="text-black">Failure Reason:</strong>
|
||||
<Text className="text-[14px] mt-[4px] text-red-500 leading-[24px]">"{syncMessage}"</Text>
|
||||
<Text className="text-[14px] mt-[4px] text-red-600 leading-[24px]">"{syncMessage}"</Text>
|
||||
</Section>
|
||||
<Section className="text-center mt-[28px]">
|
||||
<Button
|
||||
href={integrationUrl}
|
||||
className="rounded-md p-3 px-[28px] my-[8px] text-center text-[16px] bg-[#EBF852] border-solid border border-[#d1e309] text-black font-medium"
|
||||
>
|
||||
View Integrations
|
||||
</Button>
|
||||
<Section className="text-center">
|
||||
<BaseButton href={integrationUrl}>View Integrations</BaseButton>
|
||||
</Section>
|
||||
</BaseEmailWrapper>
|
||||
);
|
||||
|
@@ -1,7 +1,8 @@
|
||||
import { Heading, Link, Section, Text } from "@react-email/components";
|
||||
import { Heading, Section, Text } from "@react-email/components";
|
||||
import React from "react";
|
||||
|
||||
import { BaseEmailWrapper, BaseEmailWrapperProps } from "./BaseEmailWrapper";
|
||||
import { BaseLink } from "./BaseLink";
|
||||
|
||||
interface NewDeviceLoginTemplateProps extends Omit<BaseEmailWrapperProps, "title" | "preview" | "children"> {
|
||||
email: string;
|
||||
@@ -42,9 +43,7 @@ export const NewDeviceLoginTemplate = ({
|
||||
<Text className="mb-[0px]">
|
||||
If you believe that this login is suspicious, please contact{" "}
|
||||
{isCloud ? (
|
||||
<Link href="mailto:support@infisical.com" className="text-slate-700 no-underline">
|
||||
support@infisical.com
|
||||
</Link>
|
||||
<BaseLink href="mailto:support@infisical.com">support@infisical.com</BaseLink>
|
||||
) : (
|
||||
"your administrator"
|
||||
)}{" "}
|
||||
|
@@ -1,7 +1,8 @@
|
||||
import { Heading, Link, Section, Text } from "@react-email/components";
|
||||
import { Heading, Section, Text } from "@react-email/components";
|
||||
import React from "react";
|
||||
|
||||
import { BaseEmailWrapper, BaseEmailWrapperProps } from "./BaseEmailWrapper";
|
||||
import { BaseLink } from "./BaseLink";
|
||||
|
||||
interface OrgAdminBreakglassAccessTemplateProps extends Omit<BaseEmailWrapperProps, "title" | "preview" | "children"> {
|
||||
email: string;
|
||||
@@ -35,10 +36,7 @@ export const OrgAdminBreakglassAccessTemplate = ({
|
||||
<Text className="text-[14px] mt-[4px]">{userAgent}</Text>
|
||||
<Text className="text-[14px]">
|
||||
If you'd like to disable Admin SSO Bypass, please visit{" "}
|
||||
<Link href={`${siteUrl}/organization/settings`} className="text-slate-700 no-underline">
|
||||
Organization Security Settings
|
||||
</Link>
|
||||
.
|
||||
<BaseLink href={`${siteUrl}/organization/settings`}>Organization Security Settings</BaseLink>.
|
||||
</Text>
|
||||
</Section>
|
||||
</BaseEmailWrapper>
|
||||
|
@@ -2,6 +2,7 @@ import { Heading, Section, Text } from "@react-email/components";
|
||||
import React from "react";
|
||||
|
||||
import { BaseEmailWrapper, BaseEmailWrapperProps } from "./BaseEmailWrapper";
|
||||
import { BaseLink } from "./BaseLink";
|
||||
|
||||
interface OrgAdminProjectGrantAccessTemplateProps extends Omit<BaseEmailWrapperProps, "title" | "preview"> {
|
||||
email: string;
|
||||
@@ -24,8 +25,8 @@ export const OrgAdminProjectGrantAccessTemplate = ({
|
||||
</Heading>
|
||||
<Section className="px-[24px] mt-[36px] pt-[24px] pb-[8px] border border-solid border-gray-200 rounded-md bg-gray-50">
|
||||
<Text className="text-[14px] mt-[4px]">
|
||||
The organization admin <strong>{email}</strong> has self-issued direct access to the project{" "}
|
||||
<strong>{projectName}</strong>.
|
||||
The organization admin <BaseLink href={`mailto:${email}`}>{email}</BaseLink> has self-issued direct access to
|
||||
the project <strong>{projectName}</strong>.
|
||||
</Text>
|
||||
</Section>
|
||||
</BaseEmailWrapper>
|
||||
|
@@ -1,7 +1,9 @@
|
||||
import { Button, Heading, Link, Section, Text } from "@react-email/components";
|
||||
import { Heading, Section, Text } from "@react-email/components";
|
||||
import React from "react";
|
||||
|
||||
import { BaseButton } from "./BaseButton";
|
||||
import { BaseEmailWrapper, BaseEmailWrapperProps } from "./BaseEmailWrapper";
|
||||
import { BaseLink } from "./BaseLink";
|
||||
|
||||
interface OrganizationInvitationTemplateProps extends Omit<BaseEmailWrapperProps, "preview" | "title"> {
|
||||
metadata?: string;
|
||||
@@ -36,15 +38,13 @@ export const OrganizationInvitationTemplate = ({
|
||||
<br />
|
||||
<strong>{organizationName}</strong> on <strong>Infisical</strong>
|
||||
</Heading>
|
||||
<Section className="px-[24px] mt-[36px] pt-[12px] pb-[8px] border text-center border-solid border-gray-200 rounded-md bg-gray-50">
|
||||
<Section className="px-[24px] mb-[28px] mt-[36px] pt-[12px] pb-[8px] border text-center border-solid border-gray-200 rounded-md bg-gray-50">
|
||||
<Text className="text-black text-[14px] leading-[24px]">
|
||||
{inviterFirstName && inviterUsername ? (
|
||||
<>
|
||||
<strong>{inviterFirstName}</strong> (
|
||||
<Link href={`mailto:${inviterUsername}`} className="text-slate-700 no-underline">
|
||||
{inviterUsername}
|
||||
</Link>
|
||||
) has invited you to collaborate on <strong>{organizationName}</strong>.
|
||||
<BaseLink href={`mailto:${inviterUsername}`}>{inviterUsername}</BaseLink>) has invited you to collaborate
|
||||
on <strong>{organizationName}</strong>.
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
@@ -53,13 +53,12 @@ export const OrganizationInvitationTemplate = ({
|
||||
)}
|
||||
</Text>
|
||||
</Section>
|
||||
<Section className="text-center mt-[28px]">
|
||||
<Button
|
||||
<Section className="text-center">
|
||||
<BaseButton
|
||||
href={`${callback_url}?token=${token}${metadata ? `&metadata=${metadata}` : ""}&to=${encodeURIComponent(email)}&organization_id=${organizationId}`}
|
||||
className="rounded-md p-3 px-[28px] my-[8px] text-center text-[16px] bg-[#EBF852] border-solid border border-[#d1e309] text-black font-medium"
|
||||
>
|
||||
Accept Invite
|
||||
</Button>
|
||||
</BaseButton>
|
||||
</Section>
|
||||
<Section className="mt-[24px] bg-gray-50 pt-[2px] pb-[16px] border border-solid border-gray-200 px-[24px] rounded-md text-gray-800">
|
||||
<Text className="mb-[0px]">
|
||||
|
@@ -1,7 +1,9 @@
|
||||
import { Button, Heading, Link, Section, Text } from "@react-email/components";
|
||||
import { Heading, Section, Text } from "@react-email/components";
|
||||
import React from "react";
|
||||
|
||||
import { BaseButton } from "./BaseButton";
|
||||
import { BaseEmailWrapper, BaseEmailWrapperProps } from "./BaseEmailWrapper";
|
||||
import { BaseLink } from "./BaseLink";
|
||||
|
||||
interface PasswordResetTemplateProps extends Omit<BaseEmailWrapperProps, "title" | "preview" | "children"> {
|
||||
email: string;
|
||||
@@ -20,16 +22,13 @@ export const PasswordResetTemplate = ({ email, isCloud, siteUrl, callback_url, t
|
||||
<Heading className="text-black text-[18px] leading-[28px] text-center font-normal p-0 mx-0">
|
||||
<strong>Account Recovery</strong>
|
||||
</Heading>
|
||||
<Section className="px-[24px] mt-[36px] pt-[12px] pb-[8px] border border-solid border-gray-200 rounded-md bg-gray-50">
|
||||
<Section className="px-[24px] mb-[28px] mt-[36px] pt-[12px] pb-[8px] border border-solid border-gray-200 rounded-md bg-gray-50">
|
||||
<Text className="text-[14px]">A password reset was requested for your Infisical account.</Text>
|
||||
<Text className="text-[14px]">
|
||||
If you did not initiate this request, please contact{" "}
|
||||
{isCloud ? (
|
||||
<>
|
||||
us immediately at{" "}
|
||||
<Link href="mailto:support@infisical.com" className="text-slate-700 no-underline">
|
||||
support@infisical.com
|
||||
</Link>
|
||||
us immediately at <BaseLink href="mailto:support@infisical.com">support@infisical.com</BaseLink>
|
||||
</>
|
||||
) : (
|
||||
"your administrator immediately"
|
||||
@@ -37,13 +36,8 @@ export const PasswordResetTemplate = ({ email, isCloud, siteUrl, callback_url, t
|
||||
.
|
||||
</Text>
|
||||
</Section>
|
||||
<Section className="text-center mt-[28px]">
|
||||
<Button
|
||||
href={`${callback_url}?token=${token}&to=${encodeURIComponent(email)}`}
|
||||
className="rounded-md p-3 px-[28px] my-[8px] text-center text-[16px] bg-[#EBF852] border-solid border border-[#d1e309] text-black font-medium"
|
||||
>
|
||||
Reset Password
|
||||
</Button>
|
||||
<Section className="text-center">
|
||||
<BaseButton href={`${callback_url}?token=${token}&to=${encodeURIComponent(email)}`}>Reset Password</BaseButton>
|
||||
</Section>
|
||||
</BaseEmailWrapper>
|
||||
);
|
||||
|
@@ -1,6 +1,8 @@
|
||||
import { Button, Heading, Link, Section, Text } from "@react-email/components";
|
||||
import { Button, Heading, Section, Text } from "@react-email/components";
|
||||
import React from "react";
|
||||
|
||||
import { BaseLink } from "@app/services/smtp/emails/BaseLink";
|
||||
|
||||
import { BaseEmailWrapper, BaseEmailWrapperProps } from "./BaseEmailWrapper";
|
||||
|
||||
interface PasswordSetupTemplateProps extends Omit<BaseEmailWrapperProps, "title" | "preview" | "children"> {
|
||||
@@ -16,19 +18,16 @@ export const PasswordSetupTemplate = ({ email, isCloud, siteUrl, callback_url, t
|
||||
<Heading className="text-black text-[18px] leading-[28px] text-center font-normal p-0 mx-0">
|
||||
<strong>Password Setup</strong>
|
||||
</Heading>
|
||||
<Section className="px-[24px] mt-[36px] pt-[12px] pb-[8px] border border-solid border-gray-200 rounded-md bg-gray-50">
|
||||
<Section className="px-[24px] mb-[28px] mt-[36px] pt-[12px] pb-[8px] border border-solid border-gray-200 rounded-md bg-gray-50">
|
||||
<Text className="text-[14px]">Someone requested to set up a password for your Infisical account.</Text>
|
||||
<Text className="text-[14px] text-red-500">
|
||||
<Text className="text-[14px] text-red-600">
|
||||
Make sure you are already logged in to Infisical in the current browser before clicking the link below.
|
||||
</Text>
|
||||
<Text className="text-[14px]">
|
||||
If you did not initiate this request, please contact{" "}
|
||||
{isCloud ? (
|
||||
<>
|
||||
us immediately at{" "}
|
||||
<Link href="mailto:support@infisical.com" className="text-slate-700 no-underline">
|
||||
support@infisical.com
|
||||
</Link>
|
||||
us immediately at <BaseLink href="mailto:support@infisical.com">support@infisical.com</BaseLink>
|
||||
</>
|
||||
) : (
|
||||
"your administrator immediately"
|
||||
@@ -36,7 +35,7 @@ export const PasswordSetupTemplate = ({ email, isCloud, siteUrl, callback_url, t
|
||||
.
|
||||
</Text>
|
||||
</Section>
|
||||
<Section className="text-center mt-[28px]">
|
||||
<Section className="text-center">
|
||||
<Button
|
||||
href={`${callback_url}?token=${token}&to=${encodeURIComponent(email)}`}
|
||||
className="rounded-md p-3 px-[28px] my-[8px] text-center text-[16px] bg-[#EBF852] border-solid border border-[#d1e309] text-black font-medium"
|
||||
|
@@ -1,7 +1,9 @@
|
||||
import { Button, Heading, Link, Section, Text } from "@react-email/components";
|
||||
import { Heading, Section, Text } from "@react-email/components";
|
||||
import React from "react";
|
||||
|
||||
import { BaseButton } from "./BaseButton";
|
||||
import { BaseEmailWrapper, BaseEmailWrapperProps } from "./BaseEmailWrapper";
|
||||
import { BaseLink } from "./BaseLink";
|
||||
|
||||
interface ProjectAccessRequestTemplateProps extends Omit<BaseEmailWrapperProps, "title" | "preview" | "children"> {
|
||||
projectName: string;
|
||||
@@ -30,26 +32,17 @@ export const ProjectAccessRequestTemplate = ({
|
||||
<Heading className="text-black text-[18px] leading-[28px] text-center font-normal p-0 mx-0">
|
||||
A user has requested access to the project <strong>{projectName}</strong>
|
||||
</Heading>
|
||||
<Section className="px-[24px] mt-[36px] pt-[12px] pb-[8px] border border-solid border-gray-200 rounded-md bg-gray-50">
|
||||
<Section className="px-[24px] mb-[28px] mt-[36px] pt-[12px] pb-[8px] border border-solid border-gray-200 rounded-md bg-gray-50">
|
||||
<Text className="text-black text-[14px] leading-[24px]">
|
||||
<strong>{requesterName}</strong> (
|
||||
<Link href={`mailto:${requesterEmail}`} className="text-slate-700 no-underline">
|
||||
{requesterEmail}
|
||||
</Link>
|
||||
) has requested access to the project <strong>{projectName}</strong> in the organization{" "}
|
||||
<strong>{orgName}</strong>.
|
||||
<strong>{requesterName}</strong> (<BaseLink href={`mailto:${requesterEmail}`}>{requesterEmail}</BaseLink>) has
|
||||
requested access to the project <strong>{projectName}</strong> in the organization <strong>{orgName}</strong>.
|
||||
</Text>
|
||||
<Text className="text-[14px] text-slate-700 leading-[24px]">
|
||||
<strong className="text-black">User note:</strong> "{note}"
|
||||
</Text>
|
||||
</Section>
|
||||
<Section className="text-center mt-[28px]">
|
||||
<Button
|
||||
href={callback_url}
|
||||
className="rounded-md p-3 px-[28px] my-[8px] text-center text-[16px] bg-[#EBF852] border-solid border border-[#d1e309] text-black font-medium"
|
||||
>
|
||||
Grant Access
|
||||
</Button>
|
||||
<Section className="text-center">
|
||||
<BaseButton href={callback_url}>Grant Access</BaseButton>
|
||||
</Section>
|
||||
</BaseEmailWrapper>
|
||||
);
|
||||
|
@@ -1,6 +1,7 @@
|
||||
import { Button, Heading, Section, Text } from "@react-email/components";
|
||||
import { Heading, Section, Text } from "@react-email/components";
|
||||
import React from "react";
|
||||
|
||||
import { BaseButton } from "./BaseButton";
|
||||
import { BaseEmailWrapper, BaseEmailWrapperProps } from "./BaseEmailWrapper";
|
||||
|
||||
interface ProjectInvitationTemplateProps extends Omit<BaseEmailWrapperProps, "preview" | "title"> {
|
||||
@@ -18,18 +19,13 @@ export const ProjectInvitationTemplate = ({ callback_url, workspaceName, siteUrl
|
||||
<Heading className="text-black text-[18px] leading-[28px] text-center font-normal p-0 mx-0">
|
||||
You've been invited to join a project on Infisical
|
||||
</Heading>
|
||||
<Section className="px-[24px] mt-[36px] pt-[12px] pb-[8px] border text-center border-solid border-gray-200 rounded-md bg-gray-50">
|
||||
<Section className="px-[24px] mb-[28px] mt-[36px] pt-[12px] pb-[8px] border text-center border-solid border-gray-200 rounded-md bg-gray-50">
|
||||
<Text className="text-black text-[14px] leading-[24px]">
|
||||
You've been invited to join the project <strong>{workspaceName}</strong>.
|
||||
</Text>
|
||||
</Section>
|
||||
<Section className="text-center mt-[28px]">
|
||||
<Button
|
||||
href={callback_url}
|
||||
className="rounded-md p-3 px-[28px] my-[8px] text-center text-[16px] bg-[#EBF852] border-solid border border-[#d1e309] text-black font-medium"
|
||||
>
|
||||
Join Project
|
||||
</Button>
|
||||
<Section className="text-center">
|
||||
<BaseButton href={callback_url}>Join Project</BaseButton>
|
||||
</Section>
|
||||
<Section className="mt-[24px] bg-gray-50 pt-[2px] pb-[16px] border border-solid border-gray-200 px-[24px] rounded-md text-gray-800">
|
||||
<Text className="mb-[0px]">
|
||||
|
@@ -1,6 +1,7 @@
|
||||
import { Button, Heading, Section, Text } from "@react-email/components";
|
||||
import { Heading, Section, Text } from "@react-email/components";
|
||||
import React from "react";
|
||||
|
||||
import { BaseButton } from "./BaseButton";
|
||||
import { BaseEmailWrapper, BaseEmailWrapperProps } from "./BaseEmailWrapper";
|
||||
|
||||
interface ScimUserProvisionedTemplateProps extends Omit<BaseEmailWrapperProps, "preview" | "title"> {
|
||||
@@ -24,18 +25,13 @@ export const ScimUserProvisionedTemplate = ({
|
||||
<br />
|
||||
<strong>{organizationName}</strong> on <strong>Infisical</strong>
|
||||
</Heading>
|
||||
<Section className="px-[24px] mt-[36px] pt-[12px] pb-[8px] border text-center border-solid border-gray-200 rounded-md bg-gray-50">
|
||||
<Section className="px-[24px] mb-[28px] mt-[36px] pt-[12px] pb-[8px] border text-center border-solid border-gray-200 rounded-md bg-gray-50">
|
||||
<Text className="text-black text-[14px] leading-[24px]">
|
||||
You've been invited to collaborate on <strong>{organizationName}</strong>.
|
||||
</Text>
|
||||
</Section>
|
||||
<Section className="text-center mt-[28px]">
|
||||
<Button
|
||||
href={callback_url}
|
||||
className="rounded-md p-3 px-[28px] my-[8px] text-center text-[16px] bg-[#EBF852] border-solid border border-[#d1e309] text-black font-medium"
|
||||
>
|
||||
Accept Invite
|
||||
</Button>
|
||||
<Section className="text-center">
|
||||
<BaseButton href={callback_url}>Accept Invite</BaseButton>
|
||||
</Section>
|
||||
<Section className="mt-[24px] bg-gray-50 pt-[2px] pb-[16px] border border-solid border-gray-200 px-[24px] rounded-md text-gray-800">
|
||||
<Text className="mb-[0px]">
|
||||
|
@@ -1,7 +1,9 @@
|
||||
import { Button, Heading, Link, Section, Text } from "@react-email/components";
|
||||
import { Heading, Section, Text } from "@react-email/components";
|
||||
import React from "react";
|
||||
|
||||
import { BaseButton } from "./BaseButton";
|
||||
import { BaseEmailWrapper, BaseEmailWrapperProps } from "./BaseEmailWrapper";
|
||||
import { BaseLink } from "./BaseLink";
|
||||
|
||||
interface SecretApprovalRequestBypassedTemplateProps
|
||||
extends Omit<BaseEmailWrapperProps, "title" | "preview" | "children"> {
|
||||
@@ -35,13 +37,10 @@ export const SecretApprovalRequestBypassedTemplate = ({
|
||||
<Heading className="text-black text-[18px] leading-[28px] text-center font-normal p-0 mx-0">
|
||||
A secret approval request has been bypassed in the project <strong>{projectName}</strong>
|
||||
</Heading>
|
||||
<Section className="px-[24px] mt-[36px] pt-[12px] pb-[8px] border border-solid border-gray-200 rounded-md bg-gray-50">
|
||||
<Section className="px-[24px] mb-[28px] mt-[36px] pt-[12px] pb-[8px] border border-solid border-gray-200 rounded-md bg-gray-50">
|
||||
<Text className="text-black text-[14px] leading-[24px]">
|
||||
<strong>{requesterFullName}</strong> (
|
||||
<Link href={`mailto:${requesterEmail}`} className="text-slate-700 no-underline">
|
||||
{requesterEmail}
|
||||
</Link>
|
||||
) has {requestType === "change" ? "merged" : "accessed"} a secret {requestType === "change" ? "to" : "in"}{" "}
|
||||
<strong>{requesterFullName}</strong> (<BaseLink href={`mailto:${requesterEmail}`}>{requesterEmail}</BaseLink>)
|
||||
has {requestType === "change" ? "merged" : "accessed"} a secret {requestType === "change" ? "to" : "in"}{" "}
|
||||
<strong>{secretPath}</strong> in the <strong>{environment}</strong> environment without obtaining the required
|
||||
approval.
|
||||
</Text>
|
||||
@@ -50,13 +49,8 @@ export const SecretApprovalRequestBypassedTemplate = ({
|
||||
{bypassReason}"
|
||||
</Text>
|
||||
</Section>
|
||||
<Section className="text-center mt-[28px]">
|
||||
<Button
|
||||
href={approvalUrl}
|
||||
className="rounded-md p-3 px-[28px] my-[8px] text-center text-[16px] bg-[#EBF852] border-solid border border-[#d1e309] text-black font-medium"
|
||||
>
|
||||
Review Bypass
|
||||
</Button>
|
||||
<Section className="text-center">
|
||||
<BaseButton href={approvalUrl}>Review Bypass</BaseButton>
|
||||
</Section>
|
||||
</BaseEmailWrapper>
|
||||
);
|
||||
|
@@ -1,6 +1,7 @@
|
||||
import { Button, Heading, Section, Text } from "@react-email/components";
|
||||
import { Heading, Section, Text } from "@react-email/components";
|
||||
import React from "react";
|
||||
|
||||
import { BaseButton } from "./BaseButton";
|
||||
import { BaseEmailWrapper, BaseEmailWrapperProps } from "./BaseEmailWrapper";
|
||||
|
||||
interface SecretApprovalRequestNeedsReviewTemplateProps
|
||||
@@ -27,20 +28,15 @@ export const SecretApprovalRequestNeedsReviewTemplate = ({
|
||||
<Heading className="text-black text-[18px] leading-[28px] text-center font-normal p-0 mx-0">
|
||||
A secret approval request for the project <strong>{projectName}</strong> requires review
|
||||
</Heading>
|
||||
<Section className="px-[24px] mt-[36px] pt-[12px] pb-[8px] border border-solid border-gray-200 rounded-md bg-gray-50">
|
||||
<Section className="px-[24px] mb-[28px] mt-[36px] pt-[12px] pb-[8px] border border-solid border-gray-200 rounded-md bg-gray-50">
|
||||
<Text className="text-[14px]">Hello {firstName},</Text>
|
||||
<Text className="text-black text-[14px] leading-[24px]">
|
||||
You have a new secret change request pending your review for the project <strong>{projectName}</strong> in the
|
||||
organization <strong>{organizationName}</strong>.
|
||||
</Text>
|
||||
</Section>
|
||||
<Section className="text-center mt-[28px]">
|
||||
<Button
|
||||
href={approvalUrl}
|
||||
className="rounded-md p-3 px-[28px] my-[8px] text-center text-[16px] bg-[#EBF852] border-solid border border-[#d1e309] text-black font-medium"
|
||||
>
|
||||
Review Changes
|
||||
</Button>
|
||||
<Section className="text-center">
|
||||
<BaseButton href={approvalUrl}>Review Changes</BaseButton>
|
||||
</Section>
|
||||
</BaseEmailWrapper>
|
||||
);
|
||||
|
@@ -1,7 +1,9 @@
|
||||
import { Button, Heading, Link, Section, Text } from "@react-email/components";
|
||||
import { Heading, Section, Text } from "@react-email/components";
|
||||
import React from "react";
|
||||
|
||||
import { BaseButton } from "./BaseButton";
|
||||
import { BaseEmailWrapper, BaseEmailWrapperProps } from "./BaseEmailWrapper";
|
||||
import { BaseLink } from "./BaseLink";
|
||||
|
||||
interface SecretLeakIncidentTemplateProps extends Omit<BaseEmailWrapperProps, "title" | "preview" | "children"> {
|
||||
numberOfSecrets: number;
|
||||
@@ -24,7 +26,7 @@ export const SecretLeakIncidentTemplate = ({
|
||||
<Heading className="text-black text-[18px] leading-[28px] text-center font-normal p-0 mx-0">
|
||||
Infisical has uncovered <strong>{numberOfSecrets}</strong> secret(s) from a recent commit
|
||||
</Heading>
|
||||
<Section className="px-[24px] mt-[36px] pt-[8px] pb-[8px] text-[14px] border border-solid border-gray-200 rounded-md bg-gray-50">
|
||||
<Section className="px-[24px] mb-[28px] mt-[36px] pt-[8px] pb-[8px] text-[14px] border border-solid border-gray-200 rounded-md bg-gray-50">
|
||||
<Text className="text-[14px]">
|
||||
You are receiving this notification because one or more leaked secrets have been detected in a recent commit
|
||||
{(pusher_email || pusher_name) && (
|
||||
@@ -33,11 +35,7 @@ export const SecretLeakIncidentTemplate = ({
|
||||
pushed by <strong>{pusher_name ?? "Unknown Pusher"}</strong>{" "}
|
||||
{pusher_email && (
|
||||
<>
|
||||
(
|
||||
<Link href={`mailto:${pusher_email}`} className="text-slate-700 no-underline">
|
||||
{pusher_email}
|
||||
</Link>
|
||||
)
|
||||
(<BaseLink href={`mailto:${pusher_email}`}>{pusher_email}</BaseLink>)
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
@@ -49,24 +47,16 @@ export const SecretLeakIncidentTemplate = ({
|
||||
a comment in the given programming language. This will prevent future notifications from being sent out for
|
||||
these secrets.
|
||||
</Text>
|
||||
<Text className="text-[14px] text-red-500">
|
||||
<Text className="text-[14px] text-red-600">
|
||||
If these are production secrets, please rotate them immediately.
|
||||
</Text>
|
||||
<Text className="text-[14px]">
|
||||
Once you have taken action, be sure to update the status of the risk in the{" "}
|
||||
<Link href={`${siteUrl}/organization/secret-scanning`} className="text-slate-700 no-underline">
|
||||
Infisical Dashboard
|
||||
</Link>
|
||||
.
|
||||
<BaseLink href={`${siteUrl}/organization/secret-scanning`}>Infisical Dashboard</BaseLink>.
|
||||
</Text>
|
||||
</Section>
|
||||
<Section className="text-center mt-[28px]">
|
||||
<Button
|
||||
href={`${siteUrl}/organization/secret-scanning`}
|
||||
className="rounded-md p-3 px-[28px] my-[8px] text-center text-[16px] bg-[#EBF852] border-solid border border-[#d1e309] text-black font-medium"
|
||||
>
|
||||
View Leaked Secrets
|
||||
</Button>
|
||||
<Section className="text-center">
|
||||
<BaseButton href={`${siteUrl}/organization/secret-scanning`}>View Leaked Secrets</BaseButton>
|
||||
</Section>
|
||||
</BaseEmailWrapper>
|
||||
);
|
||||
|
@@ -1,6 +1,7 @@
|
||||
import { Button, Heading, Section, Text } from "@react-email/components";
|
||||
import { Heading, Section, Text } from "@react-email/components";
|
||||
import React from "react";
|
||||
|
||||
import { BaseButton } from "./BaseButton";
|
||||
import { BaseEmailWrapper, BaseEmailWrapperProps } from "./BaseEmailWrapper";
|
||||
|
||||
interface SecretRequestCompletedTemplateProps extends Omit<BaseEmailWrapperProps, "title" | "preview" | "children"> {
|
||||
@@ -20,7 +21,7 @@ export const SecretRequestCompletedTemplate = ({
|
||||
<Heading className="text-black text-[18px] leading-[28px] text-center font-normal p-0 mx-0">
|
||||
<strong>A secret has been shared with you</strong>
|
||||
</Heading>
|
||||
<Section className="px-[24px] mt-[36px] pt-[12px] text-center pb-[8px] border border-solid border-gray-200 rounded-md bg-gray-50">
|
||||
<Section className="px-[24px] mb-[28px] mt-[36px] pt-[12px] text-center pb-[8px] border border-solid border-gray-200 rounded-md bg-gray-50">
|
||||
<Text className="text-[14px]">
|
||||
{respondentUsername ? <strong>{respondentUsername}</strong> : "Someone"} shared a secret{" "}
|
||||
{name && (
|
||||
@@ -31,13 +32,8 @@ export const SecretRequestCompletedTemplate = ({
|
||||
with you.
|
||||
</Text>
|
||||
</Section>
|
||||
<Section className="text-center mt-[28px]">
|
||||
<Button
|
||||
href={secretRequestUrl}
|
||||
className="rounded-md p-3 px-[28px] my-[8px] text-center text-[16px] bg-[#EBF852] border-solid border border-[#d1e309] text-black font-medium"
|
||||
>
|
||||
View Secret
|
||||
</Button>
|
||||
<Section className="text-center">
|
||||
<BaseButton href={secretRequestUrl}>View Secret</BaseButton>
|
||||
</Section>
|
||||
</BaseEmailWrapper>
|
||||
);
|
||||
|
@@ -1,6 +1,7 @@
|
||||
import { Button, Heading, Section, Text } from "@react-email/components";
|
||||
import { Heading, Section, Text } from "@react-email/components";
|
||||
import React from "react";
|
||||
|
||||
import { BaseButton } from "./BaseButton";
|
||||
import { BaseEmailWrapper, BaseEmailWrapperProps } from "./BaseEmailWrapper";
|
||||
|
||||
interface SecretRotationFailedTemplateProps extends Omit<BaseEmailWrapperProps, "title" | "preview" | "children"> {
|
||||
@@ -28,7 +29,7 @@ export const SecretRotationFailedTemplate = ({
|
||||
<Heading className="text-black text-[18px] leading-[28px] text-center font-normal p-0 mx-0">
|
||||
Your <strong>{rotationType}</strong> rotation <strong>{rotationName}</strong> failed to rotate
|
||||
</Heading>
|
||||
<Section className="px-[24px] mt-[36px] pt-[26px] pb-[4px] text-[14px] border border-solid border-gray-200 rounded-md bg-gray-50">
|
||||
<Section className="px-[24px] mb-[28px] mt-[36px] pt-[26px] pb-[4px] text-[14px] border border-solid border-gray-200 rounded-md bg-gray-50">
|
||||
<strong>Name</strong>
|
||||
<Text className="text-[14px] mt-[4px]">{rotationName}</Text>
|
||||
<strong>Type</strong>
|
||||
@@ -40,15 +41,12 @@ export const SecretRotationFailedTemplate = ({
|
||||
<strong>Secret Path</strong>
|
||||
<Text className="text-[14px] mt-[4px]">{secretPath}</Text>
|
||||
<strong>Reason:</strong>
|
||||
<Text className="text-[14px] text-red-500 mt-[4px]">{content}</Text>
|
||||
<Text className="text-[14px] text-red-600 mt-[4px]">{content}</Text>
|
||||
</Section>
|
||||
<Section className="text-center mt-[28px]">
|
||||
<Button
|
||||
href={`${rotationUrl}?search=${rotationName}&secretPath=${secretPath}`}
|
||||
className="rounded-md p-3 px-[28px] my-[8px] text-center text-[16px] bg-[#EBF852] border-solid border border-[#d1e309] text-black font-medium"
|
||||
>
|
||||
<Section className="text-center">
|
||||
<BaseButton href={`${rotationUrl}?search=${rotationName}&secretPath=${secretPath}`}>
|
||||
View in Infisical
|
||||
</Button>
|
||||
</BaseButton>
|
||||
</Section>
|
||||
</BaseEmailWrapper>
|
||||
);
|
||||
|
@@ -1,6 +1,7 @@
|
||||
import { Button, Heading, Section, Text } from "@react-email/components";
|
||||
import { Heading, Section, Text } from "@react-email/components";
|
||||
import React from "react";
|
||||
|
||||
import { BaseButton } from "./BaseButton";
|
||||
import { BaseEmailWrapper, BaseEmailWrapperProps } from "./BaseEmailWrapper";
|
||||
|
||||
interface SecretScanningScanFailedTemplateProps extends Omit<BaseEmailWrapperProps, "title" | "preview" | "children"> {
|
||||
@@ -30,7 +31,7 @@ export const SecretScanningScanFailedTemplate = ({
|
||||
<Heading className="text-black text-[18px] leading-[28px] text-center font-normal p-0 mx-0">
|
||||
Infisical encountered an error while attempting to scan the resource <strong>{resourceName}</strong>
|
||||
</Heading>
|
||||
<Section className="px-[24px] mt-[36px] pt-[26px] pb-[4px] text-[14px] border border-solid border-gray-200 rounded-md bg-gray-50">
|
||||
<Section className="px-[24px] mb-[28px] mt-[36px] pt-[26px] pb-[4px] text-[14px] border border-solid border-gray-200 rounded-md bg-gray-50">
|
||||
<strong>Resource</strong>
|
||||
<Text className="text-[14px] mt-[4px]">{resourceName}</Text>
|
||||
<strong>Data Source</strong>
|
||||
@@ -40,15 +41,10 @@ export const SecretScanningScanFailedTemplate = ({
|
||||
<strong>Timestamp</strong>
|
||||
<Text className="text-[14px] mt-[4px]">{timestamp}</Text>
|
||||
<strong>Error</strong>
|
||||
<Text className="text-[14px] text-red-500 mt-[4px]">{errorMessage}</Text>
|
||||
<Text className="text-[14px] text-red-600 mt-[4px]">{errorMessage}</Text>
|
||||
</Section>
|
||||
<Section className="text-center mt-[28px]">
|
||||
<Button
|
||||
href={url}
|
||||
className="rounded-md p-3 px-[28px] my-[8px] text-center text-[16px] bg-[#EBF852] border-solid border border-[#d1e309] text-black font-medium"
|
||||
>
|
||||
View in Infisical
|
||||
</Button>
|
||||
<Section className="text-center">
|
||||
<BaseButton href={url}>View in Infisical</BaseButton>
|
||||
</Section>
|
||||
</BaseEmailWrapper>
|
||||
);
|
||||
|
@@ -1,7 +1,9 @@
|
||||
import { Button, Heading, Link, Section, Text } from "@react-email/components";
|
||||
import { Heading, Section, Text } from "@react-email/components";
|
||||
import React from "react";
|
||||
|
||||
import { BaseButton } from "./BaseButton";
|
||||
import { BaseEmailWrapper, BaseEmailWrapperProps } from "./BaseEmailWrapper";
|
||||
import { BaseLink } from "./BaseLink";
|
||||
|
||||
interface SecretScanningSecretsDetectedTemplateProps
|
||||
extends Omit<BaseEmailWrapperProps, "title" | "preview" | "children"> {
|
||||
@@ -32,7 +34,7 @@ export const SecretScanningSecretsDetectedTemplate = ({
|
||||
Infisical has uncovered <strong>{numberOfSecrets}</strong> secret(s)
|
||||
{isDiffScan ? " from a recent commit to" : " in"} <strong>{resourceName}</strong>
|
||||
</Heading>
|
||||
<Section className="px-[24px] mt-[36px] pt-[8px] pb-[8px] text-[14px] border border-solid border-gray-200 rounded-md bg-gray-50">
|
||||
<Section className="px-[24px] mb-[28px] mt-[36px] pt-[8px] pb-[8px] text-[14px] border border-solid border-gray-200 rounded-md bg-gray-50">
|
||||
<Text className="text-[14px]">
|
||||
You are receiving this notification because one or more leaked secrets have been detected
|
||||
{isDiffScan && " in a recent commit"}
|
||||
@@ -43,11 +45,7 @@ export const SecretScanningSecretsDetectedTemplate = ({
|
||||
pushed by <strong>{authorName ?? "Unknown Pusher"}</strong>{" "}
|
||||
{authorEmail && (
|
||||
<>
|
||||
(
|
||||
<Link href={`mailto:${authorEmail}`} className="text-slate-700 no-underline">
|
||||
{authorEmail}
|
||||
</Link>
|
||||
)
|
||||
(<BaseLink href={`mailto:${authorEmail}`}>{authorEmail}</BaseLink>)
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
@@ -65,24 +63,16 @@ export const SecretScanningSecretsDetectedTemplate = ({
|
||||
a comment in the given programming language. This will prevent future notifications from being sent out for
|
||||
these secrets.
|
||||
</Text>
|
||||
<Text className="text-[14px] text-red-500">
|
||||
<Text className="text-[14px] text-red-600">
|
||||
If these are production secrets, please rotate them immediately.
|
||||
</Text>
|
||||
<Text className="text-[14px]">
|
||||
Once you have taken action, be sure to update the finding status in the{" "}
|
||||
<Link href={url} className="text-slate-700 no-underline">
|
||||
Infisical Dashboard
|
||||
</Link>
|
||||
.
|
||||
<BaseLink href={url}>Infisical Dashboard</BaseLink>.
|
||||
</Text>
|
||||
</Section>
|
||||
<Section className="text-center mt-[28px]">
|
||||
<Button
|
||||
href={url}
|
||||
className="rounded-md p-3 px-[28px] my-[8px] text-center text-[16px] bg-[#EBF852] border-solid border border-[#d1e309] text-black font-medium"
|
||||
>
|
||||
View Leaked Secrets
|
||||
</Button>
|
||||
<Section className="text-center">
|
||||
<BaseButton href={url}>View Leaked Secrets</BaseButton>
|
||||
</Section>
|
||||
</BaseEmailWrapper>
|
||||
);
|
||||
|
@@ -1,6 +1,7 @@
|
||||
import { Button, Heading, Section, Text } from "@react-email/components";
|
||||
import { Heading, Section, Text } from "@react-email/components";
|
||||
import React from "react";
|
||||
|
||||
import { BaseButton } from "./BaseButton";
|
||||
import { BaseEmailWrapper, BaseEmailWrapperProps } from "./BaseEmailWrapper";
|
||||
|
||||
interface SecretSyncFailedTemplateProps extends Omit<BaseEmailWrapperProps, "title" | "preview" | "children"> {
|
||||
@@ -28,7 +29,7 @@ export const SecretSyncFailedTemplate = ({
|
||||
<Heading className="text-black text-[18px] leading-[28px] text-center font-normal p-0 mx-0">
|
||||
Your <strong>{syncDestination}</strong> sync <strong>{syncName}</strong> failed to complete
|
||||
</Heading>
|
||||
<Section className="px-[24px] mt-[36px] pt-[26px] pb-[4px] text-[14px] border border-solid border-gray-200 rounded-md bg-gray-50">
|
||||
<Section className="px-[24px] mb-[28px] mt-[36px] pt-[26px] pb-[4px] text-[14px] border border-solid border-gray-200 rounded-md bg-gray-50">
|
||||
<strong>Name</strong>
|
||||
<Text className="text-[14px] mt-[4px]">{syncName}</Text>
|
||||
<strong>Destination</strong>
|
||||
@@ -50,17 +51,12 @@ export const SecretSyncFailedTemplate = ({
|
||||
{failureMessage && (
|
||||
<>
|
||||
<strong>Reason:</strong>
|
||||
<Text className="text-[14px] text-red-500 mt-[4px]">{failureMessage}</Text>
|
||||
<Text className="text-[14px] text-red-600 mt-[4px]">{failureMessage}</Text>
|
||||
</>
|
||||
)}
|
||||
</Section>
|
||||
<Section className="text-center mt-[28px]">
|
||||
<Button
|
||||
href={syncUrl}
|
||||
className="rounded-md p-3 px-[28px] my-[8px] text-center text-[16px] bg-[#EBF852] border-solid border border-[#d1e309] text-black font-medium"
|
||||
>
|
||||
View in Infisical
|
||||
</Button>
|
||||
<Section className="text-center">
|
||||
<BaseButton href={syncUrl}>View in Infisical</BaseButton>
|
||||
</Section>
|
||||
</BaseEmailWrapper>
|
||||
);
|
||||
|
@@ -1,6 +1,7 @@
|
||||
import { Button, Heading, Section, Text } from "@react-email/components";
|
||||
import { Heading, Section, Text } from "@react-email/components";
|
||||
import React from "react";
|
||||
|
||||
import { BaseButton } from "./BaseButton";
|
||||
import { BaseEmailWrapper, BaseEmailWrapperProps } from "./BaseEmailWrapper";
|
||||
|
||||
interface ServiceTokenExpiryNoticeTemplateProps extends Omit<BaseEmailWrapperProps, "title" | "preview" | "children"> {
|
||||
@@ -24,20 +25,15 @@ export const ServiceTokenExpiryNoticeTemplate = ({
|
||||
<Heading className="text-black text-[18px] leading-[28px] text-center font-normal p-0 mx-0">
|
||||
<strong>Service token expiry notice</strong>
|
||||
</Heading>
|
||||
<Section className="px-[24px] mt-[36px] pt-[12px] pb-[8px] border border-solid border-gray-200 rounded-md bg-gray-50">
|
||||
<Section className="px-[24px] mb-[28px] mt-[36px] pt-[12px] pb-[8px] border border-solid border-gray-200 rounded-md bg-gray-50">
|
||||
<Text className="text-[14px]">
|
||||
Your service token <strong>{tokenName}</strong> for the project <strong>{projectName}</strong> will expire
|
||||
within 24 hours.
|
||||
</Text>
|
||||
<Text>If this token is still needed for your workflow, please create a new one before it expires.</Text>
|
||||
</Section>
|
||||
<Section className="text-center mt-[28px]">
|
||||
<Button
|
||||
href={url}
|
||||
className="rounded-md p-3 px-[28px] my-[8px] text-center text-[16px] bg-[#EBF852] border-solid border border-[#d1e309] text-black font-medium"
|
||||
>
|
||||
Create New Token
|
||||
</Button>
|
||||
<Section className="text-center">
|
||||
<BaseButton href={url}>Create New Token</BaseButton>
|
||||
</Section>
|
||||
</BaseEmailWrapper>
|
||||
);
|
||||
|
@@ -1,7 +1,8 @@
|
||||
import { Heading, Link, Section, Text } from "@react-email/components";
|
||||
import { Heading, Section, Text } from "@react-email/components";
|
||||
import React from "react";
|
||||
|
||||
import { BaseEmailWrapper, BaseEmailWrapperProps } from "./BaseEmailWrapper";
|
||||
import { BaseLink } from "./BaseLink";
|
||||
|
||||
interface SignupEmailVerificationTemplateProps extends Omit<BaseEmailWrapperProps, "title" | "preview" | "children"> {
|
||||
code: string;
|
||||
@@ -29,10 +30,7 @@ export const SignupEmailVerificationTemplate = ({ code, siteUrl, isCloud }: Sign
|
||||
<strong>Questions about setting up Infisical?</strong>{" "}
|
||||
{isCloud ? (
|
||||
<>
|
||||
Email us at{" "}
|
||||
<Link href="mailto:support@infisical.com" className="text-slate-700 no-underline">
|
||||
support@infisical.com
|
||||
</Link>
|
||||
Email us at <BaseLink href="mailto:support@infisical.com">support@infisical.com</BaseLink>
|
||||
</>
|
||||
) : (
|
||||
"Contact your administrator"
|
||||
|
@@ -1,6 +1,7 @@
|
||||
import { Button, Heading, Section, Text } from "@react-email/components";
|
||||
import { Heading, Section, Text } from "@react-email/components";
|
||||
import React from "react";
|
||||
|
||||
import { BaseButton } from "./BaseButton";
|
||||
import { BaseEmailWrapper, BaseEmailWrapperProps } from "./BaseEmailWrapper";
|
||||
|
||||
interface UnlockAccountTemplateProps extends Omit<BaseEmailWrapperProps, "title" | "preview" | "children"> {
|
||||
@@ -18,19 +19,14 @@ export const UnlockAccountTemplate = ({ token, siteUrl, callback_url }: UnlockAc
|
||||
<Heading className="text-black text-[18px] leading-[28px] text-center font-normal p-0 mx-0">
|
||||
<strong>Unlock your Infisical account</strong>
|
||||
</Heading>
|
||||
<Section className="px-[24px] mt-[36px] pt-[12px] pb-[8px] border border-solid border-gray-200 rounded-md bg-gray-50">
|
||||
<Section className="px-[24px] mb-[28px] mt-[36px] pt-[12px] pb-[8px] border border-solid border-gray-200 rounded-md bg-gray-50">
|
||||
<Text className="text-[14px]">
|
||||
Your account has been temporarily locked due to multiple failed login attempts.
|
||||
</Text>
|
||||
<Text>If these attempts were not made by you, reset your password immediately.</Text>
|
||||
</Section>
|
||||
<Section className="text-center mt-[28px]">
|
||||
<Button
|
||||
href={`${callback_url}?token=${token}`}
|
||||
className="rounded-md p-3 px-[28px] my-[8px] text-center text-[16px] bg-[#EBF852] border-solid border border-[#d1e309] text-black font-medium"
|
||||
>
|
||||
Unlock Account
|
||||
</Button>
|
||||
<Section className="text-center">
|
||||
<BaseButton href={`${callback_url}?token=${token}`}>Unlock Account</BaseButton>
|
||||
</Section>
|
||||
</BaseEmailWrapper>
|
||||
);
|
||||
|
@@ -105,44 +105,93 @@ The templates hold an array of templates that will be rendered and injected into
|
||||
|
||||
|
||||
### Authentication
|
||||
The Infisical Agent Injector only supports Machine Identity [Kubernetes Auth](/documentation/platform/identities/kubernetes-auth) authentication at the moment.
|
||||
The Infisical Agent Injector supports Machine Identity [Kubernetes Auth](/documentation/platform/identities/kubernetes-auth) and [LDAP Auth](/documentation/platform/identities/ldap-auth) authentication.
|
||||
|
||||
To configure Kubernetes Auth, you need to set the `auth.type` field to `kubernetes` and set the `auth.config.identity-id` to the ID of the machine identity you wish to use for authentication.
|
||||
|
||||
```yaml
|
||||
auth:
|
||||
type: "kubernetes"
|
||||
config:
|
||||
identity-id: "<your-infisical-machine-identity-id>"
|
||||
```
|
||||
<AccordionGroup>
|
||||
<Accordion title="Kubernetes Auth">
|
||||
|
||||
### Example ConfigMap
|
||||
```yaml config-map.yaml
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: demo-config-map
|
||||
data:
|
||||
config.yaml: |
|
||||
infisical:
|
||||
address: "https://app.infisical.com"
|
||||
auth:
|
||||
type: "kubernetes"
|
||||
config:
|
||||
identity-id: "<your-infisical-machine-identity-id>"
|
||||
templates:
|
||||
- destination-path: "/path/to/save/secrets/file.txt"
|
||||
template-content: |
|
||||
{{- with secret "<your-project-id>" "dev" "/" }}
|
||||
{{- range . }}
|
||||
{{ .Key }}={{ .Value }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
```
|
||||
To configure Kubernetes Auth, you need to set the `auth.type` field to `kubernetes` and set the `auth.config.identity-id` to the ID of the machine identity you wish to use for authentication.
|
||||
```yaml
|
||||
auth:
|
||||
type: "kubernetes"
|
||||
config:
|
||||
identity-id: "<your-infisical-machine-identity-id>"
|
||||
```
|
||||
|
||||
```bash
|
||||
kubectl apply -f config-map.yaml
|
||||
```
|
||||
### Example ConfigMap
|
||||
```yaml config-map.yaml
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: demo-config-map
|
||||
data:
|
||||
config.yaml: |
|
||||
infisical:
|
||||
address: "https://app.infisical.com"
|
||||
auth:
|
||||
type: "kubernetes"
|
||||
config:
|
||||
identity-id: "<your-infisical-machine-identity-id>"
|
||||
templates:
|
||||
- destination-path: "/path/to/save/secrets/file.txt"
|
||||
template-content: |
|
||||
{{- with secret "<your-project-id>" "dev" "/" }}
|
||||
{{- range . }}
|
||||
{{ .Key }}={{ .Value }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
```
|
||||
|
||||
```bash
|
||||
kubectl apply -f config-map.yaml
|
||||
```
|
||||
|
||||
</Accordion>
|
||||
<Accordion title="LDAP Auth">
|
||||
To configure LDAP Auth, you need to set the `auth.type` field to `ldap-auth` and set the `auth.config.identity-id` to the ID of the machine identity you wish to use for authentication. Configure the `auth.config.username` and `auth.config.password` to the username and password of the LDAP user to authenticate with.
|
||||
|
||||
```yaml
|
||||
auth:
|
||||
type: "ldap-auth"
|
||||
config:
|
||||
identity-id: "<your-infisical-machine-identity-id>"
|
||||
username: "<your-ldap-username>"
|
||||
password: "<your-ldap-password>"
|
||||
```
|
||||
|
||||
### Example ConfigMap
|
||||
```yaml config-map.yaml
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: demo-config-map
|
||||
data:
|
||||
config.yaml: |
|
||||
infisical:
|
||||
address: "https://app.infisical.com"
|
||||
auth:
|
||||
type: "ldap-auth"
|
||||
config:
|
||||
identity-id: "<your-infisical-machine-identity-id>"
|
||||
username: "<your-ldap-username>"
|
||||
password: "<your-ldap-password>"
|
||||
templates:
|
||||
- destination-path: "/path/to/save/secrets/file.txt"
|
||||
template-content: |
|
||||
{{- with secret "<your-project-id>" "dev" "/" }}
|
||||
{{- range . }}
|
||||
{{ .Key }}={{ .Value }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
```
|
||||
|
||||
```bash
|
||||
kubectl apply -f config-map.yaml
|
||||
```
|
||||
|
||||
</Accordion>
|
||||
</AccordionGroup>
|
||||
|
||||
To use the config map in your pod, you will need to add the `org.infisical.com/agent-config-map` annotation to your pod's deployment. The value of the annotation is the name of the config map you created above.
|
||||
```yaml
|
||||
|
@@ -68,6 +68,11 @@ spec:
|
||||
serviceAccountKeyFilePath: </path-to-service-account-key-file.json>
|
||||
gcpIdTokenAuth:
|
||||
identityId: <machine-identity-id>
|
||||
ldapAuth:
|
||||
identityId: <machine-identity-id>
|
||||
credentialsRef:
|
||||
secretName: <secret-name> # ldap-auth-credentials
|
||||
secretNamespace: <secret-namespace> # default
|
||||
kubernetesAuth:
|
||||
identityId: <machine-identity-id>
|
||||
serviceAccountRef:
|
||||
@@ -105,15 +110,15 @@ kind: Secret
|
||||
### InfisicalDynamicSecret CRD properties
|
||||
|
||||
<Accordion title="hostAPI">
|
||||
If you are fetching secrets from a self-hosted instance of Infisical set the value of `hostAPI` to
|
||||
` https://your-self-hosted-instace.com/api`
|
||||
If you are fetching secrets from a self-hosted instance of Infisical set the value of `hostAPI` to
|
||||
`https://your-self-hosted-instace.com/api`
|
||||
|
||||
When `hostAPI` is not defined the operator fetches secrets from Infisical Cloud.
|
||||
|
||||
<Accordion title="Advanced use case">
|
||||
If you have installed your Infisical instance within the same cluster as the Infisical operator, you can optionally access the Infisical backend's service directly without having to route through the public internet.
|
||||
If you have installed your Infisical instance within the same cluster as the Infisical operator, you can optionally access the Infisical backend's service directly without having to route through the public internet.
|
||||
To achieve this, use the following address for the hostAPI field:
|
||||
|
||||
|
||||
``` bash
|
||||
http://<backend-svc-name>.<namespace>.svc.cluster.local:4000/api
|
||||
```
|
||||
@@ -126,18 +131,19 @@ When `hostAPI` is not defined the operator fetches secrets from Infisical Cloud.
|
||||
<Accordion title="leaseTTL">
|
||||
The `leaseTTL` is a string-formatted duration that defines the time the lease should last for the dynamic secret.
|
||||
|
||||
The format of the field is `[duration][unit]` where `duration` is a number and `unit` is a string representing the unit of time.
|
||||
The format of the field is `[duration][unit]` where `duration` is a number and `unit` is a string representing the unit of time.
|
||||
|
||||
The following units are supported:
|
||||
The following units are supported:
|
||||
|
||||
- `s` for seconds (must be at least 5 seconds)
|
||||
- `m` for minutes
|
||||
- `h` for hours
|
||||
- `d` for days
|
||||
- `s` for seconds (must be at least 5 seconds)
|
||||
- `m` for minutes
|
||||
- `h` for hours
|
||||
- `d` for days
|
||||
|
||||
<Note>
|
||||
The lease duration at most be 1 day (24 hours). And the TTL must be less than the max TTL defined on the dynamic secret.
|
||||
</Note>
|
||||
<Note>
|
||||
The lease duration at most be 1 day (24 hours). And the TTL must be less than the max TTL defined on the dynamic secret.
|
||||
|
||||
</Note>
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="managedSecretReference">
|
||||
@@ -212,7 +218,7 @@ spec:
|
||||
|
||||
<Accordion title="dynamicSecret">
|
||||
The `dynamicSecret` field is used to specify which dynamic secret to create leases for. The required fields are `secretName`, `projectId`, `secretsPath`, and `environmentSlug`.
|
||||
|
||||
|
||||
```yaml
|
||||
spec:
|
||||
dynamicSecret:
|
||||
@@ -300,7 +306,6 @@ The available authentication methods are `universalAuth`, `kubernetesAuth`, `aws
|
||||
- `autoCreateServiceAccountToken`: If set to `true`, the operator will automatically create a short-lived service account token on-demand for the service account. Defaults to `false`.
|
||||
- `serviceAccountTokenAudiences`: Optionally specify audience for the service account token. This field is only relevant if you have set `autoCreateServiceAccountToken` to `true`. No audience is specified by default.
|
||||
|
||||
|
||||
Example:
|
||||
|
||||
```yaml
|
||||
@@ -316,7 +321,40 @@ The available authentication methods are `universalAuth`, `kubernetesAuth`, `aws
|
||||
```
|
||||
|
||||
</Accordion>
|
||||
<Accordion title="ldapAuth">
|
||||
The LDAP machine identity authentication method is used to authenticate with a configured LDAP directory. [Read more about LDAP Auth](/documentation/platform/identities/ldap-auth).
|
||||
|
||||
Valid fields:
|
||||
- `identityId`: The identity ID of the machine identity you created.
|
||||
- `credentialsRef`: The name and namespace of the Kubernetes secret that stores the LDAP credentials.
|
||||
- `credentialsRef.secretName`: The name of the Kubernetes secret.
|
||||
- `credentialsRef.secretNamespace`: The namespace of the Kubernetes secret.
|
||||
|
||||
Example:
|
||||
|
||||
```yaml
|
||||
# infisical-push-secret.yaml
|
||||
spec:
|
||||
ldapAuth:
|
||||
identityId: <machine-identity-id>
|
||||
credentialsRef:
|
||||
secretName: <secret-name>
|
||||
secretNamespace: <secret-namespace>
|
||||
```
|
||||
|
||||
```yaml
|
||||
# machine-identity-credentials.yaml
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: ldap-auth-credentials
|
||||
type: Opaque
|
||||
stringData:
|
||||
username: <ldap-username>
|
||||
password: <ldap-password>
|
||||
```
|
||||
|
||||
</Accordion>
|
||||
<Accordion title="awsIamAuth">
|
||||
The AWS IAM machine identity authentication method is used to authenticate with Infisical.
|
||||
[Read more about AWS IAM Auth](/documentation/platform/identities/aws-auth).
|
||||
@@ -391,7 +429,7 @@ The available authentication methods are `universalAuth`, `kubernetesAuth`, `aws
|
||||
<Accordion title="tls">
|
||||
This block defines the TLS settings to use for connecting to the Infisical
|
||||
instance.
|
||||
|
||||
|
||||
Fields:
|
||||
<Accordion title="caRef">
|
||||
This block defines the reference to the CA certificate to use for connecting to the Infisical instance with SSL/TLS.
|
||||
@@ -444,7 +482,7 @@ metadata:
|
||||
name: nginx-deployment
|
||||
labels:
|
||||
app: nginx
|
||||
annotations:
|
||||
annotations:
|
||||
secrets.infisical.com/auto-reload: "true" # <- redeployment annotation
|
||||
spec:
|
||||
replicas: 1
|
||||
@@ -467,7 +505,7 @@ spec:
|
||||
```
|
||||
</Accordion>
|
||||
<Info>
|
||||
#### How it works
|
||||
When the lease changes, the operator will check to see which deployments are using the operator-managed Kubernetes secret that received the update.
|
||||
#### How it works
|
||||
When the lease changes, the operator will check to see which deployments are using the operator-managed Kubernetes secret that received the update.
|
||||
Then, for each deployment that has this annotation present, a rolling update will be triggered. A redeployment won't happen if the lease is renewed, only if it's recreated.
|
||||
</Info>
|
||||
|
@@ -5,9 +5,9 @@ description: "Learn how to use the InfisicalPushSecret CRD to push and manage se
|
||||
---
|
||||
|
||||
|
||||
## Overview
|
||||
## Overview
|
||||
|
||||
The **InfisicalPushSecret** CRD allows you to create secrets in your Kubernetes cluster and push them to Infisical.
|
||||
The **InfisicalPushSecret** CRD allows you to create secrets in your Kubernetes cluster and push them to Infisical.
|
||||
|
||||
|
||||
This CRD offers the following features:
|
||||
@@ -70,6 +70,11 @@ Before applying the InfisicalPushSecret CRD, you need to create a Kubernetes sec
|
||||
serviceAccountRef:
|
||||
name: <secret-name>
|
||||
namespace: <secret-namespace>
|
||||
ldapAuth:
|
||||
identityId: <machine-identity-id>
|
||||
credentialsRef:
|
||||
secretName: <secret-name> # ldap-auth-credentials
|
||||
secretNamespace: <secret-namespace> # default
|
||||
universalAuth:
|
||||
credentialsRef:
|
||||
secretName: <secret-name> # universal-auth-credentials
|
||||
@@ -104,15 +109,15 @@ After applying the InfisicalPushSecret CRD, you should notice that the secrets y
|
||||
## InfisicalPushSecret CRD properties
|
||||
|
||||
<Accordion title="hostAPI">
|
||||
If you are fetching secrets from a self-hosted instance of Infisical set the value of `hostAPI` to
|
||||
` https://your-self-hosted-instace.com/api`
|
||||
If you are fetching secrets from a self-hosted instance of Infisical set the value of `hostAPI` to
|
||||
`https://your-self-hosted-instace.com/api`
|
||||
|
||||
When `hostAPI` is not defined the operator fetches secrets from Infisical Cloud.
|
||||
|
||||
<Accordion title="Advanced use case">
|
||||
If you have installed your Infisical instance within the same cluster as the Infisical operator, you can optionally access the Infisical backend's service directly without having to route through the public internet.
|
||||
If you have installed your Infisical instance within the same cluster as the Infisical operator, you can optionally access the Infisical backend's service directly without having to route through the public internet.
|
||||
To achieve this, use the following address for the hostAPI field:
|
||||
|
||||
|
||||
``` bash
|
||||
http://<backend-svc-name>.<namespace>.svc.cluster.local:4000/api
|
||||
```
|
||||
@@ -187,7 +192,7 @@ After applying the InfisicalPushSecret CRD, you should notice that the secrets y
|
||||
|
||||
<Accordion title="destination">
|
||||
The `destination` field is used to specify where you want to create the secrets in Infisical. The required fields are `projectId`, `environmentSlug`, and `secretsPath`.
|
||||
|
||||
|
||||
```yaml
|
||||
spec:
|
||||
destination:
|
||||
@@ -212,7 +217,7 @@ After applying the InfisicalPushSecret CRD, you should notice that the secrets y
|
||||
|
||||
<Accordion title="push">
|
||||
The `push` field is used to define what you want to push to Infisical. Currently the operator only supports pushing Kubernetes secrets to Infisical. An example of the `push` field is shown below.
|
||||
|
||||
|
||||
|
||||
|
||||
<Accordion title="secret">
|
||||
@@ -220,7 +225,7 @@ After applying the InfisicalPushSecret CRD, you should notice that the secrets y
|
||||
|
||||
|
||||
|
||||
Example usage of the `push.secret` field:
|
||||
Example usage of the `push.secret` field:
|
||||
|
||||
```yaml infisical-push-secret.yaml
|
||||
push:
|
||||
@@ -282,7 +287,7 @@ After applying the InfisicalPushSecret CRD, you should notice that the secrets y
|
||||
spec:
|
||||
universalAuth:
|
||||
credentialsRef:
|
||||
secretName: <secret-name>
|
||||
secretName: <secret-name>
|
||||
secretNamespace: <secret-namespace>
|
||||
```
|
||||
|
||||
@@ -324,7 +329,39 @@ After applying the InfisicalPushSecret CRD, you should notice that the secrets y
|
||||
namespace: <secret-namespace>
|
||||
```
|
||||
</Accordion>
|
||||
<Accordion title="ldapAuth">
|
||||
The LDAP machine identity authentication method is used to authenticate with a configured LDAP directory. [Read more about LDAP Auth](/documentation/platform/identities/ldap-auth).
|
||||
|
||||
Valid fields:
|
||||
- `identityId`: The identity ID of the machine identity you created.
|
||||
- `credentialsRef`: The name and namespace of the Kubernetes secret that stores the LDAP credentials.
|
||||
- `credentialsRef.secretName`: The name of the Kubernetes secret.
|
||||
- `credentialsRef.secretNamespace`: The namespace of the Kubernetes secret.
|
||||
|
||||
Example:
|
||||
|
||||
```yaml
|
||||
# infisical-push-secret.yaml
|
||||
spec:
|
||||
ldapAuth:
|
||||
identityId: <machine-identity-id>
|
||||
credentialsRef:
|
||||
secretName: <secret-name>
|
||||
secretNamespace: <secret-namespace>
|
||||
```
|
||||
|
||||
```yaml
|
||||
# machine-identity-credentials.yaml
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: ldap-auth-credentials
|
||||
type: Opaque
|
||||
stringData:
|
||||
username: <ldap-username>
|
||||
password: <ldap-password>
|
||||
```
|
||||
</Accordion>
|
||||
<Accordion title="awsIamAuth">
|
||||
The AWS IAM machine identity authentication method is used to authenticate with Infisical.
|
||||
[Read more about AWS IAM Auth](/documentation/platform/identities/aws-auth).
|
||||
@@ -398,7 +435,7 @@ After applying the InfisicalPushSecret CRD, you should notice that the secrets y
|
||||
<Accordion title="tls">
|
||||
This block defines the TLS settings to use for connecting to the Infisical
|
||||
instance.
|
||||
|
||||
|
||||
Fields:
|
||||
<Accordion title="caRef">
|
||||
This block defines the reference to the CA certificate to use for connecting to the Infisical instance with SSL/TLS.
|
||||
@@ -438,7 +475,7 @@ Using Go templates, you can format, combine, and create new key-value pairs of s
|
||||
Use this option when you would like to push **only** a subset of secrets from the Kubernetes secret to Infisical.
|
||||
</Accordion>
|
||||
<Accordion title="push.secret.template.data">
|
||||
Define secret keys and their corresponding templates.
|
||||
Define secret keys and their corresponding templates.
|
||||
Each data value uses a Golang template with access to all secrets defined in the `push.secret.secretName` Kubernetes secret.
|
||||
|
||||
Secrets are structured as follows:
|
||||
@@ -483,7 +520,7 @@ A generator is defined as a custom resource (`ClusterGenerator`) within the clus
|
||||
|
||||
Because of this behavior, you may want to disable automatic syncing for the `InfisicalPushSecret` resource to avoid continuous regeneration of secrets. This can be done by omitting the `resyncInterval` field from the InfisicalPushSecret CRD.
|
||||
|
||||
### Example usage
|
||||
### Example usage
|
||||
```yaml
|
||||
push:
|
||||
secret:
|
||||
@@ -625,4 +662,4 @@ After applying, you should notice that the secrets have been pushed to Infisical
|
||||
```bash
|
||||
kubectl apply -f source-push-secret.yaml # The secret that you're referencing in the InfisicalPushSecret CRD push.secret field
|
||||
kubectl apply -f example-infisical-push-secret-crd.yaml # The InfisicalPushSecret CRD itself
|
||||
```
|
||||
```
|
||||
|
@@ -44,15 +44,15 @@ spec:
|
||||
The following properties help define what instance of Infisical the operator will interact with, the interval it will sync secrets and any CA certificates that may be required to connect.
|
||||
|
||||
<Accordion title="hostAPI">
|
||||
If you are fetching secrets from a self-hosted instance of Infisical set the value of `hostAPI` to
|
||||
If you are fetching secrets from a self-hosted instance of Infisical set the value of `hostAPI` to
|
||||
` https://your-self-hosted-instace.com/api`
|
||||
|
||||
When `hostAPI` is not defined the operator fetches secrets from Infisical Cloud.
|
||||
|
||||
<Accordion title="Advanced use case">
|
||||
If you have installed your Infisical instance within the same cluster as the Infisical operator, you can optionally access the Infisical backend's service directly without having to route through the public internet.
|
||||
If you have installed your Infisical instance within the same cluster as the Infisical operator, you can optionally access the Infisical backend's service directly without having to route through the public internet.
|
||||
To achieve this, use the following address for the hostAPI field:
|
||||
|
||||
|
||||
``` bash
|
||||
http://<backend-svc-name>.<namespace>.svc.cluster.local:4000/api
|
||||
```
|
||||
@@ -110,7 +110,7 @@ The list of available authentication methods are shown below.
|
||||
<Step title="Create Kubernetes secret containing machine identity credentials">
|
||||
Once you have created your machine identity and added it to your project(s), you will need to create a Kubernetes secret containing the identity credentials.
|
||||
To quickly create a Kubernetes secret containing the identity credentials, you can run the command below.
|
||||
|
||||
|
||||
Make sure you replace `<your-identity-client-id>` with the identity client ID and `<your-identity-client-secret>` with the identity client secret.
|
||||
|
||||
``` bash
|
||||
@@ -525,49 +525,6 @@ spec:
|
||||
...
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
@@ -747,6 +704,59 @@ spec:
|
||||
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="authentication.ldapAuth">
|
||||
The LDAP machine identity authentication method is used to authenticate with Infisical using the configured LDAP directory. The username and password needs to be stored in a Kubernetes secret. This block defines the reference to the name and namespace of secret that stores these credentials.
|
||||
|
||||
<Steps>
|
||||
<Step title="Create a machine identity">
|
||||
You need to create a machine identity, and give it access to the project(s) you want to interact with. You can [read more about machine identities here](/documentation/platform/identities/universal-auth).
|
||||
</Step>
|
||||
<Step title="Create Kubernetes secret containing machine identity credentials">
|
||||
Once you have created your machine identity and added it to your project(s), you will need to create a Kubernetes secret containing the identity credentials.
|
||||
To quickly create a Kubernetes secret containing the identity credentials, you can run the command below.
|
||||
|
||||
Make sure you replace `<your-identity-ldap-username>` with the identity LDAP username and `<your-identity-ldap-password>` with the identity LDAP password.
|
||||
|
||||
``` bash
|
||||
kubectl create secret generic ldap-auth-credentials --from-literal=username="<your-identity-ldap-username>" --from-literal=password="<your-identity-ldap-password>"
|
||||
```
|
||||
</Step>
|
||||
|
||||
<Step title="Add reference for the Kubernetes secret containing the identity credentials">
|
||||
Once the secret is created, add the `secretName` and `secretNamespace` of the secret that was just created under `authentication.ldapAuth.credentialsRef` field in the InfisicalSecret resource.
|
||||
</Step>
|
||||
|
||||
</Steps>
|
||||
|
||||
<Info>
|
||||
Make sure to also populate the `secretsScope` field with the project slug
|
||||
_`projectSlug`_, environment slug _`envSlug`_, and secrets path
|
||||
_`secretsPath`_ that you want to fetch secrets from. Please see the example
|
||||
below.
|
||||
</Info>
|
||||
|
||||
## Example
|
||||
|
||||
```yaml
|
||||
apiVersion: secrets.infisical.com/v1alpha1
|
||||
kind: InfisicalSecret
|
||||
metadata:
|
||||
name: infisicalsecret-sample-crd
|
||||
spec:
|
||||
authentication:
|
||||
ldapAuth:
|
||||
secretsScope:
|
||||
projectSlug: <project-slug> # <-- project slug
|
||||
envSlug: <env-slug> # "dev", "staging", "prod", etc..
|
||||
secretsPath: "<secrets-path>" # Root is "/"
|
||||
identityId: <machine-identity-id>
|
||||
credentialsRef:
|
||||
secretName: ldap-auth-credentials # <-- name of the Kubernetes secret that stores our machine identity credentials
|
||||
secretNamespace: default # <-- namespace of the Kubernetes secret that stores our machine identity credentials
|
||||
```
|
||||
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="authentication.serviceToken">
|
||||
|
||||
The service token required to authenticate with Infisical needs to be stored in a Kubernetes secret. This block defines the reference to the name and namespace of secret that stores this service token.
|
||||
@@ -826,7 +836,7 @@ managedKubeSecretReferences:
|
||||
The name of the managed Kubernetes secret to be created
|
||||
</Accordion>
|
||||
<Accordion title="managedKubeSecretReferences[].secretNamespace">
|
||||
The namespace of the managed Kubernetes secret to be created.
|
||||
The namespace of the managed Kubernetes secret to be created.
|
||||
</Accordion>
|
||||
<Accordion title="managedKubeSecretReferences[].secretType">
|
||||
Override the default Opaque type for managed secrets with this field. Useful for creating kubernetes.io/dockerconfigjson secrets.
|
||||
@@ -855,8 +865,8 @@ Using Go templates, you can format, combine, and create new key-value pairs from
|
||||
<Accordion title="managedKubeSecretReferences[].template">
|
||||
</Accordion>
|
||||
<Accordion title="managedKubeSecretReferences[].template.includeAllSecrets">
|
||||
This property controls what secrets are included in your managed secret when using templates.
|
||||
When set to `true`, all secrets fetched from your Infisical project will be added into your managed Kubernetes secret resource.
|
||||
This property controls what secrets are included in your managed secret when using templates.
|
||||
When set to `true`, all secrets fetched from your Infisical project will be added into your managed Kubernetes secret resource.
|
||||
**Use this option when you would like to sync all secrets from Infisical to Kubernetes but want to template a subset of them.**
|
||||
|
||||
When set to `false`, only secrets defined in the `managedKubeSecretReferences[].template.data` field of the template will be included in the managed secret.
|
||||
@@ -864,7 +874,7 @@ Use this option when you would like to sync **only** a subset of secrets from In
|
||||
|
||||
</Accordion>
|
||||
<Accordion title="managedKubeSecretReferences[].template.data">
|
||||
Define secret keys and their corresponding templates.
|
||||
Define secret keys and their corresponding templates.
|
||||
Each data value uses a Golang template with access to all secrets retrieved from the specified scope.
|
||||
|
||||
Secrets are structured as follows:
|
||||
@@ -928,7 +938,9 @@ The properties includes defining the name and namespace of the Kubernetes config
|
||||
The Infisical operator will automatically create the Kubernetes config map in the specified name/namespace and ensure it stays up-to-date. If a config map already exists in the specified namespace, the operator will update the existing config map with the new data.
|
||||
|
||||
<Warning>
|
||||
The usage of config maps is only intended for storing non-sensitive data. If you are looking to store sensitive data, please use the [managed secret](#operator-managed-secrets) property instead.
|
||||
The usage of config maps is only intended for storing non-sensitive data. If
|
||||
you are looking to store sensitive data, please use the [managed
|
||||
secret](#operator-managed-secrets) property instead.
|
||||
</Warning>
|
||||
|
||||
<Accordion title="managedKubeConfigMapReferences">
|
||||
@@ -937,25 +949,24 @@ The Infisical operator will automatically create the Kubernetes config map in th
|
||||
The name of the managed Kubernetes config map that your Infisical data will be stored in.
|
||||
</Accordion>
|
||||
<Accordion title="managedKubeConfigMapReferences[].configMapNamespace">
|
||||
The namespace of the managed Kubernetes config map that your Infisical data will be stored in.
|
||||
The namespace of the managed Kubernetes config map that your Infisical data will be stored in.
|
||||
</Accordion>
|
||||
<Accordion title="managedKubeConfigMapReferences[].creationPolicy">
|
||||
Creation policies allow you to control whether or not owner references should be added to the managed Kubernetes config map that is generated by the Infisical operator.
|
||||
This is useful for tools such as ArgoCD, where every resource requires an owner reference; otherwise, it will be pruned automatically.
|
||||
|
||||
#### Available options
|
||||
#### Available options
|
||||
|
||||
- `Orphan` (default)
|
||||
- `Owner`
|
||||
- `Orphan` (default)
|
||||
- `Owner`
|
||||
|
||||
<Tip>
|
||||
When creation policy is set to `Owner`, the `InfisicalSecret` CRD must be in
|
||||
the same namespace as where the managed kubernetes config map.
|
||||
</Tip>
|
||||
<Tip>
|
||||
When creation policy is set to `Owner`, the `InfisicalSecret` CRD must be in
|
||||
the same namespace as where the managed kubernetes config map.
|
||||
</Tip>
|
||||
|
||||
</Accordion>
|
||||
|
||||
|
||||
#### Managed ConfigMap Templating
|
||||
|
||||
Fetching secrets from Infisical as is via the operator may not be enough. This is where templating functionality may be helpful.
|
||||
@@ -964,67 +975,68 @@ Using Go templates, you can format, combine, and create new key-value pairs from
|
||||
<Accordion title="managedKubeConfigMapReferences[].template">
|
||||
</Accordion>
|
||||
<Accordion title="managedKubeConfigMapReferences[].template.includeAllSecrets">
|
||||
This property controls what secrets are included in your managed config map when using templates.
|
||||
When set to `true`, all secrets fetched from your Infisical project will be added into your managed Kubernetes config map resource.
|
||||
This property controls what secrets are included in your managed config map when using templates.
|
||||
When set to `true`, all secrets fetched from your Infisical project will be added into your managed Kubernetes config map resource.
|
||||
**Use this option when you would like to sync all secrets from Infisical to Kubernetes but want to template a subset of them.**
|
||||
|
||||
When set to `false`, only secrets defined in the `managedKubeConfigMapReferences[].template.data` field of the template will be included in the managed config map.
|
||||
Use this option when you would like to sync **only** a subset of secrets from Infisical to Kubernetes.
|
||||
When set to `false`, only secrets defined in the `managedKubeConfigMapReferences[].template.data` field of the template will be included in the managed config map.
|
||||
Use this option when you would like to sync **only** a subset of secrets from Infisical to Kubernetes.
|
||||
|
||||
</Accordion>
|
||||
<Accordion title="managedKubeConfigMapReferences[].template.data">
|
||||
Define secret keys and their corresponding templates.
|
||||
Define secret keys and their corresponding templates.
|
||||
Each data value uses a Golang template with access to all secrets retrieved from the specified scope.
|
||||
|
||||
Secrets are structured as follows:
|
||||
Secrets are structured as follows:
|
||||
|
||||
```golang
|
||||
type TemplateSecret struct {
|
||||
Value string `json:"value"`
|
||||
SecretPath string `json:"secretPath"`
|
||||
}
|
||||
```
|
||||
```golang
|
||||
type TemplateSecret struct {
|
||||
Value string `json:"value"`
|
||||
SecretPath string `json:"secretPath"`
|
||||
}
|
||||
```
|
||||
|
||||
#### Example template configuration:
|
||||
#### Example template configuration:
|
||||
|
||||
```yaml
|
||||
managedKubeConfigMapReferences:
|
||||
- configMapName: managed-configmap
|
||||
configMapNamespace: default
|
||||
template:
|
||||
includeAllSecrets: true
|
||||
data:
|
||||
# Create new key that doesn't exist in your Infisical project using values of other secrets
|
||||
SITE_URL: "{{ .SITE_URL.Value }}"
|
||||
# Override an existing key in Infisical project with a new value using values of other secrets
|
||||
API_URL: "https://api.{{.SITE_URL.Value}}.{{.REGION.Value}}.com"
|
||||
```
|
||||
```yaml
|
||||
managedKubeConfigMapReferences:
|
||||
- configMapName: managed-configmap
|
||||
configMapNamespace: default
|
||||
template:
|
||||
includeAllSecrets: true
|
||||
data:
|
||||
# Create new key that doesn't exist in your Infisical project using values of other secrets
|
||||
SITE_URL: "{{ .SITE_URL.Value }}"
|
||||
# Override an existing key in Infisical project with a new value using values of other secrets
|
||||
API_URL: "https://api.{{.SITE_URL.Value}}.{{.REGION.Value}}.com"
|
||||
```
|
||||
|
||||
For this example, let's assume the following secrets exist in your Infisical project:
|
||||
For this example, let's assume the following secrets exist in your Infisical project:
|
||||
|
||||
```
|
||||
SITE_URL = "https://example.com"
|
||||
REGION = "us-east-1"
|
||||
API_URL = "old-url" # This will be overridden
|
||||
```
|
||||
```
|
||||
SITE_URL = "https://example.com"
|
||||
REGION = "us-east-1"
|
||||
API_URL = "old-url" # This will be overridden
|
||||
```
|
||||
|
||||
The resulting managed Kubernetes config map will then contain:
|
||||
The resulting managed Kubernetes config map will then contain:
|
||||
|
||||
```
|
||||
# Original config map data (from includeAllSecrets: true)
|
||||
SITE_URL = "https://example.com"
|
||||
REGION = "us-east-1"
|
||||
```
|
||||
# Original config map data (from includeAllSecrets: true)
|
||||
SITE_URL = "https://example.com"
|
||||
REGION = "us-east-1"
|
||||
|
||||
# New and overridden config map data
|
||||
SITE_URL = "https://example.com"
|
||||
API_URL = "https://api.example.com.us-east-1.com" # Existing secret overridden by template
|
||||
```
|
||||
# New and overridden config map data
|
||||
SITE_URL = "https://example.com"
|
||||
API_URL = "https://api.example.com.us-east-1.com" # Existing secret overridden by template
|
||||
```
|
||||
|
||||
To help transform your config map data further, the operator provides a set of built-in functions that you can use in your templates.
|
||||
To help transform your config map data further, the operator provides a set of built-in functions that you can use in your templates.
|
||||
|
||||
### Available templating functions
|
||||
|
||||
Please refer to the [templating functions documentation](/integrations/platforms/kubernetes/overview#available-helper-functions) for more information.
|
||||
|
||||
### Available templating functions
|
||||
|
||||
Please refer to the [templating functions documentation](/integrations/platforms/kubernetes/overview#available-helper-functions) for more information.
|
||||
</Accordion>
|
||||
|
||||
## Applying CRD
|
||||
@@ -1061,8 +1073,6 @@ To verify that the operator has successfully created the managed secret, you can
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
|
||||
|
||||
## Using Managed Secret In Your Deployment
|
||||
|
||||
To make use of the managed secret created by the operator into your deployment can be achieved through several methods.
|
||||
@@ -1071,45 +1081,45 @@ Here, we will highlight three of the most common ways to utilize it. Learn more
|
||||
<Accordion title="envFrom">
|
||||
This will take all the secrets from your managed secret and expose them to your container
|
||||
|
||||
````yaml
|
||||
envFrom:
|
||||
- secretRef:
|
||||
name: managed-secret # managed secret name
|
||||
```
|
||||
````yaml
|
||||
envFrom:
|
||||
- secretRef:
|
||||
name: managed-secret # managed secret name
|
||||
```
|
||||
|
||||
Example usage in a deployment
|
||||
```yaml
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: nginx-deployment
|
||||
labels:
|
||||
Example usage in a deployment
|
||||
```yaml
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: nginx-deployment
|
||||
labels:
|
||||
app: nginx
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: nginx
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: nginx
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: nginx
|
||||
spec:
|
||||
containers:
|
||||
- name: nginx
|
||||
image: nginx:1.14.2
|
||||
envFrom:
|
||||
- secretRef:
|
||||
name: managed-secret # <- name of managed secret
|
||||
ports:
|
||||
- containerPort: 80
|
||||
````
|
||||
spec:
|
||||
containers:
|
||||
- name: nginx
|
||||
image: nginx:1.14.2
|
||||
envFrom:
|
||||
- secretRef:
|
||||
name: managed-secret # <- name of managed secret
|
||||
ports:
|
||||
- containerPort: 80
|
||||
````
|
||||
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="env">
|
||||
This will allow you to select individual secrets by key name from your managed secret and expose them to your container
|
||||
|
||||
<Accordion title="env">
|
||||
This will allow you to select individual secrets by key name from your managed secret and expose them to your container
|
||||
|
||||
```yaml
|
||||
env:
|
||||
- name: SECRET_NAME # The environment variable's name which is made available in the container
|
||||
@@ -1119,37 +1129,38 @@ Here, we will highlight three of the most common ways to utilize it. Learn more
|
||||
key: SOME_SECRET_KEY # The name of the key which exists in the managed secret
|
||||
```
|
||||
|
||||
Example usage in a deployment
|
||||
Example usage in a deployment
|
||||
|
||||
```yaml
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: nginx-deployment
|
||||
labels:
|
||||
```yaml
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: nginx-deployment
|
||||
labels:
|
||||
app: nginx
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: nginx
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: nginx
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: nginx
|
||||
spec:
|
||||
containers:
|
||||
- name: nginx
|
||||
image: nginx:1.14.2
|
||||
env:
|
||||
- name: STRIPE_API_SECRET
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: managed-secret # <- name of managed secret
|
||||
key: STRIPE_API_SECRET
|
||||
ports:
|
||||
- containerPort: 80
|
||||
```
|
||||
spec:
|
||||
containers:
|
||||
- name: nginx
|
||||
image: nginx:1.14.2
|
||||
env:
|
||||
- name: STRIPE_API_SECRET
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: managed-secret # <- name of managed secret
|
||||
key: STRIPE_API_SECRET
|
||||
ports:
|
||||
- containerPort: 80
|
||||
```
|
||||
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="volumes">
|
||||
@@ -1161,48 +1172,48 @@ Here, we will highlight three of the most common ways to utilize it. Learn more
|
||||
secretName: managed-secret # managed secret name
|
||||
````
|
||||
|
||||
You can then mount this volume to the container's filesystem so that your deployment can access the files containing the managed secrets
|
||||
You can then mount this volume to the container's filesystem so that your deployment can access the files containing the managed secrets
|
||||
|
||||
```yaml
|
||||
volumeMounts:
|
||||
- name: secrets-volume-name
|
||||
mountPath: /etc/secrets
|
||||
readOnly: true
|
||||
```
|
||||
```yaml
|
||||
volumeMounts:
|
||||
- name: secrets-volume-name
|
||||
mountPath: /etc/secrets
|
||||
readOnly: true
|
||||
```
|
||||
|
||||
Example usage in a deployment
|
||||
Example usage in a deployment
|
||||
|
||||
```yaml
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: nginx-deployment
|
||||
labels:
|
||||
```yaml
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: nginx-deployment
|
||||
labels:
|
||||
app: nginx
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: nginx
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: nginx
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: nginx
|
||||
spec:
|
||||
containers:
|
||||
- name: nginx
|
||||
image: nginx:1.14.2
|
||||
volumeMounts:
|
||||
- name: secrets-volume-name
|
||||
mountPath: /etc/secrets
|
||||
readOnly: true
|
||||
ports:
|
||||
- containerPort: 80
|
||||
volumes:
|
||||
- name: secrets-volume-name
|
||||
secret:
|
||||
secretName: managed-secret # <- managed secrets
|
||||
```
|
||||
spec:
|
||||
containers:
|
||||
- name: nginx
|
||||
image: nginx:1.14.2
|
||||
volumeMounts:
|
||||
- name: secrets-volume-name
|
||||
mountPath: /etc/secrets
|
||||
readOnly: true
|
||||
ports:
|
||||
- containerPort: 80
|
||||
volumes:
|
||||
- name: secrets-volume-name
|
||||
secret:
|
||||
secretName: managed-secret # <- managed secrets
|
||||
```
|
||||
|
||||
</Accordion>
|
||||
|
||||
@@ -1244,7 +1255,7 @@ secrets.infisical.com/auto-reload: "true"
|
||||
name: nginx-deployment
|
||||
labels:
|
||||
app: nginx
|
||||
annotations:
|
||||
annotations:
|
||||
secrets.infisical.com/auto-reload: "true" # <- redeployment annotation
|
||||
spec:
|
||||
replicas: 1
|
||||
@@ -1339,9 +1350,11 @@ secrets.infisical.com/auto-reload: "true"
|
||||
</Accordion>
|
||||
|
||||
<Info>
|
||||
#### How it works
|
||||
When a managed secret is updated, the operator checks for any Deployments, DaemonSets, or StatefulSets that consume the updated secret and have the annotation
|
||||
`secrets.infisical.com/auto-reload: "true"`. For each matching workload, the operator triggers a rolling restart to ensure it picks up the latest secret values.
|
||||
#### How it works When a managed secret is updated, the operator checks for
|
||||
any Deployments, DaemonSets, or StatefulSets that consume the updated secret
|
||||
and have the annotation `secrets.infisical.com/auto-reload: "true"`. For each
|
||||
matching workload, the operator triggers a rolling restart to ensure it picks
|
||||
up the latest secret values.
|
||||
</Info>
|
||||
|
||||
## Using Managed ConfigMap In Your Deployment
|
||||
@@ -1350,52 +1363,52 @@ To make use of the managed ConfigMap created by the operator into your deploymen
|
||||
Here, we will highlight three of the most common ways to utilize it. Learn more about Kubernetes ConfigMaps [here](https://kubernetes.io/docs/concepts/configuration/configmap/)
|
||||
|
||||
<Tip>
|
||||
Automatic redeployment of deployments using managed ConfigMaps is not yet supported.
|
||||
Automatic redeployment of deployments using managed ConfigMaps is not yet
|
||||
supported.
|
||||
</Tip>
|
||||
|
||||
|
||||
<Accordion title="envFrom">
|
||||
This will take all the secrets from your managed ConfigMap and expose them to your container
|
||||
|
||||
````yaml
|
||||
envFrom:
|
||||
- configMapRef:
|
||||
name: managed-configmap # managed configmap name
|
||||
```
|
||||
````yaml
|
||||
envFrom:
|
||||
- configMapRef:
|
||||
name: managed-configmap # managed configmap name
|
||||
```
|
||||
|
||||
Example usage in a deployment
|
||||
```yaml
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: nginx-deployment
|
||||
labels:
|
||||
Example usage in a deployment
|
||||
```yaml
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: nginx-deployment
|
||||
labels:
|
||||
app: nginx
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: nginx
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: nginx
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: nginx
|
||||
spec:
|
||||
containers:
|
||||
- name: nginx
|
||||
image: nginx:1.14.2
|
||||
envFrom:
|
||||
- configMapRef:
|
||||
name: managed-configmap # <- name of managed configmap
|
||||
ports:
|
||||
- containerPort: 80
|
||||
````
|
||||
spec:
|
||||
containers:
|
||||
- name: nginx
|
||||
image: nginx:1.14.2
|
||||
envFrom:
|
||||
- configMapRef:
|
||||
name: managed-configmap # <- name of managed configmap
|
||||
ports:
|
||||
- containerPort: 80
|
||||
````
|
||||
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="env">
|
||||
This will allow you to select individual secrets by key name from your managed ConfigMap and expose them to your container
|
||||
|
||||
<Accordion title="env">
|
||||
This will allow you to select individual secrets by key name from your managed ConfigMap and expose them to your container
|
||||
|
||||
```yaml
|
||||
env:
|
||||
- name: CONFIG_NAME # The environment variable's name which is made available in the container
|
||||
@@ -1405,37 +1418,37 @@ Here, we will highlight three of the most common ways to utilize it. Learn more
|
||||
key: SOME_CONFIG_KEY # The name of the key which exists in the managed configmap
|
||||
```
|
||||
|
||||
Example usage in a deployment
|
||||
Example usage in a deployment
|
||||
|
||||
```yaml
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: nginx-deployment
|
||||
labels:
|
||||
```yaml
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: nginx-deployment
|
||||
labels:
|
||||
app: nginx
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: nginx
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: nginx
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: nginx
|
||||
spec:
|
||||
containers:
|
||||
- name: nginx
|
||||
image: nginx:1.14.2
|
||||
env:
|
||||
- name: STRIPE_API_SECRET
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
name: managed-configmap # <- name of managed configmap
|
||||
key: STRIPE_API_SECRET
|
||||
ports:
|
||||
- containerPort: 80
|
||||
```
|
||||
spec:
|
||||
containers:
|
||||
- name: nginx
|
||||
image: nginx:1.14.2
|
||||
env:
|
||||
- name: STRIPE_API_SECRET
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
name: managed-configmap # <- name of managed configmap
|
||||
key: STRIPE_API_SECRET
|
||||
ports:
|
||||
- containerPort: 80
|
||||
```
|
||||
|
||||
</Accordion>
|
||||
|
||||
@@ -1448,48 +1461,49 @@ Here, we will highlight three of the most common ways to utilize it. Learn more
|
||||
name: managed-configmap # managed configmap name
|
||||
````
|
||||
|
||||
You can then mount this volume to the container's filesystem so that your deployment can access the files containing the managed secrets
|
||||
You can then mount this volume to the container's filesystem so that your deployment can access the files containing the managed secrets
|
||||
|
||||
```yaml
|
||||
volumeMounts:
|
||||
- name: configmaps-volume-name
|
||||
mountPath: /etc/config
|
||||
readOnly: true
|
||||
```
|
||||
```yaml
|
||||
volumeMounts:
|
||||
- name: configmaps-volume-name
|
||||
mountPath: /etc/config
|
||||
readOnly: true
|
||||
```
|
||||
|
||||
Example usage in a deployment
|
||||
Example usage in a deployment
|
||||
|
||||
```yaml
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: nginx-deployment
|
||||
labels:
|
||||
```yaml
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: nginx-deployment
|
||||
labels:
|
||||
app: nginx
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: nginx
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: nginx
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: nginx
|
||||
spec:
|
||||
containers:
|
||||
- name: nginx
|
||||
image: nginx:1.14.2
|
||||
volumeMounts:
|
||||
- name: configmaps-volume-name
|
||||
mountPath: /etc/config
|
||||
readOnly: true
|
||||
ports:
|
||||
- containerPort: 80
|
||||
volumes:
|
||||
- name: configmaps-volume-name
|
||||
configMap:
|
||||
name: managed-configmap # <- managed configmap
|
||||
```
|
||||
spec:
|
||||
containers:
|
||||
- name: nginx
|
||||
image: nginx:1.14.2
|
||||
volumeMounts:
|
||||
- name: configmaps-volume-name
|
||||
mountPath: /etc/config
|
||||
readOnly: true
|
||||
ports:
|
||||
- containerPort: 80
|
||||
volumes:
|
||||
- name: configmaps-volume-name
|
||||
configMap:
|
||||
name: managed-configmap # <- managed configmap
|
||||
```
|
||||
|
||||
</Accordion>
|
||||
|
||||
The definition file of the Kubernetes secret for the CA certificate can be structured like the following:
|
||||
@@ -1532,20 +1546,21 @@ Thus, if a specific label is required on the resulting secret, it can be applied
|
||||
...
|
||||
```
|
||||
|
||||
This would result in the following managed secret to be created:
|
||||
This would result in the following managed secret to be created:
|
||||
|
||||
```yaml
|
||||
apiVersion: v1
|
||||
data: ...
|
||||
kind: Secret
|
||||
metadata:
|
||||
annotations:
|
||||
example.com/annotation-to-be-passed-to-managed-secret: sample-value
|
||||
secrets.infisical.com/version: W/"3f1-ZyOSsrCLGSkAhhCkY2USPu2ivRw"
|
||||
labels:
|
||||
label-to-be-passed-to-managed-secret: sample-value
|
||||
name: managed-token
|
||||
namespace: default
|
||||
type: Opaque
|
||||
```
|
||||
|
||||
```yaml
|
||||
apiVersion: v1
|
||||
data: ...
|
||||
kind: Secret
|
||||
metadata:
|
||||
annotations:
|
||||
example.com/annotation-to-be-passed-to-managed-secret: sample-value
|
||||
secrets.infisical.com/version: W/"3f1-ZyOSsrCLGSkAhhCkY2USPu2ivRw"
|
||||
labels:
|
||||
label-to-be-passed-to-managed-secret: sample-value
|
||||
name: managed-token
|
||||
namespace: default
|
||||
type: Opaque
|
||||
```
|
||||
</Accordion>
|
||||
|
@@ -65,7 +65,14 @@ Example values:
|
||||
default="false"
|
||||
optional
|
||||
>
|
||||
Determines whether your Infisical instance can automatically read the service account token of the pod it's running on. Used for features such as the IRSA auth method.
|
||||
Determines whether your Infisical instance can automatically read the service
|
||||
account token of the pod it's running on. Used for features such as the IRSA
|
||||
auth method.
|
||||
</ParamField>
|
||||
|
||||
<ParamField query="DISABLE_AUDIT_LOG_STORAGE" type="string" default="false">
|
||||
Disable storing audit logs in the database. This is useful if you're using
|
||||
audit log streams and don't want to store them in the database.
|
||||
</ParamField>
|
||||
|
||||
## CORS
|
||||
|
@@ -35,6 +35,7 @@ export type TServerConfig = {
|
||||
initialized: boolean;
|
||||
allowSignUp: boolean;
|
||||
allowedSignUpDomain?: string | null;
|
||||
disableAuditLogStorage: boolean;
|
||||
isMigrationModeOn?: boolean;
|
||||
trustSamlEmails: boolean;
|
||||
trustLdapEmails: boolean;
|
||||
|
@@ -11,6 +11,7 @@ export type TGitHubConnection = TRootAppConnection & { app: AppConnection.GitHub
|
||||
method: GitHubConnectionMethod.OAuth;
|
||||
credentials: {
|
||||
code: string;
|
||||
instanceType?: "cloud" | "server";
|
||||
host?: string;
|
||||
};
|
||||
}
|
||||
@@ -19,6 +20,7 @@ export type TGitHubConnection = TRootAppConnection & { app: AppConnection.GitHub
|
||||
credentials: {
|
||||
code: string;
|
||||
installationId: string;
|
||||
instanceType?: "cloud" | "server";
|
||||
host?: string;
|
||||
};
|
||||
}
|
||||
|
@@ -12,14 +12,15 @@ export const useCreateReminder = (secretId: string) => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation<Reminder, object, CreateReminderDTO>({
|
||||
mutationFn: async ({ message, repeatDays, nextReminderDate, recipients }) => {
|
||||
mutationFn: async ({ message, repeatDays, nextReminderDate, recipients, fromDate }) => {
|
||||
const { data } = await apiRequest.post<{ reminder: Reminder }>(
|
||||
`/api/v1/reminders/secrets/${secretId}`,
|
||||
{
|
||||
message,
|
||||
repeatDays,
|
||||
nextReminderDate,
|
||||
recipients
|
||||
recipients,
|
||||
fromDate
|
||||
}
|
||||
);
|
||||
return data.reminder;
|
||||
|
@@ -2,6 +2,7 @@ export type CreateReminderDTO = {
|
||||
message?: string | null;
|
||||
repeatDays?: number | null;
|
||||
nextReminderDate?: Date | null;
|
||||
fromDate?: Date | null;
|
||||
secretId: string;
|
||||
recipients?: string[];
|
||||
};
|
||||
|
@@ -5,4 +5,5 @@ export type ServerStatus = {
|
||||
secretScanningConfigured: boolean;
|
||||
redisConfigured: boolean;
|
||||
samlDefaultOrgSlug: string;
|
||||
auditLogStorageDisabled: boolean;
|
||||
};
|
||||
|
@@ -6,10 +6,11 @@ import { twMerge } from "tailwind-merge";
|
||||
|
||||
import { CreateOrgModal } from "@app/components/organization/CreateOrgModal";
|
||||
import { Banner } from "@app/components/page-frames/Banner";
|
||||
import { useServerConfig } from "@app/context";
|
||||
import { useServerConfig, useSubscription } from "@app/context";
|
||||
import { usePopUp } from "@app/hooks";
|
||||
import { useFetchServerStatus } from "@app/hooks/api";
|
||||
|
||||
import { AuditLogBanner } from "./components/AuditLogBanner";
|
||||
import { InsecureConnectionBanner } from "./components/InsecureConnectionBanner";
|
||||
import { Navbar } from "./components/NavBar";
|
||||
import { OrgSidebar } from "./components/OrgSidebar";
|
||||
@@ -31,6 +32,7 @@ export const OrganizationLayout = () => {
|
||||
const containerHeight = config.pageFrameContent ? "h-[94vh]" : "h-screen";
|
||||
|
||||
const { data: serverDetails, isLoading } = useFetchServerStatus();
|
||||
const { subscription } = useSubscription();
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -41,6 +43,7 @@ export const OrganizationLayout = () => {
|
||||
<Navbar />
|
||||
{!isLoading && !serverDetails?.redisConfigured && <RedisBanner />}
|
||||
{!isLoading && !serverDetails?.emailConfigured && <SmtpBanner />}
|
||||
{!isLoading && subscription.auditLogs && <AuditLogBanner />}
|
||||
{!window.isSecureContext && <InsecureConnectionBanner />}
|
||||
<div className="flex flex-grow flex-col overflow-y-hidden md:flex-row">
|
||||
<OrgSidebar isHidden={isInsideProject} />
|
||||
|
@@ -0,0 +1,23 @@
|
||||
import { useOrganization } from "@app/context";
|
||||
import { useFetchServerStatus, useGetAuditLogStreams } from "@app/hooks/api";
|
||||
|
||||
import { OrgAlertBanner } from "../OrgAlertBanner";
|
||||
|
||||
export const AuditLogBanner = () => {
|
||||
const org = useOrganization();
|
||||
const { data: status, isLoading: isLoadingStatus } = useFetchServerStatus();
|
||||
const { data: streams, isLoading: isLoadingStreams } = useGetAuditLogStreams(org.currentOrg.id);
|
||||
|
||||
if (isLoadingStreams || isLoadingStatus || !streams) return null;
|
||||
|
||||
if (status?.auditLogStorageDisabled && !streams.length) {
|
||||
return (
|
||||
<OrgAlertBanner
|
||||
text="Attention: Audit logs storage is disabled but no audit log streams have been configured."
|
||||
link="https://infisical.com/docs/documentation/platform/audit-log-streams/audit-log-streams"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
@@ -0,0 +1 @@
|
||||
export * from "./AuditLogBanner";
|
@@ -48,9 +48,16 @@ const formSchema = genericAppConnectionFieldsSchema.extend({
|
||||
app: z.literal(AppConnection.GitHub),
|
||||
method: z.nativeEnum(GitHubConnectionMethod),
|
||||
credentials: z
|
||||
.object({
|
||||
host: z.string().optional()
|
||||
})
|
||||
.union([
|
||||
z.object({
|
||||
instanceType: z.literal("cloud").optional(),
|
||||
host: z.string().optional()
|
||||
}),
|
||||
z.object({
|
||||
instanceType: z.literal("server"),
|
||||
host: z.string().min(1, "Required")
|
||||
})
|
||||
])
|
||||
.optional()
|
||||
});
|
||||
|
||||
@@ -70,7 +77,10 @@ export const GitHubConnectionForm = ({ appConnection }: Props) => {
|
||||
defaultValues: appConnection ?? {
|
||||
app: AppConnection.GitHub,
|
||||
method: GitHubConnectionMethod.App,
|
||||
gatewayId: null
|
||||
gatewayId: null,
|
||||
credentials: {
|
||||
instanceType: "cloud"
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -78,6 +88,7 @@ export const GitHubConnectionForm = ({ appConnection }: Props) => {
|
||||
handleSubmit,
|
||||
control,
|
||||
watch,
|
||||
setValue,
|
||||
formState: { isSubmitting, isDirty }
|
||||
} = form;
|
||||
|
||||
@@ -85,6 +96,7 @@ export const GitHubConnectionForm = ({ appConnection }: Props) => {
|
||||
const { data: gateways, isPending: isGatewaysLoading } = useQuery(gatewaysQueryKeys.list());
|
||||
|
||||
const selectedMethod = watch("method");
|
||||
const instanceType = watch("credentials.instanceType");
|
||||
|
||||
const onSubmit = (formData: FormData) => {
|
||||
setIsRedirecting(true);
|
||||
@@ -103,7 +115,7 @@ export const GitHubConnectionForm = ({ appConnection }: Props) => {
|
||||
switch (formData.method) {
|
||||
case GitHubConnectionMethod.App:
|
||||
window.location.assign(
|
||||
`${githubHost}/apps/${appClientSlug}/installations/new?state=${state}`
|
||||
`${githubHost}/${formData.credentials?.instanceType === "server" ? "github-apps" : "apps"}/${appClientSlug}/installations/new?state=${state}`
|
||||
);
|
||||
break;
|
||||
case GitHubConnectionMethod.OAuth:
|
||||
@@ -175,13 +187,54 @@ export const GitHubConnectionForm = ({ appConnection }: Props) => {
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
{subscription.gateway && (
|
||||
<Accordion type="single" collapsible className="w-full">
|
||||
<AccordionItem value="enterprise-options" className="data-[state=open]:border-none">
|
||||
<AccordionTrigger className="h-fit flex-none pl-1 text-sm">
|
||||
<div className="order-1 ml-3">GitHub Enterprise Options</div>
|
||||
</AccordionTrigger>
|
||||
<AccordionContent childrenClassName="px-0">
|
||||
<Accordion type="single" collapsible className="w-full">
|
||||
<AccordionItem value="enterprise-options" className="data-[state=open]:border-none">
|
||||
<AccordionTrigger className="h-fit flex-none pl-1 text-sm">
|
||||
<div className="order-1 ml-3">GitHub Enterprise Options</div>
|
||||
</AccordionTrigger>
|
||||
<AccordionContent childrenClassName="px-0">
|
||||
<Controller
|
||||
name="credentials.instanceType"
|
||||
control={control}
|
||||
render={({ field }) => (
|
||||
<FormControl label="Instance Type">
|
||||
<Select
|
||||
value={field.value}
|
||||
onValueChange={(e) => {
|
||||
field.onChange(e);
|
||||
if (e === "cloud") {
|
||||
setValue("gatewayId", null);
|
||||
}
|
||||
}}
|
||||
className="w-full border border-mineshaft-500"
|
||||
dropdownContainerClassName="max-w-none"
|
||||
placeholder="Enterprise Cloud"
|
||||
position="popper"
|
||||
>
|
||||
<SelectItem value="cloud">Enterprise Cloud</SelectItem>
|
||||
<SelectItem value="server">Enterprise Server</SelectItem>
|
||||
</Select>
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
|
||||
<Controller
|
||||
name="credentials.host"
|
||||
control={control}
|
||||
shouldUnregister
|
||||
render={({ field, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
errorText={error?.message}
|
||||
isError={Boolean(error?.message)}
|
||||
label="Instance Hostname"
|
||||
isOptional={instanceType === "cloud"}
|
||||
isRequired={instanceType === "server"}
|
||||
>
|
||||
<Input {...field} placeholder="github.com" />
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
{subscription.gateway && instanceType === "server" && (
|
||||
<OrgPermissionCan
|
||||
I={OrgGatewayPermissionActions.AttachGateways}
|
||||
a={OrgPermissionSubjects.Gateway}
|
||||
@@ -190,7 +243,6 @@ export const GitHubConnectionForm = ({ appConnection }: Props) => {
|
||||
<Controller
|
||||
control={control}
|
||||
name="gatewayId"
|
||||
defaultValue=""
|
||||
render={({ field: { value, onChange }, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
isError={Boolean(error?.message)}
|
||||
@@ -204,7 +256,7 @@ export const GitHubConnectionForm = ({ appConnection }: Props) => {
|
||||
<div>
|
||||
<Select
|
||||
isDisabled={!isAllowed}
|
||||
value={value as string}
|
||||
value={value || (null as unknown as string)}
|
||||
onValueChange={onChange}
|
||||
className="w-full border border-mineshaft-500"
|
||||
dropdownContainerClassName="max-w-none"
|
||||
@@ -214,7 +266,7 @@ export const GitHubConnectionForm = ({ appConnection }: Props) => {
|
||||
>
|
||||
<SelectItem
|
||||
value={null as unknown as string}
|
||||
onClick={() => onChange(undefined)}
|
||||
onClick={() => onChange(null)}
|
||||
>
|
||||
Internet Gateway
|
||||
</SelectItem>
|
||||
@@ -231,25 +283,10 @@ export const GitHubConnectionForm = ({ appConnection }: Props) => {
|
||||
/>
|
||||
)}
|
||||
</OrgPermissionCan>
|
||||
<Controller
|
||||
name="credentials.host"
|
||||
control={control}
|
||||
shouldUnregister
|
||||
render={({ field, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
errorText={error?.message}
|
||||
isError={Boolean(error?.message)}
|
||||
label="Hostname"
|
||||
isOptional
|
||||
>
|
||||
<Input {...field} placeholder="github.com" />
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
</Accordion>
|
||||
)}
|
||||
)}
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
</Accordion>
|
||||
<div className="mt-8 flex items-center">
|
||||
<Button
|
||||
className="mr-4"
|
||||
|
@@ -408,6 +408,7 @@ export const OAuthCallbackPage = () => {
|
||||
credentials: {
|
||||
code: code as string,
|
||||
installationId: installationId as string,
|
||||
...(credentials?.instanceType && { instanceType: credentials.instanceType }),
|
||||
...(credentials?.host && { host: credentials.host })
|
||||
},
|
||||
gatewayId
|
||||
@@ -416,6 +417,7 @@ export const OAuthCallbackPage = () => {
|
||||
connectionId,
|
||||
credentials: {
|
||||
code: code as string,
|
||||
...(credentials?.instanceType && { instanceType: credentials.instanceType }),
|
||||
...(credentials?.host && { host: credentials.host })
|
||||
},
|
||||
gatewayId
|
||||
@@ -431,6 +433,7 @@ export const OAuthCallbackPage = () => {
|
||||
method: GitHubConnectionMethod.App,
|
||||
credentials: {
|
||||
code: code as string,
|
||||
...(credentials?.instanceType && { instanceType: credentials.instanceType }),
|
||||
installationId: installationId as string,
|
||||
...(credentials?.host && { host: credentials.host })
|
||||
},
|
||||
@@ -440,6 +443,7 @@ export const OAuthCallbackPage = () => {
|
||||
method: GitHubConnectionMethod.OAuth,
|
||||
credentials: {
|
||||
code: code as string,
|
||||
...(credentials?.instanceType && { instanceType: credentials.instanceType }),
|
||||
...(credentials?.host && { host: credentials.host })
|
||||
},
|
||||
gatewayId
|
||||
|
@@ -12,6 +12,7 @@ export const AuditLogsPage = () => {
|
||||
<link rel="icon" href="/infisical.ico" />
|
||||
<meta property="og:image" content="/images/message.png" />
|
||||
</Helmet>
|
||||
|
||||
<div className="flex h-full w-full justify-center bg-bunker-800 text-white">
|
||||
<div className="w-full max-w-7xl">
|
||||
<PageHeader
|
||||
|
@@ -1,5 +1,5 @@
|
||||
import { Fragment } from "react";
|
||||
import { faFile } from "@fortawesome/free-solid-svg-icons";
|
||||
import { faCancel, faFile } from "@fortawesome/free-solid-svg-icons";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
|
||||
import {
|
||||
@@ -16,7 +16,7 @@ import {
|
||||
Tr
|
||||
} from "@app/components/v2";
|
||||
import { Timezone } from "@app/helpers/datetime";
|
||||
import { useGetAuditLogs } from "@app/hooks/api";
|
||||
import { useFetchServerStatus, useGetAuditLogs } from "@app/hooks/api";
|
||||
import { TGetAuditLogsFilter } from "@app/hooks/api/auditLogs/types";
|
||||
|
||||
import { LogsTableRow } from "./LogsTableRow";
|
||||
@@ -30,6 +30,8 @@ type Props = {
|
||||
const AUDIT_LOG_LIMIT = 30;
|
||||
|
||||
export const LogsTable = ({ filter, refetchInterval, timezone }: Props) => {
|
||||
const { data: status } = useFetchServerStatus();
|
||||
|
||||
// Determine the project ID for filtering
|
||||
const filterProjectId =
|
||||
// Use the projectId from the filter if it exists
|
||||
@@ -79,7 +81,11 @@ export const LogsTable = ({ filter, refetchInterval, timezone }: Props) => {
|
||||
{isEmpty && (
|
||||
<Tr>
|
||||
<Td colSpan={3}>
|
||||
<EmptyState title="No audit logs on file" icon={faFile} />
|
||||
{status?.auditLogStorageDisabled ? (
|
||||
<EmptyState title="Audit log storage is disabled" icon={faCancel} />
|
||||
) : (
|
||||
<EmptyState title="No audit logs on file" icon={faFile} />
|
||||
)}
|
||||
</Td>
|
||||
</Tr>
|
||||
)}
|
||||
|
@@ -1207,16 +1207,13 @@ export const OverviewPage = () => {
|
||||
secretsToDeleteKeys={secretsToDeleteKeys}
|
||||
usedBySecretSyncs={usedBySecretSyncs}
|
||||
/>
|
||||
<div ref={tableRef} className="mt-4">
|
||||
<div ref={tableRef} className="thin-scrollbar mt-4">
|
||||
<TableContainer
|
||||
onScroll={(e) => setScrollOffset(e.currentTarget.scrollLeft)}
|
||||
className="thin-scrollbar max-h-[66vh] overflow-y-auto rounded-b-none"
|
||||
className="thin-scrollbar rounded-b-none"
|
||||
>
|
||||
<Table>
|
||||
<THead
|
||||
className="sticky top-0 z-20"
|
||||
style={{ height: collapseEnvironments ? headerHeight : undefined }}
|
||||
>
|
||||
<THead style={{ height: collapseEnvironments ? headerHeight : undefined }}>
|
||||
<Tr
|
||||
className="sticky top-0 z-20 border-0"
|
||||
style={{ height: collapseEnvironments ? headerHeight : undefined }}
|
||||
@@ -1373,7 +1370,7 @@ export const OverviewPage = () => {
|
||||
})}
|
||||
onClick={() => handleExploreEnvClick(slug)}
|
||||
>
|
||||
<p className="truncate font-medium underline">{name}</p>
|
||||
<p className="truncate font-medium">{name}</p>
|
||||
</button>
|
||||
{!collapseEnvironments && missingKeyCount > 0 && (
|
||||
<Tooltip
|
||||
|
@@ -52,8 +52,15 @@ export const SecretSearchInput = ({
|
||||
if (activeIndex === 0 && e.key === "Enter") setIsOpen(true);
|
||||
}}
|
||||
autoComplete="off"
|
||||
className="input text-md h-[2.3rem] w-full rounded-md rounded-l-none bg-mineshaft-800 py-[0.375rem] pl-2.5 pr-8 text-gray-400 placeholder-mineshaft-50 placeholder-opacity-50 outline-none duration-200 placeholder:text-sm hover:ring-bunker-400/60 focus:bg-mineshaft-700/80 focus:ring-1 focus:ring-primary-400/50"
|
||||
placeholder="Search by secret, folder, tag or metadata..."
|
||||
className={twMerge(
|
||||
"input text-md h-[2.3rem] w-full rounded-md rounded-l-none bg-mineshaft-800 py-[0.375rem] pl-2.5 text-gray-400 placeholder-mineshaft-50 placeholder-opacity-50 outline-none duration-200 placeholder:text-sm hover:ring-bunker-400/60 focus:bg-mineshaft-700/80 focus:ring-1 focus:ring-primary-400/50",
|
||||
hasSearch ? "pr-8" : "pr-2.5"
|
||||
)}
|
||||
placeholder={
|
||||
isSingleEnv
|
||||
? "Search by secret, folder, tag or metadata..."
|
||||
: "Search by secret or folder name..."
|
||||
}
|
||||
value={value}
|
||||
onChange={(e) => onChange(e.target.value)}
|
||||
/>
|
||||
|
@@ -538,16 +538,20 @@ export const SecretApprovalRequestChanges = ({
|
||||
className="flex flex-nowrap items-center justify-between space-x-2 rounded border border-mineshaft-600 bg-mineshaft-800 px-2 py-1"
|
||||
key={`required-approver-${requiredApprover.userId}`}
|
||||
>
|
||||
<div className="flex text-sm">
|
||||
<Tooltip
|
||||
content={`${requiredApprover.firstName || ""} ${
|
||||
requiredApprover.lastName || ""
|
||||
}`}
|
||||
>
|
||||
<span>{requiredApprover?.email}</span>
|
||||
</Tooltip>
|
||||
<span className="text-red">*</span>
|
||||
</div>
|
||||
<Tooltip
|
||||
content={
|
||||
requiredApprover.firstName
|
||||
? `${requiredApprover.firstName || ""} ${requiredApprover.lastName || ""}`
|
||||
: undefined
|
||||
}
|
||||
position="left"
|
||||
sideOffset={10}
|
||||
>
|
||||
<div className="flex text-sm">
|
||||
<div>{requiredApprover?.email}</div>
|
||||
<span className="text-red">*</span>
|
||||
</div>
|
||||
</Tooltip>
|
||||
<div>
|
||||
{reviewer?.comment && (
|
||||
<Tooltip className="max-w-lg break-words" content={reviewer.comment}>
|
||||
|
@@ -4,6 +4,7 @@ import { faClock, faTrash } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { useQueryClient } from "@tanstack/react-query";
|
||||
import { format } from "date-fns";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
import { z } from "zod";
|
||||
|
||||
@@ -33,6 +34,7 @@ const MIN_REPEAT_DAYS = 1;
|
||||
const MAX_REPEAT_DAYS = 365;
|
||||
const DEFAULT_REPEAT_DAYS = 30;
|
||||
const DEFAULT_TEXTAREA_ROWS = 8;
|
||||
const ONE_DAY_IN_MILLIS = 86400000;
|
||||
|
||||
// Enums
|
||||
enum ReminderType {
|
||||
@@ -79,6 +81,11 @@ const ReminderFormSchema = z.object({
|
||||
.refine((data) => data > new Date(), { message: "Reminder date must be in the future" })
|
||||
.nullable()
|
||||
.optional(),
|
||||
fromDate: z.coerce
|
||||
.date()
|
||||
.refine((data) => data > new Date(), { message: "From date must be in the future" })
|
||||
.nullable()
|
||||
.optional(),
|
||||
reminderType: z.enum(["Recurring", "One Time"])
|
||||
});
|
||||
|
||||
@@ -86,7 +93,7 @@ export type TReminderFormSchema = z.infer<typeof ReminderFormSchema>;
|
||||
|
||||
// Custom hook for form state management
|
||||
const useReminderForm = (reminderData?: Reminder) => {
|
||||
const { repeatDays, message, nextReminderDate } = reminderData || {};
|
||||
const { repeatDays, message, nextReminderDate, fromDate } = reminderData || {};
|
||||
|
||||
const isEditMode = Boolean(reminderData);
|
||||
|
||||
@@ -96,9 +103,10 @@ const useReminderForm = (reminderData?: Reminder) => {
|
||||
message: message || "",
|
||||
nextReminderDate: nextReminderDate || null,
|
||||
reminderType: repeatDays ? ReminderType.Recurring : ReminderType.OneTime,
|
||||
recipients: []
|
||||
recipients: [],
|
||||
fromDate
|
||||
}),
|
||||
[repeatDays, message, nextReminderDate]
|
||||
[repeatDays, message, nextReminderDate, fromDate]
|
||||
);
|
||||
|
||||
return {
|
||||
@@ -153,7 +161,8 @@ export const CreateReminderForm = ({
|
||||
message: reminderData?.message || "",
|
||||
nextReminderDate: reminderData?.nextReminderDate || null,
|
||||
reminderType: reminderData?.repeatDays ? ReminderType.Recurring : ReminderType.OneTime,
|
||||
recipients: []
|
||||
recipients: [],
|
||||
fromDate: reminderData?.fromDate
|
||||
},
|
||||
resolver: zodResolver(ReminderFormSchema)
|
||||
});
|
||||
@@ -170,6 +179,7 @@ export const CreateReminderForm = ({
|
||||
|
||||
// Watch form values
|
||||
const reminderType = watch("reminderType");
|
||||
const fromDate = watch("fromDate");
|
||||
|
||||
// Invalidate queries helper
|
||||
const invalidateQueries = () => {
|
||||
@@ -195,7 +205,8 @@ export const CreateReminderForm = ({
|
||||
message: data.message,
|
||||
recipients: data.recipients?.map((r) => r.value) || [],
|
||||
secretId,
|
||||
nextReminderDate: data.nextReminderDate
|
||||
nextReminderDate: data.nextReminderDate,
|
||||
fromDate: data.fromDate
|
||||
});
|
||||
|
||||
invalidateQueries();
|
||||
@@ -243,6 +254,7 @@ export const CreateReminderForm = ({
|
||||
if (newType === ReminderType.Recurring) {
|
||||
setValue("repeatDays", DEFAULT_REPEAT_DAYS);
|
||||
setValue("nextReminderDate", null);
|
||||
setValue("fromDate", null);
|
||||
} else if (newType === ReminderType.OneTime) {
|
||||
const tomorrow = new Date();
|
||||
tomorrow.setDate(tomorrow.getDate() + 1);
|
||||
@@ -259,6 +271,7 @@ export const CreateReminderForm = ({
|
||||
|
||||
const {
|
||||
repeatDays: repeatDaysInitial,
|
||||
fromDate: fromDateInitial,
|
||||
message,
|
||||
recipients,
|
||||
nextReminderDate: nextReminderDateInitial
|
||||
@@ -266,6 +279,7 @@ export const CreateReminderForm = ({
|
||||
|
||||
if (repeatDaysInitial) {
|
||||
setValue("repeatDays", repeatDaysInitial);
|
||||
setValue("fromDate", fromDateInitial);
|
||||
setValue("reminderType", ReminderType.Recurring);
|
||||
} else {
|
||||
setValue("reminderType", ReminderType.OneTime);
|
||||
@@ -323,44 +337,73 @@ export const CreateReminderForm = ({
|
||||
|
||||
{/* Conditional Fields Based on Reminder Type */}
|
||||
{reminderType === ReminderType.Recurring ? (
|
||||
<Controller
|
||||
control={control}
|
||||
name="repeatDays"
|
||||
render={({ field, fieldState }) => (
|
||||
<div>
|
||||
<div className="grid grid-cols-[1fr,auto] gap-x-2">
|
||||
<Controller
|
||||
control={control}
|
||||
name="repeatDays"
|
||||
render={({ field, fieldState }) => (
|
||||
<div>
|
||||
<FormControl
|
||||
isRequired
|
||||
className="mb-0"
|
||||
label="Reminder Interval (in days)"
|
||||
isError={Boolean(fieldState.error)}
|
||||
errorText={fieldState.error?.message || ""}
|
||||
>
|
||||
<Input
|
||||
onChange={(el) => {
|
||||
const value = parseInt(el.target.value, 10);
|
||||
setValue("repeatDays", Number.isNaN(value) ? null : value);
|
||||
}}
|
||||
type="number"
|
||||
placeholder={DEFAULT_REPEAT_DAYS.toString()}
|
||||
value={field.value || ""}
|
||||
min={MIN_REPEAT_DAYS}
|
||||
max={MAX_REPEAT_DAYS}
|
||||
/>
|
||||
</FormControl>
|
||||
{/* Interval description */}
|
||||
<div
|
||||
className={twMerge(
|
||||
"ml-1 mt-2 text-xs",
|
||||
field.value ? "opacity-60" : "opacity-0"
|
||||
)}
|
||||
>
|
||||
A reminder will be sent every{" "}
|
||||
{field.value && field.value > 1 ? `${field.value} days` : "day"}
|
||||
{fromDate ? ` starting from ${format(fromDate, "MM/dd/yy")}` : ""}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
<Controller
|
||||
control={control}
|
||||
name="fromDate"
|
||||
render={({ field, fieldState }) => (
|
||||
<FormControl
|
||||
isRequired
|
||||
className="mb-0"
|
||||
label="Reminder Interval (in days)"
|
||||
label="Start Date"
|
||||
tooltipText="When enabled, this date will be used as the start date for the first reminder"
|
||||
isError={Boolean(fieldState.error)}
|
||||
errorText={fieldState.error?.message || ""}
|
||||
>
|
||||
<Input
|
||||
onChange={(el) => {
|
||||
const value = parseInt(el.target.value, 10);
|
||||
setValue("repeatDays", Number.isNaN(value) ? null : value);
|
||||
<DatePicker
|
||||
value={field.value || undefined}
|
||||
className="w-full"
|
||||
onChange={field.onChange}
|
||||
dateFormat="P"
|
||||
popUpProps={{
|
||||
open: isDatePickerOpen,
|
||||
onOpenChange: setIsDatePickerOpen
|
||||
}}
|
||||
type="number"
|
||||
placeholder={DEFAULT_REPEAT_DAYS.toString()}
|
||||
value={field.value || ""}
|
||||
min={MIN_REPEAT_DAYS}
|
||||
max={MAX_REPEAT_DAYS}
|
||||
popUpContentProps={{}}
|
||||
hideTime
|
||||
hidden={{ before: new Date(Date.now() + ONE_DAY_IN_MILLIS) }}
|
||||
/>
|
||||
</FormControl>
|
||||
|
||||
{/* Interval description */}
|
||||
<div
|
||||
className={twMerge(
|
||||
"ml-1 mt-2 text-xs",
|
||||
field.value ? "opacity-60" : "opacity-0"
|
||||
)}
|
||||
>
|
||||
A reminder will be sent every{" "}
|
||||
{field.value && field.value > 1 ? `${field.value} days` : "day"}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<Controller
|
||||
control={control}
|
||||
@@ -385,7 +428,7 @@ export const CreateReminderForm = ({
|
||||
}}
|
||||
popUpContentProps={{}}
|
||||
hideTime
|
||||
hidden={{ before: new Date(Date.now() + 86400000) }}
|
||||
hidden={{ before: new Date(Date.now() + ONE_DAY_IN_MILLIS) }}
|
||||
/>
|
||||
</FormControl>
|
||||
</div>
|
||||
|
@@ -13,6 +13,8 @@ type GenericInfisicalAuthentication struct {
|
||||
GcpIdTokenAuth GenericGcpIdTokenAuth `json:"gcpIdTokenAuth,omitempty"`
|
||||
// +kubebuilder:validation:Optional
|
||||
GcpIamAuth GenericGcpIamAuth `json:"gcpIamAuth,omitempty"`
|
||||
// +kubebuilder:validation:Optional
|
||||
LdapAuth GenericLdapAuth `json:"ldapAuth,omitempty"`
|
||||
}
|
||||
|
||||
type GenericUniversalAuth struct {
|
||||
@@ -20,6 +22,13 @@ type GenericUniversalAuth struct {
|
||||
CredentialsRef KubeSecretReference `json:"credentialsRef"`
|
||||
}
|
||||
|
||||
type GenericLdapAuth struct {
|
||||
// +kubebuilder:validation:Required
|
||||
IdentityID string `json:"identityId"`
|
||||
// +kubebuilder:validation:Required
|
||||
CredentialsRef KubeSecretReference `json:"credentialsRef"`
|
||||
}
|
||||
|
||||
type GenericAwsIamAuth struct {
|
||||
// +kubebuilder:validation:Required
|
||||
IdentityID string `json:"identityId"`
|
||||
|
@@ -21,6 +21,8 @@ type Authentication struct {
|
||||
GcpIdTokenAuth GCPIdTokenAuthDetails `json:"gcpIdTokenAuth"`
|
||||
// +kubebuilder:validation:Optional
|
||||
GcpIamAuth GcpIamAuthDetails `json:"gcpIamAuth"`
|
||||
// +kubebuilder:validation:Optional
|
||||
LdapAuth LdapAuthDetails `json:"ldapAuth"`
|
||||
}
|
||||
|
||||
type UniversalAuthDetails struct {
|
||||
@@ -30,6 +32,15 @@ type UniversalAuthDetails struct {
|
||||
SecretsScope MachineIdentityScopeInWorkspace `json:"secretsScope"`
|
||||
}
|
||||
|
||||
type LdapAuthDetails struct {
|
||||
// +kubebuilder:validation:Required
|
||||
IdentityID string `json:"identityId"`
|
||||
// +kubebuilder:validation:Required
|
||||
CredentialsRef KubeSecretReference `json:"credentialsRef"`
|
||||
// +kubebuilder:validation:Required
|
||||
SecretsScope MachineIdentityScopeInWorkspace `json:"secretsScope"`
|
||||
}
|
||||
|
||||
type KubernetesAuthDetails struct {
|
||||
// +kubebuilder:validation:Required
|
||||
IdentityID string `json:"identityId"`
|
||||
|
@@ -53,6 +53,7 @@ func (in *Authentication) DeepCopyInto(out *Authentication) {
|
||||
out.AzureAuth = in.AzureAuth
|
||||
out.GcpIdTokenAuth = in.GcpIdTokenAuth
|
||||
out.GcpIamAuth = in.GcpIamAuth
|
||||
out.LdapAuth = in.LdapAuth
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Authentication.
|
||||
@@ -326,6 +327,7 @@ func (in *GenericInfisicalAuthentication) DeepCopyInto(out *GenericInfisicalAuth
|
||||
out.AzureAuth = in.AzureAuth
|
||||
out.GcpIdTokenAuth = in.GcpIdTokenAuth
|
||||
out.GcpIamAuth = in.GcpIamAuth
|
||||
out.LdapAuth = in.LdapAuth
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GenericInfisicalAuthentication.
|
||||
@@ -359,6 +361,22 @@ func (in *GenericKubernetesAuth) DeepCopy() *GenericKubernetesAuth {
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *GenericLdapAuth) DeepCopyInto(out *GenericLdapAuth) {
|
||||
*out = *in
|
||||
out.CredentialsRef = in.CredentialsRef
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GenericLdapAuth.
|
||||
func (in *GenericLdapAuth) DeepCopy() *GenericLdapAuth {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(GenericLdapAuth)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *GenericUniversalAuth) DeepCopyInto(out *GenericUniversalAuth) {
|
||||
*out = *in
|
||||
@@ -810,6 +828,23 @@ func (in *KubernetesServiceAccountRef) DeepCopy() *KubernetesServiceAccountRef {
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *LdapAuthDetails) DeepCopyInto(out *LdapAuthDetails) {
|
||||
*out = *in
|
||||
out.CredentialsRef = in.CredentialsRef
|
||||
out.SecretsScope = in.SecretsScope
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LdapAuthDetails.
|
||||
func (in *LdapAuthDetails) DeepCopy() *LdapAuthDetails {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(LdapAuthDetails)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *MachineIdentityScopeInWorkspace) DeepCopyInto(out *MachineIdentityScopeInWorkspace) {
|
||||
*out = *in
|
||||
|
@@ -103,6 +103,27 @@ spec:
|
||||
- identityId
|
||||
- serviceAccountRef
|
||||
type: object
|
||||
ldapAuth:
|
||||
properties:
|
||||
credentialsRef:
|
||||
properties:
|
||||
secretName:
|
||||
description: The name of the Kubernetes Secret
|
||||
type: string
|
||||
secretNamespace:
|
||||
description: The name space where the Kubernetes Secret
|
||||
is located
|
||||
type: string
|
||||
required:
|
||||
- secretName
|
||||
- secretNamespace
|
||||
type: object
|
||||
identityId:
|
||||
type: string
|
||||
required:
|
||||
- credentialsRef
|
||||
- identityId
|
||||
type: object
|
||||
universalAuth:
|
||||
properties:
|
||||
credentialsRef:
|
||||
|
@@ -103,6 +103,27 @@ spec:
|
||||
- identityId
|
||||
- serviceAccountRef
|
||||
type: object
|
||||
ldapAuth:
|
||||
properties:
|
||||
credentialsRef:
|
||||
properties:
|
||||
secretName:
|
||||
description: The name of the Kubernetes Secret
|
||||
type: string
|
||||
secretNamespace:
|
||||
description: The name space where the Kubernetes Secret
|
||||
is located
|
||||
type: string
|
||||
required:
|
||||
- secretName
|
||||
- secretNamespace
|
||||
type: object
|
||||
identityId:
|
||||
type: string
|
||||
required:
|
||||
- credentialsRef
|
||||
- identityId
|
||||
type: object
|
||||
universalAuth:
|
||||
properties:
|
||||
credentialsRef:
|
||||
|
@@ -181,6 +181,43 @@ spec:
|
||||
- secretsScope
|
||||
- serviceAccountRef
|
||||
type: object
|
||||
ldapAuth:
|
||||
properties:
|
||||
credentialsRef:
|
||||
properties:
|
||||
secretName:
|
||||
description: The name of the Kubernetes Secret
|
||||
type: string
|
||||
secretNamespace:
|
||||
description: The name space where the Kubernetes Secret
|
||||
is located
|
||||
type: string
|
||||
required:
|
||||
- secretName
|
||||
- secretNamespace
|
||||
type: object
|
||||
identityId:
|
||||
type: string
|
||||
secretsScope:
|
||||
properties:
|
||||
envSlug:
|
||||
type: string
|
||||
projectSlug:
|
||||
type: string
|
||||
recursive:
|
||||
type: boolean
|
||||
secretsPath:
|
||||
type: string
|
||||
required:
|
||||
- envSlug
|
||||
- projectSlug
|
||||
- secretsPath
|
||||
type: object
|
||||
required:
|
||||
- credentialsRef
|
||||
- identityId
|
||||
- secretsScope
|
||||
type: object
|
||||
serviceAccount:
|
||||
properties:
|
||||
environmentName:
|
||||
|
@@ -65,6 +65,19 @@ spec:
|
||||
secretsPath: "/path"
|
||||
recursive: true
|
||||
|
||||
ldapAuth:
|
||||
identityId: <machine-identity-id>
|
||||
credentialsRef:
|
||||
secretName: <secret-name> # ldap-auth-credentials
|
||||
secretNamespace: <secret-namespace> # default
|
||||
|
||||
# secretsScope is identical to the secrets scope in the universalAuth field in this sample.
|
||||
secretsScope:
|
||||
projectSlug: your-project-slug
|
||||
envSlug: prod
|
||||
secretsPath: "/path"
|
||||
recursive: true
|
||||
|
||||
# Azure Auth
|
||||
azureAuth:
|
||||
identityId: <your-machine-identity-id>
|
||||
|
8
k8-operator/config/samples/ldapAuthIdentitySecret.yaml
Normal file
8
k8-operator/config/samples/ldapAuthIdentitySecret.yaml
Normal file
@@ -0,0 +1,8 @@
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: ldap-auth-credentials
|
||||
type: Opaque
|
||||
stringData:
|
||||
username: <ldap-username>
|
||||
password: <ldap-password>
|
@@ -85,6 +85,7 @@ func (r *InfisicalDynamicSecretReconciler) handleAuthentication(ctx context.Cont
|
||||
util.AuthStrategy.AZURE_MACHINE_IDENTITY: util.HandleAzureAuth,
|
||||
util.AuthStrategy.GCP_ID_TOKEN_MACHINE_IDENTITY: util.HandleGcpIdTokenAuth,
|
||||
util.AuthStrategy.GCP_IAM_MACHINE_IDENTITY: util.HandleGcpIamAuth,
|
||||
util.AuthStrategy.LDAP_MACHINE_IDENTITY: util.HandleLdapAuth,
|
||||
}
|
||||
|
||||
for authStrategy, authHandler := range authStrategies {
|
||||
|
@@ -32,6 +32,7 @@ func (r *InfisicalPushSecretReconciler) handleAuthentication(ctx context.Context
|
||||
util.AuthStrategy.AZURE_MACHINE_IDENTITY: util.HandleAzureAuth,
|
||||
util.AuthStrategy.GCP_ID_TOKEN_MACHINE_IDENTITY: util.HandleGcpIdTokenAuth,
|
||||
util.AuthStrategy.GCP_IAM_MACHINE_IDENTITY: util.HandleGcpIamAuth,
|
||||
util.AuthStrategy.LDAP_MACHINE_IDENTITY: util.HandleLdapAuth,
|
||||
}
|
||||
|
||||
for authStrategy, authHandler := range authStrategies {
|
||||
|
@@ -57,6 +57,7 @@ func (r *InfisicalSecretReconciler) handleAuthentication(ctx context.Context, in
|
||||
util.AuthStrategy.AZURE_MACHINE_IDENTITY: util.HandleAzureAuth,
|
||||
util.AuthStrategy.GCP_ID_TOKEN_MACHINE_IDENTITY: util.HandleGcpIdTokenAuth,
|
||||
util.AuthStrategy.GCP_IAM_MACHINE_IDENTITY: util.HandleGcpIamAuth,
|
||||
util.AuthStrategy.LDAP_MACHINE_IDENTITY: util.HandleLdapAuth,
|
||||
}
|
||||
|
||||
for authStrategy, authHandler := range authStrategies {
|
||||
|
@@ -5,7 +5,7 @@ go 1.21
|
||||
require (
|
||||
github.com/Masterminds/sprig/v3 v3.3.0
|
||||
github.com/aws/smithy-go v1.20.3
|
||||
github.com/infisical/go-sdk v0.5.97
|
||||
github.com/infisical/go-sdk v0.5.99
|
||||
github.com/lestrrat-go/jwx/v2 v2.1.4
|
||||
github.com/onsi/ginkgo/v2 v2.6.0
|
||||
github.com/onsi/gomega v1.24.1
|
||||
@@ -40,6 +40,7 @@ require (
|
||||
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
github.com/goccy/go-json v0.10.3 // indirect
|
||||
github.com/gofrs/flock v0.8.1 // indirect
|
||||
github.com/google/s2a-go v0.1.7 // indirect
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect
|
||||
github.com/googleapis/gax-go/v2 v2.12.5 // indirect
|
||||
@@ -52,9 +53,12 @@ require (
|
||||
github.com/lestrrat-go/option v1.0.1 // indirect
|
||||
github.com/mitchellh/copystructure v1.2.0 // indirect
|
||||
github.com/mitchellh/reflectwalk v1.0.2 // indirect
|
||||
github.com/oracle/oci-go-sdk/v65 v65.95.2 // indirect
|
||||
github.com/segmentio/asm v1.2.0 // indirect
|
||||
github.com/shopspring/decimal v1.4.0 // indirect
|
||||
github.com/sony/gobreaker v0.5.0 // indirect
|
||||
github.com/spf13/cast v1.7.0 // indirect
|
||||
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect
|
||||
go.opencensus.io v0.24.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.53.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0 // indirect
|
||||
|
@@ -152,6 +152,8 @@ github.com/go-resty/resty/v2 v2.13.1/go.mod h1:GznXlLxkq6Nh4sU59rPmUw3VtgpO3aS96
|
||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||
github.com/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA=
|
||||
github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
|
||||
github.com/gofrs/flock v0.8.1 h1:+gYjHKf32LDeiEEFhQaotPbLuUXjY5ZqxKgXy7n59aw=
|
||||
github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU=
|
||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||
@@ -237,6 +239,8 @@ github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU=
|
||||
github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
|
||||
github.com/infisical/go-sdk v0.5.97 h1:veOi6Hduda6emtwjdUI5SBg2qd2iDQc5xLKqZ15KSoM=
|
||||
github.com/infisical/go-sdk v0.5.97/go.mod h1:ExjqFLRz7LSpZpGluqDLvFl6dFBLq5LKyLW7GBaMAIs=
|
||||
github.com/infisical/go-sdk v0.5.99 h1:trvn7JhKYuSzDkc44h+yqToVjclkrRyP42t315k5kEE=
|
||||
github.com/infisical/go-sdk v0.5.99/go.mod h1:j2D2a5WPNdKXDfHO+3y/TNyLWh5Aq9QYS7EcGI96LZI=
|
||||
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
||||
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
|
||||
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
||||
@@ -303,6 +307,8 @@ github.com/onsi/ginkgo/v2 v2.6.0 h1:9t9b9vRUbFq3C4qKFCGkVuq/fIHji802N1nrtkh1mNc=
|
||||
github.com/onsi/ginkgo/v2 v2.6.0/go.mod h1:63DOGlLAH8+REH8jUGdL3YpCpu7JODesutUjdENfUAc=
|
||||
github.com/onsi/gomega v1.24.1 h1:KORJXNNTzJXzu4ScJWssJfJMnJ+2QJqhoQSRwNlze9E=
|
||||
github.com/onsi/gomega v1.24.1/go.mod h1:3AOiACssS3/MajrniINInwbfOOtfZvplPzuRSmvt1jM=
|
||||
github.com/oracle/oci-go-sdk/v65 v65.95.2 h1:0HJ0AgpLydp/DtvYrF2d4str2BjXOVAeNbuW7E07g94=
|
||||
github.com/oracle/oci-go-sdk/v65 v65.95.2/go.mod h1:u6XRPsw9tPziBh76K7GrrRXPa8P8W3BQeqJ6ZZt9VLA=
|
||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
@@ -347,6 +353,8 @@ github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+D
|
||||
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
|
||||
github.com/sony/gobreaker v0.5.0 h1:dRCvqm0P490vZPmy7ppEk2qCnCieBooFJ+YoXGYB+yg=
|
||||
github.com/sony/gobreaker v0.5.0/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY=
|
||||
github.com/spf13/cast v1.7.0 h1:ntdiHjuueXFgm5nzDRdOS4yfT43P5Fnud6DH50rz/7w=
|
||||
github.com/spf13/cast v1.7.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
|
||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||
@@ -365,8 +373,11 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 h1:ilQV1hzziu+LLM3zUTJ0trRztfwgjqKnBWNtSRkbmwM=
|
||||
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78/go.mod h1:aL8wCCfTfSfmXjznFBSZNN13rSJjlIOI1fUNAtF7rmI=
|
||||
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
@@ -407,6 +418,7 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
|
||||
golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M=
|
||||
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
|
||||
golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc=
|
||||
golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc=
|
||||
@@ -551,6 +563,7 @@ golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
|
||||
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
@@ -559,6 +572,7 @@ golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuX
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
||||
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
|
||||
golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk=
|
||||
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
|
||||
golang.org/x/term v0.28.0 h1:/Ts8HFuMR2E6IP/jlo7QVLZHggjKQbhu/7H0LJFr3Gg=
|
||||
golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek=
|
||||
|
@@ -6,11 +6,16 @@ type ServiceAccountDetails struct {
|
||||
PrivateKey string
|
||||
}
|
||||
|
||||
type MachineIdentityDetails struct {
|
||||
type UniversalAuthIdentityDetails struct {
|
||||
ClientId string
|
||||
ClientSecret string
|
||||
}
|
||||
|
||||
type LdapIdentityDetails struct {
|
||||
Username string
|
||||
Password string
|
||||
}
|
||||
|
||||
type SingleEnvironmentVariable struct {
|
||||
Key string `json:"key"`
|
||||
Value string `json:"value"`
|
||||
|
@@ -88,6 +88,7 @@ var AuthStrategy = struct {
|
||||
AZURE_MACHINE_IDENTITY AuthStrategyType
|
||||
GCP_ID_TOKEN_MACHINE_IDENTITY AuthStrategyType
|
||||
GCP_IAM_MACHINE_IDENTITY AuthStrategyType
|
||||
LDAP_MACHINE_IDENTITY AuthStrategyType
|
||||
}{
|
||||
SERVICE_TOKEN: "SERVICE_TOKEN",
|
||||
SERVICE_ACCOUNT: "SERVICE_ACCOUNT",
|
||||
@@ -97,6 +98,7 @@ var AuthStrategy = struct {
|
||||
AZURE_MACHINE_IDENTITY: "AZURE_MACHINE_IDENTITY",
|
||||
GCP_ID_TOKEN_MACHINE_IDENTITY: "GCP_ID_TOKEN_MACHINE_IDENTITY",
|
||||
GCP_IAM_MACHINE_IDENTITY: "GCP_IAM_MACHINE_IDENTITY",
|
||||
LDAP_MACHINE_IDENTITY: "LDAP_MACHINE_IDENTITY",
|
||||
}
|
||||
|
||||
type SecretCrdType string
|
||||
@@ -188,6 +190,71 @@ func HandleUniversalAuth(ctx context.Context, reconcilerClient client.Client, se
|
||||
}, nil
|
||||
}
|
||||
|
||||
func HandleLdapAuth(ctx context.Context, reconcilerClient client.Client, secretCrd SecretAuthInput, infisicalClient infisicalSdk.InfisicalClientInterface) (AuthenticationDetails, error) {
|
||||
|
||||
var ldapAuthSpec v1alpha1.LdapAuthDetails
|
||||
|
||||
switch secretCrd.Type {
|
||||
case SecretCrd.INFISICAL_SECRET:
|
||||
infisicalSecret, ok := secretCrd.Secret.(v1alpha1.InfisicalSecret)
|
||||
|
||||
if !ok {
|
||||
return AuthenticationDetails{}, errors.New("unable to cast secret to InfisicalSecret")
|
||||
}
|
||||
ldapAuthSpec = infisicalSecret.Spec.Authentication.LdapAuth
|
||||
case SecretCrd.INFISICAL_PUSH_SECRET:
|
||||
infisicalPushSecret, ok := secretCrd.Secret.(v1alpha1.InfisicalPushSecret)
|
||||
|
||||
if !ok {
|
||||
return AuthenticationDetails{}, errors.New("unable to cast secret to InfisicalPushSecret")
|
||||
}
|
||||
|
||||
ldapAuthSpec = v1alpha1.LdapAuthDetails{
|
||||
CredentialsRef: infisicalPushSecret.Spec.Authentication.LdapAuth.CredentialsRef,
|
||||
SecretsScope: v1alpha1.MachineIdentityScopeInWorkspace{},
|
||||
IdentityID: infisicalPushSecret.Spec.Authentication.LdapAuth.IdentityID,
|
||||
}
|
||||
|
||||
case SecretCrd.INFISICAL_DYNAMIC_SECRET:
|
||||
infisicalDynamicSecret, ok := secretCrd.Secret.(v1alpha1.InfisicalDynamicSecret)
|
||||
|
||||
if !ok {
|
||||
return AuthenticationDetails{}, errors.New("unable to cast secret to InfisicalDynamicSecret")
|
||||
}
|
||||
|
||||
ldapAuthSpec = v1alpha1.LdapAuthDetails{
|
||||
CredentialsRef: infisicalDynamicSecret.Spec.Authentication.LdapAuth.CredentialsRef,
|
||||
SecretsScope: v1alpha1.MachineIdentityScopeInWorkspace{},
|
||||
IdentityID: infisicalDynamicSecret.Spec.Authentication.LdapAuth.IdentityID,
|
||||
}
|
||||
}
|
||||
|
||||
ldapAuthKubeSecret, err := GetInfisicalLdapAuthFromKubeSecret(ctx, reconcilerClient, v1alpha1.KubeSecretReference{
|
||||
SecretNamespace: ldapAuthSpec.CredentialsRef.SecretNamespace,
|
||||
SecretName: ldapAuthSpec.CredentialsRef.SecretName,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return AuthenticationDetails{}, fmt.Errorf("ReconcileInfisicalSecret: unable to get machine identity creds from kube secret [err=%s]", err)
|
||||
}
|
||||
|
||||
if ldapAuthKubeSecret.Username == "" && ldapAuthKubeSecret.Password == "" {
|
||||
return AuthenticationDetails{}, ErrAuthNotApplicable
|
||||
}
|
||||
|
||||
_, err = infisicalClient.Auth().LdapAuthLogin(ldapAuthSpec.IdentityID, ldapAuthKubeSecret.Username, ldapAuthKubeSecret.Password)
|
||||
if err != nil {
|
||||
return AuthenticationDetails{}, fmt.Errorf("unable to login with machine identity credentials [err=%s]", err)
|
||||
}
|
||||
|
||||
return AuthenticationDetails{
|
||||
AuthStrategy: AuthStrategy.LDAP_MACHINE_IDENTITY,
|
||||
MachineIdentityScope: ldapAuthSpec.SecretsScope,
|
||||
IsMachineIdentityAuth: true,
|
||||
SecretType: secretCrd.Type,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func HandleKubernetesAuth(ctx context.Context, reconcilerClient client.Client, secretCrd SecretAuthInput, infisicalClient infisicalSdk.InfisicalClientInterface) (AuthenticationDetails, error) {
|
||||
var kubernetesAuthSpec v1alpha1.KubernetesAuthDetails
|
||||
|
||||
|
@@ -18,6 +18,9 @@ import (
|
||||
const INFISICAL_MACHINE_IDENTITY_CLIENT_ID = "clientId"
|
||||
const INFISICAL_MACHINE_IDENTITY_CLIENT_SECRET = "clientSecret"
|
||||
|
||||
const INFISICAL_MACHINE_IDENTITY_LDAP_USERNAME = "username"
|
||||
const INFISICAL_MACHINE_IDENTITY_LDAP_PASSWORD = "password"
|
||||
|
||||
func GetKubeSecretByNamespacedName(ctx context.Context, reconcilerClient client.Client, namespacedName types.NamespacedName) (*corev1.Secret, error) {
|
||||
kubeSecret := &corev1.Secret{}
|
||||
err := reconcilerClient.Get(ctx, namespacedName, kubeSecret)
|
||||
@@ -38,7 +41,7 @@ func GetKubeConfigMapByNamespacedName(ctx context.Context, reconcilerClient clie
|
||||
return kubeConfigMap, err
|
||||
}
|
||||
|
||||
func GetInfisicalUniversalAuthFromKubeSecret(ctx context.Context, reconcilerClient client.Client, universalAuthRef v1alpha1.KubeSecretReference) (machineIdentityDetails model.MachineIdentityDetails, err error) {
|
||||
func GetInfisicalUniversalAuthFromKubeSecret(ctx context.Context, reconcilerClient client.Client, universalAuthRef v1alpha1.KubeSecretReference) (machineIdentityDetails model.UniversalAuthIdentityDetails, err error) {
|
||||
|
||||
universalAuthCredsFromKubeSecret, err := GetKubeSecretByNamespacedName(ctx, reconcilerClient, types.NamespacedName{
|
||||
Namespace: universalAuthRef.SecretNamespace,
|
||||
@@ -48,17 +51,39 @@ func GetInfisicalUniversalAuthFromKubeSecret(ctx context.Context, reconcilerClie
|
||||
})
|
||||
|
||||
if k8Errors.IsNotFound(err) {
|
||||
return model.MachineIdentityDetails{}, nil
|
||||
return model.UniversalAuthIdentityDetails{}, nil
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return model.MachineIdentityDetails{}, fmt.Errorf("something went wrong when fetching your machine identity credentials [err=%s]", err)
|
||||
return model.UniversalAuthIdentityDetails{}, fmt.Errorf("something went wrong when fetching your machine identity credentials [err=%s]", err)
|
||||
}
|
||||
|
||||
clientIdFromSecret := universalAuthCredsFromKubeSecret.Data[INFISICAL_MACHINE_IDENTITY_CLIENT_ID]
|
||||
clientSecretFromSecret := universalAuthCredsFromKubeSecret.Data[INFISICAL_MACHINE_IDENTITY_CLIENT_SECRET]
|
||||
|
||||
return model.MachineIdentityDetails{ClientId: string(clientIdFromSecret), ClientSecret: string(clientSecretFromSecret)}, nil
|
||||
return model.UniversalAuthIdentityDetails{ClientId: string(clientIdFromSecret), ClientSecret: string(clientSecretFromSecret)}, nil
|
||||
|
||||
}
|
||||
|
||||
func GetInfisicalLdapAuthFromKubeSecret(ctx context.Context, reconcilerClient client.Client, ldapAuthRef v1alpha1.KubeSecretReference) (machineIdentityDetails model.LdapIdentityDetails, err error) {
|
||||
|
||||
ldapAuthCredsFromKubeSecret, err := GetKubeSecretByNamespacedName(ctx, reconcilerClient, types.NamespacedName{
|
||||
Namespace: ldapAuthRef.SecretNamespace,
|
||||
Name: ldapAuthRef.SecretName,
|
||||
})
|
||||
|
||||
if k8Errors.IsNotFound(err) {
|
||||
return model.LdapIdentityDetails{}, nil
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return model.LdapIdentityDetails{}, fmt.Errorf("something went wrong when fetching your machine identity credentials [err=%s]", err)
|
||||
}
|
||||
|
||||
usernameFromSecret := ldapAuthCredsFromKubeSecret.Data[INFISICAL_MACHINE_IDENTITY_LDAP_USERNAME]
|
||||
passwordFromSecret := ldapAuthCredsFromKubeSecret.Data[INFISICAL_MACHINE_IDENTITY_LDAP_PASSWORD]
|
||||
|
||||
return model.LdapIdentityDetails{Username: string(usernameFromSecret), Password: string(passwordFromSecret)}, nil
|
||||
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user