Compare commits

..

54 Commits

Author SHA1 Message Date
Maidul Islam
9269b63943 Merge pull request #3248 from kanad13/patch-2
Grammar fixes to local-development.mdx
2025-03-15 12:09:07 -04:00
Maidul Islam
8f96653273 Merge pull request #3247 from Infisical/address-saml-cve
Upgrade passport/saml to 5.0
2025-03-15 12:07:33 -04:00
=
7dffc08eba feat: resolved type error 2025-03-15 21:32:52 +05:30
Kunal Pathak
126b0ce7e7 Grammar fixes to local-development.mdx 2025-03-15 13:15:40 +01:00
=
0b71f7f297 fix: resolved idpCert rename 2025-03-15 12:57:45 +05:30
Maidul Islam
c86e508817 upgrade to saml 5.0 2025-03-14 21:17:57 -04:00
Maidul Islam
6426b85c1e Upgrade passport/saml to 5.0
This addresses the breaking changes in 5.0 listed here https://github.com/node-saml/node-saml/blob/v5.0.0/CHANGELOG.md#-major-changes

Todo: test with existing saml workflow
2025-03-14 21:16:42 -04:00
Maidul Islam
3d6da1e548 Merge pull request #3245 from Infisical/revert-3244-fix-saml-cve
Revert "Address SAML CVE"
2025-03-14 17:58:06 -04:00
Maidul Islam
7e46fe8148 Revert "Address SAML CVE" 2025-03-14 17:57:48 -04:00
Maidul Islam
3756a1901d Merge pull request #3244 from Infisical/fix-saml-cve
Main
2025-03-14 16:35:35 -04:00
Maidul Islam
9c8adf75ec Main
Address SAML CVE in https://workos.com/blog/samlstorm
2025-03-14 16:35:23 -04:00
carlosmonastyrski
f461eaa432 Merge pull request #3221 from Infisical/feat/allowShareToAnyoneEdition
Feat/allow share to anyone edition
2025-03-14 17:06:49 -03:00
carlosmonastyrski
a1fbc140ee Merge pull request #3235 from Infisical/feat/addHumanitecIntegration
Add Humanitec secret sync integration
2025-03-14 16:55:53 -03:00
carlosmonastyrski
ea27870ce3 Move useOrganization outside ShareSecretForm as it's used on a public page 2025-03-14 16:12:40 -03:00
Daniel Hougaard
5ebf142e3e Merge pull request #3239 from Infisical/daniel/k8s-config-map
feat(k8s): configmap support
2025-03-14 20:01:52 +04:00
carlosmonastyrski
4f4764dfcd Fix rebase issue with deleted files 2025-03-14 08:54:14 -03:00
Daniel Hougaard
bdceea4c91 requested changes 2025-03-14 06:59:04 +04:00
Daniel Hougaard
32fa6866e4 Merge pull request #3238 from Infisical/feat/ENG-2320-echo-environment-being-used-in-cli
feat: confirm environment exists when running `run` command
2025-03-14 03:58:05 +04:00
Daniel Hougaard
b4faef797c fix: address comment 2025-03-14 03:47:25 +04:00
Mahyar Mirrashed
08732cab62 refactor(projects): move rest api call directly into run command module 2025-03-13 16:36:41 -07:00
Mahyar Mirrashed
81d5f639ae revert: "refactor: clean smelly code"
This reverts commit c04b97c689.
2025-03-13 16:33:26 -07:00
Daniel Hougaard
25b83d4b86 docs: fix formatting 2025-03-14 02:45:59 +04:00
Mahyar Mirrashed
a500f00a49 fix(run): compare environment slug to environment slug 2025-03-13 13:21:12 -07:00
Daniel Hougaard
6842f7aa8b docs(k8s): config map support 2025-03-13 23:44:32 +04:00
Mahyar Mirrashed
ad207786e2 refactor: clean up empty line 2025-03-13 12:18:54 -07:00
Daniel Hougaard
ace8c37c25 docs: fix formatting 2025-03-13 23:11:50 +04:00
Mahyar Mirrashed
4c82408b51 fix(run): grap workspace id from workspace file if not defined on the cli 2025-03-13 11:43:00 -07:00
Mahyar Mirrashed
8146dcef16 refactor(run): call it project instead of workspace 2025-03-13 11:43:00 -07:00
Mahyar Mirrashed
2e90addbc5 refactor(run): do not report project id in error message 2025-03-13 11:43:00 -07:00
Mahyar Mirrashed
427201a634 refactor(run): set up variable before call 2025-03-13 11:43:00 -07:00
Mahyar Mirrashed
0b55ac141c refactor(projects): rename workspace to project 2025-03-13 11:43:00 -07:00
Mahyar Mirrashed
aecfa268ae fix(run): handle case where we require a login 2025-03-13 11:43:00 -07:00
Mahyar Mirrashed
fdfc020efc refactor: clean up more smelly code 2025-03-13 11:43:00 -07:00
Mahyar Mirrashed
62aa80a104 feat(run): ensure that the project has the requested environment 2025-03-13 11:43:00 -07:00
Mahyar Mirrashed
cf9d8035bd feat(run): add function to confirm project has the requested environment 2025-03-13 11:43:00 -07:00
Mahyar Mirrashed
d0c9f1ca53 feat(projects): add new module in util package for getting project details 2025-03-13 11:43:00 -07:00
Mahyar Mirrashed
2ecc7424d9 feat(models): add model for environments 2025-03-13 11:43:00 -07:00
Mahyar Mirrashed
c04b97c689 refactor: clean smelly code 2025-03-13 11:43:00 -07:00
Mahyar Mirrashed
7600a86dfc fix(nix): set gopath for usage by IDEs 2025-03-13 11:43:00 -07:00
Mahyar Mirrashed
8924eaf251 chore: ignore direnv folder 2025-03-13 11:43:00 -07:00
Mahyar Mirrashed
82e9504285 chore: ignore .idea and .go folders 2025-03-13 11:43:00 -07:00
Mahyar Mirrashed
c4e10df754 fix(nix): set the goroot for tools like jetbrains
JetBrains needs to know the GOROOT environment variables. For the sake
of other tooling, we will just set these in the flake rather than only
in the `.envrc` file. It also keeps all environment configuration
localized to our project flake.
2025-03-13 11:43:00 -07:00
Mahyar Mirrashed
ce60e96008 chore(nix): add golang dependency 2025-03-13 11:43:00 -07:00
Daniel Hougaard
930b59cb4f chore: helm 2025-03-13 20:20:43 +04:00
Daniel Hougaard
ec363a5ad4 feat(infisicalsecret-crd): added configmap support 2025-03-13 20:20:43 +04:00
carlosmonastyrski
c0de4ae3ee Add secret share permissions 2025-03-13 12:44:38 -03:00
Akhil Mohan
de7e92ccfc Merge pull request #3236 from akhilmhdh/fix/renew-token
Resolved renew token not renewing
2025-03-13 20:12:26 +05:30
Akhil Mohan
522d81ae1a Merge pull request #3237 from akhilmhdh/feat/metadata-oidc
Resolved create and update failing for service token
2025-03-13 19:47:51 +05:30
carlosmonastyrski
ef22b39421 Merge branch 'main' into feat/allowShareToAnyoneEdition 2025-03-13 11:11:37 -03:00
=
02153ffb32 fix: resolved create and update failing for service token 2025-03-13 19:41:33 +05:30
=
87ac723fcb feat: resolved renew token not renewing 2025-03-13 01:45:49 +05:30
carlosmonastyrski
71b8e3dbce Fix migration column name on check variable 2025-03-11 13:44:42 -03:00
carlosmonastyrski
e46f10292c Fix createFileRoute issue due to a missing / on route definition 2025-03-11 13:09:38 -03:00
carlosmonastyrski
acb22cdf36 Added new option to enable/disable option to share secrets with anyone 2025-03-11 12:58:09 -03:00
93 changed files with 1732 additions and 491 deletions

8
.gitignore vendored
View File

