Merge remote-tracking branch 'origin/main' into misc/privilege-management-v2-transition

This commit is contained in:
Sheen Capadngan
2025-03-19 19:51:41 +08:00
264 changed files with 6747 additions and 890 deletions
+99 -67
View File
@@ -31,7 +31,7 @@
"@fastify/swagger-ui": "^2.1.0", "@fastify/swagger-ui": "^2.1.0",
"@google-cloud/kms": "^4.5.0", "@google-cloud/kms": "^4.5.0",
"@infisical/quic": "^1.0.8", "@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/auth-app": "^7.1.1",
"@octokit/plugin-retry": "^5.0.5", "@octokit/plugin-retry": "^5.0.5",
"@octokit/rest": "^20.0.2", "@octokit/rest": "^20.0.2",
@@ -6747,32 +6747,35 @@
} }
}, },
"node_modules/@node-saml/node-saml": { "node_modules/@node-saml/node-saml": {
"version": "4.0.5", "version": "5.0.1",
"resolved": "https://registry.npmjs.org/@node-saml/node-saml/-/node-saml-4.0.5.tgz", "resolved": "https://registry.npmjs.org/@node-saml/node-saml/-/node-saml-5.0.1.tgz",
"integrity": "sha512-J5DglElbY1tjOuaR1NPtjOXkXY5bpUhDoKVoeucYN98A3w4fwgjIOPqIGcb6cQsqFq2zZ6vTCeKn5C/hvefSaw==", "integrity": "sha512-YQzFPEC+CnsfO9AFYnwfYZKIzOLx3kITaC1HrjHVLTo6hxcQhc+LgHODOMvW4VCV95Gwrz1MshRUWCPzkDqmnA==",
"license": "MIT",
"dependencies": { "dependencies": {
"@types/debug": "^4.1.7", "@types/debug": "^4.1.12",
"@types/passport": "^1.0.11", "@types/qs": "^6.9.11",
"@types/xml-crypto": "^1.4.2", "@types/xml-encryption": "^1.2.4",
"@types/xml-encryption": "^1.2.1", "@types/xml2js": "^0.4.14",
"@types/xml2js": "^0.4.11", "@xmldom/is-dom-node": "^1.0.1",
"@xmldom/xmldom": "^0.8.6", "@xmldom/xmldom": "^0.8.10",
"debug": "^4.3.4", "debug": "^4.3.4",
"xml-crypto": "^3.0.1", "xml-crypto": "^6.0.1",
"xml-encryption": "^3.0.2", "xml-encryption": "^3.0.2",
"xml2js": "^0.5.0", "xml2js": "^0.6.2",
"xmlbuilder": "^15.1.1" "xmlbuilder": "^15.1.1",
"xpath": "^0.0.34"
}, },
"engines": { "engines": {
"node": ">= 14" "node": ">= 18"
} }
}, },
"node_modules/@node-saml/node-saml/node_modules/debug": { "node_modules/@node-saml/node-saml/node_modules/debug": {
"version": "4.3.4", "version": "4.4.0",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz",
"integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==",
"license": "MIT",
"dependencies": { "dependencies": {
"ms": "2.1.2" "ms": "^2.1.3"
}, },
"engines": { "engines": {
"node": ">=6.0" "node": ">=6.0"
@@ -6783,25 +6786,43 @@
} }
} }
}, },
"node_modules/@node-saml/node-saml/node_modules/ms": { "node_modules/@node-saml/node-saml/node_modules/xml2js": {
"version": "2.1.2", "version": "0.6.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.6.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" "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": { "node_modules/@node-saml/passport-saml": {
"version": "4.0.4", "version": "5.0.1",
"resolved": "https://registry.npmjs.org/@node-saml/passport-saml/-/passport-saml-4.0.4.tgz", "resolved": "https://registry.npmjs.org/@node-saml/passport-saml/-/passport-saml-5.0.1.tgz",
"integrity": "sha512-xFw3gw0yo+K1mzlkW15NeBF7cVpRHN/4vpjmBKzov5YFImCWh/G0LcTZ8krH3yk2/eRPc3Or8LRPudVJBjmYaw==", "integrity": "sha512-fMztg3zfSnjLEgxvpl6HaDMNeh0xeQX4QHiF9e2Lsie2dc4qFE37XYbQZhVmn8XJ2awPpSWLQ736UskYgGU8lQ==",
"license": "MIT",
"dependencies": { "dependencies": {
"@node-saml/node-saml": "^4.0.4", "@node-saml/node-saml": "^5.0.1",
"@types/express": "^4.17.14", "@types/express": "^4.17.21",
"@types/passport": "^1.0.11", "@types/passport": "^1.0.16",
"@types/passport-strategy": "^0.2.35", "@types/passport-strategy": "^0.2.38",
"passport": "^0.6.0", "passport": "^0.7.0",
"passport-strategy": "^1.0.0" "passport-strategy": "^1.0.0"
}, },
"engines": { "engines": {
"node": ">= 14" "node": ">= 18"
} }
}, },
"node_modules/@nodelib/fs.scandir": { "node_modules/@nodelib/fs.scandir": {
@@ -9606,6 +9627,7 @@
"version": "4.1.12", "version": "4.1.12",
"resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz", "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz",
"integrity": "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==", "integrity": "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==",
"license": "MIT",
"dependencies": { "dependencies": {
"@types/ms": "*" "@types/ms": "*"
} }
@@ -9725,9 +9747,10 @@
"integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==" "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w=="
}, },
"node_modules/@types/ms": { "node_modules/@types/ms": {
"version": "0.7.34", "version": "2.1.0",
"resolved": "https://registry.npmjs.org/@types/ms/-/ms-0.7.34.tgz", "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz",
"integrity": "sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g==" "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==",
"license": "MIT"
}, },
"node_modules/@types/node": { "node_modules/@types/node": {
"version": "20.9.5", "version": "20.9.5",
@@ -9907,9 +9930,10 @@
"dev": true "dev": true
}, },
"node_modules/@types/qs": { "node_modules/@types/qs": {
"version": "6.9.10", "version": "6.9.18",
"resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.10.tgz", "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.18.tgz",
"integrity": "sha512-3Gnx08Ns1sEoCrWssEgTSJs/rsT2vhGP+Ja9cnnk9k4ALxinORlQneLXFeFKOTJMOeZUFD1s7w+w2AphTpvzZw==" "integrity": "sha512-kK7dgTYDyGqS+e2Q4aK9X3D7q234CIZ1Bv0q/7Z5IwRDoADNU81xXJK/YVyLbLTZCoIwUoDoffFeF+p/eIklAA==",
"license": "MIT"
}, },
"node_modules/@types/range-parser": { "node_modules/@types/range-parser": {
"version": "1.2.7", "version": "1.2.7",
@@ -10058,19 +10082,11 @@
"@types/webidl-conversions": "*" "@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": { "node_modules/@types/xml-encryption": {
"version": "1.2.4", "version": "1.2.4",
"resolved": "https://registry.npmjs.org/@types/xml-encryption/-/xml-encryption-1.2.4.tgz", "resolved": "https://registry.npmjs.org/@types/xml-encryption/-/xml-encryption-1.2.4.tgz",
"integrity": "sha512-I69K/WW1Dv7j6O3jh13z0X8sLWJRXbu5xnHDl9yHzUNDUBtUoBY058eb5s+x/WG6yZC1h8aKdI2EoyEPjyEh+Q==", "integrity": "sha512-I69K/WW1Dv7j6O3jh13z0X8sLWJRXbu5xnHDl9yHzUNDUBtUoBY058eb5s+x/WG6yZC1h8aKdI2EoyEPjyEh+Q==",
"license": "MIT",
"dependencies": { "dependencies": {
"@types/node": "*" "@types/node": "*"
} }
@@ -10079,6 +10095,7 @@
"version": "0.4.14", "version": "0.4.14",
"resolved": "https://registry.npmjs.org/@types/xml2js/-/xml2js-0.4.14.tgz", "resolved": "https://registry.npmjs.org/@types/xml2js/-/xml2js-0.4.14.tgz",
"integrity": "sha512-4YnrRemBShWRO2QjvUin8ESA41rH+9nQGLUGZV/1IDhi3SL9OhdpNC/MrulTWuptXKwhx/aDxE7toV0f/ypIXQ==", "integrity": "sha512-4YnrRemBShWRO2QjvUin8ESA41rH+9nQGLUGZV/1IDhi3SL9OhdpNC/MrulTWuptXKwhx/aDxE7toV0f/ypIXQ==",
"license": "MIT",
"dependencies": { "dependencies": {
"@types/node": "*" "@types/node": "*"
} }
@@ -10522,10 +10539,20 @@
"url": "https://opencollective.com/vitest" "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": { "node_modules/@xmldom/xmldom": {
"version": "0.8.10", "version": "0.8.10",
"resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.10.tgz", "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.10.tgz",
"integrity": "sha512-2WALfTl4xo2SkGCYRt6rDTFfk9R1czmBvUQy12gK2KuRKIpWEhcbbzy8EZXtz/jkRqHX8bFEc6FC1HjX4TUWYw==", "integrity": "sha512-2WALfTl4xo2SkGCYRt6rDTFfk9R1czmBvUQy12gK2KuRKIpWEhcbbzy8EZXtz/jkRqHX8bFEc6FC1HjX4TUWYw==",
"license": "MIT",
"engines": { "engines": {
"node": ">=10.0.0" "node": ">=10.0.0"
} }
@@ -18222,9 +18249,10 @@
} }
}, },
"node_modules/passport": { "node_modules/passport": {
"version": "0.6.0", "version": "0.7.0",
"resolved": "https://registry.npmjs.org/passport/-/passport-0.6.0.tgz", "resolved": "https://registry.npmjs.org/passport/-/passport-0.7.0.tgz",
"integrity": "sha512-0fe+p3ZnrWRW74fe8+SvCyf4a3Pb2/h7gFkQ8yTJpAO50gDzlfjZUZTO1k5Eg9kUct22OxHLqDZoKUWRHOh9ug==", "integrity": "sha512-cPLl+qZpSc+ireUvt+IzqbED1cHHkDoVYMo30jbJIdOOjQ1MQYZBPiNvmi8UM6lJuOpTPXJGZQk0DtC4y61MYQ==",
"license": "MIT",
"dependencies": { "dependencies": {
"passport-strategy": "1.x.x", "passport-strategy": "1.x.x",
"pause": "0.0.1", "pause": "0.0.1",
@@ -23692,42 +23720,44 @@
} }
}, },
"node_modules/xml-crypto": { "node_modules/xml-crypto": {
"version": "3.2.0", "version": "6.0.1",
"resolved": "https://registry.npmjs.org/xml-crypto/-/xml-crypto-3.2.0.tgz", "resolved": "https://registry.npmjs.org/xml-crypto/-/xml-crypto-6.0.1.tgz",
"integrity": "sha512-qVurBUOQrmvlgmZqIVBqmb06TD2a/PpEUfFPgD7BuBfjmoH4zgkqaWSIJrnymlCvM2GGt9x+XtJFA+ttoAufqg==", "integrity": "sha512-v05aU7NS03z4jlZ0iZGRFeZsuKO1UfEbbYiaeRMiATBFs6Jq9+wqKquEMTn4UTrYZ9iGD8yz3KT4L9o2iF682w==",
"license": "MIT",
"dependencies": { "dependencies": {
"@xmldom/xmldom": "^0.8.8", "@xmldom/is-dom-node": "^1.0.1",
"xpath": "0.0.32" "@xmldom/xmldom": "^0.8.10",
"xpath": "^0.0.33"
}, },
"engines": { "engines": {
"node": ">=4.0.0" "node": ">=16"
} }
}, },
"node_modules/xml-crypto/node_modules/xpath": { "node_modules/xml-crypto/node_modules/xpath": {
"version": "0.0.32", "version": "0.0.33",
"resolved": "https://registry.npmjs.org/xpath/-/xpath-0.0.32.tgz", "resolved": "https://registry.npmjs.org/xpath/-/xpath-0.0.33.tgz",
"integrity": "sha512-rxMJhSIoiO8vXcWvSifKqhvV96GjiD5wYb8/QHdoRyQvraTpp4IEv944nhGausZZ3u7dhQXteZuZbaqfpB7uYw==", "integrity": "sha512-NNXnzrkDrAzalLhIUc01jO2mOzXGXh1JwPgkihcLLzw98c0WgYDmmjSh1Kl3wzaxSVWMuA+fe0WTWOBDWCBmNA==",
"license": "MIT",
"engines": { "engines": {
"node": ">=0.6.0" "node": ">=0.6.0"
} }
}, },
"node_modules/xml-encryption": { "node_modules/xml-encryption": {
"version": "3.0.2", "version": "3.1.0",
"resolved": "https://registry.npmjs.org/xml-encryption/-/xml-encryption-3.0.2.tgz", "resolved": "https://registry.npmjs.org/xml-encryption/-/xml-encryption-3.1.0.tgz",
"integrity": "sha512-VxYXPvsWB01/aqVLd6ZMPWZ+qaj0aIdF+cStrVJMcFj3iymwZeI0ABzB3VqMYv48DkSpRhnrXqTUkR34j+UDyg==", "integrity": "sha512-PV7qnYpoAMXbf1kvQkqMScLeQpjCMixddAKq9PtqVrho8HnYbBOWNfG0kA4R7zxQDo7w9kiYAyzS/ullAyO55Q==",
"license": "MIT",
"dependencies": { "dependencies": {
"@xmldom/xmldom": "^0.8.5", "@xmldom/xmldom": "^0.8.5",
"escape-html": "^1.0.3", "escape-html": "^1.0.3",
"xpath": "0.0.32" "xpath": "0.0.32"
},
"engines": {
"node": ">=12"
} }
}, },
"node_modules/xml-encryption/node_modules/xpath": { "node_modules/xml-encryption/node_modules/xpath": {
"version": "0.0.32", "version": "0.0.32",
"resolved": "https://registry.npmjs.org/xpath/-/xpath-0.0.32.tgz", "resolved": "https://registry.npmjs.org/xpath/-/xpath-0.0.32.tgz",
"integrity": "sha512-rxMJhSIoiO8vXcWvSifKqhvV96GjiD5wYb8/QHdoRyQvraTpp4IEv944nhGausZZ3u7dhQXteZuZbaqfpB7uYw==", "integrity": "sha512-rxMJhSIoiO8vXcWvSifKqhvV96GjiD5wYb8/QHdoRyQvraTpp4IEv944nhGausZZ3u7dhQXteZuZbaqfpB7uYw==",
"license": "MIT",
"engines": { "engines": {
"node": ">=0.6.0" "node": ">=0.6.0"
} }
@@ -23764,6 +23794,7 @@
"version": "15.1.1", "version": "15.1.1",
"resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-15.1.1.tgz", "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-15.1.1.tgz",
"integrity": "sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg==", "integrity": "sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg==",
"license": "MIT",
"engines": { "engines": {
"node": ">=8.0" "node": ">=8.0"
} }
@@ -23774,9 +23805,10 @@
"integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==" "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw=="
}, },
"node_modules/xpath": { "node_modules/xpath": {
"version": "0.0.27", "version": "0.0.34",
"resolved": "https://registry.npmjs.org/xpath/-/xpath-0.0.27.tgz", "resolved": "https://registry.npmjs.org/xpath/-/xpath-0.0.34.tgz",
"integrity": "sha512-fg03WRxtkCV6ohClePNAECYsmpKKTv5L8y/X3Dn1hQrec3POx2jHZ/0P2qQ6HvsrU1BmeqXcof3NGGueG6LxwQ==", "integrity": "sha512-FxF6+rkr1rNSQrhUNYrAFJpRXNzlDoMxeXN5qI84939ylEv3qqPFKa85Oxr6tDaJKqwW6KKyo2v26TSv3k6LeA==",
"license": "MIT",
"engines": { "engines": {
"node": ">=0.6.0" "node": ">=0.6.0"
} }
+1 -1
View File
@@ -148,7 +148,7 @@
"@fastify/swagger-ui": "^2.1.0", "@fastify/swagger-ui": "^2.1.0",
"@google-cloud/kms": "^4.5.0", "@google-cloud/kms": "^4.5.0",
"@infisical/quic": "^1.0.8", "@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/auth-app": "^7.1.1",
"@octokit/plugin-retry": "^5.0.5", "@octokit/plugin-retry": "^5.0.5",
"@octokit/rest": "^20.0.2", "@octokit/rest": "^20.0.2",
-7
View File
@@ -1,7 +0,0 @@
import "@fastify/request-context";
declare module "@fastify/request-context" {
interface RequestContextData {
reqId: string;
}
}
+6
View File
@@ -100,6 +100,12 @@ import { TWorkflowIntegrationServiceFactory } from "@app/services/workflow-integ
declare module "@fastify/request-context" { declare module "@fastify/request-context" {
interface RequestContextData { interface RequestContextData {
reqId: string; reqId: string;
identityAuthInfo?: {
identityId: string;
oidc?: {
claims: Record<string, string>;
};
};
} }
} }
@@ -85,7 +85,7 @@ export async function up(knex: Knex): Promise<void> {
} }
if (await knex.schema.hasTable(TableName.DynamicSecret)) { if (await knex.schema.hasTable(TableName.DynamicSecret)) {
const doesGatewayColExist = await knex.schema.hasColumn(TableName.DynamicSecret, "gatewayId"); const doesGatewayColExist = await knex.schema.hasColumn(TableName.DynamicSecret, "projectGatewayId");
await knex.schema.alterTable(TableName.DynamicSecret, (t) => { await knex.schema.alterTable(TableName.DynamicSecret, (t) => {
// not setting a foreign constraint so that cascade effects are not triggered // not setting a foreign constraint so that cascade effects are not triggered
if (!doesGatewayColExist) { if (!doesGatewayColExist) {
@@ -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");
});
}
}
}
@@ -0,0 +1,21 @@
import { Knex } from "knex";
import { TableName } from "../schemas";
export async function up(knex: Knex): Promise<void> {
const hasMappingField = await knex.schema.hasColumn(TableName.IdentityOidcAuth, "claimMetadataMapping");
if (!hasMappingField) {
await knex.schema.alterTable(TableName.IdentityOidcAuth, (t) => {
t.jsonb("claimMetadataMapping");
});
}
}
export async function down(knex: Knex): Promise<void> {
const hasMappingField = await knex.schema.hasColumn(TableName.IdentityOidcAuth, "claimMetadataMapping");
if (hasMappingField) {
await knex.schema.alterTable(TableName.IdentityOidcAuth, (t) => {
t.dropColumn("claimMetadataMapping");
});
}
}
@@ -26,7 +26,8 @@ export const IdentityOidcAuthsSchema = z.object({
boundSubject: z.string().nullable().optional(), boundSubject: z.string().nullable().optional(),
createdAt: z.date(), createdAt: z.date(),
updatedAt: z.date(), updatedAt: z.date(),
encryptedCaCertificate: zodBuffer.nullable().optional() encryptedCaCertificate: zodBuffer.nullable().optional(),
claimMetadataMapping: z.unknown().nullable().optional()
}); });
export type TIdentityOidcAuths = z.infer<typeof IdentityOidcAuthsSchema>; export type TIdentityOidcAuths = z.infer<typeof IdentityOidcAuthsSchema>;
+2 -1
View File
@@ -25,7 +25,8 @@ export const OrganizationsSchema = z.object({
selectedMfaMethod: z.string().nullable().optional(), selectedMfaMethod: z.string().nullable().optional(),
shouldUseNewPrivilegeSystem: z.boolean().default(true), shouldUseNewPrivilegeSystem: z.boolean().default(true),
privilegeUpgradeInitiatedByUsername: z.string().nullable().optional(), privilegeUpgradeInitiatedByUsername: z.string().nullable().optional(),
privilegeUpgradeInitiatedAt: z.date().nullable().optional() privilegeUpgradeInitiatedAt: z.date().nullable().optional(),
allowSecretSharingOutsideOrganization: z.boolean().default(true).nullable().optional()
}); });
export type TOrganizations = z.infer<typeof OrganizationsSchema>; export type TOrganizations = z.infer<typeof OrganizationsSchema>;
+2 -2
View File
@@ -12,7 +12,6 @@ import { TImmutableDBKeys } from "./models";
export const SecretSharingSchema = z.object({ export const SecretSharingSchema = z.object({
id: z.string().uuid(), id: z.string().uuid(),
encryptedValue: z.string().nullable().optional(), encryptedValue: z.string().nullable().optional(),
type: z.string(),
iv: z.string().nullable().optional(), iv: z.string().nullable().optional(),
tag: z.string().nullable().optional(), tag: z.string().nullable().optional(),
hashedHex: z.string().nullable().optional(), hashedHex: z.string().nullable().optional(),
@@ -27,7 +26,8 @@ export const SecretSharingSchema = z.object({
lastViewedAt: z.date().nullable().optional(), lastViewedAt: z.date().nullable().optional(),
password: z.string().nullable().optional(), password: z.string().nullable().optional(),
encryptedSecret: zodBuffer.nullable().optional(), encryptedSecret: zodBuffer.nullable().optional(),
identifier: z.string().nullable().optional() identifier: z.string().nullable().optional(),
type: z.string().default("share")
}); });
export type TSecretSharing = z.infer<typeof SecretSharingSchema>; export type TSecretSharing = z.infer<typeof SecretSharingSchema>;
@@ -1,10 +1,10 @@
import ms from "ms";
import { z } from "zod"; import { z } from "zod";
import { DynamicSecretLeasesSchema } from "@app/db/schemas"; import { DynamicSecretLeasesSchema } from "@app/db/schemas";
import { DYNAMIC_SECRET_LEASES } from "@app/lib/api-docs"; import { DYNAMIC_SECRET_LEASES } from "@app/lib/api-docs";
import { daysToMillisecond } from "@app/lib/dates"; import { daysToMillisecond } from "@app/lib/dates";
import { removeTrailingSlash } from "@app/lib/fn"; import { removeTrailingSlash } from "@app/lib/fn";
import { ms } from "@app/lib/ms";
import { readLimit, writeLimit } from "@app/server/config/rateLimiter"; import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { SanitizedDynamicSecretSchema } from "@app/server/routes/sanitizedSchemas"; import { SanitizedDynamicSecretSchema } from "@app/server/routes/sanitizedSchemas";
@@ -1,4 +1,3 @@
import ms from "ms";
import { z } from "zod"; import { z } from "zod";
import { DynamicSecretLeasesSchema } from "@app/db/schemas"; 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 { DYNAMIC_SECRETS } from "@app/lib/api-docs";
import { daysToMillisecond } from "@app/lib/dates"; import { daysToMillisecond } from "@app/lib/dates";
import { removeTrailingSlash } from "@app/lib/fn"; import { removeTrailingSlash } from "@app/lib/fn";
import { ms } from "@app/lib/ms";
import { readLimit, writeLimit } from "@app/server/config/rateLimiter"; import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
import { slugSchema } from "@app/server/lib/schemas"; import { slugSchema } from "@app/server/lib/schemas";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
@@ -1,11 +1,11 @@
import slugify from "@sindresorhus/slugify"; import slugify from "@sindresorhus/slugify";
import ms from "ms";
import { z } from "zod"; import { z } from "zod";
import { IdentityProjectAdditionalPrivilegeTemporaryMode } from "@app/ee/services/identity-project-additional-privilege/identity-project-additional-privilege-types"; 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 { backfillPermissionV1SchemaToV2Schema } from "@app/ee/services/permission/project-permission";
import { IDENTITY_ADDITIONAL_PRIVILEGE } from "@app/lib/api-docs"; import { IDENTITY_ADDITIONAL_PRIVILEGE } from "@app/lib/api-docs";
import { UnauthorizedError } from "@app/lib/errors"; import { UnauthorizedError } from "@app/lib/errors";
import { ms } from "@app/lib/ms";
import { alphaNumericNanoId } from "@app/lib/nanoid"; import { alphaNumericNanoId } from "@app/lib/nanoid";
import { readLimit, writeLimit } from "@app/server/config/rateLimiter"; import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
import { slugSchema } from "@app/server/lib/schemas"; import { slugSchema } from "@app/server/lib/schemas";
+1 -1
View File
@@ -1,10 +1,10 @@
import ms from "ms";
import { z } from "zod"; import { z } from "zod";
import { KmipClientsSchema } from "@app/db/schemas"; import { KmipClientsSchema } from "@app/db/schemas";
import { EventType } from "@app/ee/services/audit-log/audit-log-types"; import { EventType } from "@app/ee/services/audit-log/audit-log-types";
import { KmipPermission } from "@app/ee/services/kmip/kmip-enum"; import { KmipPermission } from "@app/ee/services/kmip/kmip-enum";
import { KmipClientOrderBy } from "@app/ee/services/kmip/kmip-types"; import { KmipClientOrderBy } from "@app/ee/services/kmip/kmip-types";
import { ms } from "@app/lib/ms";
import { OrderByDirection } from "@app/lib/types"; import { OrderByDirection } from "@app/lib/types";
import { readLimit, writeLimit } from "@app/server/config/rateLimiter"; import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
+30 -18
View File
@@ -25,7 +25,7 @@ type TSAMLConfig = {
callbackUrl: string; callbackUrl: string;
entryPoint: string; entryPoint: string;
issuer: string; issuer: string;
cert: string; idpCert: string;
audience: string; audience: string;
wantAuthnResponseSigned?: boolean; wantAuthnResponseSigned?: boolean;
wantAssertionsSigned?: boolean; wantAssertionsSigned?: boolean;
@@ -72,7 +72,7 @@ export const registerSamlRouter = async (server: FastifyZodProvider) => {
callbackUrl: `${appCfg.SITE_URL}/api/v1/sso/saml2/${ssoConfig.id}`, callbackUrl: `${appCfg.SITE_URL}/api/v1/sso/saml2/${ssoConfig.id}`,
entryPoint: ssoConfig.entryPoint, entryPoint: ssoConfig.entryPoint,
issuer: ssoConfig.issuer, issuer: ssoConfig.issuer,
cert: ssoConfig.cert, idpCert: ssoConfig.cert,
audience: appCfg.SITE_URL || "" audience: appCfg.SITE_URL || ""
}; };
if (ssoConfig.authProvider === SamlProviders.JUMPCLOUD_SAML) { if (ssoConfig.authProvider === SamlProviders.JUMPCLOUD_SAML) {
@@ -302,15 +302,21 @@ export const registerSamlRouter = async (server: FastifyZodProvider) => {
} }
}, },
handler: async (req) => { handler: async (req) => {
const saml = await server.services.saml.createSamlCfg({ const { isActive, authProvider, issuer, entryPoint, cert } = req.body;
actor: req.permission.type, const { permission } = req;
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod, return server.services.saml.createSamlCfg({
actorOrgId: req.permission.orgId, isActive,
orgId: req.body.organizationId, authProvider,
...req.body 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) => { handler: async (req) => {
const saml = await server.services.saml.updateSamlCfg({ const { isActive, authProvider, issuer, entryPoint, cert } = req.body;
actor: req.permission.type, const { permission } = req;
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod, return server.services.saml.updateSamlCfg({
actorOrgId: req.permission.orgId, isActive,
orgId: req.body.organizationId, authProvider,
...req.body issuer,
entryPoint,
idpCert: cert,
actor: permission.type,
actorId: permission.id,
actorAuthMethod: permission.authMethod,
actorOrgId: permission.orgId,
orgId: req.body.organizationId
}); });
return saml;
} }
}); });
}; };
@@ -1,9 +1,9 @@
import ms from "ms";
import { z } from "zod"; import { z } from "zod";
import { EventType } from "@app/ee/services/audit-log/audit-log-types"; import { EventType } from "@app/ee/services/audit-log/audit-log-types";
import { SshCertType } from "@app/ee/services/ssh/ssh-certificate-authority-types"; import { SshCertType } from "@app/ee/services/ssh/ssh-certificate-authority-types";
import { SSH_CERTIFICATE_AUTHORITIES } from "@app/lib/api-docs"; import { SSH_CERTIFICATE_AUTHORITIES } from "@app/lib/api-docs";
import { ms } from "@app/lib/ms";
import { writeLimit } from "@app/server/config/rateLimiter"; import { writeLimit } from "@app/server/config/rateLimiter";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type"; import { AuthMode } from "@app/services/auth/auth-type";
@@ -1,5 +1,4 @@
import slugify from "@sindresorhus/slugify"; import slugify from "@sindresorhus/slugify";
import ms from "ms";
import { z } from "zod"; import { z } from "zod";
import { EventType } from "@app/ee/services/audit-log/audit-log-types"; import { EventType } from "@app/ee/services/audit-log/audit-log-types";
@@ -10,6 +9,7 @@ import {
isValidUserPattern isValidUserPattern
} from "@app/ee/services/ssh-certificate-template/ssh-certificate-template-validators"; } from "@app/ee/services/ssh-certificate-template/ssh-certificate-template-validators";
import { SSH_CERTIFICATE_TEMPLATES } from "@app/lib/api-docs"; import { SSH_CERTIFICATE_TEMPLATES } from "@app/lib/api-docs";
import { ms } from "@app/lib/ms";
import { readLimit, writeLimit } from "@app/server/config/rateLimiter"; import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type"; import { AuthMode } from "@app/services/auth/auth-type";
@@ -1,11 +1,11 @@
import slugify from "@sindresorhus/slugify"; import slugify from "@sindresorhus/slugify";
import ms from "ms";
import { z } from "zod"; import { z } from "zod";
import { checkForInvalidPermissionCombination } from "@app/ee/services/permission/permission-fns"; import { checkForInvalidPermissionCombination } from "@app/ee/services/permission/permission-fns";
import { ProjectPermissionV2Schema } from "@app/ee/services/permission/project-permission"; 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 { 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 { PROJECT_USER_ADDITIONAL_PRIVILEGE } from "@app/lib/api-docs";
import { ms } from "@app/lib/ms";
import { alphaNumericNanoId } from "@app/lib/nanoid"; import { alphaNumericNanoId } from "@app/lib/nanoid";
import { readLimit, writeLimit } from "@app/server/config/rateLimiter"; import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
import { slugSchema } from "@app/server/lib/schemas"; import { slugSchema } from "@app/server/lib/schemas";
@@ -1,11 +1,11 @@
import slugify from "@sindresorhus/slugify"; import slugify from "@sindresorhus/slugify";
import ms from "ms";
import { z } from "zod"; import { z } from "zod";
import { IdentityProjectAdditionalPrivilegeTemporaryMode } from "@app/ee/services/identity-project-additional-privilege-v2/identity-project-additional-privilege-v2-types"; 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 { checkForInvalidPermissionCombination } from "@app/ee/services/permission/permission-fns";
import { ProjectPermissionV2Schema } from "@app/ee/services/permission/project-permission"; import { ProjectPermissionV2Schema } from "@app/ee/services/permission/project-permission";
import { IDENTITY_ADDITIONAL_PRIVILEGE_V2 } from "@app/lib/api-docs"; import { IDENTITY_ADDITIONAL_PRIVILEGE_V2 } from "@app/lib/api-docs";
import { ms } from "@app/lib/ms";
import { alphaNumericNanoId } from "@app/lib/nanoid"; import { alphaNumericNanoId } from "@app/lib/nanoid";
import { readLimit, writeLimit } from "@app/server/config/rateLimiter"; import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
import { slugSchema } from "@app/server/lib/schemas"; import { slugSchema } from "@app/server/lib/schemas";
@@ -1,9 +1,10 @@
import slugify from "@sindresorhus/slugify"; import slugify from "@sindresorhus/slugify";
import ms from "ms"; import msFn from "ms";
import { ActionProjectType, ProjectMembershipRole } from "@app/db/schemas"; import { ActionProjectType, ProjectMembershipRole } from "@app/db/schemas";
import { getConfig } from "@app/lib/config/env"; import { getConfig } from "@app/lib/config/env";
import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors"; import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors";
import { ms } from "@app/lib/ms";
import { alphaNumericNanoId } from "@app/lib/nanoid"; import { alphaNumericNanoId } from "@app/lib/nanoid";
import { TKmsServiceFactory } from "@app/services/kms/kms-service"; import { TKmsServiceFactory } from "@app/services/kms/kms-service";
import { TProjectDALFactory } from "@app/services/project/project-dal"; import { TProjectDALFactory } from "@app/services/project/project-dal";
@@ -246,7 +247,7 @@ export const accessApprovalRequestServiceFactory = ({
requesterEmail: requestedByUser.email, requesterEmail: requestedByUser.email,
isTemporary, isTemporary,
...(isTemporary && { ...(isTemporary && {
expiresIn: ms(ms(temporaryRange || ""), { long: true }) expiresIn: msFn(ms(temporaryRange || ""), { long: true })
}), }),
secretPath, secretPath,
environment: envSlug, environment: envSlug,
@@ -978,6 +978,7 @@ interface AddIdentityOidcAuthEvent {
boundIssuer: string; boundIssuer: string;
boundAudiences: string; boundAudiences: string;
boundClaims: Record<string, string>; boundClaims: Record<string, string>;
claimMetadataMapping: Record<string, string>;
boundSubject: string; boundSubject: string;
accessTokenTTL: number; accessTokenTTL: number;
accessTokenMaxTTL: number; accessTokenMaxTTL: number;
@@ -1002,6 +1003,7 @@ interface UpdateIdentityOidcAuthEvent {
boundIssuer?: string; boundIssuer?: string;
boundAudiences?: string; boundAudiences?: string;
boundClaims?: Record<string, string>; boundClaims?: Record<string, string>;
claimMetadataMapping?: Record<string, string>;
boundSubject?: string; boundSubject?: string;
accessTokenTTL?: number; accessTokenTTL?: number;
accessTokenMaxTTL?: number; accessTokenMaxTTL?: number;
@@ -1,5 +1,4 @@
import { ForbiddenError, subject } from "@casl/ability"; import { ForbiddenError, subject } from "@casl/ability";
import ms from "ms";
import { ActionProjectType } from "@app/db/schemas"; import { ActionProjectType } from "@app/db/schemas";
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service"; import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
@@ -11,6 +10,7 @@ import {
import { getConfig } from "@app/lib/config/env"; import { getConfig } from "@app/lib/config/env";
import { BadRequestError, NotFoundError } from "@app/lib/errors"; import { BadRequestError, NotFoundError } from "@app/lib/errors";
import { logger } from "@app/lib/logger"; import { logger } from "@app/lib/logger";
import { ms } from "@app/lib/ms";
import { TKmsServiceFactory } from "@app/services/kms/kms-service"; import { TKmsServiceFactory } from "@app/services/kms/kms-service";
import { KmsDataKey } from "@app/services/kms/kms-types"; import { KmsDataKey } from "@app/services/kms/kms-types";
import { TProjectDALFactory } from "@app/services/project/project-dal"; import { TProjectDALFactory } from "@app/services/project/project-dal";
@@ -1,9 +1,9 @@
import { ForbiddenError, subject } from "@casl/ability"; import { ForbiddenError, subject } from "@casl/ability";
import { packRules } from "@casl/ability/extra"; import { packRules } from "@casl/ability/extra";
import ms from "ms";
import { ActionProjectType, TableName } from "@app/db/schemas"; import { ActionProjectType, TableName } from "@app/db/schemas";
import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors"; import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors";
import { ms } from "@app/lib/ms";
import { unpackPermissions } from "@app/server/routes/sanitizedSchema/permission"; import { unpackPermissions } from "@app/server/routes/sanitizedSchema/permission";
import { ActorType } from "@app/services/auth/auth-type"; import { ActorType } from "@app/services/auth/auth-type";
import { TIdentityProjectDALFactory } from "@app/services/identity-project/identity-project-dal"; import { TIdentityProjectDALFactory } from "@app/services/identity-project/identity-project-dal";
@@ -1,9 +1,9 @@
import { ForbiddenError, MongoAbility, RawRuleOf, subject } from "@casl/ability"; import { ForbiddenError, MongoAbility, RawRuleOf, subject } from "@casl/ability";
import { PackRule, packRules, unpackRules } from "@casl/ability/extra"; import { PackRule, packRules, unpackRules } from "@casl/ability/extra";
import ms from "ms";
import { ActionProjectType } from "@app/db/schemas"; import { ActionProjectType } from "@app/db/schemas";
import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors"; import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors";
import { ms } from "@app/lib/ms";
import { UnpackedPermissionSchema } from "@app/server/routes/sanitizedSchema/permission"; import { UnpackedPermissionSchema } from "@app/server/routes/sanitizedSchema/permission";
import { ActorType } from "@app/services/auth/auth-type"; import { ActorType } from "@app/services/auth/auth-type";
import { TIdentityProjectDALFactory } from "@app/services/identity-project/identity-project-dal"; import { TIdentityProjectDALFactory } from "@app/services/identity-project/identity-project-dal";
+1 -1
View File
@@ -1,11 +1,11 @@
import { ForbiddenError } from "@casl/ability"; import { ForbiddenError } from "@casl/ability";
import * as x509 from "@peculiar/x509"; import * as x509 from "@peculiar/x509";
import crypto, { KeyObject } from "crypto"; import crypto, { KeyObject } from "crypto";
import ms from "ms";
import { ActionProjectType } from "@app/db/schemas"; import { ActionProjectType } from "@app/db/schemas";
import { BadRequestError, InternalServerError, NotFoundError } from "@app/lib/errors"; import { BadRequestError, InternalServerError, NotFoundError } from "@app/lib/errors";
import { isValidHostname, isValidIp } from "@app/lib/ip"; import { isValidHostname, isValidIp } from "@app/lib/ip";
import { ms } from "@app/lib/ms";
import { constructPemChainFromCerts } from "@app/services/certificate/certificate-fns"; import { constructPemChainFromCerts } from "@app/services/certificate/certificate-fns";
import { CertExtendedKeyUsage, CertKeyAlgorithm, CertKeyUsage } from "@app/services/certificate/certificate-types"; import { CertExtendedKeyUsage, CertKeyAlgorithm, CertKeyUsage } from "@app/services/certificate/certificate-types";
import { import {
@@ -32,6 +32,10 @@ export enum OrgPermissionAdminConsoleAction {
AccessAllProjects = "access-all-projects" AccessAllProjects = "access-all-projects"
} }
export enum OrgPermissionSecretShareAction {
ManageSettings = "manage-settings"
}
export enum OrgPermissionGatewayActions { export enum OrgPermissionGatewayActions {
// is there a better word for this. This mean can an identity be a gateway // is there a better word for this. This mean can an identity be a gateway
CreateGateways = "create-gateways", CreateGateways = "create-gateways",
@@ -81,7 +85,8 @@ export enum OrgPermissionSubjects {
ProjectTemplates = "project-templates", ProjectTemplates = "project-templates",
AppConnections = "app-connections", AppConnections = "app-connections",
Kmip = "kmip", Kmip = "kmip",
Gateway = "gateway" Gateway = "gateway",
SecretShare = "secret-share"
} }
export type AppConnectionSubjectFields = { export type AppConnectionSubjectFields = {
@@ -113,7 +118,8 @@ export type OrgPermissionSet =
) )
] ]
| [OrgPermissionAdminConsoleAction, OrgPermissionSubjects.AdminConsole] | [OrgPermissionAdminConsoleAction, OrgPermissionSubjects.AdminConsole]
| [OrgPermissionKmipActions, OrgPermissionSubjects.Kmip]; | [OrgPermissionKmipActions, OrgPermissionSubjects.Kmip]
| [OrgPermissionSecretShareAction, OrgPermissionSubjects.SecretShare];
const AppConnectionConditionSchema = z const AppConnectionConditionSchema = z
.object({ .object({
@@ -207,6 +213,12 @@ export const OrgPermissionSchema = z.discriminatedUnion("subject", [
"Describe what action an entity can take." "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({ z.object({
subject: z.literal(OrgPermissionSubjects.Kmip).describe("The entity this permission pertains to."), subject: z.literal(OrgPermissionSubjects.Kmip).describe("The entity this permission pertains to."),
action: CASL_ACTION_SCHEMA_NATIVE_ENUM(OrgPermissionKmipActions).describe( action: CASL_ACTION_SCHEMA_NATIVE_ENUM(OrgPermissionKmipActions).describe(
@@ -322,6 +334,8 @@ const buildAdminPermission = () => {
// the proxy assignment is temporary in order to prevent "more privilege" error during role assignment to MI // the proxy assignment is temporary in order to prevent "more privilege" error during role assignment to MI
can(OrgPermissionKmipActions.Proxy, OrgPermissionSubjects.Kmip); can(OrgPermissionKmipActions.Proxy, OrgPermissionSubjects.Kmip);
can(OrgPermissionSecretShareAction.ManageSettings, OrgPermissionSubjects.SecretShare);
return rules; return rules;
}; };
@@ -133,12 +133,12 @@ function validateOrgSSO(actorAuthMethod: ActorAuthMethod, isOrgSsoEnforced: TOrg
} }
} }
const escapeHandlebarsMissingMetadata = (obj: Record<string, string>) => { const escapeHandlebarsMissingDict = (obj: Record<string, string>, key: string) => {
const handler = { const handler = {
get(target: Record<string, string>, prop: string) { get(target: Record<string, string>, prop: string) {
if (!(prop in target)) { if (!Object.hasOwn(target, prop)) {
// eslint-disable-next-line no-param-reassign // eslint-disable-next-line no-param-reassign
target[prop] = `{{identity.metadata.${prop}}}`; // Add missing key as an "own" property target[prop] = `{{${key}.${prop}}}`; // Add missing key as an "own" property
} }
return target[prop]; return target[prop];
} }
@@ -194,7 +194,7 @@ const constructPermissionErrorMessage = (
export { export {
constructPermissionErrorMessage, constructPermissionErrorMessage,
escapeHandlebarsMissingMetadata, escapeHandlebarsMissingDict,
isAuthMethodSaml, isAuthMethodSaml,
validateOrgSSO, validateOrgSSO,
validatePrivilegeChangeOperation validatePrivilegeChangeOperation
@@ -1,5 +1,6 @@
import { createMongoAbility, MongoAbility, RawRuleOf } from "@casl/ability"; import { createMongoAbility, MongoAbility, RawRuleOf } from "@casl/ability";
import { PackRule, unpackRules } from "@casl/ability/extra"; import { PackRule, unpackRules } from "@casl/ability/extra";
import { requestContext } from "@fastify/request-context";
import { MongoQuery } from "@ucast/mongo2js"; import { MongoQuery } from "@ucast/mongo2js";
import handlebars from "handlebars"; import handlebars from "handlebars";
@@ -22,7 +23,7 @@ import { TServiceTokenDALFactory } from "@app/services/service-token/service-tok
import { orgAdminPermissions, orgMemberPermissions, orgNoAccessPermissions, OrgPermissionSet } from "./org-permission"; import { orgAdminPermissions, orgMemberPermissions, orgNoAccessPermissions, OrgPermissionSet } from "./org-permission";
import { TPermissionDALFactory } from "./permission-dal"; import { TPermissionDALFactory } from "./permission-dal";
import { escapeHandlebarsMissingMetadata, validateOrgSSO } from "./permission-fns"; import { escapeHandlebarsMissingDict, validateOrgSSO } from "./permission-fns";
import { import {
TBuildOrgPermissionDTO, TBuildOrgPermissionDTO,
TBuildProjectPermissionDTO, TBuildProjectPermissionDTO,
@@ -243,20 +244,22 @@ export const permissionServiceFactory = ({
const rules = buildProjectPermissionRules(rolePermissions.concat(additionalPrivileges)); const rules = buildProjectPermissionRules(rolePermissions.concat(additionalPrivileges));
const templatedRules = handlebars.compile(JSON.stringify(rules), { data: false }); const templatedRules = handlebars.compile(JSON.stringify(rules), { data: false });
const metadataKeyValuePair = escapeHandlebarsMissingMetadata( const metadataKeyValuePair = escapeHandlebarsMissingDict(
objectify( objectify(
userProjectPermission.metadata, userProjectPermission.metadata,
(i) => i.key, (i) => i.key,
(i) => i.value (i) => i.value
) ),
"identity.metadata"
); );
const templateValue = {
id: userProjectPermission.userId,
username: userProjectPermission.username,
metadata: metadataKeyValuePair
};
const interpolateRules = templatedRules( const interpolateRules = templatedRules(
{ {
identity: { identity: templateValue
id: userProjectPermission.userId,
username: userProjectPermission.username,
metadata: metadataKeyValuePair
}
}, },
{ data: false } { data: false }
); );
@@ -317,21 +320,26 @@ export const permissionServiceFactory = ({
const rules = buildProjectPermissionRules(rolePermissions.concat(additionalPrivileges)); const rules = buildProjectPermissionRules(rolePermissions.concat(additionalPrivileges));
const templatedRules = handlebars.compile(JSON.stringify(rules), { data: false }); const templatedRules = handlebars.compile(JSON.stringify(rules), { data: false });
const metadataKeyValuePair = escapeHandlebarsMissingMetadata( const unescapedIdentityAuthInfo = requestContext.get("identityAuthInfo");
objectify( const unescapedMetadata = objectify(
identityProjectPermission.metadata, identityProjectPermission.metadata,
(i) => i.key, (i) => i.key,
(i) => i.value (i) => i.value
)
); );
const identityAuthInfo =
unescapedIdentityAuthInfo?.identityId === identityId && unescapedIdentityAuthInfo
? escapeHandlebarsMissingDict(unescapedIdentityAuthInfo as never, "identity.auth")
: {};
const metadataKeyValuePair = escapeHandlebarsMissingDict(unescapedMetadata, "identity.metadata");
const templateValue = {
id: identityProjectPermission.identityId,
username: identityProjectPermission.username,
metadata: metadataKeyValuePair,
auth: identityAuthInfo
};
const interpolateRules = templatedRules( const interpolateRules = templatedRules(
{ {
identity: { identity: templateValue
id: identityProjectPermission.identityId,
username: identityProjectPermission.username,
metadata: metadataKeyValuePair
}
}, },
{ data: false } { data: false }
); );
@@ -429,20 +437,22 @@ export const permissionServiceFactory = ({
const rules = buildProjectPermissionRules(rolePermissions.concat(additionalPrivileges)); const rules = buildProjectPermissionRules(rolePermissions.concat(additionalPrivileges));
const templatedRules = handlebars.compile(JSON.stringify(rules), { data: false }); const templatedRules = handlebars.compile(JSON.stringify(rules), { data: false });
const metadataKeyValuePair = escapeHandlebarsMissingMetadata( const metadataKeyValuePair = escapeHandlebarsMissingDict(
objectify( objectify(
userProjectPermission.metadata, userProjectPermission.metadata,
(i) => i.key, (i) => i.key,
(i) => i.value (i) => i.value
) ),
"identity.metadata"
); );
const templateValue = {
id: userProjectPermission.userId,
username: userProjectPermission.username,
metadata: metadataKeyValuePair
};
const interpolateRules = templatedRules( const interpolateRules = templatedRules(
{ {
identity: { identity: templateValue
id: userProjectPermission.userId,
username: userProjectPermission.username,
metadata: metadataKeyValuePair
}
}, },
{ data: false } { data: false }
); );
@@ -474,21 +484,22 @@ export const permissionServiceFactory = ({
const rules = buildProjectPermissionRules(rolePermissions.concat(additionalPrivileges)); const rules = buildProjectPermissionRules(rolePermissions.concat(additionalPrivileges));
const templatedRules = handlebars.compile(JSON.stringify(rules), { data: false }); const templatedRules = handlebars.compile(JSON.stringify(rules), { data: false });
const metadataKeyValuePair = escapeHandlebarsMissingMetadata( const metadataKeyValuePair = escapeHandlebarsMissingDict(
objectify( objectify(
identityProjectPermission.metadata, identityProjectPermission.metadata,
(i) => i.key, (i) => i.key,
(i) => i.value (i) => i.value
) ),
"identity.metadata"
); );
const templateValue = {
id: identityProjectPermission.identityId,
username: identityProjectPermission.username,
metadata: metadataKeyValuePair
};
const interpolateRules = templatedRules( const interpolateRules = templatedRules(
{ {
identity: { identity: templateValue
id: identityProjectPermission.identityId,
username: identityProjectPermission.username,
metadata: metadataKeyValuePair
}
}, },
{ data: false } { data: false }
); );
@@ -1,9 +1,9 @@
import { ForbiddenError, MongoAbility, RawRuleOf } from "@casl/ability"; import { ForbiddenError, MongoAbility, RawRuleOf } from "@casl/ability";
import { PackRule, packRules, unpackRules } from "@casl/ability/extra"; import { PackRule, packRules, unpackRules } from "@casl/ability/extra";
import ms from "ms";
import { ActionProjectType, TableName } from "@app/db/schemas"; import { ActionProjectType, TableName } from "@app/db/schemas";
import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors"; import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors";
import { ms } from "@app/lib/ms";
import { UnpackedPermissionSchema } from "@app/server/routes/sanitizedSchema/permission"; import { UnpackedPermissionSchema } from "@app/server/routes/sanitizedSchema/permission";
import { ActorType } from "@app/services/auth/auth-type"; import { ActorType } from "@app/services/auth/auth-type";
import { TProjectMembershipDALFactory } from "@app/services/project-membership/project-membership-dal"; import { TProjectMembershipDALFactory } from "@app/services/project-membership/project-membership-dal";
@@ -63,7 +63,7 @@ export const samlConfigServiceFactory = ({
kmsService kmsService
}: TSamlConfigServiceFactoryDep) => { }: TSamlConfigServiceFactoryDep) => {
const createSamlCfg = async ({ const createSamlCfg = async ({
cert, idpCert,
actor, actor,
actorAuthMethod, actorAuthMethod,
actorOrgId, actorOrgId,
@@ -93,9 +93,9 @@ export const samlConfigServiceFactory = ({
orgId, orgId,
authProvider, authProvider,
isActive, isActive,
encryptedSamlIssuer: encryptor({ plainText: Buffer.from(issuer) }).cipherTextBlob, encryptedSamlCertificate: encryptor({ plainText: Buffer.from(idpCert) }).cipherTextBlob,
encryptedSamlEntryPoint: encryptor({ plainText: Buffer.from(entryPoint) }).cipherTextBlob, encryptedSamlEntryPoint: encryptor({ plainText: Buffer.from(entryPoint) }).cipherTextBlob,
encryptedSamlCertificate: encryptor({ plainText: Buffer.from(cert) }).cipherTextBlob encryptedSamlIssuer: encryptor({ plainText: Buffer.from(issuer) }).cipherTextBlob
}); });
return samlConfig; return samlConfig;
@@ -106,7 +106,7 @@ export const samlConfigServiceFactory = ({
actor, actor,
actorOrgId, actorOrgId,
actorAuthMethod, actorAuthMethod,
cert, idpCert,
actorId, actorId,
issuer, issuer,
isActive, isActive,
@@ -136,8 +136,8 @@ export const samlConfigServiceFactory = ({
updateQuery.encryptedSamlIssuer = encryptor({ plainText: Buffer.from(issuer) }).cipherTextBlob; updateQuery.encryptedSamlIssuer = encryptor({ plainText: Buffer.from(issuer) }).cipherTextBlob;
} }
if (cert !== undefined) { if (idpCert !== undefined) {
updateQuery.encryptedSamlCertificate = encryptor({ plainText: Buffer.from(cert) }).cipherTextBlob; updateQuery.encryptedSamlCertificate = encryptor({ plainText: Buffer.from(idpCert) }).cipherTextBlob;
} }
const [ssoConfig] = await samlConfigDAL.update({ orgId }, updateQuery); const [ssoConfig] = await samlConfigDAL.update({ orgId }, updateQuery);
@@ -15,7 +15,7 @@ export type TCreateSamlCfgDTO = {
isActive: boolean; isActive: boolean;
entryPoint: string; entryPoint: string;
issuer: string; issuer: string;
cert: string; idpCert: string;
} & TOrgPermission; } & TOrgPermission;
export type TUpdateSamlCfgDTO = Partial<{ export type TUpdateSamlCfgDTO = Partial<{
@@ -23,7 +23,7 @@ export type TUpdateSamlCfgDTO = Partial<{
isActive: boolean; isActive: boolean;
entryPoint: string; entryPoint: string;
issuer: string; issuer: string;
cert: string; idpCert: string;
}> & }> &
TOrgPermission; TOrgPermission;
@@ -1,10 +1,10 @@
import { ForbiddenError } from "@casl/ability"; import { ForbiddenError } from "@casl/ability";
import ms from "ms";
import { ActionProjectType } from "@app/db/schemas"; import { ActionProjectType } from "@app/db/schemas";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service"; import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission"; import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
import { BadRequestError, NotFoundError } from "@app/lib/errors"; import { BadRequestError, NotFoundError } from "@app/lib/errors";
import { ms } from "@app/lib/ms";
import { TSshCertificateAuthorityDALFactory } from "../ssh/ssh-certificate-authority-dal"; import { TSshCertificateAuthorityDALFactory } from "../ssh/ssh-certificate-authority-dal";
import { TSshCertificateTemplateDALFactory } from "./ssh-certificate-template-dal"; import { TSshCertificateTemplateDALFactory } from "./ssh-certificate-template-dal";
@@ -1,13 +1,13 @@
import { execFile } from "child_process"; import { execFile } from "child_process";
import crypto from "crypto"; import crypto from "crypto";
import { promises as fs } from "fs"; import { promises as fs } from "fs";
import ms from "ms";
import os from "os"; import os from "os";
import path from "path"; import path from "path";
import { promisify } from "util"; import { promisify } from "util";
import { TSshCertificateTemplates } from "@app/db/schemas"; import { TSshCertificateTemplates } from "@app/db/schemas";
import { BadRequestError } from "@app/lib/errors"; import { BadRequestError } from "@app/lib/errors";
import { ms } from "@app/lib/ms";
import { CertKeyAlgorithm } from "@app/services/certificate/certificate-types"; import { CertKeyAlgorithm } from "@app/services/certificate/certificate-types";
import { import {
+10 -1
View File
@@ -329,6 +329,7 @@ export const OIDC_AUTH = {
boundIssuer: "The unique identifier of the identity provider issuing the JWT.", boundIssuer: "The unique identifier of the identity provider issuing the JWT.",
boundAudiences: "The list of intended recipients.", boundAudiences: "The list of intended recipients.",
boundClaims: "The attributes that should be present in the JWT for it to be valid.", boundClaims: "The attributes that should be present in the JWT for it to be valid.",
claimMetadataMapping: "The attributes that should be present in the permission metadata from the JWT.",
boundSubject: "The expected principal that is the subject of the JWT.", boundSubject: "The expected principal that is the subject of the JWT.",
accessTokenTrustedIps: "The IPs or CIDR ranges that access tokens can be used from.", accessTokenTrustedIps: "The IPs or CIDR ranges that access tokens can be used from.",
accessTokenTTL: "The lifetime for an access token in seconds.", accessTokenTTL: "The lifetime for an access token in seconds.",
@@ -342,6 +343,7 @@ export const OIDC_AUTH = {
boundIssuer: "The new unique identifier of the identity provider issuing the JWT.", boundIssuer: "The new unique identifier of the identity provider issuing the JWT.",
boundAudiences: "The new list of intended recipients.", boundAudiences: "The new list of intended recipients.",
boundClaims: "The new attributes that should be present in the JWT for it to be valid.", boundClaims: "The new attributes that should be present in the JWT for it to be valid.",
claimMetadataMapping: "The new attributes that should be present in the permission metadata from the JWT.",
boundSubject: "The new expected principal that is the subject of the JWT.", boundSubject: "The new expected principal that is the subject of the JWT.",
accessTokenTrustedIps: "The new IPs or CIDR ranges that access tokens can be used from.", accessTokenTrustedIps: "The new IPs or CIDR ranges that access tokens can be used from.",
accessTokenTTL: "The new lifetime for an access token in seconds.", accessTokenTTL: "The new lifetime for an access token in seconds.",
@@ -1725,7 +1727,8 @@ export const SecretSyncs = {
SYNC_OPTIONS: (destination: SecretSync) => { SYNC_OPTIONS: (destination: SecretSync) => {
const destinationName = SECRET_SYNC_NAME_MAP[destination]; const destinationName = SECRET_SYNC_NAME_MAP[destination];
return { return {
initialSyncBehavior: `Specify how Infisical should resolve the initial sync to the ${destinationName} destination.` initialSyncBehavior: `Specify how Infisical should resolve the initial sync to the ${destinationName} destination.`,
disableSecretDeletion: `Enable this flag to prevent removal of secrets from the ${destinationName} destination when syncing.`
}; };
}, },
ADDITIONAL_SYNC_OPTIONS: { ADDITIONAL_SYNC_OPTIONS: {
@@ -1771,6 +1774,12 @@ export const SecretSyncs = {
}, },
DATABRICKS: { DATABRICKS: {
scope: "The Databricks secret scope that secrets should be synced to." scope: "The Databricks secret scope that secrets should be synced to."
},
HUMANITEC: {
app: "The ID of the Humanitec app to sync secrets to.",
org: "The ID of the Humanitec org to sync secrets to.",
env: "The ID of the Humanitec environment to sync secrets to.",
scope: "The Humanitec scope that secrets should be synced to."
} }
} }
}; };
+15
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}` });
}
};
+34
View File
@@ -0,0 +1,34 @@
/**
* Safely retrieves a value from a nested object using dot notation path
*/
export const getStringValueByDot = (
obj: Record<string, unknown> | null | undefined,
path: string,
defaultValue?: string
): string | undefined => {
// Handle null or undefined input
if (!obj) {
return defaultValue;
}
const parts = path.split(".");
let current: unknown = obj;
for (const part of parts) {
const isObject = typeof current === "object" && !Array.isArray(current) && current !== null;
if (!isObject) {
return defaultValue;
}
if (!Object.hasOwn(current as object, part)) {
// Check if the property exists as an own property
return defaultValue;
}
current = (current as Record<string, unknown>)[part];
}
if (typeof current !== "string") {
return defaultValue;
}
return current;
};
@@ -1,3 +1,4 @@
import { requestContext } from "@fastify/request-context";
import { FastifyRequest } from "fastify"; import { FastifyRequest } from "fastify";
import fp from "fastify-plugin"; import fp from "fastify-plugin";
import jwt, { JwtPayload } from "jsonwebtoken"; import jwt, { JwtPayload } from "jsonwebtoken";
@@ -137,6 +138,12 @@ export const injectIdentity = fp(async (server: FastifyZodProvider) => {
identityName: identity.name, identityName: identity.name,
authMethod: null authMethod: null
}; };
if (token?.identityAuth?.oidc) {
requestContext.set("identityAuthInfo", {
identityId: identity.identityId,
oidc: token?.identityAuth?.oidc
});
}
break; break;
} }
case AuthMode.SERVICE_TOKEN: { case AuthMode.SERVICE_TOKEN: {
@@ -18,6 +18,10 @@ import {
} from "@app/services/app-connection/databricks"; } from "@app/services/app-connection/databricks";
import { GcpConnectionListItemSchema, SanitizedGcpConnectionSchema } from "@app/services/app-connection/gcp"; import { GcpConnectionListItemSchema, SanitizedGcpConnectionSchema } from "@app/services/app-connection/gcp";
import { GitHubConnectionListItemSchema, SanitizedGitHubConnectionSchema } from "@app/services/app-connection/github"; import { GitHubConnectionListItemSchema, SanitizedGitHubConnectionSchema } from "@app/services/app-connection/github";
import {
HumanitecConnectionListItemSchema,
SanitizedHumanitecConnectionSchema
} from "@app/services/app-connection/humanitec";
import { AuthMode } from "@app/services/auth/auth-type"; import { AuthMode } from "@app/services/auth/auth-type";
// can't use discriminated due to multiple schemas for certain apps // can't use discriminated due to multiple schemas for certain apps
@@ -27,7 +31,8 @@ const SanitizedAppConnectionSchema = z.union([
...SanitizedGcpConnectionSchema.options, ...SanitizedGcpConnectionSchema.options,
...SanitizedAzureKeyVaultConnectionSchema.options, ...SanitizedAzureKeyVaultConnectionSchema.options,
...SanitizedAzureAppConfigurationConnectionSchema.options, ...SanitizedAzureAppConfigurationConnectionSchema.options,
...SanitizedDatabricksConnectionSchema.options ...SanitizedDatabricksConnectionSchema.options,
...SanitizedHumanitecConnectionSchema.options
]); ]);
const AppConnectionOptionsSchema = z.discriminatedUnion("app", [ const AppConnectionOptionsSchema = z.discriminatedUnion("app", [
@@ -36,7 +41,8 @@ const AppConnectionOptionsSchema = z.discriminatedUnion("app", [
GcpConnectionListItemSchema, GcpConnectionListItemSchema,
AzureKeyVaultConnectionListItemSchema, AzureKeyVaultConnectionListItemSchema,
AzureAppConfigurationConnectionListItemSchema, AzureAppConfigurationConnectionListItemSchema,
DatabricksConnectionListItemSchema DatabricksConnectionListItemSchema,
HumanitecConnectionListItemSchema
]); ]);
export const registerAppConnectionRouter = async (server: FastifyZodProvider) => { export const registerAppConnectionRouter = async (server: FastifyZodProvider) => {
@@ -0,0 +1,69 @@
import z from "zod";
import { readLimit } from "@app/server/config/rateLimiter";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
import {
CreateHumanitecConnectionSchema,
HumanitecOrgWithApps,
SanitizedHumanitecConnectionSchema,
UpdateHumanitecConnectionSchema
} from "@app/services/app-connection/humanitec";
import { AuthMode } from "@app/services/auth/auth-type";
import { registerAppConnectionEndpoints } from "./app-connection-endpoints";
export const registerHumanitecConnectionRouter = async (server: FastifyZodProvider) => {
registerAppConnectionEndpoints({
app: AppConnection.Humanitec,
server,
sanitizedResponseSchema: SanitizedHumanitecConnectionSchema,
createSchema: CreateHumanitecConnectionSchema,
updateSchema: UpdateHumanitecConnectionSchema
});
// The below endpoints are not exposed and for Infisical App use
server.route({
method: "GET",
url: `/:connectionId/organizations`,
config: {
rateLimit: readLimit
},
schema: {
params: z.object({
connectionId: z.string().uuid()
}),
response: {
200: z
.object({
id: z.string(),
name: z.string(),
apps: z
.object({
id: z.string(),
name: z.string(),
envs: z
.object({
id: z.string(),
name: z.string()
})
.array()
})
.array()
})
.array()
}
},
onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => {
const { connectionId } = req.params;
const organizations: HumanitecOrgWithApps[] = await server.services.appConnection.humanitec.listOrganizations(
connectionId,
req.permission
);
return organizations;
}
});
};
@@ -6,6 +6,7 @@ import { registerAzureKeyVaultConnectionRouter } from "./azure-key-vault-connect
import { registerDatabricksConnectionRouter } from "./databricks-connection-router"; import { registerDatabricksConnectionRouter } from "./databricks-connection-router";
import { registerGcpConnectionRouter } from "./gcp-connection-router"; import { registerGcpConnectionRouter } from "./gcp-connection-router";
import { registerGitHubConnectionRouter } from "./github-connection-router"; import { registerGitHubConnectionRouter } from "./github-connection-router";
import { registerHumanitecConnectionRouter } from "./humanitec-connection-router";
export * from "./app-connection-router"; export * from "./app-connection-router";
@@ -16,5 +17,6 @@ export const APP_CONNECTION_REGISTER_ROUTER_MAP: Record<AppConnection, (server:
[AppConnection.GCP]: registerGcpConnectionRouter, [AppConnection.GCP]: registerGcpConnectionRouter,
[AppConnection.AzureKeyVault]: registerAzureKeyVaultConnectionRouter, [AppConnection.AzureKeyVault]: registerAzureKeyVaultConnectionRouter,
[AppConnection.AzureAppConfiguration]: registerAzureAppConfigurationConnectionRouter, [AppConnection.AzureAppConfiguration]: registerAzureAppConfigurationConnectionRouter,
[AppConnection.Databricks]: registerDatabricksConnectionRouter [AppConnection.Databricks]: registerDatabricksConnectionRouter,
[AppConnection.Humanitec]: registerHumanitecConnectionRouter
}; };
@@ -1,10 +1,10 @@
/* eslint-disable @typescript-eslint/no-floating-promises */ /* eslint-disable @typescript-eslint/no-floating-promises */
import ms from "ms";
import { z } from "zod"; import { z } from "zod";
import { CertificateAuthoritiesSchema, CertificateTemplatesSchema } from "@app/db/schemas"; import { CertificateAuthoritiesSchema, CertificateTemplatesSchema } from "@app/db/schemas";
import { EventType } from "@app/ee/services/audit-log/audit-log-types"; import { EventType } from "@app/ee/services/audit-log/audit-log-types";
import { CERTIFICATE_AUTHORITIES } from "@app/lib/api-docs"; import { CERTIFICATE_AUTHORITIES } from "@app/lib/api-docs";
import { ms } from "@app/lib/ms";
import { readLimit, writeLimit } from "@app/server/config/rateLimiter"; import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type"; import { AuthMode } from "@app/services/auth/auth-type";
@@ -1,9 +1,9 @@
import ms from "ms";
import { z } from "zod"; import { z } from "zod";
import { CertificatesSchema } from "@app/db/schemas"; import { CertificatesSchema } from "@app/db/schemas";
import { EventType } from "@app/ee/services/audit-log/audit-log-types"; import { EventType } from "@app/ee/services/audit-log/audit-log-types";
import { CERTIFICATE_AUTHORITIES, CERTIFICATES } from "@app/lib/api-docs"; import { CERTIFICATE_AUTHORITIES, CERTIFICATES } from "@app/lib/api-docs";
import { ms } from "@app/lib/ms";
import { readLimit, writeLimit } from "@app/server/config/rateLimiter"; import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type"; import { AuthMode } from "@app/services/auth/auth-type";
@@ -1,9 +1,9 @@
import ms from "ms";
import { z } from "zod"; import { z } from "zod";
import { CertificateTemplateEstConfigsSchema } from "@app/db/schemas"; import { CertificateTemplateEstConfigsSchema } from "@app/db/schemas";
import { EventType } from "@app/ee/services/audit-log/audit-log-types"; import { EventType } from "@app/ee/services/audit-log/audit-log-types";
import { CERTIFICATE_TEMPLATES } from "@app/lib/api-docs"; import { CERTIFICATE_TEMPLATES } from "@app/lib/api-docs";
import { ms } from "@app/lib/ms";
import { readLimit, writeLimit } from "@app/server/config/rateLimiter"; import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type"; import { AuthMode } from "@app/services/auth/auth-type";
@@ -23,6 +23,7 @@ const IdentityOidcAuthResponseSchema = IdentityOidcAuthsSchema.pick({
boundIssuer: true, boundIssuer: true,
boundAudiences: true, boundAudiences: true,
boundClaims: true, boundClaims: true,
claimMetadataMapping: true,
boundSubject: true, boundSubject: true,
createdAt: true, createdAt: true,
updatedAt: true updatedAt: true
@@ -104,6 +105,7 @@ export const registerIdentityOidcAuthRouter = async (server: FastifyZodProvider)
boundIssuer: z.string().min(1).describe(OIDC_AUTH.ATTACH.boundIssuer), boundIssuer: z.string().min(1).describe(OIDC_AUTH.ATTACH.boundIssuer),
boundAudiences: validateOidcAuthAudiencesField.describe(OIDC_AUTH.ATTACH.boundAudiences), boundAudiences: validateOidcAuthAudiencesField.describe(OIDC_AUTH.ATTACH.boundAudiences),
boundClaims: validateOidcBoundClaimsField.describe(OIDC_AUTH.ATTACH.boundClaims), boundClaims: validateOidcBoundClaimsField.describe(OIDC_AUTH.ATTACH.boundClaims),
claimMetadataMapping: validateOidcBoundClaimsField.describe(OIDC_AUTH.ATTACH.claimMetadataMapping).optional(),
boundSubject: z.string().optional().default("").describe(OIDC_AUTH.ATTACH.boundSubject), boundSubject: z.string().optional().default("").describe(OIDC_AUTH.ATTACH.boundSubject),
accessTokenTrustedIps: z accessTokenTrustedIps: z
.object({ .object({
@@ -161,6 +163,7 @@ export const registerIdentityOidcAuthRouter = async (server: FastifyZodProvider)
boundIssuer: identityOidcAuth.boundIssuer, boundIssuer: identityOidcAuth.boundIssuer,
boundAudiences: identityOidcAuth.boundAudiences, boundAudiences: identityOidcAuth.boundAudiences,
boundClaims: identityOidcAuth.boundClaims as Record<string, string>, boundClaims: identityOidcAuth.boundClaims as Record<string, string>,
claimMetadataMapping: identityOidcAuth.claimMetadataMapping as Record<string, string>,
boundSubject: identityOidcAuth.boundSubject as string, boundSubject: identityOidcAuth.boundSubject as string,
accessTokenTTL: identityOidcAuth.accessTokenTTL, accessTokenTTL: identityOidcAuth.accessTokenTTL,
accessTokenMaxTTL: identityOidcAuth.accessTokenMaxTTL, accessTokenMaxTTL: identityOidcAuth.accessTokenMaxTTL,
@@ -200,6 +203,7 @@ export const registerIdentityOidcAuthRouter = async (server: FastifyZodProvider)
boundIssuer: z.string().min(1).describe(OIDC_AUTH.UPDATE.boundIssuer), boundIssuer: z.string().min(1).describe(OIDC_AUTH.UPDATE.boundIssuer),
boundAudiences: validateOidcAuthAudiencesField.describe(OIDC_AUTH.UPDATE.boundAudiences), boundAudiences: validateOidcAuthAudiencesField.describe(OIDC_AUTH.UPDATE.boundAudiences),
boundClaims: validateOidcBoundClaimsField.describe(OIDC_AUTH.UPDATE.boundClaims), boundClaims: validateOidcBoundClaimsField.describe(OIDC_AUTH.UPDATE.boundClaims),
claimMetadataMapping: validateOidcBoundClaimsField.describe(OIDC_AUTH.UPDATE.claimMetadataMapping).optional(),
boundSubject: z.string().optional().default("").describe(OIDC_AUTH.UPDATE.boundSubject), boundSubject: z.string().optional().default("").describe(OIDC_AUTH.UPDATE.boundSubject),
accessTokenTrustedIps: z accessTokenTrustedIps: z
.object({ .object({
@@ -258,6 +262,7 @@ export const registerIdentityOidcAuthRouter = async (server: FastifyZodProvider)
boundIssuer: identityOidcAuth.boundIssuer, boundIssuer: identityOidcAuth.boundIssuer,
boundAudiences: identityOidcAuth.boundAudiences, boundAudiences: identityOidcAuth.boundAudiences,
boundClaims: identityOidcAuth.boundClaims as Record<string, string>, boundClaims: identityOidcAuth.boundClaims as Record<string, string>,
claimMetadataMapping: identityOidcAuth.claimMetadataMapping as Record<string, string>,
boundSubject: identityOidcAuth.boundSubject as string, boundSubject: identityOidcAuth.boundSubject as string,
accessTokenTTL: identityOidcAuth.accessTokenTTL, accessTokenTTL: identityOidcAuth.accessTokenTTL,
accessTokenMaxTTL: identityOidcAuth.accessTokenMaxTTL, accessTokenMaxTTL: identityOidcAuth.accessTokenMaxTTL,
@@ -256,7 +256,8 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => {
scimEnabled: z.boolean().optional(), scimEnabled: z.boolean().optional(),
defaultMembershipRoleSlug: slugSchema({ max: 64, field: "Default Membership Role" }).optional(), defaultMembershipRoleSlug: slugSchema({ max: 64, field: "Default Membership Role" }).optional(),
enforceMfa: z.boolean().optional(), enforceMfa: z.boolean().optional(),
selectedMfaMethod: z.nativeEnum(MfaMethod).optional() selectedMfaMethod: z.nativeEnum(MfaMethod).optional(),
allowSecretSharingOutsideOrganization: z.boolean().optional()
}), }),
response: { response: {
200: z.object({ 200: z.object({
@@ -1,4 +1,3 @@
import ms from "ms";
import { z } from "zod"; import { z } from "zod";
import { import {
@@ -10,6 +9,7 @@ import {
} from "@app/db/schemas"; } from "@app/db/schemas";
import { EventType } from "@app/ee/services/audit-log/audit-log-types"; import { EventType } from "@app/ee/services/audit-log/audit-log-types";
import { PROJECT_USERS } from "@app/lib/api-docs"; import { PROJECT_USERS } from "@app/lib/api-docs";
import { ms } from "@app/lib/ms";
import { readLimit, writeLimit } from "@app/server/config/rateLimiter"; import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type"; import { AuthMode } from "@app/services/auth/auth-type";
@@ -0,0 +1,17 @@
import {
CreateHumanitecSyncSchema,
HumanitecSyncSchema,
UpdateHumanitecSyncSchema
} from "@app/services/secret-sync/humanitec";
import { SecretSync } from "@app/services/secret-sync/secret-sync-enums";
import { registerSyncSecretsEndpoints } from "./secret-sync-endpoints";
export const registerHumanitecSyncRouter = async (server: FastifyZodProvider) =>
registerSyncSecretsEndpoints({
destination: SecretSync.Humanitec,
server,
responseSchema: HumanitecSyncSchema,
createSchema: CreateHumanitecSyncSchema,
updateSchema: UpdateHumanitecSyncSchema
});
@@ -7,6 +7,7 @@ import { registerAzureKeyVaultSyncRouter } from "./azure-key-vault-sync-router";
import { registerDatabricksSyncRouter } from "./databricks-sync-router"; import { registerDatabricksSyncRouter } from "./databricks-sync-router";
import { registerGcpSyncRouter } from "./gcp-sync-router"; import { registerGcpSyncRouter } from "./gcp-sync-router";
import { registerGitHubSyncRouter } from "./github-sync-router"; import { registerGitHubSyncRouter } from "./github-sync-router";
import { registerHumanitecSyncRouter } from "./humanitec-sync-router";
export * from "./secret-sync-router"; export * from "./secret-sync-router";
@@ -17,5 +18,6 @@ export const SECRET_SYNC_REGISTER_ROUTER_MAP: Record<SecretSync, (server: Fastif
[SecretSync.GCPSecretManager]: registerGcpSyncRouter, [SecretSync.GCPSecretManager]: registerGcpSyncRouter,
[SecretSync.AzureKeyVault]: registerAzureKeyVaultSyncRouter, [SecretSync.AzureKeyVault]: registerAzureKeyVaultSyncRouter,
[SecretSync.AzureAppConfiguration]: registerAzureAppConfigurationSyncRouter, [SecretSync.AzureAppConfiguration]: registerAzureAppConfigurationSyncRouter,
[SecretSync.Databricks]: registerDatabricksSyncRouter [SecretSync.Databricks]: registerDatabricksSyncRouter,
[SecretSync.Humanitec]: registerHumanitecSyncRouter
}; };
@@ -21,6 +21,7 @@ import { AzureKeyVaultSyncListItemSchema, AzureKeyVaultSyncSchema } from "@app/s
import { DatabricksSyncListItemSchema, DatabricksSyncSchema } from "@app/services/secret-sync/databricks"; import { DatabricksSyncListItemSchema, DatabricksSyncSchema } from "@app/services/secret-sync/databricks";
import { GcpSyncListItemSchema, GcpSyncSchema } from "@app/services/secret-sync/gcp"; import { GcpSyncListItemSchema, GcpSyncSchema } from "@app/services/secret-sync/gcp";
import { GitHubSyncListItemSchema, GitHubSyncSchema } from "@app/services/secret-sync/github"; import { GitHubSyncListItemSchema, GitHubSyncSchema } from "@app/services/secret-sync/github";
import { HumanitecSyncListItemSchema, HumanitecSyncSchema } from "@app/services/secret-sync/humanitec";
const SecretSyncSchema = z.discriminatedUnion("destination", [ const SecretSyncSchema = z.discriminatedUnion("destination", [
AwsParameterStoreSyncSchema, AwsParameterStoreSyncSchema,
@@ -29,7 +30,8 @@ const SecretSyncSchema = z.discriminatedUnion("destination", [
GcpSyncSchema, GcpSyncSchema,
AzureKeyVaultSyncSchema, AzureKeyVaultSyncSchema,
AzureAppConfigurationSyncSchema, AzureAppConfigurationSyncSchema,
DatabricksSyncSchema DatabricksSyncSchema,
HumanitecSyncSchema
]); ]);
const SecretSyncOptionsSchema = z.discriminatedUnion("destination", [ const SecretSyncOptionsSchema = z.discriminatedUnion("destination", [
@@ -39,7 +41,8 @@ const SecretSyncOptionsSchema = z.discriminatedUnion("destination", [
GcpSyncListItemSchema, GcpSyncListItemSchema,
AzureKeyVaultSyncListItemSchema, AzureKeyVaultSyncListItemSchema,
AzureAppConfigurationSyncListItemSchema, AzureAppConfigurationSyncListItemSchema,
DatabricksSyncListItemSchema DatabricksSyncListItemSchema,
HumanitecSyncListItemSchema
]); ]);
export const registerSecretSyncRouter = async (server: FastifyZodProvider) => { export const registerSecretSyncRouter = async (server: FastifyZodProvider) => {
@@ -1,4 +1,3 @@
import ms from "ms";
import { z } from "zod"; import { z } from "zod";
import { import {
@@ -8,6 +7,7 @@ import {
ProjectUserMembershipRolesSchema ProjectUserMembershipRolesSchema
} from "@app/db/schemas"; } from "@app/db/schemas";
import { PROJECTS } from "@app/lib/api-docs"; import { PROJECTS } from "@app/lib/api-docs";
import { ms } from "@app/lib/ms";
import { readLimit, writeLimit } from "@app/server/config/rateLimiter"; import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type"; import { AuthMode } from "@app/services/auth/auth-type";
@@ -1,4 +1,3 @@
import ms from "ms";
import { z } from "zod"; import { z } from "zod";
import { import {
@@ -9,6 +8,7 @@ import {
} from "@app/db/schemas"; } from "@app/db/schemas";
import { ORGANIZATIONS, PROJECT_IDENTITIES } from "@app/lib/api-docs"; import { ORGANIZATIONS, PROJECT_IDENTITIES } from "@app/lib/api-docs";
import { BadRequestError } from "@app/lib/errors"; import { BadRequestError } from "@app/lib/errors";
import { ms } from "@app/lib/ms";
import { OrderByDirection } from "@app/lib/types"; import { OrderByDirection } from "@app/lib/types";
import { readLimit, writeLimit } from "@app/server/config/rateLimiter"; import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
@@ -4,7 +4,8 @@ export enum AppConnection {
Databricks = "databricks", Databricks = "databricks",
GCP = "gcp", GCP = "gcp",
AzureKeyVault = "azure-key-vault", AzureKeyVault = "azure-key-vault",
AzureAppConfiguration = "azure-app-configuration" AzureAppConfiguration = "azure-app-configuration",
Humanitec = "humanitec"
} }
export enum AWSRegion { export enum AWSRegion {
@@ -35,6 +35,11 @@ import {
getAzureKeyVaultConnectionListItem, getAzureKeyVaultConnectionListItem,
validateAzureKeyVaultConnectionCredentials validateAzureKeyVaultConnectionCredentials
} from "./azure-key-vault"; } from "./azure-key-vault";
import {
getHumanitecConnectionListItem,
HumanitecConnectionMethod,
validateHumanitecConnectionCredentials
} from "./humanitec";
export const listAppConnectionOptions = () => { export const listAppConnectionOptions = () => {
return [ return [
@@ -43,7 +48,8 @@ export const listAppConnectionOptions = () => {
getGcpConnectionListItem(), getGcpConnectionListItem(),
getAzureKeyVaultConnectionListItem(), getAzureKeyVaultConnectionListItem(),
getAzureAppConfigurationConnectionListItem(), getAzureAppConfigurationConnectionListItem(),
getDatabricksConnectionListItem() getDatabricksConnectionListItem(),
getHumanitecConnectionListItem()
].sort((a, b) => a.name.localeCompare(b.name)); ].sort((a, b) => a.name.localeCompare(b.name));
}; };
@@ -106,6 +112,8 @@ export const validateAppConnectionCredentials = async (
return validateAzureKeyVaultConnectionCredentials(appConnection); return validateAzureKeyVaultConnectionCredentials(appConnection);
case AppConnection.AzureAppConfiguration: case AppConnection.AzureAppConfiguration:
return validateAzureAppConfigurationConnectionCredentials(appConnection); return validateAzureAppConfigurationConnectionCredentials(appConnection);
case AppConnection.Humanitec:
return validateHumanitecConnectionCredentials(appConnection);
default: default:
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
throw new Error(`Unhandled App Connection ${app}`); throw new Error(`Unhandled App Connection ${app}`);
@@ -128,6 +136,8 @@ export const getAppConnectionMethodName = (method: TAppConnection["method"]) =>
return "Service Account Impersonation"; return "Service Account Impersonation";
case DatabricksConnectionMethod.ServicePrincipal: case DatabricksConnectionMethod.ServicePrincipal:
return "Service Principal"; return "Service Principal";
case HumanitecConnectionMethod.API_TOKEN:
return "API Token";
default: default:
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
throw new Error(`Unhandled App Connection Method: ${method}`); throw new Error(`Unhandled App Connection Method: ${method}`);
@@ -6,5 +6,6 @@ export const APP_CONNECTION_NAME_MAP: Record<AppConnection, string> = {
[AppConnection.GCP]: "GCP", [AppConnection.GCP]: "GCP",
[AppConnection.AzureKeyVault]: "Azure Key Vault", [AppConnection.AzureKeyVault]: "Azure Key Vault",
[AppConnection.AzureAppConfiguration]: "Azure App Configuration", [AppConnection.AzureAppConfiguration]: "Azure App Configuration",
[AppConnection.Databricks]: "Databricks" [AppConnection.Databricks]: "Databricks",
[AppConnection.Humanitec]: "Humanitec"
}; };
@@ -35,6 +35,8 @@ import { ValidateGcpConnectionCredentialsSchema } from "./gcp";
import { gcpConnectionService } from "./gcp/gcp-connection-service"; import { gcpConnectionService } from "./gcp/gcp-connection-service";
import { ValidateGitHubConnectionCredentialsSchema } from "./github"; import { ValidateGitHubConnectionCredentialsSchema } from "./github";
import { githubConnectionService } from "./github/github-connection-service"; import { githubConnectionService } from "./github/github-connection-service";
import { ValidateHumanitecConnectionCredentialsSchema } from "./humanitec";
import { humanitecConnectionService } from "./humanitec/humanitec-connection-service";
export type TAppConnectionServiceFactoryDep = { export type TAppConnectionServiceFactoryDep = {
appConnectionDAL: TAppConnectionDALFactory; appConnectionDAL: TAppConnectionDALFactory;
@@ -50,7 +52,8 @@ const VALIDATE_APP_CONNECTION_CREDENTIALS_MAP: Record<AppConnection, TValidateAp
[AppConnection.GCP]: ValidateGcpConnectionCredentialsSchema, [AppConnection.GCP]: ValidateGcpConnectionCredentialsSchema,
[AppConnection.AzureKeyVault]: ValidateAzureKeyVaultConnectionCredentialsSchema, [AppConnection.AzureKeyVault]: ValidateAzureKeyVaultConnectionCredentialsSchema,
[AppConnection.AzureAppConfiguration]: ValidateAzureAppConfigurationConnectionCredentialsSchema, [AppConnection.AzureAppConfiguration]: ValidateAzureAppConfigurationConnectionCredentialsSchema,
[AppConnection.Databricks]: ValidateDatabricksConnectionCredentialsSchema [AppConnection.Databricks]: ValidateDatabricksConnectionCredentialsSchema,
[AppConnection.Humanitec]: ValidateHumanitecConnectionCredentialsSchema
}; };
export const appConnectionServiceFactory = ({ export const appConnectionServiceFactory = ({
@@ -371,6 +374,7 @@ export const appConnectionServiceFactory = ({
github: githubConnectionService(connectAppConnectionById), github: githubConnectionService(connectAppConnectionById),
gcp: gcpConnectionService(connectAppConnectionById), gcp: gcpConnectionService(connectAppConnectionById),
databricks: databricksConnectionService(connectAppConnectionById, appConnectionDAL, kmsService), databricks: databricksConnectionService(connectAppConnectionById, appConnectionDAL, kmsService),
aws: awsConnectionService(connectAppConnectionById) aws: awsConnectionService(connectAppConnectionById),
humanitec: humanitecConnectionService(connectAppConnectionById)
}; };
}; };
@@ -32,6 +32,12 @@ import {
TValidateAzureKeyVaultConnectionCredentials TValidateAzureKeyVaultConnectionCredentials
} from "./azure-key-vault"; } from "./azure-key-vault";
import { TGcpConnection, TGcpConnectionConfig, TGcpConnectionInput, TValidateGcpConnectionCredentials } from "./gcp"; import { TGcpConnection, TGcpConnectionConfig, TGcpConnectionInput, TValidateGcpConnectionCredentials } from "./gcp";
import {
THumanitecConnection,
THumanitecConnectionConfig,
THumanitecConnectionInput,
TValidateHumanitecConnectionCredentials
} from "./humanitec";
export type TAppConnection = { id: string } & ( export type TAppConnection = { id: string } & (
| TAwsConnection | TAwsConnection
@@ -40,6 +46,7 @@ export type TAppConnection = { id: string } & (
| TAzureKeyVaultConnection | TAzureKeyVaultConnection
| TAzureAppConfigurationConnection | TAzureAppConfigurationConnection
| TDatabricksConnection | TDatabricksConnection
| THumanitecConnection
); );
export type TAppConnectionInput = { id: string } & ( export type TAppConnectionInput = { id: string } & (
@@ -49,6 +56,7 @@ export type TAppConnectionInput = { id: string } & (
| TAzureKeyVaultConnectionInput | TAzureKeyVaultConnectionInput
| TAzureAppConfigurationConnectionInput | TAzureAppConfigurationConnectionInput
| TDatabricksConnectionInput | TDatabricksConnectionInput
| THumanitecConnectionInput
); );
export type TCreateAppConnectionDTO = Pick< export type TCreateAppConnectionDTO = Pick<
@@ -66,7 +74,8 @@ export type TAppConnectionConfig =
| TGcpConnectionConfig | TGcpConnectionConfig
| TAzureKeyVaultConnectionConfig | TAzureKeyVaultConnectionConfig
| TAzureAppConfigurationConnectionConfig | TAzureAppConfigurationConnectionConfig
| TDatabricksConnectionConfig; | TDatabricksConnectionConfig
| THumanitecConnectionConfig;
export type TValidateAppConnectionCredentials = export type TValidateAppConnectionCredentials =
| TValidateAwsConnectionCredentials | TValidateAwsConnectionCredentials
@@ -74,7 +83,8 @@ export type TValidateAppConnectionCredentials =
| TValidateGcpConnectionCredentials | TValidateGcpConnectionCredentials
| TValidateAzureKeyVaultConnectionCredentials | TValidateAzureKeyVaultConnectionCredentials
| TValidateAzureAppConfigurationConnectionCredentials | TValidateAzureAppConfigurationConnectionCredentials
| TValidateDatabricksConnectionCredentials; | TValidateDatabricksConnectionCredentials
| TValidateHumanitecConnectionCredentials;
export type TListAwsConnectionKmsKeys = { export type TListAwsConnectionKmsKeys = {
connectionId: string; connectionId: string;
@@ -0,0 +1,3 @@
export enum HumanitecConnectionMethod {
API_TOKEN = "api-token"
}
@@ -0,0 +1,95 @@
import { AxiosError, AxiosResponse } from "axios";
import { request } from "@app/lib/config/request";
import { BadRequestError, InternalServerError } from "@app/lib/errors";
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
import { IntegrationUrls } from "@app/services/integration-auth/integration-list";
import { HumanitecConnectionMethod } from "./humanitec-connection-enums";
import {
HumanitecApp,
HumanitecOrg,
HumanitecOrgWithApps,
THumanitecConnection,
THumanitecConnectionConfig
} from "./humanitec-connection-types";
export const getHumanitecConnectionListItem = () => {
return {
name: "Humanitec" as const,
app: AppConnection.Humanitec as const,
methods: Object.values(HumanitecConnectionMethod) as [HumanitecConnectionMethod.API_TOKEN]
};
};
export const validateHumanitecConnectionCredentials = async (config: THumanitecConnectionConfig) => {
const { credentials: inputCredentials } = config;
let response: AxiosResponse<HumanitecOrg[]> | null = null;
try {
response = await request.get<HumanitecOrg[]>(`${IntegrationUrls.HUMANITEC_API_URL}/orgs`, {
headers: {
Authorization: `Bearer ${inputCredentials.apiToken}`
}
});
} catch (error: unknown) {
if (error instanceof AxiosError) {
throw new BadRequestError({
message: `Failed to validate credentials: ${error.message || "Unknown error"}`
});
}
throw new BadRequestError({
message: "Unable to validate connection - verify credentials"
});
}
if (!response?.data) {
throw new InternalServerError({
message: "Failed to get organizations: Response was empty"
});
}
return inputCredentials;
};
export const listOrganizations = async (appConnection: THumanitecConnection): Promise<HumanitecOrgWithApps[]> => {
const {
credentials: { apiToken }
} = appConnection;
const response = await request.get<HumanitecOrg[]>(`${IntegrationUrls.HUMANITEC_API_URL}/orgs`, {
headers: {
Authorization: `Bearer ${apiToken}`
}
});
if (!response.data) {
throw new InternalServerError({
message: "Failed to get organizations: Response was empty"
});
}
const orgs = response.data;
const orgsWithApps: HumanitecOrgWithApps[] = [];
for (const org of orgs) {
// eslint-disable-next-line no-await-in-loop
const appsResponse = await request.get<HumanitecApp[]>(`${IntegrationUrls.HUMANITEC_API_URL}/orgs/${org.id}/apps`, {
headers: {
Authorization: `Bearer ${apiToken}`
}
});
if (appsResponse.data) {
const apps = appsResponse.data;
orgsWithApps.push({
...org,
apps: apps.map((app) => ({
name: app.name,
id: app.id,
envs: app.envs
}))
});
}
}
return orgsWithApps;
};
@@ -0,0 +1,58 @@
import z from "zod";
import { AppConnections } from "@app/lib/api-docs";
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
import {
BaseAppConnectionSchema,
GenericCreateAppConnectionFieldsSchema,
GenericUpdateAppConnectionFieldsSchema
} from "@app/services/app-connection/app-connection-schemas";
import { HumanitecConnectionMethod } from "./humanitec-connection-enums";
export const HumanitecConnectionAccessTokenCredentialsSchema = z.object({
apiToken: z.string().trim().min(1, "API Token required")
});
const BaseHumanitecConnectionSchema = BaseAppConnectionSchema.extend({ app: z.literal(AppConnection.Humanitec) });
export const HumanitecConnectionSchema = BaseHumanitecConnectionSchema.extend({
method: z.literal(HumanitecConnectionMethod.API_TOKEN),
credentials: HumanitecConnectionAccessTokenCredentialsSchema
});
export const SanitizedHumanitecConnectionSchema = z.discriminatedUnion("method", [
BaseHumanitecConnectionSchema.extend({
method: z.literal(HumanitecConnectionMethod.API_TOKEN),
credentials: HumanitecConnectionAccessTokenCredentialsSchema.pick({})
})
]);
export const ValidateHumanitecConnectionCredentialsSchema = z.discriminatedUnion("method", [
z.object({
method: z
.literal(HumanitecConnectionMethod.API_TOKEN)
.describe(AppConnections?.CREATE(AppConnection.Humanitec).method),
credentials: HumanitecConnectionAccessTokenCredentialsSchema.describe(
AppConnections.CREATE(AppConnection.Humanitec).credentials
)
})
]);
export const CreateHumanitecConnectionSchema = ValidateHumanitecConnectionCredentialsSchema.and(
GenericCreateAppConnectionFieldsSchema(AppConnection.Humanitec)
);
export const UpdateHumanitecConnectionSchema = z
.object({
credentials: HumanitecConnectionAccessTokenCredentialsSchema.optional().describe(
AppConnections.UPDATE(AppConnection.Humanitec).credentials
)
})
.and(GenericUpdateAppConnectionFieldsSchema(AppConnection.Humanitec));
export const HumanitecConnectionListItemSchema = z.object({
name: z.literal("Humanitec"),
app: z.literal(AppConnection.Humanitec),
methods: z.nativeEnum(HumanitecConnectionMethod).array()
});
@@ -0,0 +1,29 @@
import { logger } from "@app/lib/logger";
import { OrgServiceActor } from "@app/lib/types";
import { AppConnection } from "../app-connection-enums";
import { listOrganizations as getHumanitecOrganizations } from "./humanitec-connection-fns";
import { THumanitecConnection } from "./humanitec-connection-types";
type TGetAppConnectionFunc = (
app: AppConnection,
connectionId: string,
actor: OrgServiceActor
) => Promise<THumanitecConnection>;
export const humanitecConnectionService = (getAppConnection: TGetAppConnectionFunc) => {
const listOrganizations = async (connectionId: string, actor: OrgServiceActor) => {
const appConnection = await getAppConnection(AppConnection.Humanitec, connectionId, actor);
try {
const organizations = await getHumanitecOrganizations(appConnection);
return organizations;
} catch (error) {
logger.error(error, "Failed to establish connection with Humanitec");
return [];
}
};
return {
listOrganizations
};
};
@@ -0,0 +1,40 @@
import z from "zod";
import { DiscriminativePick } from "@app/lib/types";
import { AppConnection } from "../app-connection-enums";
import {
CreateHumanitecConnectionSchema,
HumanitecConnectionSchema,
ValidateHumanitecConnectionCredentialsSchema
} from "./humanitec-connection-schemas";
export type THumanitecConnection = z.infer<typeof HumanitecConnectionSchema>;
export type THumanitecConnectionInput = z.infer<typeof CreateHumanitecConnectionSchema> & {
app: AppConnection.Humanitec;
};
export type TValidateHumanitecConnectionCredentials = typeof ValidateHumanitecConnectionCredentialsSchema;
export type THumanitecConnectionConfig = DiscriminativePick<
THumanitecConnectionInput,
"method" | "app" | "credentials"
> & {
orgId: string;
};
export type HumanitecOrg = {
id: string;
name: string;
};
export type HumanitecApp = {
name: string;
id: string;
envs: { name: string; id: string }[];
};
export type HumanitecOrgWithApps = HumanitecOrg & {
apps: HumanitecApp[];
};
@@ -0,0 +1,4 @@
export * from "./humanitec-connection-enums";
export * from "./humanitec-connection-fns";
export * from "./humanitec-connection-schemas";
export * from "./humanitec-connection-types";
@@ -2,7 +2,6 @@
import { ForbiddenError } from "@casl/ability"; import { ForbiddenError } from "@casl/ability";
import * as x509 from "@peculiar/x509"; import * as x509 from "@peculiar/x509";
import crypto, { KeyObject } from "crypto"; import crypto, { KeyObject } from "crypto";
import ms from "ms";
import { z } from "zod"; import { z } from "zod";
import { ActionProjectType, ProjectType, TCertificateAuthorities, TCertificateTemplates } from "@app/db/schemas"; 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 { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
import { getConfig } from "@app/lib/config/env"; import { getConfig } from "@app/lib/config/env";
import { BadRequestError, NotFoundError } from "@app/lib/errors"; import { BadRequestError, NotFoundError } from "@app/lib/errors";
import { ms } from "@app/lib/ms";
import { TCertificateBodyDALFactory } from "@app/services/certificate/certificate-body-dal"; import { TCertificateBodyDALFactory } from "@app/services/certificate/certificate-body-dal";
import { TCertificateDALFactory } from "@app/services/certificate/certificate-dal"; import { TCertificateDALFactory } from "@app/services/certificate/certificate-dal";
import { TKmsServiceFactory } from "@app/services/kms/kms-service"; import { TKmsServiceFactory } from "@app/services/kms/kms-service";
@@ -1,7 +1,6 @@
import ms from "ms";
import { TCertificateTemplates } from "@app/db/schemas"; import { TCertificateTemplates } from "@app/db/schemas";
import { BadRequestError } from "@app/lib/errors"; import { BadRequestError } from "@app/lib/errors";
import { ms } from "@app/lib/ms";
export const validateCertificateDetailsAgainstTemplate = ( export const validateCertificateDetailsAgainstTemplate = (
cert: { cert: {
@@ -1,5 +1,4 @@
import { ForbiddenError } from "@casl/ability"; import { ForbiddenError } from "@casl/ability";
import ms from "ms";
import { ActionProjectType, ProjectMembershipRole, SecretKeyEncoding, TGroups } from "@app/db/schemas"; import { ActionProjectType, ProjectMembershipRole, SecretKeyEncoding, TGroups } from "@app/db/schemas";
import { import {
@@ -12,6 +11,7 @@ import { decryptAsymmetric, encryptAsymmetric } from "@app/lib/crypto";
import { infisicalSymmetricDecrypt } from "@app/lib/crypto/encryption"; import { infisicalSymmetricDecrypt } from "@app/lib/crypto/encryption";
import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors"; import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors";
import { groupBy } from "@app/lib/fn"; import { groupBy } from "@app/lib/fn";
import { ms } from "@app/lib/ms";
import { isUuidV4 } from "@app/lib/validator"; import { isUuidV4 } from "@app/lib/validator";
import { TGroupDALFactory } from "../../ee/services/group/group-dal"; import { TGroupDALFactory } from "../../ee/services/group/group-dal";
@@ -7,4 +7,9 @@ export type TIdentityAccessTokenJwtPayload = {
clientSecretId: string; clientSecretId: string;
identityAccessTokenId: string; identityAccessTokenId: string;
authTokenType: string; authTokenType: string;
identityAuth: {
oidc?: {
claims: Record<string, string>;
};
};
}; };
@@ -14,6 +14,7 @@ import { TPermissionServiceFactory } from "@app/ee/services/permission/permissio
import { getConfig } from "@app/lib/config/env"; import { getConfig } from "@app/lib/config/env";
import { BadRequestError, ForbiddenRequestError, NotFoundError, UnauthorizedError } from "@app/lib/errors"; import { BadRequestError, ForbiddenRequestError, NotFoundError, UnauthorizedError } from "@app/lib/errors";
import { extractIPDetails, isValidIpOrCidr } from "@app/lib/ip"; import { extractIPDetails, isValidIpOrCidr } from "@app/lib/ip";
import { getStringValueByDot } from "@app/lib/template/dot-access";
import { ActorType, AuthTokenType } from "../auth/auth-type"; import { ActorType, AuthTokenType } from "../auth/auth-type";
import { TIdentityOrgDALFactory } from "../identity/identity-org-dal"; import { TIdentityOrgDALFactory } from "../identity/identity-org-dal";
@@ -180,8 +181,9 @@ export const identityJwtAuthServiceFactory = ({
if (identityJwtAuth.boundClaims) { if (identityJwtAuth.boundClaims) {
Object.keys(identityJwtAuth.boundClaims).forEach((claimKey) => { Object.keys(identityJwtAuth.boundClaims).forEach((claimKey) => {
const claimValue = (identityJwtAuth.boundClaims as Record<string, string>)[claimKey]; const claimValue = (identityJwtAuth.boundClaims as Record<string, string>)[claimKey];
const value = getStringValueByDot(tokenData, claimKey) || "";
if (!tokenData[claimKey]) { if (!value) {
throw new UnauthorizedError({ throw new UnauthorizedError({
message: `Access denied: token has no ${claimKey} field` message: `Access denied: token has no ${claimKey} field`
}); });
@@ -15,6 +15,7 @@ import { TPermissionServiceFactory } from "@app/ee/services/permission/permissio
import { getConfig } from "@app/lib/config/env"; import { getConfig } from "@app/lib/config/env";
import { BadRequestError, ForbiddenRequestError, NotFoundError, UnauthorizedError } from "@app/lib/errors"; import { BadRequestError, ForbiddenRequestError, NotFoundError, UnauthorizedError } from "@app/lib/errors";
import { extractIPDetails, isValidIpOrCidr } from "@app/lib/ip"; import { extractIPDetails, isValidIpOrCidr } from "@app/lib/ip";
import { getStringValueByDot } from "@app/lib/template/dot-access";
import { ActorType, AuthTokenType } from "../auth/auth-type"; import { ActorType, AuthTokenType } from "../auth/auth-type";
import { TIdentityOrgDALFactory } from "../identity/identity-org-dal"; import { TIdentityOrgDALFactory } from "../identity/identity-org-dal";
@@ -80,7 +81,7 @@ export const identityOidcAuthServiceFactory = ({
const { data: discoveryDoc } = await axios.get<{ jwks_uri: string }>( const { data: discoveryDoc } = await axios.get<{ jwks_uri: string }>(
`${identityOidcAuth.oidcDiscoveryUrl}/.well-known/openid-configuration`, `${identityOidcAuth.oidcDiscoveryUrl}/.well-known/openid-configuration`,
{ {
httpsAgent: requestAgent httpsAgent: identityOidcAuth.oidcDiscoveryUrl.includes("https") ? requestAgent : undefined
} }
); );
const jwksUri = discoveryDoc.jwks_uri; const jwksUri = discoveryDoc.jwks_uri;
@@ -94,7 +95,7 @@ export const identityOidcAuthServiceFactory = ({
const client = new JwksClient({ const client = new JwksClient({
jwksUri, jwksUri,
requestAgent requestAgent: identityOidcAuth.oidcDiscoveryUrl.includes("https") ? requestAgent : undefined
}); });
const { kid } = decodedToken.header; const { kid } = decodedToken.header;
@@ -111,7 +112,6 @@ export const identityOidcAuthServiceFactory = ({
message: `Access denied: ${error.message}` message: `Access denied: ${error.message}`
}); });
} }
throw error; throw error;
} }
@@ -138,10 +138,16 @@ export const identityOidcAuthServiceFactory = ({
if (identityOidcAuth.boundClaims) { if (identityOidcAuth.boundClaims) {
Object.keys(identityOidcAuth.boundClaims).forEach((claimKey) => { Object.keys(identityOidcAuth.boundClaims).forEach((claimKey) => {
const claimValue = (identityOidcAuth.boundClaims as Record<string, string>)[claimKey]; const claimValue = (identityOidcAuth.boundClaims as Record<string, string>)[claimKey];
const value = getStringValueByDot(tokenData, claimKey) || "";
if (!value) {
throw new UnauthorizedError({
message: `Access denied: token has no ${claimKey} field`
});
}
// handle both single and multi-valued claims // handle both single and multi-valued claims
if ( if (!claimValue.split(", ").some((claimEntry) => doesFieldValueMatchOidcPolicy(value, claimEntry))) {
!claimValue.split(", ").some((claimEntry) => doesFieldValueMatchOidcPolicy(tokenData[claimKey], claimEntry))
) {
throw new UnauthorizedError({ throw new UnauthorizedError({
message: "Access denied: OIDC claim not allowed." message: "Access denied: OIDC claim not allowed."
}); });
@@ -149,6 +155,20 @@ export const identityOidcAuthServiceFactory = ({
}); });
} }
const filteredClaims: Record<string, string> = {};
if (identityOidcAuth.claimMetadataMapping) {
Object.keys(identityOidcAuth.claimMetadataMapping).forEach((permissionKey) => {
const claimKey = (identityOidcAuth.claimMetadataMapping as Record<string, string>)[permissionKey];
const value = getStringValueByDot(tokenData, claimKey) || "";
if (!value) {
throw new UnauthorizedError({
message: `Access denied: token has no ${claimKey} field`
});
}
filteredClaims[permissionKey] = value;
});
}
const identityAccessToken = await identityOidcAuthDAL.transaction(async (tx) => { const identityAccessToken = await identityOidcAuthDAL.transaction(async (tx) => {
const newToken = await identityAccessTokenDAL.create( const newToken = await identityAccessTokenDAL.create(
{ {
@@ -170,7 +190,12 @@ export const identityOidcAuthServiceFactory = ({
{ {
identityId: identityOidcAuth.identityId, identityId: identityOidcAuth.identityId,
identityAccessTokenId: identityAccessToken.id, identityAccessTokenId: identityAccessToken.id,
authTokenType: AuthTokenType.IDENTITY_ACCESS_TOKEN authTokenType: AuthTokenType.IDENTITY_ACCESS_TOKEN,
identityAuth: {
oidc: {
claims: filteredClaims
}
}
} as TIdentityAccessTokenJwtPayload, } as TIdentityAccessTokenJwtPayload,
appCfg.AUTH_SECRET, appCfg.AUTH_SECRET,
// akhilmhdh: for non-expiry tokens you should not even set the value, including undefined. Even for undefined jsonwebtoken throws error // akhilmhdh: for non-expiry tokens you should not even set the value, including undefined. Even for undefined jsonwebtoken throws error
@@ -191,6 +216,7 @@ export const identityOidcAuthServiceFactory = ({
boundIssuer, boundIssuer,
boundAudiences, boundAudiences,
boundClaims, boundClaims,
claimMetadataMapping,
boundSubject, boundSubject,
accessTokenTTL, accessTokenTTL,
accessTokenMaxTTL, accessTokenMaxTTL,
@@ -257,6 +283,7 @@ export const identityOidcAuthServiceFactory = ({
boundIssuer, boundIssuer,
boundAudiences, boundAudiences,
boundClaims, boundClaims,
claimMetadataMapping,
boundSubject, boundSubject,
accessTokenMaxTTL, accessTokenMaxTTL,
accessTokenTTL, accessTokenTTL,
@@ -277,6 +304,7 @@ export const identityOidcAuthServiceFactory = ({
boundIssuer, boundIssuer,
boundAudiences, boundAudiences,
boundClaims, boundClaims,
claimMetadataMapping,
boundSubject, boundSubject,
accessTokenTTL, accessTokenTTL,
accessTokenMaxTTL, accessTokenMaxTTL,
@@ -338,6 +366,7 @@ export const identityOidcAuthServiceFactory = ({
boundIssuer, boundIssuer,
boundAudiences, boundAudiences,
boundClaims, boundClaims,
claimMetadataMapping,
boundSubject, boundSubject,
accessTokenMaxTTL, accessTokenMaxTTL,
accessTokenTTL, accessTokenTTL,
@@ -7,6 +7,7 @@ export type TAttachOidcAuthDTO = {
boundIssuer: string; boundIssuer: string;
boundAudiences: string; boundAudiences: string;
boundClaims: Record<string, string>; boundClaims: Record<string, string>;
claimMetadataMapping?: Record<string, string>;
boundSubject: string; boundSubject: string;
accessTokenTTL: number; accessTokenTTL: number;
accessTokenMaxTTL: number; accessTokenMaxTTL: number;
@@ -21,6 +22,7 @@ export type TUpdateOidcAuthDTO = {
boundIssuer?: string; boundIssuer?: string;
boundAudiences?: string; boundAudiences?: string;
boundClaims?: Record<string, string>; boundClaims?: Record<string, string>;
claimMetadataMapping?: Record<string, string>;
boundSubject?: string; boundSubject?: string;
accessTokenTTL?: number; accessTokenTTL?: number;
accessTokenMaxTTL?: number; accessTokenMaxTTL?: number;
@@ -1,5 +1,4 @@
import { ForbiddenError, subject } from "@casl/ability"; import { ForbiddenError, subject } from "@casl/ability";
import ms from "ms";
import { ActionProjectType, ProjectMembershipRole } from "@app/db/schemas"; import { ActionProjectType, ProjectMembershipRole } from "@app/db/schemas";
import { import {
@@ -10,6 +9,7 @@ import { TPermissionServiceFactory } from "@app/ee/services/permission/permissio
import { ProjectPermissionIdentityActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission"; import { ProjectPermissionIdentityActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors"; import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors";
import { groupBy } from "@app/lib/fn"; import { groupBy } from "@app/lib/fn";
import { ms } from "@app/lib/ms";
import { TIdentityOrgDALFactory } from "../identity/identity-org-dal"; import { TIdentityOrgDALFactory } from "../identity/identity-org-dal";
import { TProjectDALFactory } from "../project/project-dal"; import { TProjectDALFactory } from "../project/project-dal";
@@ -1,7 +1,7 @@
import { TDbClient } from "@app/db"; import { TDbClient } from "@app/db";
import { TableName, TIdentities } from "@app/db/schemas"; import { TableName, TIdentities } from "@app/db/schemas";
import { ormify, selectAllTableCols } from "@app/lib/knex";
import { DatabaseError } from "@app/lib/errors"; import { DatabaseError } from "@app/lib/errors";
import { ormify, selectAllTableCols } from "@app/lib/knex";
export type TIdentityDALFactory = ReturnType<typeof identityDALFactory>; export type TIdentityDALFactory = ReturnType<typeof identityDALFactory>;
@@ -93,6 +93,7 @@ export enum IntegrationUrls {
NORTHFLANK_API_URL = "https://api.northflank.com", NORTHFLANK_API_URL = "https://api.northflank.com",
HASURA_CLOUD_API_URL = "https://data.pro.hasura.io/v1/graphql", HASURA_CLOUD_API_URL = "https://data.pro.hasura.io/v1/graphql",
AZURE_DEVOPS_API_URL = "https://dev.azure.com", AZURE_DEVOPS_API_URL = "https://dev.azure.com",
HUMANITEC_API_URL = "https://api.humanitec.io",
GCP_SECRET_MANAGER_SERVICE_NAME = "secretmanager.googleapis.com", GCP_SECRET_MANAGER_SERVICE_NAME = "secretmanager.googleapis.com",
GCP_SECRET_MANAGER_URL = `https://${GCP_SECRET_MANAGER_SERVICE_NAME}`, GCP_SECRET_MANAGER_URL = `https://${GCP_SECRET_MANAGER_SERVICE_NAME}`,
+19 -2
View File
@@ -22,6 +22,7 @@ import { TOidcConfigDALFactory } from "@app/ee/services/oidc/oidc-config-dal";
import { import {
OrgPermissionActions, OrgPermissionActions,
OrgPermissionGroupActions, OrgPermissionGroupActions,
OrgPermissionSecretShareAction,
OrgPermissionSubjects OrgPermissionSubjects
} from "@app/ee/services/permission/org-permission"; } from "@app/ee/services/permission/org-permission";
import { import {
@@ -334,12 +335,27 @@ export const orgServiceFactory = ({
actorOrgId, actorOrgId,
actorAuthMethod, actorAuthMethod,
orgId, orgId,
data: { name, slug, authEnforced, scimEnabled, defaultMembershipRoleSlug, enforceMfa, selectedMfaMethod } data: {
name,
slug,
authEnforced,
scimEnabled,
defaultMembershipRoleSlug,
enforceMfa,
selectedMfaMethod,
allowSecretSharingOutsideOrganization
}
}: TUpdateOrgDTO) => { }: TUpdateOrgDTO) => {
const appCfg = getConfig(); const appCfg = getConfig();
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId); const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Settings); 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 plan = await licenseService.getPlan(orgId);
const currentOrg = await orgDAL.findOrgById(actorOrgId); const currentOrg = await orgDAL.findOrgById(actorOrgId);
@@ -406,7 +422,8 @@ export const orgServiceFactory = ({
scimEnabled, scimEnabled,
defaultMembershipRole, defaultMembershipRole,
enforceMfa, enforceMfa,
selectedMfaMethod selectedMfaMethod,
allowSecretSharingOutsideOrganization
}); });
if (!org) throw new NotFoundError({ message: `Organization with ID '${orgId}' not found` }); if (!org) throw new NotFoundError({ message: `Organization with ID '${orgId}' not found` });
return org; return org;
+1
View File
@@ -72,6 +72,7 @@ export type TUpdateOrgDTO = {
defaultMembershipRoleSlug: string; defaultMembershipRoleSlug: string;
enforceMfa: boolean; enforceMfa: boolean;
selectedMfaMethod: MfaMethod; selectedMfaMethod: MfaMethod;
allowSecretSharingOutsideOrganization: boolean;
}>; }>;
} & TOrgPermission; } & TOrgPermission;
@@ -1,6 +1,5 @@
/* eslint-disable no-await-in-loop */ /* eslint-disable no-await-in-loop */
import { ForbiddenError } from "@casl/ability"; import { ForbiddenError } from "@casl/ability";
import ms from "ms";
import { ActionProjectType, ProjectMembershipRole, ProjectVersion, TableName } from "@app/db/schemas"; import { ActionProjectType, ProjectMembershipRole, ProjectVersion, TableName } from "@app/db/schemas";
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service"; import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
@@ -14,6 +13,7 @@ import { TProjectUserAdditionalPrivilegeDALFactory } from "@app/ee/services/proj
import { getConfig } from "@app/lib/config/env"; import { getConfig } from "@app/lib/config/env";
import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors"; import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors";
import { groupBy } from "@app/lib/fn"; import { groupBy } from "@app/lib/fn";
import { ms } from "@app/lib/ms";
import { TUserGroupMembershipDALFactory } from "../../ee/services/group/user-group-membership-dal"; import { TUserGroupMembershipDALFactory } from "../../ee/services/group/user-group-membership-dal";
import { ActorType } from "../auth/auth-type"; import { ActorType } from "../auth/auth-type";
@@ -82,6 +82,13 @@ export const secretSharingServiceFactory = ({
if (!permission) throw new ForbiddenRequestError({ name: "User is not a part of the specified organization" }); if (!permission) throw new ForbiddenRequestError({ name: "User is not a part of the specified organization" });
$validateSharedSecretExpiry(expiresAt); $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) { if (secretValue.length > 10_000) {
throw new BadRequestError({ message: "Shared secret value too long" }); throw new BadRequestError({ message: "Shared secret value too long" });
} }
@@ -382,6 +382,8 @@ export const AwsParameterStoreSyncFns = {
} }
} }
if (syncOptions.disableSecretDeletion) return;
const parametersToDelete: AWS.SSM.Parameter[] = []; const parametersToDelete: AWS.SSM.Parameter[] = [];
for (const entry of Object.entries(awsParameterStoreSecretsRecord)) { for (const entry of Object.entries(awsParameterStoreSecretsRecord)) {
@@ -396,6 +396,8 @@ export const AwsSecretsManagerSyncFns = {
} }
} }
if (syncOptions.disableSecretDeletion) return;
for await (const secretKey of Object.keys(awsSecretsRecord)) { for await (const secretKey of Object.keys(awsSecretsRecord)) {
if (!(secretKey in secretMap) || !secretMap[secretKey].value) { if (!(secretKey in secretMap) || !secretMap[secretKey].value) {
try { try {
@@ -136,6 +136,8 @@ export const azureAppConfigurationSyncFactory = ({
} }
} }
if (secretSync.syncOptions.disableSecretDeletion) return;
for await (const key of Object.keys(azureAppConfigSecrets)) { for await (const key of Object.keys(azureAppConfigSecrets)) {
const azureSecret = azureAppConfigSecrets[key]; const azureSecret = azureAppConfigSecrets[key];
if ( if (
@@ -189,6 +189,8 @@ export const azureKeyVaultSyncFactory = ({ kmsService, appConnectionDAL }: TAzur
}); });
} }
if (secretSync.syncOptions.disableSecretDeletion) return;
for await (const deleteSecretKey of deleteSecrets.filter( for await (const deleteSecretKey of deleteSecrets.filter(
(secret) => !setSecrets.find((setSecret) => setSecret.key === secret) (secret) => !setSecrets.find((setSecret) => setSecret.key === secret)
)) { )) {
@@ -112,6 +112,8 @@ export const databricksSyncFactory = ({ kmsService, appConnectionDAL }: TDatabri
accessToken accessToken
}); });
if (secretSync.syncOptions.disableSecretDeletion) return;
for await (const secret of databricksSecretKeys) { for await (const secret of databricksSecretKeys) {
if (!(secret.key in secretMap)) { if (!(secret.key in secretMap)) {
await deleteDatabricksSecrets({ await deleteDatabricksSecrets({
@@ -71,8 +71,16 @@ const getGcpSecrets = async (accessToken: string, secretSync: TGcpSyncWithCreden
res[key] = Buffer.from(secretLatest.payload.data, "base64").toString("utf-8"); res[key] = Buffer.from(secretLatest.payload.data, "base64").toString("utf-8");
} catch (error) { } catch (error) {
// when a secret in GCP has no versions, we treat it as if it's a blank value // when a secret in GCP has no versions, or is disabled/destroyed, we treat it as if it's a blank value
if (error instanceof AxiosError && error.response?.status === 404) { if (
error instanceof AxiosError &&
(error.response?.status === 404 ||
(error.response?.status === 400 &&
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
error.response.data.error.status === "FAILED_PRECONDITION" &&
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access,@typescript-eslint/no-unsafe-call
error.response.data.error.message.match(/(?:disabled|destroyed)/i)))
) {
res[key] = ""; res[key] = "";
} else { } else {
throw new SecretSyncError({ throw new SecretSyncError({
@@ -147,6 +155,9 @@ export const GcpSyncFns = {
for await (const key of Object.keys(gcpSecrets)) { for await (const key of Object.keys(gcpSecrets)) {
try { try {
if (!(key in secretMap) || !secretMap[key].value) { if (!(key in secretMap) || !secretMap[key].value) {
// eslint-disable-next-line no-continue
if (secretSync.syncOptions.disableSecretDeletion) continue;
// case: delete secret // case: delete secret
await request.delete( await request.delete(
`${IntegrationUrls.GCP_SECRET_MANAGER_URL}/v1/projects/${destinationConfig.projectId}/secrets/${key}`, `${IntegrationUrls.GCP_SECRET_MANAGER_URL}/v1/projects/${destinationConfig.projectId}/secrets/${key}`,
@@ -192,12 +192,6 @@ export const GithubSyncFns = {
const publicKey = await getPublicKey(client, secretSync); const publicKey = await getPublicKey(client, secretSync);
for await (const encryptedSecret of encryptedSecrets) {
if (!(encryptedSecret.name in secretMap)) {
await deleteSecret(client, secretSync, encryptedSecret);
}
}
await sodium.ready.then(async () => { await sodium.ready.then(async () => {
for await (const key of Object.keys(secretMap)) { for await (const key of Object.keys(secretMap)) {
// convert secret & base64 key to Uint8Array. // convert secret & base64 key to Uint8Array.
@@ -224,6 +218,14 @@ export const GithubSyncFns = {
} }
} }
}); });
if (secretSync.syncOptions.disableSecretDeletion) return;
for await (const encryptedSecret of encryptedSecrets) {
if (!(encryptedSecret.name in secretMap)) {
await deleteSecret(client, secretSync, encryptedSecret);
}
}
}, },
getSecrets: async (secretSync: TGitHubSyncWithCredentials) => { getSecrets: async (secretSync: TGitHubSyncWithCredentials) => {
throw new Error(`${SECRET_SYNC_NAME_MAP[secretSync.destination]} does not support importing secrets.`); throw new Error(`${SECRET_SYNC_NAME_MAP[secretSync.destination]} does not support importing secrets.`);
@@ -0,0 +1,10 @@
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
import { SecretSync } from "@app/services/secret-sync/secret-sync-enums";
import { TSecretSyncListItem } from "@app/services/secret-sync/secret-sync-types";
export const HUMANITEC_SYNC_LIST_OPTION: TSecretSyncListItem = {
name: "Humanitec",
destination: SecretSync.Humanitec,
connection: AppConnection.Humanitec,
canImportSecrets: false
};
@@ -0,0 +1,4 @@
export enum HumanitecSyncScope {
Application = "application",
Environment = "environment"
}
@@ -0,0 +1,220 @@
import { request } from "@app/lib/config/request";
import { logger } from "@app/lib/logger";
import { IntegrationUrls } from "@app/services/integration-auth/integration-list";
import { SecretSyncError } from "@app/services/secret-sync/secret-sync-errors";
import { SECRET_SYNC_NAME_MAP } from "@app/services/secret-sync/secret-sync-maps";
import { TSecretMap } from "@app/services/secret-sync/secret-sync-types";
import { HumanitecSyncScope } from "./humanitec-sync-enums";
import { HumanitecSecret, THumanitecSyncWithCredentials } from "./humanitec-sync-types";
const getHumanitecSecrets = async (secretSync: THumanitecSyncWithCredentials) => {
const {
destinationConfig,
connection: {
credentials: { apiToken }
}
} = secretSync;
let url = `${IntegrationUrls.HUMANITEC_API_URL}/orgs/${destinationConfig.org}/apps/${destinationConfig.app}`;
if (destinationConfig.scope === HumanitecSyncScope.Environment) {
url += `/envs/${destinationConfig.env}`;
}
url += "/values";
const { data } = await request.get<HumanitecSecret[]>(url, {
headers: {
Authorization: `Bearer ${apiToken}`,
Accept: "application/json"
}
});
return data;
};
const deleteSecret = async (secretSync: THumanitecSyncWithCredentials, encryptedSecret: HumanitecSecret) => {
const {
destinationConfig,
connection: {
credentials: { apiToken }
}
} = secretSync;
if (destinationConfig.scope === HumanitecSyncScope.Environment && encryptedSecret.source === "app") {
logger.info(
`Humanitec secret ${encryptedSecret.key} on app ${destinationConfig.app} has no environment override, not deleted as it is an app-level secret`
);
return;
}
try {
let url = `${IntegrationUrls.HUMANITEC_API_URL}/orgs/${destinationConfig.org}/apps/${destinationConfig.app}`;
if (destinationConfig.scope === HumanitecSyncScope.Environment) {
url += `/envs/${destinationConfig.env}`;
}
url += `/values/${encryptedSecret.key}`;
await request.delete(url, {
headers: {
Authorization: `Bearer ${apiToken}`,
Accept: "application/json"
}
});
} catch (error) {
throw new SecretSyncError({
error,
secretKey: encryptedSecret.key
});
}
};
const createSecret = async (secretSync: THumanitecSyncWithCredentials, secretMap: TSecretMap, key: string) => {
try {
const {
destinationConfig,
connection: {
credentials: { apiToken }
}
} = secretSync;
const appLevelSecret = destinationConfig.scope === HumanitecSyncScope.Application ? secretMap[key].value : "";
await request.post(
`${IntegrationUrls.HUMANITEC_API_URL}/orgs/${destinationConfig.org}/apps/${destinationConfig.app}/values`,
{
key,
value: appLevelSecret,
description: secretMap[key].comment || "",
is_secret: true
},
{
headers: {
Authorization: `Bearer ${apiToken}`,
Accept: "application/json"
}
}
);
if (destinationConfig.scope === HumanitecSyncScope.Environment) {
await request.patch(
`${IntegrationUrls.HUMANITEC_API_URL}/orgs/${destinationConfig.org}/apps/${destinationConfig.app}/envs/${destinationConfig.env}/values/${key}`,
{
value: secretMap[key].value,
description: secretMap[key].comment || ""
},
{
headers: {
Authorization: `Bearer ${apiToken}`,
Accept: "application/json"
}
}
);
}
} catch (error) {
throw new SecretSyncError({
error,
secretKey: key
});
}
};
const updateSecret = async (
secretSync: THumanitecSyncWithCredentials,
secretMap: TSecretMap,
encryptedSecret: HumanitecSecret
) => {
try {
const {
destinationConfig,
connection: {
credentials: { apiToken }
}
} = secretSync;
if (destinationConfig.scope === HumanitecSyncScope.Application) {
await request.patch(
`${IntegrationUrls.HUMANITEC_API_URL}/orgs/${destinationConfig.org}/apps/${destinationConfig.app}/values/${encryptedSecret.key}`,
{
value: secretMap[encryptedSecret.key].value,
description: secretMap[encryptedSecret.key].comment || ""
},
{
headers: {
Authorization: `Bearer ${apiToken}`,
Accept: "application/json"
}
}
);
} else if (encryptedSecret.source === "app") {
await request.post(
`${IntegrationUrls.HUMANITEC_API_URL}/orgs/${destinationConfig.org}/apps/${destinationConfig.app}/envs/${destinationConfig.env}/values`,
{
value: secretMap[encryptedSecret.key].value,
description: secretMap[encryptedSecret.key].comment || "",
key: encryptedSecret.key,
is_secret: true
},
{
headers: {
Authorization: `Bearer ${apiToken}`,
Accept: "application/json"
}
}
);
} else {
await request.patch(
`${IntegrationUrls.HUMANITEC_API_URL}/orgs/${destinationConfig.org}/apps/${destinationConfig.app}/envs/${destinationConfig.env}/values/${encryptedSecret.key}`,
{
value: secretMap[encryptedSecret.key].value,
description: secretMap[encryptedSecret.key].comment || ""
},
{
headers: {
Authorization: `Bearer ${apiToken}`,
Accept: "application/json"
}
}
);
}
} catch (error) {
throw new SecretSyncError({
error,
secretKey: encryptedSecret.key
});
}
};
export const HumanitecSyncFns = {
syncSecrets: async (secretSync: THumanitecSyncWithCredentials, secretMap: TSecretMap) => {
const humanitecSecrets = await getHumanitecSecrets(secretSync);
const humanitecSecretsKeys = new Map(humanitecSecrets.map((s) => [s.key, s]));
for await (const key of Object.keys(secretMap)) {
const existingSecret = humanitecSecretsKeys.get(key);
if (!existingSecret) {
await createSecret(secretSync, secretMap, key);
} else {
await updateSecret(secretSync, secretMap, existingSecret);
}
}
if (secretSync.syncOptions.disableSecretDeletion) return;
for await (const humanitecSecret of humanitecSecrets) {
if (!secretMap[humanitecSecret.key]) {
await deleteSecret(secretSync, humanitecSecret);
}
}
},
getSecrets: async (secretSync: THumanitecSyncWithCredentials): Promise<TSecretMap> => {
throw new Error(`${SECRET_SYNC_NAME_MAP[secretSync.destination]} does not support importing secrets.`);
},
removeSecrets: async (secretSync: THumanitecSyncWithCredentials, secretMap: TSecretMap) => {
const encryptedSecrets = await getHumanitecSecrets(secretSync);
for await (const encryptedSecret of encryptedSecrets) {
if (encryptedSecret.key in secretMap) {
await deleteSecret(secretSync, encryptedSecret);
}
}
}
};
@@ -0,0 +1,54 @@
import { z } from "zod";
import { SecretSyncs } from "@app/lib/api-docs";
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
import { HumanitecSyncScope } from "@app/services/secret-sync/humanitec/humanitec-sync-enums";
import { SecretSync } from "@app/services/secret-sync/secret-sync-enums";
import {
BaseSecretSyncSchema,
GenericCreateSecretSyncFieldsSchema,
GenericUpdateSecretSyncFieldsSchema
} from "@app/services/secret-sync/secret-sync-schemas";
import { TSyncOptionsConfig } from "@app/services/secret-sync/secret-sync-types";
const HumanitecSyncDestinationConfigSchema = z.discriminatedUnion("scope", [
z.object({
scope: z.literal(HumanitecSyncScope.Application).describe(SecretSyncs.DESTINATION_CONFIG.HUMANITEC.scope),
org: z.string().min(1, "Org ID is required").describe(SecretSyncs.DESTINATION_CONFIG.HUMANITEC.org),
app: z.string().min(1, "App ID is required").describe(SecretSyncs.DESTINATION_CONFIG.HUMANITEC.app)
}),
z.object({
scope: z.literal(HumanitecSyncScope.Environment).describe(SecretSyncs.DESTINATION_CONFIG.HUMANITEC.scope),
org: z.string().min(1, "Org ID is required").describe(SecretSyncs.DESTINATION_CONFIG.HUMANITEC.org),
app: z.string().min(1, "App ID is required").describe(SecretSyncs.DESTINATION_CONFIG.HUMANITEC.app),
env: z.string().min(1, "Env ID is required").describe(SecretSyncs.DESTINATION_CONFIG.HUMANITEC.env)
})
]);
const HumanitecSyncOptionsConfig: TSyncOptionsConfig = { canImportSecrets: false };
export const HumanitecSyncSchema = BaseSecretSyncSchema(SecretSync.Humanitec, HumanitecSyncOptionsConfig).extend({
destination: z.literal(SecretSync.Humanitec),
destinationConfig: HumanitecSyncDestinationConfigSchema
});
export const CreateHumanitecSyncSchema = GenericCreateSecretSyncFieldsSchema(
SecretSync.Humanitec,
HumanitecSyncOptionsConfig
).extend({
destinationConfig: HumanitecSyncDestinationConfigSchema
});
export const UpdateHumanitecSyncSchema = GenericUpdateSecretSyncFieldsSchema(
SecretSync.Humanitec,
HumanitecSyncOptionsConfig
).extend({
destinationConfig: HumanitecSyncDestinationConfigSchema.optional()
});
export const HumanitecSyncListItemSchema = z.object({
name: z.literal("Humanitec"),
connection: z.literal(AppConnection.Humanitec),
destination: z.literal(SecretSync.Humanitec),
canImportSecrets: z.literal(false)
});
@@ -0,0 +1,23 @@
import z from "zod";
import { THumanitecConnection } from "@app/services/app-connection/humanitec";
import { CreateHumanitecSyncSchema, HumanitecSyncListItemSchema, HumanitecSyncSchema } from "./humanitec-sync-schemas";
export type THumanitecSyncListItem = z.infer<typeof HumanitecSyncListItemSchema>;
export type THumanitecSync = z.infer<typeof HumanitecSyncSchema>;
export type THumanitecSyncInput = z.infer<typeof CreateHumanitecSyncSchema>;
export type THumanitecSyncWithCredentials = THumanitecSync & {
connection: THumanitecConnection;
};
export type HumanitecSecret = {
description: string;
is_secret: boolean;
key: string;
source: "app" | "env";
value: string;
};
@@ -0,0 +1,5 @@
export * from "./humanitec-sync-constants";
export * from "./humanitec-sync-enums";
export * from "./humanitec-sync-fns";
export * from "./humanitec-sync-schemas";
export * from "./humanitec-sync-types";
@@ -5,7 +5,8 @@ export enum SecretSync {
GCPSecretManager = "gcp-secret-manager", GCPSecretManager = "gcp-secret-manager",
AzureKeyVault = "azure-key-vault", AzureKeyVault = "azure-key-vault",
AzureAppConfiguration = "azure-app-configuration", AzureAppConfiguration = "azure-app-configuration",
Databricks = "databricks" Databricks = "databricks",
Humanitec = "humanitec"
} }
export enum SecretSyncInitialSyncBehavior { export enum SecretSyncInitialSyncBehavior {
@@ -24,6 +24,8 @@ import { AZURE_APP_CONFIGURATION_SYNC_LIST_OPTION, azureAppConfigurationSyncFact
import { AZURE_KEY_VAULT_SYNC_LIST_OPTION, azureKeyVaultSyncFactory } from "./azure-key-vault"; import { AZURE_KEY_VAULT_SYNC_LIST_OPTION, azureKeyVaultSyncFactory } from "./azure-key-vault";
import { GCP_SYNC_LIST_OPTION } from "./gcp"; import { GCP_SYNC_LIST_OPTION } from "./gcp";
import { GcpSyncFns } from "./gcp/gcp-sync-fns"; import { GcpSyncFns } from "./gcp/gcp-sync-fns";
import { HUMANITEC_SYNC_LIST_OPTION } from "./humanitec";
import { HumanitecSyncFns } from "./humanitec/humanitec-sync-fns";
const SECRET_SYNC_LIST_OPTIONS: Record<SecretSync, TSecretSyncListItem> = { const SECRET_SYNC_LIST_OPTIONS: Record<SecretSync, TSecretSyncListItem> = {
[SecretSync.AWSParameterStore]: AWS_PARAMETER_STORE_SYNC_LIST_OPTION, [SecretSync.AWSParameterStore]: AWS_PARAMETER_STORE_SYNC_LIST_OPTION,
@@ -32,7 +34,8 @@ const SECRET_SYNC_LIST_OPTIONS: Record<SecretSync, TSecretSyncListItem> = {
[SecretSync.GCPSecretManager]: GCP_SYNC_LIST_OPTION, [SecretSync.GCPSecretManager]: GCP_SYNC_LIST_OPTION,
[SecretSync.AzureKeyVault]: AZURE_KEY_VAULT_SYNC_LIST_OPTION, [SecretSync.AzureKeyVault]: AZURE_KEY_VAULT_SYNC_LIST_OPTION,
[SecretSync.AzureAppConfiguration]: AZURE_APP_CONFIGURATION_SYNC_LIST_OPTION, [SecretSync.AzureAppConfiguration]: AZURE_APP_CONFIGURATION_SYNC_LIST_OPTION,
[SecretSync.Databricks]: DATABRICKS_SYNC_LIST_OPTION [SecretSync.Databricks]: DATABRICKS_SYNC_LIST_OPTION,
[SecretSync.Humanitec]: HUMANITEC_SYNC_LIST_OPTION
}; };
export const listSecretSyncOptions = () => { export const listSecretSyncOptions = () => {
@@ -116,6 +119,8 @@ export const SecretSyncFns = {
appConnectionDAL, appConnectionDAL,
kmsService kmsService
}).syncSecrets(secretSync, secretMap); }).syncSecrets(secretSync, secretMap);
case SecretSync.Humanitec:
return HumanitecSyncFns.syncSecrets(secretSync, secretMap);
default: default:
throw new Error( throw new Error(
`Unhandled sync destination for sync secrets fns: ${(secretSync as TSecretSyncWithCredentials).destination}` `Unhandled sync destination for sync secrets fns: ${(secretSync as TSecretSyncWithCredentials).destination}`
@@ -157,6 +162,9 @@ export const SecretSyncFns = {
appConnectionDAL, appConnectionDAL,
kmsService kmsService
}).getSecrets(secretSync); }).getSecrets(secretSync);
case SecretSync.Humanitec:
secretMap = await HumanitecSyncFns.getSecrets(secretSync);
break;
default: default:
throw new Error( throw new Error(
`Unhandled sync destination for get secrets fns: ${(secretSync as TSecretSyncWithCredentials).destination}` `Unhandled sync destination for get secrets fns: ${(secretSync as TSecretSyncWithCredentials).destination}`
@@ -197,6 +205,8 @@ export const SecretSyncFns = {
appConnectionDAL, appConnectionDAL,
kmsService kmsService
}).removeSecrets(secretSync, secretMap); }).removeSecrets(secretSync, secretMap);
case SecretSync.Humanitec:
return HumanitecSyncFns.removeSecrets(secretSync, secretMap);
default: default:
throw new Error( throw new Error(
`Unhandled sync destination for remove secrets fns: ${(secretSync as TSecretSyncWithCredentials).destination}` `Unhandled sync destination for remove secrets fns: ${(secretSync as TSecretSyncWithCredentials).destination}`
@@ -8,7 +8,8 @@ export const SECRET_SYNC_NAME_MAP: Record<SecretSync, string> = {
[SecretSync.GCPSecretManager]: "GCP Secret Manager", [SecretSync.GCPSecretManager]: "GCP Secret Manager",
[SecretSync.AzureKeyVault]: "Azure Key Vault", [SecretSync.AzureKeyVault]: "Azure Key Vault",
[SecretSync.AzureAppConfiguration]: "Azure App Configuration", [SecretSync.AzureAppConfiguration]: "Azure App Configuration",
[SecretSync.Databricks]: "Databricks" [SecretSync.Databricks]: "Databricks",
[SecretSync.Humanitec]: "Humanitec"
}; };
export const SECRET_SYNC_CONNECTION_MAP: Record<SecretSync, AppConnection> = { export const SECRET_SYNC_CONNECTION_MAP: Record<SecretSync, AppConnection> = {
@@ -18,5 +19,6 @@ export const SECRET_SYNC_CONNECTION_MAP: Record<SecretSync, AppConnection> = {
[SecretSync.GCPSecretManager]: AppConnection.GCP, [SecretSync.GCPSecretManager]: AppConnection.GCP,
[SecretSync.AzureKeyVault]: AppConnection.AzureKeyVault, [SecretSync.AzureKeyVault]: AppConnection.AzureKeyVault,
[SecretSync.AzureAppConfiguration]: AppConnection.AzureAppConfiguration, [SecretSync.AzureAppConfiguration]: AppConnection.AzureAppConfiguration,
[SecretSync.Databricks]: AppConnection.Databricks [SecretSync.Databricks]: AppConnection.Databricks,
[SecretSync.Humanitec]: AppConnection.Humanitec
}; };
@@ -23,7 +23,8 @@ const BaseSyncOptionsSchema = <T extends AnyZodObject | undefined = undefined>({
initialSyncBehavior: (canImportSecrets initialSyncBehavior: (canImportSecrets
? z.nativeEnum(SecretSyncInitialSyncBehavior) ? z.nativeEnum(SecretSyncInitialSyncBehavior)
: z.literal(SecretSyncInitialSyncBehavior.OverwriteDestination) : z.literal(SecretSyncInitialSyncBehavior.OverwriteDestination)
).describe(SecretSyncs.SYNC_OPTIONS(destination).initialSyncBehavior) ).describe(SecretSyncs.SYNC_OPTIONS(destination).initialSyncBehavior),
disableSecretDeletion: z.boolean().optional().describe(SecretSyncs.SYNC_OPTIONS(destination).disableSecretDeletion)
}); });
const schema = merge ? baseSchema.merge(merge) : baseSchema; const schema = merge ? baseSchema.merge(merge) : baseSchema;
@@ -43,6 +43,12 @@ import {
TAzureKeyVaultSyncWithCredentials TAzureKeyVaultSyncWithCredentials
} from "./azure-key-vault"; } from "./azure-key-vault";
import { TGcpSync, TGcpSyncInput, TGcpSyncListItem, TGcpSyncWithCredentials } from "./gcp"; import { TGcpSync, TGcpSyncInput, TGcpSyncListItem, TGcpSyncWithCredentials } from "./gcp";
import {
THumanitecSync,
THumanitecSyncInput,
THumanitecSyncListItem,
THumanitecSyncWithCredentials
} from "./humanitec";
export type TSecretSync = export type TSecretSync =
| TAwsParameterStoreSync | TAwsParameterStoreSync
@@ -51,7 +57,8 @@ export type TSecretSync =
| TGcpSync | TGcpSync
| TAzureKeyVaultSync | TAzureKeyVaultSync
| TAzureAppConfigurationSync | TAzureAppConfigurationSync
| TDatabricksSync; | TDatabricksSync
| THumanitecSync;
export type TSecretSyncWithCredentials = export type TSecretSyncWithCredentials =
| TAwsParameterStoreSyncWithCredentials | TAwsParameterStoreSyncWithCredentials
@@ -60,7 +67,8 @@ export type TSecretSyncWithCredentials =
| TGcpSyncWithCredentials | TGcpSyncWithCredentials
| TAzureKeyVaultSyncWithCredentials | TAzureKeyVaultSyncWithCredentials
| TAzureAppConfigurationSyncWithCredentials | TAzureAppConfigurationSyncWithCredentials
| TDatabricksSyncWithCredentials; | TDatabricksSyncWithCredentials
| THumanitecSyncWithCredentials;
export type TSecretSyncInput = export type TSecretSyncInput =
| TAwsParameterStoreSyncInput | TAwsParameterStoreSyncInput
@@ -69,7 +77,8 @@ export type TSecretSyncInput =
| TGcpSyncInput | TGcpSyncInput
| TAzureKeyVaultSyncInput | TAzureKeyVaultSyncInput
| TAzureAppConfigurationSyncInput | TAzureAppConfigurationSyncInput
| TDatabricksSyncInput; | TDatabricksSyncInput
| THumanitecSyncInput;
export type TSecretSyncListItem = export type TSecretSyncListItem =
| TAwsParameterStoreSyncListItem | TAwsParameterStoreSyncListItem
@@ -78,7 +87,8 @@ export type TSecretSyncListItem =
| TGcpSyncListItem | TGcpSyncListItem
| TAzureKeyVaultSyncListItem | TAzureKeyVaultSyncListItem
| TAzureAppConfigurationSyncListItem | TAzureAppConfigurationSyncListItem
| TDatabricksSyncListItem; | TDatabricksSyncListItem
| THumanitecSyncListItem;
export type TSyncOptionsConfig = { export type TSyncOptionsConfig = {
canImportSecrets: boolean; canImportSecrets: boolean;
@@ -7,6 +7,7 @@ import { getConfig } from "@app/lib/config/env";
import { infisicalSymmetricEncypt } from "@app/lib/crypto/encryption"; import { infisicalSymmetricEncypt } from "@app/lib/crypto/encryption";
import { getUserPrivateKey } from "@app/lib/crypto/srp"; import { getUserPrivateKey } from "@app/lib/crypto/srp";
import { BadRequestError, NotFoundError } from "@app/lib/errors"; import { BadRequestError, NotFoundError } from "@app/lib/errors";
import { TIdentityDALFactory } from "@app/services/identity/identity-dal";
import { TAuthLoginFactory } from "../auth/auth-login-service"; import { TAuthLoginFactory } from "../auth/auth-login-service";
import { AuthMethod } from "../auth/auth-type"; 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 { UserAliasType } from "../user-alias/user-alias-types";
import { TSuperAdminDALFactory } from "./super-admin-dal"; import { TSuperAdminDALFactory } from "./super-admin-dal";
import { LoginMethod, TAdminGetIdentitiesDTO, TAdminGetUsersDTO, TAdminSignUpDTO } from "./super-admin-types"; import { LoginMethod, TAdminGetIdentitiesDTO, TAdminGetUsersDTO, TAdminSignUpDTO } from "./super-admin-types";
import { TIdentityDALFactory } from "@app/services/identity/identity-dal";
type TSuperAdminServiceFactoryDep = { type TSuperAdminServiceFactoryDep = {
identityDAL: Pick<TIdentityDALFactory, "getIdentitiesByFilter">; identityDAL: Pick<TIdentityDALFactory, "getIdentitiesByFilter">;
+17 -3
View File
@@ -143,7 +143,15 @@ var secretsSetCmd = &cobra.Command{
Short: "Used set secrets", Short: "Used set secrets",
Use: "set [secrets]", Use: "set [secrets]",
DisableFlagsInUseLine: true, DisableFlagsInUseLine: true,
Args: cobra.MinimumNArgs(1), Args: func(cmd *cobra.Command, args []string) error {
if cmd.Flags().Changed("file") {
if len(args) > 0 {
return fmt.Errorf("secrets cannot be provided as command-line arguments when the --file option is used. Please choose either file-based or argument-based secret input")
}
return nil
}
return cobra.MinimumNArgs(1)(cmd, args)
},
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
token, err := util.GetInfisicalToken(cmd) token, err := util.GetInfisicalToken(cmd)
if err != nil { if err != nil {
@@ -177,13 +185,18 @@ var secretsSetCmd = &cobra.Command{
util.HandleError(err, "Unable to parse secret type") util.HandleError(err, "Unable to parse secret type")
} }
file, err := cmd.Flags().GetString("file")
if err != nil {
util.HandleError(err, "Unable to parse flag")
}
var secretOperations []models.SecretSetOperation var secretOperations []models.SecretSetOperation
if token != nil && (token.Type == util.SERVICE_TOKEN_IDENTIFIER || token.Type == util.UNIVERSAL_AUTH_TOKEN_IDENTIFIER) { if token != nil && (token.Type == util.SERVICE_TOKEN_IDENTIFIER || token.Type == util.UNIVERSAL_AUTH_TOKEN_IDENTIFIER) {
if projectId == "" { if projectId == "" {
util.PrintErrorMessageAndExit("When using service tokens or machine identities, you must set the --projectId flag") util.PrintErrorMessageAndExit("When using service tokens or machine identities, you must set the --projectId flag")
} }
secretOperations, err = util.SetRawSecrets(args, secretType, environmentName, secretsPath, projectId, token) secretOperations, err = util.SetRawSecrets(args, secretType, environmentName, secretsPath, projectId, token, file)
} else { } else {
if projectId == "" { if projectId == "" {
workspaceFile, err := util.GetWorkSpaceFromFile() workspaceFile, err := util.GetWorkSpaceFromFile()
@@ -206,7 +219,7 @@ var secretsSetCmd = &cobra.Command{
secretOperations, err = util.SetRawSecrets(args, secretType, environmentName, secretsPath, projectId, &models.TokenDetails{ secretOperations, err = util.SetRawSecrets(args, secretType, environmentName, secretsPath, projectId, &models.TokenDetails{
Type: "", Type: "",
Token: loggedInUserDetails.UserCredentials.JTWToken, Token: loggedInUserDetails.UserCredentials.JTWToken,
}) }, file)
} }
if err != nil { if err != nil {
@@ -691,6 +704,7 @@ func init() {
secretsSetCmd.Flags().String("projectId", "", "manually set the project ID to for setting secrets when using machine identity based auth") secretsSetCmd.Flags().String("projectId", "", "manually set the project ID to for setting secrets when using machine identity based auth")
secretsSetCmd.Flags().String("path", "/", "set secrets within a folder path") secretsSetCmd.Flags().String("path", "/", "set secrets within a folder path")
secretsSetCmd.Flags().String("type", util.SECRET_TYPE_SHARED, "the type of secret to create: personal or shared") secretsSetCmd.Flags().String("type", util.SECRET_TYPE_SHARED, "the type of secret to create: personal or shared")
secretsSetCmd.Flags().String("file", "", "Load secrets from the specified file. File format: .env or YAML (comments: # or //). This option is mutually exclusive with command-line secrets arguments.")
secretsDeleteCmd.Flags().String("type", "personal", "the type of secret to delete: personal or shared (default: personal)") secretsDeleteCmd.Flags().String("type", "personal", "the type of secret to delete: personal or shared (default: personal)")
secretsDeleteCmd.Flags().String("token", "", "Fetch secrets using service token or machine identity access token") secretsDeleteCmd.Flags().String("token", "", "Fetch secrets using service token or machine identity access token")
+94 -1
View File
@@ -17,6 +17,7 @@ import (
"github.com/go-resty/resty/v2" "github.com/go-resty/resty/v2"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
"github.com/zalando/go-keyring" "github.com/zalando/go-keyring"
"gopkg.in/yaml.v3"
) )
func GetPlainTextSecretsViaServiceToken(fullServiceToken string, environment string, secretPath string, includeImports bool, recursive bool, tagSlugs string, expandSecretReferences bool) ([]models.SingleEnvironmentVariable, error) { func GetPlainTextSecretsViaServiceToken(fullServiceToken string, environment string, secretPath string, includeImports bool, recursive bool, tagSlugs string, expandSecretReferences bool) ([]models.SingleEnvironmentVariable, error) {
@@ -564,7 +565,99 @@ func GetPlainTextWorkspaceKey(authenticationToken string, receiverPrivateKey str
return crypto.DecryptAsymmetric(encryptedWorkspaceKey, encryptedWorkspaceKeyNonce, encryptedWorkspaceKeySenderPublicKey, currentUsersPrivateKey), nil return crypto.DecryptAsymmetric(encryptedWorkspaceKey, encryptedWorkspaceKeyNonce, encryptedWorkspaceKeySenderPublicKey, currentUsersPrivateKey), nil
} }
func SetRawSecrets(secretArgs []string, secretType string, environmentName string, secretsPath string, projectId string, tokenDetails *models.TokenDetails) ([]models.SecretSetOperation, error) { func parseSecrets(fileName string, content string) (map[string]string, error) {
secrets := make(map[string]string)
if strings.HasSuffix(fileName, ".yaml") || strings.HasSuffix(fileName, ".yml") {
// Handle YAML secrets
var yamlData map[string]interface{}
if err := yaml.Unmarshal([]byte(content), &yamlData); err != nil {
return nil, fmt.Errorf("failed to parse YAML file: %v", err)
}
for key, value := range yamlData {
if strValue, ok := value.(string); ok {
secrets[key] = strValue
} else {
return nil, fmt.Errorf("YAML secret '%s' must be a string", key)
}
}
} else {
// Handle .env files
lines := strings.Split(content, "\n")
for _, line := range lines {
line = strings.TrimSpace(line)
// Ignore empty lines and comments
if line == "" || strings.HasPrefix(line, "#") || strings.HasPrefix(line, "//") {
continue
}
// Ensure it's a valid key=value pair
splitKeyValue := strings.SplitN(line, "=", 2)
if len(splitKeyValue) != 2 {
return nil, fmt.Errorf("invalid format, expected key=value in line: %s", line)
}
key, value := strings.TrimSpace(splitKeyValue[0]), strings.TrimSpace(splitKeyValue[1])
// Handle quoted values
if (strings.HasPrefix(value, `"`) && strings.HasSuffix(value, `"`)) ||
(strings.HasPrefix(value, `'`) && strings.HasSuffix(value, `'`)) {
value = value[1 : len(value)-1] // Remove surrounding quotes
}
secrets[key] = value
}
}
return secrets, nil
}
func validateSecretKey(key string) error {
if key == "" {
return errors.New("secret keys cannot be empty")
}
if unicode.IsNumber(rune(key[0])) {
return fmt.Errorf("secret key '%s' cannot start with a number", key)
}
if strings.Contains(key, " ") {
return fmt.Errorf("secret key '%s' cannot contain spaces", key)
}
return nil
}
func SetRawSecrets(secretArgs []string, secretType string, environmentName string, secretsPath string, projectId string, tokenDetails *models.TokenDetails, file string) ([]models.SecretSetOperation, error) {
if file != "" {
content, err := os.ReadFile(file)
if err != nil {
if errors.Is(err, os.ErrNotExist) {
PrintErrorMessageAndExit("File does not exist")
}
return nil, fmt.Errorf("unable to process file [err=%v]", err)
}
parsedSecrets, err := parseSecrets(file, string(content))
if err != nil {
PrintErrorMessageAndExit(fmt.Sprintf("error parsing secrets: %v", err))
}
// Step 2: Validate secrets
for key, value := range parsedSecrets {
if err := validateSecretKey(key); err != nil {
PrintErrorMessageAndExit(err.Error())
}
if strings.TrimSpace(value) == "" {
PrintErrorMessageAndExit(fmt.Sprintf("Secret key '%s' has an empty value", key))
}
secretArgs = append(secretArgs, fmt.Sprintf("%s=%s", key, value))
}
if len(secretArgs) == 0 {
PrintErrorMessageAndExit("no valid secrets found in the file")
}
}
if tokenDetails == nil { if tokenDetails == nil {
return nil, fmt.Errorf("unable to process set secret operations, token details are missing") return nil, fmt.Errorf("unable to process set secret operations, token details are missing")
-12
View File
@@ -15,15 +15,3 @@ Since Infisical's team is globally distributed, it is hard for us to keep track
## Winter break ## Winter break
Every year, Infisical team goes on a company-wide vacation during winter holidays. This year, the winter break period starts on December 21st, 2024 and ends on January 5th, 2025. You should expect to do no scheduled work during this period, but we will have a rotation process for [high and urgent service disruptions](https://infisical.com/sla). Every year, Infisical team goes on a company-wide vacation during winter holidays. This year, the winter break period starts on December 21st, 2024 and ends on January 5th, 2025. You should expect to do no scheduled work during this period, but we will have a rotation process for [high and urgent service disruptions](https://infisical.com/sla).
## Parental leave
At Infisical, we recognize that parental leave is a special and important time, significantly different from a typical vacation. We’re proud to offer parental leave to everyone, regardless of gender, and whether you’ve become a parent through childbirth or adoption.
For team members who have been with Infisical for over a year by the time of your child’s birth or adoption, you are eligible for up to 12 weeks of paid parental leave. This leave will be provided in one continuous block to allow you uninterrupted time with your family. If you have been with Infisical for less than a year, we will follow the parental leave provisions required by your local jurisdiction.
While we trust your judgment, parental leave is intended to be a distinct benefit and is not designed to be combined with our unlimited PTO policy. To ensure fairness and balance, we generally discourage combining parental leave with an extended vacation.
When you’re ready, please notify Maidul about your plans for parental leave, ideally at least four months in advance. This allows us to support you fully and arrange any necessary logistics, including salary adjustments and statutory paperwork.
We’re here to support you as you embark on this exciting new chapter in your life!
@@ -0,0 +1,4 @@
---
title: "Available"
openapi: "GET /api/v1/app-connections/humanitec/available"
---
@@ -0,0 +1,4 @@
---
title: "Create"
openapi: "POST /api/v1/app-connections/humanitec"
---

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