@@ -1,3 +1,5 @@
.direnv/
# backend
node_modules
.env
@@ -26,8 +28,6 @@ node_modules
/.pnp
.pnp.js
.env
# testing
coverage
reports
@@ -63,10 +63,12 @@ yarn-error.log*
# Editor specific
.vscode/*
.idea/*
**/.idea/*
frontend-build
# cli
.go/
*.tgz
cli/infisical-merge
cli/test/infisical-merge

View File

@@ -31,7 +31,7 @@
"@fastify/swagger-ui": "^2.1.0",
"@google-cloud/kms": "^4.5.0",
"@infisical/quic": "^1.0.8",
"@node-saml/passport-saml": "^4.0.4",
"@node-saml/passport-saml": "^5.0.1",
"@octokit/auth-app": "^7.1.1",
"@octokit/plugin-retry": "^5.0.5",
"@octokit/rest": "^20.0.2",
@@ -6747,32 +6747,35 @@
}
},
"node_modules/@node-saml/node-saml": {
"version": "4.0.5",
"resolved": "https://registry.npmjs.org/@node-saml/node-saml/-/node-saml-4.0.5.tgz",
"integrity": "sha512-J5DglElbY1tjOuaR1NPtjOXkXY5bpUhDoKVoeucYN98A3w4fwgjIOPqIGcb6cQsqFq2zZ6vTCeKn5C/hvefSaw==",
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/@node-saml/node-saml/-/node-saml-5.0.1.tgz",
"integrity": "sha512-YQzFPEC+CnsfO9AFYnwfYZKIzOLx3kITaC1HrjHVLTo6hxcQhc+LgHODOMvW4VCV95Gwrz1MshRUWCPzkDqmnA==",
"license": "MIT",
"dependencies": {
"@types/debug": "^4.1.7",
"@types/passport": "^1.0.11",
"@types/xml-crypto": "^1.4.2",
"@types/xml-encryption": "^1.2.1",
"@types/xml2js": "^0.4.11",
"@xmldom/xmldom": "^0.8.6",
"@types/debug": "^4.1.12",
"@types/qs": "^6.9.11",
"@types/xml-encryption": "^1.2.4",
"@types/xml2js": "^0.4.14",
"@xmldom/is-dom-node": "^1.0.1",
"@xmldom/xmldom": "^0.8.10",
"debug": "^4.3.4",
"xml-crypto": "^3.0.1",
"xml-crypto": "^6.0.1",
"xml-encryption": "^3.0.2",
"xml2js": "^0.5.0",
"xmlbuilder": "^15.1.1"
"xml2js": "^0.6.2",
"xmlbuilder": "^15.1.1",
"xpath": "^0.0.34"
},
"engines": {
"node": ">= 14"
"node": ">= 18"
}
},
"node_modules/@node-saml/node-saml/node_modules/debug": {
"version": "4.3.4",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
"integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
"version": "4.4.0",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz",
"integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==",
"license": "MIT",
"dependencies": {
"ms": "2.1.2"
"ms": "^2.1.3"
},
"engines": {
"node": ">=6.0"
@@ -6783,25 +6786,43 @@
}
}
},
"node_modules/@node-saml/node-saml/node_modules/ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
"node_modules/@node-saml/node-saml/node_modules/xml2js": {
"version": "0.6.2",
"resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.6.2.tgz",
"integrity": "sha512-T4rieHaC1EXcES0Kxxj4JWgaUQHDk+qwHcYOCFHfiwKz7tOVPLq7Hjq9dM1WCMhylqMEfP7hMcOIChvotiZegA==",
"license": "MIT",
"dependencies": {
"sax": ">=0.6.0",
"xmlbuilder": "~11.0.0"
},
"engines": {
"node": ">=4.0.0"
}
},
"node_modules/@node-saml/node-saml/node_modules/xml2js/node_modules/xmlbuilder": {
"version": "11.0.1",
"resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz",
"integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==",
"license": "MIT",
"engines": {
"node": ">=4.0"
}
},
"node_modules/@node-saml/passport-saml": {
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/@node-saml/passport-saml/-/passport-saml-4.0.4.tgz",
"integrity": "sha512-xFw3gw0yo+K1mzlkW15NeBF7cVpRHN/4vpjmBKzov5YFImCWh/G0LcTZ8krH3yk2/eRPc3Or8LRPudVJBjmYaw==",
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/@node-saml/passport-saml/-/passport-saml-5.0.1.tgz",
"integrity": "sha512-fMztg3zfSnjLEgxvpl6HaDMNeh0xeQX4QHiF9e2Lsie2dc4qFE37XYbQZhVmn8XJ2awPpSWLQ736UskYgGU8lQ==",
"license": "MIT",
"dependencies": {
"@node-saml/node-saml": "^4.0.4",
"@types/express": "^4.17.14",
"@types/passport": "^1.0.11",
"@types/passport-strategy": "^0.2.35",
"passport": "^0.6.0",
"@node-saml/node-saml": "^5.0.1",
"@types/express": "^4.17.21",
"@types/passport": "^1.0.16",
"@types/passport-strategy": "^0.2.38",
"passport": "^0.7.0",
"passport-strategy": "^1.0.0"
},
"engines": {
"node": ">= 14"
"node": ">= 18"
}
},
"node_modules/@nodelib/fs.scandir": {
@@ -9606,6 +9627,7 @@
"version": "4.1.12",
"resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz",
"integrity": "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==",
"license": "MIT",
"dependencies": {
"@types/ms": "*"
}
@@ -9725,9 +9747,10 @@
"integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w=="
},
"node_modules/@types/ms": {
"version": "0.7.34",
"resolved": "https://registry.npmjs.org/@types/ms/-/ms-0.7.34.tgz",
"integrity": "sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g=="
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz",
"integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==",
"license": "MIT"
},
"node_modules/@types/node": {
"version": "20.9.5",
@@ -9907,9 +9930,10 @@
"dev": true
},
"node_modules/@types/qs": {
"version": "6.9.10",
"resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.10.tgz",
"integrity": "sha512-3Gnx08Ns1sEoCrWssEgTSJs/rsT2vhGP+Ja9cnnk9k4ALxinORlQneLXFeFKOTJMOeZUFD1s7w+w2AphTpvzZw=="
"version": "6.9.18",
"resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.18.tgz",
"integrity": "sha512-kK7dgTYDyGqS+e2Q4aK9X3D7q234CIZ1Bv0q/7Z5IwRDoADNU81xXJK/YVyLbLTZCoIwUoDoffFeF+p/eIklAA==",
"license": "MIT"
},
"node_modules/@types/range-parser": {
"version": "1.2.7",
@@ -10058,19 +10082,11 @@
"@types/webidl-conversions": "*"
}
},
"node_modules/@types/xml-crypto": {
"version": "1.4.6",
"resolved": "https://registry.npmjs.org/@types/xml-crypto/-/xml-crypto-1.4.6.tgz",
"integrity": "sha512-A6jEW2FxLZo1CXsRWnZHUX2wzR3uDju2Bozt6rDbSmU/W8gkilaVbwFEVN0/NhnUdMVzwYobWtM6bU1QJJFb7Q==",
"dependencies": {
"@types/node": "*",
"xpath": "0.0.27"
}
},
"node_modules/@types/xml-encryption": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/@types/xml-encryption/-/xml-encryption-1.2.4.tgz",
"integrity": "sha512-I69K/WW1Dv7j6O3jh13z0X8sLWJRXbu5xnHDl9yHzUNDUBtUoBY058eb5s+x/WG6yZC1h8aKdI2EoyEPjyEh+Q==",
"license": "MIT",
"dependencies": {
"@types/node": "*"
}
@@ -10079,6 +10095,7 @@
"version": "0.4.14",
"resolved": "https://registry.npmjs.org/@types/xml2js/-/xml2js-0.4.14.tgz",
"integrity": "sha512-4YnrRemBShWRO2QjvUin8ESA41rH+9nQGLUGZV/1IDhi3SL9OhdpNC/MrulTWuptXKwhx/aDxE7toV0f/ypIXQ==",
"license": "MIT",
"dependencies": {
"@types/node": "*"
}
@@ -10522,10 +10539,20 @@
"url": "https://opencollective.com/vitest"
}
},
"node_modules/@xmldom/is-dom-node": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@xmldom/is-dom-node/-/is-dom-node-1.0.1.tgz",
"integrity": "sha512-CJDxIgE5I0FH+ttq/Fxy6nRpxP70+e2O048EPe85J2use3XKdatVM7dDVvFNjQudd9B49NPoZ+8PG49zj4Er8Q==",
"license": "MIT",
"engines": {
"node": ">= 16"
}
},
"node_modules/@xmldom/xmldom": {
"version": "0.8.10",
"resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.10.tgz",
"integrity": "sha512-2WALfTl4xo2SkGCYRt6rDTFfk9R1czmBvUQy12gK2KuRKIpWEhcbbzy8EZXtz/jkRqHX8bFEc6FC1HjX4TUWYw==",
"license": "MIT",
"engines": {
"node": ">=10.0.0"
}
@@ -18222,9 +18249,10 @@
}
},
"node_modules/passport": {
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/passport/-/passport-0.6.0.tgz",
"integrity": "sha512-0fe+p3ZnrWRW74fe8+SvCyf4a3Pb2/h7gFkQ8yTJpAO50gDzlfjZUZTO1k5Eg9kUct22OxHLqDZoKUWRHOh9ug==",
"version": "0.7.0",
"resolved": "https://registry.npmjs.org/passport/-/passport-0.7.0.tgz",
"integrity": "sha512-cPLl+qZpSc+ireUvt+IzqbED1cHHkDoVYMo30jbJIdOOjQ1MQYZBPiNvmi8UM6lJuOpTPXJGZQk0DtC4y61MYQ==",
"license": "MIT",
"dependencies": {
"passport-strategy": "1.x.x",
"pause": "0.0.1",
@@ -23692,42 +23720,44 @@
}
},
"node_modules/xml-crypto": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/xml-crypto/-/xml-crypto-3.2.0.tgz",
"integrity": "sha512-qVurBUOQrmvlgmZqIVBqmb06TD2a/PpEUfFPgD7BuBfjmoH4zgkqaWSIJrnymlCvM2GGt9x+XtJFA+ttoAufqg==",
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/xml-crypto/-/xml-crypto-6.0.1.tgz",
"integrity": "sha512-v05aU7NS03z4jlZ0iZGRFeZsuKO1UfEbbYiaeRMiATBFs6Jq9+wqKquEMTn4UTrYZ9iGD8yz3KT4L9o2iF682w==",
"license": "MIT",
"dependencies": {
"@xmldom/xmldom": "^0.8.8",
"xpath": "0.0.32"
"@xmldom/is-dom-node": "^1.0.1",
"@xmldom/xmldom": "^0.8.10",
"xpath": "^0.0.33"
},
"engines": {
"node": ">=4.0.0"
"node": ">=16"
}
},
"node_modules/xml-crypto/node_modules/xpath": {
"version": "0.0.32",
"resolved": "https://registry.npmjs.org/xpath/-/xpath-0.0.32.tgz",
"integrity": "sha512-rxMJhSIoiO8vXcWvSifKqhvV96GjiD5wYb8/QHdoRyQvraTpp4IEv944nhGausZZ3u7dhQXteZuZbaqfpB7uYw==",
"version": "0.0.33",
"resolved": "https://registry.npmjs.org/xpath/-/xpath-0.0.33.tgz",
"integrity": "sha512-NNXnzrkDrAzalLhIUc01jO2mOzXGXh1JwPgkihcLLzw98c0WgYDmmjSh1Kl3wzaxSVWMuA+fe0WTWOBDWCBmNA==",
"license": "MIT",
"engines": {
"node": ">=0.6.0"
}
},
"node_modules/xml-encryption": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/xml-encryption/-/xml-encryption-3.0.2.tgz",
"integrity": "sha512-VxYXPvsWB01/aqVLd6ZMPWZ+qaj0aIdF+cStrVJMcFj3iymwZeI0ABzB3VqMYv48DkSpRhnrXqTUkR34j+UDyg==",
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/xml-encryption/-/xml-encryption-3.1.0.tgz",
"integrity": "sha512-PV7qnYpoAMXbf1kvQkqMScLeQpjCMixddAKq9PtqVrho8HnYbBOWNfG0kA4R7zxQDo7w9kiYAyzS/ullAyO55Q==",
"license": "MIT",
"dependencies": {
"@xmldom/xmldom": "^0.8.5",
"escape-html": "^1.0.3",
"xpath": "0.0.32"
},
"engines": {
"node": ">=12"
}
},
"node_modules/xml-encryption/node_modules/xpath": {
"version": "0.0.32",
"resolved": "https://registry.npmjs.org/xpath/-/xpath-0.0.32.tgz",
"integrity": "sha512-rxMJhSIoiO8vXcWvSifKqhvV96GjiD5wYb8/QHdoRyQvraTpp4IEv944nhGausZZ3u7dhQXteZuZbaqfpB7uYw==",
"license": "MIT",
"engines": {
"node": ">=0.6.0"
}
@@ -23764,6 +23794,7 @@
"version": "15.1.1",
"resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-15.1.1.tgz",
"integrity": "sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg==",
"license": "MIT",
"engines": {
"node": ">=8.0"
}
@@ -23774,9 +23805,10 @@
"integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw=="
},
"node_modules/xpath": {
"version": "0.0.27",
"resolved": "https://registry.npmjs.org/xpath/-/xpath-0.0.27.tgz",
"integrity": "sha512-fg03WRxtkCV6ohClePNAECYsmpKKTv5L8y/X3Dn1hQrec3POx2jHZ/0P2qQ6HvsrU1BmeqXcof3NGGueG6LxwQ==",
"version": "0.0.34",
"resolved": "https://registry.npmjs.org/xpath/-/xpath-0.0.34.tgz",
"integrity": "sha512-FxF6+rkr1rNSQrhUNYrAFJpRXNzlDoMxeXN5qI84939ylEv3qqPFKa85Oxr6tDaJKqwW6KKyo2v26TSv3k6LeA==",
"license": "MIT",
"engines": {
"node": ">=0.6.0"
}

View File

@@ -148,7 +148,7 @@
"@fastify/swagger-ui": "^2.1.0",
"@google-cloud/kms": "^4.5.0",
"@infisical/quic": "^1.0.8",
"@node-saml/passport-saml": "^4.0.4",
"@node-saml/passport-saml": "^5.0.1",
"@octokit/auth-app": "^7.1.1",
"@octokit/plugin-retry": "^5.0.5",
"@octokit/rest": "^20.0.2",

View File

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

View File

@@ -22,7 +22,8 @@ export const OrganizationsSchema = z.object({
kmsEncryptedDataKey: zodBuffer.nullable().optional(),
defaultMembershipRole: z.string().default("member"),
enforceMfa: z.boolean().default(false),
selectedMfaMethod: z.string().nullable().optional()
selectedMfaMethod: z.string().nullable().optional(),
allowSecretSharingOutsideOrganization: z.boolean().default(true).nullable().optional()
});
export type TOrganizations = z.infer<typeof OrganizationsSchema>;

View File

@@ -1,10 +1,10 @@
import ms from "ms";
import { z } from "zod";
import { DynamicSecretLeasesSchema } from "@app/db/schemas";
import { DYNAMIC_SECRET_LEASES } from "@app/lib/api-docs";
import { daysToMillisecond } from "@app/lib/dates";
import { removeTrailingSlash } from "@app/lib/fn";
import { ms } from "@app/lib/ms";
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { SanitizedDynamicSecretSchema } from "@app/server/routes/sanitizedSchemas";

View File

@@ -1,4 +1,3 @@
import ms from "ms";
import { z } from "zod";
import { DynamicSecretLeasesSchema } from "@app/db/schemas";
@@ -6,6 +5,7 @@ import { DynamicSecretProviderSchema } from "@app/ee/services/dynamic-secret/pro
import { DYNAMIC_SECRETS } from "@app/lib/api-docs";
import { daysToMillisecond } from "@app/lib/dates";
import { removeTrailingSlash } from "@app/lib/fn";
import { ms } from "@app/lib/ms";
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
import { slugSchema } from "@app/server/lib/schemas";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";

View File

@@ -1,11 +1,11 @@
import slugify from "@sindresorhus/slugify";
import ms from "ms";
import { z } from "zod";
import { IdentityProjectAdditionalPrivilegeTemporaryMode } from "@app/ee/services/identity-project-additional-privilege/identity-project-additional-privilege-types";
import { backfillPermissionV1SchemaToV2Schema } from "@app/ee/services/permission/project-permission";
import { IDENTITY_ADDITIONAL_PRIVILEGE } from "@app/lib/api-docs";
import { UnauthorizedError } from "@app/lib/errors";
import { ms } from "@app/lib/ms";
import { alphaNumericNanoId } from "@app/lib/nanoid";
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
import { slugSchema } from "@app/server/lib/schemas";

View File

@@ -1,10 +1,10 @@
import ms from "ms";
import { z } from "zod";
import { KmipClientsSchema } from "@app/db/schemas";
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
import { KmipPermission } from "@app/ee/services/kmip/kmip-enum";
import { KmipClientOrderBy } from "@app/ee/services/kmip/kmip-types";
import { ms } from "@app/lib/ms";
import { OrderByDirection } from "@app/lib/types";
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";

View File

@@ -25,7 +25,7 @@ type TSAMLConfig = {
callbackUrl: string;
entryPoint: string;
issuer: string;
cert: string;
idpCert: string;
audience: string;
wantAuthnResponseSigned?: boolean;
wantAssertionsSigned?: boolean;
@@ -72,7 +72,7 @@ export const registerSamlRouter = async (server: FastifyZodProvider) => {
callbackUrl: `${appCfg.SITE_URL}/api/v1/sso/saml2/${ssoConfig.id}`,
entryPoint: ssoConfig.entryPoint,
issuer: ssoConfig.issuer,
cert: ssoConfig.cert,
idpCert: ssoConfig.cert,
audience: appCfg.SITE_URL || ""
};
if (ssoConfig.authProvider === SamlProviders.JUMPCLOUD_SAML) {
@@ -302,15 +302,21 @@ export const registerSamlRouter = async (server: FastifyZodProvider) => {
}
},
handler: async (req) => {
const saml = await server.services.saml.createSamlCfg({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
orgId: req.body.organizationId,
...req.body
const { isActive, authProvider, issuer, entryPoint, cert } = req.body;
const { permission } = req;
return server.services.saml.createSamlCfg({
isActive,
authProvider,
issuer,
entryPoint,
idpCert: cert,
actor: permission.type,
actorId: permission.id,
actorAuthMethod: permission.authMethod,
actorOrgId: permission.orgId,
orgId: req.body.organizationId
});
return saml;
}
});
@@ -337,15 +343,21 @@ export const registerSamlRouter = async (server: FastifyZodProvider) => {
}
},
handler: async (req) => {
const saml = await server.services.saml.updateSamlCfg({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
orgId: req.body.organizationId,
...req.body
const { isActive, authProvider, issuer, entryPoint, cert } = req.body;
const { permission } = req;
return server.services.saml.updateSamlCfg({
isActive,
authProvider,
issuer,
entryPoint,
idpCert: cert,
actor: permission.type,
actorId: permission.id,
actorAuthMethod: permission.authMethod,
actorOrgId: permission.orgId,
orgId: req.body.organizationId
});
return saml;
}
});
};

View File

@@ -1,9 +1,9 @@
import ms from "ms";
import { z } from "zod";
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
import { SshCertType } from "@app/ee/services/ssh/ssh-certificate-authority-types";
import { SSH_CERTIFICATE_AUTHORITIES } from "@app/lib/api-docs";
import { ms } from "@app/lib/ms";
import { writeLimit } from "@app/server/config/rateLimiter";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type";

View File

@@ -1,5 +1,4 @@
import slugify from "@sindresorhus/slugify";
import ms from "ms";
import { z } from "zod";
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
@@ -10,6 +9,7 @@ import {
isValidUserPattern
} from "@app/ee/services/ssh-certificate-template/ssh-certificate-template-validators";
import { SSH_CERTIFICATE_TEMPLATES } from "@app/lib/api-docs";
import { ms } from "@app/lib/ms";
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type";

View File

@@ -1,11 +1,11 @@
import slugify from "@sindresorhus/slugify";
import ms from "ms";
import { z } from "zod";
import { checkForInvalidPermissionCombination } from "@app/ee/services/permission/permission-fns";
import { ProjectPermissionV2Schema } from "@app/ee/services/permission/project-permission";
import { ProjectUserAdditionalPrivilegeTemporaryMode } from "@app/ee/services/project-user-additional-privilege/project-user-additional-privilege-types";
import { PROJECT_USER_ADDITIONAL_PRIVILEGE } from "@app/lib/api-docs";
import { ms } from "@app/lib/ms";
import { alphaNumericNanoId } from "@app/lib/nanoid";
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
import { slugSchema } from "@app/server/lib/schemas";

View File

@@ -1,11 +1,11 @@
import slugify from "@sindresorhus/slugify";
import ms from "ms";
import { z } from "zod";
import { IdentityProjectAdditionalPrivilegeTemporaryMode } from "@app/ee/services/identity-project-additional-privilege-v2/identity-project-additional-privilege-v2-types";
import { checkForInvalidPermissionCombination } from "@app/ee/services/permission/permission-fns";
import { ProjectPermissionV2Schema } from "@app/ee/services/permission/project-permission";
import { IDENTITY_ADDITIONAL_PRIVILEGE_V2 } from "@app/lib/api-docs";
import { ms } from "@app/lib/ms";
import { alphaNumericNanoId } from "@app/lib/nanoid";
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
import { slugSchema } from "@app/server/lib/schemas";

View File

@@ -1,9 +1,10 @@
import slugify from "@sindresorhus/slugify";
import ms from "ms";
import msFn from "ms";
import { ActionProjectType, ProjectMembershipRole } from "@app/db/schemas";
import { getConfig } from "@app/lib/config/env";
import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors";
import { ms } from "@app/lib/ms";
import { alphaNumericNanoId } from "@app/lib/nanoid";
import { TKmsServiceFactory } from "@app/services/kms/kms-service";
import { TProjectDALFactory } from "@app/services/project/project-dal";
@@ -246,7 +247,7 @@ export const accessApprovalRequestServiceFactory = ({
requesterEmail: requestedByUser.email,
isTemporary,
...(isTemporary && {
expiresIn: ms(ms(temporaryRange || ""), { long: true })
expiresIn: msFn(ms(temporaryRange || ""), { long: true })
}),
secretPath,
environment: envSlug,

View File

@@ -1,5 +1,4 @@
import { ForbiddenError, subject } from "@casl/ability";
import ms from "ms";
import { ActionProjectType } from "@app/db/schemas";
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
@@ -11,6 +10,7 @@ import {
import { getConfig } from "@app/lib/config/env";
import { BadRequestError, NotFoundError } from "@app/lib/errors";
import { logger } from "@app/lib/logger";
import { ms } from "@app/lib/ms";
import { TKmsServiceFactory } from "@app/services/kms/kms-service";
import { KmsDataKey } from "@app/services/kms/kms-types";
import { TProjectDALFactory } from "@app/services/project/project-dal";

View File

@@ -1,10 +1,10 @@
import { ForbiddenError, subject } from "@casl/ability";
import { packRules } from "@casl/ability/extra";
import ms from "ms";
import { ActionProjectType, TableName } from "@app/db/schemas";
import { validatePermissionBoundary } from "@app/lib/casl/boundary";
import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors";
import { ms } from "@app/lib/ms";
import { unpackPermissions } from "@app/server/routes/sanitizedSchema/permission";
import { ActorType } from "@app/services/auth/auth-type";
import { TIdentityProjectDALFactory } from "@app/services/identity-project/identity-project-dal";

View File

@@ -1,10 +1,10 @@
import { ForbiddenError, MongoAbility, RawRuleOf, subject } from "@casl/ability";
import { PackRule, packRules, unpackRules } from "@casl/ability/extra";
import ms from "ms";
import { ActionProjectType } from "@app/db/schemas";
import { validatePermissionBoundary } from "@app/lib/casl/boundary";
import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors";
import { ms } from "@app/lib/ms";
import { UnpackedPermissionSchema } from "@app/server/routes/sanitizedSchema/permission";
import { ActorType } from "@app/services/auth/auth-type";
import { TIdentityProjectDALFactory } from "@app/services/identity-project/identity-project-dal";

View File

@@ -1,11 +1,11 @@
import { ForbiddenError } from "@casl/ability";
import * as x509 from "@peculiar/x509";
import crypto, { KeyObject } from "crypto";
import ms from "ms";
import { ActionProjectType } from "@app/db/schemas";
import { BadRequestError, InternalServerError, NotFoundError } from "@app/lib/errors";
import { isValidHostname, isValidIp } from "@app/lib/ip";
import { ms } from "@app/lib/ms";
import { constructPemChainFromCerts } from "@app/services/certificate/certificate-fns";
import { CertExtendedKeyUsage, CertKeyAlgorithm, CertKeyUsage } from "@app/services/certificate/certificate-types";
import {

View File

@@ -32,6 +32,10 @@ export enum OrgPermissionAdminConsoleAction {
AccessAllProjects = "access-all-projects"
}
export enum OrgPermissionSecretShareAction {
ManageSettings = "manage-settings"
}
export enum OrgPermissionGatewayActions {
// is there a better word for this. This mean can an identity be a gateway
CreateGateways = "create-gateways",
@@ -59,7 +63,8 @@ export enum OrgPermissionSubjects {
ProjectTemplates = "project-templates",
AppConnections = "app-connections",
Kmip = "kmip",
Gateway = "gateway"
Gateway = "gateway",
SecretShare = "secret-share"
}
export type AppConnectionSubjectFields = {
@@ -91,7 +96,8 @@ export type OrgPermissionSet =
)
]
| [OrgPermissionAdminConsoleAction, OrgPermissionSubjects.AdminConsole]
| [OrgPermissionKmipActions, OrgPermissionSubjects.Kmip];
| [OrgPermissionKmipActions, OrgPermissionSubjects.Kmip]
| [OrgPermissionSecretShareAction, OrgPermissionSubjects.SecretShare];
const AppConnectionConditionSchema = z
.object({
@@ -185,6 +191,12 @@ export const OrgPermissionSchema = z.discriminatedUnion("subject", [
"Describe what action an entity can take."
)
}),
z.object({
subject: z.literal(OrgPermissionSubjects.SecretShare).describe("The entity this permission pertains to."),
action: CASL_ACTION_SCHEMA_NATIVE_ENUM(OrgPermissionSecretShareAction).describe(
"Describe what action an entity can take."
)
}),
z.object({
subject: z.literal(OrgPermissionSubjects.Kmip).describe("The entity this permission pertains to."),
action: CASL_ACTION_SCHEMA_NATIVE_ENUM(OrgPermissionKmipActions).describe(
@@ -292,6 +304,8 @@ const buildAdminPermission = () => {
// the proxy assignment is temporary in order to prevent "more privilege" error during role assignment to MI
can(OrgPermissionKmipActions.Proxy, OrgPermissionSubjects.Kmip);
can(OrgPermissionSecretShareAction.ManageSettings, OrgPermissionSubjects.SecretShare);
return rules;
};

View File

@@ -1,10 +1,10 @@
import { ForbiddenError, MongoAbility, RawRuleOf } from "@casl/ability";
import { PackRule, packRules, unpackRules } from "@casl/ability/extra";
import ms from "ms";
import { ActionProjectType, TableName } from "@app/db/schemas";
import { validatePermissionBoundary } from "@app/lib/casl/boundary";
import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors";
import { ms } from "@app/lib/ms";
import { UnpackedPermissionSchema } from "@app/server/routes/sanitizedSchema/permission";
import { ActorType } from "@app/services/auth/auth-type";
import { TProjectMembershipDALFactory } from "@app/services/project-membership/project-membership-dal";

View File

@@ -63,7 +63,7 @@ export const samlConfigServiceFactory = ({
kmsService
}: TSamlConfigServiceFactoryDep) => {
const createSamlCfg = async ({
cert,
idpCert,
actor,
actorAuthMethod,
actorOrgId,
@@ -93,9 +93,9 @@ export const samlConfigServiceFactory = ({
orgId,
authProvider,
isActive,
encryptedSamlIssuer: encryptor({ plainText: Buffer.from(issuer) }).cipherTextBlob,
encryptedSamlCertificate: encryptor({ plainText: Buffer.from(idpCert) }).cipherTextBlob,
encryptedSamlEntryPoint: encryptor({ plainText: Buffer.from(entryPoint) }).cipherTextBlob,
encryptedSamlCertificate: encryptor({ plainText: Buffer.from(cert) }).cipherTextBlob
encryptedSamlIssuer: encryptor({ plainText: Buffer.from(issuer) }).cipherTextBlob
});
return samlConfig;
@@ -106,7 +106,7 @@ export const samlConfigServiceFactory = ({
actor,
actorOrgId,
actorAuthMethod,
cert,
idpCert,
actorId,
issuer,
isActive,
@@ -136,8 +136,8 @@ export const samlConfigServiceFactory = ({
updateQuery.encryptedSamlIssuer = encryptor({ plainText: Buffer.from(issuer) }).cipherTextBlob;
}
if (cert !== undefined) {
updateQuery.encryptedSamlCertificate = encryptor({ plainText: Buffer.from(cert) }).cipherTextBlob;
if (idpCert !== undefined) {
updateQuery.encryptedSamlCertificate = encryptor({ plainText: Buffer.from(idpCert) }).cipherTextBlob;
}
const [ssoConfig] = await samlConfigDAL.update({ orgId }, updateQuery);

View File

@@ -15,7 +15,7 @@ export type TCreateSamlCfgDTO = {
isActive: boolean;
entryPoint: string;
issuer: string;
cert: string;
idpCert: string;
} & TOrgPermission;
export type TUpdateSamlCfgDTO = Partial<{
@@ -23,7 +23,7 @@ export type TUpdateSamlCfgDTO = Partial<{
isActive: boolean;
entryPoint: string;
issuer: string;
cert: string;
idpCert: string;
}> &
TOrgPermission;

View File

@@ -1,10 +1,10 @@
import { ForbiddenError } from "@casl/ability";
import ms from "ms";
import { ActionProjectType } from "@app/db/schemas";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
import { BadRequestError, NotFoundError } from "@app/lib/errors";
import { ms } from "@app/lib/ms";
import { TSshCertificateAuthorityDALFactory } from "../ssh/ssh-certificate-authority-dal";
import { TSshCertificateTemplateDALFactory } from "./ssh-certificate-template-dal";

View File

@@ -1,13 +1,13 @@
import { execFile } from "child_process";
import crypto from "crypto";
import { promises as fs } from "fs";
import ms from "ms";
import os from "os";
import path from "path";
import { promisify } from "util";
import { TSshCertificateTemplates } from "@app/db/schemas";
import { BadRequestError } from "@app/lib/errors";
import { ms } from "@app/lib/ms";
import { CertKeyAlgorithm } from "@app/services/certificate/certificate-types";
import {

View File

@@ -0,0 +1,15 @@
import msFn, { StringValue } from "ms";
import { BadRequestError } from "../errors";
export const ms = (val: string) => {
if (typeof val !== "string") {
throw new BadRequestError({ message: `Date must be string` });
}
try {
return msFn(val as StringValue);
} catch {
throw new BadRequestError({ message: `Invalid date format string: ${val}` });
}
};

View File

@@ -1,10 +1,10 @@
/* eslint-disable @typescript-eslint/no-floating-promises */
import ms from "ms";
import { z } from "zod";
import { CertificateAuthoritiesSchema, CertificateTemplatesSchema } from "@app/db/schemas";
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
import { CERTIFICATE_AUTHORITIES } from "@app/lib/api-docs";
import { ms } from "@app/lib/ms";
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type";

View File

@@ -1,9 +1,9 @@
import ms from "ms";
import { z } from "zod";
import { CertificatesSchema } from "@app/db/schemas";
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
import { CERTIFICATE_AUTHORITIES, CERTIFICATES } from "@app/lib/api-docs";
import { ms } from "@app/lib/ms";
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type";

View File

@@ -1,9 +1,9 @@
import ms from "ms";
import { z } from "zod";
import { CertificateTemplateEstConfigsSchema } from "@app/db/schemas";
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
import { CERTIFICATE_TEMPLATES } from "@app/lib/api-docs";
import { ms } from "@app/lib/ms";
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type";

View File

@@ -257,7 +257,8 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => {
scimEnabled: z.boolean().optional(),
defaultMembershipRoleSlug: slugSchema({ max: 64, field: "Default Membership Role" }).optional(),
enforceMfa: z.boolean().optional(),
selectedMfaMethod: z.nativeEnum(MfaMethod).optional()
selectedMfaMethod: z.nativeEnum(MfaMethod).optional(),
allowSecretSharingOutsideOrganization: z.boolean().optional()
}),
response: {
200: z.object({

View File

@@ -1,4 +1,3 @@
import ms from "ms";
import { z } from "zod";
import {
@@ -10,6 +9,7 @@ import {
} from "@app/db/schemas";
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
import { PROJECT_USERS } from "@app/lib/api-docs";
import { ms } from "@app/lib/ms";
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type";

View File

@@ -1,4 +1,3 @@
import ms from "ms";
import { z } from "zod";
import {
@@ -8,6 +7,7 @@ import {
ProjectUserMembershipRolesSchema
} from "@app/db/schemas";
import { PROJECTS } from "@app/lib/api-docs";
import { ms } from "@app/lib/ms";
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type";

View File

@@ -1,4 +1,3 @@
import ms from "ms";
import { z } from "zod";
import {
@@ -9,6 +8,7 @@ import {
} from "@app/db/schemas";
import { ORGANIZATIONS, PROJECT_IDENTITIES } from "@app/lib/api-docs";
import { BadRequestError } from "@app/lib/errors";
import { ms } from "@app/lib/ms";
import { OrderByDirection } from "@app/lib/types";
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";

View File

@@ -2,7 +2,6 @@
import { ForbiddenError } from "@casl/ability";
import * as x509 from "@peculiar/x509";
import crypto, { KeyObject } from "crypto";
import ms from "ms";
import { z } from "zod";
import { ActionProjectType, ProjectType, TCertificateAuthorities, TCertificateTemplates } from "@app/db/schemas";
@@ -10,6 +9,7 @@ import { TPermissionServiceFactory } from "@app/ee/services/permission/permissio
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
import { getConfig } from "@app/lib/config/env";
import { BadRequestError, NotFoundError } from "@app/lib/errors";
import { ms } from "@app/lib/ms";
import { TCertificateBodyDALFactory } from "@app/services/certificate/certificate-body-dal";
import { TCertificateDALFactory } from "@app/services/certificate/certificate-dal";
import { TKmsServiceFactory } from "@app/services/kms/kms-service";

View File

@@ -1,7 +1,6 @@
import ms from "ms";
import { TCertificateTemplates } from "@app/db/schemas";
import { BadRequestError } from "@app/lib/errors";
import { ms } from "@app/lib/ms";
export const validateCertificateDetailsAgainstTemplate = (
cert: {

View File

@@ -1,5 +1,4 @@
import { ForbiddenError } from "@casl/ability";
import ms from "ms";
import { ActionProjectType, ProjectMembershipRole, SecretKeyEncoding, TGroups } from "@app/db/schemas";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
@@ -9,6 +8,7 @@ import { decryptAsymmetric, encryptAsymmetric } from "@app/lib/crypto";
import { infisicalSymmetricDecrypt } from "@app/lib/crypto/encryption";
import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors";
import { groupBy } from "@app/lib/fn";
import { ms } from "@app/lib/ms";
import { isUuidV4 } from "@app/lib/validator";
import { TGroupDALFactory } from "../../ee/services/group/group-dal";

View File

@@ -78,9 +78,7 @@ export const identityAccessTokenServiceFactory = ({
const renewAccessToken = async ({ accessToken }: TRenewAccessTokenDTO) => {
const appCfg = getConfig();
const decodedToken = jwt.verify(accessToken, appCfg.AUTH_SECRET) as JwtPayload & {
identityAccessTokenId: string;
};
const decodedToken = jwt.verify(accessToken, appCfg.AUTH_SECRET) as TIdentityAccessTokenJwtPayload;
if (decodedToken.authTokenType !== AuthTokenType.IDENTITY_ACCESS_TOKEN) {
throw new BadRequestError({ message: "Only identity access tokens can be renewed" });
}
@@ -127,7 +125,23 @@ export const identityAccessTokenServiceFactory = ({
accessTokenLastRenewedAt: new Date()
});
return { accessToken, identityAccessToken: updatedIdentityAccessToken };
const renewedToken = jwt.sign(
{
identityId: decodedToken.identityId,
clientSecretId: decodedToken.clientSecretId,
identityAccessTokenId: decodedToken.identityAccessTokenId,
authTokenType: AuthTokenType.IDENTITY_ACCESS_TOKEN
} as TIdentityAccessTokenJwtPayload,
appCfg.AUTH_SECRET,
// akhilmhdh: for non-expiry tokens you should not even set the value, including undefined. Even for undefined jsonwebtoken throws error
Number(identityAccessToken.accessTokenTTL) === 0
? undefined
: {
expiresIn: Number(identityAccessToken.accessTokenTTL)
}
);
return { accessToken: renewedToken, identityAccessToken: updatedIdentityAccessToken };
};
const revokeAccessToken = async (accessToken: string) => {

View File

@@ -1,5 +1,4 @@
import { ForbiddenError, subject } from "@casl/ability";
import ms from "ms";
import { ActionProjectType, ProjectMembershipRole } from "@app/db/schemas";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
@@ -7,6 +6,7 @@ import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services
import { validatePermissionBoundary } from "@app/lib/casl/boundary";
import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors";
import { groupBy } from "@app/lib/fn";
import { ms } from "@app/lib/ms";
import { ActorType } from "../auth/auth-type";
import { TIdentityOrgDALFactory } from "../identity/identity-org-dal";

View File

@@ -1,7 +1,7 @@
import { TDbClient } from "@app/db";
import { TableName, TIdentities } from "@app/db/schemas";
import { ormify, selectAllTableCols } from "@app/lib/knex";
import { DatabaseError } from "@app/lib/errors";
import { ormify, selectAllTableCols } from "@app/lib/knex";
export type TIdentityDALFactory = ReturnType<typeof identityDALFactory>;

View File

@@ -19,7 +19,11 @@ import {
import { TGroupDALFactory } from "@app/ee/services/group/group-dal";
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
import { TOidcConfigDALFactory } from "@app/ee/services/oidc/oidc-config-dal";
import { OrgPermissionActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission";
import {
OrgPermissionActions,
OrgPermissionSecretShareAction,
OrgPermissionSubjects
} from "@app/ee/services/permission/org-permission";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
import { TProjectUserAdditionalPrivilegeDALFactory } from "@app/ee/services/project-user-additional-privilege/project-user-additional-privilege-dal";
@@ -286,12 +290,27 @@ export const orgServiceFactory = ({
actorOrgId,
actorAuthMethod,
orgId,
data: { name, slug, authEnforced, scimEnabled, defaultMembershipRoleSlug, enforceMfa, selectedMfaMethod }
data: {
name,
slug,
authEnforced,
scimEnabled,
defaultMembershipRoleSlug,
enforceMfa,
selectedMfaMethod,
allowSecretSharingOutsideOrganization
}
}: TUpdateOrgDTO) => {
const appCfg = getConfig();
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Settings);
if (allowSecretSharingOutsideOrganization !== undefined) {
ForbiddenError.from(permission).throwUnlessCan(
OrgPermissionSecretShareAction.ManageSettings,
OrgPermissionSubjects.SecretShare
);
}
const plan = await licenseService.getPlan(orgId);
const currentOrg = await orgDAL.findOrgById(actorOrgId);
@@ -358,7 +377,8 @@ export const orgServiceFactory = ({
scimEnabled,
defaultMembershipRole,
enforceMfa,
selectedMfaMethod
selectedMfaMethod,
allowSecretSharingOutsideOrganization
});
if (!org) throw new NotFoundError({ message: `Organization with ID '${orgId}' not found` });
return org;

View File

@@ -72,6 +72,7 @@ export type TUpdateOrgDTO = {
defaultMembershipRoleSlug: string;
enforceMfa: boolean;
selectedMfaMethod: MfaMethod;
allowSecretSharingOutsideOrganization: boolean;
}>;
} & TOrgPermission;

View File

@@ -1,6 +1,5 @@
/* eslint-disable no-await-in-loop */
import { ForbiddenError } from "@casl/ability";
import ms from "ms";
import { ActionProjectType, ProjectMembershipRole, ProjectVersion, TableName } from "@app/db/schemas";
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
@@ -11,6 +10,7 @@ import { validatePermissionBoundary } from "@app/lib/casl/boundary";
import { getConfig } from "@app/lib/config/env";
import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors";
import { groupBy } from "@app/lib/fn";
import { ms } from "@app/lib/ms";
import { TUserGroupMembershipDALFactory } from "../../ee/services/group/user-group-membership-dal";
import { ActorType } from "../auth/auth-type";

View File

@@ -82,6 +82,13 @@ export const secretSharingServiceFactory = ({
if (!permission) throw new ForbiddenRequestError({ name: "User is not a part of the specified organization" });
$validateSharedSecretExpiry(expiresAt);
const org = await orgDAL.findOrgById(orgId);
if (!org.allowSecretSharingOutsideOrganization && accessType === SecretSharingAccessType.Anyone) {
throw new BadRequestError({
message: "Organization does not allow sharing secrets to members outside of this organization"
});
}
if (secretValue.length > 10_000) {
throw new BadRequestError({ message: "Shared secret value too long" });
}

View File

@@ -94,7 +94,7 @@ export const fnSecretBulkInsert = async ({
);
const userActorId = actor && actor.type === ActorType.USER ? actor.actorId : undefined;
const identityActorId = actor && actor.type !== ActorType.USER ? actor.actorId : undefined;
const identityActorId = actor && actor.type === ActorType.IDENTITY ? actor.actorId : undefined;
const actorType = actor?.type || ActorType.PLATFORM;
const newSecrets = await secretDAL.insertMany(
@@ -182,7 +182,7 @@ export const fnSecretBulkUpdate = async ({
actor
}: TFnSecretBulkUpdate) => {
const userActorId = actor && actor?.type === ActorType.USER ? actor?.actorId : undefined;
const identityActorId = actor && actor?.type !== ActorType.USER ? actor?.actorId : undefined;
const identityActorId = actor && actor?.type === ActorType.IDENTITY ? actor?.actorId : undefined;
const actorType = actor?.type || ActorType.PLATFORM;
const sanitizedInputSecrets = inputSecrets.map(

View File

@@ -7,6 +7,7 @@ import { getConfig } from "@app/lib/config/env";
import { infisicalSymmetricEncypt } from "@app/lib/crypto/encryption";
import { getUserPrivateKey } from "@app/lib/crypto/srp";
import { BadRequestError, NotFoundError } from "@app/lib/errors";
import { TIdentityDALFactory } from "@app/services/identity/identity-dal";
import { TAuthLoginFactory } from "../auth/auth-login-service";
import { AuthMethod } from "../auth/auth-type";
@@ -20,7 +21,6 @@ import { TUserAliasDALFactory } from "../user-alias/user-alias-dal";
import { UserAliasType } from "../user-alias/user-alias-types";
import { TSuperAdminDALFactory } from "./super-admin-dal";
import { LoginMethod, TAdminGetIdentitiesDTO, TAdminGetUsersDTO, TAdminSignUpDTO } from "./super-admin-types";
import { TIdentityDALFactory } from "@app/services/identity/identity-dal";
type TSuperAdminServiceFactoryDep = {
identityDAL: Pick<TIdentityDALFactory, "getIdentitiesByFilter">;

View File

@@ -2,6 +2,12 @@ package api
import "time"
type Environment struct {
Name string `json:"name"`
Slug string `json:"slug"`
ID string `json:"id"`
}
// Stores info for login one
type LoginOneRequest struct {
Email string `json:"email"`
@@ -14,7 +20,6 @@ type LoginOneResponse struct {
}
// Stores info for login two
type LoginTwoRequest struct {
Email string `json:"email"`
ClientProof string `json:"clientProof"`
@@ -168,9 +173,10 @@ type Secret struct {
}
type Project struct {
ID string `json:"id"`
Name string `json:"name"`
Slug string `json:"slug"`
ID string `json:"id"`
Name string `json:"name"`
Slug string `json:"slug"`
Environments []Environment `json:"environments"`
}
type RawSecret struct {

View File

@@ -15,6 +15,9 @@ import (
"syscall"
"time"
"github.com/Infisical/infisical-merge/packages/api"
"github.com/go-resty/resty/v2"
"github.com/Infisical/infisical-merge/packages/models"
"github.com/Infisical/infisical-merge/packages/util"
"github.com/fatih/color"
@@ -59,11 +62,11 @@ var runCmd = &cobra.Command{
return nil
},
Run: func(cmd *cobra.Command, args []string) {
environmentName, _ := cmd.Flags().GetString("env")
environmentSlug, _ := cmd.Flags().GetString("env")
if !cmd.Flags().Changed("env") {
environmentFromWorkspace := util.GetEnvFromWorkspaceFile()
if environmentFromWorkspace != "" {
environmentName = environmentFromWorkspace
environmentSlug = environmentFromWorkspace
}
}
@@ -136,8 +139,20 @@ var runCmd = &cobra.Command{
util.HandleError(err, "Unable to parse flag")
}
log.Debug().Msgf("Confirming selected environment is valid: %s", environmentSlug)
hasEnvironment, err := confirmProjectHasEnvironment(environmentSlug, projectId, token)
if err != nil {
util.HandleError(err, "Could not confirm project has environment")
}
if !hasEnvironment {
util.HandleError(fmt.Errorf("project does not have environment '%s'", environmentSlug))
}
log.Debug().Msgf("Project '%s' has environment '%s'", projectId, environmentSlug)
request := models.GetAllSecretsParameters{
Environment: environmentName,
Environment: environmentSlug,
WorkspaceId: projectId,
TagSlugs: tagSlugs,
SecretsPath: secretsPath,
@@ -308,7 +323,6 @@ func waitForExitCommand(cmd *exec.Cmd) (int, error) {
}
func executeCommandWithWatchMode(commandFlag string, args []string, watchModeInterval int, request models.GetAllSecretsParameters, projectConfigDir string, secretOverriding bool, token *models.TokenDetails) {
var cmd *exec.Cmd
var err error
var lastSecretsFetch time.Time
@@ -439,8 +453,53 @@ func executeCommandWithWatchMode(commandFlag string, args []string, watchModeInt
}
}
func fetchAndFormatSecretsForShell(request models.GetAllSecretsParameters, projectConfigDir string, secretOverriding bool, token *models.TokenDetails) (models.InjectableEnvironmentResult, error) {
func confirmProjectHasEnvironment(environmentSlug, projectId string, token *models.TokenDetails) (bool, error) {
var accessToken string
if token != nil && (token.Type == util.SERVICE_TOKEN_IDENTIFIER || token.Type == util.UNIVERSAL_AUTH_TOKEN_IDENTIFIER) {
accessToken = token.Token
} else {
util.RequireLogin()
util.RequireLocalWorkspaceFile()
loggedInUserDetails, err := util.GetCurrentLoggedInUserDetails(true)
if err != nil {
util.HandleError(err, "Unable to authenticate")
}
if loggedInUserDetails.LoginExpired {
util.PrintErrorMessageAndExit("Your login session has expired, please run [infisical login] and try again")
}
accessToken = loggedInUserDetails.UserCredentials.JTWToken
}
if projectId == "" {
workspaceFile, err := util.GetWorkSpaceFromFile()
if err != nil {
util.HandleError(err, "Unable to get local project details")
}
projectId = workspaceFile.WorkspaceId
}
httpClient := resty.New()
httpClient.SetAuthToken(accessToken).
SetHeader("Accept", "application/json")
project, err := api.CallGetProjectById(httpClient, projectId)
if err != nil {
return false, err
}
for _, env := range project.Environments {
if env.Slug == environmentSlug {
return true, nil
}
}
return false, nil
}
func fetchAndFormatSecretsForShell(request models.GetAllSecretsParameters, projectConfigDir string, secretOverriding bool, token *models.TokenDetails) (models.InjectableEnvironmentResult, error) {
if token != nil && token.Type == util.SERVICE_TOKEN_IDENTIFIER {
request.InfisicalToken = token.Token
} else if token != nil && token.Type == util.UNIVERSAL_AUTH_TOKEN_IDENTIFIER {

View File

@@ -232,7 +232,6 @@ func FilterSecretsByTag(plainTextSecrets []models.SingleEnvironmentVariable, tag
func GetAllEnvironmentVariables(params models.GetAllSecretsParameters, projectConfigFilePath string) ([]models.SingleEnvironmentVariable, error) {
var secretsToReturn []models.SingleEnvironmentVariable
// var serviceTokenDetails api.GetServiceTokenDetailsResponse
var errorToReturn error
if params.InfisicalToken == "" && params.UniversalAuthAccessToken == "" {

View File

@@ -76,7 +76,6 @@ func TestUniversalAuth_SecretsGetWrongEnvironment(t *testing.T) {
if err != nil {
t.Fatalf("snapshot failed: %v", err)
}
}
func TestUserAuth_SecretsGetAll(t *testing.T) {

View File

@@ -6,7 +6,7 @@ description: "Learn how to manage secrets in local development environments."
## Problem at hand
There is a number of issues that arise with secret management in local development environment:
There are a number of issues that arise with secret management in local development environment:
1. **Getting secrets onto local machines**. When new developers join or a new project is created, the process of getting the development set of secrets onto local machines is often unclear. As a result, developers end up spending a lot of time onboarding and risk potentially following insecure practices when sharing secrets from one developer to another.
2. **Syncing secrets with teammates**. One of the problems with .env files is that they become unsynced when one of the developers updates a secret or configuration. Even if the rest of the team is notified, developers don't make all the right changes immediately, and later on end up spending a lot of time debugging an issue due to missing environment variables. This leads to a lot of inefficiencies and lost time.
3. **Accidentally leaking secrets**. When developing locally, it's common for developers to accidentally leak a hardcoded secret as part of a commit. As soon as the secret is part of the git history, it becomes hard to get it removed and create a security vulnerability.

View File

@@ -126,21 +126,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>
</Accordion>
<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">
The `managedSecretReference` field is used to define the Kubernetes secret where the dynamic secret lease should be stored. The required fields are `secretName` and `secretNamespace`.

View File

@@ -93,7 +93,7 @@ When `hostAPI` is not defined the operator fetches secrets from Infisical Cloud.
CA certificate to use for connecting to the Infisical instance with SSL/TLS.
</Accordion>
### Authentication methods
### Authentication Methods
To retrieve the requested secrets, the operator must first authenticate with Infisical.
The list of available authentication methods are shown below.
@@ -535,7 +535,7 @@ spec:
</Accordion>
### Operator managed secrets
### Operator Managed Secrets
The managed secret properties specify where to store the secrets retrieved from your Infisical project.
This includes defining the name and namespace of the Kubernetes secret that will hold these secrets.
@@ -584,7 +584,7 @@ This is useful for tools such as ArgoCD, where every resource requires an owner
</Accordion>
### Manged secret templating
#### Managed Secret Templating
Fetching secrets from Infisical as is via the operator may not be enough. This is where templating functionality may be helpful.
Using Go templates, you can format, combine, and create new key-value pairs from secrets fetched from Infisical before storing them as Kubernetes Secrets.
@@ -681,6 +681,135 @@ template:
</Accordion>
### Operator Managed ConfigMaps
The managed config map properties specify where to store the secrets retrieved from your Infisical project. Config maps can be used to store **non-sensitive** data, such as application configuration variables.
The properties includes defining the name and namespace of the Kubernetes config map that will hold the data retrieved from your Infisical project.
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.
</Warning>
<Accordion title="managedKubeConfigMapReferences">
</Accordion>
<Accordion title="managedKubeConfigMapReferences[].configMapName">
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.
</Accordion>
<Accordion title="managedKubeConfigMapReferences[].creationPolicy">
Creation polices 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
- `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>
</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.
Using Go templates, you can format, combine, and create new key-value pairs from secrets fetched from Infisical before storing them as Kubernetes Config Maps.
<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.
**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.
</Accordion>
<Accordion title="managedKubeConfigMapReferences[].template.data">
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:
```golang
type TemplateSecret struct {
Value string `json:"value"`
SecretPath string `json:"secretPath"`
}
```
#### 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"
```
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
```
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"
# 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.
### Available templating functions
<Accordion title="decodeBase64ToBytes">
**Function name**: decodeBase64ToBytes
**Description**:
Given a base64 encoded string, this function will decodes the base64-encoded string.
This function is useful when your Infisical secrets are already stored as base64 encoded value in Infisical.
**Returns**: The decoded base64 string as bytes.
**Example**:
The example below assumes that the `BINARY_KEY_BASE64` secret is stored as a base64 encoded value in Infisical.
The resulting managed config map will contain the decoded value of `BINARY_KEY_BASE64`.
```yaml
managedKubeConfigMapReferences:
- configMapName: managed-configmap
configMapNamespace: default
template:
includeAllSecrets: true
data:
BINARY_KEY: "{{ decodeBase64ToBytes .BINARY_KEY_BASE64.Value }}"
```
</Accordion>
</Accordion>
## Applying CRD
Once you have configured the InfisicalSecret CRD with the required fields, you can apply it to your cluster.
@@ -692,17 +821,32 @@ kubectl apply -f example-infisical-secret-crd.yaml
To verify that the operator has successfully created the managed secret, you can check the secrets in the namespace that was specified.
```bash
# Verify managed secret is created
kubectl get secrets -n <namespace of managed secret>
```
<Tabs>
<Tab title="Managed Secret">
```bash
# Verify managed secret is created
kubectl get secrets -n <namespace of managed secret>
```
<Info>
The Infisical secrets will be synced and stored into the managed secret every
1 minute unless configured otherwise.
</Info>
</Tab>
<Tab title="Managed ConfigMap">
```bash
# Verify managed config map is created
kubectl get configmaps -n <namespace of managed config map>
```
<Info>
The Infisical config map data will be synced and stored into the managed config map every
1 minute unless configured otherwise.
</Info>
</Tab>
</Tabs>
<Info>
The Infisical secrets will be synced and stored into the managed secret every
1 minutes.
</Info>
## Using managed secret in your deployment
## 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.
Here, we will highlight three of the most common ways to utilize it. Learn more about Kubernetes secrets [here](https://kubernetes.io/docs/concepts/configuration/secret/)
@@ -755,7 +899,7 @@ spec:
valueFrom:
secretKeyRef:
name: managed-secret # managed secret name
key: SOME_SECRET_KEY # The name of the key which exists in the managed secret
key: SOME_SECRET_KEY # The name of the key which exists in the managed secret
```
Example usage in a deployment
@@ -764,28 +908,30 @@ Example usage in a deployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
labels:
app: nginx
name: nginx-deployment
labels:
app: nginx
spec:
replicas: 1
selector:
matchLabels:
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
replicas: 1
selector:
matchLabels:
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
```
</Accordion>
@@ -861,12 +1007,12 @@ stringData:
-----END CERTIFICATE-----
```
### Auto redeployment
### Automatic Redeployment
Deployments using managed secrets don't reload automatically on updates, so they may use outdated secrets unless manually redeployed.
To address this, we added functionality to automatically redeploy your deployment when its managed secret updates.
#### Enabling auto redeploy
#### Enabling Automatic Redeployment
To enable auto redeployment you simply have to add the following annotation to the deployment, statefulset, or daemonset that consumes a managed secret.
@@ -910,7 +1056,173 @@ spec:
Then, for each deployment that has this annotation present, a rolling update will be triggered.
</Info>
## Propagating labels & annotations
## Using Managed ConfigMap In Your Deployment
To make use of the managed ConfigMap created by the operator into your deployment can be achieved through several methods.
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.
</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
```
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
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
````
</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
```yaml
env:
- name: CONFIG_NAME # The environment variable's name which is made available in the container
valueFrom:
configMapKeyRef:
name: managed-configmap # managed configmap name
key: SOME_CONFIG_KEY # The name of the key which exists in the managed configmap
```
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
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
```
</Accordion>
<Accordion title="volumes">
This will allow you to create a volume on your container which comprises of files holding the secrets in your managed kubernetes secret
```yaml
volumes:
- name: configmaps-volume-name # The name of the volume under which configmaps will be stored
configMap:
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
```yaml
volumeMounts:
- name: configmaps-volume-name
mountPath: /etc/config
readOnly: true
```
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
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
```
</Accordion>
The definition file of the Kubernetes secret for the CA certificate can be structured like the following:
```yaml
apiVersion: v1
kind: Secret
metadata:
name: custom-ca-certificate
type: Opaque
stringData:
ca.crt: |
-----BEGIN CERTIFICATE-----
MIIEZzCCA0+gAwIBAgIUDk9+HZcMHppiNy0TvoBg8/aMEqIwDQYJKoZIhvcNAQEL
...
BQAwDTELMAkGA1UEChMCUEgwHhcNMjQxMDI1MTU0MjAzWhcNMjUxMDI1MjE0MjAz
-----END CERTIFICATE-----
```
## Propagating Labels & Annotations
The operator will transfer all labels & annotations present on the `InfisicalSecret` CRD to the managed Kubernetes secret to be created.
Thus, if a specific label is required on the resulting secret, it can be applied as demonstrated in the following example:
@@ -949,5 +1261,4 @@ metadata:
namespace: default
type: Opaque
```
</Accordion>

View File

@@ -14,11 +14,21 @@
git
lazygit
go
python312Full
nodejs_20
nodePackages.prettier
infisical
];
env = {
GOROOT = "${pkgs.go}/share/go";
};
shellHook = ''
export GOPATH="$(pwd)/.go"
mkdir -p "$GOPATH"
'';
};
};
}

View File

@@ -23,7 +23,11 @@ export const ROUTE_PATHS = Object.freeze({
),
SecretSharing: setRoute(
"/organization/secret-sharing",
"/_authenticate/_inject-org-details/_org-layout/organization/secret-sharing"
"/_authenticate/_inject-org-details/_org-layout/organization/secret-sharing/"
),
SecretSharingSettings: setRoute(
"/organization/secret-sharing/settings",
"/_authenticate/_inject-org-details/_org-layout/organization/secret-sharing/settings"
),
SettingsPage: setRoute(
"/organization/settings",

View File

@@ -34,13 +34,18 @@ export enum OrgPermissionSubjects {
ProjectTemplates = "project-templates",
AppConnections = "app-connections",
Kmip = "kmip",
Gateway = "gateway"
Gateway = "gateway",
SecretShare = "secret-share"
}
export enum OrgPermissionAdminConsoleAction {
AccessAllProjects = "access-all-projects"
}
export enum OrgPermissionSecretShareAction {
ManageSettings = "manage-settings"
}
export enum OrgPermissionAppConnectionActions {
Read = "read",
Create = "create",
@@ -78,7 +83,8 @@ export type OrgPermissionSet =
| [OrgPermissionActions, OrgPermissionSubjects.ProjectTemplates]
| [OrgPermissionAppConnectionActions, OrgPermissionSubjects.AppConnections]
| [OrgPermissionKmipActions, OrgPermissionSubjects.Kmip]
| [OrgGatewayPermissionActions, OrgPermissionSubjects.Gateway];
| [OrgGatewayPermissionActions, OrgPermissionSubjects.Gateway]
| [OrgPermissionSecretShareAction, OrgPermissionSubjects.SecretShare];
// TODO(scott): add back once org UI refactored
// | [
// OrgPermissionAppConnectionActions,

View File

@@ -109,7 +109,8 @@ export const useUpdateOrg = () => {
orgId,
defaultMembershipRoleSlug,
enforceMfa,
selectedMfaMethod
selectedMfaMethod,
allowSecretSharingOutsideOrganization
}) => {
return apiRequest.patch(`/api/v1/organization/${orgId}`, {
name,
@@ -118,7 +119,8 @@ export const useUpdateOrg = () => {
slug,
defaultMembershipRoleSlug,
enforceMfa,
selectedMfaMethod
selectedMfaMethod,
allowSecretSharingOutsideOrganization
});
},
onSuccess: () => {

View File

@@ -15,6 +15,7 @@ export type Organization = {
defaultMembershipRole: string;
enforceMfa: boolean;
selectedMfaMethod?: MfaMethod;
allowSecretSharingOutsideOrganization?: boolean;
};
export type UpdateOrgDTO = {
@@ -26,6 +27,7 @@ export type UpdateOrgDTO = {
defaultMembershipRoleSlug?: string;
enforceMfa?: boolean;
selectedMfaMethod?: MfaMethod;
allowSecretSharingOutsideOrganization?: boolean;
};
export type BillingDetails = {

View File

@@ -1,30 +1,33 @@
import { useTranslation } from "react-i18next";
import { faMobile } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { Link, linkOptions, Outlet, useLocation, useRouterState } from "@tanstack/react-router";
import { linkOptions, Outlet, useLocation, useRouterState } from "@tanstack/react-router";
import { AnimatePresence, motion } from "framer-motion";
import { twMerge } from "tailwind-merge";
import { CreateOrgModal } from "@app/components/organization/CreateOrgModal";
import { Banner } from "@app/components/page-frames/Banner";
import {
BreadcrumbContainer,
Menu,
MenuGroup,
MenuItem,
TBreadcrumbFormat
} from "@app/components/v2";
import { useServerConfig } from "@app/context";
import { BreadcrumbContainer, TBreadcrumbFormat } from "@app/components/v2";
import { OrgPermissionSubjects, useOrgPermission, useServerConfig } from "@app/context";
import { OrgPermissionSecretShareAction } from "@app/context/OrgPermissionContext/types";
import { usePopUp } from "@app/hooks";
import { InsecureConnectionBanner } from "./components/InsecureConnectionBanner";
import { MinimizedOrgSidebar } from "./components/MinimizedOrgSidebar";
import { SidebarHeader } from "./components/SidebarHeader";
import { DefaultSideBar, SecretSharingSideBar } from "./ProductsSideBar";
export const OrganizationLayout = () => {
const matches = useRouterState({ select: (s) => s.matches.at(-1)?.context });
const location = useLocation();
const { config } = useServerConfig();
const { permission } = useOrgPermission();
const shouldShowProductsSidebar = permission.can(
OrgPermissionSecretShareAction.ManageSettings,
OrgPermissionSubjects.SecretShare
);
const isOrganizationSpecificPage = location.pathname.startsWith("/organization");
const breadcrumbs =
isOrganizationSpecificPage && matches && "breadcrumbs" in matches
@@ -35,16 +38,23 @@ export const OrganizationLayout = () => {
const { t } = useTranslation();
const isSecretSharingPage = (
[
linkOptions({ to: "/organization/secret-sharing" }).to,
linkOptions({ to: "/organization/secret-sharing/settings" }).to
] as string[]
).includes(location.pathname);
const shouldShowOrgSidebar =
location.pathname.startsWith("/organization") &&
(!isSecretSharingPage || shouldShowProductsSidebar) &&
!(
[
linkOptions({ to: "/organization/secret-manager/overview" }).to,
linkOptions({ to: "/organization/cert-manager/overview" }).to,
linkOptions({ to: "/organization/ssh/overview" }).to,
linkOptions({ to: "/organization/kms/overview" }).to,
linkOptions({ to: "/organization/secret-scanning" }).to,
linkOptions({ to: "/organization/secret-sharing" }).to
linkOptions({ to: "/organization/secret-scanning" }).to
] as string[]
).includes(location.pathname);
@@ -73,58 +83,7 @@ export const OrganizationLayout = () => {
<div className="p-2 pt-3">
<SidebarHeader />
</div>
<Menu>
<MenuGroup title="Organization Control">
<Link to="/organization/audit-logs">
{({ isActive }) => (
<MenuItem isSelected={isActive} icon="moving-block">
Audit Logs
</MenuItem>
)}
</Link>
{(window.location.origin.includes("https://app.infisical.com") ||
window.location.origin.includes("https://eu.infisical.com") ||
window.location.origin.includes("https://gamma.infisical.com")) && (
<Link to="/organization/billing">
{({ isActive }) => (
<MenuItem isSelected={isActive} icon="spinning-coin">
Usage & Billing
</MenuItem>
)}
</Link>
)}
</MenuGroup>
<MenuGroup title="Other">
<Link to="/organization/access-management">
{({ isActive }) => (
<MenuItem isSelected={isActive} icon="groups">
Access Control
</MenuItem>
)}
</Link>
<Link to="/organization/app-connections">
{({ isActive }) => (
<MenuItem isSelected={isActive} icon="jigsaw-puzzle">
App Connections
</MenuItem>
)}
</Link>
<Link to="/organization/gateways">
{({ isActive }) => (
<MenuItem isSelected={isActive} icon="gateway" iconMode="reverse">
Gateways
</MenuItem>
)}
</Link>
<Link to="/organization/settings">
{({ isActive }) => (
<MenuItem isSelected={isActive} icon="toggle-settings">
Organization Settings
</MenuItem>
)}
</Link>
</MenuGroup>
</Menu>
{isSecretSharingPage ? <SecretSharingSideBar /> : <DefaultSideBar />}
</nav>
</motion.div>
)}

View File

@@ -0,0 +1,58 @@
import { Link } from "@tanstack/react-router";
import { Menu, MenuGroup, MenuItem } from "@app/components/v2";
export const DefaultSideBar = () => (
<Menu>
<MenuGroup title="Organization Control">
<Link to="/organization/audit-logs">
{({ isActive }) => (
<MenuItem isSelected={isActive} icon="moving-block">
Audit Logs
</MenuItem>
)}
</Link>
{(window.location.origin.includes("https://app.infisical.com") ||
window.location.origin.includes("https://eu.infisical.com") ||
window.location.origin.includes("https://gamma.infisical.com")) && (
<Link to="/organization/billing">
{({ isActive }) => (
<MenuItem isSelected={isActive} icon="spinning-coin">
Usage & Billing
</MenuItem>
)}
</Link>
)}
</MenuGroup>
<MenuGroup title="Other">
<Link to="/organization/access-management">
{({ isActive }) => (
<MenuItem isSelected={isActive} icon="groups">
Access Control
</MenuItem>
)}
</Link>
<Link to="/organization/app-connections">
{({ isActive }) => (
<MenuItem isSelected={isActive} icon="jigsaw-puzzle">
App Connections
</MenuItem>
)}
</Link>
<Link to="/organization/gateways">
{({ isActive }) => (
<MenuItem isSelected={isActive} icon="gateway" iconMode="reverse">
Gateways
</MenuItem>
)}
</Link>
<Link to="/organization/settings">
{({ isActive }) => (
<MenuItem isSelected={isActive} icon="toggle-settings">
Organization Settings
</MenuItem>
)}
</Link>
</MenuGroup>
</Menu>
);

View File

@@ -0,0 +1,34 @@
import { Link, useMatchRoute } from "@tanstack/react-router";
import { Menu, MenuGroup, MenuItem } from "@app/components/v2";
export const SecretSharingSideBar = () => {
const matchRoute = useMatchRoute();
const isOverviewActive = !!matchRoute({
to: "/organization/secret-sharing",
fuzzy: false
});
return (
<Menu>
<MenuGroup title="Overview">
<Link to="/organization/secret-sharing">
{() => (
<MenuItem isSelected={isOverviewActive} icon="lock-closed">
Secret Sharing
</MenuItem>
)}
</Link>
</MenuGroup>
<MenuGroup title="Other">
<Link to="/organization/secret-sharing/settings">
{({ isActive }) => (
<MenuItem isSelected={isActive} icon="toggle-settings">
Settings
</MenuItem>
)}
</Link>
</MenuGroup>
</Menu>
);
};

View File

@@ -0,0 +1,2 @@
export * from "./DefaultSideBar";
export * from "./SecretSharingSideBar";

View File

@@ -5,7 +5,8 @@ import { OrgPermissionSubjects } from "@app/context";
import {
OrgGatewayPermissionActions,
OrgPermissionAppConnectionActions,
OrgPermissionKmipActions
OrgPermissionKmipActions,
OrgPermissionSecretShareAction
} from "@app/context/OrgPermissionContext/types";
import { TPermission } from "@app/hooks/api/roles/types";
@@ -50,6 +51,12 @@ const adminConsolePermissionSchmea = z
})
.optional();
const secretSharingPermissionSchema = z
.object({
[OrgPermissionSecretShareAction.ManageSettings]: z.boolean().optional()
})
.optional();
export const formSchema = z.object({
name: z.string().trim(),
description: z.string().trim().optional(),
@@ -83,7 +90,8 @@ export const formSchema = z.object({
[OrgPermissionSubjects.ProjectTemplates]: generalPermissionSchema,
"app-connections": appConnectionsPermissionSchema,
kmip: kmipPermissionSchema,
gateway: orgGatewayPermissionSchema
gateway: orgGatewayPermissionSchema,
"secret-share": secretSharingPermissionSchema
})
.optional()
});

View File

@@ -0,0 +1,130 @@
import { useEffect, useMemo } from "react";
import { Control, Controller, UseFormSetValue, useWatch } from "react-hook-form";
import { faChevronDown, faChevronRight } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { createNotification } from "@app/components/notifications";
import { Checkbox, Select, SelectItem, Td, Tr } from "@app/components/v2";
import { useToggle } from "@app/hooks";
import { TFormSchema } from "../OrgRoleModifySection.utils";
type Props = {
isEditable: boolean;
setValue: UseFormSetValue<TFormSchema>;
control: Control<TFormSchema>;
};
enum Permission {
NoAccess = "no-access",
Custom = "custom"
}
const PERMISSION_ACTIONS = [{ action: "manage-settings", label: "Manage settings" }] as const;
export const OrgPermissionSecretShareRow = ({ isEditable, control, setValue }: Props) => {
const [isRowExpanded, setIsRowExpanded] = useToggle();
const [isCustom, setIsCustom] = useToggle();
const rule = useWatch({
control,
name: "permissions.secret-share"
});
const selectedPermissionCategory = useMemo(() => {
if (rule?.["manage-settings"]) {
return Permission.Custom;
}
return Permission.NoAccess;
}, [rule, isCustom]);
useEffect(() => {
if (selectedPermissionCategory === Permission.Custom) setIsCustom.on();
else setIsCustom.off();
}, [selectedPermissionCategory]);
useEffect(() => {
const isRowCustom = selectedPermissionCategory === Permission.Custom;
if (isRowCustom) {
setIsRowExpanded.on();
}
}, []);
const handlePermissionChange = (val: Permission) => {
if (!val) return;
if (val === Permission.Custom) {
setIsRowExpanded.on();
setIsCustom.on();
return;
}
setIsCustom.off();
if (val === Permission.NoAccess) {
setValue("permissions.secret-share", { "manage-settings": false }, { shouldDirty: true });
}
};
return (
<>
<Tr
className="h-10 cursor-pointer transition-colors duration-100 hover:bg-mineshaft-700"
onClick={() => setIsRowExpanded.toggle()}
>
<Td>
<FontAwesomeIcon icon={isRowExpanded ? faChevronDown : faChevronRight} />
</Td>
<Td>Secret Share</Td>
<Td>
<Select
value={selectedPermissionCategory}
className="w-40 bg-mineshaft-600"
dropdownContainerClassName="border border-mineshaft-600 bg-mineshaft-800"
onValueChange={handlePermissionChange}
isDisabled={!isEditable}
>
<SelectItem value={Permission.NoAccess}>No Access</SelectItem>
<SelectItem value={Permission.Custom}>Custom</SelectItem>
</Select>
</Td>
</Tr>
{isRowExpanded && (
<Tr>
<Td
colSpan={3}
className={`bg-bunker-600 px-0 py-0 ${isRowExpanded && "border-mineshaft-500 p-8"}`}
>
<div className="grid grid-cols-3 gap-4">
{PERMISSION_ACTIONS.map(({ action, label }) => {
return (
<Controller
name={`permissions.secret-share.${action}`}
key={`permissions.secret-share.${action}`}
control={control}
render={({ field }) => (
<Checkbox
isChecked={field.value}
onCheckedChange={(e) => {
if (!isEditable) {
createNotification({
type: "error",
text: "Failed to update default role"
});
return;
}
field.onChange(e);
}}
id={`permissions.secret-share.${action}`}
>
{label}
</Checkbox>
)}
/>
);
})}
</div>
</Td>
</Tr>
)}
</>
);
};

View File

@@ -74,7 +74,7 @@ type Props = {
title: string;
formName: keyof Omit<
Exclude<TFormSchema["permissions"], undefined>,
"workspace" | "organization-admin-console" | "kmip" | "gateway"
"workspace" | "organization-admin-console" | "kmip" | "gateway" | "secret-share"
>;
setValue: UseFormSetValue<TFormSchema>;
control: Control<TFormSchema>;

View File

@@ -16,6 +16,7 @@ import {
import { OrgPermissionAdminConsoleRow } from "./OrgPermissionAdminConsoleRow";
import { OrgGatewayPermissionRow } from "./OrgPermissionGatewayRow";
import { OrgPermissionKmipRow } from "./OrgPermissionKmipRow";
import { OrgPermissionSecretShareRow } from "./OrgPermissionSecretShareRow";
import { OrgRoleWorkspaceRow } from "./OrgRoleWorkspaceRow";
import { RolePermissionRow } from "./RolePermissionRow";
@@ -177,6 +178,11 @@ export const RolePermissionsSection = ({ roleId }: Props) => {
setValue={setValue}
isEditable={isCustomRole}
/>
<OrgPermissionSecretShareRow
control={control}
setValue={setValue}
isEditable={isCustomRole}
/>
<OrgRoleWorkspaceRow
control={control}
setValue={setValue}

View File

@@ -1,4 +1,5 @@
import { Modal, ModalContent } from "@app/components/v2";
import { useOrganization } from "@app/context";
import { UsePopUpState } from "@app/hooks/usePopUp";
import { ShareSecretForm } from "@app/pages/public/ShareSecretPage/components";
@@ -11,6 +12,7 @@ type Props = {
};
export const AddShareSecretModal = ({ popUp, handlePopUpToggle }: Props) => {
const { currentOrg } = useOrganization();
return (
<Modal
isOpen={popUp?.createSharedSecret?.isOpen}
@@ -25,6 +27,9 @@ export const AddShareSecretModal = ({ popUp, handlePopUpToggle }: Props) => {
<ShareSecretForm
isPublic={false}
value={(popUp.createSharedSecret.data as { value?: string })?.value}
allowSecretSharingOutsideOrganization={
currentOrg?.allowSecretSharingOutsideOrganization ?? true
}
/>
</ModalContent>
</Modal>

View File

@@ -11,7 +11,7 @@ const SecretSharingQueryParams = z.object({
});
export const Route = createFileRoute(
"/_authenticate/_inject-org-details/_org-layout/organization/secret-sharing"
"/_authenticate/_inject-org-details/_org-layout/organization/secret-sharing/"
)({
component: SecretSharingPage,

View File

@@ -0,0 +1,35 @@
import { Helmet } from "react-helmet";
import { useTranslation } from "react-i18next";
import { PageHeader } from "@app/components/v2";
import {
OrgPermissionSecretShareAction,
OrgPermissionSubjects
} from "@app/context/OrgPermissionContext/types";
import { withPermission } from "@app/hoc";
import { SecretSharingSettingsTabGroup } from "./components";
export const SecretSharingSettingsPage = withPermission(
() => {
const { t } = useTranslation();
return (
<>
<Helmet>
<title>{t("common.head-title", { title: t("settings.org.title") })}</title>
</Helmet>
<div className="flex w-full justify-center bg-bunker-800 text-white">
<div className="w-full max-w-7xl">
<PageHeader title={t("settings.org.title")} />
<SecretSharingSettingsTabGroup />
</div>
</div>
</>
);
},
{
action: OrgPermissionSecretShareAction.ManageSettings,
subject: OrgPermissionSubjects.SecretShare
}
);

View File

@@ -0,0 +1,58 @@
import { createNotification } from "@app/components/notifications";
import { OrgPermissionCan } from "@app/components/permissions";
import { Switch } from "@app/components/v2";
import { OrgPermissionActions, OrgPermissionSubjects, useOrganization } from "@app/context";
import { useUpdateOrg } from "@app/hooks/api";
export const SecretSharingAllowShareToAnyone = () => {
const { currentOrg } = useOrganization();
const { mutateAsync } = useUpdateOrg();
const handleSecretSharingToggle = async (value: boolean) => {
try {
if (!currentOrg?.id) return;
await mutateAsync({
orgId: currentOrg.id,
allowSecretSharingOutsideOrganization: value
});
createNotification({
text: `Successfully ${value ? "enabled" : "disabled"} secret sharing to members outside of this organization`,
type: "success"
});
} catch (err) {
console.error(err);
createNotification({
text: (err as { response: { data: { message: string } } }).response.data.message,
type: "error"
});
}
};
return (
<div className="mb-4 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-6">
<div className="py-4">
<div className="mb-2 flex justify-between">
<h3 className="text-md text-mineshaft-100">
Allow sharing secrets to members outside of this organization
</h3>
<OrgPermissionCan I={OrgPermissionActions.Edit} a={OrgPermissionSubjects.Settings}>
{(isAllowed) => (
<Switch
id="enable-secret-sharing-outside-org"
onCheckedChange={(value) => handleSecretSharingToggle(value)}
isChecked={currentOrg?.allowSecretSharingOutsideOrganization ?? false}
isDisabled={!isAllowed}
/>
)}
</OrgPermissionCan>
</div>
<p className="text-sm text-mineshaft-300">
If enabled, team members will be able to share secrets to members outside of this
organization
</p>
</div>
</div>
);
};

View File

@@ -0,0 +1 @@
export { SecretSharingAllowShareToAnyone } from "./SecretSharingAllowShareToAnyone";

View File

@@ -0,0 +1,9 @@
import { SecretSharingAllowShareToAnyone } from "../SecretSharingAllowShareToAnyone";
export const SecretSharingSettingsGeneralTab = () => {
return (
<div className="w-full">
<SecretSharingAllowShareToAnyone />
</div>
);
};

View File

@@ -0,0 +1 @@
export { SecretSharingSettingsGeneralTab } from "./SecretSharingSettingsGeneralTab";

View File

@@ -0,0 +1,39 @@
import { useState } from "react";
import { useSearch } from "@tanstack/react-router";
import { Tab, TabList, TabPanel, Tabs } from "@app/components/v2";
import { ROUTE_PATHS } from "@app/const/routes";
import { SecretSharingSettingsGeneralTab } from "../SecretSharingSettingsGeneralTab";
export const SecretSharingSettingsTabGroup = () => {
const search = useSearch({
from: ROUTE_PATHS.Organization.SecretSharingSettings.id
});
const tabs = [
{
name: "General",
key: "tab-secret-sharing-general",
component: SecretSharingSettingsGeneralTab
}
];
const [selectedTab, setSelectedTab] = useState(search.selectedTab || tabs[0].key);
return (
<Tabs value={selectedTab} onValueChange={setSelectedTab}>
<TabList>
{tabs.map((tab) => (
<Tab value={tab.key} key={tab.key}>
{tab.name}
</Tab>
))}
</TabList>
{tabs.map(({ key, component: Component }) => (
<TabPanel value={key} key={`tab-panel-${key}`}>
<Component />
</TabPanel>
))}
</Tabs>
);
};

View File

@@ -0,0 +1 @@
export { SecretSharingSettingsTabGroup } from "./SecretSharingSettingsTabGroup";

View File

@@ -0,0 +1 @@
export { SecretSharingSettingsTabGroup } from "./SecretSharingSettingsTabGroup";

View File

@@ -0,0 +1,37 @@
import { faHome } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { createFileRoute, linkOptions, stripSearchParams } from "@tanstack/react-router";
import { zodValidator } from "@tanstack/zod-adapter";
import { z } from "zod";
import { SecretSharingSettingsPage } from "./SecretSharingSettingsPage";
const SettingsPageQueryParams = z.object({
selectedTab: z.string().catch("")
});
export const Route = createFileRoute(
"/_authenticate/_inject-org-details/_org-layout/organization/secret-sharing/settings"
)({
component: SecretSharingSettingsPage,
validateSearch: zodValidator(SettingsPageQueryParams),
search: {
middlewares: [stripSearchParams({ selectedTab: "" })]
},
context: () => ({
breadcrumbs: [
{
label: "Home",
icon: () => <FontAwesomeIcon icon={faHome} />,
link: linkOptions({ to: "/" })
},
{
label: "Secret Sharing",
link: linkOptions({ to: "/organization/secret-sharing" })
},
{
label: "Settings"
}
]
})
});

View File

@@ -41,9 +41,14 @@ export type FormData = z.infer<typeof schema>;
type Props = {
isPublic: boolean; // whether or not this is a public (non-authenticated) secret sharing form
value?: string;
allowSecretSharingOutsideOrganization?: boolean;
};
export const ShareSecretForm = ({ isPublic, value }: Props) => {
export const ShareSecretForm = ({
isPublic,
value,
allowSecretSharingOutsideOrganization = true
}: Props) => {
const [secretLink, setSecretLink] = useState("");
const [, isCopyingSecret, setCopyTextSecret] = useTimedReset<string>({
initialState: "Copy to clipboard"
@@ -230,7 +235,9 @@ export const ShareSecretForm = ({ isPublic, value }: Props) => {
onValueChange={(e) => onChange(e)}
className="w-full"
>
<SelectItem value={SecretSharingAccessType.Anyone}>Anyone</SelectItem>
{allowSecretSharingOutsideOrganization && (
<SelectItem value={SecretSharingAccessType.Anyone}>Anyone</SelectItem>
)}
<SelectItem value={SecretSharingAccessType.Organization}>
People within your organization
</SelectItem>

View File

@@ -42,7 +42,6 @@ import { Route as authProviderSuccessPageRouteImport } from './pages/auth/Provid
import { Route as authProviderErrorPageRouteImport } from './pages/auth/ProviderErrorPage/route'
import { Route as userPersonalSettingsPageRouteImport } from './pages/user/PersonalSettingsPage/route'
import { Route as organizationSettingsPageRouteImport } from './pages/organization/SettingsPage/route'
import { Route as organizationSecretSharingPageRouteImport } from './pages/organization/SecretSharingPage/route'
import { Route as organizationSecretScanningPageRouteImport } from './pages/organization/SecretScanningPage/route'
import { Route as organizationBillingPageRouteImport } from './pages/organization/BillingPage/route'
import { Route as organizationAuditLogsPageRouteImport } from './pages/organization/AuditLogsPage/route'
@@ -54,6 +53,7 @@ import { Route as secretManagerLayoutImport } from './pages/secret-manager/layou
import { Route as kmsLayoutImport } from './pages/kms/layout'
import { Route as certManagerLayoutImport } from './pages/cert-manager/layout'
import { Route as organizationSshOverviewPageRouteImport } from './pages/organization/SshOverviewPage/route'
import { Route as organizationSecretSharingSettingsPageRouteImport } from './pages/organization/SecretSharingSettingsPage/route'
import { Route as organizationSecretManagerOverviewPageRouteImport } from './pages/organization/SecretManagerOverviewPage/route'
import { Route as organizationRoleByIDPageRouteImport } from './pages/organization/RoleByIDPage/route'
import { Route as organizationUserDetailsByIDPageRouteImport } from './pages/organization/UserDetailsByIDPage/route'
@@ -61,6 +61,7 @@ import { Route as organizationKmsOverviewPageRouteImport } from './pages/organiz
import { Route as organizationIdentityDetailsByIDPageRouteImport } from './pages/organization/IdentityDetailsByIDPage/route'
import { Route as organizationGroupDetailsByIDPageRouteImport } from './pages/organization/GroupDetailsByIDPage/route'
import { Route as organizationCertManagerOverviewPageRouteImport } from './pages/organization/CertManagerOverviewPage/route'
import { Route as organizationSecretSharingPageRouteImport } from './pages/organization/SecretSharingPage/route'
import { Route as organizationGatewaysGatewayListPageRouteImport } from './pages/organization/Gateways/GatewayListPage/route'
import { Route as organizationAppConnectionsAppConnectionsPageRouteImport } from './pages/organization/AppConnections/AppConnectionsPage/route'
import { Route as projectAccessControlPageRouteSshImport } from './pages/project/AccessControlPage/route-ssh'
@@ -211,6 +212,10 @@ const AuthenticateInjectOrgDetailsOrgLayoutSecretManagerProjectIdImport =
createFileRoute(
'/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId',
)()
const AuthenticateInjectOrgDetailsOrgLayoutOrganizationSecretSharingImport =
createFileRoute(
'/_authenticate/_inject-org-details/_org-layout/organization/secret-sharing',
)()
const AuthenticateInjectOrgDetailsOrgLayoutOrganizationGatewaysImport =
createFileRoute(
'/_authenticate/_inject-org-details/_org-layout/organization/gateways',
@@ -467,6 +472,14 @@ const AuthenticateInjectOrgDetailsOrgLayoutSecretManagerProjectIdRoute =
getParentRoute: () => organizationLayoutRoute,
} as any)
const AuthenticateInjectOrgDetailsOrgLayoutOrganizationSecretSharingRoute =
AuthenticateInjectOrgDetailsOrgLayoutOrganizationSecretSharingImport.update({
id: '/secret-sharing',
path: '/secret-sharing',
getParentRoute: () =>
AuthenticateInjectOrgDetailsOrgLayoutOrganizationRoute,
} as any)
const AuthenticateInjectOrgDetailsOrgLayoutOrganizationGatewaysRoute =
AuthenticateInjectOrgDetailsOrgLayoutOrganizationGatewaysImport.update({
id: '/gateways',
@@ -505,14 +518,6 @@ const organizationSettingsPageRouteRoute =
AuthenticateInjectOrgDetailsOrgLayoutOrganizationRoute,
} as any)
const organizationSecretSharingPageRouteRoute =
organizationSecretSharingPageRouteImport.update({
id: '/secret-sharing',
path: '/secret-sharing',
getParentRoute: () =>
AuthenticateInjectOrgDetailsOrgLayoutOrganizationRoute,
} as any)
const organizationSecretScanningPageRouteRoute =
organizationSecretScanningPageRouteImport.update({
id: '/secret-scanning',
@@ -590,6 +595,14 @@ const organizationSshOverviewPageRouteRoute =
AuthenticateInjectOrgDetailsOrgLayoutOrganizationRoute,
} as any)
const organizationSecretSharingSettingsPageRouteRoute =
organizationSecretSharingSettingsPageRouteImport.update({
id: '/settings',
path: '/settings',
getParentRoute: () =>
AuthenticateInjectOrgDetailsOrgLayoutOrganizationSecretSharingRoute,
} as any)
const organizationSecretManagerOverviewPageRouteRoute =
organizationSecretManagerOverviewPageRouteImport.update({
id: '/secret-manager/overview',
@@ -646,6 +659,14 @@ const organizationCertManagerOverviewPageRouteRoute =
AuthenticateInjectOrgDetailsOrgLayoutOrganizationRoute,
} as any)
const organizationSecretSharingPageRouteRoute =
organizationSecretSharingPageRouteImport.update({
id: '/',
path: '/',
getParentRoute: () =>
AuthenticateInjectOrgDetailsOrgLayoutOrganizationSecretSharingRoute,
} as any)
const organizationGatewaysGatewayListPageRouteRoute =
organizationGatewaysGatewayListPageRouteImport.update({
id: '/',
@@ -1887,13 +1908,6 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof organizationSecretScanningPageRouteImport
parentRoute: typeof AuthenticateInjectOrgDetailsOrgLayoutOrganizationImport
}
'/_authenticate/_inject-org-details/_org-layout/organization/secret-sharing': {
id: '/_authenticate/_inject-org-details/_org-layout/organization/secret-sharing'
path: '/secret-sharing'
fullPath: '/organization/secret-sharing'
preLoaderRoute: typeof organizationSecretSharingPageRouteImport
parentRoute: typeof AuthenticateInjectOrgDetailsOrgLayoutOrganizationImport
}
'/_authenticate/_inject-org-details/_org-layout/organization/settings': {
id: '/_authenticate/_inject-org-details/_org-layout/organization/settings'
path: '/settings'
@@ -1929,6 +1943,13 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof AuthenticateInjectOrgDetailsOrgLayoutOrganizationGatewaysImport
parentRoute: typeof AuthenticateInjectOrgDetailsOrgLayoutOrganizationImport
}
'/_authenticate/_inject-org-details/_org-layout/organization/secret-sharing': {
id: '/_authenticate/_inject-org-details/_org-layout/organization/secret-sharing'
path: '/secret-sharing'
fullPath: '/organization/secret-sharing'
preLoaderRoute: typeof AuthenticateInjectOrgDetailsOrgLayoutOrganizationSecretSharingImport
parentRoute: typeof AuthenticateInjectOrgDetailsOrgLayoutOrganizationImport
}
'/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId': {
id: '/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId'
path: '/secret-manager/$projectId'
@@ -1957,6 +1978,13 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof organizationGatewaysGatewayListPageRouteImport
parentRoute: typeof AuthenticateInjectOrgDetailsOrgLayoutOrganizationGatewaysImport
}
'/_authenticate/_inject-org-details/_org-layout/organization/secret-sharing/': {
id: '/_authenticate/_inject-org-details/_org-layout/organization/secret-sharing/'
path: '/'
fullPath: '/organization/secret-sharing/'
preLoaderRoute: typeof organizationSecretSharingPageRouteImport
parentRoute: typeof AuthenticateInjectOrgDetailsOrgLayoutOrganizationSecretSharingImport
}
'/_authenticate/_inject-org-details/_org-layout/organization/cert-manager/overview': {
id: '/_authenticate/_inject-org-details/_org-layout/organization/cert-manager/overview'
path: '/cert-manager/overview'
@@ -2006,6 +2034,13 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof organizationSecretManagerOverviewPageRouteImport
parentRoute: typeof AuthenticateInjectOrgDetailsOrgLayoutOrganizationImport
}
'/_authenticate/_inject-org-details/_org-layout/organization/secret-sharing/settings': {
id: '/_authenticate/_inject-org-details/_org-layout/organization/secret-sharing/settings'
path: '/settings'
fullPath: '/organization/secret-sharing/settings'
preLoaderRoute: typeof organizationSecretSharingSettingsPageRouteImport
parentRoute: typeof AuthenticateInjectOrgDetailsOrgLayoutOrganizationSecretSharingImport
}
'/_authenticate/_inject-org-details/_org-layout/organization/ssh/overview': {
id: '/_authenticate/_inject-org-details/_org-layout/organization/ssh/overview'
path: '/ssh/overview'
@@ -2965,16 +3000,34 @@ const AuthenticateInjectOrgDetailsOrgLayoutOrganizationGatewaysRouteWithChildren
AuthenticateInjectOrgDetailsOrgLayoutOrganizationGatewaysRouteChildren,
)
interface AuthenticateInjectOrgDetailsOrgLayoutOrganizationSecretSharingRouteChildren {
organizationSecretSharingPageRouteRoute: typeof organizationSecretSharingPageRouteRoute
organizationSecretSharingSettingsPageRouteRoute: typeof organizationSecretSharingSettingsPageRouteRoute
}
const AuthenticateInjectOrgDetailsOrgLayoutOrganizationSecretSharingRouteChildren: AuthenticateInjectOrgDetailsOrgLayoutOrganizationSecretSharingRouteChildren =
{
organizationSecretSharingPageRouteRoute:
organizationSecretSharingPageRouteRoute,
organizationSecretSharingSettingsPageRouteRoute:
organizationSecretSharingSettingsPageRouteRoute,
}
const AuthenticateInjectOrgDetailsOrgLayoutOrganizationSecretSharingRouteWithChildren =
AuthenticateInjectOrgDetailsOrgLayoutOrganizationSecretSharingRoute._addFileChildren(
AuthenticateInjectOrgDetailsOrgLayoutOrganizationSecretSharingRouteChildren,
)
interface AuthenticateInjectOrgDetailsOrgLayoutOrganizationRouteChildren {
organizationAccessManagementPageRouteRoute: typeof organizationAccessManagementPageRouteRoute
organizationAdminPageRouteRoute: typeof organizationAdminPageRouteRoute
organizationAuditLogsPageRouteRoute: typeof organizationAuditLogsPageRouteRoute
organizationBillingPageRouteRoute: typeof organizationBillingPageRouteRoute
organizationSecretScanningPageRouteRoute: typeof organizationSecretScanningPageRouteRoute
organizationSecretSharingPageRouteRoute: typeof organizationSecretSharingPageRouteRoute
organizationSettingsPageRouteRoute: typeof organizationSettingsPageRouteRoute
AuthenticateInjectOrgDetailsOrgLayoutOrganizationAppConnectionsRoute: typeof AuthenticateInjectOrgDetailsOrgLayoutOrganizationAppConnectionsRouteWithChildren
AuthenticateInjectOrgDetailsOrgLayoutOrganizationGatewaysRoute: typeof AuthenticateInjectOrgDetailsOrgLayoutOrganizationGatewaysRouteWithChildren
AuthenticateInjectOrgDetailsOrgLayoutOrganizationSecretSharingRoute: typeof AuthenticateInjectOrgDetailsOrgLayoutOrganizationSecretSharingRouteWithChildren
organizationCertManagerOverviewPageRouteRoute: typeof organizationCertManagerOverviewPageRouteRoute
organizationGroupDetailsByIDPageRouteRoute: typeof organizationGroupDetailsByIDPageRouteRoute
organizationIdentityDetailsByIDPageRouteRoute: typeof organizationIdentityDetailsByIDPageRouteRoute
@@ -2994,13 +3047,13 @@ const AuthenticateInjectOrgDetailsOrgLayoutOrganizationRouteChildren: Authentica
organizationBillingPageRouteRoute: organizationBillingPageRouteRoute,
organizationSecretScanningPageRouteRoute:
organizationSecretScanningPageRouteRoute,
organizationSecretSharingPageRouteRoute:
organizationSecretSharingPageRouteRoute,
organizationSettingsPageRouteRoute: organizationSettingsPageRouteRoute,
AuthenticateInjectOrgDetailsOrgLayoutOrganizationAppConnectionsRoute:
AuthenticateInjectOrgDetailsOrgLayoutOrganizationAppConnectionsRouteWithChildren,
AuthenticateInjectOrgDetailsOrgLayoutOrganizationGatewaysRoute:
AuthenticateInjectOrgDetailsOrgLayoutOrganizationGatewaysRouteWithChildren,
AuthenticateInjectOrgDetailsOrgLayoutOrganizationSecretSharingRoute:
AuthenticateInjectOrgDetailsOrgLayoutOrganizationSecretSharingRouteWithChildren,
organizationCertManagerOverviewPageRouteRoute:
organizationCertManagerOverviewPageRouteRoute,
organizationGroupDetailsByIDPageRouteRoute:
@@ -3672,16 +3725,17 @@ export interface FileRoutesByFullPath {
'/organization/audit-logs': typeof organizationAuditLogsPageRouteRoute
'/organization/billing': typeof organizationBillingPageRouteRoute
'/organization/secret-scanning': typeof organizationSecretScanningPageRouteRoute
'/organization/secret-sharing': typeof organizationSecretSharingPageRouteRoute
'/organization/settings': typeof organizationSettingsPageRouteRoute
'/cert-manager/$projectId': typeof certManagerLayoutRouteWithChildren
'/kms/$projectId': typeof kmsLayoutRouteWithChildren
'/organization/app-connections': typeof AuthenticateInjectOrgDetailsOrgLayoutOrganizationAppConnectionsRouteWithChildren
'/organization/gateways': typeof AuthenticateInjectOrgDetailsOrgLayoutOrganizationGatewaysRouteWithChildren
'/organization/secret-sharing': typeof AuthenticateInjectOrgDetailsOrgLayoutOrganizationSecretSharingRouteWithChildren
'/secret-manager/$projectId': typeof secretManagerLayoutRouteWithChildren
'/ssh/$projectId': typeof sshLayoutRouteWithChildren
'/organization/app-connections/': typeof organizationAppConnectionsAppConnectionsPageRouteRoute
'/organization/gateways/': typeof organizationGatewaysGatewayListPageRouteRoute
'/organization/secret-sharing/': typeof organizationSecretSharingPageRouteRoute
'/organization/cert-manager/overview': typeof organizationCertManagerOverviewPageRouteRoute
'/organization/groups/$groupId': typeof organizationGroupDetailsByIDPageRouteRoute
'/organization/identities/$identityId': typeof organizationIdentityDetailsByIDPageRouteRoute
@@ -3689,6 +3743,7 @@ export interface FileRoutesByFullPath {
'/organization/members/$membershipId': typeof organizationUserDetailsByIDPageRouteRoute
'/organization/roles/$roleId': typeof organizationRoleByIDPageRouteRoute
'/organization/secret-manager/overview': typeof organizationSecretManagerOverviewPageRouteRoute
'/organization/secret-sharing/settings': typeof organizationSecretSharingSettingsPageRouteRoute
'/organization/ssh/overview': typeof organizationSshOverviewPageRouteRoute
'/cert-manager/$projectId/overview': typeof certManagerCertificatesPageRouteRoute
'/cert-manager/$projectId/settings': typeof certManagerSettingsPageRouteRoute
@@ -3845,7 +3900,6 @@ export interface FileRoutesByTo {
'/organization/audit-logs': typeof organizationAuditLogsPageRouteRoute
'/organization/billing': typeof organizationBillingPageRouteRoute
'/organization/secret-scanning': typeof organizationSecretScanningPageRouteRoute
'/organization/secret-sharing': typeof organizationSecretSharingPageRouteRoute
'/organization/settings': typeof organizationSettingsPageRouteRoute
'/cert-manager/$projectId': typeof certManagerLayoutRouteWithChildren
'/kms/$projectId': typeof kmsLayoutRouteWithChildren
@@ -3853,6 +3907,7 @@ export interface FileRoutesByTo {
'/ssh/$projectId': typeof sshLayoutRouteWithChildren
'/organization/app-connections': typeof organizationAppConnectionsAppConnectionsPageRouteRoute
'/organization/gateways': typeof organizationGatewaysGatewayListPageRouteRoute
'/organization/secret-sharing': typeof organizationSecretSharingPageRouteRoute
'/organization/cert-manager/overview': typeof organizationCertManagerOverviewPageRouteRoute
'/organization/groups/$groupId': typeof organizationGroupDetailsByIDPageRouteRoute
'/organization/identities/$identityId': typeof organizationIdentityDetailsByIDPageRouteRoute
@@ -3860,6 +3915,7 @@ export interface FileRoutesByTo {
'/organization/members/$membershipId': typeof organizationUserDetailsByIDPageRouteRoute
'/organization/roles/$roleId': typeof organizationRoleByIDPageRouteRoute
'/organization/secret-manager/overview': typeof organizationSecretManagerOverviewPageRouteRoute
'/organization/secret-sharing/settings': typeof organizationSecretSharingSettingsPageRouteRoute
'/organization/ssh/overview': typeof organizationSshOverviewPageRouteRoute
'/cert-manager/$projectId/overview': typeof certManagerCertificatesPageRouteRoute
'/cert-manager/$projectId/settings': typeof certManagerSettingsPageRouteRoute
@@ -4025,16 +4081,17 @@ export interface FileRoutesById {
'/_authenticate/_inject-org-details/_org-layout/organization/audit-logs': typeof organizationAuditLogsPageRouteRoute
'/_authenticate/_inject-org-details/_org-layout/organization/billing': typeof organizationBillingPageRouteRoute
'/_authenticate/_inject-org-details/_org-layout/organization/secret-scanning': typeof organizationSecretScanningPageRouteRoute
'/_authenticate/_inject-org-details/_org-layout/organization/secret-sharing': typeof organizationSecretSharingPageRouteRoute
'/_authenticate/_inject-org-details/_org-layout/organization/settings': typeof organizationSettingsPageRouteRoute
'/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId': typeof AuthenticateInjectOrgDetailsOrgLayoutCertManagerProjectIdRouteWithChildren
'/_authenticate/_inject-org-details/_org-layout/kms/$projectId': typeof AuthenticateInjectOrgDetailsOrgLayoutKmsProjectIdRouteWithChildren
'/_authenticate/_inject-org-details/_org-layout/organization/app-connections': typeof AuthenticateInjectOrgDetailsOrgLayoutOrganizationAppConnectionsRouteWithChildren
'/_authenticate/_inject-org-details/_org-layout/organization/gateways': typeof AuthenticateInjectOrgDetailsOrgLayoutOrganizationGatewaysRouteWithChildren
'/_authenticate/_inject-org-details/_org-layout/organization/secret-sharing': typeof AuthenticateInjectOrgDetailsOrgLayoutOrganizationSecretSharingRouteWithChildren
'/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId': typeof AuthenticateInjectOrgDetailsOrgLayoutSecretManagerProjectIdRouteWithChildren
'/_authenticate/_inject-org-details/_org-layout/ssh/$projectId': typeof AuthenticateInjectOrgDetailsOrgLayoutSshProjectIdRouteWithChildren
'/_authenticate/_inject-org-details/_org-layout/organization/app-connections/': typeof organizationAppConnectionsAppConnectionsPageRouteRoute
'/_authenticate/_inject-org-details/_org-layout/organization/gateways/': typeof organizationGatewaysGatewayListPageRouteRoute
'/_authenticate/_inject-org-details/_org-layout/organization/secret-sharing/': typeof organizationSecretSharingPageRouteRoute
'/_authenticate/_inject-org-details/_org-layout/organization/cert-manager/overview': typeof organizationCertManagerOverviewPageRouteRoute
'/_authenticate/_inject-org-details/_org-layout/organization/groups/$groupId': typeof organizationGroupDetailsByIDPageRouteRoute
'/_authenticate/_inject-org-details/_org-layout/organization/identities/$identityId': typeof organizationIdentityDetailsByIDPageRouteRoute
@@ -4042,6 +4099,7 @@ export interface FileRoutesById {
'/_authenticate/_inject-org-details/_org-layout/organization/members/$membershipId': typeof organizationUserDetailsByIDPageRouteRoute
'/_authenticate/_inject-org-details/_org-layout/organization/roles/$roleId': typeof organizationRoleByIDPageRouteRoute
'/_authenticate/_inject-org-details/_org-layout/organization/secret-manager/overview': typeof organizationSecretManagerOverviewPageRouteRoute
'/_authenticate/_inject-org-details/_org-layout/organization/secret-sharing/settings': typeof organizationSecretSharingSettingsPageRouteRoute
'/_authenticate/_inject-org-details/_org-layout/organization/ssh/overview': typeof organizationSshOverviewPageRouteRoute
'/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout': typeof certManagerLayoutRouteWithChildren
'/_authenticate/_inject-org-details/_org-layout/kms/$projectId/_kms-layout': typeof kmsLayoutRouteWithChildren
@@ -4208,16 +4266,17 @@ export interface FileRouteTypes {
| '/organization/audit-logs'
| '/organization/billing'
| '/organization/secret-scanning'
| '/organization/secret-sharing'
| '/organization/settings'
| '/cert-manager/$projectId'
| '/kms/$projectId'
| '/organization/app-connections'
| '/organization/gateways'
| '/organization/secret-sharing'
| '/secret-manager/$projectId'
| '/ssh/$projectId'
| '/organization/app-connections/'
| '/organization/gateways/'
| '/organization/secret-sharing/'
| '/organization/cert-manager/overview'
| '/organization/groups/$groupId'
| '/organization/identities/$identityId'
@@ -4225,6 +4284,7 @@ export interface FileRouteTypes {
| '/organization/members/$membershipId'
| '/organization/roles/$roleId'
| '/organization/secret-manager/overview'
| '/organization/secret-sharing/settings'
| '/organization/ssh/overview'
| '/cert-manager/$projectId/overview'
| '/cert-manager/$projectId/settings'
@@ -4380,7 +4440,6 @@ export interface FileRouteTypes {
| '/organization/audit-logs'
| '/organization/billing'
| '/organization/secret-scanning'
| '/organization/secret-sharing'
| '/organization/settings'
| '/cert-manager/$projectId'
| '/kms/$projectId'
@@ -4388,6 +4447,7 @@ export interface FileRouteTypes {
| '/ssh/$projectId'
| '/organization/app-connections'
| '/organization/gateways'
| '/organization/secret-sharing'
| '/organization/cert-manager/overview'
| '/organization/groups/$groupId'
| '/organization/identities/$identityId'
@@ -4395,6 +4455,7 @@ export interface FileRouteTypes {
| '/organization/members/$membershipId'
| '/organization/roles/$roleId'
| '/organization/secret-manager/overview'
| '/organization/secret-sharing/settings'
| '/organization/ssh/overview'
| '/cert-manager/$projectId/overview'
| '/cert-manager/$projectId/settings'
@@ -4558,16 +4619,17 @@ export interface FileRouteTypes {
| '/_authenticate/_inject-org-details/_org-layout/organization/audit-logs'
| '/_authenticate/_inject-org-details/_org-layout/organization/billing'
| '/_authenticate/_inject-org-details/_org-layout/organization/secret-scanning'
| '/_authenticate/_inject-org-details/_org-layout/organization/secret-sharing'
| '/_authenticate/_inject-org-details/_org-layout/organization/settings'
| '/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId'
| '/_authenticate/_inject-org-details/_org-layout/kms/$projectId'
| '/_authenticate/_inject-org-details/_org-layout/organization/app-connections'
| '/_authenticate/_inject-org-details/_org-layout/organization/gateways'
| '/_authenticate/_inject-org-details/_org-layout/organization/secret-sharing'
| '/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId'
| '/_authenticate/_inject-org-details/_org-layout/ssh/$projectId'
| '/_authenticate/_inject-org-details/_org-layout/organization/app-connections/'
| '/_authenticate/_inject-org-details/_org-layout/organization/gateways/'
| '/_authenticate/_inject-org-details/_org-layout/organization/secret-sharing/'
| '/_authenticate/_inject-org-details/_org-layout/organization/cert-manager/overview'
| '/_authenticate/_inject-org-details/_org-layout/organization/groups/$groupId'
| '/_authenticate/_inject-org-details/_org-layout/organization/identities/$identityId'
@@ -4575,6 +4637,7 @@ export interface FileRouteTypes {
| '/_authenticate/_inject-org-details/_org-layout/organization/members/$membershipId'
| '/_authenticate/_inject-org-details/_org-layout/organization/roles/$roleId'
| '/_authenticate/_inject-org-details/_org-layout/organization/secret-manager/overview'
| '/_authenticate/_inject-org-details/_org-layout/organization/secret-sharing/settings'
| '/_authenticate/_inject-org-details/_org-layout/organization/ssh/overview'
| '/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout'
| '/_authenticate/_inject-org-details/_org-layout/kms/$projectId/_kms-layout'
@@ -4936,10 +4999,10 @@ export const routeTree = rootRoute
"/_authenticate/_inject-org-details/_org-layout/organization/audit-logs",
"/_authenticate/_inject-org-details/_org-layout/organization/billing",
"/_authenticate/_inject-org-details/_org-layout/organization/secret-scanning",
"/_authenticate/_inject-org-details/_org-layout/organization/secret-sharing",
"/_authenticate/_inject-org-details/_org-layout/organization/settings",
"/_authenticate/_inject-org-details/_org-layout/organization/app-connections",
"/_authenticate/_inject-org-details/_org-layout/organization/gateways",
"/_authenticate/_inject-org-details/_org-layout/organization/secret-sharing",
"/_authenticate/_inject-org-details/_org-layout/organization/cert-manager/overview",
"/_authenticate/_inject-org-details/_org-layout/organization/groups/$groupId",
"/_authenticate/_inject-org-details/_org-layout/organization/identities/$identityId",
@@ -4981,10 +5044,6 @@ export const routeTree = rootRoute
"filePath": "organization/SecretScanningPage/route.tsx",
"parent": "/_authenticate/_inject-org-details/_org-layout/organization"
},
"/_authenticate/_inject-org-details/_org-layout/organization/secret-sharing": {
"filePath": "organization/SecretSharingPage/route.tsx",
"parent": "/_authenticate/_inject-org-details/_org-layout/organization"
},
"/_authenticate/_inject-org-details/_org-layout/organization/settings": {
"filePath": "organization/SettingsPage/route.tsx",
"parent": "/_authenticate/_inject-org-details/_org-layout/organization"
@@ -5018,6 +5077,14 @@ export const routeTree = rootRoute
"/_authenticate/_inject-org-details/_org-layout/organization/gateways/"
]
},
"/_authenticate/_inject-org-details/_org-layout/organization/secret-sharing": {
"filePath": "",
"parent": "/_authenticate/_inject-org-details/_org-layout/organization",
"children": [
"/_authenticate/_inject-org-details/_org-layout/organization/secret-sharing/",
"/_authenticate/_inject-org-details/_org-layout/organization/secret-sharing/settings"
]
},
"/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId": {
"filePath": "",
"parent": "/_authenticate/_inject-org-details/_org-layout",
@@ -5040,6 +5107,10 @@ export const routeTree = rootRoute
"filePath": "organization/Gateways/GatewayListPage/route.tsx",
"parent": "/_authenticate/_inject-org-details/_org-layout/organization/gateways"
},
"/_authenticate/_inject-org-details/_org-layout/organization/secret-sharing/": {
"filePath": "organization/SecretSharingPage/route.tsx",
"parent": "/_authenticate/_inject-org-details/_org-layout/organization/secret-sharing"
},
"/_authenticate/_inject-org-details/_org-layout/organization/cert-manager/overview": {
"filePath": "organization/CertManagerOverviewPage/route.tsx",
"parent": "/_authenticate/_inject-org-details/_org-layout/organization"
@@ -5068,6 +5139,10 @@ export const routeTree = rootRoute
"filePath": "organization/SecretManagerOverviewPage/route.tsx",
"parent": "/_authenticate/_inject-org-details/_org-layout/organization"
},
"/_authenticate/_inject-org-details/_org-layout/organization/secret-sharing/settings": {
"filePath": "organization/SecretSharingSettingsPage/route.tsx",
"parent": "/_authenticate/_inject-org-details/_org-layout/organization/secret-sharing"
},
"/_authenticate/_inject-org-details/_org-layout/organization/ssh/overview": {
"filePath": "organization/SshOverviewPage/route.tsx",
"parent": "/_authenticate/_inject-org-details/_org-layout/organization"

View File

@@ -16,7 +16,10 @@ const organizationRoutes = route("/organization", [
route("/admin", "organization/AdminPage/route.tsx"),
route("/audit-logs", "organization/AuditLogsPage/route.tsx"),
route("/billing", "organization/BillingPage/route.tsx"),
route("/secret-sharing", "organization/SecretSharingPage/route.tsx"),
route("/secret-sharing", [
index("organization/SecretSharingPage/route.tsx"),
route("/settings", "organization/SecretSharingSettingsPage/route.tsx")
]),
route("/settings", "organization/SettingsPage/route.tsx"),
route("/secret-scanning", "organization/SecretScanningPage/route.tsx"),
route("/groups/$groupId", "organization/GroupDetailsByIDPage/route.tsx"),

View File

@@ -13,9 +13,9 @@ type: application
# This is the chart version. This version number should be incremented each time you make changes
# to the chart and its templates, including the app version.
# Versions are expected to follow Semantic Versioning (https://semver.org/)
version: v0.8.12
version: v0.8.13
# This is the version number of the application being deployed. This version number should be
# incremented each time you make changes to the application. Versions are not expected to
# follow Semantic Versioning. They should reflect the version the application is using.
# It is recommended to use it with quotes.
appVersion: "v0.8.12"
appVersion: "v0.8.13"

View File

@@ -262,6 +262,43 @@ spec:
hostAPI:
description: Infisical host to pull secrets from
type: string
managedKubeConfigMapReferences:
items:
properties:
configMapName:
description: The name of the Kubernetes ConfigMap
type: string
configMapNamespace:
description: The namespace where the Kubernetes ConfigMap is located
type: string
creationPolicy:
default: Orphan
description: 'The Kubernetes ConfigMap creation policy. Enum with
values: ''Owner'', ''Orphan''. Owner creates the config map
and sets .metadata.ownerReferences of the InfisicalSecret CRD
that created it. Orphan will not set the config map owner. This
will result in the config map being orphaned and not deleted
when the resource is deleted.'
type: string
template:
description: The template to transform the secret data
properties:
data:
additionalProperties:
type: string
description: The template key values
type: object
includeAllSecrets:
description: This injects all retrieved secrets into the top
level of your template. Secrets defined in the template
will take precedence over the injected ones.
type: boolean
type: object
required:
- configMapName
- configMapNamespace
type: object
type: array
managedKubeSecretReferences:
items:
properties:
@@ -380,6 +417,7 @@ spec:
- secretNamespace
type: object
required:
- managedKubeConfigMapReferences
- resyncInterval
type: object
status:
@@ -466,5 +504,4 @@ status:
kind: ""
plural: ""
conditions: []
storedVersions: []
{{- end }}
storedVersions: []

View File

@@ -32,7 +32,7 @@ controllerManager:
- ALL
image:
repository: infisical/kubernetes-operator
tag: v0.8.12
tag: v0.8.13
resources:
limits:
cpu: 500m

View File

@@ -107,3 +107,25 @@ type ManagedKubeSecretConfig struct {
// +kubebuilder:validation:Optional
Template *InfisicalSecretTemplate `json:"template,omitempty"`
}
type ManagedKubeConfigMapConfig struct {
// The name of the Kubernetes ConfigMap
// +kubebuilder:validation:Required
ConfigMapName string `json:"configMapName"`
// The Kubernetes ConfigMap creation policy.
// Enum with values: 'Owner', 'Orphan'.
// Owner creates the config map and sets .metadata.ownerReferences of the InfisicalSecret CRD that created it.
// Orphan will not set the config map owner. This will result in the config map being orphaned and not deleted when the resource is deleted.
// +kubebuilder:validation:Optional
// +kubebuilder:default:=Orphan
CreationPolicy string `json:"creationPolicy"`
// The namespace where the Kubernetes ConfigMap is located
// +kubebuilder:validation:Required
ConfigMapNamespace string `json:"configMapNamespace"`
// The template to transform the secret data
// +kubebuilder:validation:Optional
Template *InfisicalSecretTemplate `json:"template,omitempty"`
}

View File

@@ -139,6 +139,8 @@ type InfisicalSecretSpec struct {
// +kubebuilder:validation:Optional
ManagedKubeSecretReferences []ManagedKubeSecretConfig `json:"managedKubeSecretReferences"`
// +kubebuilder:validation:Optional
ManagedKubeConfigMapReferences []ManagedKubeConfigMapConfig `json:"managedKubeConfigMapReferences"`
// +kubebuilder:default:=60
ResyncInterval int `json:"resyncInterval"`

View File

@@ -572,6 +572,13 @@ func (in *InfisicalSecretSpec) DeepCopyInto(out *InfisicalSecretSpec) {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
if in.ManagedKubeConfigMapReferences != nil {
in, out := &in.ManagedKubeConfigMapReferences, &out.ManagedKubeConfigMapReferences
*out = make([]ManagedKubeConfigMapConfig, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
out.TLS = in.TLS
}
@@ -691,6 +698,26 @@ func (in *MachineIdentityScopeInWorkspace) DeepCopy() *MachineIdentityScopeInWor
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ManagedKubeConfigMapConfig) DeepCopyInto(out *ManagedKubeConfigMapConfig) {
*out = *in
if in.Template != nil {
in, out := &in.Template, &out.Template
*out = new(InfisicalSecretTemplate)
(*in).DeepCopyInto(*out)
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ManagedKubeConfigMapConfig.
func (in *ManagedKubeConfigMapConfig) DeepCopy() *ManagedKubeConfigMapConfig {
if in == nil {
return nil
}
out := new(ManagedKubeConfigMapConfig)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ManagedKubeSecretConfig) DeepCopyInto(out *ManagedKubeSecretConfig) {
*out = *in

View File

@@ -261,6 +261,44 @@ spec:
hostAPI:
description: Infisical host to pull secrets from
type: string
managedKubeConfigMapReferences:
items:
properties:
configMapName:
description: The name of the Kubernetes ConfigMap
type: string
configMapNamespace:
description: The namespace where the Kubernetes ConfigMap is
located
type: string
creationPolicy:
default: Orphan
description: 'The Kubernetes ConfigMap creation policy. Enum
with values: ''Owner'', ''Orphan''. Owner creates the config
map and sets .metadata.ownerReferences of the InfisicalSecret
CRD that created it. Orphan will not set the config map owner.
This will result in the config map being orphaned and not
deleted when the resource is deleted.'
type: string
template:
description: The template to transform the secret data
properties:
data:
additionalProperties:
type: string
description: The template key values
type: object
includeAllSecrets:
description: This injects all retrieved secrets into the
top level of your template. Secrets defined in the template
will take precedence over the injected ones.
type: boolean
type: object
required:
- configMapName
- configMapNamespace
type: object
type: array
managedKubeSecretReferences:
items:
properties:

View File

@@ -11,7 +11,7 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
func (r *InfisicalSecretReconciler) SetReadyToSyncSecretsConditions(ctx context.Context, infisicalSecret *v1alpha1.InfisicalSecret, secretsCount int, errorToConditionOn error) error {
func (r *InfisicalSecretReconciler) SetReadyToSyncSecretsConditions(ctx context.Context, logger logr.Logger, infisicalSecret *v1alpha1.InfisicalSecret, secretsCount int, errorToConditionOn error) {
if infisicalSecret.Status.Conditions == nil {
infisicalSecret.Status.Conditions = []metav1.Condition{}
}
@@ -39,7 +39,10 @@ func (r *InfisicalSecretReconciler) SetReadyToSyncSecretsConditions(ctx context.
})
}
return r.Client.Status().Update(ctx, infisicalSecret)
err := r.Client.Status().Update(ctx, infisicalSecret)
if err != nil {
logger.Error(err, "Could not set condition for ReadyToSyncSecrets")
}
}
func (r *InfisicalSecretReconciler) SetInfisicalTokenLoadCondition(ctx context.Context, logger logr.Logger, infisicalSecret *v1alpha1.InfisicalSecret, authStrategy util.AuthStrategyType, errorToConditionOn error) {

View File

@@ -73,6 +73,7 @@ func (r *InfisicalSecretReconciler) Reconcile(ctx context.Context, req ctrl.Requ
// It's important we don't directly modify the CRD object, so we create a copy of it and move existing data into it.
managedKubeSecretReferences := infisicalSecretCRD.Spec.ManagedKubeSecretReferences
managedKubeConfigMapReferences := infisicalSecretCRD.Spec.ManagedKubeConfigMapReferences
if infisicalSecretCRD.Spec.ManagedSecretReference.SecretName != "" && managedKubeSecretReferences != nil && len(managedKubeSecretReferences) > 0 {
errMessage := "InfisicalSecret CRD cannot have both managedSecretReference and managedKubeSecretReferences"
@@ -89,8 +90,8 @@ func (r *InfisicalSecretReconciler) Reconcile(ctx context.Context, req ctrl.Requ
managedKubeSecretReferences = append(managedKubeSecretReferences, infisicalSecretCRD.Spec.ManagedSecretReference)
}
if len(managedKubeSecretReferences) == 0 {
errMessage := "InfisicalSecret CRD must have at least one managed secret reference set in the `managedKubeSecretReferences` field"
if len(managedKubeSecretReferences) == 0 && len(managedKubeConfigMapReferences) == 0 {
errMessage := "InfisicalSecret CRD must have at least one managed secret reference set in the `managedKubeSecretReferences` or `managedKubeConfigMapReferences` field"
logger.Error(defaultErrors.New(errMessage), errMessage)
return ctrl.Result{}, defaultErrors.New(errMessage)
}
@@ -151,8 +152,8 @@ func (r *InfisicalSecretReconciler) Reconcile(ctx context.Context, req ctrl.Requ
api.API_CA_CERTIFICATE = ""
}
secretsCount, err := r.ReconcileInfisicalSecret(ctx, logger, infisicalSecretCRD, managedKubeSecretReferences)
r.SetReadyToSyncSecretsConditions(ctx, &infisicalSecretCRD, secretsCount, err)
secretsCount, err := r.ReconcileInfisicalSecret(ctx, logger, &infisicalSecretCRD, managedKubeSecretReferences, managedKubeConfigMapReferences)
r.SetReadyToSyncSecretsConditions(ctx, logger, &infisicalSecretCRD, secretsCount, err)
if err != nil {
logger.Error(err, fmt.Sprintf("unable to reconcile InfisicalSecret. Will requeue after [requeueTime=%v]", requeueTime))
@@ -163,6 +164,7 @@ func (r *InfisicalSecretReconciler) Reconcile(ctx context.Context, req ctrl.Requ
numDeployments, err := controllerhelpers.ReconcileDeploymentsWithMultipleManagedSecrets(ctx, r.Client, logger, managedKubeSecretReferences)
r.SetInfisicalAutoRedeploymentReady(ctx, logger, &infisicalSecretCRD, numDeployments, err)
if err != nil {
logger.Error(err, fmt.Sprintf("unable to reconcile auto redeployment. Will requeue after [requeueTime=%v]", requeueTime))
return ctrl.Result{

View File

@@ -12,6 +12,7 @@ import (
"github.com/Infisical/infisical/k8-operator/api/v1alpha1"
"github.com/Infisical/infisical/k8-operator/packages/api"
"github.com/Infisical/infisical/k8-operator/packages/constants"
"github.com/Infisical/infisical/k8-operator/packages/crypto"
"github.com/Infisical/infisical/k8-operator/packages/model"
"github.com/Infisical/infisical/k8-operator/packages/util"
"github.com/go-logr/logr"
@@ -165,10 +166,24 @@ var infisicalSecretTemplateFunctions = template.FuncMap{
},
}
func (r *InfisicalSecretReconciler) createInfisicalManagedKubeSecret(ctx context.Context, logger logr.Logger, infisicalSecret v1alpha1.InfisicalSecret, managedSecretReference v1alpha1.ManagedKubeSecretConfig, secretsFromAPI []model.SingleEnvironmentVariable, ETag string) error {
func convertBinaryToStringMap(binaryMap map[string][]byte) map[string]string {
stringMap := make(map[string]string)
for k, v := range binaryMap {
stringMap[k] = string(v)
}
return stringMap
}
func (r *InfisicalSecretReconciler) createInfisicalManagedKubeResource(ctx context.Context, logger logr.Logger, infisicalSecret v1alpha1.InfisicalSecret, managedSecretReferenceInterface interface{}, secretsFromAPI []model.SingleEnvironmentVariable, ETag string, resourceType constants.ManagedKubeResourceType) error {
plainProcessedSecrets := make(map[string][]byte)
secretType := managedSecretReference.SecretType
managedTemplateData := managedSecretReference.Template
var managedTemplateData *v1alpha1.InfisicalSecretTemplate
if resourceType == constants.MANAGED_KUBE_RESOURCE_TYPE_SECRET {
managedTemplateData = managedSecretReferenceInterface.(v1alpha1.ManagedKubeSecretConfig).Template
} else if resourceType == constants.MANAGED_KUBE_RESOURCE_TYPE_CONFIG_MAP {
managedTemplateData = managedSecretReferenceInterface.(v1alpha1.ManagedKubeConfigMapConfig).Template
}
if managedTemplateData == nil || managedTemplateData.IncludeAllSecrets {
for _, secret := range secretsFromAPI {
@@ -221,34 +236,70 @@ func (r *InfisicalSecretReconciler) createInfisicalManagedKubeSecret(ctx context
}
}
annotations[constants.SECRET_VERSION_ANNOTATION] = ETag
// create a new secret as specified by the managed secret spec of CRD
newKubeSecretInstance := &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: managedSecretReference.SecretName,
Namespace: managedSecretReference.SecretNamespace,
Annotations: annotations,
Labels: labels,
},
Type: corev1.SecretType(secretType),
Data: plainProcessedSecrets,
}
if resourceType == constants.MANAGED_KUBE_RESOURCE_TYPE_SECRET {
if managedSecretReference.CreationPolicy == "Owner" {
// Set InfisicalSecret instance as the owner and controller of the managed secret
err := ctrl.SetControllerReference(&infisicalSecret, newKubeSecretInstance, r.Scheme)
if err != nil {
return err
managedSecretReference := managedSecretReferenceInterface.(v1alpha1.ManagedKubeSecretConfig)
annotations[constants.SECRET_VERSION_ANNOTATION] = ETag
// create a new secret as specified by the managed secret spec of CRD
newKubeSecretInstance := &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: managedSecretReference.SecretName,
Namespace: managedSecretReference.SecretNamespace,
Annotations: annotations,
Labels: labels,
},
Type: corev1.SecretType(managedSecretReference.SecretType),
Data: plainProcessedSecrets,
}
}
err := r.Client.Create(ctx, newKubeSecretInstance)
if err != nil {
return fmt.Errorf("unable to create the managed Kubernetes secret : %w", err)
}
if managedSecretReference.CreationPolicy == "Owner" {
// Set InfisicalSecret instance as the owner and controller of the managed secret
err := ctrl.SetControllerReference(&infisicalSecret, newKubeSecretInstance, r.Scheme)
if err != nil {
return err
}
}
err := r.Client.Create(ctx, newKubeSecretInstance)
if err != nil {
return fmt.Errorf("unable to create the managed Kubernetes secret : %w", err)
}
logger.Info(fmt.Sprintf("Successfully created a managed Kubernetes secret with your Infisical secrets. Type: %s", managedSecretReference.SecretType))
return nil
} else if resourceType == constants.MANAGED_KUBE_RESOURCE_TYPE_CONFIG_MAP {
managedSecretReference := managedSecretReferenceInterface.(v1alpha1.ManagedKubeConfigMapConfig)
// create a new config map as specified by the managed secret spec of CRD
newKubeConfigMapInstance := &corev1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: managedSecretReference.ConfigMapName,
Namespace: managedSecretReference.ConfigMapNamespace,
Annotations: annotations,
Labels: labels,
},
Data: convertBinaryToStringMap(plainProcessedSecrets),
}
if managedSecretReference.CreationPolicy == "Owner" {
// Set InfisicalSecret instance as the owner and controller of the managed config map
err := ctrl.SetControllerReference(&infisicalSecret, newKubeConfigMapInstance, r.Scheme)
if err != nil {
return err
}
}
err := r.Client.Create(ctx, newKubeConfigMapInstance)
if err != nil {
return fmt.Errorf("unable to create the managed Kubernetes config map : %w", err)
}
logger.Info(fmt.Sprintf("Successfully created a managed Kubernetes config map with your Infisical secrets. Type: %s", managedSecretReference.ConfigMapName))
return nil
}
return fmt.Errorf("invalid resource type")
logger.Info(fmt.Sprintf("Successfully created a managed Kubernetes secret with your Infisical secrets. Type: %s", secretType))
return nil
}
func (r *InfisicalSecretReconciler) updateInfisicalManagedKubeSecret(ctx context.Context, logger logr.Logger, managedSecretReference v1alpha1.ManagedKubeSecretConfig, managedKubeSecret corev1.Secret, secretsFromAPI []model.SingleEnvironmentVariable, ETag string) error {
@@ -302,6 +353,109 @@ func (r *InfisicalSecretReconciler) updateInfisicalManagedKubeSecret(ctx context
return nil
}
func (r *InfisicalSecretReconciler) updateInfisicalManagedConfigMap(ctx context.Context, logger logr.Logger, managedConfigMapReference v1alpha1.ManagedKubeConfigMapConfig, managedConfigMap corev1.ConfigMap, secretsFromAPI []model.SingleEnvironmentVariable, ETag string) error {
managedTemplateData := managedConfigMapReference.Template
plainProcessedSecrets := make(map[string][]byte)
if managedTemplateData == nil || managedTemplateData.IncludeAllSecrets {
for _, secret := range secretsFromAPI {
plainProcessedSecrets[secret.Key] = []byte(secret.Value)
}
}
if managedTemplateData != nil {
secretKeyValue := make(map[string]model.SecretTemplateOptions)
for _, secret := range secretsFromAPI {
secretKeyValue[secret.Key] = model.SecretTemplateOptions{
Value: secret.Value,
SecretPath: secret.SecretPath,
}
}
for templateKey, userTemplate := range managedTemplateData.Data {
tmpl, err := template.New("secret-templates").Funcs(infisicalSecretTemplateFunctions).Parse(userTemplate)
if err != nil {
return fmt.Errorf("unable to compile template: %s [err=%v]", templateKey, err)
}
buf := bytes.NewBuffer(nil)
err = tmpl.Execute(buf, secretKeyValue)
if err != nil {
return fmt.Errorf("unable to execute template: %s [err=%v]", templateKey, err)
}
plainProcessedSecrets[templateKey] = buf.Bytes()
}
}
// Initialize the Annotations map if it's nil
if managedConfigMap.ObjectMeta.Annotations == nil {
managedConfigMap.ObjectMeta.Annotations = make(map[string]string)
}
managedConfigMap.Data = convertBinaryToStringMap(plainProcessedSecrets)
managedConfigMap.ObjectMeta.Annotations[constants.SECRET_VERSION_ANNOTATION] = ETag
err := r.Client.Update(ctx, &managedConfigMap)
if err != nil {
return fmt.Errorf("unable to update Kubernetes config map because [%w]", err)
}
logger.Info("successfully updated managed Kubernetes config map")
return nil
}
func (r *InfisicalSecretReconciler) fetchSecretsFromAPI(ctx context.Context, logger logr.Logger, authDetails util.AuthenticationDetails, infisicalClient infisicalSdk.InfisicalClientInterface, infisicalSecret v1alpha1.InfisicalSecret) ([]model.SingleEnvironmentVariable, error) {
if authDetails.AuthStrategy == util.AuthStrategy.SERVICE_ACCOUNT { // Service Account // ! Legacy auth method
serviceAccountCreds, err := r.getInfisicalServiceAccountCredentialsFromKubeSecret(ctx, infisicalSecret)
if err != nil {
return nil, fmt.Errorf("ReconcileInfisicalSecret: unable to get service account creds from kube secret [err=%s]", err)
}
plainTextSecretsFromApi, err := util.GetPlainTextSecretsViaServiceAccount(infisicalClient, serviceAccountCreds, infisicalSecret.Spec.Authentication.ServiceAccount.ProjectId, infisicalSecret.Spec.Authentication.ServiceAccount.EnvironmentName)
if err != nil {
return nil, fmt.Errorf("\nfailed to get secrets because [err=%v]", err)
}
logger.Info("ReconcileInfisicalSecret: Fetched secrets via service account")
return plainTextSecretsFromApi, nil
} else if authDetails.AuthStrategy == util.AuthStrategy.SERVICE_TOKEN { // Service Tokens // ! Legacy / Deprecated auth method
infisicalToken, err := r.getInfisicalTokenFromKubeSecret(ctx, infisicalSecret)
if err != nil {
return nil, fmt.Errorf("ReconcileInfisicalSecret: unable to get service token from kube secret [err=%s]", err)
}
envSlug := infisicalSecret.Spec.Authentication.ServiceToken.SecretsScope.EnvSlug
secretsPath := infisicalSecret.Spec.Authentication.ServiceToken.SecretsScope.SecretsPath
recursive := infisicalSecret.Spec.Authentication.ServiceToken.SecretsScope.Recursive
plainTextSecretsFromApi, err := util.GetPlainTextSecretsViaServiceToken(infisicalClient, infisicalToken, envSlug, secretsPath, recursive)
if err != nil {
return nil, fmt.Errorf("\nfailed to get secrets because [err=%v]", err)
}
logger.Info("ReconcileInfisicalSecret: Fetched secrets via [type=SERVICE_TOKEN]")
return plainTextSecretsFromApi, nil
} else if authDetails.IsMachineIdentityAuth { // * Machine Identity authentication, the SDK will be authenticated at this point
plainTextSecretsFromApi, err := util.GetPlainTextSecretsViaMachineIdentity(infisicalClient, authDetails.MachineIdentityScope)
if err != nil {
return nil, fmt.Errorf("\nfailed to get secrets because [err=%v]", err)
}
logger.Info(fmt.Sprintf("ReconcileInfisicalSecret: Fetched secrets via machine identity [type=%v]", authDetails.AuthStrategy))
return plainTextSecretsFromApi, nil
} else {
return nil, errors.New("no authentication method provided. Please configure a authentication method then try again")
}
}
func (r *InfisicalSecretReconciler) getResourceVariables(infisicalSecret v1alpha1.InfisicalSecret) util.ResourceVariables {
var resourceVariables util.ResourceVariables
@@ -336,9 +490,13 @@ func (r *InfisicalSecretReconciler) updateResourceVariables(infisicalSecret v1al
infisicalSecretResourceVariablesMap[string(infisicalSecret.UID)] = resourceVariables
}
func (r *InfisicalSecretReconciler) ReconcileInfisicalSecret(ctx context.Context, logger logr.Logger, infisicalSecret v1alpha1.InfisicalSecret, managedKubeSecretReferences []v1alpha1.ManagedKubeSecretConfig) (int, error) {
func (r *InfisicalSecretReconciler) ReconcileInfisicalSecret(ctx context.Context, logger logr.Logger, infisicalSecret *v1alpha1.InfisicalSecret, managedKubeSecretReferences []v1alpha1.ManagedKubeSecretConfig, managedKubeConfigMapReferences []v1alpha1.ManagedKubeConfigMapConfig) (int, error) {
resourceVariables := r.getResourceVariables(infisicalSecret)
if infisicalSecret == nil {
return 0, fmt.Errorf("infisicalSecret is nil")
}
resourceVariables := r.getResourceVariables(*infisicalSecret)
infisicalClient := resourceVariables.InfisicalClient
cancelCtx := resourceVariables.CancelCtx
authDetails := resourceVariables.AuthDetails
@@ -346,95 +504,74 @@ func (r *InfisicalSecretReconciler) ReconcileInfisicalSecret(ctx context.Context
if authDetails.AuthStrategy == "" {
logger.Info("No authentication strategy found. Attempting to authenticate")
authDetails, err = r.handleAuthentication(ctx, infisicalSecret, infisicalClient)
r.SetInfisicalTokenLoadCondition(ctx, logger, &infisicalSecret, authDetails.AuthStrategy, err)
authDetails, err = r.handleAuthentication(ctx, *infisicalSecret, infisicalClient)
r.SetInfisicalTokenLoadCondition(ctx, logger, infisicalSecret, authDetails.AuthStrategy, err)
if err != nil {
return 0, fmt.Errorf("unable to authenticate [err=%s]", err)
}
r.updateResourceVariables(infisicalSecret, util.ResourceVariables{
r.updateResourceVariables(*infisicalSecret, util.ResourceVariables{
InfisicalClient: infisicalClient,
CancelCtx: cancelCtx,
AuthDetails: authDetails,
})
}
secretsCount := 0
plainTextSecretsFromApi, err := r.fetchSecretsFromAPI(ctx, logger, authDetails, infisicalClient, *infisicalSecret)
for _, managedSecretReference := range managedKubeSecretReferences {
// Look for managed secret by name and namespace
managedKubeSecret, err := util.GetKubeSecretByNamespacedName(ctx, r.Client, types.NamespacedName{
Name: managedSecretReference.SecretName,
Namespace: managedSecretReference.SecretNamespace,
})
if err != nil {
return 0, fmt.Errorf("failed to fetch secrets from API for managed secrets [err=%s]", err)
}
secretsCount := len(plainTextSecretsFromApi)
if err != nil && !k8Errors.IsNotFound(err) {
return 0, fmt.Errorf("something went wrong when fetching the managed Kubernetes secret [%w]", err)
if len(managedKubeSecretReferences) > 0 {
for _, managedSecretReference := range managedKubeSecretReferences {
// Look for managed secret by name and namespace
managedKubeSecret, err := util.GetKubeSecretByNamespacedName(ctx, r.Client, types.NamespacedName{
Name: managedSecretReference.SecretName,
Namespace: managedSecretReference.SecretNamespace,
})
if err != nil && !k8Errors.IsNotFound(err) {
return 0, fmt.Errorf("something went wrong when fetching the managed Kubernetes secret [%w]", err)
}
newEtag := crypto.ComputeEtag([]byte(fmt.Sprintf("%v", plainTextSecretsFromApi)))
if managedKubeSecret == nil {
if err := r.createInfisicalManagedKubeResource(ctx, logger, *infisicalSecret, managedSecretReference, plainTextSecretsFromApi, newEtag, constants.MANAGED_KUBE_RESOURCE_TYPE_SECRET); err != nil {
return 0, fmt.Errorf("failed to create managed secret [err=%s]", err)
}
} else {
if err := r.updateInfisicalManagedKubeSecret(ctx, logger, managedSecretReference, *managedKubeSecret, plainTextSecretsFromApi, newEtag); err != nil {
return 0, fmt.Errorf("failed to update managed secret [err=%s]", err)
}
}
}
}
// Get exiting Etag if exists
secretVersionBasedOnETag := ""
if managedKubeSecret != nil {
secretVersionBasedOnETag = managedKubeSecret.Annotations[constants.SECRET_VERSION_ANNOTATION]
}
if len(managedKubeConfigMapReferences) > 0 {
for _, managedConfigMapReference := range managedKubeConfigMapReferences {
managedKubeConfigMap, err := util.GetKubeConfigMapByNamespacedName(ctx, r.Client, types.NamespacedName{
Name: managedConfigMapReference.ConfigMapName,
Namespace: managedConfigMapReference.ConfigMapNamespace,
})
var plainTextSecretsFromApi []model.SingleEnvironmentVariable
var updateDetails model.RequestUpdateUpdateDetails
if authDetails.AuthStrategy == util.AuthStrategy.SERVICE_ACCOUNT { // Service Account // ! Legacy auth method
serviceAccountCreds, err := r.getInfisicalServiceAccountCredentialsFromKubeSecret(ctx, infisicalSecret)
if err != nil {
return 0, fmt.Errorf("ReconcileInfisicalSecret: unable to get service account creds from kube secret [err=%s]", err)
if err != nil && !k8Errors.IsNotFound(err) {
return 0, fmt.Errorf("something went wrong when fetching the managed Kubernetes config map [%w]", err)
}
plainTextSecretsFromApi, updateDetails, err = util.GetPlainTextSecretsViaServiceAccount(infisicalClient, serviceAccountCreds, infisicalSecret.Spec.Authentication.ServiceAccount.ProjectId, infisicalSecret.Spec.Authentication.ServiceAccount.EnvironmentName, secretVersionBasedOnETag)
if err != nil {
return 0, fmt.Errorf("\nfailed to get secrets because [err=%v]", err)
newEtag := crypto.ComputeEtag([]byte(fmt.Sprintf("%v", plainTextSecretsFromApi)))
if managedKubeConfigMap == nil {
if err := r.createInfisicalManagedKubeResource(ctx, logger, *infisicalSecret, managedConfigMapReference, plainTextSecretsFromApi, newEtag, constants.MANAGED_KUBE_RESOURCE_TYPE_CONFIG_MAP); err != nil {
return 0, fmt.Errorf("failed to create managed config map [err=%s]", err)
}
} else {
if err := r.updateInfisicalManagedConfigMap(ctx, logger, managedConfigMapReference, *managedKubeConfigMap, plainTextSecretsFromApi, newEtag); err != nil {
return 0, fmt.Errorf("failed to update managed config map [err=%s]", err)
}
}
logger.Info("ReconcileInfisicalSecret: Fetched secrets via service account")
} else if authDetails.AuthStrategy == util.AuthStrategy.SERVICE_TOKEN { // Service Tokens // ! Legacy / Deprecated auth method
infisicalToken, err := r.getInfisicalTokenFromKubeSecret(ctx, infisicalSecret)
if err != nil {
return 0, fmt.Errorf("ReconcileInfisicalSecret: unable to get service token from kube secret [err=%s]", err)
}
envSlug := infisicalSecret.Spec.Authentication.ServiceToken.SecretsScope.EnvSlug
secretsPath := infisicalSecret.Spec.Authentication.ServiceToken.SecretsScope.SecretsPath
recursive := infisicalSecret.Spec.Authentication.ServiceToken.SecretsScope.Recursive
plainTextSecretsFromApi, updateDetails, err = util.GetPlainTextSecretsViaServiceToken(infisicalClient, infisicalToken, secretVersionBasedOnETag, envSlug, secretsPath, recursive)
if err != nil {
return 0, fmt.Errorf("\nfailed to get secrets because [err=%v]", err)
}
logger.Info("ReconcileInfisicalSecret: Fetched secrets via [type=SERVICE_TOKEN]")
} else if authDetails.IsMachineIdentityAuth { // * Machine Identity authentication, the SDK will be authenticated at this point
plainTextSecretsFromApi, updateDetails, err = util.GetPlainTextSecretsViaMachineIdentity(infisicalClient, secretVersionBasedOnETag, authDetails.MachineIdentityScope)
if err != nil {
return 0, fmt.Errorf("\nfailed to get secrets because [err=%v]", err)
}
logger.Info(fmt.Sprintf("ReconcileInfisicalSecret: Fetched secrets via machine identity [type=%v]", authDetails.AuthStrategy))
} else {
return 0, errors.New("no authentication method provided. Please configure a authentication method then try again")
}
secretsCount = len(plainTextSecretsFromApi)
if managedKubeSecret == nil {
if err := r.createInfisicalManagedKubeSecret(ctx, logger, infisicalSecret, managedSecretReference, plainTextSecretsFromApi, updateDetails.ETag); err != nil {
return 0, fmt.Errorf("failed to create managed secret [err=%s]", err)
}
} else {
if err := r.updateInfisicalManagedKubeSecret(ctx, logger, managedSecretReference, *managedKubeSecret, plainTextSecretsFromApi, updateDetails.ETag); err != nil {
return 0, fmt.Errorf("failed to update managed secret [err=%s]", err)
}
}
}

View File

@@ -26,6 +26,13 @@ const (
PUSH_SECRET_DELETE_POLICY_ENABLED PushSecretDeletionPolicy = "Delete"
)
type ManagedKubeResourceType string
const (
MANAGED_KUBE_RESOURCE_TYPE_SECRET ManagedKubeResourceType = "Secret"
MANAGED_KUBE_RESOURCE_TYPE_CONFIG_MAP ManagedKubeResourceType = "ConfigMap"
)
type DynamicSecretLeaseRevocationPolicy string
const (

View File

@@ -11,11 +11,6 @@ type MachineIdentityDetails struct {
ClientSecret string
}
type RequestUpdateUpdateDetails struct {
Modified bool
ETag string
}
type SingleEnvironmentVariable struct {
Key string `json:"key"`
Value string `json:"value"`

View File

@@ -25,6 +25,16 @@ func GetKubeSecretByNamespacedName(ctx context.Context, reconcilerClient client.
return kubeSecret, err
}
func GetKubeConfigMapByNamespacedName(ctx context.Context, reconcilerClient client.Client, namespacedName types.NamespacedName) (*corev1.ConfigMap, error) {
kubeConfigMap := &corev1.ConfigMap{}
err := reconcilerClient.Get(ctx, namespacedName, kubeConfigMap)
if err != nil {
kubeConfigMap = nil
}
return kubeConfigMap, err
}
func GetInfisicalUniversalAuthFromKubeSecret(ctx context.Context, reconcilerClient client.Client, universalAuthRef v1alpha1.KubeSecretReference) (machineIdentityDetails model.MachineIdentityDetails, err error) {
universalAuthCredsFromKubeSecret, err := GetKubeSecretByNamespacedName(ctx, reconcilerClient, types.NamespacedName{

View File

@@ -6,7 +6,6 @@ import (
"github.com/Infisical/infisical/k8-operator/api/v1alpha1"
"github.com/Infisical/infisical/k8-operator/packages/api"
"github.com/Infisical/infisical/k8-operator/packages/crypto"
"github.com/Infisical/infisical/k8-operator/packages/model"
"github.com/go-resty/resty/v2"
infisical "github.com/infisical/go-sdk"
@@ -49,7 +48,7 @@ func GetServiceTokenDetails(infisicalToken string) (api.GetServiceTokenDetailsRe
return serviceTokenDetails, nil
}
func GetPlainTextSecretsViaMachineIdentity(infisicalClient infisical.InfisicalClientInterface, etag string, secretScope v1alpha1.MachineIdentityScopeInWorkspace) ([]model.SingleEnvironmentVariable, model.RequestUpdateUpdateDetails, error) {
func GetPlainTextSecretsViaMachineIdentity(infisicalClient infisical.InfisicalClientInterface, secretScope v1alpha1.MachineIdentityScopeInWorkspace) ([]model.SingleEnvironmentVariable, error) {
secrets, err := infisicalClient.Secrets().List(infisical.ListSecretsOptions{
ProjectSlug: secretScope.ProjectSlug,
@@ -61,7 +60,7 @@ func GetPlainTextSecretsViaMachineIdentity(infisicalClient infisical.InfisicalCl
})
if err != nil {
return nil, model.RequestUpdateUpdateDetails{}, err
return nil, fmt.Errorf("unable to get secrets. [err=%v]", err)
}
var environmentVariables []model.SingleEnvironmentVariable
@@ -77,18 +76,13 @@ func GetPlainTextSecretsViaMachineIdentity(infisicalClient infisical.InfisicalCl
})
}
newEtag := crypto.ComputeEtag([]byte(fmt.Sprintf("%v", environmentVariables)))
return environmentVariables, model.RequestUpdateUpdateDetails{
Modified: etag != newEtag,
ETag: newEtag,
}, nil
return environmentVariables, nil
}
func GetPlainTextSecretsViaServiceToken(infisicalClient infisical.InfisicalClientInterface, fullServiceToken string, etag string, envSlug string, secretPath string, recursive bool) ([]model.SingleEnvironmentVariable, model.RequestUpdateUpdateDetails, error) {
func GetPlainTextSecretsViaServiceToken(infisicalClient infisical.InfisicalClientInterface, fullServiceToken string, envSlug string, secretPath string, recursive bool) ([]model.SingleEnvironmentVariable, error) {
serviceTokenParts := strings.SplitN(fullServiceToken, ".", 4)
if len(serviceTokenParts) < 4 {
return nil, model.RequestUpdateUpdateDetails{}, fmt.Errorf("invalid service token entered. Please double check your service token and try again")
return nil, fmt.Errorf("invalid service token entered. Please double check your service token and try again")
}
serviceToken := fmt.Sprintf("%v.%v.%v", serviceTokenParts[0], serviceTokenParts[1], serviceTokenParts[2])
@@ -100,7 +94,7 @@ func GetPlainTextSecretsViaServiceToken(infisicalClient infisical.InfisicalClien
serviceTokenDetails, err := api.CallGetServiceTokenDetailsV2(httpClient)
if err != nil {
return nil, model.RequestUpdateUpdateDetails{}, fmt.Errorf("unable to get service token details. [err=%v]", err)
return nil, fmt.Errorf("unable to get service token details. [err=%v]", err)
}
secrets, err := infisicalClient.Secrets().List(infisical.ListSecretsOptions{
@@ -113,7 +107,7 @@ func GetPlainTextSecretsViaServiceToken(infisicalClient infisical.InfisicalClien
})
if err != nil {
return nil, model.RequestUpdateUpdateDetails{}, err
return nil, err
}
var environmentVariables []model.SingleEnvironmentVariable
@@ -129,31 +123,26 @@ func GetPlainTextSecretsViaServiceToken(infisicalClient infisical.InfisicalClien
})
}
newEtag := crypto.ComputeEtag([]byte(fmt.Sprintf("%v", environmentVariables)))
return environmentVariables, model.RequestUpdateUpdateDetails{
Modified: etag != newEtag,
ETag: newEtag,
}, nil
return environmentVariables, nil
}
// Fetches plaintext secrets from an API endpoint using a service account.
// The function fetches the service account details and keys, decrypts the workspace key, fetches the encrypted secrets for the specified project and environment, and decrypts the secrets using the decrypted workspace key.
// Returns the plaintext secrets, encrypted secrets response, and any errors that occurred during the process.
func GetPlainTextSecretsViaServiceAccount(infisicalClient infisical.InfisicalClientInterface, serviceAccountCreds model.ServiceAccountDetails, projectId string, environmentName string, etag string) ([]model.SingleEnvironmentVariable, model.RequestUpdateUpdateDetails, error) {
func GetPlainTextSecretsViaServiceAccount(infisicalClient infisical.InfisicalClientInterface, serviceAccountCreds model.ServiceAccountDetails, projectId string, environmentName string) ([]model.SingleEnvironmentVariable, error) {
httpClient := resty.New()
httpClient.SetAuthToken(serviceAccountCreds.AccessKey).
SetHeader("Accept", "application/json")
serviceAccountDetails, err := api.CallGetServiceTokenAccountDetailsV2(httpClient)
if err != nil {
return nil, model.RequestUpdateUpdateDetails{}, fmt.Errorf("GetPlainTextSecretsViaServiceAccount: unable to get service account details. [err=%v]", err)
return nil, fmt.Errorf("GetPlainTextSecretsViaServiceAccount: unable to get service account details. [err=%v]", err)
}
serviceAccountKeys, err := api.CallGetServiceAccountKeysV2(httpClient, api.GetServiceAccountKeysRequest{ServiceAccountId: serviceAccountDetails.ServiceAccount.ID})
if err != nil {
return nil, model.RequestUpdateUpdateDetails{}, fmt.Errorf("GetPlainTextSecretsViaServiceAccount: unable to get service account key details. [err=%v]", err)
return nil, fmt.Errorf("GetPlainTextSecretsViaServiceAccount: unable to get service account key details. [err=%v]", err)
}
// find key for requested project
@@ -165,7 +154,7 @@ func GetPlainTextSecretsViaServiceAccount(infisicalClient infisical.InfisicalCli
}
if workspaceServiceAccountKey.ID == "" || workspaceServiceAccountKey.EncryptedKey == "" || workspaceServiceAccountKey.Nonce == "" || serviceAccountCreds.PublicKey == "" || serviceAccountCreds.PrivateKey == "" {
return nil, model.RequestUpdateUpdateDetails{}, fmt.Errorf("unable to find key for [projectId=%s] [err=%v]. Ensure that the given service account has access to given projectId", projectId, err)
return nil, fmt.Errorf("unable to find key for [projectId=%s] [err=%v]. Ensure that the given service account has access to given projectId", projectId, err)
}
secrets, err := infisicalClient.Secrets().List(infisical.ListSecretsOptions{
@@ -178,7 +167,7 @@ func GetPlainTextSecretsViaServiceAccount(infisicalClient infisical.InfisicalCli
})
if err != nil {
return nil, model.RequestUpdateUpdateDetails{}, err
return nil, err
}
var environmentVariables []model.SingleEnvironmentVariable
@@ -193,49 +182,5 @@ func GetPlainTextSecretsViaServiceAccount(infisicalClient infisical.InfisicalCli
})
}
newEtag := crypto.ComputeEtag([]byte(fmt.Sprintf("%v", environmentVariables)))
return environmentVariables, model.RequestUpdateUpdateDetails{
Modified: etag != newEtag,
ETag: newEtag,
}, nil
}
func getSecretsByKeys(secrets []model.SingleEnvironmentVariable) map[string]model.SingleEnvironmentVariable {
secretMapByName := make(map[string]model.SingleEnvironmentVariable, len(secrets))
for _, secret := range secrets {
secretMapByName[secret.Key] = secret
}
return secretMapByName
}
func MergeRawImportedSecrets(secrets []model.SingleEnvironmentVariable, importedSecrets []api.ImportedRawSecretV3) []model.SingleEnvironmentVariable {
if importedSecrets == nil {
return secrets
}
hasOverriden := make(map[string]bool)
for _, sec := range secrets {
hasOverriden[sec.Key] = true
}
for i := len(importedSecrets) - 1; i >= 0; i-- {
importSec := importedSecrets[i]
for _, sec := range importSec.Secrets {
if _, ok := hasOverriden[sec.SecretKey]; !ok {
secrets = append(secrets, model.SingleEnvironmentVariable{
Key: sec.SecretKey,
Value: sec.SecretValue,
Type: sec.Type,
ID: sec.ID,
})
hasOverriden[sec.SecretKey] = true
}
}
}
return secrets
return environmentVariables, nil
}