mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-08 16:28:11 +00:00
Merge branch 'feature/certificate-template' into feature/est-simpleenroll
This commit is contained in:
@@ -50,6 +50,6 @@ jobs:
|
|||||||
CLI_TESTS_ENV_SLUG: ${{ secrets.CLI_TESTS_ENV_SLUG }}
|
CLI_TESTS_ENV_SLUG: ${{ secrets.CLI_TESTS_ENV_SLUG }}
|
||||||
CLI_TESTS_USER_EMAIL: ${{ secrets.CLI_TESTS_USER_EMAIL }}
|
CLI_TESTS_USER_EMAIL: ${{ secrets.CLI_TESTS_USER_EMAIL }}
|
||||||
CLI_TESTS_USER_PASSWORD: ${{ secrets.CLI_TESTS_USER_PASSWORD }}
|
CLI_TESTS_USER_PASSWORD: ${{ secrets.CLI_TESTS_USER_PASSWORD }}
|
||||||
INFISICAL_VAULT_FILE_PASSPHRASE: ${{ secrets.CLI_TESTS_INFISICAL_VAULT_FILE_PASSPHRASE }}
|
# INFISICAL_VAULT_FILE_PASSPHRASE: ${{ secrets.CLI_TESTS_INFISICAL_VAULT_FILE_PASSPHRASE }}
|
||||||
|
|
||||||
run: go test -v -count=1 ./test
|
run: go test -v -count=1 ./test
|
||||||
|
|||||||
@@ -15,3 +15,16 @@ up-prod:
|
|||||||
|
|
||||||
down:
|
down:
|
||||||
docker compose -f docker-compose.dev.yml down
|
docker compose -f docker-compose.dev.yml down
|
||||||
|
|
||||||
|
reviewable-ui:
|
||||||
|
cd frontend && \
|
||||||
|
npm run lint:fix && \
|
||||||
|
npm run type:check
|
||||||
|
|
||||||
|
reviewable-api:
|
||||||
|
cd backend && \
|
||||||
|
npm run lint:fix && \
|
||||||
|
npm run type:check
|
||||||
|
|
||||||
|
reviewable: reviewable-ui reviewable-api
|
||||||
|
|
||||||
|
|||||||
Generated
+73
-6
@@ -25,6 +25,7 @@
|
|||||||
"@fastify/swagger": "^8.14.0",
|
"@fastify/swagger": "^8.14.0",
|
||||||
"@fastify/swagger-ui": "^2.1.0",
|
"@fastify/swagger-ui": "^2.1.0",
|
||||||
"@node-saml/passport-saml": "^4.0.4",
|
"@node-saml/passport-saml": "^4.0.4",
|
||||||
|
"@octokit/plugin-retry": "^5.0.5",
|
||||||
"@octokit/rest": "^20.0.2",
|
"@octokit/rest": "^20.0.2",
|
||||||
"@octokit/webhooks-types": "^7.3.1",
|
"@octokit/webhooks-types": "^7.3.1",
|
||||||
"@peculiar/asn1-schema": "^2.3.8",
|
"@peculiar/asn1-schema": "^2.3.8",
|
||||||
@@ -76,6 +77,7 @@
|
|||||||
"pkijs": "^3.2.4",
|
"pkijs": "^3.2.4",
|
||||||
"posthog-node": "^3.6.2",
|
"posthog-node": "^3.6.2",
|
||||||
"probot": "^13.0.0",
|
"probot": "^13.0.0",
|
||||||
|
"safe-regex": "^2.1.1",
|
||||||
"smee-client": "^2.0.0",
|
"smee-client": "^2.0.0",
|
||||||
"tedious": "^18.2.1",
|
"tedious": "^18.2.1",
|
||||||
"tweetnacl": "^1.0.3",
|
"tweetnacl": "^1.0.3",
|
||||||
@@ -107,6 +109,7 @@
|
|||||||
"@types/picomatch": "^2.3.3",
|
"@types/picomatch": "^2.3.3",
|
||||||
"@types/prompt-sync": "^4.2.3",
|
"@types/prompt-sync": "^4.2.3",
|
||||||
"@types/resolve": "^1.20.6",
|
"@types/resolve": "^1.20.6",
|
||||||
|
"@types/safe-regex": "^1.1.6",
|
||||||
"@types/uuid": "^9.0.7",
|
"@types/uuid": "^9.0.7",
|
||||||
"@typescript-eslint/eslint-plugin": "^6.20.0",
|
"@typescript-eslint/eslint-plugin": "^6.20.0",
|
||||||
"@typescript-eslint/parser": "^6.20.0",
|
"@typescript-eslint/parser": "^6.20.0",
|
||||||
@@ -7824,19 +7827,45 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@octokit/plugin-retry": {
|
"node_modules/@octokit/plugin-retry": {
|
||||||
"version": "6.0.1",
|
"version": "5.0.5",
|
||||||
"resolved": "https://registry.npmjs.org/@octokit/plugin-retry/-/plugin-retry-6.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/@octokit/plugin-retry/-/plugin-retry-5.0.5.tgz",
|
||||||
"integrity": "sha512-SKs+Tz9oj0g4p28qkZwl/topGcb0k0qPNX/i7vBKmDsjoeqnVfFUquqrE/O9oJY7+oLzdCtkiWSXLpLjvl6uog==",
|
"integrity": "sha512-sB1RWMhSrre02Atv95K6bhESlJ/sPdZkK/wE/w1IdSCe0yM6FxSjksLa6T7aAvxvxlLKzQEC4KIiqpqyov1Tbg==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@octokit/request-error": "^5.0.0",
|
"@octokit/request-error": "^4.0.1",
|
||||||
"@octokit/types": "^12.0.0",
|
"@octokit/types": "^10.0.0",
|
||||||
"bottleneck": "^2.15.3"
|
"bottleneck": "^2.15.3"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 18"
|
"node": ">= 18"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@octokit/core": ">=5"
|
"@octokit/core": ">=3"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@octokit/plugin-retry/node_modules/@octokit/openapi-types": {
|
||||||
|
"version": "18.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-18.1.1.tgz",
|
||||||
|
"integrity": "sha512-VRaeH8nCDtF5aXWnjPuEMIYf1itK/s3JYyJcWFJT8X9pSNnBtriDf7wlEWsGuhPLl4QIH4xM8fqTXDwJ3Mu6sw=="
|
||||||
|
},
|
||||||
|
"node_modules/@octokit/plugin-retry/node_modules/@octokit/request-error": {
|
||||||
|
"version": "4.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-4.0.2.tgz",
|
||||||
|
"integrity": "sha512-uqwUEmZw3x4I9DGYq9fODVAAvcLsPQv97NRycP6syEFu5916M189VnNBW2zANNwqg3OiligNcAey7P0SET843w==",
|
||||||
|
"dependencies": {
|
||||||
|
"@octokit/types": "^10.0.0",
|
||||||
|
"deprecation": "^2.0.0",
|
||||||
|
"once": "^1.4.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@octokit/plugin-retry/node_modules/@octokit/types": {
|
||||||
|
"version": "10.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-10.0.0.tgz",
|
||||||
|
"integrity": "sha512-Vm8IddVmhCgU1fxC1eyinpwqzXPEYu0NrYzD3YZjlGjyftdLBTeqNblRC0jmJmgxbJIsQlyogVeGnrNaaMVzIg==",
|
||||||
|
"dependencies": {
|
||||||
|
"@octokit/openapi-types": "^18.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@octokit/plugin-throttling": {
|
"node_modules/@octokit/plugin-throttling": {
|
||||||
@@ -9786,6 +9815,12 @@
|
|||||||
"integrity": "sha512-A4STmOXPhMUtHH+S6ymgE2GiBSMqf4oTvcQZMcHzokuTLVYzXTB8ttjcgxOVaAp2lGwEdzZ0J+cRbbeevQj1UQ==",
|
"integrity": "sha512-A4STmOXPhMUtHH+S6ymgE2GiBSMqf4oTvcQZMcHzokuTLVYzXTB8ttjcgxOVaAp2lGwEdzZ0J+cRbbeevQj1UQ==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/safe-regex": {
|
||||||
|
"version": "1.1.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/safe-regex/-/safe-regex-1.1.6.tgz",
|
||||||
|
"integrity": "sha512-CQ/uPB9fLOPKwDsrTeVbNIkwfUthTWOx0l6uIGwVFjZxv7e68pCW5gtTYFzdJi3EBJp8h8zYhJbTasAbX7gEMQ==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"node_modules/@types/semver": {
|
"node_modules/@types/semver": {
|
||||||
"version": "7.5.6",
|
"version": "7.5.6",
|
||||||
"resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.6.tgz",
|
"resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.6.tgz",
|
||||||
@@ -17432,6 +17467,22 @@
|
|||||||
"node": ">=18"
|
"node": ">=18"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/probot/node_modules/@octokit/plugin-retry": {
|
||||||
|
"version": "6.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@octokit/plugin-retry/-/plugin-retry-6.0.1.tgz",
|
||||||
|
"integrity": "sha512-SKs+Tz9oj0g4p28qkZwl/topGcb0k0qPNX/i7vBKmDsjoeqnVfFUquqrE/O9oJY7+oLzdCtkiWSXLpLjvl6uog==",
|
||||||
|
"dependencies": {
|
||||||
|
"@octokit/request-error": "^5.0.0",
|
||||||
|
"@octokit/types": "^12.0.0",
|
||||||
|
"bottleneck": "^2.15.3"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 18"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@octokit/core": ">=5"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/probot/node_modules/commander": {
|
"node_modules/probot/node_modules/commander": {
|
||||||
"version": "11.1.0",
|
"version": "11.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/commander/-/commander-11.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/commander/-/commander-11.1.0.tgz",
|
||||||
@@ -17814,6 +17865,14 @@
|
|||||||
"@babel/runtime": "^7.8.4"
|
"@babel/runtime": "^7.8.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/regexp-tree": {
|
||||||
|
"version": "0.1.27",
|
||||||
|
"resolved": "https://registry.npmjs.org/regexp-tree/-/regexp-tree-0.1.27.tgz",
|
||||||
|
"integrity": "sha512-iETxpjK6YoRWJG5o6hXLwvjYAoW+FEZn9os0PD/b6AP6xQwsa/Y7lCVgIixBbUPMfhu+i2LtdeAqVTgGlQarfA==",
|
||||||
|
"bin": {
|
||||||
|
"regexp-tree": "bin/regexp-tree"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/regexp.prototype.flags": {
|
"node_modules/regexp.prototype.flags": {
|
||||||
"version": "1.5.1",
|
"version": "1.5.1",
|
||||||
"resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.1.tgz",
|
"resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.1.tgz",
|
||||||
@@ -18130,6 +18189,14 @@
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"node_modules/safe-regex": {
|
||||||
|
"version": "2.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-2.1.1.tgz",
|
||||||
|
"integrity": "sha512-rx+x8AMzKb5Q5lQ95Zoi6ZbJqwCLkqi3XuJXp5P3rT8OEc6sZCJG5AE5dU3lsgRr/F4Bs31jSlVN+j5KrsGu9A==",
|
||||||
|
"dependencies": {
|
||||||
|
"regexp-tree": "~0.1.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/safe-regex-test": {
|
"node_modules/safe-regex-test": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.0.tgz",
|
||||||
|
|||||||
@@ -78,6 +78,7 @@
|
|||||||
"@types/picomatch": "^2.3.3",
|
"@types/picomatch": "^2.3.3",
|
||||||
"@types/prompt-sync": "^4.2.3",
|
"@types/prompt-sync": "^4.2.3",
|
||||||
"@types/resolve": "^1.20.6",
|
"@types/resolve": "^1.20.6",
|
||||||
|
"@types/safe-regex": "^1.1.6",
|
||||||
"@types/uuid": "^9.0.7",
|
"@types/uuid": "^9.0.7",
|
||||||
"@typescript-eslint/eslint-plugin": "^6.20.0",
|
"@typescript-eslint/eslint-plugin": "^6.20.0",
|
||||||
"@typescript-eslint/parser": "^6.20.0",
|
"@typescript-eslint/parser": "^6.20.0",
|
||||||
@@ -121,6 +122,7 @@
|
|||||||
"@fastify/swagger": "^8.14.0",
|
"@fastify/swagger": "^8.14.0",
|
||||||
"@fastify/swagger-ui": "^2.1.0",
|
"@fastify/swagger-ui": "^2.1.0",
|
||||||
"@node-saml/passport-saml": "^4.0.4",
|
"@node-saml/passport-saml": "^4.0.4",
|
||||||
|
"@octokit/plugin-retry": "^5.0.5",
|
||||||
"@octokit/rest": "^20.0.2",
|
"@octokit/rest": "^20.0.2",
|
||||||
"@octokit/webhooks-types": "^7.3.1",
|
"@octokit/webhooks-types": "^7.3.1",
|
||||||
"@peculiar/asn1-schema": "^2.3.8",
|
"@peculiar/asn1-schema": "^2.3.8",
|
||||||
@@ -172,6 +174,7 @@
|
|||||||
"pkijs": "^3.2.4",
|
"pkijs": "^3.2.4",
|
||||||
"posthog-node": "^3.6.2",
|
"posthog-node": "^3.6.2",
|
||||||
"probot": "^13.0.0",
|
"probot": "^13.0.0",
|
||||||
|
"safe-regex": "^2.1.1",
|
||||||
"smee-client": "^2.0.0",
|
"smee-client": "^2.0.0",
|
||||||
"tedious": "^18.2.1",
|
"tedious": "^18.2.1",
|
||||||
"tweetnacl": "^1.0.3",
|
"tweetnacl": "^1.0.3",
|
||||||
|
|||||||
@@ -7,14 +7,33 @@ const prompt = promptSync({
|
|||||||
sigint: true
|
sigint: true
|
||||||
});
|
});
|
||||||
|
|
||||||
|
type ComponentType = 1 | 2 | 3;
|
||||||
|
|
||||||
console.log(`
|
console.log(`
|
||||||
Component List
|
Component List
|
||||||
--------------
|
--------------
|
||||||
|
0. Exit
|
||||||
1. Service component
|
1. Service component
|
||||||
2. DAL component
|
2. DAL component
|
||||||
3. Router component
|
3. Router component
|
||||||
`);
|
`);
|
||||||
const componentType = parseInt(prompt("Select a component: "), 10);
|
|
||||||
|
function getComponentType(): ComponentType {
|
||||||
|
while (true) {
|
||||||
|
const input = prompt("Select a component (0-3): ");
|
||||||
|
const componentType = parseInt(input, 10);
|
||||||
|
|
||||||
|
if (componentType === 0) {
|
||||||
|
console.log("Exiting the program. Goodbye!");
|
||||||
|
process.exit(0);
|
||||||
|
} else if (componentType === 1 || componentType === 2 || componentType === 3) {
|
||||||
|
return componentType;
|
||||||
|
} else {
|
||||||
|
console.log("Invalid input. Please enter 0, 1, 2, or 3.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const componentType = getComponentType();
|
||||||
|
|
||||||
if (componentType === 1) {
|
if (componentType === 1) {
|
||||||
const componentName = prompt("Enter service name: ");
|
const componentName = prompt("Enter service name: ");
|
||||||
|
|||||||
Vendored
+4
@@ -18,6 +18,7 @@ import { TOidcConfigServiceFactory } from "@app/ee/services/oidc/oidc-config-ser
|
|||||||
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
||||||
import { TProjectUserAdditionalPrivilegeServiceFactory } from "@app/ee/services/project-user-additional-privilege/project-user-additional-privilege-service";
|
import { TProjectUserAdditionalPrivilegeServiceFactory } from "@app/ee/services/project-user-additional-privilege/project-user-additional-privilege-service";
|
||||||
import { TRateLimitServiceFactory } from "@app/ee/services/rate-limit/rate-limit-service";
|
import { TRateLimitServiceFactory } from "@app/ee/services/rate-limit/rate-limit-service";
|
||||||
|
import { RateLimitConfiguration } from "@app/ee/services/rate-limit/rate-limit-types";
|
||||||
import { TSamlConfigServiceFactory } from "@app/ee/services/saml-config/saml-config-service";
|
import { TSamlConfigServiceFactory } from "@app/ee/services/saml-config/saml-config-service";
|
||||||
import { TScimServiceFactory } from "@app/ee/services/scim/scim-service";
|
import { TScimServiceFactory } from "@app/ee/services/scim/scim-service";
|
||||||
import { TSecretApprovalPolicyServiceFactory } from "@app/ee/services/secret-approval-policy/secret-approval-policy-service";
|
import { TSecretApprovalPolicyServiceFactory } from "@app/ee/services/secret-approval-policy/secret-approval-policy-service";
|
||||||
@@ -35,6 +36,7 @@ import { ActorAuthMethod, ActorType } from "@app/services/auth/auth-type";
|
|||||||
import { TAuthTokenServiceFactory } from "@app/services/auth-token/auth-token-service";
|
import { TAuthTokenServiceFactory } from "@app/services/auth-token/auth-token-service";
|
||||||
import { TCertificateServiceFactory } from "@app/services/certificate/certificate-service";
|
import { TCertificateServiceFactory } from "@app/services/certificate/certificate-service";
|
||||||
import { TCertificateAuthorityServiceFactory } from "@app/services/certificate-authority/certificate-authority-service";
|
import { TCertificateAuthorityServiceFactory } from "@app/services/certificate-authority/certificate-authority-service";
|
||||||
|
import { TCertificateTemplateServiceFactory } from "@app/services/certificate-template/certificate-template-service";
|
||||||
import { TGroupProjectServiceFactory } from "@app/services/group-project/group-project-service";
|
import { TGroupProjectServiceFactory } from "@app/services/group-project/group-project-service";
|
||||||
import { TIdentityServiceFactory } from "@app/services/identity/identity-service";
|
import { TIdentityServiceFactory } from "@app/services/identity/identity-service";
|
||||||
import { TIdentityAccessTokenServiceFactory } from "@app/services/identity-access-token/identity-access-token-service";
|
import { TIdentityAccessTokenServiceFactory } from "@app/services/identity-access-token/identity-access-token-service";
|
||||||
@@ -91,6 +93,7 @@ declare module "fastify" {
|
|||||||
id: string;
|
id: string;
|
||||||
orgId: string;
|
orgId: string;
|
||||||
};
|
};
|
||||||
|
rateLimits: RateLimitConfiguration;
|
||||||
// passport data
|
// passport data
|
||||||
passportUser: {
|
passportUser: {
|
||||||
isUserCompleted: string;
|
isUserCompleted: string;
|
||||||
@@ -154,6 +157,7 @@ declare module "fastify" {
|
|||||||
auditLog: TAuditLogServiceFactory;
|
auditLog: TAuditLogServiceFactory;
|
||||||
auditLogStream: TAuditLogStreamServiceFactory;
|
auditLogStream: TAuditLogStreamServiceFactory;
|
||||||
certificate: TCertificateServiceFactory;
|
certificate: TCertificateServiceFactory;
|
||||||
|
certificateTemplate: TCertificateTemplateServiceFactory;
|
||||||
certificateAuthority: TCertificateAuthorityServiceFactory;
|
certificateAuthority: TCertificateAuthorityServiceFactory;
|
||||||
certificateAuthorityCrl: TCertificateAuthorityCrlServiceFactory;
|
certificateAuthorityCrl: TCertificateAuthorityCrlServiceFactory;
|
||||||
pkiCollection: TPkiCollectionServiceFactory;
|
pkiCollection: TPkiCollectionServiceFactory;
|
||||||
|
|||||||
Vendored
+8
@@ -53,6 +53,9 @@ import {
|
|||||||
TCertificateSecretsUpdate,
|
TCertificateSecretsUpdate,
|
||||||
TCertificatesInsert,
|
TCertificatesInsert,
|
||||||
TCertificatesUpdate,
|
TCertificatesUpdate,
|
||||||
|
TCertificateTemplates,
|
||||||
|
TCertificateTemplatesInsert,
|
||||||
|
TCertificateTemplatesUpdate,
|
||||||
TDynamicSecretLeases,
|
TDynamicSecretLeases,
|
||||||
TDynamicSecretLeasesInsert,
|
TDynamicSecretLeasesInsert,
|
||||||
TDynamicSecretLeasesUpdate,
|
TDynamicSecretLeasesUpdate,
|
||||||
@@ -369,6 +372,11 @@ declare module "knex/types/tables" {
|
|||||||
TCertificateAuthorityCrlUpdate
|
TCertificateAuthorityCrlUpdate
|
||||||
>;
|
>;
|
||||||
[TableName.Certificate]: KnexOriginal.CompositeTableType<TCertificates, TCertificatesInsert, TCertificatesUpdate>;
|
[TableName.Certificate]: KnexOriginal.CompositeTableType<TCertificates, TCertificatesInsert, TCertificatesUpdate>;
|
||||||
|
[TableName.CertificateTemplate]: KnexOriginal.CompositeTableType<
|
||||||
|
TCertificateTemplates,
|
||||||
|
TCertificateTemplatesInsert,
|
||||||
|
TCertificateTemplatesUpdate
|
||||||
|
>;
|
||||||
[TableName.CertificateBody]: KnexOriginal.CompositeTableType<
|
[TableName.CertificateBody]: KnexOriginal.CompositeTableType<
|
||||||
TCertificateBodies,
|
TCertificateBodies,
|
||||||
TCertificateBodiesInsert,
|
TCertificateBodiesInsert,
|
||||||
|
|||||||
@@ -4,16 +4,19 @@ import { TableName } from "../schemas";
|
|||||||
|
|
||||||
export async function up(knex: Knex): Promise<void> {
|
export async function up(knex: Knex): Promise<void> {
|
||||||
if (await knex.schema.hasTable(TableName.CertificateAuthority)) {
|
if (await knex.schema.hasTable(TableName.CertificateAuthority)) {
|
||||||
const hasActiveCaCertVersionColumn = await knex.schema.hasColumn(
|
const hasActiveCaCertIdColumn = await knex.schema.hasColumn(TableName.CertificateAuthority, "activeCaCertId");
|
||||||
TableName.CertificateAuthority,
|
if (!hasActiveCaCertIdColumn) {
|
||||||
"activeCaCertVersion"
|
|
||||||
);
|
|
||||||
if (!hasActiveCaCertVersionColumn) {
|
|
||||||
await knex.schema.alterTable(TableName.CertificateAuthority, (t) => {
|
await knex.schema.alterTable(TableName.CertificateAuthority, (t) => {
|
||||||
t.integer("activeCaCertVersion").nullable();
|
t.uuid("activeCaCertId").nullable();
|
||||||
|
t.foreign("activeCaCertId").references("id").inTable(TableName.CertificateAuthorityCert);
|
||||||
});
|
});
|
||||||
|
|
||||||
await knex(TableName.CertificateAuthority).where("status", "active").update({ activeCaCertVersion: 1 });
|
await knex.raw(`
|
||||||
|
UPDATE "${TableName.CertificateAuthority}" ca
|
||||||
|
SET "activeCaCertId" = cac.id
|
||||||
|
FROM "${TableName.CertificateAuthorityCert}" cac
|
||||||
|
WHERE ca.id = cac."caId"
|
||||||
|
`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -22,7 +25,7 @@ export async function up(knex: Knex): Promise<void> {
|
|||||||
if (!hasVersionColumn) {
|
if (!hasVersionColumn) {
|
||||||
await knex.schema.alterTable(TableName.CertificateAuthorityCert, (t) => {
|
await knex.schema.alterTable(TableName.CertificateAuthorityCert, (t) => {
|
||||||
t.integer("version").nullable();
|
t.integer("version").nullable();
|
||||||
// t.dropUnique(["caId"]);
|
t.dropUnique(["caId"]);
|
||||||
});
|
});
|
||||||
|
|
||||||
await knex(TableName.CertificateAuthorityCert).update({ version: 1 }).whereNull("version");
|
await knex(TableName.CertificateAuthorityCert).update({ version: 1 }).whereNull("version");
|
||||||
@@ -54,18 +57,38 @@ export async function up(knex: Knex): Promise<void> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// if (await knex.schema.hasTable(TableName.CertificateAuthoritySecret)) {
|
if (await knex.schema.hasTable(TableName.CertificateAuthoritySecret)) {
|
||||||
// await knex.schema.alterTable(TableName.CertificateAuthoritySecret, (t) => {
|
await knex.schema.alterTable(TableName.CertificateAuthoritySecret, (t) => {
|
||||||
// t.dropUnique(["caId"]);
|
t.dropUnique(["caId"]);
|
||||||
// });
|
});
|
||||||
// }
|
}
|
||||||
|
|
||||||
|
if (await knex.schema.hasTable(TableName.Certificate)) {
|
||||||
|
await knex.schema.alterTable(TableName.Certificate, (t) => {
|
||||||
|
t.uuid("caCertId").nullable();
|
||||||
|
t.foreign("caCertId").references("id").inTable(TableName.CertificateAuthorityCert);
|
||||||
|
});
|
||||||
|
|
||||||
|
await knex.raw(`
|
||||||
|
UPDATE "${TableName.Certificate}" cert
|
||||||
|
SET "caCertId" = (
|
||||||
|
SELECT caCert.id
|
||||||
|
FROM "${TableName.CertificateAuthorityCert}" caCert
|
||||||
|
WHERE caCert."caId" = cert."caId"
|
||||||
|
)
|
||||||
|
`);
|
||||||
|
|
||||||
|
await knex.schema.alterTable(TableName.Certificate, (t) => {
|
||||||
|
t.uuid("caCertId").notNullable().alter();
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function down(knex: Knex): Promise<void> {
|
export async function down(knex: Knex): Promise<void> {
|
||||||
if (await knex.schema.hasTable(TableName.CertificateAuthority)) {
|
if (await knex.schema.hasTable(TableName.CertificateAuthority)) {
|
||||||
if (await knex.schema.hasColumn(TableName.CertificateAuthority, "activeCaCertVersion")) {
|
if (await knex.schema.hasColumn(TableName.CertificateAuthority, "activeCaCertId")) {
|
||||||
await knex.schema.alterTable(TableName.CertificateAuthority, (t) => {
|
await knex.schema.alterTable(TableName.CertificateAuthority, (t) => {
|
||||||
t.dropColumn("activeCaCertVersion");
|
t.dropColumn("activeCaCertId");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -83,4 +106,12 @@ export async function down(knex: Knex): Promise<void> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (await knex.schema.hasTable(TableName.Certificate)) {
|
||||||
|
if (await knex.schema.hasColumn(TableName.Certificate, "caCertId")) {
|
||||||
|
await knex.schema.alterTable(TableName.Certificate, (t) => {
|
||||||
|
t.dropColumn("caCertId");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
import { Knex } from "knex";
|
||||||
|
|
||||||
|
import { TableName } from "../schemas";
|
||||||
|
|
||||||
|
export async function up(knex: Knex): Promise<void> {
|
||||||
|
const hasCreationLimitCol = await knex.schema.hasColumn(TableName.RateLimit, "creationLimit");
|
||||||
|
await knex.schema.alterTable(TableName.RateLimit, (t) => {
|
||||||
|
if (hasCreationLimitCol) {
|
||||||
|
t.dropColumn("creationLimit");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function down(knex: Knex): Promise<void> {
|
||||||
|
const hasCreationLimitCol = await knex.schema.hasColumn(TableName.RateLimit, "creationLimit");
|
||||||
|
await knex.schema.alterTable(TableName.RateLimit, (t) => {
|
||||||
|
if (!hasCreationLimitCol) {
|
||||||
|
t.integer("creationLimit").defaultTo(30).notNullable();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
import { Knex } from "knex";
|
||||||
|
|
||||||
|
import { TableName } from "../schemas";
|
||||||
|
|
||||||
|
export async function up(knex: Knex): Promise<void> {
|
||||||
|
const hasNameField = await knex.schema.hasColumn(TableName.SecretTag, "name");
|
||||||
|
if (hasNameField) {
|
||||||
|
await knex.schema.alterTable(TableName.SecretTag, (t) => {
|
||||||
|
t.dropColumn("name");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function down(knex: Knex): Promise<void> {
|
||||||
|
const hasNameField = await knex.schema.hasColumn(TableName.SecretTag, "name");
|
||||||
|
if (!hasNameField) {
|
||||||
|
await knex.schema.alterTable(TableName.SecretTag, (t) => {
|
||||||
|
t.string("name");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
+5
-1
@@ -11,9 +11,12 @@ export async function up(knex: Knex): Promise<void> {
|
|||||||
t.string("projectId").notNullable();
|
t.string("projectId").notNullable();
|
||||||
t.foreign("projectId").references("id").inTable(TableName.Project).onDelete("CASCADE");
|
t.foreign("projectId").references("id").inTable(TableName.Project).onDelete("CASCADE");
|
||||||
t.string("name").notNullable();
|
t.string("name").notNullable();
|
||||||
|
t.string("description").notNullable();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await createOnUpdateTrigger(knex, TableName.PkiCollection);
|
||||||
|
|
||||||
if (!(await knex.schema.hasTable(TableName.PkiCollectionItem))) {
|
if (!(await knex.schema.hasTable(TableName.PkiCollectionItem))) {
|
||||||
await knex.schema.createTable(TableName.PkiCollectionItem, (t) => {
|
await knex.schema.createTable(TableName.PkiCollectionItem, (t) => {
|
||||||
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
|
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
|
||||||
@@ -27,6 +30,8 @@ export async function up(knex: Knex): Promise<void> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await createOnUpdateTrigger(knex, TableName.PkiCollectionItem);
|
||||||
|
|
||||||
if (!(await knex.schema.hasTable(TableName.PkiAlert))) {
|
if (!(await knex.schema.hasTable(TableName.PkiAlert))) {
|
||||||
await knex.schema.createTable(TableName.PkiAlert, (t) => {
|
await knex.schema.createTable(TableName.PkiAlert, (t) => {
|
||||||
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
|
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
|
||||||
@@ -42,7 +47,6 @@ export async function up(knex: Knex): Promise<void> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
await createOnUpdateTrigger(knex, TableName.PkiCollection);
|
|
||||||
await createOnUpdateTrigger(knex, TableName.PkiAlert);
|
await createOnUpdateTrigger(knex, TableName.PkiAlert);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
import { Knex } from "knex";
|
||||||
|
|
||||||
|
import { TableName } from "../schemas";
|
||||||
|
import { createOnUpdateTrigger, dropOnUpdateTrigger } from "../utils";
|
||||||
|
|
||||||
|
export async function up(knex: Knex): Promise<void> {
|
||||||
|
const hasCertificateTemplateTable = await knex.schema.hasTable(TableName.CertificateTemplate);
|
||||||
|
if (!hasCertificateTemplateTable) {
|
||||||
|
await knex.schema.createTable(TableName.CertificateTemplate, (tb) => {
|
||||||
|
tb.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
|
||||||
|
tb.uuid("caId").notNullable();
|
||||||
|
tb.foreign("caId").references("id").inTable(TableName.CertificateAuthority).onDelete("CASCADE");
|
||||||
|
tb.uuid("pkiCollectionId");
|
||||||
|
tb.foreign("pkiCollectionId").references("id").inTable(TableName.PkiCollection).onDelete("SET NULL");
|
||||||
|
tb.string("name").notNullable();
|
||||||
|
tb.string("commonName").notNullable();
|
||||||
|
tb.string("subjectAlternativeName").notNullable();
|
||||||
|
tb.string("ttl").notNullable();
|
||||||
|
tb.timestamps(true, true, true);
|
||||||
|
});
|
||||||
|
|
||||||
|
await createOnUpdateTrigger(knex, TableName.CertificateTemplate);
|
||||||
|
}
|
||||||
|
|
||||||
|
const doesCertificateTableHaveTemplateId = await knex.schema.hasColumn(
|
||||||
|
TableName.Certificate,
|
||||||
|
"certificateTemplateId"
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!doesCertificateTableHaveTemplateId) {
|
||||||
|
await knex.schema.alterTable(TableName.Certificate, (tb) => {
|
||||||
|
tb.uuid("certificateTemplateId");
|
||||||
|
tb.foreign("certificateTemplateId").references("id").inTable(TableName.CertificateTemplate).onDelete("SET NULL");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function down(knex: Knex): Promise<void> {
|
||||||
|
const doesCertificateTableHaveTemplateId = await knex.schema.hasColumn(
|
||||||
|
TableName.Certificate,
|
||||||
|
"certificateTemplateId"
|
||||||
|
);
|
||||||
|
|
||||||
|
if (doesCertificateTableHaveTemplateId) {
|
||||||
|
await knex.schema.alterTable(TableName.Certificate, (t) => {
|
||||||
|
t.dropColumn("certificateTemplateId");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const hasCertificateTemplateTable = await knex.schema.hasTable(TableName.CertificateTemplate);
|
||||||
|
if (hasCertificateTemplateTable) {
|
||||||
|
await knex.schema.dropTable(TableName.CertificateTemplate);
|
||||||
|
await dropOnUpdateTrigger(knex, TableName.CertificateTemplate);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -28,7 +28,7 @@ export const CertificateAuthoritiesSchema = z.object({
|
|||||||
keyAlgorithm: z.string(),
|
keyAlgorithm: z.string(),
|
||||||
notBefore: z.date().nullable().optional(),
|
notBefore: z.date().nullable().optional(),
|
||||||
notAfter: z.date().nullable().optional(),
|
notAfter: z.date().nullable().optional(),
|
||||||
activeCaCertVersion: z.number().nullable().optional()
|
activeCaCertId: z.string().uuid().nullable().optional()
|
||||||
});
|
});
|
||||||
|
|
||||||
export type TCertificateAuthorities = z.infer<typeof CertificateAuthoritiesSchema>;
|
export type TCertificateAuthorities = z.infer<typeof CertificateAuthoritiesSchema>;
|
||||||
|
|||||||
@@ -0,0 +1,24 @@
|
|||||||
|
// Code generated by automation script, DO NOT EDIT.
|
||||||
|
// Automated by pulling database and generating zod schema
|
||||||
|
// To update. Just run npm run generate:schema
|
||||||
|
// Written by akhilmhdh.
|
||||||
|
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { TImmutableDBKeys } from "./models";
|
||||||
|
|
||||||
|
export const CertificateTemplatesSchema = z.object({
|
||||||
|
id: z.string().uuid(),
|
||||||
|
caId: z.string().uuid(),
|
||||||
|
pkiCollectionId: z.string().uuid().nullable().optional(),
|
||||||
|
name: z.string(),
|
||||||
|
commonName: z.string(),
|
||||||
|
subjectAlternativeName: z.string(),
|
||||||
|
ttl: z.string(),
|
||||||
|
createdAt: z.date(),
|
||||||
|
updatedAt: z.date()
|
||||||
|
});
|
||||||
|
|
||||||
|
export type TCertificateTemplates = z.infer<typeof CertificateTemplatesSchema>;
|
||||||
|
export type TCertificateTemplatesInsert = Omit<z.input<typeof CertificateTemplatesSchema>, TImmutableDBKeys>;
|
||||||
|
export type TCertificateTemplatesUpdate = Partial<Omit<z.input<typeof CertificateTemplatesSchema>, TImmutableDBKeys>>;
|
||||||
@@ -20,7 +20,9 @@ export const CertificatesSchema = z.object({
|
|||||||
notAfter: z.date(),
|
notAfter: z.date(),
|
||||||
revokedAt: z.date().nullable().optional(),
|
revokedAt: z.date().nullable().optional(),
|
||||||
revocationReason: z.number().nullable().optional(),
|
revocationReason: z.number().nullable().optional(),
|
||||||
altNames: z.string().default("").nullable().optional()
|
altNames: z.string().default("").nullable().optional(),
|
||||||
|
caCertId: z.string().uuid(),
|
||||||
|
certificateTemplateId: z.string().uuid().nullable().optional()
|
||||||
});
|
});
|
||||||
|
|
||||||
export type TCertificates = z.infer<typeof CertificatesSchema>;
|
export type TCertificates = z.infer<typeof CertificatesSchema>;
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ export * from "./certificate-authority-crl";
|
|||||||
export * from "./certificate-authority-secret";
|
export * from "./certificate-authority-secret";
|
||||||
export * from "./certificate-bodies";
|
export * from "./certificate-bodies";
|
||||||
export * from "./certificate-secrets";
|
export * from "./certificate-secrets";
|
||||||
|
export * from "./certificate-templates";
|
||||||
export * from "./certificates";
|
export * from "./certificates";
|
||||||
export * from "./dynamic-secret-leases";
|
export * from "./dynamic-secret-leases";
|
||||||
export * from "./dynamic-secrets";
|
export * from "./dynamic-secrets";
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ export enum TableName {
|
|||||||
Certificate = "certificates",
|
Certificate = "certificates",
|
||||||
CertificateBody = "certificate_bodies",
|
CertificateBody = "certificate_bodies",
|
||||||
CertificateSecret = "certificate_secrets",
|
CertificateSecret = "certificate_secrets",
|
||||||
|
CertificateTemplate = "certificate_templates",
|
||||||
PkiAlert = "pki_alerts",
|
PkiAlert = "pki_alerts",
|
||||||
PkiCollection = "pki_collections",
|
PkiCollection = "pki_collections",
|
||||||
PkiCollectionItem = "pki_collection_items",
|
PkiCollectionItem = "pki_collection_items",
|
||||||
|
|||||||
@@ -12,7 +12,8 @@ export const PkiCollectionsSchema = z.object({
|
|||||||
createdAt: z.date(),
|
createdAt: z.date(),
|
||||||
updatedAt: z.date(),
|
updatedAt: z.date(),
|
||||||
projectId: z.string(),
|
projectId: z.string(),
|
||||||
name: z.string()
|
name: z.string(),
|
||||||
|
description: z.string()
|
||||||
});
|
});
|
||||||
|
|
||||||
export type TPkiCollections = z.infer<typeof PkiCollectionsSchema>;
|
export type TPkiCollections = z.infer<typeof PkiCollectionsSchema>;
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ export const RateLimitSchema = z.object({
|
|||||||
authRateLimit: z.number().default(60),
|
authRateLimit: z.number().default(60),
|
||||||
inviteUserRateLimit: z.number().default(30),
|
inviteUserRateLimit: z.number().default(30),
|
||||||
mfaRateLimit: z.number().default(20),
|
mfaRateLimit: z.number().default(20),
|
||||||
creationLimit: z.number().default(30),
|
|
||||||
publicEndpointLimit: z.number().default(30),
|
publicEndpointLimit: z.number().default(30),
|
||||||
createdAt: z.date(),
|
createdAt: z.date(),
|
||||||
updatedAt: z.date()
|
updatedAt: z.date()
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import { TImmutableDBKeys } from "./models";
|
|||||||
|
|
||||||
export const SecretTagsSchema = z.object({
|
export const SecretTagsSchema = z.object({
|
||||||
id: z.string().uuid(),
|
id: z.string().uuid(),
|
||||||
name: z.string(),
|
|
||||||
slug: z.string(),
|
slug: z.string(),
|
||||||
color: z.string().nullable().optional(),
|
color: z.string().nullable().optional(),
|
||||||
createdAt: z.date(),
|
createdAt: z.date(),
|
||||||
|
|||||||
@@ -131,7 +131,7 @@ export const registerDynamicSecretLeaseRouter = async (server: FastifyZodProvide
|
|||||||
.default("/")
|
.default("/")
|
||||||
.transform(removeTrailingSlash)
|
.transform(removeTrailingSlash)
|
||||||
.describe(DYNAMIC_SECRET_LEASES.RENEW.path),
|
.describe(DYNAMIC_SECRET_LEASES.RENEW.path),
|
||||||
environmentSlug: z.string().min(1).describe(DYNAMIC_SECRET_LEASES.RENEW.ttl)
|
environmentSlug: z.string().min(1).describe(DYNAMIC_SECRET_LEASES.RENEW.environmentSlug)
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
|
|||||||
@@ -58,7 +58,6 @@ export const registerRateLimitRouter = async (server: FastifyZodProvider) => {
|
|||||||
authRateLimit: z.number(),
|
authRateLimit: z.number(),
|
||||||
inviteUserRateLimit: z.number(),
|
inviteUserRateLimit: z.number(),
|
||||||
mfaRateLimit: z.number(),
|
mfaRateLimit: z.number(),
|
||||||
creationLimit: z.number(),
|
|
||||||
publicEndpointLimit: z.number()
|
publicEndpointLimit: z.number()
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
|
|||||||
@@ -75,15 +75,16 @@ export const auditLogDALFactory = (db: TDbClient) => {
|
|||||||
.del()
|
.del()
|
||||||
.returning("id");
|
.returning("id");
|
||||||
numberOfRetryOnFailure = 0; // reset
|
numberOfRetryOnFailure = 0; // reset
|
||||||
// eslint-disable-next-line no-await-in-loop
|
|
||||||
await new Promise((resolve) => {
|
|
||||||
setTimeout(resolve, 100); // time to breathe for db
|
|
||||||
});
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
numberOfRetryOnFailure += 1;
|
numberOfRetryOnFailure += 1;
|
||||||
logger.error(error, "Failed to delete audit log on pruning");
|
logger.error(error, "Failed to delete audit log on pruning");
|
||||||
|
} finally {
|
||||||
|
// eslint-disable-next-line no-await-in-loop
|
||||||
|
await new Promise((resolve) => {
|
||||||
|
setTimeout(resolve, 10); // time to breathe for db
|
||||||
|
});
|
||||||
}
|
}
|
||||||
} while (deletedAuditLogIds.length > 0 && numberOfRetryOnFailure < MAX_RETRY_ON_FAILURE);
|
} while (deletedAuditLogIds.length > 0 || numberOfRetryOnFailure < MAX_RETRY_ON_FAILURE);
|
||||||
};
|
};
|
||||||
|
|
||||||
return { ...auditLogOrm, pruneAuditLog, find };
|
return { ...auditLogOrm, pruneAuditLog, find };
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { ForbiddenError } from "@casl/ability";
|
import { ForbiddenError } from "@casl/ability";
|
||||||
|
|
||||||
|
import { getConfig } from "@app/lib/config/env";
|
||||||
import { BadRequestError } from "@app/lib/errors";
|
import { BadRequestError } from "@app/lib/errors";
|
||||||
|
|
||||||
import { TPermissionServiceFactory } from "../permission/permission-service";
|
import { TPermissionServiceFactory } from "../permission/permission-service";
|
||||||
@@ -61,6 +62,10 @@ export const auditLogServiceFactory = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const createAuditLog = async (data: TCreateAuditLogDTO) => {
|
const createAuditLog = async (data: TCreateAuditLogDTO) => {
|
||||||
|
const appCfg = getConfig();
|
||||||
|
if (appCfg.DISABLE_AUDIT_LOG_GENERATION) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
// add all cases in which project id or org id cannot be added
|
// add all cases in which project id or org id cannot be added
|
||||||
if (data.event.type !== EventType.LOGIN_IDENTITY_UNIVERSAL_AUTH) {
|
if (data.event.type !== EventType.LOGIN_IDENTITY_UNIVERSAL_AUTH) {
|
||||||
if (!data.projectId && !data.orgId) throw new BadRequestError({ message: "Must either project id or org id" });
|
if (!data.projectId && !data.orgId) throw new BadRequestError({ message: "Must either project id or org id" });
|
||||||
|
|||||||
@@ -162,7 +162,11 @@ export enum EventType {
|
|||||||
UPDATE_PROJECT_KMS = "update-project-kms",
|
UPDATE_PROJECT_KMS = "update-project-kms",
|
||||||
GET_PROJECT_KMS_BACKUP = "get-project-kms-backup",
|
GET_PROJECT_KMS_BACKUP = "get-project-kms-backup",
|
||||||
LOAD_PROJECT_KMS_BACKUP = "load-project-kms-backup",
|
LOAD_PROJECT_KMS_BACKUP = "load-project-kms-backup",
|
||||||
ORG_ADMIN_ACCESS_PROJECT = "org-admin-accessed-project"
|
ORG_ADMIN_ACCESS_PROJECT = "org-admin-accessed-project",
|
||||||
|
CREATE_CERTIFICATE_TEMPLATE = "create-certificate-template",
|
||||||
|
UPDATE_CERTIFICATE_TEMPLATE = "update-certificate-template",
|
||||||
|
DELETE_CERTIFICATE_TEMPLATE = "delete-certificate-template",
|
||||||
|
GET_CERTIFICATE_TEMPLATE = "get-certificate-template"
|
||||||
}
|
}
|
||||||
|
|
||||||
interface UserActorMetadata {
|
interface UserActorMetadata {
|
||||||
@@ -352,6 +356,7 @@ interface DeleteIntegrationEvent {
|
|||||||
targetServiceId?: string;
|
targetServiceId?: string;
|
||||||
path?: string;
|
path?: string;
|
||||||
region?: string;
|
region?: string;
|
||||||
|
shouldDeleteIntegrationSecrets?: boolean;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1365,6 +1370,46 @@ interface LoadProjectKmsBackupEvent {
|
|||||||
metadata: Record<string, string>; // no metadata yet
|
metadata: Record<string, string>; // no metadata yet
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface CreateCertificateTemplate {
|
||||||
|
type: EventType.CREATE_CERTIFICATE_TEMPLATE;
|
||||||
|
metadata: {
|
||||||
|
certificateTemplateId: string;
|
||||||
|
caId: string;
|
||||||
|
pkiCollectionId?: string;
|
||||||
|
name: string;
|
||||||
|
commonName: string;
|
||||||
|
subjectAlternativeName: string;
|
||||||
|
ttl: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
interface GetCertificateTemplate {
|
||||||
|
type: EventType.GET_CERTIFICATE_TEMPLATE;
|
||||||
|
metadata: {
|
||||||
|
certificateTemplateId: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
interface UpdateCertificateTemplate {
|
||||||
|
type: EventType.UPDATE_CERTIFICATE_TEMPLATE;
|
||||||
|
metadata: {
|
||||||
|
certificateTemplateId: string;
|
||||||
|
caId: string;
|
||||||
|
pkiCollectionId?: string;
|
||||||
|
name: string;
|
||||||
|
commonName: string;
|
||||||
|
subjectAlternativeName: string;
|
||||||
|
ttl: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
interface DeleteCertificateTemplate {
|
||||||
|
type: EventType.DELETE_CERTIFICATE_TEMPLATE;
|
||||||
|
metadata: {
|
||||||
|
certificateTemplateId: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
interface OrgAdminAccessProjectEvent {
|
interface OrgAdminAccessProjectEvent {
|
||||||
type: EventType.ORG_ADMIN_ACCESS_PROJECT;
|
type: EventType.ORG_ADMIN_ACCESS_PROJECT;
|
||||||
metadata: {
|
metadata: {
|
||||||
@@ -1498,4 +1543,8 @@ export type Event =
|
|||||||
| UpdateProjectKmsEvent
|
| UpdateProjectKmsEvent
|
||||||
| GetProjectKmsBackupEvent
|
| GetProjectKmsBackupEvent
|
||||||
| LoadProjectKmsBackupEvent
|
| LoadProjectKmsBackupEvent
|
||||||
| OrgAdminAccessProjectEvent;
|
| OrgAdminAccessProjectEvent
|
||||||
|
| CreateCertificateTemplate
|
||||||
|
| UpdateCertificateTemplate
|
||||||
|
| GetCertificateTemplate
|
||||||
|
| DeleteCertificateTemplate;
|
||||||
|
|||||||
@@ -40,7 +40,12 @@ export const getDefaultOnPremFeatures = (): TFeatureSet => ({
|
|||||||
secretRotation: true,
|
secretRotation: true,
|
||||||
caCrl: false,
|
caCrl: false,
|
||||||
instanceUserManagement: false,
|
instanceUserManagement: false,
|
||||||
externalKms: false
|
externalKms: false,
|
||||||
|
rateLimits: {
|
||||||
|
readLimit: 60,
|
||||||
|
writeLimit: 200,
|
||||||
|
secretsLimit: 40
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
export const setupLicenceRequestWithStore = (baseURL: string, refreshUrl: string, licenseKey: string) => {
|
export const setupLicenceRequestWithStore = (baseURL: string, refreshUrl: string, licenseKey: string) => {
|
||||||
|
|||||||
@@ -58,6 +58,11 @@ export type TFeatureSet = {
|
|||||||
caCrl: false;
|
caCrl: false;
|
||||||
instanceUserManagement: false;
|
instanceUserManagement: false;
|
||||||
externalKms: false;
|
externalKms: false;
|
||||||
|
rateLimits: {
|
||||||
|
readLimit: number;
|
||||||
|
writeLimit: number;
|
||||||
|
secretsLimit: number;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TOrgPlansTableDTO = {
|
export type TOrgPlansTableDTO = {
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ export enum ProjectPermissionSub {
|
|||||||
Identity = "identity",
|
Identity = "identity",
|
||||||
CertificateAuthorities = "certificate-authorities",
|
CertificateAuthorities = "certificate-authorities",
|
||||||
Certificates = "certificates",
|
Certificates = "certificates",
|
||||||
|
CertificateTemplates = "certificate-templates",
|
||||||
PkiAlerts = "pki-alerts",
|
PkiAlerts = "pki-alerts",
|
||||||
PkiCollections = "pki-collections",
|
PkiCollections = "pki-collections",
|
||||||
Kms = "kms"
|
Kms = "kms"
|
||||||
@@ -65,6 +66,7 @@ export type ProjectPermissionSet =
|
|||||||
| [ProjectPermissionActions, ProjectPermissionSub.Identity]
|
| [ProjectPermissionActions, ProjectPermissionSub.Identity]
|
||||||
| [ProjectPermissionActions, ProjectPermissionSub.CertificateAuthorities]
|
| [ProjectPermissionActions, ProjectPermissionSub.CertificateAuthorities]
|
||||||
| [ProjectPermissionActions, ProjectPermissionSub.Certificates]
|
| [ProjectPermissionActions, ProjectPermissionSub.Certificates]
|
||||||
|
| [ProjectPermissionActions, ProjectPermissionSub.CertificateTemplates]
|
||||||
| [ProjectPermissionActions, ProjectPermissionSub.PkiAlerts]
|
| [ProjectPermissionActions, ProjectPermissionSub.PkiAlerts]
|
||||||
| [ProjectPermissionActions, ProjectPermissionSub.PkiCollections]
|
| [ProjectPermissionActions, ProjectPermissionSub.PkiCollections]
|
||||||
| [ProjectPermissionActions.Delete, ProjectPermissionSub.Project]
|
| [ProjectPermissionActions.Delete, ProjectPermissionSub.Project]
|
||||||
@@ -165,6 +167,11 @@ const buildAdminPermissionRules = () => {
|
|||||||
can(ProjectPermissionActions.Edit, ProjectPermissionSub.Certificates);
|
can(ProjectPermissionActions.Edit, ProjectPermissionSub.Certificates);
|
||||||
can(ProjectPermissionActions.Delete, ProjectPermissionSub.Certificates);
|
can(ProjectPermissionActions.Delete, ProjectPermissionSub.Certificates);
|
||||||
|
|
||||||
|
can(ProjectPermissionActions.Read, ProjectPermissionSub.CertificateTemplates);
|
||||||
|
can(ProjectPermissionActions.Create, ProjectPermissionSub.CertificateTemplates);
|
||||||
|
can(ProjectPermissionActions.Edit, ProjectPermissionSub.CertificateTemplates);
|
||||||
|
can(ProjectPermissionActions.Delete, ProjectPermissionSub.CertificateTemplates);
|
||||||
|
|
||||||
can(ProjectPermissionActions.Read, ProjectPermissionSub.PkiAlerts);
|
can(ProjectPermissionActions.Read, ProjectPermissionSub.PkiAlerts);
|
||||||
can(ProjectPermissionActions.Create, ProjectPermissionSub.PkiAlerts);
|
can(ProjectPermissionActions.Create, ProjectPermissionSub.PkiAlerts);
|
||||||
can(ProjectPermissionActions.Edit, ProjectPermissionSub.PkiAlerts);
|
can(ProjectPermissionActions.Edit, ProjectPermissionSub.PkiAlerts);
|
||||||
@@ -251,6 +258,8 @@ const buildMemberPermissionRules = () => {
|
|||||||
can(ProjectPermissionActions.Edit, ProjectPermissionSub.Certificates);
|
can(ProjectPermissionActions.Edit, ProjectPermissionSub.Certificates);
|
||||||
can(ProjectPermissionActions.Delete, ProjectPermissionSub.Certificates);
|
can(ProjectPermissionActions.Delete, ProjectPermissionSub.Certificates);
|
||||||
|
|
||||||
|
can(ProjectPermissionActions.Read, ProjectPermissionSub.CertificateTemplates);
|
||||||
|
|
||||||
can(ProjectPermissionActions.Read, ProjectPermissionSub.PkiAlerts);
|
can(ProjectPermissionActions.Read, ProjectPermissionSub.PkiAlerts);
|
||||||
can(ProjectPermissionActions.Read, ProjectPermissionSub.PkiCollections);
|
can(ProjectPermissionActions.Read, ProjectPermissionSub.PkiCollections);
|
||||||
|
|
||||||
|
|||||||
@@ -4,17 +4,16 @@ import { logger } from "@app/lib/logger";
|
|||||||
|
|
||||||
import { TLicenseServiceFactory } from "../license/license-service";
|
import { TLicenseServiceFactory } from "../license/license-service";
|
||||||
import { TRateLimitDALFactory } from "./rate-limit-dal";
|
import { TRateLimitDALFactory } from "./rate-limit-dal";
|
||||||
import { TRateLimit, TRateLimitUpdateDTO } from "./rate-limit-types";
|
import { RateLimitConfiguration, TRateLimit, TRateLimitUpdateDTO } from "./rate-limit-types";
|
||||||
|
|
||||||
let rateLimitMaxConfiguration = {
|
let rateLimitMaxConfiguration: RateLimitConfiguration = {
|
||||||
readLimit: 60,
|
readLimit: 60,
|
||||||
publicEndpointLimit: 30,
|
publicEndpointLimit: 30,
|
||||||
writeLimit: 200,
|
writeLimit: 200,
|
||||||
secretsLimit: 60,
|
secretsLimit: 60,
|
||||||
authRateLimit: 60,
|
authRateLimit: 60,
|
||||||
inviteUserRateLimit: 30,
|
inviteUserRateLimit: 30,
|
||||||
mfaRateLimit: 20,
|
mfaRateLimit: 20
|
||||||
creationLimit: 30
|
|
||||||
};
|
};
|
||||||
|
|
||||||
Object.freeze(rateLimitMaxConfiguration);
|
Object.freeze(rateLimitMaxConfiguration);
|
||||||
@@ -67,8 +66,7 @@ export const rateLimitServiceFactory = ({ rateLimitDAL, licenseService }: TRateL
|
|||||||
secretsLimit: rateLimit.secretsRateLimit,
|
secretsLimit: rateLimit.secretsRateLimit,
|
||||||
authRateLimit: rateLimit.authRateLimit,
|
authRateLimit: rateLimit.authRateLimit,
|
||||||
inviteUserRateLimit: rateLimit.inviteUserRateLimit,
|
inviteUserRateLimit: rateLimit.inviteUserRateLimit,
|
||||||
mfaRateLimit: rateLimit.mfaRateLimit,
|
mfaRateLimit: rateLimit.mfaRateLimit
|
||||||
creationLimit: rateLimit.creationLimit
|
|
||||||
};
|
};
|
||||||
|
|
||||||
logger.info(`syncRateLimitConfiguration: rate limit configuration: %o`, newRateLimitMaxConfiguration);
|
logger.info(`syncRateLimitConfiguration: rate limit configuration: %o`, newRateLimitMaxConfiguration);
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ export type TRateLimitUpdateDTO = {
|
|||||||
authRateLimit: number;
|
authRateLimit: number;
|
||||||
inviteUserRateLimit: number;
|
inviteUserRateLimit: number;
|
||||||
mfaRateLimit: number;
|
mfaRateLimit: number;
|
||||||
creationLimit: number;
|
|
||||||
publicEndpointLimit: number;
|
publicEndpointLimit: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -14,3 +13,13 @@ export type TRateLimit = {
|
|||||||
createdAt: Date;
|
createdAt: Date;
|
||||||
updatedAt: Date;
|
updatedAt: Date;
|
||||||
} & TRateLimitUpdateDTO;
|
} & TRateLimitUpdateDTO;
|
||||||
|
|
||||||
|
export type RateLimitConfiguration = {
|
||||||
|
readLimit: number;
|
||||||
|
publicEndpointLimit: number;
|
||||||
|
writeLimit: number;
|
||||||
|
secretsLimit: number;
|
||||||
|
authRateLimit: number;
|
||||||
|
inviteUserRateLimit: number;
|
||||||
|
mfaRateLimit: number;
|
||||||
|
};
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import { removeTrailingSlash } from "@app/lib/fn";
|
|||||||
import { containsGlobPatterns } from "@app/lib/picomatch";
|
import { containsGlobPatterns } from "@app/lib/picomatch";
|
||||||
import { TProjectEnvDALFactory } from "@app/services/project-env/project-env-dal";
|
import { TProjectEnvDALFactory } from "@app/services/project-env/project-env-dal";
|
||||||
|
|
||||||
|
import { TLicenseServiceFactory } from "../license/license-service";
|
||||||
import { TSecretApprovalPolicyApproverDALFactory } from "./secret-approval-policy-approver-dal";
|
import { TSecretApprovalPolicyApproverDALFactory } from "./secret-approval-policy-approver-dal";
|
||||||
import { TSecretApprovalPolicyDALFactory } from "./secret-approval-policy-dal";
|
import { TSecretApprovalPolicyDALFactory } from "./secret-approval-policy-dal";
|
||||||
import {
|
import {
|
||||||
@@ -28,6 +29,7 @@ type TSecretApprovalPolicyServiceFactoryDep = {
|
|||||||
secretApprovalPolicyDAL: TSecretApprovalPolicyDALFactory;
|
secretApprovalPolicyDAL: TSecretApprovalPolicyDALFactory;
|
||||||
projectEnvDAL: Pick<TProjectEnvDALFactory, "findOne">;
|
projectEnvDAL: Pick<TProjectEnvDALFactory, "findOne">;
|
||||||
secretApprovalPolicyApproverDAL: TSecretApprovalPolicyApproverDALFactory;
|
secretApprovalPolicyApproverDAL: TSecretApprovalPolicyApproverDALFactory;
|
||||||
|
licenseService: Pick<TLicenseServiceFactory, "getPlan">;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TSecretApprovalPolicyServiceFactory = ReturnType<typeof secretApprovalPolicyServiceFactory>;
|
export type TSecretApprovalPolicyServiceFactory = ReturnType<typeof secretApprovalPolicyServiceFactory>;
|
||||||
@@ -36,7 +38,8 @@ export const secretApprovalPolicyServiceFactory = ({
|
|||||||
secretApprovalPolicyDAL,
|
secretApprovalPolicyDAL,
|
||||||
permissionService,
|
permissionService,
|
||||||
secretApprovalPolicyApproverDAL,
|
secretApprovalPolicyApproverDAL,
|
||||||
projectEnvDAL
|
projectEnvDAL,
|
||||||
|
licenseService
|
||||||
}: TSecretApprovalPolicyServiceFactoryDep) => {
|
}: TSecretApprovalPolicyServiceFactoryDep) => {
|
||||||
const createSecretApprovalPolicy = async ({
|
const createSecretApprovalPolicy = async ({
|
||||||
name,
|
name,
|
||||||
@@ -65,6 +68,15 @@ export const secretApprovalPolicyServiceFactory = ({
|
|||||||
ProjectPermissionActions.Create,
|
ProjectPermissionActions.Create,
|
||||||
ProjectPermissionSub.SecretApproval
|
ProjectPermissionSub.SecretApproval
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const plan = await licenseService.getPlan(actorOrgId);
|
||||||
|
if (!plan.secretApproval) {
|
||||||
|
throw new BadRequestError({
|
||||||
|
message:
|
||||||
|
"Failed to create secret approval policy due to plan restriction. Upgrade plan to create secret approval policy."
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const env = await projectEnvDAL.findOne({ slug: environment, projectId });
|
const env = await projectEnvDAL.findOne({ slug: environment, projectId });
|
||||||
if (!env) throw new BadRequestError({ message: "Environment not found" });
|
if (!env) throw new BadRequestError({ message: "Environment not found" });
|
||||||
|
|
||||||
@@ -115,6 +127,14 @@ export const secretApprovalPolicyServiceFactory = ({
|
|||||||
);
|
);
|
||||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.SecretApproval);
|
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.SecretApproval);
|
||||||
|
|
||||||
|
const plan = await licenseService.getPlan(actorOrgId);
|
||||||
|
if (!plan.secretApproval) {
|
||||||
|
throw new BadRequestError({
|
||||||
|
message:
|
||||||
|
"Failed to update secret approval policy due to plan restriction. Upgrade plan to update secret approval policy."
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const updatedSap = await secretApprovalPolicyDAL.transaction(async (tx) => {
|
const updatedSap = await secretApprovalPolicyDAL.transaction(async (tx) => {
|
||||||
const doc = await secretApprovalPolicyDAL.updateById(
|
const doc = await secretApprovalPolicyDAL.updateById(
|
||||||
secretApprovalPolicy.id,
|
secretApprovalPolicy.id,
|
||||||
@@ -167,6 +187,14 @@ export const secretApprovalPolicyServiceFactory = ({
|
|||||||
ProjectPermissionSub.SecretApproval
|
ProjectPermissionSub.SecretApproval
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const plan = await licenseService.getPlan(actorOrgId);
|
||||||
|
if (!plan.secretApproval) {
|
||||||
|
throw new BadRequestError({
|
||||||
|
message:
|
||||||
|
"Failed to update secret approval policy due to plan restriction. Upgrade plan to update secret approval policy."
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
await secretApprovalPolicyDAL.deleteById(secretPolicyId);
|
await secretApprovalPolicyDAL.deleteById(secretPolicyId);
|
||||||
return sapPolicy;
|
return sapPolicy;
|
||||||
};
|
};
|
||||||
|
|||||||
+12
-16
@@ -81,15 +81,13 @@ export const secretApprovalRequestSecretDALFactory = (db: TDbClient) => {
|
|||||||
.select({
|
.select({
|
||||||
secVerTagId: "secVerTag.id",
|
secVerTagId: "secVerTag.id",
|
||||||
secVerTagColor: "secVerTag.color",
|
secVerTagColor: "secVerTag.color",
|
||||||
secVerTagSlug: "secVerTag.slug",
|
secVerTagSlug: "secVerTag.slug"
|
||||||
secVerTagName: "secVerTag.name"
|
|
||||||
})
|
})
|
||||||
.select(
|
.select(
|
||||||
db.ref("id").withSchema(TableName.SecretTag).as("tagId"),
|
db.ref("id").withSchema(TableName.SecretTag).as("tagId"),
|
||||||
db.ref("id").withSchema(TableName.SecretApprovalRequestSecretTag).as("tagJnId"),
|
db.ref("id").withSchema(TableName.SecretApprovalRequestSecretTag).as("tagJnId"),
|
||||||
db.ref("color").withSchema(TableName.SecretTag).as("tagColor"),
|
db.ref("color").withSchema(TableName.SecretTag).as("tagColor"),
|
||||||
db.ref("slug").withSchema(TableName.SecretTag).as("tagSlug"),
|
db.ref("slug").withSchema(TableName.SecretTag).as("tagSlug")
|
||||||
db.ref("name").withSchema(TableName.SecretTag).as("tagName")
|
|
||||||
)
|
)
|
||||||
.select(
|
.select(
|
||||||
db.ref("secretBlindIndex").withSchema(TableName.Secret).as("orgSecBlindIndex"),
|
db.ref("secretBlindIndex").withSchema(TableName.Secret).as("orgSecBlindIndex"),
|
||||||
@@ -124,9 +122,9 @@ export const secretApprovalRequestSecretDALFactory = (db: TDbClient) => {
|
|||||||
{
|
{
|
||||||
key: "tagJnId",
|
key: "tagJnId",
|
||||||
label: "tags" as const,
|
label: "tags" as const,
|
||||||
mapper: ({ tagId: id, tagName: name, tagSlug: slug, tagColor: color }) => ({
|
mapper: ({ tagId: id, tagSlug: slug, tagColor: color }) => ({
|
||||||
id,
|
id,
|
||||||
name,
|
name: slug,
|
||||||
slug,
|
slug,
|
||||||
color
|
color
|
||||||
})
|
})
|
||||||
@@ -200,11 +198,11 @@ export const secretApprovalRequestSecretDALFactory = (db: TDbClient) => {
|
|||||||
{
|
{
|
||||||
key: "secVerTagId",
|
key: "secVerTagId",
|
||||||
label: "tags" as const,
|
label: "tags" as const,
|
||||||
mapper: ({ secVerTagId: id, secVerTagName: name, secVerTagSlug: slug, secVerTagColor: color }) => ({
|
mapper: ({ secVerTagId: id, secVerTagSlug: slug, secVerTagColor: color }) => ({
|
||||||
// eslint-disable-next-line
|
// eslint-disable-next-line
|
||||||
id,
|
id,
|
||||||
// eslint-disable-next-line
|
// eslint-disable-next-line
|
||||||
name,
|
name: slug,
|
||||||
// eslint-disable-next-line
|
// eslint-disable-next-line
|
||||||
slug,
|
slug,
|
||||||
// eslint-disable-next-line
|
// eslint-disable-next-line
|
||||||
@@ -262,15 +260,13 @@ export const secretApprovalRequestSecretDALFactory = (db: TDbClient) => {
|
|||||||
.select({
|
.select({
|
||||||
secVerTagId: "secVerTag.id",
|
secVerTagId: "secVerTag.id",
|
||||||
secVerTagColor: "secVerTag.color",
|
secVerTagColor: "secVerTag.color",
|
||||||
secVerTagSlug: "secVerTag.slug",
|
secVerTagSlug: "secVerTag.slug"
|
||||||
secVerTagName: "secVerTag.name"
|
|
||||||
})
|
})
|
||||||
.select(
|
.select(
|
||||||
db.ref("id").withSchema(TableName.SecretTag).as("tagId"),
|
db.ref("id").withSchema(TableName.SecretTag).as("tagId"),
|
||||||
db.ref("id").withSchema(TableName.SecretApprovalRequestSecretTagV2).as("tagJnId"),
|
db.ref("id").withSchema(TableName.SecretApprovalRequestSecretTagV2).as("tagJnId"),
|
||||||
db.ref("color").withSchema(TableName.SecretTag).as("tagColor"),
|
db.ref("color").withSchema(TableName.SecretTag).as("tagColor"),
|
||||||
db.ref("slug").withSchema(TableName.SecretTag).as("tagSlug"),
|
db.ref("slug").withSchema(TableName.SecretTag).as("tagSlug")
|
||||||
db.ref("name").withSchema(TableName.SecretTag).as("tagName")
|
|
||||||
)
|
)
|
||||||
.select(
|
.select(
|
||||||
db.ref("version").withSchema(TableName.SecretV2).as("orgSecVersion"),
|
db.ref("version").withSchema(TableName.SecretV2).as("orgSecVersion"),
|
||||||
@@ -292,9 +288,9 @@ export const secretApprovalRequestSecretDALFactory = (db: TDbClient) => {
|
|||||||
{
|
{
|
||||||
key: "tagJnId",
|
key: "tagJnId",
|
||||||
label: "tags" as const,
|
label: "tags" as const,
|
||||||
mapper: ({ tagId: id, tagName: name, tagSlug: slug, tagColor: color }) => ({
|
mapper: ({ tagId: id, tagSlug: slug, tagColor: color }) => ({
|
||||||
id,
|
id,
|
||||||
name,
|
name: slug,
|
||||||
slug,
|
slug,
|
||||||
color
|
color
|
||||||
})
|
})
|
||||||
@@ -330,11 +326,11 @@ export const secretApprovalRequestSecretDALFactory = (db: TDbClient) => {
|
|||||||
{
|
{
|
||||||
key: "secVerTagId",
|
key: "secVerTagId",
|
||||||
label: "tags" as const,
|
label: "tags" as const,
|
||||||
mapper: ({ secVerTagId: id, secVerTagName: name, secVerTagSlug: slug, secVerTagColor: color }) => ({
|
mapper: ({ secVerTagId: id, secVerTagSlug: slug, secVerTagColor: color }) => ({
|
||||||
// eslint-disable-next-line
|
// eslint-disable-next-line
|
||||||
id,
|
id,
|
||||||
// eslint-disable-next-line
|
// eslint-disable-next-line
|
||||||
name,
|
name: slug,
|
||||||
// eslint-disable-next-line
|
// eslint-disable-next-line
|
||||||
slug,
|
slug,
|
||||||
// eslint-disable-next-line
|
// eslint-disable-next-line
|
||||||
|
|||||||
@@ -50,6 +50,7 @@ import { TSecretVersionV2TagDALFactory } from "@app/services/secret-v2-bridge/se
|
|||||||
import { SmtpTemplates, TSmtpService } from "@app/services/smtp/smtp-service";
|
import { SmtpTemplates, TSmtpService } from "@app/services/smtp/smtp-service";
|
||||||
import { TUserDALFactory } from "@app/services/user/user-dal";
|
import { TUserDALFactory } from "@app/services/user/user-dal";
|
||||||
|
|
||||||
|
import { TLicenseServiceFactory } from "../license/license-service";
|
||||||
import { TPermissionServiceFactory } from "../permission/permission-service";
|
import { TPermissionServiceFactory } from "../permission/permission-service";
|
||||||
import { ProjectPermissionActions, ProjectPermissionSub } from "../permission/project-permission";
|
import { ProjectPermissionActions, ProjectPermissionSub } from "../permission/project-permission";
|
||||||
import { TSecretSnapshotServiceFactory } from "../secret-snapshot/secret-snapshot-service";
|
import { TSecretSnapshotServiceFactory } from "../secret-snapshot/secret-snapshot-service";
|
||||||
@@ -97,6 +98,7 @@ type TSecretApprovalRequestServiceFactoryDep = {
|
|||||||
>;
|
>;
|
||||||
secretVersionV2BridgeDAL: Pick<TSecretVersionV2DALFactory, "insertMany" | "findLatestVersionMany">;
|
secretVersionV2BridgeDAL: Pick<TSecretVersionV2DALFactory, "insertMany" | "findLatestVersionMany">;
|
||||||
secretVersionTagV2BridgeDAL: Pick<TSecretVersionV2TagDALFactory, "insertMany">;
|
secretVersionTagV2BridgeDAL: Pick<TSecretVersionV2TagDALFactory, "insertMany">;
|
||||||
|
licenseService: Pick<TLicenseServiceFactory, "getPlan">;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TSecretApprovalRequestServiceFactory = ReturnType<typeof secretApprovalRequestServiceFactory>;
|
export type TSecretApprovalRequestServiceFactory = ReturnType<typeof secretApprovalRequestServiceFactory>;
|
||||||
@@ -122,7 +124,8 @@ export const secretApprovalRequestServiceFactory = ({
|
|||||||
kmsService,
|
kmsService,
|
||||||
secretV2BridgeDAL,
|
secretV2BridgeDAL,
|
||||||
secretVersionV2BridgeDAL,
|
secretVersionV2BridgeDAL,
|
||||||
secretVersionTagV2BridgeDAL
|
secretVersionTagV2BridgeDAL,
|
||||||
|
licenseService
|
||||||
}: TSecretApprovalRequestServiceFactoryDep) => {
|
}: TSecretApprovalRequestServiceFactoryDep) => {
|
||||||
const requestCount = async ({ projectId, actor, actorId, actorOrgId, actorAuthMethod }: TApprovalRequestCountDTO) => {
|
const requestCount = async ({ projectId, actor, actorId, actorOrgId, actorAuthMethod }: TApprovalRequestCountDTO) => {
|
||||||
if (actor === ActorType.SERVICE) throw new BadRequestError({ message: "Cannot use service token" });
|
if (actor === ActorType.SERVICE) throw new BadRequestError({ message: "Cannot use service token" });
|
||||||
@@ -224,12 +227,10 @@ export const secretApprovalRequestServiceFactory = ({
|
|||||||
secretKey: el.key,
|
secretKey: el.key,
|
||||||
id: el.id,
|
id: el.id,
|
||||||
version: el.version,
|
version: el.version,
|
||||||
secretValue: el.encryptedValue
|
secretValue: el.encryptedValue ? secretManagerDecryptor({ cipherTextBlob: el.encryptedValue }).toString() : "",
|
||||||
? secretManagerDecryptor({ cipherTextBlob: el.encryptedValue }).toString()
|
|
||||||
: undefined,
|
|
||||||
secretComment: el.encryptedComment
|
secretComment: el.encryptedComment
|
||||||
? secretManagerDecryptor({ cipherTextBlob: el.encryptedComment }).toString()
|
? secretManagerDecryptor({ cipherTextBlob: el.encryptedComment }).toString()
|
||||||
: undefined,
|
: "",
|
||||||
secret: el.secret
|
secret: el.secret
|
||||||
? {
|
? {
|
||||||
secretKey: el.secret.key,
|
secretKey: el.secret.key,
|
||||||
@@ -237,10 +238,10 @@ export const secretApprovalRequestServiceFactory = ({
|
|||||||
version: el.secret.version,
|
version: el.secret.version,
|
||||||
secretValue: el.secret.encryptedValue
|
secretValue: el.secret.encryptedValue
|
||||||
? secretManagerDecryptor({ cipherTextBlob: el.secret.encryptedValue }).toString()
|
? secretManagerDecryptor({ cipherTextBlob: el.secret.encryptedValue }).toString()
|
||||||
: undefined,
|
: "",
|
||||||
secretComment: el.secret.encryptedComment
|
secretComment: el.secret.encryptedComment
|
||||||
? secretManagerDecryptor({ cipherTextBlob: el.secret.encryptedComment }).toString()
|
? secretManagerDecryptor({ cipherTextBlob: el.secret.encryptedComment }).toString()
|
||||||
: undefined
|
: ""
|
||||||
}
|
}
|
||||||
: undefined,
|
: undefined,
|
||||||
secretVersion: el.secretVersion
|
secretVersion: el.secretVersion
|
||||||
@@ -250,10 +251,10 @@ export const secretApprovalRequestServiceFactory = ({
|
|||||||
version: el.secretVersion.version,
|
version: el.secretVersion.version,
|
||||||
secretValue: el.secretVersion.encryptedValue
|
secretValue: el.secretVersion.encryptedValue
|
||||||
? secretManagerDecryptor({ cipherTextBlob: el.secretVersion.encryptedValue }).toString()
|
? secretManagerDecryptor({ cipherTextBlob: el.secretVersion.encryptedValue }).toString()
|
||||||
: undefined,
|
: "",
|
||||||
secretComment: el.secretVersion.encryptedComment
|
secretComment: el.secretVersion.encryptedComment
|
||||||
? secretManagerDecryptor({ cipherTextBlob: el.secretVersion.encryptedComment }).toString()
|
? secretManagerDecryptor({ cipherTextBlob: el.secretVersion.encryptedComment }).toString()
|
||||||
: undefined
|
: ""
|
||||||
}
|
}
|
||||||
: undefined
|
: undefined
|
||||||
}));
|
}));
|
||||||
@@ -297,6 +298,14 @@ export const secretApprovalRequestServiceFactory = ({
|
|||||||
if (!secretApprovalRequest) throw new BadRequestError({ message: "Secret approval request not found" });
|
if (!secretApprovalRequest) throw new BadRequestError({ message: "Secret approval request not found" });
|
||||||
if (actor !== ActorType.USER) throw new BadRequestError({ message: "Must be a user" });
|
if (actor !== ActorType.USER) throw new BadRequestError({ message: "Must be a user" });
|
||||||
|
|
||||||
|
const plan = await licenseService.getPlan(actorOrgId);
|
||||||
|
if (!plan.secretApproval) {
|
||||||
|
throw new BadRequestError({
|
||||||
|
message:
|
||||||
|
"Failed to review secret approval request due to plan restriction. Upgrade plan to review secret approval request."
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const { policy } = secretApprovalRequest;
|
const { policy } = secretApprovalRequest;
|
||||||
const { hasRole } = await permissionService.getProjectPermission(
|
const { hasRole } = await permissionService.getProjectPermission(
|
||||||
ActorType.USER,
|
ActorType.USER,
|
||||||
@@ -347,6 +356,14 @@ export const secretApprovalRequestServiceFactory = ({
|
|||||||
if (!secretApprovalRequest) throw new BadRequestError({ message: "Secret approval request not found" });
|
if (!secretApprovalRequest) throw new BadRequestError({ message: "Secret approval request not found" });
|
||||||
if (actor !== ActorType.USER) throw new BadRequestError({ message: "Must be a user" });
|
if (actor !== ActorType.USER) throw new BadRequestError({ message: "Must be a user" });
|
||||||
|
|
||||||
|
const plan = await licenseService.getPlan(actorOrgId);
|
||||||
|
if (!plan.secretApproval) {
|
||||||
|
throw new BadRequestError({
|
||||||
|
message:
|
||||||
|
"Failed to update secret approval request due to plan restriction. Upgrade plan to update secret approval request."
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const { policy } = secretApprovalRequest;
|
const { policy } = secretApprovalRequest;
|
||||||
const { hasRole } = await permissionService.getProjectPermission(
|
const { hasRole } = await permissionService.getProjectPermission(
|
||||||
ActorType.USER,
|
ActorType.USER,
|
||||||
@@ -388,6 +405,14 @@ export const secretApprovalRequestServiceFactory = ({
|
|||||||
if (!secretApprovalRequest) throw new BadRequestError({ message: "Secret approval request not found" });
|
if (!secretApprovalRequest) throw new BadRequestError({ message: "Secret approval request not found" });
|
||||||
if (actor !== ActorType.USER) throw new BadRequestError({ message: "Must be a user" });
|
if (actor !== ActorType.USER) throw new BadRequestError({ message: "Must be a user" });
|
||||||
|
|
||||||
|
const plan = await licenseService.getPlan(actorOrgId);
|
||||||
|
if (!plan.secretApproval) {
|
||||||
|
throw new BadRequestError({
|
||||||
|
message:
|
||||||
|
"Failed to merge secret approval request due to plan restriction. Upgrade plan to merge secret approval request."
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const { policy, folderId, projectId } = secretApprovalRequest;
|
const { policy, folderId, projectId } = secretApprovalRequest;
|
||||||
const { hasRole } = await permissionService.getProjectPermission(
|
const { hasRole } = await permissionService.getProjectPermission(
|
||||||
ActorType.USER,
|
ActorType.USER,
|
||||||
|
|||||||
@@ -257,7 +257,7 @@ export const secretReplicationServiceFactory = ({
|
|||||||
secretDAL: secretV2BridgeDAL,
|
secretDAL: secretV2BridgeDAL,
|
||||||
folderDAL,
|
folderDAL,
|
||||||
secretImportDAL,
|
secretImportDAL,
|
||||||
decryptor: (value) => (value ? secretManagerDecryptor({ cipherTextBlob: value }).toString() : undefined)
|
decryptor: (value) => (value ? secretManagerDecryptor({ cipherTextBlob: value }).toString() : "")
|
||||||
});
|
});
|
||||||
// secrets that gets replicated across imports
|
// secrets that gets replicated across imports
|
||||||
const sourceDecryptedLocalSecrets = sourceLocalSecrets.map((el) => ({
|
const sourceDecryptedLocalSecrets = sourceLocalSecrets.map((el) => ({
|
||||||
@@ -449,7 +449,7 @@ export const secretReplicationServiceFactory = ({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (locallyDeletedSecrets.length) {
|
if (locallyDeletedSecrets.length) {
|
||||||
await secretDAL.delete(
|
await secretV2BridgeDAL.delete(
|
||||||
{
|
{
|
||||||
$in: {
|
$in: {
|
||||||
id: locallyDeletedSecrets.map(({ id }) => id)
|
id: locallyDeletedSecrets.map(({ id }) => id)
|
||||||
|
|||||||
@@ -164,10 +164,10 @@ export const secretSnapshotServiceFactory = ({
|
|||||||
secretKey: el.key,
|
secretKey: el.key,
|
||||||
secretValue: el.encryptedValue
|
secretValue: el.encryptedValue
|
||||||
? secretManagerDecryptor({ cipherTextBlob: el.encryptedValue }).toString()
|
? secretManagerDecryptor({ cipherTextBlob: el.encryptedValue }).toString()
|
||||||
: undefined,
|
: "",
|
||||||
secretComment: el.encryptedComment
|
secretComment: el.encryptedComment
|
||||||
? secretManagerDecryptor({ cipherTextBlob: el.encryptedComment }).toString()
|
? secretManagerDecryptor({ cipherTextBlob: el.encryptedComment }).toString()
|
||||||
: undefined
|
: ""
|
||||||
}))
|
}))
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -100,8 +100,7 @@ export const snapshotDALFactory = (db: TDbClient) => {
|
|||||||
db.ref("id").withSchema(TableName.SecretTag).as("tagId"),
|
db.ref("id").withSchema(TableName.SecretTag).as("tagId"),
|
||||||
db.ref("id").withSchema(TableName.SecretVersionTag).as("tagVersionId"),
|
db.ref("id").withSchema(TableName.SecretVersionTag).as("tagVersionId"),
|
||||||
db.ref("color").withSchema(TableName.SecretTag).as("tagColor"),
|
db.ref("color").withSchema(TableName.SecretTag).as("tagColor"),
|
||||||
db.ref("slug").withSchema(TableName.SecretTag).as("tagSlug"),
|
db.ref("slug").withSchema(TableName.SecretTag).as("tagSlug")
|
||||||
db.ref("name").withSchema(TableName.SecretTag).as("tagName")
|
|
||||||
);
|
);
|
||||||
return sqlNestRelationships({
|
return sqlNestRelationships({
|
||||||
data,
|
data,
|
||||||
@@ -132,9 +131,9 @@ export const snapshotDALFactory = (db: TDbClient) => {
|
|||||||
{
|
{
|
||||||
key: "tagVersionId",
|
key: "tagVersionId",
|
||||||
label: "tags" as const,
|
label: "tags" as const,
|
||||||
mapper: ({ tagId: id, tagName: name, tagSlug: slug, tagColor: color, tagVersionId: vId }) => ({
|
mapper: ({ tagId: id, tagSlug: slug, tagColor: color, tagVersionId: vId }) => ({
|
||||||
id,
|
id,
|
||||||
name,
|
name: slug,
|
||||||
slug,
|
slug,
|
||||||
color,
|
color,
|
||||||
vId
|
vId
|
||||||
@@ -195,8 +194,7 @@ export const snapshotDALFactory = (db: TDbClient) => {
|
|||||||
db.ref("id").withSchema(TableName.SecretTag).as("tagId"),
|
db.ref("id").withSchema(TableName.SecretTag).as("tagId"),
|
||||||
db.ref("id").withSchema(TableName.SecretVersionV2Tag).as("tagVersionId"),
|
db.ref("id").withSchema(TableName.SecretVersionV2Tag).as("tagVersionId"),
|
||||||
db.ref("color").withSchema(TableName.SecretTag).as("tagColor"),
|
db.ref("color").withSchema(TableName.SecretTag).as("tagColor"),
|
||||||
db.ref("slug").withSchema(TableName.SecretTag).as("tagSlug"),
|
db.ref("slug").withSchema(TableName.SecretTag).as("tagSlug")
|
||||||
db.ref("name").withSchema(TableName.SecretTag).as("tagName")
|
|
||||||
);
|
);
|
||||||
return sqlNestRelationships({
|
return sqlNestRelationships({
|
||||||
data,
|
data,
|
||||||
@@ -227,9 +225,9 @@ export const snapshotDALFactory = (db: TDbClient) => {
|
|||||||
{
|
{
|
||||||
key: "tagVersionId",
|
key: "tagVersionId",
|
||||||
label: "tags" as const,
|
label: "tags" as const,
|
||||||
mapper: ({ tagId: id, tagName: name, tagSlug: slug, tagColor: color, tagVersionId: vId }) => ({
|
mapper: ({ tagId: id, tagSlug: slug, tagColor: color, tagVersionId: vId }) => ({
|
||||||
id,
|
id,
|
||||||
name,
|
name: slug,
|
||||||
slug,
|
slug,
|
||||||
color,
|
color,
|
||||||
vId
|
vId
|
||||||
@@ -353,8 +351,7 @@ export const snapshotDALFactory = (db: TDbClient) => {
|
|||||||
db.ref("id").withSchema(TableName.SecretTag).as("tagId"),
|
db.ref("id").withSchema(TableName.SecretTag).as("tagId"),
|
||||||
db.ref("id").withSchema(TableName.SecretVersionTag).as("tagVersionId"),
|
db.ref("id").withSchema(TableName.SecretVersionTag).as("tagVersionId"),
|
||||||
db.ref("color").withSchema(TableName.SecretTag).as("tagColor"),
|
db.ref("color").withSchema(TableName.SecretTag).as("tagColor"),
|
||||||
db.ref("slug").withSchema(TableName.SecretTag).as("tagSlug"),
|
db.ref("slug").withSchema(TableName.SecretTag).as("tagSlug")
|
||||||
db.ref("name").withSchema(TableName.SecretTag).as("tagName")
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const formated = sqlNestRelationships({
|
const formated = sqlNestRelationships({
|
||||||
@@ -377,9 +374,9 @@ export const snapshotDALFactory = (db: TDbClient) => {
|
|||||||
{
|
{
|
||||||
key: "tagVersionId",
|
key: "tagVersionId",
|
||||||
label: "tags" as const,
|
label: "tags" as const,
|
||||||
mapper: ({ tagId: id, tagName: name, tagSlug: slug, tagColor: color, tagVersionId: vId }) => ({
|
mapper: ({ tagId: id, tagSlug: slug, tagColor: color, tagVersionId: vId }) => ({
|
||||||
id,
|
id,
|
||||||
name,
|
name: slug,
|
||||||
slug,
|
slug,
|
||||||
color,
|
color,
|
||||||
vId
|
vId
|
||||||
@@ -508,8 +505,7 @@ export const snapshotDALFactory = (db: TDbClient) => {
|
|||||||
db.ref("id").withSchema(TableName.SecretTag).as("tagId"),
|
db.ref("id").withSchema(TableName.SecretTag).as("tagId"),
|
||||||
db.ref("id").withSchema(TableName.SecretVersionV2Tag).as("tagVersionId"),
|
db.ref("id").withSchema(TableName.SecretVersionV2Tag).as("tagVersionId"),
|
||||||
db.ref("color").withSchema(TableName.SecretTag).as("tagColor"),
|
db.ref("color").withSchema(TableName.SecretTag).as("tagColor"),
|
||||||
db.ref("slug").withSchema(TableName.SecretTag).as("tagSlug"),
|
db.ref("slug").withSchema(TableName.SecretTag).as("tagSlug")
|
||||||
db.ref("name").withSchema(TableName.SecretTag).as("tagName")
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const formated = sqlNestRelationships({
|
const formated = sqlNestRelationships({
|
||||||
@@ -532,9 +528,9 @@ export const snapshotDALFactory = (db: TDbClient) => {
|
|||||||
{
|
{
|
||||||
key: "tagVersionId",
|
key: "tagVersionId",
|
||||||
label: "tags" as const,
|
label: "tags" as const,
|
||||||
mapper: ({ tagId: id, tagName: name, tagSlug: slug, tagColor: color, tagVersionId: vId }) => ({
|
mapper: ({ tagId: id, tagSlug: slug, tagColor: color, tagVersionId: vId }) => ({
|
||||||
id,
|
id,
|
||||||
name,
|
name: slug,
|
||||||
slug,
|
slug,
|
||||||
color,
|
color,
|
||||||
vId
|
vId
|
||||||
|
|||||||
@@ -5,17 +5,26 @@ import { Redlock, Settings } from "@app/lib/red-lock";
|
|||||||
export type TKeyStoreFactory = ReturnType<typeof keyStoreFactory>;
|
export type TKeyStoreFactory = ReturnType<typeof keyStoreFactory>;
|
||||||
|
|
||||||
// all the key prefixes used must be set here to avoid conflict
|
// all the key prefixes used must be set here to avoid conflict
|
||||||
export enum KeyStorePrefixes {
|
export const KeyStorePrefixes = {
|
||||||
SecretReplication = "secret-replication-import-lock",
|
SecretReplication: "secret-replication-import-lock",
|
||||||
KmsProjectDataKeyCreation = "kms-project-data-key-creation-lock",
|
KmsProjectDataKeyCreation: "kms-project-data-key-creation-lock",
|
||||||
KmsProjectKeyCreation = "kms-project-key-creation-lock",
|
KmsProjectKeyCreation: "kms-project-key-creation-lock",
|
||||||
WaitUntilReadyKmsProjectDataKeyCreation = "wait-until-ready-kms-project-data-key-creation-",
|
WaitUntilReadyKmsProjectDataKeyCreation: "wait-until-ready-kms-project-data-key-creation-",
|
||||||
WaitUntilReadyKmsProjectKeyCreation = "wait-until-ready-kms-project-key-creation-",
|
WaitUntilReadyKmsProjectKeyCreation: "wait-until-ready-kms-project-key-creation-",
|
||||||
KmsOrgKeyCreation = "kms-org-key-creation-lock",
|
KmsOrgKeyCreation: "kms-org-key-creation-lock",
|
||||||
KmsOrgDataKeyCreation = "kms-org-data-key-creation-lock",
|
KmsOrgDataKeyCreation: "kms-org-data-key-creation-lock",
|
||||||
WaitUntilReadyKmsOrgKeyCreation = "wait-until-ready-kms-org-key-creation-",
|
WaitUntilReadyKmsOrgKeyCreation: "wait-until-ready-kms-org-key-creation-",
|
||||||
WaitUntilReadyKmsOrgDataKeyCreation = "wait-until-ready-kms-org-data-key-creation-"
|
WaitUntilReadyKmsOrgDataKeyCreation: "wait-until-ready-kms-org-data-key-creation-",
|
||||||
}
|
|
||||||
|
SyncSecretIntegrationLock: (projectId: string, environmentSlug: string, secretPath: string) =>
|
||||||
|
`sync-integration-mutex-${projectId}-${environmentSlug}-${secretPath}` as const,
|
||||||
|
SyncSecretIntegrationLastRunTimestamp: (projectId: string, environmentSlug: string, secretPath: string) =>
|
||||||
|
`sync-integration-last-run-${projectId}-${environmentSlug}-${secretPath}` as const
|
||||||
|
};
|
||||||
|
|
||||||
|
export const KeyStoreTtls = {
|
||||||
|
SetSyncSecretIntegrationLastRunTimestampInSeconds: 10
|
||||||
|
};
|
||||||
|
|
||||||
type TWaitTillReady = {
|
type TWaitTillReady = {
|
||||||
key: string;
|
key: string;
|
||||||
@@ -37,10 +46,10 @@ export const keyStoreFactory = (redisUrl: string) => {
|
|||||||
|
|
||||||
const setItemWithExpiry = async (
|
const setItemWithExpiry = async (
|
||||||
key: string,
|
key: string,
|
||||||
exp: number | string,
|
expiryInSeconds: number | string,
|
||||||
value: string | number | Buffer,
|
value: string | number | Buffer,
|
||||||
prefix?: string
|
prefix?: string
|
||||||
) => redis.set(prefix ? `${prefix}:${key}` : key, value, "EX", exp);
|
) => redis.set(prefix ? `${prefix}:${key}` : key, value, "EX", expiryInSeconds);
|
||||||
|
|
||||||
const deleteItem = async (key: string) => redis.del(key);
|
const deleteItem = async (key: string) => redis.del(key);
|
||||||
|
|
||||||
|
|||||||
@@ -596,7 +596,8 @@ export const RAW_SECRETS = {
|
|||||||
"The slug of the project to list secrets from. This parameter is only applicable by machine identities.",
|
"The slug of the project to list secrets from. This parameter is only applicable by machine identities.",
|
||||||
environment: "The slug of the environment to list secrets from.",
|
environment: "The slug of the environment to list secrets from.",
|
||||||
secretPath: "The secret path to list secrets from.",
|
secretPath: "The secret path to list secrets from.",
|
||||||
includeImports: "Weather to include imported secrets or not."
|
includeImports: "Weather to include imported secrets or not.",
|
||||||
|
tagSlugs: "The comma separated tag slugs to filter secrets"
|
||||||
},
|
},
|
||||||
CREATE: {
|
CREATE: {
|
||||||
secretName: "The name of the secret to create.",
|
secretName: "The name of the secret to create.",
|
||||||
@@ -1088,6 +1089,8 @@ export const CERTIFICATE_AUTHORITIES = {
|
|||||||
},
|
},
|
||||||
ISSUE_CERT: {
|
ISSUE_CERT: {
|
||||||
caId: "The ID of the CA to issue the certificate from",
|
caId: "The ID of the CA to issue the certificate from",
|
||||||
|
certificateTemplateId: "The ID of the certificate template to issue the certificate from",
|
||||||
|
pkiCollectionId: "The ID of the PKI collection to add the certificate to",
|
||||||
friendlyName: "A friendly name for the certificate",
|
friendlyName: "A friendly name for the certificate",
|
||||||
commonName: "The common name (CN) for the certificate",
|
commonName: "The common name (CN) for the certificate",
|
||||||
altNames:
|
altNames:
|
||||||
@@ -1103,6 +1106,7 @@ export const CERTIFICATE_AUTHORITIES = {
|
|||||||
},
|
},
|
||||||
SIGN_CERT: {
|
SIGN_CERT: {
|
||||||
caId: "The ID of the CA to issue the certificate from",
|
caId: "The ID of the CA to issue the certificate from",
|
||||||
|
pkiCollectionId: "The ID of the PKI collection to add the certificate to",
|
||||||
csr: "The pem-encoded CSR to sign with the CA to be used for certificate issuance",
|
csr: "The pem-encoded CSR to sign with the CA to be used for certificate issuance",
|
||||||
friendlyName: "A friendly name for the certificate",
|
friendlyName: "A friendly name for the certificate",
|
||||||
commonName: "The common name (CN) for the certificate",
|
commonName: "The common name (CN) for the certificate",
|
||||||
@@ -1144,6 +1148,32 @@ export const CERTIFICATES = {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const CERTIFICATE_TEMPLATES = {
|
||||||
|
CREATE: {
|
||||||
|
caId: "The ID of the certificate authority to associate the template with",
|
||||||
|
pkiCollectionId: "The ID of the PKI collection to bind to the template",
|
||||||
|
name: "The name of the template",
|
||||||
|
commonName: "The regular expression string to use for validating common names",
|
||||||
|
subjectAlternativeName: "The regular expression string to use for validating subject alternative names",
|
||||||
|
ttl: "The max TTL for the template"
|
||||||
|
},
|
||||||
|
GET: {
|
||||||
|
certificateTemplateId: "The ID of the certificate template to get"
|
||||||
|
},
|
||||||
|
UPDATE: {
|
||||||
|
certificateTemplateId: "The ID of the certificate template to update",
|
||||||
|
caId: "The ID of the certificate authority to update the association with the template",
|
||||||
|
pkiCollectionId: "The ID of the PKI collection to update the binding to the template",
|
||||||
|
name: "The updated name of the template",
|
||||||
|
commonName: "The updated regular expression string for validating common names",
|
||||||
|
subjectAlternativeName: "The updated regular expression string for validating subject alternative names",
|
||||||
|
ttl: "The updated max TTL for the template"
|
||||||
|
},
|
||||||
|
DELETE: {
|
||||||
|
certificateTemplateId: "The ID of the certificate template to delete"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
export const ALERTS = {
|
export const ALERTS = {
|
||||||
CREATE: {
|
CREATE: {
|
||||||
projectId: "The ID of the project to create the alert in",
|
projectId: "The ID of the project to create the alert in",
|
||||||
@@ -1170,14 +1200,16 @@ export const ALERTS = {
|
|||||||
export const PKI_COLLECTIONS = {
|
export const PKI_COLLECTIONS = {
|
||||||
CREATE: {
|
CREATE: {
|
||||||
projectId: "The ID of the project to create the PKI collection in",
|
projectId: "The ID of the project to create the PKI collection in",
|
||||||
name: "The name of the PKI collection"
|
name: "The name of the PKI collection",
|
||||||
|
description: "A description for the PKI collection"
|
||||||
},
|
},
|
||||||
GET: {
|
GET: {
|
||||||
collectionId: "The ID of the PKI collection to get"
|
collectionId: "The ID of the PKI collection to get"
|
||||||
},
|
},
|
||||||
UPDATE: {
|
UPDATE: {
|
||||||
collectionId: "The ID of the PKI collection to update",
|
collectionId: "The ID of the PKI collection to update",
|
||||||
name: "The name of the PKI collection to update to"
|
name: "The name of the PKI collection to update to",
|
||||||
|
description: "The description for the PKI collection to update to"
|
||||||
},
|
},
|
||||||
DELETE: {
|
DELETE: {
|
||||||
collectionId: "The ID of the PKI collection to delete"
|
collectionId: "The ID of the PKI collection to delete"
|
||||||
|
|||||||
@@ -140,7 +140,8 @@ const envSchema = z
|
|||||||
MAINTENANCE_MODE: zodStrBool.default("false"),
|
MAINTENANCE_MODE: zodStrBool.default("false"),
|
||||||
CAPTCHA_SECRET: zpStr(z.string().optional()),
|
CAPTCHA_SECRET: zpStr(z.string().optional()),
|
||||||
PLAIN_API_KEY: zpStr(z.string().optional()),
|
PLAIN_API_KEY: zpStr(z.string().optional()),
|
||||||
PLAIN_WISH_LABEL_IDS: zpStr(z.string().optional())
|
PLAIN_WISH_LABEL_IDS: zpStr(z.string().optional()),
|
||||||
|
DISABLE_AUDIT_LOG_GENERATION: zodStrBool.default("false")
|
||||||
})
|
})
|
||||||
.transform((data) => ({
|
.transform((data) => ({
|
||||||
...data,
|
...data,
|
||||||
|
|||||||
@@ -1,2 +1,8 @@
|
|||||||
export const getLastMidnightDateISO = (last = 1) =>
|
export const getLastMidnightDateISO = (last = 1) =>
|
||||||
`${new Date(new Date().setDate(new Date().getDate() - last)).toISOString().slice(0, 10)}T00:00:00Z`;
|
`${new Date(new Date().setDate(new Date().getDate() - last)).toISOString().slice(0, 10)}T00:00:00Z`;
|
||||||
|
|
||||||
|
export const getTimeDifferenceInSeconds = (lhsTimestamp: string, rhsTimestamp: string) => {
|
||||||
|
const lhs = new Date(lhsTimestamp);
|
||||||
|
const rhs = new Date(rhsTimestamp);
|
||||||
|
return Math.floor((Number(lhs) - Number(rhs)) / 1000);
|
||||||
|
};
|
||||||
|
|||||||
@@ -128,6 +128,16 @@ export const ormify = <DbOps extends object, Tname extends keyof Tables>(db: Kne
|
|||||||
throw new DatabaseError({ error, name: "Create" });
|
throw new DatabaseError({ error, name: "Create" });
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
// This spilit the insert into multiple chunk
|
||||||
|
batchInsert: async (data: readonly Tables[Tname]["insert"][], tx?: Knex) => {
|
||||||
|
try {
|
||||||
|
if (!data.length) return [];
|
||||||
|
const res = await (tx || db).batchInsert(tableName, data as never).returning("*");
|
||||||
|
return res as Tables[Tname]["base"][];
|
||||||
|
} catch (error) {
|
||||||
|
throw new DatabaseError({ error, name: "batchInsert" });
|
||||||
|
}
|
||||||
|
},
|
||||||
upsert: async (data: readonly Tables[Tname]["insert"][], onConflictField: keyof Tables[Tname]["base"], tx?: Knex) => {
|
upsert: async (data: readonly Tables[Tname]["insert"][], onConflictField: keyof Tables[Tname]["base"], tx?: Knex) => {
|
||||||
try {
|
try {
|
||||||
if (!data.length) return [];
|
if (!data.length) return [];
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ export enum QueueName {
|
|||||||
// TODO(akhilmhdh): This will get removed later. For now this is kept to stop the repeatable queue
|
// TODO(akhilmhdh): This will get removed later. For now this is kept to stop the repeatable queue
|
||||||
AuditLogPrune = "audit-log-prune",
|
AuditLogPrune = "audit-log-prune",
|
||||||
DailyResourceCleanUp = "daily-resource-cleanup",
|
DailyResourceCleanUp = "daily-resource-cleanup",
|
||||||
|
DailyExpiringPkiItemAlert = "daily-expiring-pki-item-alert",
|
||||||
TelemetryInstanceStats = "telemtry-self-hosted-stats",
|
TelemetryInstanceStats = "telemtry-self-hosted-stats",
|
||||||
IntegrationSync = "sync-integrations",
|
IntegrationSync = "sync-integrations",
|
||||||
SecretWebhook = "secret-webhook",
|
SecretWebhook = "secret-webhook",
|
||||||
@@ -36,6 +37,7 @@ export enum QueueJobs {
|
|||||||
// TODO(akhilmhdh): This will get removed later. For now this is kept to stop the repeatable queue
|
// TODO(akhilmhdh): This will get removed later. For now this is kept to stop the repeatable queue
|
||||||
AuditLogPrune = "audit-log-prune-job",
|
AuditLogPrune = "audit-log-prune-job",
|
||||||
DailyResourceCleanUp = "daily-resource-cleanup-job",
|
DailyResourceCleanUp = "daily-resource-cleanup-job",
|
||||||
|
DailyExpiringPkiItemAlert = "daily-expiring-pki-item-alert",
|
||||||
SecWebhook = "secret-webhook-trigger",
|
SecWebhook = "secret-webhook-trigger",
|
||||||
TelemetryInstanceStats = "telemetry-self-hosted-stats",
|
TelemetryInstanceStats = "telemetry-self-hosted-stats",
|
||||||
IntegrationSync = "secret-integration-pull",
|
IntegrationSync = "secret-integration-pull",
|
||||||
@@ -71,6 +73,10 @@ export type TQueueJobTypes = {
|
|||||||
name: QueueJobs.DailyResourceCleanUp;
|
name: QueueJobs.DailyResourceCleanUp;
|
||||||
payload: undefined;
|
payload: undefined;
|
||||||
};
|
};
|
||||||
|
[QueueName.DailyExpiringPkiItemAlert]: {
|
||||||
|
name: QueueJobs.DailyExpiringPkiItemAlert;
|
||||||
|
payload: undefined;
|
||||||
|
};
|
||||||
[QueueName.AuditLogPrune]: {
|
[QueueName.AuditLogPrune]: {
|
||||||
name: QueueJobs.AuditLogPrune;
|
name: QueueJobs.AuditLogPrune;
|
||||||
payload: undefined;
|
payload: undefined;
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import type { RateLimitOptions, RateLimitPluginOptions } from "@fastify/rate-limit";
|
import type { RateLimitOptions, RateLimitPluginOptions } from "@fastify/rate-limit";
|
||||||
import { Redis } from "ioredis";
|
import { Redis } from "ioredis";
|
||||||
|
|
||||||
import { getRateLimiterConfig } from "@app/ee/services/rate-limit/rate-limit-service";
|
|
||||||
import { getConfig } from "@app/lib/config/env";
|
import { getConfig } from "@app/lib/config/env";
|
||||||
|
|
||||||
export const globalRateLimiterCfg = (): RateLimitPluginOptions => {
|
export const globalRateLimiterCfg = (): RateLimitPluginOptions => {
|
||||||
@@ -22,14 +21,16 @@ export const globalRateLimiterCfg = (): RateLimitPluginOptions => {
|
|||||||
// GET endpoints
|
// GET endpoints
|
||||||
export const readLimit: RateLimitOptions = {
|
export const readLimit: RateLimitOptions = {
|
||||||
timeWindow: 60 * 1000,
|
timeWindow: 60 * 1000,
|
||||||
max: () => getRateLimiterConfig().readLimit,
|
hook: "preValidation",
|
||||||
|
max: (req) => req.rateLimits.readLimit,
|
||||||
keyGenerator: (req) => req.realIp
|
keyGenerator: (req) => req.realIp
|
||||||
};
|
};
|
||||||
|
|
||||||
// POST, PATCH, PUT, DELETE endpoints
|
// POST, PATCH, PUT, DELETE endpoints
|
||||||
export const writeLimit: RateLimitOptions = {
|
export const writeLimit: RateLimitOptions = {
|
||||||
timeWindow: 60 * 1000,
|
timeWindow: 60 * 1000,
|
||||||
max: () => getRateLimiterConfig().writeLimit,
|
hook: "preValidation",
|
||||||
|
max: (req) => req.rateLimits.writeLimit,
|
||||||
keyGenerator: (req) => req.realIp
|
keyGenerator: (req) => req.realIp
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -37,42 +38,40 @@ export const writeLimit: RateLimitOptions = {
|
|||||||
export const secretsLimit: RateLimitOptions = {
|
export const secretsLimit: RateLimitOptions = {
|
||||||
// secrets, folders, secret imports
|
// secrets, folders, secret imports
|
||||||
timeWindow: 60 * 1000,
|
timeWindow: 60 * 1000,
|
||||||
max: () => getRateLimiterConfig().secretsLimit,
|
hook: "preValidation",
|
||||||
|
max: (req) => req.rateLimits.secretsLimit,
|
||||||
keyGenerator: (req) => req.realIp
|
keyGenerator: (req) => req.realIp
|
||||||
};
|
};
|
||||||
|
|
||||||
export const authRateLimit: RateLimitOptions = {
|
export const authRateLimit: RateLimitOptions = {
|
||||||
timeWindow: 60 * 1000,
|
timeWindow: 60 * 1000,
|
||||||
max: () => getRateLimiterConfig().authRateLimit,
|
hook: "preValidation",
|
||||||
|
max: (req) => req.rateLimits.authRateLimit,
|
||||||
keyGenerator: (req) => req.realIp
|
keyGenerator: (req) => req.realIp
|
||||||
};
|
};
|
||||||
|
|
||||||
export const inviteUserRateLimit: RateLimitOptions = {
|
export const inviteUserRateLimit: RateLimitOptions = {
|
||||||
timeWindow: 60 * 1000,
|
timeWindow: 60 * 1000,
|
||||||
max: () => getRateLimiterConfig().inviteUserRateLimit,
|
hook: "preValidation",
|
||||||
|
max: (req) => req.rateLimits.inviteUserRateLimit,
|
||||||
keyGenerator: (req) => req.realIp
|
keyGenerator: (req) => req.realIp
|
||||||
};
|
};
|
||||||
|
|
||||||
export const mfaRateLimit: RateLimitOptions = {
|
export const mfaRateLimit: RateLimitOptions = {
|
||||||
timeWindow: 60 * 1000,
|
timeWindow: 60 * 1000,
|
||||||
max: () => getRateLimiterConfig().mfaRateLimit,
|
hook: "preValidation",
|
||||||
|
max: (req) => req.rateLimits.mfaRateLimit,
|
||||||
keyGenerator: (req) => {
|
keyGenerator: (req) => {
|
||||||
return req.headers.authorization?.split(" ")[1] || req.realIp;
|
return req.headers.authorization?.split(" ")[1] || req.realIp;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const creationLimit: RateLimitOptions = {
|
|
||||||
// identity, project, org
|
|
||||||
timeWindow: 60 * 1000,
|
|
||||||
max: () => getRateLimiterConfig().creationLimit,
|
|
||||||
keyGenerator: (req) => req.realIp
|
|
||||||
};
|
|
||||||
|
|
||||||
// Public endpoints to avoid brute force attacks
|
// Public endpoints to avoid brute force attacks
|
||||||
export const publicEndpointLimit: RateLimitOptions = {
|
export const publicEndpointLimit: RateLimitOptions = {
|
||||||
// Read Shared Secrets
|
// Read Shared Secrets
|
||||||
timeWindow: 60 * 1000,
|
timeWindow: 60 * 1000,
|
||||||
max: () => getRateLimiterConfig().publicEndpointLimit,
|
hook: "preValidation",
|
||||||
|
max: (req) => req.rateLimits.publicEndpointLimit,
|
||||||
keyGenerator: (req) => req.realIp
|
keyGenerator: (req) => req.realIp
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,38 @@
|
|||||||
|
import fp from "fastify-plugin";
|
||||||
|
|
||||||
|
import { getRateLimiterConfig } from "@app/ee/services/rate-limit/rate-limit-service";
|
||||||
|
import { getConfig } from "@app/lib/config/env";
|
||||||
|
|
||||||
|
export const injectRateLimits = fp(async (server) => {
|
||||||
|
server.decorateRequest("rateLimits", null);
|
||||||
|
server.addHook("onRequest", async (req) => {
|
||||||
|
const appCfg = getConfig();
|
||||||
|
|
||||||
|
const instanceRateLimiterConfig = getRateLimiterConfig();
|
||||||
|
if (!req.auth?.orgId) {
|
||||||
|
// for public endpoints, we always use the instance-wide default rate limits
|
||||||
|
req.rateLimits = instanceRateLimiterConfig;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { rateLimits, customRateLimits } = await server.services.license.getPlan(req.auth.orgId);
|
||||||
|
|
||||||
|
if (customRateLimits && !appCfg.isCloud) {
|
||||||
|
// we do this because for self-hosted/dedicated instances, we want custom rate limits to be based on admin configuration
|
||||||
|
// note that the syncing of custom rate limit happens on the instanceRateLimiterConfig object
|
||||||
|
req.rateLimits = instanceRateLimiterConfig;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// we're using the null coalescing operator in order to handle outdated licenses
|
||||||
|
req.rateLimits = {
|
||||||
|
readLimit: rateLimits?.readLimit ?? instanceRateLimiterConfig.readLimit,
|
||||||
|
writeLimit: rateLimits?.writeLimit ?? instanceRateLimiterConfig.writeLimit,
|
||||||
|
secretsLimit: rateLimits?.secretsLimit ?? instanceRateLimiterConfig.secretsLimit,
|
||||||
|
publicEndpointLimit: instanceRateLimiterConfig.publicEndpointLimit,
|
||||||
|
authRateLimit: instanceRateLimiterConfig.authRateLimit,
|
||||||
|
inviteUserRateLimit: instanceRateLimiterConfig.inviteUserRateLimit,
|
||||||
|
mfaRateLimit: instanceRateLimiterConfig.mfaRateLimit
|
||||||
|
};
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -90,6 +90,8 @@ import { certificateAuthorityEstConfigDALFactory } from "@app/services/certifica
|
|||||||
import { certificateAuthorityQueueFactory } from "@app/services/certificate-authority/certificate-authority-queue";
|
import { certificateAuthorityQueueFactory } from "@app/services/certificate-authority/certificate-authority-queue";
|
||||||
import { certificateAuthoritySecretDALFactory } from "@app/services/certificate-authority/certificate-authority-secret-dal";
|
import { certificateAuthoritySecretDALFactory } from "@app/services/certificate-authority/certificate-authority-secret-dal";
|
||||||
import { certificateAuthorityServiceFactory } from "@app/services/certificate-authority/certificate-authority-service";
|
import { certificateAuthorityServiceFactory } from "@app/services/certificate-authority/certificate-authority-service";
|
||||||
|
import { certificateTemplateDALFactory } from "@app/services/certificate-template/certificate-template-dal";
|
||||||
|
import { certificateTemplateServiceFactory } from "@app/services/certificate-template/certificate-template-service";
|
||||||
import { groupProjectDALFactory } from "@app/services/group-project/group-project-dal";
|
import { groupProjectDALFactory } from "@app/services/group-project/group-project-dal";
|
||||||
import { groupProjectMembershipRoleDALFactory } from "@app/services/group-project/group-project-membership-role-dal";
|
import { groupProjectMembershipRoleDALFactory } from "@app/services/group-project/group-project-membership-role-dal";
|
||||||
import { groupProjectServiceFactory } from "@app/services/group-project/group-project-service";
|
import { groupProjectServiceFactory } from "@app/services/group-project/group-project-service";
|
||||||
@@ -132,6 +134,7 @@ import { orgRoleServiceFactory } from "@app/services/org/org-role-service";
|
|||||||
import { orgServiceFactory } from "@app/services/org/org-service";
|
import { orgServiceFactory } from "@app/services/org/org-service";
|
||||||
import { orgAdminServiceFactory } from "@app/services/org-admin/org-admin-service";
|
import { orgAdminServiceFactory } from "@app/services/org-admin/org-admin-service";
|
||||||
import { orgMembershipDALFactory } from "@app/services/org-membership/org-membership-dal";
|
import { orgMembershipDALFactory } from "@app/services/org-membership/org-membership-dal";
|
||||||
|
import { dailyExpiringPkiItemAlertQueueServiceFactory } from "@app/services/pki-alert/expiring-pki-item-alert-queue";
|
||||||
import { pkiAlertDALFactory } from "@app/services/pki-alert/pki-alert-dal";
|
import { pkiAlertDALFactory } from "@app/services/pki-alert/pki-alert-dal";
|
||||||
import { pkiAlertServiceFactory } from "@app/services/pki-alert/pki-alert-service";
|
import { pkiAlertServiceFactory } from "@app/services/pki-alert/pki-alert-service";
|
||||||
import { pkiCollectionDALFactory } from "@app/services/pki-collection/pki-collection-dal";
|
import { pkiCollectionDALFactory } from "@app/services/pki-collection/pki-collection-dal";
|
||||||
@@ -190,6 +193,7 @@ import { webhookServiceFactory } from "@app/services/webhook/webhook-service";
|
|||||||
import { injectAuditLogInfo } from "../plugins/audit-log";
|
import { injectAuditLogInfo } from "../plugins/audit-log";
|
||||||
import { injectIdentity } from "../plugins/auth/inject-identity";
|
import { injectIdentity } from "../plugins/auth/inject-identity";
|
||||||
import { injectPermission } from "../plugins/auth/inject-permission";
|
import { injectPermission } from "../plugins/auth/inject-permission";
|
||||||
|
import { injectRateLimits } from "../plugins/inject-rate-limits";
|
||||||
import { registerSecretScannerGhApp } from "../plugins/secret-scanner";
|
import { registerSecretScannerGhApp } from "../plugins/secret-scanner";
|
||||||
import { registerCertificateEstRouter } from "./est/certificate-est-router";
|
import { registerCertificateEstRouter } from "./est/certificate-est-router";
|
||||||
import { registerV1Routes } from "./v1";
|
import { registerV1Routes } from "./v1";
|
||||||
@@ -362,7 +366,8 @@ export const registerRoutes = async (
|
|||||||
projectEnvDAL,
|
projectEnvDAL,
|
||||||
secretApprovalPolicyApproverDAL: sapApproverDAL,
|
secretApprovalPolicyApproverDAL: sapApproverDAL,
|
||||||
permissionService,
|
permissionService,
|
||||||
secretApprovalPolicyDAL
|
secretApprovalPolicyDAL,
|
||||||
|
licenseService
|
||||||
});
|
});
|
||||||
const tokenService = tokenServiceFactory({ tokenDAL: authTokenDAL, userDAL, orgMembershipDAL });
|
const tokenService = tokenServiceFactory({ tokenDAL: authTokenDAL, userDAL, orgMembershipDAL });
|
||||||
|
|
||||||
@@ -587,6 +592,7 @@ export const registerRoutes = async (
|
|||||||
const certificateAuthoritySecretDAL = certificateAuthoritySecretDALFactory(db);
|
const certificateAuthoritySecretDAL = certificateAuthoritySecretDALFactory(db);
|
||||||
const certificateAuthorityCrlDAL = certificateAuthorityCrlDALFactory(db);
|
const certificateAuthorityCrlDAL = certificateAuthorityCrlDALFactory(db);
|
||||||
const certificateAuthorityEstConfigDAL = certificateAuthorityEstConfigDALFactory(db);
|
const certificateAuthorityEstConfigDAL = certificateAuthorityEstConfigDALFactory(db);
|
||||||
|
const certificateTemplateDAL = certificateTemplateDALFactory(db);
|
||||||
|
|
||||||
const certificateDAL = certificateDALFactory(db);
|
const certificateDAL = certificateDALFactory(db);
|
||||||
const certificateBodyDAL = certificateBodyDALFactory(db);
|
const certificateBodyDAL = certificateBodyDALFactory(db);
|
||||||
@@ -622,9 +628,12 @@ export const registerRoutes = async (
|
|||||||
certificateAuthorityCertDAL,
|
certificateAuthorityCertDAL,
|
||||||
certificateAuthoritySecretDAL,
|
certificateAuthoritySecretDAL,
|
||||||
certificateAuthorityCrlDAL,
|
certificateAuthorityCrlDAL,
|
||||||
|
certificateTemplateDAL,
|
||||||
certificateAuthorityQueue,
|
certificateAuthorityQueue,
|
||||||
certificateDAL,
|
certificateDAL,
|
||||||
certificateBodyDAL,
|
certificateBodyDAL,
|
||||||
|
pkiCollectionDAL,
|
||||||
|
pkiCollectionItemDAL,
|
||||||
projectDAL,
|
projectDAL,
|
||||||
kmsService,
|
kmsService,
|
||||||
permissionService,
|
permissionService,
|
||||||
@@ -640,6 +649,12 @@ export const registerRoutes = async (
|
|||||||
licenseService
|
licenseService
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const certificateTemplateService = certificateTemplateServiceFactory({
|
||||||
|
certificateTemplateDAL,
|
||||||
|
certificateAuthorityDAL,
|
||||||
|
permissionService
|
||||||
|
});
|
||||||
|
|
||||||
const pkiAlertService = pkiAlertServiceFactory({
|
const pkiAlertService = pkiAlertServiceFactory({
|
||||||
pkiAlertDAL,
|
pkiAlertDAL,
|
||||||
pkiCollectionDAL,
|
pkiCollectionDAL,
|
||||||
@@ -677,7 +692,8 @@ export const registerRoutes = async (
|
|||||||
identityProjectMembershipRoleDAL,
|
identityProjectMembershipRoleDAL,
|
||||||
keyStore,
|
keyStore,
|
||||||
kmsService,
|
kmsService,
|
||||||
projectBotDAL
|
projectBotDAL,
|
||||||
|
certificateTemplateDAL
|
||||||
});
|
});
|
||||||
|
|
||||||
const projectEnvService = projectEnvServiceFactory({
|
const projectEnvService = projectEnvServiceFactory({
|
||||||
@@ -740,6 +756,7 @@ export const registerRoutes = async (
|
|||||||
kmsService
|
kmsService
|
||||||
});
|
});
|
||||||
const secretQueueService = secretQueueFactory({
|
const secretQueueService = secretQueueFactory({
|
||||||
|
keyStore,
|
||||||
queueService,
|
queueService,
|
||||||
secretDAL,
|
secretDAL,
|
||||||
folderDAL,
|
folderDAL,
|
||||||
@@ -825,7 +842,8 @@ export const registerRoutes = async (
|
|||||||
secretVersionTagV2BridgeDAL,
|
secretVersionTagV2BridgeDAL,
|
||||||
smtpService,
|
smtpService,
|
||||||
projectEnvDAL,
|
projectEnvDAL,
|
||||||
userDAL
|
userDAL,
|
||||||
|
licenseService
|
||||||
});
|
});
|
||||||
|
|
||||||
const secretService = secretServiceFactory({
|
const secretService = secretServiceFactory({
|
||||||
@@ -926,8 +944,15 @@ export const registerRoutes = async (
|
|||||||
folderDAL,
|
folderDAL,
|
||||||
integrationDAL,
|
integrationDAL,
|
||||||
integrationAuthDAL,
|
integrationAuthDAL,
|
||||||
secretQueueService
|
secretQueueService,
|
||||||
|
integrationAuthService,
|
||||||
|
projectBotService,
|
||||||
|
secretV2BridgeDAL,
|
||||||
|
secretImportDAL,
|
||||||
|
secretDAL,
|
||||||
|
kmsService
|
||||||
});
|
});
|
||||||
|
|
||||||
const serviceTokenService = serviceTokenServiceFactory({
|
const serviceTokenService = serviceTokenServiceFactory({
|
||||||
projectEnvDAL,
|
projectEnvDAL,
|
||||||
serviceTokenDAL,
|
serviceTokenDAL,
|
||||||
@@ -1054,13 +1079,18 @@ export const registerRoutes = async (
|
|||||||
const dailyResourceCleanUp = dailyResourceCleanUpQueueServiceFactory({
|
const dailyResourceCleanUp = dailyResourceCleanUpQueueServiceFactory({
|
||||||
auditLogDAL,
|
auditLogDAL,
|
||||||
queueService,
|
queueService,
|
||||||
pkiAlertService,
|
|
||||||
secretVersionDAL,
|
secretVersionDAL,
|
||||||
secretFolderVersionDAL: folderVersionDAL,
|
secretFolderVersionDAL: folderVersionDAL,
|
||||||
snapshotDAL,
|
snapshotDAL,
|
||||||
identityAccessTokenDAL,
|
identityAccessTokenDAL,
|
||||||
secretSharingDAL,
|
secretSharingDAL,
|
||||||
secretVersionV2DAL: secretVersionV2BridgeDAL
|
secretVersionV2DAL: secretVersionV2BridgeDAL,
|
||||||
|
identityUniversalAuthClientSecretDAL: identityUaClientSecretDAL
|
||||||
|
});
|
||||||
|
|
||||||
|
const dailyExpiringPkiItemAlert = dailyExpiringPkiItemAlertQueueServiceFactory({
|
||||||
|
queueService,
|
||||||
|
pkiAlertService
|
||||||
});
|
});
|
||||||
|
|
||||||
const oidcService = oidcConfigServiceFactory({
|
const oidcService = oidcConfigServiceFactory({
|
||||||
@@ -1087,6 +1117,7 @@ export const registerRoutes = async (
|
|||||||
|
|
||||||
await telemetryQueue.startTelemetryCheck();
|
await telemetryQueue.startTelemetryCheck();
|
||||||
await dailyResourceCleanUp.startCleanUp();
|
await dailyResourceCleanUp.startCleanUp();
|
||||||
|
await dailyExpiringPkiItemAlert.startSendingAlerts();
|
||||||
await kmsService.startService();
|
await kmsService.startService();
|
||||||
|
|
||||||
// inject all services
|
// inject all services
|
||||||
@@ -1144,6 +1175,7 @@ export const registerRoutes = async (
|
|||||||
auditLogStream: auditLogStreamService,
|
auditLogStream: auditLogStreamService,
|
||||||
certificate: certificateService,
|
certificate: certificateService,
|
||||||
certificateAuthority: certificateAuthorityService,
|
certificateAuthority: certificateAuthorityService,
|
||||||
|
certificateTemplate: certificateTemplateService,
|
||||||
certificateAuthorityCrl: certificateAuthorityCrlService,
|
certificateAuthorityCrl: certificateAuthorityCrlService,
|
||||||
pkiAlert: pkiAlertService,
|
pkiAlert: pkiAlertService,
|
||||||
pkiCollection: pkiCollectionService,
|
pkiCollection: pkiCollectionService,
|
||||||
@@ -1175,6 +1207,7 @@ export const registerRoutes = async (
|
|||||||
|
|
||||||
await server.register(injectIdentity, { userDAL, serviceTokenDAL });
|
await server.register(injectIdentity, { userDAL, serviceTokenDAL });
|
||||||
await server.register(injectPermission);
|
await server.register(injectPermission);
|
||||||
|
await server.register(injectRateLimits);
|
||||||
await server.register(injectAuditLogInfo);
|
await server.register(injectAuditLogInfo);
|
||||||
|
|
||||||
server.route({
|
server.route({
|
||||||
|
|||||||
@@ -63,8 +63,8 @@ export const secretRawSchema = z.object({
|
|||||||
version: z.number(),
|
version: z.number(),
|
||||||
type: z.string(),
|
type: z.string(),
|
||||||
secretKey: z.string(),
|
secretKey: z.string(),
|
||||||
secretValue: z.string().optional(),
|
secretValue: z.string(),
|
||||||
secretComment: z.string().optional(),
|
secretComment: z.string(),
|
||||||
secretReminderNote: z.string().nullable().optional(),
|
secretReminderNote: z.string().nullable().optional(),
|
||||||
secretReminderRepeatDays: z.number().nullable().optional(),
|
secretReminderRepeatDays: z.number().nullable().optional(),
|
||||||
skipMultilineEncoding: z.boolean().default(false).nullable().optional(),
|
skipMultilineEncoding: z.boolean().default(false).nullable().optional(),
|
||||||
|
|||||||
@@ -284,7 +284,7 @@ export const registerCaRouter = async (server: FastifyZodProvider) => {
|
|||||||
},
|
},
|
||||||
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
schema: {
|
schema: {
|
||||||
description: "Renew CA certificate for CA",
|
description: "Perform CA certificate renewal",
|
||||||
params: z.object({
|
params: z.object({
|
||||||
caId: z.string().trim().describe(CERTIFICATE_AUTHORITIES.RENEW_CA_CERT.caId)
|
caId: z.string().trim().describe(CERTIFICATE_AUTHORITIES.RENEW_CA_CERT.caId)
|
||||||
}),
|
}),
|
||||||
@@ -557,6 +557,7 @@ export const registerCaRouter = async (server: FastifyZodProvider) => {
|
|||||||
}),
|
}),
|
||||||
body: z
|
body: z
|
||||||
.object({
|
.object({
|
||||||
|
pkiCollectionId: z.string().trim().optional().describe(CERTIFICATE_AUTHORITIES.ISSUE_CERT.pkiCollectionId),
|
||||||
friendlyName: z.string().trim().optional().describe(CERTIFICATE_AUTHORITIES.ISSUE_CERT.friendlyName),
|
friendlyName: z.string().trim().optional().describe(CERTIFICATE_AUTHORITIES.ISSUE_CERT.friendlyName),
|
||||||
commonName: z.string().trim().min(1).describe(CERTIFICATE_AUTHORITIES.ISSUE_CERT.commonName),
|
commonName: z.string().trim().min(1).describe(CERTIFICATE_AUTHORITIES.ISSUE_CERT.commonName),
|
||||||
altNames: validateAltNamesField.describe(CERTIFICATE_AUTHORITIES.ISSUE_CERT.altNames),
|
altNames: validateAltNamesField.describe(CERTIFICATE_AUTHORITIES.ISSUE_CERT.altNames),
|
||||||
@@ -636,6 +637,7 @@ export const registerCaRouter = async (server: FastifyZodProvider) => {
|
|||||||
body: z
|
body: z
|
||||||
.object({
|
.object({
|
||||||
csr: z.string().trim().min(1).describe(CERTIFICATE_AUTHORITIES.SIGN_CERT.csr),
|
csr: z.string().trim().min(1).describe(CERTIFICATE_AUTHORITIES.SIGN_CERT.csr),
|
||||||
|
pkiCollectionId: z.string().trim().optional().describe(CERTIFICATE_AUTHORITIES.SIGN_CERT.pkiCollectionId),
|
||||||
friendlyName: z.string().trim().optional().describe(CERTIFICATE_AUTHORITIES.SIGN_CERT.friendlyName),
|
friendlyName: z.string().trim().optional().describe(CERTIFICATE_AUTHORITIES.SIGN_CERT.friendlyName),
|
||||||
commonName: z.string().trim().min(1).optional().describe(CERTIFICATE_AUTHORITIES.SIGN_CERT.commonName),
|
commonName: z.string().trim().min(1).optional().describe(CERTIFICATE_AUTHORITIES.SIGN_CERT.commonName),
|
||||||
altNames: validateAltNamesField.describe(CERTIFICATE_AUTHORITIES.SIGN_CERT.altNames),
|
altNames: validateAltNamesField.describe(CERTIFICATE_AUTHORITIES.SIGN_CERT.altNames),
|
||||||
|
|||||||
@@ -1,12 +1,17 @@
|
|||||||
|
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 { CERTIFICATES } from "@app/lib/api-docs";
|
import { CERTIFICATE_AUTHORITIES, CERTIFICATES } from "@app/lib/api-docs";
|
||||||
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";
|
||||||
import { CrlReason } from "@app/services/certificate/certificate-types";
|
import { CrlReason } from "@app/services/certificate/certificate-types";
|
||||||
|
import {
|
||||||
|
validateAltNamesField,
|
||||||
|
validateCaDateField
|
||||||
|
} from "@app/services/certificate-authority/certificate-authority-validators";
|
||||||
|
|
||||||
export const registerCertRouter = async (server: FastifyZodProvider) => {
|
export const registerCertRouter = async (server: FastifyZodProvider) => {
|
||||||
server.route({
|
server.route({
|
||||||
@@ -55,6 +60,185 @@ export const registerCertRouter = async (server: FastifyZodProvider) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: "POST",
|
||||||
|
url: "/issue-certificate",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
|
schema: {
|
||||||
|
description: "Issue certificate",
|
||||||
|
body: z
|
||||||
|
.object({
|
||||||
|
caId: z.string().trim().optional().describe(CERTIFICATE_AUTHORITIES.ISSUE_CERT.caId),
|
||||||
|
certificateTemplateId: z
|
||||||
|
.string()
|
||||||
|
.trim()
|
||||||
|
.optional()
|
||||||
|
.describe(CERTIFICATE_AUTHORITIES.ISSUE_CERT.certificateTemplateId),
|
||||||
|
pkiCollectionId: z.string().trim().optional().describe(CERTIFICATE_AUTHORITIES.SIGN_CERT.pkiCollectionId),
|
||||||
|
friendlyName: z.string().trim().optional().describe(CERTIFICATE_AUTHORITIES.ISSUE_CERT.friendlyName),
|
||||||
|
commonName: z.string().trim().min(1).describe(CERTIFICATE_AUTHORITIES.ISSUE_CERT.commonName),
|
||||||
|
altNames: validateAltNamesField.describe(CERTIFICATE_AUTHORITIES.ISSUE_CERT.altNames),
|
||||||
|
ttl: z
|
||||||
|
.string()
|
||||||
|
.refine((val) => ms(val) > 0, "TTL must be a positive number")
|
||||||
|
.describe(CERTIFICATE_AUTHORITIES.ISSUE_CERT.ttl),
|
||||||
|
notBefore: validateCaDateField.optional().describe(CERTIFICATE_AUTHORITIES.ISSUE_CERT.notBefore),
|
||||||
|
notAfter: validateCaDateField.optional().describe(CERTIFICATE_AUTHORITIES.ISSUE_CERT.notAfter)
|
||||||
|
})
|
||||||
|
.refine(
|
||||||
|
(data) => {
|
||||||
|
const { ttl, notAfter } = data;
|
||||||
|
return (ttl !== undefined && notAfter === undefined) || (ttl === undefined && notAfter !== undefined);
|
||||||
|
},
|
||||||
|
{
|
||||||
|
message: "Either ttl or notAfter must be present, but not both",
|
||||||
|
path: ["ttl", "notAfter"]
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.refine(
|
||||||
|
(data) =>
|
||||||
|
(data.caId !== undefined && data.certificateTemplateId === undefined) ||
|
||||||
|
(data.caId === undefined && data.certificateTemplateId !== undefined),
|
||||||
|
{
|
||||||
|
message: "Either CA ID or Certificate Template ID must be present, but not both",
|
||||||
|
path: ["caId", "certificateTemplateId"]
|
||||||
|
}
|
||||||
|
),
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
certificate: z.string().trim().describe(CERTIFICATE_AUTHORITIES.ISSUE_CERT.certificate),
|
||||||
|
issuingCaCertificate: z.string().trim().describe(CERTIFICATE_AUTHORITIES.ISSUE_CERT.issuingCaCertificate),
|
||||||
|
certificateChain: z.string().trim().describe(CERTIFICATE_AUTHORITIES.ISSUE_CERT.certificateChain),
|
||||||
|
privateKey: z.string().trim().describe(CERTIFICATE_AUTHORITIES.ISSUE_CERT.privateKey),
|
||||||
|
serialNumber: z.string().trim().describe(CERTIFICATE_AUTHORITIES.ISSUE_CERT.serialNumber)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handler: async (req) => {
|
||||||
|
const { certificate, certificateChain, issuingCaCertificate, privateKey, serialNumber, ca } =
|
||||||
|
await server.services.certificateAuthority.issueCertFromCa({
|
||||||
|
actor: req.permission.type,
|
||||||
|
actorId: req.permission.id,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
|
actorOrgId: req.permission.orgId,
|
||||||
|
...req.body
|
||||||
|
});
|
||||||
|
|
||||||
|
await server.services.auditLog.createAuditLog({
|
||||||
|
...req.auditLogInfo,
|
||||||
|
projectId: ca.projectId,
|
||||||
|
event: {
|
||||||
|
type: EventType.ISSUE_CERT,
|
||||||
|
metadata: {
|
||||||
|
caId: ca.id,
|
||||||
|
dn: ca.dn,
|
||||||
|
serialNumber
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
certificate,
|
||||||
|
certificateChain,
|
||||||
|
issuingCaCertificate,
|
||||||
|
privateKey,
|
||||||
|
serialNumber
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: "POST",
|
||||||
|
url: "/sign-certificate",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
|
schema: {
|
||||||
|
description: "Sign certificate",
|
||||||
|
body: z
|
||||||
|
.object({
|
||||||
|
caId: z.string().trim().optional().describe(CERTIFICATE_AUTHORITIES.SIGN_CERT.caId),
|
||||||
|
certificateTemplateId: z
|
||||||
|
.string()
|
||||||
|
.trim()
|
||||||
|
.optional()
|
||||||
|
.describe(CERTIFICATE_AUTHORITIES.ISSUE_CERT.certificateTemplateId),
|
||||||
|
pkiCollectionId: z.string().trim().optional().describe(CERTIFICATE_AUTHORITIES.SIGN_CERT.pkiCollectionId),
|
||||||
|
csr: z.string().trim().min(1).describe(CERTIFICATE_AUTHORITIES.SIGN_CERT.csr),
|
||||||
|
friendlyName: z.string().trim().optional().describe(CERTIFICATE_AUTHORITIES.SIGN_CERT.friendlyName),
|
||||||
|
commonName: z.string().trim().min(1).optional().describe(CERTIFICATE_AUTHORITIES.SIGN_CERT.commonName),
|
||||||
|
altNames: validateAltNamesField.describe(CERTIFICATE_AUTHORITIES.SIGN_CERT.altNames),
|
||||||
|
ttl: z
|
||||||
|
.string()
|
||||||
|
.refine((val) => ms(val) > 0, "TTL must be a positive number")
|
||||||
|
.describe(CERTIFICATE_AUTHORITIES.SIGN_CERT.ttl),
|
||||||
|
notBefore: validateCaDateField.optional().describe(CERTIFICATE_AUTHORITIES.SIGN_CERT.notBefore),
|
||||||
|
notAfter: validateCaDateField.optional().describe(CERTIFICATE_AUTHORITIES.SIGN_CERT.notAfter)
|
||||||
|
})
|
||||||
|
.refine(
|
||||||
|
(data) => {
|
||||||
|
const { ttl, notAfter } = data;
|
||||||
|
return (ttl !== undefined && notAfter === undefined) || (ttl === undefined && notAfter !== undefined);
|
||||||
|
},
|
||||||
|
{
|
||||||
|
message: "Either ttl or notAfter must be present, but not both",
|
||||||
|
path: ["ttl", "notAfter"]
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.refine(
|
||||||
|
(data) =>
|
||||||
|
(data.caId !== undefined && data.certificateTemplateId === undefined) ||
|
||||||
|
(data.caId === undefined && data.certificateTemplateId !== undefined),
|
||||||
|
{
|
||||||
|
message: "Either CA ID or Certificate Template ID must be present, but not both",
|
||||||
|
path: ["caId", "certificateTemplateId"]
|
||||||
|
}
|
||||||
|
),
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
certificate: z.string().trim().describe(CERTIFICATE_AUTHORITIES.SIGN_CERT.certificate),
|
||||||
|
issuingCaCertificate: z.string().trim().describe(CERTIFICATE_AUTHORITIES.ISSUE_CERT.issuingCaCertificate),
|
||||||
|
certificateChain: z.string().trim().describe(CERTIFICATE_AUTHORITIES.ISSUE_CERT.certificateChain),
|
||||||
|
serialNumber: z.string().trim().describe(CERTIFICATE_AUTHORITIES.ISSUE_CERT.serialNumber)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handler: async (req) => {
|
||||||
|
const { certificate, certificateChain, issuingCaCertificate, serialNumber, ca } =
|
||||||
|
await server.services.certificateAuthority.signCertFromCa({
|
||||||
|
actor: req.permission.type,
|
||||||
|
actorId: req.permission.id,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
|
actorOrgId: req.permission.orgId,
|
||||||
|
...req.body
|
||||||
|
});
|
||||||
|
|
||||||
|
await server.services.auditLog.createAuditLog({
|
||||||
|
...req.auditLogInfo,
|
||||||
|
projectId: ca.projectId,
|
||||||
|
event: {
|
||||||
|
type: EventType.SIGN_CERT,
|
||||||
|
metadata: {
|
||||||
|
caId: ca.id,
|
||||||
|
dn: ca.dn,
|
||||||
|
serialNumber
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
certificate,
|
||||||
|
certificateChain,
|
||||||
|
issuingCaCertificate,
|
||||||
|
serialNumber
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
server.route({
|
server.route({
|
||||||
method: "POST",
|
method: "POST",
|
||||||
url: "/:serialNumber/revoke",
|
url: "/:serialNumber/revoke",
|
||||||
|
|||||||
@@ -0,0 +1,205 @@
|
|||||||
|
import ms from "ms";
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
|
||||||
|
import { CERTIFICATE_TEMPLATES } from "@app/lib/api-docs";
|
||||||
|
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
||||||
|
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||||
|
import { AuthMode } from "@app/services/auth/auth-type";
|
||||||
|
import { sanitizedCertificateTemplate } from "@app/services/certificate-template/certificate-template-schema";
|
||||||
|
import { validateTemplateRegexField } from "@app/services/certificate-template/certificate-template-validators";
|
||||||
|
|
||||||
|
export const registerCertificateTemplateRouter = async (server: FastifyZodProvider) => {
|
||||||
|
server.route({
|
||||||
|
method: "GET",
|
||||||
|
url: "/:certificateTemplateId",
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
params: z.object({
|
||||||
|
certificateTemplateId: z.string().describe(CERTIFICATE_TEMPLATES.GET.certificateTemplateId)
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: sanitizedCertificateTemplate
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const certificateTemplate = await server.services.certificateTemplate.getCertTemplate({
|
||||||
|
id: req.params.certificateTemplateId,
|
||||||
|
actor: req.permission.type,
|
||||||
|
actorId: req.permission.id,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
|
actorOrgId: req.permission.orgId
|
||||||
|
});
|
||||||
|
|
||||||
|
await server.services.auditLog.createAuditLog({
|
||||||
|
...req.auditLogInfo,
|
||||||
|
projectId: certificateTemplate.projectId,
|
||||||
|
event: {
|
||||||
|
type: EventType.GET_CERTIFICATE_TEMPLATE,
|
||||||
|
metadata: {
|
||||||
|
certificateTemplateId: certificateTemplate.id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return certificateTemplate;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: "POST",
|
||||||
|
url: "/",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
body: z.object({
|
||||||
|
caId: z.string().describe(CERTIFICATE_TEMPLATES.CREATE.caId),
|
||||||
|
pkiCollectionId: z.string().optional().describe(CERTIFICATE_TEMPLATES.CREATE.pkiCollectionId),
|
||||||
|
name: z.string().min(1).describe(CERTIFICATE_TEMPLATES.CREATE.name),
|
||||||
|
commonName: validateTemplateRegexField.describe(CERTIFICATE_TEMPLATES.CREATE.commonName),
|
||||||
|
subjectAlternativeName: validateTemplateRegexField.describe(
|
||||||
|
CERTIFICATE_TEMPLATES.CREATE.subjectAlternativeName
|
||||||
|
),
|
||||||
|
ttl: z
|
||||||
|
.string()
|
||||||
|
.refine((val) => ms(val) > 0, "TTL must be a positive number")
|
||||||
|
.describe(CERTIFICATE_TEMPLATES.CREATE.ttl)
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: sanitizedCertificateTemplate
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const certificateTemplate = await server.services.certificateTemplate.createCertTemplate({
|
||||||
|
actor: req.permission.type,
|
||||||
|
actorId: req.permission.id,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
|
actorOrgId: req.permission.orgId,
|
||||||
|
...req.body
|
||||||
|
});
|
||||||
|
|
||||||
|
await server.services.auditLog.createAuditLog({
|
||||||
|
...req.auditLogInfo,
|
||||||
|
projectId: certificateTemplate.projectId,
|
||||||
|
event: {
|
||||||
|
type: EventType.CREATE_CERTIFICATE_TEMPLATE,
|
||||||
|
metadata: {
|
||||||
|
certificateTemplateId: certificateTemplate.id,
|
||||||
|
caId: certificateTemplate.caId,
|
||||||
|
pkiCollectionId: certificateTemplate.pkiCollectionId as string,
|
||||||
|
name: certificateTemplate.name,
|
||||||
|
commonName: certificateTemplate.commonName,
|
||||||
|
subjectAlternativeName: certificateTemplate.subjectAlternativeName,
|
||||||
|
ttl: certificateTemplate.ttl
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return certificateTemplate;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: "PATCH",
|
||||||
|
url: "/:certificateTemplateId",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
body: z.object({
|
||||||
|
caId: z.string().optional().describe(CERTIFICATE_TEMPLATES.UPDATE.caId),
|
||||||
|
pkiCollectionId: z.string().optional().describe(CERTIFICATE_TEMPLATES.UPDATE.pkiCollectionId),
|
||||||
|
name: z.string().min(1).optional().describe(CERTIFICATE_TEMPLATES.UPDATE.name),
|
||||||
|
commonName: validateTemplateRegexField.optional().describe(CERTIFICATE_TEMPLATES.UPDATE.commonName),
|
||||||
|
subjectAlternativeName: validateTemplateRegexField
|
||||||
|
.optional()
|
||||||
|
.describe(CERTIFICATE_TEMPLATES.UPDATE.subjectAlternativeName),
|
||||||
|
ttl: z
|
||||||
|
.string()
|
||||||
|
.refine((val) => ms(val) > 0, "TTL must be a positive number")
|
||||||
|
.optional()
|
||||||
|
.describe(CERTIFICATE_TEMPLATES.UPDATE.ttl)
|
||||||
|
}),
|
||||||
|
params: z.object({
|
||||||
|
certificateTemplateId: z.string().describe(CERTIFICATE_TEMPLATES.UPDATE.certificateTemplateId)
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: sanitizedCertificateTemplate
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const certificateTemplate = await server.services.certificateTemplate.updateCertTemplate({
|
||||||
|
...req.body,
|
||||||
|
id: req.params.certificateTemplateId,
|
||||||
|
actor: req.permission.type,
|
||||||
|
actorId: req.permission.id,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
|
actorOrgId: req.permission.orgId
|
||||||
|
});
|
||||||
|
|
||||||
|
await server.services.auditLog.createAuditLog({
|
||||||
|
...req.auditLogInfo,
|
||||||
|
projectId: certificateTemplate.projectId,
|
||||||
|
event: {
|
||||||
|
type: EventType.UPDATE_CERTIFICATE_TEMPLATE,
|
||||||
|
metadata: {
|
||||||
|
certificateTemplateId: certificateTemplate.id,
|
||||||
|
caId: certificateTemplate.caId,
|
||||||
|
pkiCollectionId: certificateTemplate.pkiCollectionId as string,
|
||||||
|
name: certificateTemplate.name,
|
||||||
|
commonName: certificateTemplate.commonName,
|
||||||
|
subjectAlternativeName: certificateTemplate.subjectAlternativeName,
|
||||||
|
ttl: certificateTemplate.ttl
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return certificateTemplate;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: "DELETE",
|
||||||
|
url: "/:certificateTemplateId",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
params: z.object({
|
||||||
|
certificateTemplateId: z.string().describe(CERTIFICATE_TEMPLATES.DELETE.certificateTemplateId)
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: sanitizedCertificateTemplate
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const certificateTemplate = await server.services.certificateTemplate.deleteCertTemplate({
|
||||||
|
id: req.params.certificateTemplateId,
|
||||||
|
actor: req.permission.type,
|
||||||
|
actorId: req.permission.id,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
|
actorOrgId: req.permission.orgId
|
||||||
|
});
|
||||||
|
|
||||||
|
await server.services.auditLog.createAuditLog({
|
||||||
|
...req.auditLogInfo,
|
||||||
|
projectId: certificateTemplate.projectId,
|
||||||
|
event: {
|
||||||
|
type: EventType.DELETE_CERTIFICATE_TEMPLATE,
|
||||||
|
metadata: {
|
||||||
|
certificateTemplateId: certificateTemplate.id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return certificateTemplate;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
@@ -3,7 +3,7 @@ import { z } from "zod";
|
|||||||
import { IdentitiesSchema, IdentityOrgMembershipsSchema, OrgMembershipRole, OrgRolesSchema } from "@app/db/schemas";
|
import { IdentitiesSchema, IdentityOrgMembershipsSchema, OrgMembershipRole, OrgRolesSchema } 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 { IDENTITIES } from "@app/lib/api-docs";
|
import { IDENTITIES } from "@app/lib/api-docs";
|
||||||
import { creationLimit, readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
||||||
import { getTelemetryDistinctId } from "@app/server/lib/telemetry";
|
import { getTelemetryDistinctId } from "@app/server/lib/telemetry";
|
||||||
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";
|
||||||
@@ -16,7 +16,7 @@ export const registerIdentityRouter = async (server: FastifyZodProvider) => {
|
|||||||
method: "POST",
|
method: "POST",
|
||||||
url: "/",
|
url: "/",
|
||||||
config: {
|
config: {
|
||||||
rateLimit: creationLimit
|
rateLimit: writeLimit
|
||||||
},
|
},
|
||||||
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
schema: {
|
schema: {
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { registerAuthRoutes } from "./auth-router";
|
|||||||
import { registerProjectBotRouter } from "./bot-router";
|
import { registerProjectBotRouter } from "./bot-router";
|
||||||
import { registerCaRouter } from "./certificate-authority-router";
|
import { registerCaRouter } from "./certificate-authority-router";
|
||||||
import { registerCertRouter } from "./certificate-router";
|
import { registerCertRouter } from "./certificate-router";
|
||||||
|
import { registerCertificateTemplateRouter } from "./certificate-template-router";
|
||||||
import { registerIdentityAccessTokenRouter } from "./identity-access-token-router";
|
import { registerIdentityAccessTokenRouter } from "./identity-access-token-router";
|
||||||
import { registerIdentityAwsAuthRouter } from "./identity-aws-iam-auth-router";
|
import { registerIdentityAwsAuthRouter } from "./identity-aws-iam-auth-router";
|
||||||
import { registerIdentityAzureAuthRouter } from "./identity-azure-auth-router";
|
import { registerIdentityAzureAuthRouter } from "./identity-azure-auth-router";
|
||||||
@@ -76,8 +77,9 @@ export const registerV1Routes = async (server: FastifyZodProvider) => {
|
|||||||
async (pkiRouter) => {
|
async (pkiRouter) => {
|
||||||
await pkiRouter.register(registerCaRouter, { prefix: "/ca" });
|
await pkiRouter.register(registerCaRouter, { prefix: "/ca" });
|
||||||
await pkiRouter.register(registerCertRouter, { prefix: "/certificates" });
|
await pkiRouter.register(registerCertRouter, { prefix: "/certificates" });
|
||||||
await server.register(registerPkiAlertRouter, { prefix: "/alerts" });
|
await pkiRouter.register(registerCertificateTemplateRouter, { prefix: "/certificate-templates" });
|
||||||
await server.register(registerPkiCollectionRouter, { prefix: "/collections" });
|
await pkiRouter.register(registerPkiAlertRouter, { prefix: "/alerts" });
|
||||||
|
await pkiRouter.register(registerPkiCollectionRouter, { prefix: "/collections" });
|
||||||
},
|
},
|
||||||
{ prefix: "/pki" }
|
{ prefix: "/pki" }
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -170,6 +170,12 @@ export const registerIntegrationRouter = async (server: FastifyZodProvider) => {
|
|||||||
params: z.object({
|
params: z.object({
|
||||||
integrationId: z.string().trim().describe(INTEGRATION.DELETE.integrationId)
|
integrationId: z.string().trim().describe(INTEGRATION.DELETE.integrationId)
|
||||||
}),
|
}),
|
||||||
|
querystring: z.object({
|
||||||
|
shouldDeleteIntegrationSecrets: z
|
||||||
|
.enum(["true", "false"])
|
||||||
|
.optional()
|
||||||
|
.transform((val) => val === "true")
|
||||||
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
integration: IntegrationsSchema
|
integration: IntegrationsSchema
|
||||||
@@ -183,7 +189,8 @@ export const registerIntegrationRouter = async (server: FastifyZodProvider) => {
|
|||||||
actorAuthMethod: req.permission.authMethod,
|
actorAuthMethod: req.permission.authMethod,
|
||||||
actor: req.permission.type,
|
actor: req.permission.type,
|
||||||
actorOrgId: req.permission.orgId,
|
actorOrgId: req.permission.orgId,
|
||||||
id: req.params.integrationId
|
id: req.params.integrationId,
|
||||||
|
shouldDeleteIntegrationSecrets: req.query.shouldDeleteIntegrationSecrets
|
||||||
});
|
});
|
||||||
|
|
||||||
await server.services.auditLog.createAuditLog({
|
await server.services.auditLog.createAuditLog({
|
||||||
@@ -205,7 +212,8 @@ export const registerIntegrationRouter = async (server: FastifyZodProvider) => {
|
|||||||
targetService: integration.targetService,
|
targetService: integration.targetService,
|
||||||
targetServiceId: integration.targetServiceId,
|
targetServiceId: integration.targetServiceId,
|
||||||
path: integration.path,
|
path: integration.path,
|
||||||
region: integration.region
|
region: integration.region,
|
||||||
|
shouldDeleteIntegrationSecrets: req.query.shouldDeleteIntegrationSecrets
|
||||||
// eslint-disable-next-line
|
// eslint-disable-next-line
|
||||||
}) as any
|
}) as any
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,7 +22,11 @@ export const registerPkiAlertRouter = async (server: FastifyZodProvider) => {
|
|||||||
pkiCollectionId: z.string().trim().describe(ALERTS.CREATE.pkiCollectionId),
|
pkiCollectionId: z.string().trim().describe(ALERTS.CREATE.pkiCollectionId),
|
||||||
name: z.string().trim().describe(ALERTS.CREATE.name),
|
name: z.string().trim().describe(ALERTS.CREATE.name),
|
||||||
alertBeforeDays: z.number().describe(ALERTS.CREATE.alertBeforeDays),
|
alertBeforeDays: z.number().describe(ALERTS.CREATE.alertBeforeDays),
|
||||||
emails: z.array(z.string().trim().email({ message: "Invalid email address" })).describe(ALERTS.CREATE.emails)
|
emails: z
|
||||||
|
.array(z.string().trim().email({ message: "Invalid email address" }))
|
||||||
|
.min(1, { message: "You must specify at least 1 email" })
|
||||||
|
.max(5, { message: "You can specify a maximum of 5 emails" })
|
||||||
|
.describe(ALERTS.CREATE.emails)
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: PkiAlertsSchema
|
200: PkiAlertsSchema
|
||||||
@@ -114,6 +118,8 @@ export const registerPkiAlertRouter = async (server: FastifyZodProvider) => {
|
|||||||
pkiCollectionId: z.string().trim().optional().describe(ALERTS.UPDATE.pkiCollectionId),
|
pkiCollectionId: z.string().trim().optional().describe(ALERTS.UPDATE.pkiCollectionId),
|
||||||
emails: z
|
emails: z
|
||||||
.array(z.string().trim().email({ message: "Invalid email address" }))
|
.array(z.string().trim().email({ message: "Invalid email address" }))
|
||||||
|
.min(1, { message: "You must specify at least 1 email" })
|
||||||
|
.max(5, { message: "You can specify a maximum of 5 emails" })
|
||||||
.optional()
|
.optional()
|
||||||
.describe(ALERTS.UPDATE.emails)
|
.describe(ALERTS.UPDATE.emails)
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -20,7 +20,8 @@ export const registerPkiCollectionRouter = async (server: FastifyZodProvider) =>
|
|||||||
description: "Create PKI collection",
|
description: "Create PKI collection",
|
||||||
body: z.object({
|
body: z.object({
|
||||||
projectId: z.string().trim().describe(PKI_COLLECTIONS.CREATE.projectId),
|
projectId: z.string().trim().describe(PKI_COLLECTIONS.CREATE.projectId),
|
||||||
name: z.string().trim().describe(PKI_COLLECTIONS.CREATE.name)
|
name: z.string().trim().describe(PKI_COLLECTIONS.CREATE.name),
|
||||||
|
description: z.string().trim().default("").describe(PKI_COLLECTIONS.CREATE.description)
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: PkiCollectionsSchema
|
200: PkiCollectionsSchema
|
||||||
@@ -104,7 +105,8 @@ export const registerPkiCollectionRouter = async (server: FastifyZodProvider) =>
|
|||||||
collectionId: z.string().trim().describe(PKI_COLLECTIONS.UPDATE.collectionId)
|
collectionId: z.string().trim().describe(PKI_COLLECTIONS.UPDATE.collectionId)
|
||||||
}),
|
}),
|
||||||
body: z.object({
|
body: z.object({
|
||||||
name: z.string().trim().optional().describe(PKI_COLLECTIONS.UPDATE.name)
|
name: z.string().trim().optional().describe(PKI_COLLECTIONS.UPDATE.name),
|
||||||
|
description: z.string().trim().optional().describe(PKI_COLLECTIONS.UPDATE.description)
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: PkiCollectionsSchema
|
200: PkiCollectionsSchema
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import slugify from "@sindresorhus/slugify";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import { SecretTagsSchema } from "@app/db/schemas";
|
import { SecretTagsSchema } from "@app/db/schemas";
|
||||||
@@ -49,7 +50,8 @@ export const registerSecretTagRouter = async (server: FastifyZodProvider) => {
|
|||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
workspaceTag: SecretTagsSchema
|
// akhilmhdh: for terraform backward compatiability
|
||||||
|
workspaceTag: SecretTagsSchema.extend({ name: z.string() })
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -79,7 +81,8 @@ export const registerSecretTagRouter = async (server: FastifyZodProvider) => {
|
|||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
workspaceTag: SecretTagsSchema
|
// akhilmhdh: for terraform backward compatiability
|
||||||
|
workspaceTag: SecretTagsSchema.extend({ name: z.string() })
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -108,8 +111,14 @@ export const registerSecretTagRouter = async (server: FastifyZodProvider) => {
|
|||||||
projectId: z.string().trim().describe(SECRET_TAGS.CREATE.projectId)
|
projectId: z.string().trim().describe(SECRET_TAGS.CREATE.projectId)
|
||||||
}),
|
}),
|
||||||
body: z.object({
|
body: z.object({
|
||||||
name: z.string().trim().describe(SECRET_TAGS.CREATE.name),
|
slug: z
|
||||||
slug: z.string().trim().describe(SECRET_TAGS.CREATE.slug),
|
.string()
|
||||||
|
.toLowerCase()
|
||||||
|
.trim()
|
||||||
|
.describe(SECRET_TAGS.CREATE.slug)
|
||||||
|
.refine((v) => slugify(v) === v, {
|
||||||
|
message: "Invalid slug. Slug can only contain alphanumeric characters and hyphens."
|
||||||
|
}),
|
||||||
color: z.string().trim().describe(SECRET_TAGS.CREATE.color)
|
color: z.string().trim().describe(SECRET_TAGS.CREATE.color)
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
@@ -144,8 +153,14 @@ export const registerSecretTagRouter = async (server: FastifyZodProvider) => {
|
|||||||
tagId: z.string().trim().describe(SECRET_TAGS.UPDATE.tagId)
|
tagId: z.string().trim().describe(SECRET_TAGS.UPDATE.tagId)
|
||||||
}),
|
}),
|
||||||
body: z.object({
|
body: z.object({
|
||||||
name: z.string().trim().describe(SECRET_TAGS.UPDATE.name),
|
slug: z
|
||||||
slug: z.string().trim().describe(SECRET_TAGS.UPDATE.slug),
|
.string()
|
||||||
|
.toLowerCase()
|
||||||
|
.trim()
|
||||||
|
.describe(SECRET_TAGS.UPDATE.slug)
|
||||||
|
.refine((v) => slugify(v) === v, {
|
||||||
|
message: "Invalid slug. Slug can only contain alphanumeric characters and hyphens."
|
||||||
|
}),
|
||||||
color: z.string().trim().describe(SECRET_TAGS.UPDATE.color)
|
color: z.string().trim().describe(SECRET_TAGS.UPDATE.color)
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import {
|
|||||||
UsersSchema
|
UsersSchema
|
||||||
} from "@app/db/schemas";
|
} from "@app/db/schemas";
|
||||||
import { ORGANIZATIONS } from "@app/lib/api-docs";
|
import { ORGANIZATIONS } from "@app/lib/api-docs";
|
||||||
import { creationLimit, 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 { ActorType, AuthMode } from "@app/services/auth/auth-type";
|
import { ActorType, AuthMode } from "@app/services/auth/auth-type";
|
||||||
|
|
||||||
@@ -307,7 +307,7 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => {
|
|||||||
method: "POST",
|
method: "POST",
|
||||||
url: "/",
|
url: "/",
|
||||||
config: {
|
config: {
|
||||||
rateLimit: creationLimit
|
rateLimit: writeLimit
|
||||||
},
|
},
|
||||||
schema: {
|
schema: {
|
||||||
body: z.object({
|
body: z.object({
|
||||||
|
|||||||
@@ -10,11 +10,12 @@ 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 { PROJECTS } from "@app/lib/api-docs";
|
import { PROJECTS } from "@app/lib/api-docs";
|
||||||
import { creationLimit, readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
||||||
import { getTelemetryDistinctId } from "@app/server/lib/telemetry";
|
import { getTelemetryDistinctId } from "@app/server/lib/telemetry";
|
||||||
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";
|
||||||
import { CaStatus } from "@app/services/certificate-authority/certificate-authority-types";
|
import { CaStatus } from "@app/services/certificate-authority/certificate-authority-types";
|
||||||
|
import { sanitizedCertificateTemplate } from "@app/services/certificate-template/certificate-template-schema";
|
||||||
import { ProjectFilterType } from "@app/services/project/project-types";
|
import { ProjectFilterType } from "@app/services/project/project-types";
|
||||||
import { PostHogEventTypes } from "@app/services/telemetry/telemetry-types";
|
import { PostHogEventTypes } from "@app/services/telemetry/telemetry-types";
|
||||||
|
|
||||||
@@ -148,7 +149,7 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
|
|||||||
method: "POST",
|
method: "POST",
|
||||||
url: "/",
|
url: "/",
|
||||||
config: {
|
config: {
|
||||||
rateLimit: creationLimit
|
rateLimit: writeLimit
|
||||||
},
|
},
|
||||||
schema: {
|
schema: {
|
||||||
description: "Create a new project",
|
description: "Create a new project",
|
||||||
@@ -458,4 +459,34 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
|
|||||||
return { collections: pkiCollections };
|
return { collections: pkiCollections };
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: "GET",
|
||||||
|
url: "/:projectId/certificate-templates",
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
params: z.object({
|
||||||
|
projectId: z.string().trim()
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
certificateTemplates: sanitizedCertificateTemplate.array()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const { certificateTemplates } = await server.services.project.listProjectCertificateTemplates({
|
||||||
|
projectId: req.params.projectId,
|
||||||
|
actorId: req.permission.id,
|
||||||
|
actorOrgId: req.permission.orgId,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
|
actor: req.permission.type
|
||||||
|
});
|
||||||
|
|
||||||
|
return { certificateTemplates };
|
||||||
|
}
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -59,9 +59,10 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
|
|||||||
tags: SecretTagsSchema.pick({
|
tags: SecretTagsSchema.pick({
|
||||||
id: true,
|
id: true,
|
||||||
slug: true,
|
slug: true,
|
||||||
name: true,
|
|
||||||
color: true
|
color: true
|
||||||
}).array()
|
})
|
||||||
|
.extend({ name: z.string() })
|
||||||
|
.array()
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
@@ -116,16 +117,15 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
|
|||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
secret: SecretsSchema.omit({ secretBlindIndex: true }).merge(
|
secret: SecretsSchema.omit({ secretBlindIndex: true }).extend({
|
||||||
z.object({
|
tags: SecretTagsSchema.pick({
|
||||||
tags: SecretTagsSchema.pick({
|
id: true,
|
||||||
id: true,
|
slug: true,
|
||||||
slug: true,
|
color: true
|
||||||
name: true,
|
|
||||||
color: true
|
|
||||||
}).array()
|
|
||||||
})
|
})
|
||||||
)
|
.extend({ name: z.string() })
|
||||||
|
.array()
|
||||||
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -180,7 +180,13 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
|
|||||||
.enum(["true", "false"])
|
.enum(["true", "false"])
|
||||||
.default("false")
|
.default("false")
|
||||||
.transform((value) => value === "true")
|
.transform((value) => value === "true")
|
||||||
.describe(RAW_SECRETS.LIST.includeImports)
|
.describe(RAW_SECRETS.LIST.includeImports),
|
||||||
|
tagSlugs: z
|
||||||
|
.string()
|
||||||
|
.describe(RAW_SECRETS.LIST.tagSlugs)
|
||||||
|
.optional()
|
||||||
|
// split by comma and trim the strings
|
||||||
|
.transform((el) => (el ? el.split(",").map((i) => i.trim()) : []))
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
@@ -190,9 +196,9 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
|
|||||||
tags: SecretTagsSchema.pick({
|
tags: SecretTagsSchema.pick({
|
||||||
id: true,
|
id: true,
|
||||||
slug: true,
|
slug: true,
|
||||||
name: true,
|
|
||||||
color: true
|
color: true
|
||||||
})
|
})
|
||||||
|
.extend({ name: z.string() })
|
||||||
.array()
|
.array()
|
||||||
.optional()
|
.optional()
|
||||||
})
|
})
|
||||||
@@ -251,7 +257,8 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
|
|||||||
projectId: workspaceId,
|
projectId: workspaceId,
|
||||||
path: secretPath,
|
path: secretPath,
|
||||||
includeImports: req.query.include_imports,
|
includeImports: req.query.include_imports,
|
||||||
recursive: req.query.recursive
|
recursive: req.query.recursive,
|
||||||
|
tagSlugs: req.query.tagSlugs
|
||||||
});
|
});
|
||||||
|
|
||||||
await server.services.auditLog.createAuditLog({
|
await server.services.auditLog.createAuditLog({
|
||||||
@@ -325,9 +332,9 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
|
|||||||
tags: SecretTagsSchema.pick({
|
tags: SecretTagsSchema.pick({
|
||||||
id: true,
|
id: true,
|
||||||
slug: true,
|
slug: true,
|
||||||
name: true,
|
|
||||||
color: true
|
color: true
|
||||||
})
|
})
|
||||||
|
.extend({ name: z.string() })
|
||||||
.array()
|
.array()
|
||||||
.optional()
|
.optional()
|
||||||
})
|
})
|
||||||
@@ -731,9 +738,10 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
|
|||||||
tags: SecretTagsSchema.pick({
|
tags: SecretTagsSchema.pick({
|
||||||
id: true,
|
id: true,
|
||||||
slug: true,
|
slug: true,
|
||||||
name: true,
|
|
||||||
color: true
|
color: true
|
||||||
}).array()
|
})
|
||||||
|
.extend({ name: z.string() })
|
||||||
|
.array()
|
||||||
})
|
})
|
||||||
.array(),
|
.array(),
|
||||||
imports: z
|
imports: z
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import * as x509 from "@peculiar/x509";
|
import * as x509 from "@peculiar/x509";
|
||||||
import crypto from "crypto";
|
import crypto from "crypto";
|
||||||
|
|
||||||
import { BadRequestError } from "@app/lib/errors";
|
import { NotFoundError } from "@app/lib/errors";
|
||||||
import { getProjectKmsCertificateKeyId } from "@app/services/project/project-fns";
|
import { getProjectKmsCertificateKeyId } from "@app/services/project/project-fns";
|
||||||
|
|
||||||
import { CertKeyAlgorithm, CertStatus } from "../certificate/certificate-types";
|
import { CertKeyAlgorithm, CertStatus } from "../certificate/certificate-types";
|
||||||
@@ -106,10 +106,10 @@ export const getCaCredentials = async ({
|
|||||||
kmsService
|
kmsService
|
||||||
}: TGetCaCredentialsDTO) => {
|
}: TGetCaCredentialsDTO) => {
|
||||||
const ca = await certificateAuthorityDAL.findById(caId);
|
const ca = await certificateAuthorityDAL.findById(caId);
|
||||||
if (!ca) throw new BadRequestError({ message: "CA not found" });
|
if (!ca) throw new NotFoundError({ message: "CA not found" });
|
||||||
|
|
||||||
const caSecret = await certificateAuthoritySecretDAL.findOne({ caId });
|
const caSecret = await certificateAuthoritySecretDAL.findOne({ caId });
|
||||||
if (!caSecret) throw new BadRequestError({ message: "CA secret not found" });
|
if (!caSecret) throw new NotFoundError({ message: "CA secret not found" });
|
||||||
|
|
||||||
const keyId = await getProjectKmsCertificateKeyId({
|
const keyId = await getProjectKmsCertificateKeyId({
|
||||||
projectId: ca.projectId,
|
projectId: ca.projectId,
|
||||||
@@ -158,7 +158,7 @@ export const getCaCertChains = async ({
|
|||||||
kmsService
|
kmsService
|
||||||
}: TGetCaCertChainsDTO) => {
|
}: TGetCaCertChainsDTO) => {
|
||||||
const ca = await certificateAuthorityDAL.findById(caId);
|
const ca = await certificateAuthorityDAL.findById(caId);
|
||||||
if (!ca) throw new BadRequestError({ message: "CA not found" });
|
if (!ca) throw new NotFoundError({ message: "CA not found" });
|
||||||
|
|
||||||
const keyId = await getProjectKmsCertificateKeyId({
|
const keyId = await getProjectKmsCertificateKeyId({
|
||||||
projectId: ca.projectId,
|
projectId: ca.projectId,
|
||||||
@@ -195,19 +195,18 @@ export const getCaCertChains = async ({
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the decrypted pem-encoded certificate and certificate chain
|
* Return the decrypted pem-encoded certificate and certificate chain
|
||||||
* for CA with id [caId].
|
* corresponding to CA certificate with id [caCertId].
|
||||||
*/
|
*/
|
||||||
export const getCaCertChain = async ({
|
export const getCaCertChain = async ({
|
||||||
caId,
|
caCertId,
|
||||||
certificateAuthorityDAL,
|
certificateAuthorityDAL,
|
||||||
certificateAuthorityCertDAL,
|
certificateAuthorityCertDAL,
|
||||||
projectDAL,
|
projectDAL,
|
||||||
kmsService
|
kmsService
|
||||||
}: TGetCaCertChainDTO) => {
|
}: TGetCaCertChainDTO) => {
|
||||||
const ca = await certificateAuthorityDAL.findById(caId);
|
const caCert = await certificateAuthorityCertDAL.findById(caCertId);
|
||||||
if (!ca) throw new BadRequestError({ message: "CA not found" });
|
if (!caCert) throw new NotFoundError({ message: "CA certificate not found" });
|
||||||
|
const ca = await certificateAuthorityDAL.findById(caCert.caId);
|
||||||
const caCert = await certificateAuthorityCertDAL.findOne({ caId: ca.id });
|
|
||||||
|
|
||||||
const keyId = await getProjectKmsCertificateKeyId({
|
const keyId = await getProjectKmsCertificateKeyId({
|
||||||
projectId: ca.projectId,
|
projectId: ca.projectId,
|
||||||
@@ -250,7 +249,7 @@ export const rebuildCaCrl = async ({
|
|||||||
kmsService
|
kmsService
|
||||||
}: TRebuildCaCrlDTO) => {
|
}: TRebuildCaCrlDTO) => {
|
||||||
const ca = await certificateAuthorityDAL.findById(caId);
|
const ca = await certificateAuthorityDAL.findById(caId);
|
||||||
if (!ca) throw new BadRequestError({ message: "CA not found" });
|
if (!ca) throw new NotFoundError({ message: "CA not found" });
|
||||||
|
|
||||||
const caSecret = await certificateAuthoritySecretDAL.findOne({ caId: ca.id });
|
const caSecret = await certificateAuthoritySecretDAL.findOne({ caId: ca.id });
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import crypto, { KeyObject } from "crypto";
|
|||||||
import ms from "ms";
|
import ms from "ms";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { TCertificateAuthorities, TCertificateTemplates } from "@app/db/schemas";
|
||||||
import { TCertificateAuthorityEstConfigsUpdate } from "@app/db/schemas/certificate-authority-est-configs";
|
import { TCertificateAuthorityEstConfigsUpdate } from "@app/db/schemas/certificate-authority-est-configs";
|
||||||
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";
|
||||||
@@ -14,11 +15,15 @@ import { BadRequestError, NotFoundError } from "@app/lib/errors";
|
|||||||
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";
|
||||||
|
import { TPkiCollectionDALFactory } from "@app/services/pki-collection/pki-collection-dal";
|
||||||
|
import { TPkiCollectionItemDALFactory } from "@app/services/pki-collection/pki-collection-item-dal";
|
||||||
import { TProjectDALFactory } from "@app/services/project/project-dal";
|
import { TProjectDALFactory } from "@app/services/project/project-dal";
|
||||||
import { getProjectKmsCertificateKeyId } from "@app/services/project/project-fns";
|
import { getProjectKmsCertificateKeyId } from "@app/services/project/project-fns";
|
||||||
|
|
||||||
import { TCertificateAuthorityCrlDALFactory } from "../../ee/services/certificate-authority-crl/certificate-authority-crl-dal";
|
import { TCertificateAuthorityCrlDALFactory } from "../../ee/services/certificate-authority-crl/certificate-authority-crl-dal";
|
||||||
import { CertKeyAlgorithm, CertStatus } from "../certificate/certificate-types";
|
import { CertKeyAlgorithm, CertStatus } from "../certificate/certificate-types";
|
||||||
|
import { TCertificateTemplateDALFactory } from "../certificate-template/certificate-template-dal";
|
||||||
|
import { validateCertificateDetailsAgainstTemplate } from "../certificate-template/certificate-template-fns";
|
||||||
import { TCertificateAuthorityCertDALFactory } from "./certificate-authority-cert-dal";
|
import { TCertificateAuthorityCertDALFactory } from "./certificate-authority-cert-dal";
|
||||||
import { TCertificateAuthorityDALFactory } from "./certificate-authority-dal";
|
import { TCertificateAuthorityDALFactory } from "./certificate-authority-dal";
|
||||||
import { TCertificateAuthorityEstConfigDALFactory } from "./certificate-authority-est-config-dal";
|
import { TCertificateAuthorityEstConfigDALFactory } from "./certificate-authority-est-config-dal";
|
||||||
@@ -58,13 +63,19 @@ type TCertificateAuthorityServiceFactoryDep = {
|
|||||||
TCertificateAuthorityDALFactory,
|
TCertificateAuthorityDALFactory,
|
||||||
"transaction" | "create" | "findById" | "updateById" | "deleteById" | "findOne"
|
"transaction" | "create" | "findById" | "updateById" | "deleteById" | "findOne"
|
||||||
>;
|
>;
|
||||||
certificateAuthorityCertDAL: Pick<TCertificateAuthorityCertDALFactory, "create" | "findOne" | "transaction" | "find">;
|
certificateAuthorityCertDAL: Pick<
|
||||||
|
TCertificateAuthorityCertDALFactory,
|
||||||
|
"create" | "findOne" | "transaction" | "find" | "findById"
|
||||||
|
>;
|
||||||
certificateAuthoritySecretDAL: Pick<TCertificateAuthoritySecretDALFactory, "create" | "findOne">;
|
certificateAuthoritySecretDAL: Pick<TCertificateAuthoritySecretDALFactory, "create" | "findOne">;
|
||||||
certificateAuthorityCrlDAL: Pick<TCertificateAuthorityCrlDALFactory, "create" | "findOne" | "update">;
|
certificateAuthorityCrlDAL: Pick<TCertificateAuthorityCrlDALFactory, "create" | "findOne" | "update">;
|
||||||
|
certificateTemplateDAL: Pick<TCertificateTemplateDALFactory, "getById">;
|
||||||
certificateAuthorityQueue: TCertificateAuthorityQueueFactory; // TODO: Pick
|
certificateAuthorityQueue: TCertificateAuthorityQueueFactory; // TODO: Pick
|
||||||
certificateAuthorityEstConfigDAL: Pick<TCertificateAuthorityEstConfigDALFactory, "updateById" | "create" | "findOne">;
|
certificateAuthorityEstConfigDAL: Pick<TCertificateAuthorityEstConfigDALFactory, "updateById" | "create" | "findOne">;
|
||||||
certificateDAL: Pick<TCertificateDALFactory, "transaction" | "create" | "find">;
|
certificateDAL: Pick<TCertificateDALFactory, "transaction" | "create" | "find">;
|
||||||
certificateBodyDAL: Pick<TCertificateBodyDALFactory, "create">;
|
certificateBodyDAL: Pick<TCertificateBodyDALFactory, "create">;
|
||||||
|
pkiCollectionDAL: Pick<TPkiCollectionDALFactory, "findById">;
|
||||||
|
pkiCollectionItemDAL: Pick<TPkiCollectionItemDALFactory, "create">;
|
||||||
projectDAL: Pick<TProjectDALFactory, "findProjectBySlug" | "findOne" | "updateById" | "findById" | "transaction">;
|
projectDAL: Pick<TProjectDALFactory, "findProjectBySlug" | "findOne" | "updateById" | "findById" | "transaction">;
|
||||||
kmsService: Pick<TKmsServiceFactory, "generateKmsKey" | "encryptWithKmsKey" | "decryptWithKmsKey">;
|
kmsService: Pick<TKmsServiceFactory, "generateKmsKey" | "encryptWithKmsKey" | "decryptWithKmsKey">;
|
||||||
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission">;
|
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission">;
|
||||||
@@ -78,8 +89,11 @@ export const certificateAuthorityServiceFactory = ({
|
|||||||
certificateAuthoritySecretDAL,
|
certificateAuthoritySecretDAL,
|
||||||
certificateAuthorityEstConfigDAL,
|
certificateAuthorityEstConfigDAL,
|
||||||
certificateAuthorityCrlDAL,
|
certificateAuthorityCrlDAL,
|
||||||
|
certificateTemplateDAL,
|
||||||
certificateDAL,
|
certificateDAL,
|
||||||
certificateBodyDAL,
|
certificateBodyDAL,
|
||||||
|
pkiCollectionDAL,
|
||||||
|
pkiCollectionItemDAL,
|
||||||
projectDAL,
|
projectDAL,
|
||||||
kmsService,
|
kmsService,
|
||||||
permissionService
|
permissionService
|
||||||
@@ -162,8 +176,7 @@ export const certificateAuthorityServiceFactory = ({
|
|||||||
maxPathLength,
|
maxPathLength,
|
||||||
notBefore: notBeforeDate,
|
notBefore: notBeforeDate,
|
||||||
notAfter: notAfterDate,
|
notAfter: notAfterDate,
|
||||||
serialNumber,
|
serialNumber
|
||||||
activeCaCertVersion: 1
|
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
tx
|
tx
|
||||||
@@ -222,7 +235,7 @@ export const certificateAuthorityServiceFactory = ({
|
|||||||
plainText: Buffer.alloc(0)
|
plainText: Buffer.alloc(0)
|
||||||
});
|
});
|
||||||
|
|
||||||
await certificateAuthorityCertDAL.create(
|
const caCert = await certificateAuthorityCertDAL.create(
|
||||||
{
|
{
|
||||||
caId: ca.id,
|
caId: ca.id,
|
||||||
encryptedCertificate,
|
encryptedCertificate,
|
||||||
@@ -232,6 +245,14 @@ export const certificateAuthorityServiceFactory = ({
|
|||||||
},
|
},
|
||||||
tx
|
tx
|
||||||
);
|
);
|
||||||
|
|
||||||
|
await certificateAuthorityDAL.updateById(
|
||||||
|
ca.id,
|
||||||
|
{
|
||||||
|
activeCaCertId: caCert.id
|
||||||
|
},
|
||||||
|
tx
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// create empty CRL
|
// create empty CRL
|
||||||
@@ -356,9 +377,7 @@ export const certificateAuthorityServiceFactory = ({
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (ca.type === CaType.ROOT) throw new BadRequestError({ message: "Root CA cannot generate CSR" });
|
if (ca.type === CaType.ROOT) throw new BadRequestError({ message: "Root CA cannot generate CSR" });
|
||||||
|
if (ca.activeCaCertId) throw new BadRequestError({ message: "CA already has a certificate installed" });
|
||||||
const [caCert] = await certificateAuthorityCertDAL.find({ caId: ca.id }, { sort: [["version", "desc"]] });
|
|
||||||
if (caCert) throw new BadRequestError({ message: "CA already has a certificate installed" });
|
|
||||||
|
|
||||||
const { caPrivateKey, caPublicKey } = await getCaCredentials({
|
const { caPrivateKey, caPublicKey } = await getCaCredentials({
|
||||||
caId,
|
caId,
|
||||||
@@ -403,6 +422,8 @@ export const certificateAuthorityServiceFactory = ({
|
|||||||
const ca = await certificateAuthorityDAL.findById(caId);
|
const ca = await certificateAuthorityDAL.findById(caId);
|
||||||
if (!ca) throw new BadRequestError({ message: "CA not found" });
|
if (!ca) throw new BadRequestError({ message: "CA not found" });
|
||||||
|
|
||||||
|
if (!ca.activeCaCertId) throw new BadRequestError({ message: "CA does not have a certificate installed" });
|
||||||
|
|
||||||
const { permission } = await permissionService.getProjectPermission(
|
const { permission } = await permissionService.getProjectPermission(
|
||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
@@ -419,8 +440,7 @@ export const certificateAuthorityServiceFactory = ({
|
|||||||
if (ca.status === CaStatus.DISABLED) throw new BadRequestError({ message: "CA is disabled" });
|
if (ca.status === CaStatus.DISABLED) throw new BadRequestError({ message: "CA is disabled" });
|
||||||
|
|
||||||
// get latest CA certificate
|
// get latest CA certificate
|
||||||
const [caCert] = await certificateAuthorityCertDAL.find({ caId: ca.id }, { sort: [["version", "desc"]] });
|
const caCert = await certificateAuthorityCertDAL.findById(ca.activeCaCertId);
|
||||||
if (!caCert) throw new BadRequestError({ message: "CA does not have a certificate installed" });
|
|
||||||
|
|
||||||
const serialNumber = crypto.randomBytes(32).toString("hex");
|
const serialNumber = crypto.randomBytes(32).toString("hex");
|
||||||
|
|
||||||
@@ -498,13 +518,12 @@ export const certificateAuthorityServiceFactory = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
await certificateAuthorityDAL.transaction(async (tx) => {
|
await certificateAuthorityDAL.transaction(async (tx) => {
|
||||||
const newActiveCaCertVersion = caCert.version + 1;
|
const newCaCert = await certificateAuthorityCertDAL.create(
|
||||||
await certificateAuthorityCertDAL.create(
|
|
||||||
{
|
{
|
||||||
caId: ca.id,
|
caId: ca.id,
|
||||||
encryptedCertificate,
|
encryptedCertificate,
|
||||||
encryptedCertificateChain,
|
encryptedCertificateChain,
|
||||||
version: newActiveCaCertVersion,
|
version: caCert.version + 1,
|
||||||
caSecretId: caSecret.id
|
caSecretId: caSecret.id
|
||||||
},
|
},
|
||||||
tx
|
tx
|
||||||
@@ -513,7 +532,7 @@ export const certificateAuthorityServiceFactory = ({
|
|||||||
await certificateAuthorityDAL.updateById(
|
await certificateAuthorityDAL.updateById(
|
||||||
ca.id,
|
ca.id,
|
||||||
{
|
{
|
||||||
activeCaCertVersion: newActiveCaCertVersion,
|
activeCaCertId: newCaCert.id,
|
||||||
notBefore: notBeforeDate,
|
notBefore: notBeforeDate,
|
||||||
notAfter: new Date(notAfter)
|
notAfter: new Date(notAfter)
|
||||||
},
|
},
|
||||||
@@ -542,10 +561,9 @@ export const certificateAuthorityServiceFactory = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
// get latest parent CA certificate
|
// get latest parent CA certificate
|
||||||
const [parentCaCert] = await certificateAuthorityCertDAL.find(
|
if (!parentCa.activeCaCertId)
|
||||||
{ caId: parentCa.id },
|
throw new BadRequestError({ message: "Parent CA does not have a certificate installed" });
|
||||||
{ sort: [["version", "desc"]] }
|
const parentCaCert = await certificateAuthorityCertDAL.findById(parentCa.activeCaCertId);
|
||||||
);
|
|
||||||
|
|
||||||
const decryptedParentCaCert = await kmsDecryptor({
|
const decryptedParentCaCert = await kmsDecryptor({
|
||||||
cipherTextBlob: parentCaCert.encryptedCertificate
|
cipherTextBlob: parentCaCert.encryptedCertificate
|
||||||
@@ -590,7 +608,7 @@ export const certificateAuthorityServiceFactory = ({
|
|||||||
const intermediateCert = await x509.X509CertificateGenerator.create({
|
const intermediateCert = await x509.X509CertificateGenerator.create({
|
||||||
serialNumber,
|
serialNumber,
|
||||||
subject: csrObj.subject,
|
subject: csrObj.subject,
|
||||||
issuer: caCertObj.subject,
|
issuer: parentCaCertObj.subject,
|
||||||
notBefore: notBeforeDate,
|
notBefore: notBeforeDate,
|
||||||
notAfter: new Date(notAfter),
|
notAfter: new Date(notAfter),
|
||||||
signingKey: parentCaPrivateKey,
|
signingKey: parentCaPrivateKey,
|
||||||
@@ -609,7 +627,7 @@ export const certificateAuthorityServiceFactory = ({
|
|||||||
ca.maxPathLength === -1 || !ca.maxPathLength ? undefined : ca.maxPathLength,
|
ca.maxPathLength === -1 || !ca.maxPathLength ? undefined : ca.maxPathLength,
|
||||||
true
|
true
|
||||||
),
|
),
|
||||||
await x509.AuthorityKeyIdentifierExtension.create(caCertObj, false),
|
await x509.AuthorityKeyIdentifierExtension.create(parentCaCertObj, false),
|
||||||
await x509.SubjectKeyIdentifierExtension.create(csrObj.publicKey)
|
await x509.SubjectKeyIdentifierExtension.create(csrObj.publicKey)
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
@@ -619,7 +637,7 @@ export const certificateAuthorityServiceFactory = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const { caCert: parentCaCertificate, caCertChain: parentCaCertChain } = await getCaCertChain({
|
const { caCert: parentCaCertificate, caCertChain: parentCaCertChain } = await getCaCertChain({
|
||||||
caId: parentCa.id,
|
caCertId: parentCa.activeCaCertId,
|
||||||
certificateAuthorityDAL,
|
certificateAuthorityDAL,
|
||||||
certificateAuthorityCertDAL,
|
certificateAuthorityCertDAL,
|
||||||
projectDAL,
|
projectDAL,
|
||||||
@@ -633,13 +651,12 @@ export const certificateAuthorityServiceFactory = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
await certificateAuthorityDAL.transaction(async (tx) => {
|
await certificateAuthorityDAL.transaction(async (tx) => {
|
||||||
const newActiveCaCertVersion = caCert.version + 1;
|
const newCaCert = await certificateAuthorityCertDAL.create(
|
||||||
await certificateAuthorityCertDAL.create(
|
|
||||||
{
|
{
|
||||||
caId: ca.id,
|
caId: ca.id,
|
||||||
encryptedCertificate,
|
encryptedCertificate,
|
||||||
encryptedCertificateChain,
|
encryptedCertificateChain,
|
||||||
version: newActiveCaCertVersion,
|
version: caCert.version + 1,
|
||||||
caSecretId: caSecret.id
|
caSecretId: caSecret.id
|
||||||
},
|
},
|
||||||
tx
|
tx
|
||||||
@@ -648,7 +665,7 @@ export const certificateAuthorityServiceFactory = ({
|
|||||||
await certificateAuthorityDAL.updateById(
|
await certificateAuthorityDAL.updateById(
|
||||||
ca.id,
|
ca.id,
|
||||||
{
|
{
|
||||||
activeCaCertVersion: newActiveCaCertVersion,
|
activeCaCertId: newCaCert.id,
|
||||||
notBefore: notBeforeDate,
|
notBefore: notBeforeDate,
|
||||||
notAfter: new Date(notAfter)
|
notAfter: new Date(notAfter)
|
||||||
},
|
},
|
||||||
@@ -707,11 +724,11 @@ export const certificateAuthorityServiceFactory = ({
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Return current certificate and certificate chain for CA
|
* Return current certificate and certificate chain for CA
|
||||||
* get latest?? ca cert
|
|
||||||
*/
|
*/
|
||||||
const getCaCert = async ({ caId, actorId, actorAuthMethod, actor, actorOrgId }: TGetCaCertDTO) => {
|
const getCaCert = async ({ caId, actorId, actorAuthMethod, actor, actorOrgId }: TGetCaCertDTO) => {
|
||||||
const ca = await certificateAuthorityDAL.findById(caId);
|
const ca = await certificateAuthorityDAL.findById(caId);
|
||||||
if (!ca) throw new BadRequestError({ message: "CA not found" });
|
if (!ca) throw new BadRequestError({ message: "CA not found" });
|
||||||
|
if (!ca.activeCaCertId) throw new BadRequestError({ message: "CA does not have a certificate installed" });
|
||||||
|
|
||||||
const { permission } = await permissionService.getProjectPermission(
|
const { permission } = await permissionService.getProjectPermission(
|
||||||
actor,
|
actor,
|
||||||
@@ -727,7 +744,7 @@ export const certificateAuthorityServiceFactory = ({
|
|||||||
);
|
);
|
||||||
|
|
||||||
const { caCert, caCertChain, serialNumber } = await getCaCertChain({
|
const { caCert, caCertChain, serialNumber } = await getCaCertChain({
|
||||||
caId,
|
caCertId: ca.activeCaCertId,
|
||||||
certificateAuthorityDAL,
|
certificateAuthorityDAL,
|
||||||
certificateAuthorityCertDAL,
|
certificateAuthorityCertDAL,
|
||||||
projectDAL,
|
projectDAL,
|
||||||
@@ -773,9 +790,9 @@ export const certificateAuthorityServiceFactory = ({
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (ca.status === CaStatus.DISABLED) throw new BadRequestError({ message: "CA is disabled" });
|
if (ca.status === CaStatus.DISABLED) throw new BadRequestError({ message: "CA is disabled" });
|
||||||
|
if (!ca.activeCaCertId) throw new BadRequestError({ message: "CA does not have a certificate installed" });
|
||||||
|
|
||||||
const [caCert] = await certificateAuthorityCertDAL.find({ caId: ca.id }, { sort: [["version", "desc"]] });
|
const caCert = await certificateAuthorityCertDAL.findById(ca.activeCaCertId);
|
||||||
if (!caCert) throw new BadRequestError({ message: "CA does not have a certificate installed" });
|
|
||||||
|
|
||||||
if (ca.notAfter && new Date() > new Date(ca.notAfter)) {
|
if (ca.notAfter && new Date() > new Date(ca.notAfter)) {
|
||||||
throw new BadRequestError({ message: "CA is expired" });
|
throw new BadRequestError({ message: "CA is expired" });
|
||||||
@@ -863,7 +880,7 @@ export const certificateAuthorityServiceFactory = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const { caCert: issuingCaCertificate, caCertChain } = await getCaCertChain({
|
const { caCert: issuingCaCertificate, caCertChain } = await getCaCertChain({
|
||||||
caId,
|
caCertId: ca.activeCaCertId,
|
||||||
certificateAuthorityDAL,
|
certificateAuthorityDAL,
|
||||||
certificateAuthorityCertDAL,
|
certificateAuthorityCertDAL,
|
||||||
projectDAL,
|
projectDAL,
|
||||||
@@ -909,8 +926,7 @@ export const certificateAuthorityServiceFactory = ({
|
|||||||
ProjectPermissionSub.CertificateAuthorities
|
ProjectPermissionSub.CertificateAuthorities
|
||||||
);
|
);
|
||||||
|
|
||||||
const [caCert] = await certificateAuthorityCertDAL.find({ caId: ca.id }, { sort: [["version", "desc"]] });
|
if (ca.activeCaCertId) throw new BadRequestError({ message: "CA has already imported a certificate" });
|
||||||
if (caCert) throw new BadRequestError({ message: "CA has already imported a certificate" });
|
|
||||||
|
|
||||||
const certObj = new x509.X509Certificate(certificate);
|
const certObj = new x509.X509Certificate(certificate);
|
||||||
const maxPathLength = certObj.getExtension(x509.BasicConstraintsExtension)?.pathLength;
|
const maxPathLength = certObj.getExtension(x509.BasicConstraintsExtension)?.pathLength;
|
||||||
@@ -976,7 +992,7 @@ export const certificateAuthorityServiceFactory = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
await certificateAuthorityCertDAL.transaction(async (tx) => {
|
await certificateAuthorityCertDAL.transaction(async (tx) => {
|
||||||
await certificateAuthorityCertDAL.create(
|
const newCaCert = await certificateAuthorityCertDAL.create(
|
||||||
{
|
{
|
||||||
caId: ca.id,
|
caId: ca.id,
|
||||||
encryptedCertificate,
|
encryptedCertificate,
|
||||||
@@ -995,7 +1011,8 @@ export const certificateAuthorityServiceFactory = ({
|
|||||||
notBefore: new Date(certObj.notBefore),
|
notBefore: new Date(certObj.notBefore),
|
||||||
notAfter: new Date(certObj.notAfter),
|
notAfter: new Date(certObj.notAfter),
|
||||||
serialNumber: certObj.serialNumber,
|
serialNumber: certObj.serialNumber,
|
||||||
parentCaId: parentCa?.id
|
parentCaId: parentCa?.id,
|
||||||
|
activeCaCertId: newCaCert.id
|
||||||
},
|
},
|
||||||
tx
|
tx
|
||||||
);
|
);
|
||||||
@@ -1010,6 +1027,8 @@ export const certificateAuthorityServiceFactory = ({
|
|||||||
*/
|
*/
|
||||||
const issueCertFromCa = async ({
|
const issueCertFromCa = async ({
|
||||||
caId,
|
caId,
|
||||||
|
certificateTemplateId,
|
||||||
|
pkiCollectionId,
|
||||||
friendlyName,
|
friendlyName,
|
||||||
commonName,
|
commonName,
|
||||||
altNames,
|
altNames,
|
||||||
@@ -1021,8 +1040,27 @@ export const certificateAuthorityServiceFactory = ({
|
|||||||
actor,
|
actor,
|
||||||
actorOrgId
|
actorOrgId
|
||||||
}: TIssueCertFromCaDTO) => {
|
}: TIssueCertFromCaDTO) => {
|
||||||
const ca = await certificateAuthorityDAL.findById(caId);
|
let ca: TCertificateAuthorities | undefined;
|
||||||
if (!ca) throw new BadRequestError({ message: "CA not found" });
|
let certificateTemplate: TCertificateTemplates | undefined;
|
||||||
|
let collectionId = pkiCollectionId;
|
||||||
|
|
||||||
|
if (caId) {
|
||||||
|
ca = await certificateAuthorityDAL.findById(caId);
|
||||||
|
} else if (certificateTemplateId) {
|
||||||
|
certificateTemplate = await certificateTemplateDAL.getById(certificateTemplateId);
|
||||||
|
if (!certificateTemplate) {
|
||||||
|
throw new NotFoundError({
|
||||||
|
message: "Certificate template not found"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
collectionId = certificateTemplate.pkiCollectionId as string;
|
||||||
|
ca = await certificateAuthorityDAL.findById(certificateTemplate.caId);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!ca) {
|
||||||
|
throw new BadRequestError({ message: "CA not found" });
|
||||||
|
}
|
||||||
|
|
||||||
const { permission } = await permissionService.getProjectPermission(
|
const { permission } = await permissionService.getProjectPermission(
|
||||||
actor,
|
actor,
|
||||||
@@ -1035,14 +1073,20 @@ export const certificateAuthorityServiceFactory = ({
|
|||||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Create, ProjectPermissionSub.Certificates);
|
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Create, ProjectPermissionSub.Certificates);
|
||||||
|
|
||||||
if (ca.status === CaStatus.DISABLED) throw new BadRequestError({ message: "CA is disabled" });
|
if (ca.status === CaStatus.DISABLED) throw new BadRequestError({ message: "CA is disabled" });
|
||||||
|
if (!ca.activeCaCertId) throw new BadRequestError({ message: "CA does not have a certificate installed" });
|
||||||
const [caCert] = await certificateAuthorityCertDAL.find({ caId: ca.id }, { sort: [["version", "desc"]] });
|
const caCert = await certificateAuthorityCertDAL.findById(ca.activeCaCertId);
|
||||||
if (!caCert) throw new BadRequestError({ message: "CA does not have a certificate installed" });
|
|
||||||
|
|
||||||
if (ca.notAfter && new Date() > new Date(ca.notAfter)) {
|
if (ca.notAfter && new Date() > new Date(ca.notAfter)) {
|
||||||
throw new BadRequestError({ message: "CA is expired" });
|
throw new BadRequestError({ message: "CA is expired" });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// check PKI collection
|
||||||
|
if (collectionId) {
|
||||||
|
const pkiCollection = await pkiCollectionDAL.findById(collectionId);
|
||||||
|
if (!pkiCollection) throw new NotFoundError({ message: "PKI collection not found" });
|
||||||
|
if (pkiCollection.projectId !== ca.projectId) throw new BadRequestError({ message: "Invalid PKI collection" });
|
||||||
|
}
|
||||||
|
|
||||||
const certificateManagerKmsId = await getProjectKmsCertificateKeyId({
|
const certificateManagerKmsId = await getProjectKmsCertificateKeyId({
|
||||||
projectId: ca.projectId,
|
projectId: ca.projectId,
|
||||||
projectDAL,
|
projectDAL,
|
||||||
@@ -1111,11 +1155,13 @@ export const certificateAuthorityServiceFactory = ({
|
|||||||
await x509.SubjectKeyIdentifierExtension.create(csrObj.publicKey)
|
await x509.SubjectKeyIdentifierExtension.create(csrObj.publicKey)
|
||||||
];
|
];
|
||||||
|
|
||||||
|
let altNamesArray: {
|
||||||
|
type: "email" | "dns";
|
||||||
|
value: string;
|
||||||
|
}[] = [];
|
||||||
|
|
||||||
if (altNames) {
|
if (altNames) {
|
||||||
const altNamesArray: {
|
altNamesArray = altNames
|
||||||
type: "email" | "dns";
|
|
||||||
value: string;
|
|
||||||
}[] = altNames
|
|
||||||
.split(",")
|
.split(",")
|
||||||
.map((name) => name.trim())
|
.map((name) => name.trim())
|
||||||
.map((altName) => {
|
.map((altName) => {
|
||||||
@@ -1143,6 +1189,18 @@ export const certificateAuthorityServiceFactory = ({
|
|||||||
extensions.push(altNamesExtension);
|
extensions.push(altNamesExtension);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (certificateTemplate) {
|
||||||
|
validateCertificateDetailsAgainstTemplate(
|
||||||
|
{
|
||||||
|
commonName,
|
||||||
|
notBeforeDate,
|
||||||
|
notAfterDate,
|
||||||
|
altNames: altNamesArray.map((entry) => entry.value)
|
||||||
|
},
|
||||||
|
certificateTemplate
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const serialNumber = crypto.randomBytes(32).toString("hex");
|
const serialNumber = crypto.randomBytes(32).toString("hex");
|
||||||
const leafCert = await x509.X509CertificateGenerator.create({
|
const leafCert = await x509.X509CertificateGenerator.create({
|
||||||
serialNumber,
|
serialNumber,
|
||||||
@@ -1169,7 +1227,9 @@ export const certificateAuthorityServiceFactory = ({
|
|||||||
await certificateDAL.transaction(async (tx) => {
|
await certificateDAL.transaction(async (tx) => {
|
||||||
const cert = await certificateDAL.create(
|
const cert = await certificateDAL.create(
|
||||||
{
|
{
|
||||||
caId: ca.id,
|
caId: (ca as TCertificateAuthorities).id,
|
||||||
|
caCertId: caCert.id,
|
||||||
|
certificateTemplateId: certificateTemplate?.id,
|
||||||
status: CertStatus.ACTIVE,
|
status: CertStatus.ACTIVE,
|
||||||
friendlyName: friendlyName || commonName,
|
friendlyName: friendlyName || commonName,
|
||||||
commonName,
|
commonName,
|
||||||
@@ -1189,11 +1249,21 @@ export const certificateAuthorityServiceFactory = ({
|
|||||||
tx
|
tx
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (collectionId) {
|
||||||
|
await pkiCollectionItemDAL.create(
|
||||||
|
{
|
||||||
|
pkiCollectionId: collectionId,
|
||||||
|
certId: cert.id
|
||||||
|
},
|
||||||
|
tx
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return cert;
|
return cert;
|
||||||
});
|
});
|
||||||
|
|
||||||
const { caCert: issuingCaCertificate, caCertChain } = await getCaCertChain({
|
const { caCert: issuingCaCertificate, caCertChain } = await getCaCertChain({
|
||||||
caId: ca.id,
|
caCertId: caCert.id,
|
||||||
certificateAuthorityDAL,
|
certificateAuthorityDAL,
|
||||||
certificateAuthorityCertDAL,
|
certificateAuthorityCertDAL,
|
||||||
projectDAL,
|
projectDAL,
|
||||||
@@ -1215,8 +1285,41 @@ export const certificateAuthorityServiceFactory = ({
|
|||||||
* Note: CSR is generated externally and submitted to Infisical.
|
* Note: CSR is generated externally and submitted to Infisical.
|
||||||
*/
|
*/
|
||||||
const signCertFromCa = async (dto: TSignCertFromCaDTO) => {
|
const signCertFromCa = async (dto: TSignCertFromCaDTO) => {
|
||||||
const ca = await certificateAuthorityDAL.findById(dto.caId);
|
let ca: TCertificateAuthorities | undefined;
|
||||||
if (!ca) throw new BadRequestError({ message: "CA not found" });
|
let certificateTemplate: TCertificateTemplates | undefined;
|
||||||
|
|
||||||
|
const {
|
||||||
|
caId,
|
||||||
|
certificateTemplateId,
|
||||||
|
csr,
|
||||||
|
pkiCollectionId,
|
||||||
|
friendlyName,
|
||||||
|
commonName,
|
||||||
|
altNames,
|
||||||
|
ttl,
|
||||||
|
notBefore,
|
||||||
|
notAfter
|
||||||
|
} = dto;
|
||||||
|
|
||||||
|
let collectionId = pkiCollectionId;
|
||||||
|
|
||||||
|
if (caId) {
|
||||||
|
ca = await certificateAuthorityDAL.findById(caId);
|
||||||
|
} else if (certificateTemplateId) {
|
||||||
|
certificateTemplate = await certificateTemplateDAL.getById(certificateTemplateId);
|
||||||
|
if (!certificateTemplate) {
|
||||||
|
throw new NotFoundError({
|
||||||
|
message: "Certificate template not found"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
collectionId = certificateTemplate.pkiCollectionId as string;
|
||||||
|
ca = await certificateAuthorityDAL.findById(certificateTemplate.caId);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!ca) {
|
||||||
|
throw new BadRequestError({ message: "CA not found" });
|
||||||
|
}
|
||||||
|
|
||||||
if (!dto.isInternal) {
|
if (!dto.isInternal) {
|
||||||
const { permission } = await permissionService.getProjectPermission(
|
const { permission } = await permissionService.getProjectPermission(
|
||||||
@@ -1233,17 +1336,22 @@ export const certificateAuthorityServiceFactory = ({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const { csr, friendlyName, commonName, altNames, ttl, notBefore, notAfter } = dto;
|
|
||||||
|
|
||||||
if (ca.status === CaStatus.DISABLED) throw new BadRequestError({ message: "CA is disabled" });
|
if (ca.status === CaStatus.DISABLED) throw new BadRequestError({ message: "CA is disabled" });
|
||||||
|
if (!ca.activeCaCertId) throw new BadRequestError({ message: "CA does not have a certificate installed" });
|
||||||
|
|
||||||
const [caCert] = await certificateAuthorityCertDAL.find({ caId: ca.id }, { sort: [["version", "desc"]] });
|
const caCert = await certificateAuthorityCertDAL.findById(ca.activeCaCertId);
|
||||||
if (!caCert) throw new BadRequestError({ message: "CA does not have a certificate installed" });
|
|
||||||
|
|
||||||
if (ca.notAfter && new Date() > new Date(ca.notAfter)) {
|
if (ca.notAfter && new Date() > new Date(ca.notAfter)) {
|
||||||
throw new BadRequestError({ message: "CA is expired" });
|
throw new BadRequestError({ message: "CA is expired" });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// check PKI collection
|
||||||
|
if (pkiCollectionId) {
|
||||||
|
const pkiCollection = await pkiCollectionDAL.findById(pkiCollectionId);
|
||||||
|
if (!pkiCollection) throw new NotFoundError({ message: "PKI collection not found" });
|
||||||
|
if (pkiCollection.projectId !== ca.projectId) throw new BadRequestError({ message: "Invalid PKI collection" });
|
||||||
|
}
|
||||||
|
|
||||||
const certificateManagerKmsId = await getProjectKmsCertificateKeyId({
|
const certificateManagerKmsId = await getProjectKmsCertificateKeyId({
|
||||||
projectId: ca.projectId,
|
projectId: ca.projectId,
|
||||||
projectDAL,
|
projectDAL,
|
||||||
@@ -1311,11 +1419,12 @@ export const certificateAuthorityServiceFactory = ({
|
|||||||
await x509.SubjectKeyIdentifierExtension.create(csrObj.publicKey)
|
await x509.SubjectKeyIdentifierExtension.create(csrObj.publicKey)
|
||||||
];
|
];
|
||||||
|
|
||||||
|
let altNamesArray: {
|
||||||
|
type: "email" | "dns";
|
||||||
|
value: string;
|
||||||
|
}[] = [];
|
||||||
if (altNames) {
|
if (altNames) {
|
||||||
const altNamesArray: {
|
altNamesArray = altNames
|
||||||
type: "email" | "dns";
|
|
||||||
value: string;
|
|
||||||
}[] = altNames
|
|
||||||
.split(",")
|
.split(",")
|
||||||
.map((name) => name.trim())
|
.map((name) => name.trim())
|
||||||
.map((altName) => {
|
.map((altName) => {
|
||||||
@@ -1343,6 +1452,18 @@ export const certificateAuthorityServiceFactory = ({
|
|||||||
extensions.push(altNamesExtension);
|
extensions.push(altNamesExtension);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (certificateTemplate) {
|
||||||
|
validateCertificateDetailsAgainstTemplate(
|
||||||
|
{
|
||||||
|
commonName: cn,
|
||||||
|
notBeforeDate,
|
||||||
|
notAfterDate,
|
||||||
|
altNames: altNamesArray.map((entry) => entry.value)
|
||||||
|
},
|
||||||
|
certificateTemplate
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const serialNumber = crypto.randomBytes(32).toString("hex");
|
const serialNumber = crypto.randomBytes(32).toString("hex");
|
||||||
const leafCert = await x509.X509CertificateGenerator.create({
|
const leafCert = await x509.X509CertificateGenerator.create({
|
||||||
serialNumber,
|
serialNumber,
|
||||||
@@ -1366,7 +1487,9 @@ export const certificateAuthorityServiceFactory = ({
|
|||||||
await certificateDAL.transaction(async (tx) => {
|
await certificateDAL.transaction(async (tx) => {
|
||||||
const cert = await certificateDAL.create(
|
const cert = await certificateDAL.create(
|
||||||
{
|
{
|
||||||
caId: ca.id,
|
caId: (ca as TCertificateAuthorities).id,
|
||||||
|
caCertId: caCert.id,
|
||||||
|
certificateTemplateId: certificateTemplate?.id,
|
||||||
status: CertStatus.ACTIVE,
|
status: CertStatus.ACTIVE,
|
||||||
friendlyName: friendlyName || csrObj.subject,
|
friendlyName: friendlyName || csrObj.subject,
|
||||||
commonName: cn,
|
commonName: cn,
|
||||||
@@ -1386,11 +1509,21 @@ export const certificateAuthorityServiceFactory = ({
|
|||||||
tx
|
tx
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (collectionId) {
|
||||||
|
await pkiCollectionItemDAL.create(
|
||||||
|
{
|
||||||
|
pkiCollectionId: collectionId,
|
||||||
|
certId: cert.id
|
||||||
|
},
|
||||||
|
tx
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return cert;
|
return cert;
|
||||||
});
|
});
|
||||||
|
|
||||||
const { caCert: issuingCaCertificate, caCertChain } = await getCaCertChain({
|
const { caCert: issuingCaCertificate, caCertChain } = await getCaCertChain({
|
||||||
caId: ca.id,
|
caCertId: ca.activeCaCertId,
|
||||||
certificateAuthorityDAL,
|
certificateAuthorityDAL,
|
||||||
certificateAuthorityCertDAL,
|
certificateAuthorityCertDAL,
|
||||||
projectDAL,
|
projectDAL,
|
||||||
|
|||||||
@@ -86,7 +86,9 @@ export type TImportCertToCaDTO = {
|
|||||||
} & Omit<TProjectPermission, "projectId">;
|
} & Omit<TProjectPermission, "projectId">;
|
||||||
|
|
||||||
export type TIssueCertFromCaDTO = {
|
export type TIssueCertFromCaDTO = {
|
||||||
caId: string;
|
caId?: string;
|
||||||
|
certificateTemplateId?: string;
|
||||||
|
pkiCollectionId?: string;
|
||||||
friendlyName?: string;
|
friendlyName?: string;
|
||||||
commonName: string;
|
commonName: string;
|
||||||
altNames: string;
|
altNames: string;
|
||||||
@@ -98,8 +100,10 @@ export type TIssueCertFromCaDTO = {
|
|||||||
export type TSignCertFromCaDTO =
|
export type TSignCertFromCaDTO =
|
||||||
| {
|
| {
|
||||||
isInternal: true;
|
isInternal: true;
|
||||||
caId: string;
|
caId?: string;
|
||||||
csr: string;
|
csr: string;
|
||||||
|
certificateTemplateId?: string;
|
||||||
|
pkiCollectionId?: string;
|
||||||
friendlyName?: string;
|
friendlyName?: string;
|
||||||
commonName?: string;
|
commonName?: string;
|
||||||
altNames: string;
|
altNames: string;
|
||||||
@@ -109,8 +113,10 @@ export type TSignCertFromCaDTO =
|
|||||||
}
|
}
|
||||||
| ({
|
| ({
|
||||||
isInternal: false;
|
isInternal: false;
|
||||||
caId: string;
|
caId?: string;
|
||||||
csr: string;
|
csr: string;
|
||||||
|
certificateTemplateId?: string;
|
||||||
|
pkiCollectionId?: string;
|
||||||
friendlyName?: string;
|
friendlyName?: string;
|
||||||
commonName?: string;
|
commonName?: string;
|
||||||
altNames: string;
|
altNames: string;
|
||||||
@@ -145,9 +151,9 @@ export type TGetCaCertChainsDTO = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export type TGetCaCertChainDTO = {
|
export type TGetCaCertChainDTO = {
|
||||||
caId: string;
|
caCertId: string;
|
||||||
certificateAuthorityDAL: Pick<TCertificateAuthorityDALFactory, "findById">;
|
certificateAuthorityDAL: Pick<TCertificateAuthorityDALFactory, "findById">;
|
||||||
certificateAuthorityCertDAL: Pick<TCertificateAuthorityCertDALFactory, "findOne">;
|
certificateAuthorityCertDAL: Pick<TCertificateAuthorityCertDALFactory, "findById">;
|
||||||
projectDAL: Pick<TProjectDALFactory, "findOne" | "updateById" | "transaction">;
|
projectDAL: Pick<TProjectDALFactory, "findOne" | "updateById" | "transaction">;
|
||||||
kmsService: Pick<TKmsServiceFactory, "decryptWithKmsKey" | "generateKmsKey">;
|
kmsService: Pick<TKmsServiceFactory, "decryptWithKmsKey" | "generateKmsKey">;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,57 @@
|
|||||||
|
import { TDbClient } from "@app/db";
|
||||||
|
import { TableName } from "@app/db/schemas";
|
||||||
|
import { DatabaseError } from "@app/lib/errors";
|
||||||
|
import { ormify, selectAllTableCols } from "@app/lib/knex";
|
||||||
|
|
||||||
|
export type TCertificateTemplateDALFactory = ReturnType<typeof certificateTemplateDALFactory>;
|
||||||
|
|
||||||
|
export const certificateTemplateDALFactory = (db: TDbClient) => {
|
||||||
|
const certificateTemplateOrm = ormify(db, TableName.CertificateTemplate);
|
||||||
|
|
||||||
|
const getCertTemplatesByProjectId = async (projectId: string) => {
|
||||||
|
try {
|
||||||
|
const certTemplates = await db
|
||||||
|
.replicaNode()(TableName.CertificateTemplate)
|
||||||
|
.join(
|
||||||
|
TableName.CertificateAuthority,
|
||||||
|
`${TableName.CertificateAuthority}.id`,
|
||||||
|
`${TableName.CertificateTemplate}.caId`
|
||||||
|
)
|
||||||
|
.where(`${TableName.CertificateAuthority}.projectId`, "=", projectId)
|
||||||
|
.select(selectAllTableCols(TableName.CertificateTemplate))
|
||||||
|
.select(
|
||||||
|
db.ref("friendlyName").as("caName").withSchema(TableName.CertificateAuthority),
|
||||||
|
db.ref("projectId").withSchema(TableName.CertificateAuthority)
|
||||||
|
);
|
||||||
|
|
||||||
|
return certTemplates;
|
||||||
|
} catch (error) {
|
||||||
|
throw new DatabaseError({ error, name: "Get certificate templates by project ID" });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getById = async (id: string) => {
|
||||||
|
try {
|
||||||
|
const certTemplate = await db
|
||||||
|
.replicaNode()(TableName.CertificateTemplate)
|
||||||
|
.join(
|
||||||
|
TableName.CertificateAuthority,
|
||||||
|
`${TableName.CertificateAuthority}.id`,
|
||||||
|
`${TableName.CertificateTemplate}.caId`
|
||||||
|
)
|
||||||
|
.where(`${TableName.CertificateTemplate}.id`, "=", id)
|
||||||
|
.select(selectAllTableCols(TableName.CertificateTemplate))
|
||||||
|
.select(
|
||||||
|
db.ref("projectId").withSchema(TableName.CertificateAuthority),
|
||||||
|
db.ref("friendlyName").as("caName").withSchema(TableName.CertificateAuthority)
|
||||||
|
)
|
||||||
|
.first();
|
||||||
|
|
||||||
|
return certTemplate;
|
||||||
|
} catch (error) {
|
||||||
|
throw new DatabaseError({ error, name: "Get certificate template by ID" });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return { ...certificateTemplateOrm, getCertTemplatesByProjectId, getById };
|
||||||
|
};
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
import ms from "ms";
|
||||||
|
|
||||||
|
import { TCertificateTemplates } from "@app/db/schemas";
|
||||||
|
import { BadRequestError } from "@app/lib/errors";
|
||||||
|
|
||||||
|
export const validateCertificateDetailsAgainstTemplate = (
|
||||||
|
cert: {
|
||||||
|
commonName: string;
|
||||||
|
notBeforeDate: Date;
|
||||||
|
notAfterDate: Date;
|
||||||
|
altNames: string[];
|
||||||
|
},
|
||||||
|
template: TCertificateTemplates
|
||||||
|
) => {
|
||||||
|
const commonNameRegex = new RegExp(template.commonName);
|
||||||
|
if (!commonNameRegex.test(cert.commonName)) {
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: "Invalid common name based on template policy"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cert.notAfterDate.getTime() - cert.notBeforeDate.getTime() > ms(template.ttl)) {
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: "Invalid validity date based on template policy"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const subjectAlternativeNameRegex = new RegExp(template.subjectAlternativeName);
|
||||||
|
cert.altNames.forEach((altName) => {
|
||||||
|
if (!subjectAlternativeNameRegex.test(altName)) {
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: "Invalid subject alternative name based on template policy"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
import z from "zod";
|
||||||
|
|
||||||
|
import { CertificateTemplatesSchema } from "@app/db/schemas";
|
||||||
|
|
||||||
|
export const sanitizedCertificateTemplate = CertificateTemplatesSchema.pick({
|
||||||
|
id: true,
|
||||||
|
caId: true,
|
||||||
|
name: true,
|
||||||
|
commonName: true,
|
||||||
|
subjectAlternativeName: true,
|
||||||
|
pkiCollectionId: true,
|
||||||
|
ttl: true
|
||||||
|
}).merge(
|
||||||
|
z.object({
|
||||||
|
projectId: z.string(),
|
||||||
|
caName: z.string()
|
||||||
|
})
|
||||||
|
);
|
||||||
@@ -0,0 +1,196 @@
|
|||||||
|
import { ForbiddenError } from "@casl/ability";
|
||||||
|
|
||||||
|
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
||||||
|
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
|
||||||
|
import { BadRequestError, NotFoundError } from "@app/lib/errors";
|
||||||
|
|
||||||
|
import { TCertificateAuthorityDALFactory } from "../certificate-authority/certificate-authority-dal";
|
||||||
|
import { TCertificateTemplateDALFactory } from "./certificate-template-dal";
|
||||||
|
import {
|
||||||
|
TCreateCertTemplateDTO,
|
||||||
|
TDeleteCertTemplateDTO,
|
||||||
|
TGetCertTemplateDTO,
|
||||||
|
TUpdateCertTemplateDTO
|
||||||
|
} from "./certificate-template-types";
|
||||||
|
|
||||||
|
type TCertificateTemplateServiceFactoryDep = {
|
||||||
|
certificateTemplateDAL: TCertificateTemplateDALFactory;
|
||||||
|
certificateAuthorityDAL: Pick<TCertificateAuthorityDALFactory, "findById">;
|
||||||
|
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission">;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TCertificateTemplateServiceFactory = ReturnType<typeof certificateTemplateServiceFactory>;
|
||||||
|
|
||||||
|
export const certificateTemplateServiceFactory = ({
|
||||||
|
certificateTemplateDAL,
|
||||||
|
certificateAuthorityDAL,
|
||||||
|
permissionService
|
||||||
|
}: TCertificateTemplateServiceFactoryDep) => {
|
||||||
|
const createCertTemplate = async ({
|
||||||
|
caId,
|
||||||
|
pkiCollectionId,
|
||||||
|
name,
|
||||||
|
commonName,
|
||||||
|
subjectAlternativeName,
|
||||||
|
ttl,
|
||||||
|
actorId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actor,
|
||||||
|
actorOrgId
|
||||||
|
}: TCreateCertTemplateDTO) => {
|
||||||
|
const ca = await certificateAuthorityDAL.findById(caId);
|
||||||
|
if (!ca) {
|
||||||
|
throw new NotFoundError({
|
||||||
|
message: "CA not found"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
const { permission } = await permissionService.getProjectPermission(
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
ca.projectId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId
|
||||||
|
);
|
||||||
|
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
|
ProjectPermissionActions.Create,
|
||||||
|
ProjectPermissionSub.CertificateTemplates
|
||||||
|
);
|
||||||
|
|
||||||
|
const { id } = await certificateTemplateDAL.create({
|
||||||
|
caId,
|
||||||
|
pkiCollectionId,
|
||||||
|
name,
|
||||||
|
commonName,
|
||||||
|
subjectAlternativeName,
|
||||||
|
ttl
|
||||||
|
});
|
||||||
|
|
||||||
|
const certificateTemplate = await certificateTemplateDAL.getById(id);
|
||||||
|
if (!certificateTemplate) {
|
||||||
|
throw new NotFoundError({
|
||||||
|
message: "Certificate template not found"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return certificateTemplate;
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateCertTemplate = async ({
|
||||||
|
id,
|
||||||
|
caId,
|
||||||
|
pkiCollectionId,
|
||||||
|
name,
|
||||||
|
commonName,
|
||||||
|
subjectAlternativeName,
|
||||||
|
ttl,
|
||||||
|
actorId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actor,
|
||||||
|
actorOrgId
|
||||||
|
}: TUpdateCertTemplateDTO) => {
|
||||||
|
const certTemplate = await certificateTemplateDAL.getById(id);
|
||||||
|
if (!certTemplate) {
|
||||||
|
throw new NotFoundError({
|
||||||
|
message: "Certificate template not found."
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const { permission } = await permissionService.getProjectPermission(
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
certTemplate.projectId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId
|
||||||
|
);
|
||||||
|
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
|
ProjectPermissionActions.Edit,
|
||||||
|
ProjectPermissionSub.CertificateTemplates
|
||||||
|
);
|
||||||
|
|
||||||
|
if (caId) {
|
||||||
|
const ca = await certificateAuthorityDAL.findById(caId);
|
||||||
|
if (!ca || ca.projectId !== certTemplate.projectId) {
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: "Invalid CA"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await certificateTemplateDAL.updateById(certTemplate.id, {
|
||||||
|
caId,
|
||||||
|
pkiCollectionId,
|
||||||
|
commonName,
|
||||||
|
subjectAlternativeName,
|
||||||
|
name,
|
||||||
|
ttl
|
||||||
|
});
|
||||||
|
|
||||||
|
const updatedTemplate = await certificateTemplateDAL.getById(id);
|
||||||
|
if (!updatedTemplate) {
|
||||||
|
throw new NotFoundError({
|
||||||
|
message: "Certificate template not found"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return updatedTemplate;
|
||||||
|
};
|
||||||
|
|
||||||
|
const deleteCertTemplate = async ({ id, actorId, actorAuthMethod, actor, actorOrgId }: TDeleteCertTemplateDTO) => {
|
||||||
|
const certTemplate = await certificateTemplateDAL.getById(id);
|
||||||
|
if (!certTemplate) {
|
||||||
|
throw new NotFoundError({
|
||||||
|
message: "Certificate template not found."
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const { permission } = await permissionService.getProjectPermission(
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
certTemplate.projectId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId
|
||||||
|
);
|
||||||
|
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
|
ProjectPermissionActions.Delete,
|
||||||
|
ProjectPermissionSub.CertificateTemplates
|
||||||
|
);
|
||||||
|
|
||||||
|
await certificateTemplateDAL.deleteById(certTemplate.id);
|
||||||
|
|
||||||
|
return certTemplate;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getCertTemplate = async ({ id, actorId, actorAuthMethod, actor, actorOrgId }: TGetCertTemplateDTO) => {
|
||||||
|
const certTemplate = await certificateTemplateDAL.getById(id);
|
||||||
|
if (!certTemplate) {
|
||||||
|
throw new NotFoundError({
|
||||||
|
message: "Certificate template not found."
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const { permission } = await permissionService.getProjectPermission(
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
certTemplate.projectId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId
|
||||||
|
);
|
||||||
|
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
|
ProjectPermissionActions.Read,
|
||||||
|
ProjectPermissionSub.CertificateTemplates
|
||||||
|
);
|
||||||
|
|
||||||
|
return certTemplate;
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
createCertTemplate,
|
||||||
|
getCertTemplate,
|
||||||
|
deleteCertTemplate,
|
||||||
|
updateCertTemplate
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
import { TProjectPermission } from "@app/lib/types";
|
||||||
|
|
||||||
|
export type TCreateCertTemplateDTO = {
|
||||||
|
caId: string;
|
||||||
|
pkiCollectionId?: string;
|
||||||
|
name: string;
|
||||||
|
commonName: string;
|
||||||
|
subjectAlternativeName: string;
|
||||||
|
ttl: string;
|
||||||
|
} & Omit<TProjectPermission, "projectId">;
|
||||||
|
|
||||||
|
export type TUpdateCertTemplateDTO = {
|
||||||
|
id: string;
|
||||||
|
caId?: string;
|
||||||
|
pkiCollectionId?: string;
|
||||||
|
name?: string;
|
||||||
|
commonName?: string;
|
||||||
|
subjectAlternativeName?: string;
|
||||||
|
ttl?: string;
|
||||||
|
} & Omit<TProjectPermission, "projectId">;
|
||||||
|
|
||||||
|
export type TGetCertTemplateDTO = {
|
||||||
|
id: string;
|
||||||
|
} & Omit<TProjectPermission, "projectId">;
|
||||||
|
|
||||||
|
export type TDeleteCertTemplateDTO = {
|
||||||
|
id: string;
|
||||||
|
} & Omit<TProjectPermission, "projectId">;
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
import safe from "safe-regex";
|
||||||
|
import z from "zod";
|
||||||
|
|
||||||
|
export const validateTemplateRegexField = z
|
||||||
|
.string()
|
||||||
|
.min(1)
|
||||||
|
.max(100)
|
||||||
|
.regex(/^[a-zA-Z0-9 *@\-\\.\\]+$/, {
|
||||||
|
message: "Invalid pattern: only alphanumeric characters, spaces, *, ., @, -, and \\ are allowed."
|
||||||
|
})
|
||||||
|
// we ensure that the inputted pattern is computationally safe by limiting star height to 1
|
||||||
|
.refine((v) => safe(v), {
|
||||||
|
message: "Unsafe REGEX pattern"
|
||||||
|
});
|
||||||
@@ -21,7 +21,7 @@ type TCertificateServiceFactoryDep = {
|
|||||||
certificateDAL: Pick<TCertificateDALFactory, "findOne" | "deleteById" | "update" | "find">;
|
certificateDAL: Pick<TCertificateDALFactory, "findOne" | "deleteById" | "update" | "find">;
|
||||||
certificateBodyDAL: Pick<TCertificateBodyDALFactory, "findOne">;
|
certificateBodyDAL: Pick<TCertificateBodyDALFactory, "findOne">;
|
||||||
certificateAuthorityDAL: Pick<TCertificateAuthorityDALFactory, "findById">;
|
certificateAuthorityDAL: Pick<TCertificateAuthorityDALFactory, "findById">;
|
||||||
certificateAuthorityCertDAL: Pick<TCertificateAuthorityCertDALFactory, "findOne">;
|
certificateAuthorityCertDAL: Pick<TCertificateAuthorityCertDALFactory, "findById">;
|
||||||
certificateAuthorityCrlDAL: Pick<TCertificateAuthorityCrlDALFactory, "update">;
|
certificateAuthorityCrlDAL: Pick<TCertificateAuthorityCrlDALFactory, "update">;
|
||||||
certificateAuthoritySecretDAL: Pick<TCertificateAuthoritySecretDALFactory, "findOne">;
|
certificateAuthoritySecretDAL: Pick<TCertificateAuthoritySecretDALFactory, "findOne">;
|
||||||
projectDAL: Pick<TProjectDALFactory, "findOne" | "updateById" | "findById" | "transaction">;
|
projectDAL: Pick<TProjectDALFactory, "findOne" | "updateById" | "findById" | "transaction">;
|
||||||
@@ -180,7 +180,7 @@ export const certificateServiceFactory = ({
|
|||||||
const certObj = new x509.X509Certificate(decryptedCert);
|
const certObj = new x509.X509Certificate(decryptedCert);
|
||||||
|
|
||||||
const { caCert, caCertChain } = await getCaCertChain({
|
const { caCert, caCertChain } = await getCaCertChain({
|
||||||
caId: ca.id,
|
caCertId: cert.caCertId,
|
||||||
certificateAuthorityDAL,
|
certificateAuthorityDAL,
|
||||||
certificateAuthorityCertDAL,
|
certificateAuthorityCertDAL,
|
||||||
projectDAL,
|
projectDAL,
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { TDbClient } from "@app/db";
|
|||||||
import { TableName } from "@app/db/schemas";
|
import { TableName } from "@app/db/schemas";
|
||||||
import { DatabaseError } from "@app/lib/errors";
|
import { DatabaseError } from "@app/lib/errors";
|
||||||
import { ormify } from "@app/lib/knex";
|
import { ormify } from "@app/lib/knex";
|
||||||
|
import { logger } from "@app/lib/logger";
|
||||||
|
|
||||||
export type TIdentityUaClientSecretDALFactory = ReturnType<typeof identityUaClientSecretDALFactory>;
|
export type TIdentityUaClientSecretDALFactory = ReturnType<typeof identityUaClientSecretDALFactory>;
|
||||||
|
|
||||||
@@ -23,5 +24,55 @@ export const identityUaClientSecretDALFactory = (db: TDbClient) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return { ...uaClientSecretOrm, incrementUsage };
|
const removeExpiredClientSecrets = async (tx?: Knex) => {
|
||||||
|
const BATCH_SIZE = 10000;
|
||||||
|
const MAX_RETRY_ON_FAILURE = 3;
|
||||||
|
|
||||||
|
let deletedClientSecret: { id: string }[] = [];
|
||||||
|
let numberOfRetryOnFailure = 0;
|
||||||
|
|
||||||
|
do {
|
||||||
|
try {
|
||||||
|
const findExpiredClientSecretQuery = (tx || db)(TableName.IdentityUaClientSecret)
|
||||||
|
.where({
|
||||||
|
isClientSecretRevoked: true
|
||||||
|
})
|
||||||
|
.orWhere((qb) => {
|
||||||
|
void qb
|
||||||
|
.where("clientSecretNumUses", ">", 0)
|
||||||
|
.andWhere(
|
||||||
|
"clientSecretNumUses",
|
||||||
|
">=",
|
||||||
|
db.ref("clientSecretNumUsesLimit").withSchema(TableName.IdentityUaClientSecret)
|
||||||
|
);
|
||||||
|
})
|
||||||
|
.orWhere((qb) => {
|
||||||
|
void qb
|
||||||
|
.where("clientSecretTTL", ">", 0)
|
||||||
|
.andWhereRaw(
|
||||||
|
`"${TableName.IdentityUaClientSecret}"."createdAt" + make_interval(secs => "${TableName.IdentityUaClientSecret}"."clientSecretTTL") < NOW()`
|
||||||
|
);
|
||||||
|
})
|
||||||
|
.select("id")
|
||||||
|
.limit(BATCH_SIZE);
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-await-in-loop
|
||||||
|
deletedClientSecret = await (tx || db)(TableName.IdentityUaClientSecret)
|
||||||
|
.whereIn("id", findExpiredClientSecretQuery)
|
||||||
|
.del()
|
||||||
|
.returning("id");
|
||||||
|
numberOfRetryOnFailure = 0; // reset
|
||||||
|
} catch (error) {
|
||||||
|
numberOfRetryOnFailure += 1;
|
||||||
|
logger.error(error, "Failed to delete client secret on pruning");
|
||||||
|
} finally {
|
||||||
|
// eslint-disable-next-line no-await-in-loop
|
||||||
|
await new Promise((resolve) => {
|
||||||
|
setTimeout(resolve, 10); // time to breathe for db
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} while (deletedClientSecret.length > 0 || numberOfRetryOnFailure < MAX_RETRY_ON_FAILURE);
|
||||||
|
};
|
||||||
|
|
||||||
|
return { ...uaClientSecretOrm, incrementUsage, removeExpiredClientSecrets };
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,357 @@
|
|||||||
|
import { retry } from "@octokit/plugin-retry";
|
||||||
|
import { Octokit } from "@octokit/rest";
|
||||||
|
|
||||||
|
import { TIntegrationAuths, TIntegrations } from "@app/db/schemas";
|
||||||
|
import { decryptSymmetric128BitHexKeyUTF8 } from "@app/lib/crypto";
|
||||||
|
import { BadRequestError, NotFoundError } from "@app/lib/errors";
|
||||||
|
import { logger } from "@app/lib/logger";
|
||||||
|
|
||||||
|
import { IntegrationMetadataSchema } from "../integration/integration-schema";
|
||||||
|
import { TKmsServiceFactory } from "../kms/kms-service";
|
||||||
|
import { KmsDataKey } from "../kms/kms-types";
|
||||||
|
import { TProjectBotServiceFactory } from "../project-bot/project-bot-service";
|
||||||
|
import { TSecretDALFactory } from "../secret/secret-dal";
|
||||||
|
import { TSecretFolderDALFactory } from "../secret-folder/secret-folder-dal";
|
||||||
|
import { TSecretImportDALFactory } from "../secret-import/secret-import-dal";
|
||||||
|
import { fnSecretsV2FromImports } from "../secret-import/secret-import-fns";
|
||||||
|
import { TSecretV2BridgeDALFactory } from "../secret-v2-bridge/secret-v2-bridge-dal";
|
||||||
|
import { TIntegrationAuthServiceFactory } from "./integration-auth-service";
|
||||||
|
import { Integrations } from "./integration-list";
|
||||||
|
|
||||||
|
const MAX_SYNC_SECRET_DEPTH = 5;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the secrets in a given [folderId] including secrets from
|
||||||
|
* nested imported folders recursively.
|
||||||
|
*/
|
||||||
|
const getIntegrationSecretsV2 = async (
|
||||||
|
dto: {
|
||||||
|
projectId: string;
|
||||||
|
environment: string;
|
||||||
|
folderId: string;
|
||||||
|
depth: number;
|
||||||
|
decryptor: (value: Buffer | null | undefined) => string;
|
||||||
|
},
|
||||||
|
secretV2BridgeDAL: Pick<TSecretV2BridgeDALFactory, "find" | "findByFolderId">,
|
||||||
|
folderDAL: Pick<TSecretFolderDALFactory, "findByManySecretPath">,
|
||||||
|
secretImportDAL: Pick<TSecretImportDALFactory, "find" | "findByFolderIds">
|
||||||
|
) => {
|
||||||
|
const content: Record<string, boolean> = {};
|
||||||
|
if (dto.depth > MAX_SYNC_SECRET_DEPTH) {
|
||||||
|
logger.info(
|
||||||
|
`getIntegrationSecrets: secret depth exceeded for [projectId=${dto.projectId}] [folderId=${dto.folderId}] [depth=${dto.depth}]`
|
||||||
|
);
|
||||||
|
return content;
|
||||||
|
}
|
||||||
|
|
||||||
|
// process secrets in current folder
|
||||||
|
const secrets = await secretV2BridgeDAL.findByFolderId(dto.folderId);
|
||||||
|
|
||||||
|
secrets.forEach((secret) => {
|
||||||
|
const secretKey = secret.key;
|
||||||
|
content[secretKey] = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
// check if current folder has any imports from other folders
|
||||||
|
const secretImports = await secretImportDAL.find({ folderId: dto.folderId, isReplication: false });
|
||||||
|
|
||||||
|
// if no imports then return secrets in the current folder
|
||||||
|
if (!secretImports.length) return content;
|
||||||
|
const importedSecrets = await fnSecretsV2FromImports({
|
||||||
|
decryptor: dto.decryptor,
|
||||||
|
folderDAL,
|
||||||
|
secretDAL: secretV2BridgeDAL,
|
||||||
|
secretImportDAL,
|
||||||
|
allowedImports: secretImports
|
||||||
|
});
|
||||||
|
|
||||||
|
for (let i = importedSecrets.length - 1; i >= 0; i -= 1) {
|
||||||
|
for (let j = 0; j < importedSecrets[i].secrets.length; j += 1) {
|
||||||
|
const importedSecret = importedSecrets[i].secrets[j];
|
||||||
|
if (!content[importedSecret.key]) {
|
||||||
|
content[importedSecret.key] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return content;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the secrets in a given [folderId] including secrets from
|
||||||
|
* nested imported folders recursively.
|
||||||
|
*/
|
||||||
|
const getIntegrationSecretsV1 = async (
|
||||||
|
dto: {
|
||||||
|
projectId: string;
|
||||||
|
environment: string;
|
||||||
|
folderId: string;
|
||||||
|
key: string;
|
||||||
|
depth: number;
|
||||||
|
},
|
||||||
|
secretDAL: Pick<TSecretDALFactory, "findByFolderId">,
|
||||||
|
folderDAL: Pick<TSecretFolderDALFactory, "findByManySecretPath">,
|
||||||
|
secretImportDAL: Pick<TSecretImportDALFactory, "find" | "findByFolderIds">
|
||||||
|
) => {
|
||||||
|
let content: Record<string, boolean> = {};
|
||||||
|
if (dto.depth > MAX_SYNC_SECRET_DEPTH) {
|
||||||
|
logger.info(
|
||||||
|
`getIntegrationSecrets: secret depth exceeded for [projectId=${dto.projectId}] [folderId=${dto.folderId}] [depth=${dto.depth}]`
|
||||||
|
);
|
||||||
|
return content;
|
||||||
|
}
|
||||||
|
|
||||||
|
// process secrets in current folder
|
||||||
|
const secrets = await secretDAL.findByFolderId(dto.folderId);
|
||||||
|
secrets.forEach((secret) => {
|
||||||
|
const secretKey = decryptSymmetric128BitHexKeyUTF8({
|
||||||
|
ciphertext: secret.secretKeyCiphertext,
|
||||||
|
iv: secret.secretKeyIV,
|
||||||
|
tag: secret.secretKeyTag,
|
||||||
|
key: dto.key
|
||||||
|
});
|
||||||
|
|
||||||
|
content[secretKey] = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
// check if current folder has any imports from other folders
|
||||||
|
const secretImport = await secretImportDAL.find({ folderId: dto.folderId, isReplication: false });
|
||||||
|
|
||||||
|
// if no imports then return secrets in the current folder
|
||||||
|
if (!secretImport) return content;
|
||||||
|
|
||||||
|
const importedFolders = await folderDAL.findByManySecretPath(
|
||||||
|
secretImport.map(({ importEnv, importPath }) => ({
|
||||||
|
envId: importEnv.id,
|
||||||
|
secretPath: importPath
|
||||||
|
}))
|
||||||
|
);
|
||||||
|
|
||||||
|
for await (const folder of importedFolders) {
|
||||||
|
if (folder) {
|
||||||
|
// get secrets contained in each imported folder by recursively calling
|
||||||
|
// this function against the imported folder
|
||||||
|
const importedSecrets = await getIntegrationSecretsV1(
|
||||||
|
{
|
||||||
|
environment: dto.environment,
|
||||||
|
projectId: dto.projectId,
|
||||||
|
folderId: folder.id,
|
||||||
|
key: dto.key,
|
||||||
|
depth: dto.depth + 1
|
||||||
|
},
|
||||||
|
secretDAL,
|
||||||
|
folderDAL,
|
||||||
|
secretImportDAL
|
||||||
|
);
|
||||||
|
|
||||||
|
// add the imported secrets to the current folder secrets
|
||||||
|
content = { ...importedSecrets, ...content };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return content;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const deleteGithubSecrets = async ({
|
||||||
|
integration,
|
||||||
|
secrets,
|
||||||
|
accessToken
|
||||||
|
}: {
|
||||||
|
integration: Omit<TIntegrations, "envId">;
|
||||||
|
secrets: Record<string, boolean>;
|
||||||
|
accessToken: string;
|
||||||
|
}) => {
|
||||||
|
interface GitHubSecret {
|
||||||
|
name: string;
|
||||||
|
created_at: string;
|
||||||
|
updated_at: string;
|
||||||
|
visibility?: "all" | "private" | "selected";
|
||||||
|
selected_repositories_url?: string | undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const OctokitWithRetry = Octokit.plugin(retry);
|
||||||
|
const octokit = new OctokitWithRetry({
|
||||||
|
auth: accessToken
|
||||||
|
});
|
||||||
|
|
||||||
|
enum GithubScope {
|
||||||
|
Repo = "github-repo",
|
||||||
|
Org = "github-org",
|
||||||
|
Env = "github-env"
|
||||||
|
}
|
||||||
|
|
||||||
|
let encryptedGithubSecrets: GitHubSecret[];
|
||||||
|
|
||||||
|
switch (integration.scope) {
|
||||||
|
case GithubScope.Org: {
|
||||||
|
encryptedGithubSecrets = (
|
||||||
|
await octokit.request("GET /orgs/{org}/actions/secrets", {
|
||||||
|
org: integration.owner as string
|
||||||
|
})
|
||||||
|
).data.secrets;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case GithubScope.Env: {
|
||||||
|
encryptedGithubSecrets = (
|
||||||
|
await octokit.request("GET /repositories/{repository_id}/environments/{environment_name}/secrets", {
|
||||||
|
repository_id: Number(integration.appId),
|
||||||
|
environment_name: integration.targetEnvironmentId as string
|
||||||
|
})
|
||||||
|
).data.secrets;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
encryptedGithubSecrets = (
|
||||||
|
await octokit.request("GET /repos/{owner}/{repo}/actions/secrets", {
|
||||||
|
owner: integration.owner as string,
|
||||||
|
repo: integration.app as string
|
||||||
|
})
|
||||||
|
).data.secrets;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for await (const encryptedSecret of encryptedGithubSecrets) {
|
||||||
|
if (encryptedSecret.name in secrets) {
|
||||||
|
switch (integration.scope) {
|
||||||
|
case GithubScope.Org: {
|
||||||
|
await octokit.request("DELETE /orgs/{org}/actions/secrets/{secret_name}", {
|
||||||
|
org: integration.owner as string,
|
||||||
|
secret_name: encryptedSecret.name
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case GithubScope.Env: {
|
||||||
|
await octokit.request(
|
||||||
|
"DELETE /repositories/{repository_id}/environments/{environment_name}/secrets/{secret_name}",
|
||||||
|
{
|
||||||
|
repository_id: Number(integration.appId),
|
||||||
|
environment_name: integration.targetEnvironmentId as string,
|
||||||
|
secret_name: encryptedSecret.name
|
||||||
|
}
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
await octokit.request("DELETE /repos/{owner}/{repo}/actions/secrets/{secret_name}", {
|
||||||
|
owner: integration.owner as string,
|
||||||
|
repo: integration.app as string,
|
||||||
|
secret_name: encryptedSecret.name
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// small delay to prevent hitting API rate limits
|
||||||
|
await new Promise((resolve) => {
|
||||||
|
setTimeout(resolve, 50);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const deleteIntegrationSecrets = async ({
|
||||||
|
integration,
|
||||||
|
integrationAuth,
|
||||||
|
integrationAuthService,
|
||||||
|
projectBotService,
|
||||||
|
secretV2BridgeDAL,
|
||||||
|
folderDAL,
|
||||||
|
secretDAL,
|
||||||
|
secretImportDAL,
|
||||||
|
kmsService
|
||||||
|
}: {
|
||||||
|
integration: Omit<TIntegrations, "envId"> & {
|
||||||
|
projectId: string;
|
||||||
|
environment: {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
slug: string;
|
||||||
|
};
|
||||||
|
secretPath: string;
|
||||||
|
};
|
||||||
|
integrationAuth: TIntegrationAuths;
|
||||||
|
integrationAuthService: Pick<TIntegrationAuthServiceFactory, "getIntegrationAccessToken" | "getIntegrationAuth">;
|
||||||
|
projectBotService: Pick<TProjectBotServiceFactory, "getBotKey">;
|
||||||
|
secretV2BridgeDAL: Pick<TSecretV2BridgeDALFactory, "find" | "findByFolderId">;
|
||||||
|
folderDAL: Pick<TSecretFolderDALFactory, "findByManySecretPath" | "findBySecretPath">;
|
||||||
|
secretImportDAL: Pick<TSecretImportDALFactory, "find" | "findByFolderIds">;
|
||||||
|
secretDAL: Pick<TSecretDALFactory, "findByFolderId">;
|
||||||
|
kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey">;
|
||||||
|
}) => {
|
||||||
|
const { shouldUseSecretV2Bridge, botKey } = await projectBotService.getBotKey(integration.projectId);
|
||||||
|
const { decryptor: secretManagerDecryptor } = await kmsService.createCipherPairWithDataKey({
|
||||||
|
type: KmsDataKey.SecretManager,
|
||||||
|
projectId: integration.projectId
|
||||||
|
});
|
||||||
|
|
||||||
|
const folder = await folderDAL.findBySecretPath(
|
||||||
|
integration.projectId,
|
||||||
|
integration.environment.slug,
|
||||||
|
integration.secretPath
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!folder) {
|
||||||
|
throw new NotFoundError({
|
||||||
|
message: "Folder not found."
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const { accessToken } = await integrationAuthService.getIntegrationAccessToken(
|
||||||
|
integrationAuth,
|
||||||
|
shouldUseSecretV2Bridge,
|
||||||
|
botKey
|
||||||
|
);
|
||||||
|
|
||||||
|
const secrets = shouldUseSecretV2Bridge
|
||||||
|
? await getIntegrationSecretsV2(
|
||||||
|
{
|
||||||
|
environment: integration.environment.id,
|
||||||
|
projectId: integration.projectId,
|
||||||
|
folderId: folder.id,
|
||||||
|
depth: 1,
|
||||||
|
decryptor: (value) => (value ? secretManagerDecryptor({ cipherTextBlob: value }).toString() : "")
|
||||||
|
},
|
||||||
|
secretV2BridgeDAL,
|
||||||
|
folderDAL,
|
||||||
|
secretImportDAL
|
||||||
|
)
|
||||||
|
: await getIntegrationSecretsV1(
|
||||||
|
{
|
||||||
|
environment: integration.environment.id,
|
||||||
|
projectId: integration.projectId,
|
||||||
|
folderId: folder.id,
|
||||||
|
key: botKey as string,
|
||||||
|
depth: 1
|
||||||
|
},
|
||||||
|
secretDAL,
|
||||||
|
folderDAL,
|
||||||
|
secretImportDAL
|
||||||
|
);
|
||||||
|
|
||||||
|
const suffixedSecrets: typeof secrets = {};
|
||||||
|
const metadata = IntegrationMetadataSchema.parse(integration.metadata);
|
||||||
|
|
||||||
|
if (metadata) {
|
||||||
|
Object.keys(secrets).forEach((key) => {
|
||||||
|
const prefix = metadata?.secretPrefix || "";
|
||||||
|
const suffix = metadata?.secretSuffix || "";
|
||||||
|
const newKey = prefix + key + suffix;
|
||||||
|
suffixedSecrets[newKey] = secrets[key];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (integration.integration) {
|
||||||
|
case Integrations.GITHUB: {
|
||||||
|
await deleteGithubSecrets({
|
||||||
|
integration,
|
||||||
|
accessToken,
|
||||||
|
secrets: Object.keys(suffixedSecrets).length !== 0 ? suffixedSecrets : secrets
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: "Invalid integration"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -538,19 +538,20 @@ const syncSecretsAWSParameterStore = async ({
|
|||||||
integration,
|
integration,
|
||||||
secrets,
|
secrets,
|
||||||
accessId,
|
accessId,
|
||||||
accessToken
|
accessToken,
|
||||||
|
projectId
|
||||||
}: {
|
}: {
|
||||||
integration: TIntegrations;
|
integration: TIntegrations & { secretPath: string; environment: { slug: string } };
|
||||||
secrets: Record<string, { value: string; comment?: string }>;
|
secrets: Record<string, { value: string; comment?: string }>;
|
||||||
accessId: string | null;
|
accessId: string | null;
|
||||||
accessToken: string;
|
accessToken: string;
|
||||||
|
projectId?: string;
|
||||||
}) => {
|
}) => {
|
||||||
let response: { isSynced: boolean; syncMessage: string } | null = null;
|
let response: { isSynced: boolean; syncMessage: string } | null = null;
|
||||||
|
|
||||||
if (!accessId) {
|
if (!accessId) {
|
||||||
throw new Error("AWS access ID is required");
|
throw new Error("AWS access ID is required");
|
||||||
}
|
}
|
||||||
|
|
||||||
const config = new AWS.Config({
|
const config = new AWS.Config({
|
||||||
region: integration.region as string,
|
region: integration.region as string,
|
||||||
credentials: {
|
credentials: {
|
||||||
@@ -567,7 +568,9 @@ const syncSecretsAWSParameterStore = async ({
|
|||||||
|
|
||||||
const metadata = z.record(z.any()).parse(integration.metadata || {});
|
const metadata = z.record(z.any()).parse(integration.metadata || {});
|
||||||
const awsParameterStoreSecretsObj: Record<string, AWS.SSM.Parameter> = {};
|
const awsParameterStoreSecretsObj: Record<string, AWS.SSM.Parameter> = {};
|
||||||
|
logger.info(
|
||||||
|
`getIntegrationSecrets: integration sync triggered for ssm with [projectId=${projectId}] [environment=${integration.environment.slug}] [secretPath=${integration.secretPath}] [shouldDisableDelete=${metadata.shouldDisableDelete}]`
|
||||||
|
);
|
||||||
// now fetch all aws parameter store secrets
|
// now fetch all aws parameter store secrets
|
||||||
let hasNext = true;
|
let hasNext = true;
|
||||||
let nextToken: string | undefined;
|
let nextToken: string | undefined;
|
||||||
@@ -594,6 +597,18 @@ const syncSecretsAWSParameterStore = async ({
|
|||||||
nextToken = parameters.NextToken;
|
nextToken = parameters.NextToken;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
logger.info(
|
||||||
|
`getIntegrationSecrets: all fetched keys from AWS SSM [projectId=${projectId}] [environment=${
|
||||||
|
integration.environment.slug
|
||||||
|
}] [secretPath=${integration.secretPath}] [awsParameterStoreSecretsObj=${Object.keys(
|
||||||
|
awsParameterStoreSecretsObj
|
||||||
|
).join(",")}]`
|
||||||
|
);
|
||||||
|
logger.info(
|
||||||
|
`getIntegrationSecrets: all secrets from Infisical to send to AWS SSM [projectId=${projectId}] [environment=${
|
||||||
|
integration.environment.slug
|
||||||
|
}] [secretPath=${integration.secretPath}] [secrets=${Object.keys(secrets).join(",")}]`
|
||||||
|
);
|
||||||
// Identify secrets to create
|
// Identify secrets to create
|
||||||
// don't use Promise.all() and promise map here
|
// don't use Promise.all() and promise map here
|
||||||
// it will cause rate limit
|
// it will cause rate limit
|
||||||
@@ -603,24 +618,56 @@ const syncSecretsAWSParameterStore = async ({
|
|||||||
// case: secret does not exist in AWS parameter store
|
// case: secret does not exist in AWS parameter store
|
||||||
// -> create secret
|
// -> create secret
|
||||||
if (secrets[key].value) {
|
if (secrets[key].value) {
|
||||||
|
logger.info(
|
||||||
|
`getIntegrationSecrets: create secret in AWS SSM for [projectId=${projectId}] [environment=${integration.environment.slug}] [secretPath=${integration.secretPath}] [key=${key}]`
|
||||||
|
);
|
||||||
await ssm
|
await ssm
|
||||||
.putParameter({
|
.putParameter({
|
||||||
Name: `${integration.path}${key}`,
|
Name: `${integration.path}${key}`,
|
||||||
Type: "SecureString",
|
Type: "SecureString",
|
||||||
Value: secrets[key].value,
|
Value: secrets[key].value,
|
||||||
...(metadata.kmsKeyId && { KeyId: metadata.kmsKeyId }),
|
...(metadata.kmsKeyId && { KeyId: metadata.kmsKeyId }),
|
||||||
// Overwrite: true,
|
Overwrite: true
|
||||||
Tags: metadata.secretAWSTag
|
|
||||||
? metadata.secretAWSTag.map((tag: { key: string; value: string }) => ({
|
|
||||||
Key: tag.key,
|
|
||||||
Value: tag.value
|
|
||||||
}))
|
|
||||||
: []
|
|
||||||
})
|
})
|
||||||
.promise();
|
.promise();
|
||||||
|
if (metadata.secretAWSTag?.length) {
|
||||||
|
try {
|
||||||
|
await ssm
|
||||||
|
.addTagsToResource({
|
||||||
|
ResourceType: "Parameter",
|
||||||
|
ResourceId: `${integration.path}${key}`,
|
||||||
|
Tags: metadata.secretAWSTag
|
||||||
|
? metadata.secretAWSTag.map((tag: { key: string; value: string }) => ({
|
||||||
|
Key: tag.key,
|
||||||
|
Value: tag.value
|
||||||
|
}))
|
||||||
|
: []
|
||||||
|
})
|
||||||
|
.promise();
|
||||||
|
} catch (err) {
|
||||||
|
logger.error(
|
||||||
|
err,
|
||||||
|
`getIntegrationSecrets: create secret in AWS SSM for failed [projectId=${projectId}] [environment=${integration.environment.slug}] [secretPath=${integration.secretPath}] [key=${key}]`
|
||||||
|
);
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
if ((err as any).code === "AccessDeniedException") {
|
||||||
|
logger.error(
|
||||||
|
`AWS Parameter Store Error [integration=${integration.id}]: double check AWS account permissions (refer to the Infisical docs)`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
response = {
|
||||||
|
isSynced: false,
|
||||||
|
syncMessage: (err as AWSError)?.message || "Error syncing with AWS Parameter Store"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// case: secret exists in AWS parameter store
|
// case: secret exists in AWS parameter store
|
||||||
} else {
|
} else {
|
||||||
|
logger.info(
|
||||||
|
`getIntegrationSecrets: update secret in AWS SSM for [projectId=${projectId}] [environment=${integration.environment.slug}] [secretPath=${integration.secretPath}] [key=${key}]`
|
||||||
|
);
|
||||||
// -> update secret
|
// -> update secret
|
||||||
if (awsParameterStoreSecretsObj[key].Value !== secrets[key].value) {
|
if (awsParameterStoreSecretsObj[key].Value !== secrets[key].value) {
|
||||||
await ssm
|
await ssm
|
||||||
@@ -648,6 +695,10 @@ const syncSecretsAWSParameterStore = async ({
|
|||||||
})
|
})
|
||||||
.promise();
|
.promise();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
logger.error(
|
||||||
|
err,
|
||||||
|
`getIntegrationSecrets: update secret in AWS SSM for failed [projectId=${projectId}] [environment=${integration.environment.slug}] [secretPath=${integration.secretPath}] [key=${key}]`
|
||||||
|
);
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
if ((err as any).code === "AccessDeniedException") {
|
if ((err as any).code === "AccessDeniedException") {
|
||||||
logger.error(
|
logger.error(
|
||||||
@@ -670,9 +721,18 @@ const syncSecretsAWSParameterStore = async ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!metadata.shouldDisableDelete) {
|
if (!metadata.shouldDisableDelete) {
|
||||||
|
logger.info(
|
||||||
|
`getIntegrationSecrets: inside of shouldDisableDelete AWS SSM [projectId=${projectId}] [environment=${integration.environment.slug}] [secretPath=${integration.secretPath}] [step=1]`
|
||||||
|
);
|
||||||
for (const key in awsParameterStoreSecretsObj) {
|
for (const key in awsParameterStoreSecretsObj) {
|
||||||
if (Object.hasOwn(awsParameterStoreSecretsObj, key)) {
|
if (Object.hasOwn(awsParameterStoreSecretsObj, key)) {
|
||||||
|
logger.info(
|
||||||
|
`getIntegrationSecrets: inside of shouldDisableDelete AWS SSM [projectId=${projectId}] [environment=${integration.environment.slug}] [secretPath=${integration.secretPath}] [key=${key}] [step=2]`
|
||||||
|
);
|
||||||
if (!(key in secrets)) {
|
if (!(key in secrets)) {
|
||||||
|
logger.info(
|
||||||
|
`getIntegrationSecrets: inside of shouldDisableDelete AWS SSM [projectId=${projectId}] [environment=${integration.environment.slug}] [secretPath=${integration.secretPath}] [key=${key}] [step=3]`
|
||||||
|
);
|
||||||
// case:
|
// case:
|
||||||
// -> delete secret
|
// -> delete secret
|
||||||
await ssm
|
await ssm
|
||||||
@@ -680,6 +740,9 @@ const syncSecretsAWSParameterStore = async ({
|
|||||||
Name: awsParameterStoreSecretsObj[key].Name as string
|
Name: awsParameterStoreSecretsObj[key].Name as string
|
||||||
})
|
})
|
||||||
.promise();
|
.promise();
|
||||||
|
logger.info(
|
||||||
|
`getIntegrationSecrets: inside of shouldDisableDelete AWS SSM [projectId=${projectId}] [environment=${integration.environment.slug}] [secretPath=${integration.secretPath}] [key=${key}] [step=4]`
|
||||||
|
);
|
||||||
}
|
}
|
||||||
await new Promise((resolve) => {
|
await new Promise((resolve) => {
|
||||||
setTimeout(resolve, 50);
|
setTimeout(resolve, 50);
|
||||||
@@ -3656,7 +3719,8 @@ export const syncIntegrationSecrets = async ({
|
|||||||
integration,
|
integration,
|
||||||
secrets,
|
secrets,
|
||||||
accessId,
|
accessId,
|
||||||
accessToken
|
accessToken,
|
||||||
|
projectId
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
case Integrations.AWS_SECRET_MANAGER:
|
case Integrations.AWS_SECRET_MANAGER:
|
||||||
|
|||||||
@@ -6,8 +6,15 @@ import { BadRequestError } from "@app/lib/errors";
|
|||||||
import { TProjectPermission } from "@app/lib/types";
|
import { TProjectPermission } from "@app/lib/types";
|
||||||
|
|
||||||
import { TIntegrationAuthDALFactory } from "../integration-auth/integration-auth-dal";
|
import { TIntegrationAuthDALFactory } from "../integration-auth/integration-auth-dal";
|
||||||
|
import { TIntegrationAuthServiceFactory } from "../integration-auth/integration-auth-service";
|
||||||
|
import { deleteIntegrationSecrets } from "../integration-auth/integration-delete-secret";
|
||||||
|
import { TKmsServiceFactory } from "../kms/kms-service";
|
||||||
|
import { TProjectBotServiceFactory } from "../project-bot/project-bot-service";
|
||||||
|
import { TSecretDALFactory } from "../secret/secret-dal";
|
||||||
import { TSecretQueueFactory } from "../secret/secret-queue";
|
import { TSecretQueueFactory } from "../secret/secret-queue";
|
||||||
import { TSecretFolderDALFactory } from "../secret-folder/secret-folder-dal";
|
import { TSecretFolderDALFactory } from "../secret-folder/secret-folder-dal";
|
||||||
|
import { TSecretImportDALFactory } from "../secret-import/secret-import-dal";
|
||||||
|
import { TSecretV2BridgeDALFactory } from "../secret-v2-bridge/secret-v2-bridge-dal";
|
||||||
import { TIntegrationDALFactory } from "./integration-dal";
|
import { TIntegrationDALFactory } from "./integration-dal";
|
||||||
import {
|
import {
|
||||||
TCreateIntegrationDTO,
|
TCreateIntegrationDTO,
|
||||||
@@ -19,9 +26,15 @@ import {
|
|||||||
type TIntegrationServiceFactoryDep = {
|
type TIntegrationServiceFactoryDep = {
|
||||||
integrationDAL: TIntegrationDALFactory;
|
integrationDAL: TIntegrationDALFactory;
|
||||||
integrationAuthDAL: TIntegrationAuthDALFactory;
|
integrationAuthDAL: TIntegrationAuthDALFactory;
|
||||||
folderDAL: Pick<TSecretFolderDALFactory, "findBySecretPath">;
|
integrationAuthService: TIntegrationAuthServiceFactory;
|
||||||
|
folderDAL: Pick<TSecretFolderDALFactory, "findBySecretPath" | "findByManySecretPath">;
|
||||||
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission">;
|
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission">;
|
||||||
|
projectBotService: TProjectBotServiceFactory;
|
||||||
secretQueueService: Pick<TSecretQueueFactory, "syncIntegrations">;
|
secretQueueService: Pick<TSecretQueueFactory, "syncIntegrations">;
|
||||||
|
secretV2BridgeDAL: Pick<TSecretV2BridgeDALFactory, "find" | "findByFolderId">;
|
||||||
|
secretImportDAL: Pick<TSecretImportDALFactory, "find" | "findByFolderIds">;
|
||||||
|
kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey">;
|
||||||
|
secretDAL: Pick<TSecretDALFactory, "findByFolderId">;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TIntegrationServiceFactory = ReturnType<typeof integrationServiceFactory>;
|
export type TIntegrationServiceFactory = ReturnType<typeof integrationServiceFactory>;
|
||||||
@@ -31,7 +44,13 @@ export const integrationServiceFactory = ({
|
|||||||
integrationAuthDAL,
|
integrationAuthDAL,
|
||||||
folderDAL,
|
folderDAL,
|
||||||
permissionService,
|
permissionService,
|
||||||
secretQueueService
|
secretQueueService,
|
||||||
|
integrationAuthService,
|
||||||
|
projectBotService,
|
||||||
|
secretV2BridgeDAL,
|
||||||
|
secretImportDAL,
|
||||||
|
kmsService,
|
||||||
|
secretDAL
|
||||||
}: TIntegrationServiceFactoryDep) => {
|
}: TIntegrationServiceFactoryDep) => {
|
||||||
const createIntegration = async ({
|
const createIntegration = async ({
|
||||||
app,
|
app,
|
||||||
@@ -161,7 +180,14 @@ export const integrationServiceFactory = ({
|
|||||||
return updatedIntegration;
|
return updatedIntegration;
|
||||||
};
|
};
|
||||||
|
|
||||||
const deleteIntegration = async ({ actorId, id, actor, actorAuthMethod, actorOrgId }: TDeleteIntegrationDTO) => {
|
const deleteIntegration = async ({
|
||||||
|
actorId,
|
||||||
|
id,
|
||||||
|
actor,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId,
|
||||||
|
shouldDeleteIntegrationSecrets
|
||||||
|
}: TDeleteIntegrationDTO) => {
|
||||||
const integration = await integrationDAL.findById(id);
|
const integration = await integrationDAL.findById(id);
|
||||||
if (!integration) throw new BadRequestError({ message: "Integration auth not found" });
|
if (!integration) throw new BadRequestError({ message: "Integration auth not found" });
|
||||||
|
|
||||||
@@ -174,6 +200,22 @@ export const integrationServiceFactory = ({
|
|||||||
);
|
);
|
||||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Delete, ProjectPermissionSub.Integrations);
|
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Delete, ProjectPermissionSub.Integrations);
|
||||||
|
|
||||||
|
const integrationAuth = await integrationAuthDAL.findById(integration.integrationAuthId);
|
||||||
|
|
||||||
|
if (shouldDeleteIntegrationSecrets) {
|
||||||
|
await deleteIntegrationSecrets({
|
||||||
|
integration,
|
||||||
|
integrationAuth,
|
||||||
|
projectBotService,
|
||||||
|
integrationAuthService,
|
||||||
|
secretV2BridgeDAL,
|
||||||
|
folderDAL,
|
||||||
|
secretImportDAL,
|
||||||
|
secretDAL,
|
||||||
|
kmsService
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const deletedIntegration = await integrationDAL.transaction(async (tx) => {
|
const deletedIntegration = await integrationDAL.transaction(async (tx) => {
|
||||||
// delete integration
|
// delete integration
|
||||||
const deletedIntegrationResult = await integrationDAL.deleteById(id, tx);
|
const deletedIntegrationResult = await integrationDAL.deleteById(id, tx);
|
||||||
|
|||||||
@@ -63,6 +63,7 @@ export type TUpdateIntegrationDTO = {
|
|||||||
|
|
||||||
export type TDeleteIntegrationDTO = {
|
export type TDeleteIntegrationDTO = {
|
||||||
id: string;
|
id: string;
|
||||||
|
shouldDeleteIntegrationSecrets?: boolean;
|
||||||
} & Omit<TProjectPermission, "projectId">;
|
} & Omit<TProjectPermission, "projectId">;
|
||||||
|
|
||||||
export type TSyncIntegrationDTO = {
|
export type TSyncIntegrationDTO = {
|
||||||
|
|||||||
@@ -0,0 +1,48 @@
|
|||||||
|
import { logger } from "@app/lib/logger";
|
||||||
|
import { QueueJobs, QueueName, TQueueServiceFactory } from "@app/queue";
|
||||||
|
import { TPkiAlertServiceFactory } from "@app/services/pki-alert/pki-alert-service";
|
||||||
|
|
||||||
|
type TDailyExpiringPkiItemAlertQueueServiceFactoryDep = {
|
||||||
|
queueService: TQueueServiceFactory;
|
||||||
|
pkiAlertService: Pick<TPkiAlertServiceFactory, "sendPkiItemExpiryNotices">;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TDailyExpiringPkiItemAlertQueueServiceFactory = ReturnType<
|
||||||
|
typeof dailyExpiringPkiItemAlertQueueServiceFactory
|
||||||
|
>;
|
||||||
|
|
||||||
|
export const dailyExpiringPkiItemAlertQueueServiceFactory = ({
|
||||||
|
queueService,
|
||||||
|
pkiAlertService
|
||||||
|
}: TDailyExpiringPkiItemAlertQueueServiceFactoryDep) => {
|
||||||
|
queueService.start(QueueName.DailyExpiringPkiItemAlert, async () => {
|
||||||
|
logger.info(`${QueueName.DailyExpiringPkiItemAlert}: queue task started`);
|
||||||
|
await pkiAlertService.sendPkiItemExpiryNotices();
|
||||||
|
logger.info(`${QueueName.DailyExpiringPkiItemAlert}: queue task completed`);
|
||||||
|
});
|
||||||
|
|
||||||
|
// we do a repeat cron job in utc timezone at 12 Midnight each day
|
||||||
|
const startSendingAlerts = async () => {
|
||||||
|
// clear previous job
|
||||||
|
await queueService.stopRepeatableJob(
|
||||||
|
QueueName.DailyExpiringPkiItemAlert,
|
||||||
|
QueueJobs.DailyExpiringPkiItemAlert,
|
||||||
|
{ pattern: "0 0 * * *", utc: true },
|
||||||
|
QueueName.DailyExpiringPkiItemAlert // just a job id
|
||||||
|
);
|
||||||
|
|
||||||
|
await queueService.queue(QueueName.DailyExpiringPkiItemAlert, QueueJobs.DailyExpiringPkiItemAlert, undefined, {
|
||||||
|
delay: 5000,
|
||||||
|
jobId: QueueName.DailyExpiringPkiItemAlert,
|
||||||
|
repeat: { pattern: "0 0 * * *", utc: true }
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
queueService.listen(QueueName.DailyExpiringPkiItemAlert, "failed", (_, err) => {
|
||||||
|
logger.error(err, `${QueueName.DailyExpiringPkiItemAlert}: Expiring PKI item alert failed`);
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
startSendingAlerts
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -12,8 +12,11 @@ import { TPkiAlertDALFactory } from "./pki-alert-dal";
|
|||||||
import { TCreateAlertDTO, TDeleteAlertDTO, TGetAlertByIdDTO, TUpdateAlertDTO } from "./pki-alert-types";
|
import { TCreateAlertDTO, TDeleteAlertDTO, TGetAlertByIdDTO, TUpdateAlertDTO } from "./pki-alert-types";
|
||||||
|
|
||||||
type TPkiAlertServiceFactoryDep = {
|
type TPkiAlertServiceFactoryDep = {
|
||||||
pkiAlertDAL: TPkiAlertDALFactory;
|
pkiAlertDAL: Pick<
|
||||||
pkiCollectionDAL: TPkiCollectionDALFactory;
|
TPkiAlertDALFactory,
|
||||||
|
"create" | "findById" | "updateById" | "deleteById" | "getExpiringPkiCollectionItemsForAlerting"
|
||||||
|
>;
|
||||||
|
pkiCollectionDAL: Pick<TPkiCollectionDALFactory, "findById">;
|
||||||
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission">;
|
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission">;
|
||||||
smtpService: Pick<TSmtpService, "sendMail">;
|
smtpService: Pick<TSmtpService, "sendMail">;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -81,7 +81,7 @@ export const pkiCollectionItemDALFactory = (db: TDbClient) => {
|
|||||||
|
|
||||||
return parseInt((count as unknown as CountResult).count || "0", 10);
|
return parseInt((count as unknown as CountResult).count || "0", 10);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw new DatabaseError({ error, name: "Count all project certificates" });
|
throw new DatabaseError({ error, name: "Count all PKI collection items" });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -22,10 +22,13 @@ import {
|
|||||||
} from "./pki-collection-types";
|
} from "./pki-collection-types";
|
||||||
|
|
||||||
type TPkiCollectionServiceFactoryDep = {
|
type TPkiCollectionServiceFactoryDep = {
|
||||||
pkiCollectionDAL: TPkiCollectionDALFactory; // TODO: Pick
|
pkiCollectionDAL: Pick<TPkiCollectionDALFactory, "create" | "findById" | "updateById" | "deleteById">;
|
||||||
pkiCollectionItemDAL: TPkiCollectionItemDALFactory;
|
pkiCollectionItemDAL: Pick<
|
||||||
certificateAuthorityDAL: TCertificateAuthorityDALFactory;
|
TPkiCollectionItemDALFactory,
|
||||||
certificateDAL: TCertificateDALFactory;
|
"findOne" | "create" | "deleteById" | "findPkiCollectionItems" | "countItemsInPkiCollection"
|
||||||
|
>;
|
||||||
|
certificateAuthorityDAL: Pick<TCertificateAuthorityDALFactory, "find" | "findOne">;
|
||||||
|
certificateDAL: Pick<TCertificateDALFactory, "find">;
|
||||||
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission">;
|
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission">;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -40,6 +43,7 @@ export const pkiCollectionServiceFactory = ({
|
|||||||
}: TPkiCollectionServiceFactoryDep) => {
|
}: TPkiCollectionServiceFactoryDep) => {
|
||||||
const createPkiCollection = async ({
|
const createPkiCollection = async ({
|
||||||
name,
|
name,
|
||||||
|
description,
|
||||||
projectId,
|
projectId,
|
||||||
actorId,
|
actorId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
@@ -61,7 +65,8 @@ export const pkiCollectionServiceFactory = ({
|
|||||||
|
|
||||||
const pkiCollection = await pkiCollectionDAL.create({
|
const pkiCollection = await pkiCollectionDAL.create({
|
||||||
projectId,
|
projectId,
|
||||||
name
|
name,
|
||||||
|
description
|
||||||
});
|
});
|
||||||
|
|
||||||
return pkiCollection;
|
return pkiCollection;
|
||||||
@@ -92,6 +97,7 @@ export const pkiCollectionServiceFactory = ({
|
|||||||
const updatePkiCollection = async ({
|
const updatePkiCollection = async ({
|
||||||
collectionId,
|
collectionId,
|
||||||
name,
|
name,
|
||||||
|
description,
|
||||||
actorId,
|
actorId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actor,
|
actor,
|
||||||
@@ -110,7 +116,8 @@ export const pkiCollectionServiceFactory = ({
|
|||||||
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.PkiCollections);
|
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.PkiCollections);
|
||||||
pkiCollection = await pkiCollectionDAL.updateById(collectionId, {
|
pkiCollection = await pkiCollectionDAL.updateById(collectionId, {
|
||||||
name
|
name,
|
||||||
|
description
|
||||||
});
|
});
|
||||||
|
|
||||||
return pkiCollection;
|
return pkiCollection;
|
||||||
@@ -135,7 +142,7 @@ export const pkiCollectionServiceFactory = ({
|
|||||||
);
|
);
|
||||||
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionActions.Create,
|
ProjectPermissionActions.Delete,
|
||||||
ProjectPermissionSub.PkiCollections
|
ProjectPermissionSub.PkiCollections
|
||||||
);
|
);
|
||||||
pkiCollection = await pkiCollectionDAL.deleteById(collectionId);
|
pkiCollection = await pkiCollectionDAL.deleteById(collectionId);
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { TProjectPermission } from "@app/lib/types";
|
|||||||
|
|
||||||
export type TCreatePkiCollectionDTO = {
|
export type TCreatePkiCollectionDTO = {
|
||||||
name: string;
|
name: string;
|
||||||
|
description: string;
|
||||||
} & TProjectPermission;
|
} & TProjectPermission;
|
||||||
|
|
||||||
export type TGetPkiCollectionByIdDTO = {
|
export type TGetPkiCollectionByIdDTO = {
|
||||||
@@ -11,6 +12,7 @@ export type TGetPkiCollectionByIdDTO = {
|
|||||||
export type TUpdatePkiCollectionDTO = {
|
export type TUpdatePkiCollectionDTO = {
|
||||||
collectionId: string;
|
collectionId: string;
|
||||||
name?: string;
|
name?: string;
|
||||||
|
description?: string;
|
||||||
} & Omit<TProjectPermission, "projectId">;
|
} & Omit<TProjectPermission, "projectId">;
|
||||||
|
|
||||||
export type TDeletePkiCollectionDTO = {
|
export type TDeletePkiCollectionDTO = {
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ export const projectBotDALFactory = (db: TDbClient) => {
|
|||||||
const doc = await db
|
const doc = await db
|
||||||
.replicaNode()(TableName.ProjectMembership)
|
.replicaNode()(TableName.ProjectMembership)
|
||||||
.where(`${TableName.ProjectMembership}.projectId` as "projectId", projectId)
|
.where(`${TableName.ProjectMembership}.projectId` as "projectId", projectId)
|
||||||
|
.where(`${TableName.ProjectKeys}.projectId` as "projectId", projectId)
|
||||||
.where(`${TableName.Users}.isGhost` as "isGhost", false)
|
.where(`${TableName.Users}.isGhost` as "isGhost", false)
|
||||||
.join(TableName.Users, `${TableName.ProjectMembership}.userId`, `${TableName.Users}.id`)
|
.join(TableName.Users, `${TableName.ProjectMembership}.userId`, `${TableName.Users}.id`)
|
||||||
.join(TableName.ProjectKeys, `${TableName.ProjectMembership}.userId`, `${TableName.ProjectKeys}.receiverId`)
|
.join(TableName.ProjectKeys, `${TableName.ProjectMembership}.userId`, `${TableName.ProjectKeys}.receiverId`)
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import { TProjectPermission } from "@app/lib/types";
|
|||||||
import { ActorType } from "../auth/auth-type";
|
import { ActorType } from "../auth/auth-type";
|
||||||
import { TCertificateDALFactory } from "../certificate/certificate-dal";
|
import { TCertificateDALFactory } from "../certificate/certificate-dal";
|
||||||
import { TCertificateAuthorityDALFactory } from "../certificate-authority/certificate-authority-dal";
|
import { TCertificateAuthorityDALFactory } from "../certificate-authority/certificate-authority-dal";
|
||||||
|
import { TCertificateTemplateDALFactory } from "../certificate-template/certificate-template-dal";
|
||||||
import { TIdentityOrgDALFactory } from "../identity/identity-org-dal";
|
import { TIdentityOrgDALFactory } from "../identity/identity-org-dal";
|
||||||
import { TIdentityProjectDALFactory } from "../identity-project/identity-project-dal";
|
import { TIdentityProjectDALFactory } from "../identity-project/identity-project-dal";
|
||||||
import { TIdentityProjectMembershipRoleDALFactory } from "../identity-project/identity-project-membership-role-dal";
|
import { TIdentityProjectMembershipRoleDALFactory } from "../identity-project/identity-project-membership-role-dal";
|
||||||
@@ -41,6 +42,7 @@ import {
|
|||||||
TGetProjectKmsKey,
|
TGetProjectKmsKey,
|
||||||
TListProjectAlertsDTO,
|
TListProjectAlertsDTO,
|
||||||
TListProjectCasDTO,
|
TListProjectCasDTO,
|
||||||
|
TListProjectCertificateTemplatesDTO,
|
||||||
TListProjectCertsDTO,
|
TListProjectCertsDTO,
|
||||||
TLoadProjectKmsBackupDTO,
|
TLoadProjectKmsBackupDTO,
|
||||||
TToggleProjectAutoCapitalizationDTO,
|
TToggleProjectAutoCapitalizationDTO,
|
||||||
@@ -73,6 +75,7 @@ type TProjectServiceFactoryDep = {
|
|||||||
projectUserMembershipRoleDAL: Pick<TProjectUserMembershipRoleDALFactory, "create">;
|
projectUserMembershipRoleDAL: Pick<TProjectUserMembershipRoleDALFactory, "create">;
|
||||||
certificateAuthorityDAL: Pick<TCertificateAuthorityDALFactory, "find">;
|
certificateAuthorityDAL: Pick<TCertificateAuthorityDALFactory, "find">;
|
||||||
certificateDAL: Pick<TCertificateDALFactory, "find" | "countCertificatesInProject">;
|
certificateDAL: Pick<TCertificateDALFactory, "find" | "countCertificatesInProject">;
|
||||||
|
certificateTemplateDAL: Pick<TCertificateTemplateDALFactory, "getCertTemplatesByProjectId">;
|
||||||
pkiAlertDAL: Pick<TPkiAlertDALFactory, "find">;
|
pkiAlertDAL: Pick<TPkiAlertDALFactory, "find">;
|
||||||
pkiCollectionDAL: Pick<TPkiCollectionDALFactory, "find">;
|
pkiCollectionDAL: Pick<TPkiCollectionDALFactory, "find">;
|
||||||
permissionService: TPermissionServiceFactory;
|
permissionService: TPermissionServiceFactory;
|
||||||
@@ -112,6 +115,7 @@ export const projectServiceFactory = ({
|
|||||||
identityProjectMembershipRoleDAL,
|
identityProjectMembershipRoleDAL,
|
||||||
certificateAuthorityDAL,
|
certificateAuthorityDAL,
|
||||||
certificateDAL,
|
certificateDAL,
|
||||||
|
certificateTemplateDAL,
|
||||||
pkiCollectionDAL,
|
pkiCollectionDAL,
|
||||||
pkiAlertDAL,
|
pkiAlertDAL,
|
||||||
keyStore,
|
keyStore,
|
||||||
@@ -737,6 +741,36 @@ export const projectServiceFactory = ({
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return list of certificate templates for project
|
||||||
|
*/
|
||||||
|
const listProjectCertificateTemplates = async ({
|
||||||
|
projectId,
|
||||||
|
actorId,
|
||||||
|
actorOrgId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actor
|
||||||
|
}: TListProjectCertificateTemplatesDTO) => {
|
||||||
|
const { permission } = await permissionService.getProjectPermission(
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
projectId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId
|
||||||
|
);
|
||||||
|
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
|
ProjectPermissionActions.Read,
|
||||||
|
ProjectPermissionSub.CertificateTemplates
|
||||||
|
);
|
||||||
|
|
||||||
|
const certificateTemplates = await certificateTemplateDAL.getCertTemplatesByProjectId(projectId);
|
||||||
|
|
||||||
|
return {
|
||||||
|
certificateTemplates
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
const updateProjectKmsKey = async ({
|
const updateProjectKmsKey = async ({
|
||||||
projectId,
|
projectId,
|
||||||
kms,
|
kms,
|
||||||
@@ -857,6 +891,7 @@ export const projectServiceFactory = ({
|
|||||||
listProjectCertificates,
|
listProjectCertificates,
|
||||||
listProjectAlerts,
|
listProjectAlerts,
|
||||||
listProjectPkiCollections,
|
listProjectPkiCollections,
|
||||||
|
listProjectCertificateTemplates,
|
||||||
updateVersionLimit,
|
updateVersionLimit,
|
||||||
updateAuditLogsRetention,
|
updateAuditLogsRetention,
|
||||||
updateProjectKmsKey,
|
updateProjectKmsKey,
|
||||||
|
|||||||
@@ -117,3 +117,5 @@ export type TLoadProjectKmsBackupDTO = {
|
|||||||
} & TProjectPermission;
|
} & TProjectPermission;
|
||||||
|
|
||||||
export type TGetProjectKmsKey = TProjectPermission;
|
export type TGetProjectKmsKey = TProjectPermission;
|
||||||
|
|
||||||
|
export type TListProjectCertificateTemplatesDTO = TProjectPermission;
|
||||||
|
|||||||
@@ -2,9 +2,9 @@ import { TAuditLogDALFactory } from "@app/ee/services/audit-log/audit-log-dal";
|
|||||||
import { TSnapshotDALFactory } from "@app/ee/services/secret-snapshot/snapshot-dal";
|
import { TSnapshotDALFactory } from "@app/ee/services/secret-snapshot/snapshot-dal";
|
||||||
import { logger } from "@app/lib/logger";
|
import { logger } from "@app/lib/logger";
|
||||||
import { QueueJobs, QueueName, TQueueServiceFactory } from "@app/queue";
|
import { QueueJobs, QueueName, TQueueServiceFactory } from "@app/queue";
|
||||||
import { TPkiAlertServiceFactory } from "@app/services/pki-alert/pki-alert-service";
|
|
||||||
|
|
||||||
import { TIdentityAccessTokenDALFactory } from "../identity-access-token/identity-access-token-dal";
|
import { TIdentityAccessTokenDALFactory } from "../identity-access-token/identity-access-token-dal";
|
||||||
|
import { TIdentityUaClientSecretDALFactory } from "../identity-ua/identity-ua-client-secret-dal";
|
||||||
import { TSecretVersionDALFactory } from "../secret/secret-version-dal";
|
import { TSecretVersionDALFactory } from "../secret/secret-version-dal";
|
||||||
import { TSecretFolderVersionDALFactory } from "../secret-folder/secret-folder-version-dal";
|
import { TSecretFolderVersionDALFactory } from "../secret-folder/secret-folder-version-dal";
|
||||||
import { TSecretSharingDALFactory } from "../secret-sharing/secret-sharing-dal";
|
import { TSecretSharingDALFactory } from "../secret-sharing/secret-sharing-dal";
|
||||||
@@ -13,13 +13,13 @@ import { TSecretVersionV2DALFactory } from "../secret-v2-bridge/secret-version-d
|
|||||||
type TDailyResourceCleanUpQueueServiceFactoryDep = {
|
type TDailyResourceCleanUpQueueServiceFactoryDep = {
|
||||||
auditLogDAL: Pick<TAuditLogDALFactory, "pruneAuditLog">;
|
auditLogDAL: Pick<TAuditLogDALFactory, "pruneAuditLog">;
|
||||||
identityAccessTokenDAL: Pick<TIdentityAccessTokenDALFactory, "removeExpiredTokens">;
|
identityAccessTokenDAL: Pick<TIdentityAccessTokenDALFactory, "removeExpiredTokens">;
|
||||||
|
identityUniversalAuthClientSecretDAL: Pick<TIdentityUaClientSecretDALFactory, "removeExpiredClientSecrets">;
|
||||||
secretVersionDAL: Pick<TSecretVersionDALFactory, "pruneExcessVersions">;
|
secretVersionDAL: Pick<TSecretVersionDALFactory, "pruneExcessVersions">;
|
||||||
secretVersionV2DAL: Pick<TSecretVersionV2DALFactory, "pruneExcessVersions">;
|
secretVersionV2DAL: Pick<TSecretVersionV2DALFactory, "pruneExcessVersions">;
|
||||||
secretFolderVersionDAL: Pick<TSecretFolderVersionDALFactory, "pruneExcessVersions">;
|
secretFolderVersionDAL: Pick<TSecretFolderVersionDALFactory, "pruneExcessVersions">;
|
||||||
snapshotDAL: Pick<TSnapshotDALFactory, "pruneExcessSnapshots">;
|
snapshotDAL: Pick<TSnapshotDALFactory, "pruneExcessSnapshots">;
|
||||||
secretSharingDAL: Pick<TSecretSharingDALFactory, "pruneExpiredSharedSecrets">;
|
secretSharingDAL: Pick<TSecretSharingDALFactory, "pruneExpiredSharedSecrets">;
|
||||||
queueService: TQueueServiceFactory;
|
queueService: TQueueServiceFactory;
|
||||||
pkiAlertService: Pick<TPkiAlertServiceFactory, "sendPkiItemExpiryNotices">;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TDailyResourceCleanUpQueueServiceFactory = ReturnType<typeof dailyResourceCleanUpQueueServiceFactory>;
|
export type TDailyResourceCleanUpQueueServiceFactory = ReturnType<typeof dailyResourceCleanUpQueueServiceFactory>;
|
||||||
@@ -27,24 +27,24 @@ export type TDailyResourceCleanUpQueueServiceFactory = ReturnType<typeof dailyRe
|
|||||||
export const dailyResourceCleanUpQueueServiceFactory = ({
|
export const dailyResourceCleanUpQueueServiceFactory = ({
|
||||||
auditLogDAL,
|
auditLogDAL,
|
||||||
queueService,
|
queueService,
|
||||||
pkiAlertService,
|
|
||||||
snapshotDAL,
|
snapshotDAL,
|
||||||
secretVersionDAL,
|
secretVersionDAL,
|
||||||
secretFolderVersionDAL,
|
secretFolderVersionDAL,
|
||||||
identityAccessTokenDAL,
|
identityAccessTokenDAL,
|
||||||
secretSharingDAL,
|
secretSharingDAL,
|
||||||
secretVersionV2DAL
|
secretVersionV2DAL,
|
||||||
|
identityUniversalAuthClientSecretDAL
|
||||||
}: TDailyResourceCleanUpQueueServiceFactoryDep) => {
|
}: TDailyResourceCleanUpQueueServiceFactoryDep) => {
|
||||||
queueService.start(QueueName.DailyResourceCleanUp, async () => {
|
queueService.start(QueueName.DailyResourceCleanUp, async () => {
|
||||||
logger.info(`${QueueName.DailyResourceCleanUp}: queue task started`);
|
logger.info(`${QueueName.DailyResourceCleanUp}: queue task started`);
|
||||||
await auditLogDAL.pruneAuditLog();
|
await auditLogDAL.pruneAuditLog();
|
||||||
await identityAccessTokenDAL.removeExpiredTokens();
|
await identityAccessTokenDAL.removeExpiredTokens();
|
||||||
|
await identityUniversalAuthClientSecretDAL.removeExpiredClientSecrets();
|
||||||
await secretSharingDAL.pruneExpiredSharedSecrets();
|
await secretSharingDAL.pruneExpiredSharedSecrets();
|
||||||
await snapshotDAL.pruneExcessSnapshots();
|
await snapshotDAL.pruneExcessSnapshots();
|
||||||
await secretVersionDAL.pruneExcessVersions();
|
await secretVersionDAL.pruneExcessVersions();
|
||||||
await secretVersionV2DAL.pruneExcessVersions();
|
await secretVersionV2DAL.pruneExcessVersions();
|
||||||
await secretFolderVersionDAL.pruneExcessVersions();
|
await secretFolderVersionDAL.pruneExcessVersions();
|
||||||
await pkiAlertService.sendPkiItemExpiryNotices();
|
|
||||||
logger.info(`${QueueName.DailyResourceCleanUp}: queue task completed`);
|
logger.info(`${QueueName.DailyResourceCleanUp}: queue task completed`);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -36,8 +36,8 @@ type TSecretImportSecretsV2 = {
|
|||||||
secretKey: string;
|
secretKey: string;
|
||||||
// akhilmhdh: yes i know you can put ?.
|
// akhilmhdh: yes i know you can put ?.
|
||||||
// But for somereason ts consider ? and undefined explicit as different just ts things
|
// But for somereason ts consider ? and undefined explicit as different just ts things
|
||||||
secretValue: string | undefined;
|
secretValue: string;
|
||||||
secretComment: string | undefined;
|
secretComment: string;
|
||||||
})[];
|
})[];
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -157,7 +157,7 @@ export const fnSecretsV2FromImports = async ({
|
|||||||
secretImportDAL: Pick<TSecretImportDALFactory, "findByFolderIds">;
|
secretImportDAL: Pick<TSecretImportDALFactory, "findByFolderIds">;
|
||||||
depth?: number;
|
depth?: number;
|
||||||
cyclicDetector?: Set<string>;
|
cyclicDetector?: Set<string>;
|
||||||
decryptor: (value?: Buffer | null) => string | undefined;
|
decryptor: (value?: Buffer | null) => string;
|
||||||
expandSecretReferences?: (
|
expandSecretReferences?: (
|
||||||
secrets: Record<string, { value?: string; comment?: string; skipMultilineEncoding?: boolean | null }>
|
secrets: Record<string, { value?: string; comment?: string; skipMultilineEncoding?: boolean | null }>
|
||||||
) => Promise<Record<string, { value?: string; comment?: string; skipMultilineEncoding?: boolean | null }>>;
|
) => Promise<Record<string, { value?: string; comment?: string; skipMultilineEncoding?: boolean | null }>>;
|
||||||
@@ -231,6 +231,7 @@ export const fnSecretsV2FromImports = async ({
|
|||||||
_id: item.id // The old Python SDK depends on the _id field being returned. We return this to keep the older Python SDK versions backwards compatible with the new Postgres backend.
|
_id: item.id // The old Python SDK depends on the _id field being returned. We return this to keep the older Python SDK versions backwards compatible with the new Postgres backend.
|
||||||
}))
|
}))
|
||||||
.concat(folderDeeperImportSecrets);
|
.concat(folderDeeperImportSecrets);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
secretPath: importPath,
|
secretPath: importPath,
|
||||||
environment: importEnv.slug,
|
environment: importEnv.slug,
|
||||||
@@ -254,7 +255,7 @@ export const fnSecretsV2FromImports = async ({
|
|||||||
};
|
};
|
||||||
return acc;
|
return acc;
|
||||||
},
|
},
|
||||||
{} as Record<string, { value?: string; comment?: string; skipMultilineEncoding?: boolean | null }>
|
{} as Record<string, { value: string; comment?: string; skipMultilineEncoding?: boolean | null }>
|
||||||
);
|
);
|
||||||
// eslint-disable-next-line
|
// eslint-disable-next-line
|
||||||
await expandSecretReferences(secretsGroupByKey);
|
await expandSecretReferences(secretsGroupByKey);
|
||||||
|
|||||||
@@ -507,7 +507,7 @@ export const secretImportServiceFactory = ({
|
|||||||
folderDAL,
|
folderDAL,
|
||||||
secretDAL: secretV2BridgeDAL,
|
secretDAL: secretV2BridgeDAL,
|
||||||
secretImportDAL,
|
secretImportDAL,
|
||||||
decryptor: (value) => (value ? secretManagerDecryptor({ cipherTextBlob: value }).toString() : undefined)
|
decryptor: (value) => (value ? secretManagerDecryptor({ cipherTextBlob: value }).toString() : "")
|
||||||
});
|
});
|
||||||
return importedSecrets;
|
return importedSecrets;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ export const secretTagDALFactory = (db: TDbClient) => {
|
|||||||
...secretTagOrm,
|
...secretTagOrm,
|
||||||
saveTagsToSecret: secretJnTagOrm.insertMany,
|
saveTagsToSecret: secretJnTagOrm.insertMany,
|
||||||
deleteTagsToSecret: secretJnTagOrm.delete,
|
deleteTagsToSecret: secretJnTagOrm.delete,
|
||||||
saveTagsToSecretV2: secretV2JnTagOrm.insertMany,
|
saveTagsToSecretV2: secretV2JnTagOrm.batchInsert,
|
||||||
deleteTagsToSecretV2: secretV2JnTagOrm.delete,
|
deleteTagsToSecretV2: secretV2JnTagOrm.delete,
|
||||||
findSecretTagsByProjectId,
|
findSecretTagsByProjectId,
|
||||||
deleteTagsManySecret,
|
deleteTagsManySecret,
|
||||||
|
|||||||
@@ -22,16 +22,7 @@ type TSecretTagServiceFactoryDep = {
|
|||||||
export type TSecretTagServiceFactory = ReturnType<typeof secretTagServiceFactory>;
|
export type TSecretTagServiceFactory = ReturnType<typeof secretTagServiceFactory>;
|
||||||
|
|
||||||
export const secretTagServiceFactory = ({ secretTagDAL, permissionService }: TSecretTagServiceFactoryDep) => {
|
export const secretTagServiceFactory = ({ secretTagDAL, permissionService }: TSecretTagServiceFactoryDep) => {
|
||||||
const createTag = async ({
|
const createTag = async ({ slug, actor, color, actorId, actorOrgId, actorAuthMethod, projectId }: TCreateTagDTO) => {
|
||||||
name,
|
|
||||||
slug,
|
|
||||||
actor,
|
|
||||||
color,
|
|
||||||
actorId,
|
|
||||||
actorOrgId,
|
|
||||||
actorAuthMethod,
|
|
||||||
projectId
|
|
||||||
}: TCreateTagDTO) => {
|
|
||||||
const { permission } = await permissionService.getProjectPermission(
|
const { permission } = await permissionService.getProjectPermission(
|
||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
@@ -46,7 +37,6 @@ export const secretTagServiceFactory = ({ secretTagDAL, permissionService }: TSe
|
|||||||
|
|
||||||
const newTag = await secretTagDAL.create({
|
const newTag = await secretTagDAL.create({
|
||||||
projectId,
|
projectId,
|
||||||
name,
|
|
||||||
slug,
|
slug,
|
||||||
color,
|
color,
|
||||||
createdBy: actorId,
|
createdBy: actorId,
|
||||||
@@ -55,7 +45,7 @@ export const secretTagServiceFactory = ({ secretTagDAL, permissionService }: TSe
|
|||||||
return newTag;
|
return newTag;
|
||||||
};
|
};
|
||||||
|
|
||||||
const updateTag = async ({ actorId, actor, actorOrgId, actorAuthMethod, id, name, color, slug }: TUpdateTagDTO) => {
|
const updateTag = async ({ actorId, actor, actorOrgId, actorAuthMethod, id, color, slug }: TUpdateTagDTO) => {
|
||||||
const tag = await secretTagDAL.findById(id);
|
const tag = await secretTagDAL.findById(id);
|
||||||
if (!tag) throw new BadRequestError({ message: "Tag doesn't exist" });
|
if (!tag) throw new BadRequestError({ message: "Tag doesn't exist" });
|
||||||
|
|
||||||
@@ -73,7 +63,7 @@ export const secretTagServiceFactory = ({ secretTagDAL, permissionService }: TSe
|
|||||||
);
|
);
|
||||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Tags);
|
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Tags);
|
||||||
|
|
||||||
const updatedTag = await secretTagDAL.updateById(tag.id, { name, color, slug });
|
const updatedTag = await secretTagDAL.updateById(tag.id, { color, slug });
|
||||||
return updatedTag;
|
return updatedTag;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -107,7 +97,7 @@ export const secretTagServiceFactory = ({ secretTagDAL, permissionService }: TSe
|
|||||||
);
|
);
|
||||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Tags);
|
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Tags);
|
||||||
|
|
||||||
return tag;
|
return { ...tag, name: tag.slug };
|
||||||
};
|
};
|
||||||
|
|
||||||
const getTagBySlug = async ({ actorId, actor, actorOrgId, actorAuthMethod, slug, projectId }: TGetTagBySlugDTO) => {
|
const getTagBySlug = async ({ actorId, actor, actorOrgId, actorAuthMethod, slug, projectId }: TGetTagBySlugDTO) => {
|
||||||
@@ -123,7 +113,7 @@ export const secretTagServiceFactory = ({ secretTagDAL, permissionService }: TSe
|
|||||||
);
|
);
|
||||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Tags);
|
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Tags);
|
||||||
|
|
||||||
return tag;
|
return { ...tag, name: tag.slug };
|
||||||
};
|
};
|
||||||
|
|
||||||
const getProjectTags = async ({ actor, actorId, actorOrgId, actorAuthMethod, projectId }: TListProjectTagsDTO) => {
|
const getProjectTags = async ({ actor, actorId, actorOrgId, actorAuthMethod, projectId }: TListProjectTagsDTO) => {
|
||||||
|
|||||||
@@ -1,14 +1,12 @@
|
|||||||
import { TProjectPermission } from "@app/lib/types";
|
import { TProjectPermission } from "@app/lib/types";
|
||||||
|
|
||||||
export type TCreateTagDTO = {
|
export type TCreateTagDTO = {
|
||||||
name: string;
|
|
||||||
color: string;
|
color: string;
|
||||||
slug: string;
|
slug: string;
|
||||||
} & TProjectPermission;
|
} & TProjectPermission;
|
||||||
|
|
||||||
export type TUpdateTagDTO = {
|
export type TUpdateTagDTO = {
|
||||||
id: string;
|
id: string;
|
||||||
name?: string;
|
|
||||||
slug?: string;
|
slug?: string;
|
||||||
color?: string;
|
color?: string;
|
||||||
} & Omit<TProjectPermission, "projectId">;
|
} & Omit<TProjectPermission, "projectId">;
|
||||||
|
|||||||
@@ -136,7 +136,6 @@ export const secretV2BridgeDALFactory = (db: TDbClient) => {
|
|||||||
.select(db.ref("id").withSchema(TableName.SecretTag).as("tagId"))
|
.select(db.ref("id").withSchema(TableName.SecretTag).as("tagId"))
|
||||||
.select(db.ref("color").withSchema(TableName.SecretTag).as("tagColor"))
|
.select(db.ref("color").withSchema(TableName.SecretTag).as("tagColor"))
|
||||||
.select(db.ref("slug").withSchema(TableName.SecretTag).as("tagSlug"))
|
.select(db.ref("slug").withSchema(TableName.SecretTag).as("tagSlug"))
|
||||||
.select(db.ref("name").withSchema(TableName.SecretTag).as("tagName"))
|
|
||||||
.orderBy("id", "asc");
|
.orderBy("id", "asc");
|
||||||
|
|
||||||
const data = sqlNestRelationships({
|
const data = sqlNestRelationships({
|
||||||
@@ -147,11 +146,11 @@ export const secretV2BridgeDALFactory = (db: TDbClient) => {
|
|||||||
{
|
{
|
||||||
key: "tagId",
|
key: "tagId",
|
||||||
label: "tags" as const,
|
label: "tags" as const,
|
||||||
mapper: ({ tagId: id, tagColor: color, tagSlug: slug, tagName: name }) => ({
|
mapper: ({ tagId: id, tagColor: color, tagSlug: slug }) => ({
|
||||||
id,
|
id,
|
||||||
color,
|
color,
|
||||||
slug,
|
slug,
|
||||||
name
|
name: slug
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -169,14 +168,13 @@ export const secretV2BridgeDALFactory = (db: TDbClient) => {
|
|||||||
.where({ [`${TableName.SecretV2}Id` as const]: secretId })
|
.where({ [`${TableName.SecretV2}Id` as const]: secretId })
|
||||||
.select(db.ref("id").withSchema(TableName.SecretTag).as("tagId"))
|
.select(db.ref("id").withSchema(TableName.SecretTag).as("tagId"))
|
||||||
.select(db.ref("color").withSchema(TableName.SecretTag).as("tagColor"))
|
.select(db.ref("color").withSchema(TableName.SecretTag).as("tagColor"))
|
||||||
.select(db.ref("slug").withSchema(TableName.SecretTag).as("tagSlug"))
|
.select(db.ref("slug").withSchema(TableName.SecretTag).as("tagSlug"));
|
||||||
.select(db.ref("name").withSchema(TableName.SecretTag).as("tagName"));
|
|
||||||
|
|
||||||
return tags.map((el) => ({
|
return tags.map((el) => ({
|
||||||
id: el.tagId,
|
id: el.tagId,
|
||||||
color: el.tagColor,
|
color: el.tagColor,
|
||||||
slug: el.tagSlug,
|
slug: el.tagSlug,
|
||||||
name: el.tagName
|
name: el.tagSlug
|
||||||
}));
|
}));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw new DatabaseError({ error, name: "get secret tags" });
|
throw new DatabaseError({ error, name: "get secret tags" });
|
||||||
@@ -210,7 +208,6 @@ export const secretV2BridgeDALFactory = (db: TDbClient) => {
|
|||||||
.select(db.ref("id").withSchema(TableName.SecretTag).as("tagId"))
|
.select(db.ref("id").withSchema(TableName.SecretTag).as("tagId"))
|
||||||
.select(db.ref("color").withSchema(TableName.SecretTag).as("tagColor"))
|
.select(db.ref("color").withSchema(TableName.SecretTag).as("tagColor"))
|
||||||
.select(db.ref("slug").withSchema(TableName.SecretTag).as("tagSlug"))
|
.select(db.ref("slug").withSchema(TableName.SecretTag).as("tagSlug"))
|
||||||
.select(db.ref("name").withSchema(TableName.SecretTag).as("tagName"))
|
|
||||||
.orderBy("id", "asc");
|
.orderBy("id", "asc");
|
||||||
|
|
||||||
const data = sqlNestRelationships({
|
const data = sqlNestRelationships({
|
||||||
@@ -221,11 +218,11 @@ export const secretV2BridgeDALFactory = (db: TDbClient) => {
|
|||||||
{
|
{
|
||||||
key: "tagId",
|
key: "tagId",
|
||||||
label: "tags" as const,
|
label: "tags" as const,
|
||||||
mapper: ({ tagId: id, tagColor: color, tagSlug: slug, tagName: name }) => ({
|
mapper: ({ tagId: id, tagColor: color, tagSlug: slug }) => ({
|
||||||
id,
|
id,
|
||||||
color,
|
color,
|
||||||
slug,
|
slug,
|
||||||
name
|
name: slug
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -290,7 +287,7 @@ export const secretV2BridgeDALFactory = (db: TDbClient) => {
|
|||||||
}))
|
}))
|
||||||
);
|
);
|
||||||
if (!newSecretReferences.length) return;
|
if (!newSecretReferences.length) return;
|
||||||
const secretReferences = await (tx || db)(TableName.SecretReferenceV2).insert(newSecretReferences);
|
const secretReferences = await (tx || db).batchInsert(TableName.SecretReferenceV2, newSecretReferences);
|
||||||
return secretReferences;
|
return secretReferences;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw new DatabaseError({ error, name: "UpsertSecretReference" });
|
throw new DatabaseError({ error, name: "UpsertSecretReference" });
|
||||||
@@ -350,8 +347,7 @@ export const secretV2BridgeDALFactory = (db: TDbClient) => {
|
|||||||
.select(selectAllTableCols(TableName.SecretV2))
|
.select(selectAllTableCols(TableName.SecretV2))
|
||||||
.select(db.ref("id").withSchema(TableName.SecretTag).as("tagId"))
|
.select(db.ref("id").withSchema(TableName.SecretTag).as("tagId"))
|
||||||
.select(db.ref("color").withSchema(TableName.SecretTag).as("tagColor"))
|
.select(db.ref("color").withSchema(TableName.SecretTag).as("tagColor"))
|
||||||
.select(db.ref("slug").withSchema(TableName.SecretTag).as("tagSlug"))
|
.select(db.ref("slug").withSchema(TableName.SecretTag).as("tagSlug"));
|
||||||
.select(db.ref("name").withSchema(TableName.SecretTag).as("tagName"));
|
|
||||||
const docs = sqlNestRelationships({
|
const docs = sqlNestRelationships({
|
||||||
data: rawDocs,
|
data: rawDocs,
|
||||||
key: "id",
|
key: "id",
|
||||||
@@ -360,11 +356,11 @@ export const secretV2BridgeDALFactory = (db: TDbClient) => {
|
|||||||
{
|
{
|
||||||
key: "tagId",
|
key: "tagId",
|
||||||
label: "tags" as const,
|
label: "tags" as const,
|
||||||
mapper: ({ tagId: id, tagColor: color, tagSlug: slug, tagName: name }) => ({
|
mapper: ({ tagId: id, tagColor: color, tagSlug: slug }) => ({
|
||||||
id,
|
id,
|
||||||
color,
|
color,
|
||||||
slug,
|
slug,
|
||||||
name
|
name: slug
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -528,8 +528,8 @@ export const reshapeBridgeSecret = (
|
|||||||
environment: string,
|
environment: string,
|
||||||
secretPath: string,
|
secretPath: string,
|
||||||
secret: Omit<TSecretsV2, "encryptedValue" | "encryptedComment"> & {
|
secret: Omit<TSecretsV2, "encryptedValue" | "encryptedComment"> & {
|
||||||
value?: string;
|
value: string;
|
||||||
comment?: string;
|
comment: string;
|
||||||
tags?: {
|
tags?: {
|
||||||
id: string;
|
id: string;
|
||||||
slug: string;
|
slug: string;
|
||||||
@@ -542,8 +542,8 @@ export const reshapeBridgeSecret = (
|
|||||||
secretPath,
|
secretPath,
|
||||||
workspace: workspaceId,
|
workspace: workspaceId,
|
||||||
environment,
|
environment,
|
||||||
secretValue: secret.value,
|
secretValue: secret.value || "",
|
||||||
secretComment: secret.comment,
|
secretComment: secret.comment || "",
|
||||||
version: secret.version,
|
version: secret.version,
|
||||||
type: secret.type,
|
type: secret.type,
|
||||||
_id: secret.id,
|
_id: secret.id,
|
||||||
|
|||||||
@@ -196,7 +196,7 @@ export const secretV2BridgeServiceFactory = ({
|
|||||||
return reshapeBridgeSecret(projectId, environment, secretPath, {
|
return reshapeBridgeSecret(projectId, environment, secretPath, {
|
||||||
...secret[0],
|
...secret[0],
|
||||||
value: inputSecret.secretValue,
|
value: inputSecret.secretValue,
|
||||||
comment: inputSecret.secretComment
|
comment: inputSecret.secretComment || ""
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -339,8 +339,8 @@ export const secretV2BridgeServiceFactory = ({
|
|||||||
});
|
});
|
||||||
return reshapeBridgeSecret(projectId, environment, secretPath, {
|
return reshapeBridgeSecret(projectId, environment, secretPath, {
|
||||||
...updatedSecret[0],
|
...updatedSecret[0],
|
||||||
value: inputSecret.secretValue,
|
value: inputSecret.secretValue || "",
|
||||||
comment: inputSecret.secretComment
|
comment: inputSecret.secretComment || ""
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -378,6 +378,18 @@ export const secretV2BridgeServiceFactory = ({
|
|||||||
throw new BadRequestError({ message: "Must be user to delete personal secret" });
|
throw new BadRequestError({ message: "Must be user to delete personal secret" });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const secretToDelete = await secretDAL.findOne({
|
||||||
|
key: inputSecret.secretName,
|
||||||
|
folderId,
|
||||||
|
...(inputSecret.type === SecretType.Shared
|
||||||
|
? {}
|
||||||
|
: {
|
||||||
|
type: SecretType.Personal,
|
||||||
|
userId: actorId
|
||||||
|
})
|
||||||
|
});
|
||||||
|
if (!secretToDelete) throw new NotFoundError({ message: "Secret not found" });
|
||||||
|
|
||||||
const deletedSecret = await secretDAL.transaction(async (tx) =>
|
const deletedSecret = await secretDAL.transaction(async (tx) =>
|
||||||
fnSecretBulkDelete({
|
fnSecretBulkDelete({
|
||||||
projectId,
|
projectId,
|
||||||
@@ -412,10 +424,10 @@ export const secretV2BridgeServiceFactory = ({
|
|||||||
...deletedSecret[0],
|
...deletedSecret[0],
|
||||||
value: deletedSecret[0].encryptedValue
|
value: deletedSecret[0].encryptedValue
|
||||||
? secretManagerDecryptor({ cipherTextBlob: deletedSecret[0].encryptedValue }).toString()
|
? secretManagerDecryptor({ cipherTextBlob: deletedSecret[0].encryptedValue }).toString()
|
||||||
: undefined,
|
: "",
|
||||||
comment: deletedSecret[0].encryptedComment
|
comment: deletedSecret[0].encryptedComment
|
||||||
? secretManagerDecryptor({ cipherTextBlob: deletedSecret[0].encryptedComment }).toString()
|
? secretManagerDecryptor({ cipherTextBlob: deletedSecret[0].encryptedComment }).toString()
|
||||||
: undefined
|
: ""
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -429,6 +441,7 @@ export const secretV2BridgeServiceFactory = ({
|
|||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
includeImports,
|
includeImports,
|
||||||
recursive,
|
recursive,
|
||||||
|
tagSlugs = [],
|
||||||
expandSecretReferences: shouldExpandSecretReferences
|
expandSecretReferences: shouldExpandSecretReferences
|
||||||
}: TGetSecretsDTO) => {
|
}: TGetSecretsDTO) => {
|
||||||
const { permission } = await permissionService.getProjectPermission(
|
const { permission } = await permissionService.getProjectPermission(
|
||||||
@@ -496,6 +509,9 @@ export const secretV2BridgeServiceFactory = ({
|
|||||||
: ""
|
: ""
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
const filteredSecrets = tagSlugs.length
|
||||||
|
? decryptedSecrets.filter((secret) => Boolean(secret.tags?.find((el) => tagSlugs.includes(el.slug))))
|
||||||
|
: decryptedSecrets;
|
||||||
const expandSecretReferences = expandSecretReferencesFactory({
|
const expandSecretReferences = expandSecretReferencesFactory({
|
||||||
projectId,
|
projectId,
|
||||||
folderDAL,
|
folderDAL,
|
||||||
@@ -504,7 +520,7 @@ export const secretV2BridgeServiceFactory = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (shouldExpandSecretReferences) {
|
if (shouldExpandSecretReferences) {
|
||||||
const secretsGroupByPath = groupBy(decryptedSecrets, (i) => i.secretPath);
|
const secretsGroupByPath = groupBy(filteredSecrets, (i) => i.secretPath);
|
||||||
for (const secretPathKey in secretsGroupByPath) {
|
for (const secretPathKey in secretsGroupByPath) {
|
||||||
if (Object.hasOwn(secretsGroupByPath, secretPathKey)) {
|
if (Object.hasOwn(secretsGroupByPath, secretPathKey)) {
|
||||||
const secretsGroupByKey = secretsGroupByPath[secretPathKey].reduce(
|
const secretsGroupByKey = secretsGroupByPath[secretPathKey].reduce(
|
||||||
@@ -522,7 +538,7 @@ export const secretV2BridgeServiceFactory = ({
|
|||||||
await expandSecretReferences(secretsGroupByKey);
|
await expandSecretReferences(secretsGroupByKey);
|
||||||
secretsGroupByPath[secretPathKey].forEach((decryptedSecret) => {
|
secretsGroupByPath[secretPathKey].forEach((decryptedSecret) => {
|
||||||
// eslint-disable-next-line no-param-reassign
|
// eslint-disable-next-line no-param-reassign
|
||||||
decryptedSecret.secretValue = secretsGroupByKey[decryptedSecret.secretKey].value;
|
decryptedSecret.secretValue = secretsGroupByKey[decryptedSecret.secretKey].value || "";
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -530,7 +546,7 @@ export const secretV2BridgeServiceFactory = ({
|
|||||||
|
|
||||||
if (!includeImports) {
|
if (!includeImports) {
|
||||||
return {
|
return {
|
||||||
secrets: decryptedSecrets
|
secrets: filteredSecrets
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -554,11 +570,11 @@ export const secretV2BridgeServiceFactory = ({
|
|||||||
folderDAL,
|
folderDAL,
|
||||||
secretImportDAL,
|
secretImportDAL,
|
||||||
expandSecretReferences,
|
expandSecretReferences,
|
||||||
decryptor: (value) => (value ? secretManagerDecryptor({ cipherTextBlob: value }).toString() : undefined)
|
decryptor: (value) => (value ? secretManagerDecryptor({ cipherTextBlob: value }).toString() : "")
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
secrets: decryptedSecrets,
|
secrets: filteredSecrets,
|
||||||
imports: importedSecrets
|
imports: importedSecrets
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
@@ -654,7 +670,7 @@ export const secretV2BridgeServiceFactory = ({
|
|||||||
secretDAL,
|
secretDAL,
|
||||||
folderDAL,
|
folderDAL,
|
||||||
secretImportDAL,
|
secretImportDAL,
|
||||||
decryptor: (value) => (value ? secretManagerDecryptor({ cipherTextBlob: value }).toString() : undefined),
|
decryptor: (value) => (value ? secretManagerDecryptor({ cipherTextBlob: value }).toString() : ""),
|
||||||
expandSecretReferences: shouldExpandSecretReferences ? expandSecretReferences : undefined
|
expandSecretReferences: shouldExpandSecretReferences ? expandSecretReferences : undefined
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -662,12 +678,11 @@ export const secretV2BridgeServiceFactory = ({
|
|||||||
for (let j = 0; j < importedSecrets[i].secrets.length; j += 1) {
|
for (let j = 0; j < importedSecrets[i].secrets.length; j += 1) {
|
||||||
const importedSecret = importedSecrets[i].secrets[j];
|
const importedSecret = importedSecrets[i].secrets[j];
|
||||||
if (secretName === importedSecret.key) {
|
if (secretName === importedSecret.key) {
|
||||||
return reshapeBridgeSecret(
|
return reshapeBridgeSecret(projectId, importedSecrets[i].environment, importedSecrets[i].secretPath, {
|
||||||
projectId,
|
...importedSecret,
|
||||||
importedSecrets[i].environment,
|
value: importedSecret.secretValue || "",
|
||||||
importedSecrets[i].secretPath,
|
comment: importedSecret.secretComment || ""
|
||||||
importedSecret
|
});
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -676,7 +691,7 @@ export const secretV2BridgeServiceFactory = ({
|
|||||||
|
|
||||||
let secretValue = secret.encryptedValue
|
let secretValue = secret.encryptedValue
|
||||||
? secretManagerDecryptor({ cipherTextBlob: secret.encryptedValue }).toString()
|
? secretManagerDecryptor({ cipherTextBlob: secret.encryptedValue }).toString()
|
||||||
: undefined;
|
: "";
|
||||||
if (shouldExpandSecretReferences && secretValue) {
|
if (shouldExpandSecretReferences && secretValue) {
|
||||||
const secretReferenceExpandedRecord = {
|
const secretReferenceExpandedRecord = {
|
||||||
[secret.key]: { value: secretValue }
|
[secret.key]: { value: secretValue }
|
||||||
@@ -691,7 +706,7 @@ export const secretV2BridgeServiceFactory = ({
|
|||||||
value: secretValue,
|
value: secretValue,
|
||||||
comment: secret.encryptedComment
|
comment: secret.encryptedComment
|
||||||
? secretManagerDecryptor({ cipherTextBlob: secret.encryptedComment }).toString()
|
? secretManagerDecryptor({ cipherTextBlob: secret.encryptedComment }).toString()
|
||||||
: undefined
|
: ""
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -781,10 +796,8 @@ export const secretV2BridgeServiceFactory = ({
|
|||||||
return newSecrets.map((el) =>
|
return newSecrets.map((el) =>
|
||||||
reshapeBridgeSecret(projectId, environment, secretPath, {
|
reshapeBridgeSecret(projectId, environment, secretPath, {
|
||||||
...el,
|
...el,
|
||||||
value: el.encryptedValue ? secretManagerDecryptor({ cipherTextBlob: el.encryptedValue }).toString() : undefined,
|
value: el.encryptedValue ? secretManagerDecryptor({ cipherTextBlob: el.encryptedValue }).toString() : "",
|
||||||
comment: el.encryptedComment
|
comment: el.encryptedComment ? secretManagerDecryptor({ cipherTextBlob: el.encryptedComment }).toString() : ""
|
||||||
? secretManagerDecryptor({ cipherTextBlob: el.encryptedComment }).toString()
|
|
||||||
: undefined
|
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -902,10 +915,8 @@ export const secretV2BridgeServiceFactory = ({
|
|||||||
return secrets.map((el) =>
|
return secrets.map((el) =>
|
||||||
reshapeBridgeSecret(projectId, environment, secretPath, {
|
reshapeBridgeSecret(projectId, environment, secretPath, {
|
||||||
...el,
|
...el,
|
||||||
value: el.encryptedValue ? secretManagerDecryptor({ cipherTextBlob: el.encryptedValue }).toString() : undefined,
|
value: el.encryptedValue ? secretManagerDecryptor({ cipherTextBlob: el.encryptedValue }).toString() : "",
|
||||||
comment: el.encryptedComment
|
comment: el.encryptedComment ? secretManagerDecryptor({ cipherTextBlob: el.encryptedComment }).toString() : ""
|
||||||
? secretManagerDecryptor({ cipherTextBlob: el.encryptedComment }).toString()
|
|
||||||
: undefined
|
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -981,10 +992,8 @@ export const secretV2BridgeServiceFactory = ({
|
|||||||
return secretsDeleted.map((el) =>
|
return secretsDeleted.map((el) =>
|
||||||
reshapeBridgeSecret(projectId, environment, secretPath, {
|
reshapeBridgeSecret(projectId, environment, secretPath, {
|
||||||
...el,
|
...el,
|
||||||
value: el.encryptedValue ? secretManagerDecryptor({ cipherTextBlob: el.encryptedValue }).toString() : undefined,
|
value: el.encryptedValue ? secretManagerDecryptor({ cipherTextBlob: el.encryptedValue }).toString() : "",
|
||||||
comment: el.encryptedComment
|
comment: el.encryptedComment ? secretManagerDecryptor({ cipherTextBlob: el.encryptedComment }).toString() : ""
|
||||||
? secretManagerDecryptor({ cipherTextBlob: el.encryptedComment }).toString()
|
|
||||||
: undefined
|
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -1020,10 +1029,8 @@ export const secretV2BridgeServiceFactory = ({
|
|||||||
return secretVersions.map((el) =>
|
return secretVersions.map((el) =>
|
||||||
reshapeBridgeSecret(folder.projectId, folder.environment.envSlug, "/", {
|
reshapeBridgeSecret(folder.projectId, folder.environment.envSlug, "/", {
|
||||||
...el,
|
...el,
|
||||||
value: el.encryptedValue ? secretManagerDecryptor({ cipherTextBlob: el.encryptedValue }).toString() : undefined,
|
value: el.encryptedValue ? secretManagerDecryptor({ cipherTextBlob: el.encryptedValue }).toString() : "",
|
||||||
comment: el.encryptedComment
|
comment: el.encryptedComment ? secretManagerDecryptor({ cipherTextBlob: el.encryptedComment }).toString() : ""
|
||||||
? secretManagerDecryptor({ cipherTextBlob: el.encryptedComment }).toString()
|
|
||||||
: undefined
|
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ export type TGetSecretsDTO = {
|
|||||||
environment: string;
|
environment: string;
|
||||||
includeImports?: boolean;
|
includeImports?: boolean;
|
||||||
recursive?: boolean;
|
recursive?: boolean;
|
||||||
|
tagSlugs?: string[];
|
||||||
} & TProjectPermission;
|
} & TProjectPermission;
|
||||||
|
|
||||||
export type TGetASecretDTO = {
|
export type TGetASecretDTO = {
|
||||||
|
|||||||
@@ -123,7 +123,6 @@ export const secretDALFactory = (db: TDbClient) => {
|
|||||||
.select(db.ref("id").withSchema(TableName.SecretTag).as("tagId"))
|
.select(db.ref("id").withSchema(TableName.SecretTag).as("tagId"))
|
||||||
.select(db.ref("color").withSchema(TableName.SecretTag).as("tagColor"))
|
.select(db.ref("color").withSchema(TableName.SecretTag).as("tagColor"))
|
||||||
.select(db.ref("slug").withSchema(TableName.SecretTag).as("tagSlug"))
|
.select(db.ref("slug").withSchema(TableName.SecretTag).as("tagSlug"))
|
||||||
.select(db.ref("name").withSchema(TableName.SecretTag).as("tagName"))
|
|
||||||
.orderBy("id", "asc");
|
.orderBy("id", "asc");
|
||||||
const data = sqlNestRelationships({
|
const data = sqlNestRelationships({
|
||||||
data: secs,
|
data: secs,
|
||||||
@@ -133,11 +132,11 @@ export const secretDALFactory = (db: TDbClient) => {
|
|||||||
{
|
{
|
||||||
key: "tagId",
|
key: "tagId",
|
||||||
label: "tags" as const,
|
label: "tags" as const,
|
||||||
mapper: ({ tagId: id, tagColor: color, tagSlug: slug, tagName: name }) => ({
|
mapper: ({ tagId: id, tagColor: color, tagSlug: slug }) => ({
|
||||||
id,
|
id,
|
||||||
color,
|
color,
|
||||||
slug,
|
slug,
|
||||||
name
|
name: slug
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -155,14 +154,13 @@ export const secretDALFactory = (db: TDbClient) => {
|
|||||||
.where({ [`${TableName.Secret}Id` as const]: secretId })
|
.where({ [`${TableName.Secret}Id` as const]: secretId })
|
||||||
.select(db.ref("id").withSchema(TableName.SecretTag).as("tagId"))
|
.select(db.ref("id").withSchema(TableName.SecretTag).as("tagId"))
|
||||||
.select(db.ref("color").withSchema(TableName.SecretTag).as("tagColor"))
|
.select(db.ref("color").withSchema(TableName.SecretTag).as("tagColor"))
|
||||||
.select(db.ref("slug").withSchema(TableName.SecretTag).as("tagSlug"))
|
.select(db.ref("slug").withSchema(TableName.SecretTag).as("tagSlug"));
|
||||||
.select(db.ref("name").withSchema(TableName.SecretTag).as("tagName"));
|
|
||||||
|
|
||||||
return tags.map((el) => ({
|
return tags.map((el) => ({
|
||||||
id: el.tagId,
|
id: el.tagId,
|
||||||
color: el.tagColor,
|
color: el.tagColor,
|
||||||
slug: el.tagSlug,
|
slug: el.tagSlug,
|
||||||
name: el.tagName
|
name: el.tagSlug
|
||||||
}));
|
}));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw new DatabaseError({ error, name: "get secret tags" });
|
throw new DatabaseError({ error, name: "get secret tags" });
|
||||||
@@ -188,7 +186,6 @@ export const secretDALFactory = (db: TDbClient) => {
|
|||||||
.select(db.ref("id").withSchema(TableName.SecretTag).as("tagId"))
|
.select(db.ref("id").withSchema(TableName.SecretTag).as("tagId"))
|
||||||
.select(db.ref("color").withSchema(TableName.SecretTag).as("tagColor"))
|
.select(db.ref("color").withSchema(TableName.SecretTag).as("tagColor"))
|
||||||
.select(db.ref("slug").withSchema(TableName.SecretTag).as("tagSlug"))
|
.select(db.ref("slug").withSchema(TableName.SecretTag).as("tagSlug"))
|
||||||
.select(db.ref("name").withSchema(TableName.SecretTag).as("tagName"))
|
|
||||||
.orderBy("id", "asc");
|
.orderBy("id", "asc");
|
||||||
const data = sqlNestRelationships({
|
const data = sqlNestRelationships({
|
||||||
data: secs,
|
data: secs,
|
||||||
@@ -198,11 +195,11 @@ export const secretDALFactory = (db: TDbClient) => {
|
|||||||
{
|
{
|
||||||
key: "tagId",
|
key: "tagId",
|
||||||
label: "tags" as const,
|
label: "tags" as const,
|
||||||
mapper: ({ tagId: id, tagColor: color, tagSlug: slug, tagName: name }) => ({
|
mapper: ({ tagId: id, tagColor: color, tagSlug: slug }) => ({
|
||||||
id,
|
id,
|
||||||
color,
|
color,
|
||||||
slug,
|
slug,
|
||||||
name
|
name: slug
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -318,8 +315,7 @@ export const secretDALFactory = (db: TDbClient) => {
|
|||||||
.select(selectAllTableCols(TableName.Secret))
|
.select(selectAllTableCols(TableName.Secret))
|
||||||
.select(db.ref("id").withSchema(TableName.SecretTag).as("tagId"))
|
.select(db.ref("id").withSchema(TableName.SecretTag).as("tagId"))
|
||||||
.select(db.ref("color").withSchema(TableName.SecretTag).as("tagColor"))
|
.select(db.ref("color").withSchema(TableName.SecretTag).as("tagColor"))
|
||||||
.select(db.ref("slug").withSchema(TableName.SecretTag).as("tagSlug"))
|
.select(db.ref("slug").withSchema(TableName.SecretTag).as("tagSlug"));
|
||||||
.select(db.ref("name").withSchema(TableName.SecretTag).as("tagName"));
|
|
||||||
const docs = sqlNestRelationships({
|
const docs = sqlNestRelationships({
|
||||||
data: rawDocs,
|
data: rawDocs,
|
||||||
key: "id",
|
key: "id",
|
||||||
@@ -328,11 +324,11 @@ export const secretDALFactory = (db: TDbClient) => {
|
|||||||
{
|
{
|
||||||
key: "tagId",
|
key: "tagId",
|
||||||
label: "tags" as const,
|
label: "tags" as const,
|
||||||
mapper: ({ tagId: id, tagColor: color, tagSlug: slug, tagName: name }) => ({
|
mapper: ({ tagId: id, tagColor: color, tagSlug: slug }) => ({
|
||||||
id,
|
id,
|
||||||
color,
|
color,
|
||||||
slug,
|
slug,
|
||||||
name
|
name: slug
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -370,7 +370,6 @@ export const decryptSecretRaw = (
|
|||||||
id: string;
|
id: string;
|
||||||
slug: string;
|
slug: string;
|
||||||
color?: string | null;
|
color?: string | null;
|
||||||
name: string;
|
|
||||||
}[];
|
}[];
|
||||||
},
|
},
|
||||||
key: string
|
key: string
|
||||||
@@ -412,7 +411,7 @@ export const decryptSecretRaw = (
|
|||||||
_id: secret.id,
|
_id: secret.id,
|
||||||
id: secret.id,
|
id: secret.id,
|
||||||
user: secret.userId,
|
user: secret.userId,
|
||||||
tags: secret.tags,
|
tags: secret.tags?.map((el) => ({ ...el, name: el.slug })),
|
||||||
skipMultilineEncoding: secret.skipMultilineEncoding,
|
skipMultilineEncoding: secret.skipMultilineEncoding,
|
||||||
secretReminderRepeatDays: secret.secretReminderRepeatDays,
|
secretReminderRepeatDays: secret.secretReminderRepeatDays,
|
||||||
secretReminderNote: secret.secretReminderNote,
|
secretReminderNote: secret.secretReminderNote,
|
||||||
|
|||||||
@@ -6,11 +6,12 @@ import { TSecretApprovalRequestDALFactory } from "@app/ee/services/secret-approv
|
|||||||
import { TSecretRotationDALFactory } from "@app/ee/services/secret-rotation/secret-rotation-dal";
|
import { TSecretRotationDALFactory } from "@app/ee/services/secret-rotation/secret-rotation-dal";
|
||||||
import { TSnapshotDALFactory } from "@app/ee/services/secret-snapshot/snapshot-dal";
|
import { TSnapshotDALFactory } from "@app/ee/services/secret-snapshot/snapshot-dal";
|
||||||
import { TSnapshotSecretV2DALFactory } from "@app/ee/services/secret-snapshot/snapshot-secret-v2-dal";
|
import { TSnapshotSecretV2DALFactory } from "@app/ee/services/secret-snapshot/snapshot-secret-v2-dal";
|
||||||
|
import { KeyStorePrefixes, KeyStoreTtls, TKeyStoreFactory } from "@app/keystore/keystore";
|
||||||
import { getConfig } from "@app/lib/config/env";
|
import { getConfig } from "@app/lib/config/env";
|
||||||
import { decryptSymmetric128BitHexKeyUTF8 } from "@app/lib/crypto";
|
import { decryptSymmetric128BitHexKeyUTF8 } from "@app/lib/crypto";
|
||||||
import { daysToMillisecond, secondsToMillis } from "@app/lib/dates";
|
import { daysToMillisecond, secondsToMillis } from "@app/lib/dates";
|
||||||
import { BadRequestError } from "@app/lib/errors";
|
import { BadRequestError } from "@app/lib/errors";
|
||||||
import { groupBy, isSamePath, unique } from "@app/lib/fn";
|
import { getTimeDifferenceInSeconds, groupBy, isSamePath, unique } from "@app/lib/fn";
|
||||||
import { logger } from "@app/lib/logger";
|
import { logger } from "@app/lib/logger";
|
||||||
import { QueueJobs, QueueName, TQueueServiceFactory } from "@app/queue";
|
import { QueueJobs, QueueName, TQueueServiceFactory } from "@app/queue";
|
||||||
import { TProjectBotDALFactory } from "@app/services/project-bot/project-bot-dal";
|
import { TProjectBotDALFactory } from "@app/services/project-bot/project-bot-dal";
|
||||||
@@ -73,12 +74,13 @@ type TSecretQueueFactoryDep = {
|
|||||||
secretVersionTagDAL: TSecretVersionTagDALFactory;
|
secretVersionTagDAL: TSecretVersionTagDALFactory;
|
||||||
kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey">;
|
kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey">;
|
||||||
secretV2BridgeDAL: TSecretV2BridgeDALFactory;
|
secretV2BridgeDAL: TSecretV2BridgeDALFactory;
|
||||||
secretVersionV2BridgeDAL: Pick<TSecretVersionV2DALFactory, "insertMany" | "findLatestVersionMany">;
|
secretVersionV2BridgeDAL: Pick<TSecretVersionV2DALFactory, "batchInsert" | "insertMany" | "findLatestVersionMany">;
|
||||||
secretVersionTagV2BridgeDAL: Pick<TSecretVersionV2TagDALFactory, "insertMany">;
|
secretVersionTagV2BridgeDAL: Pick<TSecretVersionV2TagDALFactory, "insertMany" | "batchInsert">;
|
||||||
secretRotationDAL: Pick<TSecretRotationDALFactory, "secretOutputV2InsertMany" | "find">;
|
secretRotationDAL: Pick<TSecretRotationDALFactory, "secretOutputV2InsertMany" | "find">;
|
||||||
secretApprovalRequestDAL: Pick<TSecretApprovalRequestDALFactory, "deleteByProjectId">;
|
secretApprovalRequestDAL: Pick<TSecretApprovalRequestDALFactory, "deleteByProjectId">;
|
||||||
snapshotDAL: Pick<TSnapshotDALFactory, "findNSecretV1SnapshotByFolderId" | "deleteSnapshotsAboveLimit">;
|
snapshotDAL: Pick<TSnapshotDALFactory, "findNSecretV1SnapshotByFolderId" | "deleteSnapshotsAboveLimit">;
|
||||||
snapshotSecretV2BridgeDAL: Pick<TSnapshotSecretV2DALFactory, "insertMany">;
|
snapshotSecretV2BridgeDAL: Pick<TSnapshotSecretV2DALFactory, "insertMany" | "batchInsert">;
|
||||||
|
keyStore: Pick<TKeyStoreFactory, "acquireLock" | "setItemWithExpiry" | "getItem">;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TGetSecrets = {
|
export type TGetSecrets = {
|
||||||
@@ -122,7 +124,8 @@ export const secretQueueFactory = ({
|
|||||||
secretRotationDAL,
|
secretRotationDAL,
|
||||||
snapshotDAL,
|
snapshotDAL,
|
||||||
snapshotSecretV2BridgeDAL,
|
snapshotSecretV2BridgeDAL,
|
||||||
secretApprovalRequestDAL
|
secretApprovalRequestDAL,
|
||||||
|
keyStore
|
||||||
}: TSecretQueueFactoryDep) => {
|
}: TSecretQueueFactoryDep) => {
|
||||||
const removeSecretReminder = async (dto: TRemoveSecretReminderDTO) => {
|
const removeSecretReminder = async (dto: TRemoveSecretReminderDTO) => {
|
||||||
const appCfg = getConfig();
|
const appCfg = getConfig();
|
||||||
@@ -576,7 +579,6 @@ export const secretQueueFactory = ({
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const { shouldUseSecretV2Bridge, botKey } = await projectBotService.getBotKey(projectId);
|
const { shouldUseSecretV2Bridge, botKey } = await projectBotService.getBotKey(projectId);
|
||||||
const { decryptor: secretManagerDecryptor } = await kmsService.createCipherPairWithDataKey({
|
const { decryptor: secretManagerDecryptor } = await kmsService.createCipherPairWithDataKey({
|
||||||
type: KmsDataKey.SecretManager,
|
type: KmsDataKey.SecretManager,
|
||||||
@@ -641,108 +643,157 @@ export const secretQueueFactory = ({
|
|||||||
`getIntegrationSecrets: secret integration sync started [jobId=${job.id}] [jobId=${job.id}] [projectId=${job.data.projectId}] [environment=${job.data.environment}] [secretPath=${job.data.secretPath}] [depth=${job.data.depth}]`
|
`getIntegrationSecrets: secret integration sync started [jobId=${job.id}] [jobId=${job.id}] [projectId=${job.data.projectId}] [environment=${job.data.environment}] [secretPath=${job.data.secretPath}] [depth=${job.data.depth}]`
|
||||||
);
|
);
|
||||||
|
|
||||||
const secrets = shouldUseSecretV2Bridge
|
const lock = await keyStore.acquireLock(
|
||||||
? await getIntegrationSecretsV2({
|
[KeyStorePrefixes.SyncSecretIntegrationLock(projectId, environment, secretPath)],
|
||||||
environment,
|
10000,
|
||||||
projectId,
|
{
|
||||||
folderId: folder.id,
|
retryCount: 3,
|
||||||
depth: 1,
|
retryDelay: 2000
|
||||||
decryptor: (value) => (value ? secretManagerDecryptor({ cipherTextBlob: value }).toString() : "")
|
}
|
||||||
})
|
);
|
||||||
: await getIntegrationSecrets({
|
const lockAcquiredTime = new Date();
|
||||||
environment,
|
|
||||||
projectId,
|
|
||||||
folderId: folder.id,
|
|
||||||
key: botKey as string,
|
|
||||||
depth: 1
|
|
||||||
});
|
|
||||||
|
|
||||||
for (const integration of toBeSyncedIntegrations) {
|
const lastRunSyncIntegrationTimestamp = await keyStore.getItem(
|
||||||
const integrationAuth = {
|
KeyStorePrefixes.SyncSecretIntegrationLastRunTimestamp(projectId, environment, secretPath)
|
||||||
...integration.integrationAuth,
|
);
|
||||||
createdAt: new Date(),
|
|
||||||
updatedAt: new Date(),
|
|
||||||
projectId: integration.projectId
|
|
||||||
};
|
|
||||||
|
|
||||||
const { accessToken, accessId } = await integrationAuthService.getIntegrationAccessToken(
|
// check whether the integration should wait or not
|
||||||
integrationAuth,
|
if (lastRunSyncIntegrationTimestamp) {
|
||||||
shouldUseSecretV2Bridge,
|
const INTEGRATION_INTERVAL = 2000;
|
||||||
botKey
|
const isStaleSyncIntegration = new Date(job.timestamp) < new Date(lastRunSyncIntegrationTimestamp);
|
||||||
|
if (isStaleSyncIntegration) {
|
||||||
|
logger.info(
|
||||||
|
`getIntegrationSecrets: secret integration sync stale [jobId=${job.id}] [jobId=${job.id}] [projectId=${job.data.projectId}] [environment=${job.data.environment}] [secretPath=${job.data.secretPath}] [depth=${job.data.depth}]`
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const timeDifferenceWithLastIntegration = getTimeDifferenceInSeconds(
|
||||||
|
lockAcquiredTime.toISOString(),
|
||||||
|
lastRunSyncIntegrationTimestamp
|
||||||
);
|
);
|
||||||
let awsAssumeRoleArn = null;
|
if (timeDifferenceWithLastIntegration < INTEGRATION_INTERVAL && timeDifferenceWithLastIntegration > 0)
|
||||||
if (shouldUseSecretV2Bridge) {
|
await new Promise((resolve) => {
|
||||||
if (integrationAuth.encryptedAwsAssumeIamRoleArn) {
|
setTimeout(resolve, 2000 - timeDifferenceWithLastIntegration * 1000);
|
||||||
awsAssumeRoleArn = secretManagerDecryptor({
|
|
||||||
cipherTextBlob: Buffer.from(integrationAuth.encryptedAwsAssumeIamRoleArn)
|
|
||||||
}).toString();
|
|
||||||
}
|
|
||||||
} else if (
|
|
||||||
integrationAuth.awsAssumeIamRoleArnTag &&
|
|
||||||
integrationAuth.awsAssumeIamRoleArnIV &&
|
|
||||||
integrationAuth.awsAssumeIamRoleArnCipherText
|
|
||||||
) {
|
|
||||||
awsAssumeRoleArn = decryptSymmetric128BitHexKeyUTF8({
|
|
||||||
ciphertext: integrationAuth.awsAssumeIamRoleArnCipherText,
|
|
||||||
iv: integrationAuth.awsAssumeIamRoleArnIV,
|
|
||||||
tag: integrationAuth.awsAssumeIamRoleArnTag,
|
|
||||||
key: botKey as string
|
|
||||||
});
|
});
|
||||||
}
|
|
||||||
|
|
||||||
const suffixedSecrets: typeof secrets = {};
|
|
||||||
const metadata = integration.metadata as Record<string, string>;
|
|
||||||
if (metadata) {
|
|
||||||
Object.keys(secrets).forEach((key) => {
|
|
||||||
const prefix = metadata?.secretPrefix || "";
|
|
||||||
const suffix = metadata?.secretSuffix || "";
|
|
||||||
const newKey = prefix + key + suffix;
|
|
||||||
suffixedSecrets[newKey] = secrets[key];
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
// akhilmhdh: this needs to changed later to be more easier to use
|
|
||||||
// at present this is not at all extendable like to add a new parameter for just one integration need to modify multiple places
|
|
||||||
const response = await syncIntegrationSecrets({
|
|
||||||
createManySecretsRawFn,
|
|
||||||
updateManySecretsRawFn,
|
|
||||||
integrationDAL,
|
|
||||||
integration,
|
|
||||||
integrationAuth,
|
|
||||||
secrets: Object.keys(suffixedSecrets).length !== 0 ? suffixedSecrets : secrets,
|
|
||||||
accessId: accessId as string,
|
|
||||||
awsAssumeRoleArn,
|
|
||||||
accessToken,
|
|
||||||
projectId,
|
|
||||||
appendices: {
|
|
||||||
prefix: metadata?.secretPrefix || "",
|
|
||||||
suffix: metadata?.secretSuffix || ""
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
await integrationDAL.updateById(integration.id, {
|
|
||||||
lastSyncJobId: job.id,
|
|
||||||
lastUsed: new Date(),
|
|
||||||
syncMessage: response?.syncMessage ?? "",
|
|
||||||
isSynced: response?.isSynced ?? true
|
|
||||||
});
|
|
||||||
} catch (err) {
|
|
||||||
logger.info("Secret integration sync error: %o", err);
|
|
||||||
|
|
||||||
const message =
|
|
||||||
(err instanceof AxiosError ? JSON.stringify(err?.response?.data) : (err as Error)?.message) ||
|
|
||||||
"Unknown error occurred.";
|
|
||||||
|
|
||||||
await integrationDAL.updateById(integration.id, {
|
|
||||||
lastSyncJobId: job.id,
|
|
||||||
lastUsed: new Date(),
|
|
||||||
syncMessage: message,
|
|
||||||
isSynced: false
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// akhilmhdh: this try catch is for lock release
|
||||||
|
try {
|
||||||
|
const secrets = shouldUseSecretV2Bridge
|
||||||
|
? await getIntegrationSecretsV2({
|
||||||
|
environment,
|
||||||
|
projectId,
|
||||||
|
folderId: folder.id,
|
||||||
|
depth: 1,
|
||||||
|
decryptor: (value) => (value ? secretManagerDecryptor({ cipherTextBlob: value }).toString() : "")
|
||||||
|
})
|
||||||
|
: await getIntegrationSecrets({
|
||||||
|
environment,
|
||||||
|
projectId,
|
||||||
|
folderId: folder.id,
|
||||||
|
key: botKey as string,
|
||||||
|
depth: 1
|
||||||
|
});
|
||||||
|
|
||||||
|
for (const integration of toBeSyncedIntegrations) {
|
||||||
|
const integrationAuth = {
|
||||||
|
...integration.integrationAuth,
|
||||||
|
createdAt: new Date(),
|
||||||
|
updatedAt: new Date(),
|
||||||
|
projectId: integration.projectId
|
||||||
|
};
|
||||||
|
|
||||||
|
const { accessToken, accessId } = await integrationAuthService.getIntegrationAccessToken(
|
||||||
|
integrationAuth,
|
||||||
|
shouldUseSecretV2Bridge,
|
||||||
|
botKey
|
||||||
|
);
|
||||||
|
let awsAssumeRoleArn = null;
|
||||||
|
if (shouldUseSecretV2Bridge) {
|
||||||
|
if (integrationAuth.encryptedAwsAssumeIamRoleArn) {
|
||||||
|
awsAssumeRoleArn = secretManagerDecryptor({
|
||||||
|
cipherTextBlob: Buffer.from(integrationAuth.encryptedAwsAssumeIamRoleArn)
|
||||||
|
}).toString();
|
||||||
|
}
|
||||||
|
} else if (
|
||||||
|
integrationAuth.awsAssumeIamRoleArnTag &&
|
||||||
|
integrationAuth.awsAssumeIamRoleArnIV &&
|
||||||
|
integrationAuth.awsAssumeIamRoleArnCipherText
|
||||||
|
) {
|
||||||
|
awsAssumeRoleArn = decryptSymmetric128BitHexKeyUTF8({
|
||||||
|
ciphertext: integrationAuth.awsAssumeIamRoleArnCipherText,
|
||||||
|
iv: integrationAuth.awsAssumeIamRoleArnIV,
|
||||||
|
tag: integrationAuth.awsAssumeIamRoleArnTag,
|
||||||
|
key: botKey as string
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const suffixedSecrets: typeof secrets = {};
|
||||||
|
const metadata = integration.metadata as Record<string, string>;
|
||||||
|
if (metadata) {
|
||||||
|
Object.keys(secrets).forEach((key) => {
|
||||||
|
const prefix = metadata?.secretPrefix || "";
|
||||||
|
const suffix = metadata?.secretSuffix || "";
|
||||||
|
const newKey = prefix + key + suffix;
|
||||||
|
suffixedSecrets[newKey] = secrets[key];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// akhilmhdh: this try catch is for catching integration error and saving it in db
|
||||||
|
try {
|
||||||
|
// akhilmhdh: this needs to changed later to be more easier to use
|
||||||
|
// at present this is not at all extendable like to add a new parameter for just one integration need to modify multiple places
|
||||||
|
const response = await syncIntegrationSecrets({
|
||||||
|
createManySecretsRawFn,
|
||||||
|
updateManySecretsRawFn,
|
||||||
|
integrationDAL,
|
||||||
|
integration,
|
||||||
|
integrationAuth,
|
||||||
|
secrets: Object.keys(suffixedSecrets).length !== 0 ? suffixedSecrets : secrets,
|
||||||
|
accessId: accessId as string,
|
||||||
|
awsAssumeRoleArn,
|
||||||
|
accessToken,
|
||||||
|
projectId,
|
||||||
|
appendices: {
|
||||||
|
prefix: metadata?.secretPrefix || "",
|
||||||
|
suffix: metadata?.secretSuffix || ""
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
await integrationDAL.updateById(integration.id, {
|
||||||
|
lastSyncJobId: job.id,
|
||||||
|
lastUsed: new Date(),
|
||||||
|
syncMessage: response?.syncMessage ?? "",
|
||||||
|
isSynced: response?.isSynced ?? true
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
logger.error(
|
||||||
|
err,
|
||||||
|
`Secret integration sync error [projectId=${job.data.projectId}] [environment=${job.data.environment}] [secretPath=${job.data.secretPath}]`
|
||||||
|
);
|
||||||
|
|
||||||
|
const message =
|
||||||
|
(err instanceof AxiosError ? JSON.stringify(err?.response?.data) : (err as Error)?.message) ||
|
||||||
|
"Unknown error occurred.";
|
||||||
|
|
||||||
|
await integrationDAL.updateById(integration.id, {
|
||||||
|
lastSyncJobId: job.id,
|
||||||
|
lastUsed: new Date(),
|
||||||
|
syncMessage: message,
|
||||||
|
isSynced: false
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
await lock.release();
|
||||||
|
}
|
||||||
|
|
||||||
|
await keyStore.setItemWithExpiry(
|
||||||
|
KeyStorePrefixes.SyncSecretIntegrationLastRunTimestamp(projectId, environment, secretPath),
|
||||||
|
KeyStoreTtls.SetSyncSecretIntegrationLastRunTimestampInSeconds,
|
||||||
|
lockAcquiredTime.toISOString()
|
||||||
|
);
|
||||||
logger.info("Secret integration sync ended: %s", job.id);
|
logger.info("Secret integration sync ended: %s", job.id);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -828,7 +879,7 @@ export const secretQueueFactory = ({
|
|||||||
secretId: string;
|
secretId: string;
|
||||||
references: { environment: string; secretPath: string; secretKey: string }[];
|
references: { environment: string; secretPath: string; secretKey: string }[];
|
||||||
}[] = [];
|
}[] = [];
|
||||||
await secretV2BridgeDAL.insertMany(
|
await secretV2BridgeDAL.batchInsert(
|
||||||
projectV1Secrets.map((el) => {
|
projectV1Secrets.map((el) => {
|
||||||
const key = decryptSymmetric128BitHexKeyUTF8({
|
const key = decryptSymmetric128BitHexKeyUTF8({
|
||||||
ciphertext: el.secretKeyCiphertext,
|
ciphertext: el.secretKeyCiphertext,
|
||||||
@@ -1004,14 +1055,14 @@ export const secretQueueFactory = ({
|
|||||||
|
|
||||||
const projectV3SecretVersions = Object.values(projectV3SecretVersionsGroupById);
|
const projectV3SecretVersions = Object.values(projectV3SecretVersionsGroupById);
|
||||||
if (projectV3SecretVersions.length) {
|
if (projectV3SecretVersions.length) {
|
||||||
await secretVersionV2BridgeDAL.insertMany(projectV3SecretVersions, tx);
|
await secretVersionV2BridgeDAL.batchInsert(projectV3SecretVersions, tx);
|
||||||
}
|
}
|
||||||
if (projectV3SecretVersionTags.length) {
|
if (projectV3SecretVersionTags.length) {
|
||||||
await secretVersionTagV2BridgeDAL.insertMany(projectV3SecretVersionTags, tx);
|
await secretVersionTagV2BridgeDAL.batchInsert(projectV3SecretVersionTags, tx);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (projectV3SnapshotSecrets.length) {
|
if (projectV3SnapshotSecrets.length) {
|
||||||
await snapshotSecretV2BridgeDAL.insertMany(projectV3SnapshotSecrets, tx);
|
await snapshotSecretV2BridgeDAL.batchInsert(projectV3SnapshotSecrets, tx);
|
||||||
}
|
}
|
||||||
await snapshotDAL.deleteSnapshotsAboveLimit(folderId, SNAPSHOT_BATCH_SIZE, tx);
|
await snapshotDAL.deleteSnapshotsAboveLimit(folderId, SNAPSHOT_BATCH_SIZE, tx);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -964,7 +964,8 @@ export const secretServiceFactory = ({
|
|||||||
environment,
|
environment,
|
||||||
includeImports,
|
includeImports,
|
||||||
expandSecretReferences,
|
expandSecretReferences,
|
||||||
recursive
|
recursive,
|
||||||
|
tagSlugs = []
|
||||||
}: TGetSecretsRawDTO) => {
|
}: TGetSecretsRawDTO) => {
|
||||||
const { botKey, shouldUseSecretV2Bridge } = await projectBotService.getBotKey(projectId);
|
const { botKey, shouldUseSecretV2Bridge } = await projectBotService.getBotKey(projectId);
|
||||||
if (shouldUseSecretV2Bridge) {
|
if (shouldUseSecretV2Bridge) {
|
||||||
@@ -978,7 +979,8 @@ export const secretServiceFactory = ({
|
|||||||
path,
|
path,
|
||||||
recursive,
|
recursive,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
includeImports
|
includeImports,
|
||||||
|
tagSlugs
|
||||||
});
|
});
|
||||||
return { secrets, imports };
|
return { secrets, imports };
|
||||||
}
|
}
|
||||||
@@ -998,6 +1000,9 @@ export const secretServiceFactory = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const decryptedSecrets = secrets.map((el) => decryptSecretRaw(el, botKey));
|
const decryptedSecrets = secrets.map((el) => decryptSecretRaw(el, botKey));
|
||||||
|
const filteredSecrets = tagSlugs.length
|
||||||
|
? decryptedSecrets.filter((secret) => Boolean(secret.tags?.find((el) => tagSlugs.includes(el.slug))))
|
||||||
|
: decryptedSecrets;
|
||||||
const processedImports = (imports || [])?.map(({ secrets: importedSecrets, ...el }) => {
|
const processedImports = (imports || [])?.map(({ secrets: importedSecrets, ...el }) => {
|
||||||
const decryptedImportSecrets = importedSecrets.map((sec) =>
|
const decryptedImportSecrets = importedSecrets.map((sec) =>
|
||||||
decryptSecretRaw(
|
decryptSecretRaw(
|
||||||
@@ -1106,14 +1111,14 @@ export const secretServiceFactory = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
// expand secrets
|
// expand secrets
|
||||||
await batchSecretsExpand(decryptedSecrets);
|
await batchSecretsExpand(filteredSecrets);
|
||||||
|
|
||||||
// expand imports by batch
|
// expand imports by batch
|
||||||
await Promise.all(processedImports.map((processedImport) => batchSecretsExpand(processedImport.secrets)));
|
await Promise.all(processedImports.map((processedImport) => batchSecretsExpand(processedImport.secrets)));
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
secrets: decryptedSecrets,
|
secrets: filteredSecrets,
|
||||||
imports: processedImports
|
imports: processedImports
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
@@ -1149,6 +1154,7 @@ export const secretServiceFactory = ({
|
|||||||
type,
|
type,
|
||||||
secretName
|
secretName
|
||||||
});
|
});
|
||||||
|
|
||||||
return secret;
|
return secret;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2081,7 +2087,7 @@ export const secretServiceFactory = ({
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
...updatedSecret[0],
|
...updatedSecret[0],
|
||||||
tags: [...existingSecretTags, ...tags].map((t) => ({ id: t.id, slug: t.slug, name: t.name, color: t.color }))
|
tags: [...existingSecretTags, ...tags].map((t) => ({ id: t.id, slug: t.slug, name: t.slug, color: t.color }))
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -149,6 +149,7 @@ export type TGetSecretsRawDTO = {
|
|||||||
environment: string;
|
environment: string;
|
||||||
includeImports?: boolean;
|
includeImports?: boolean;
|
||||||
recursive?: boolean;
|
recursive?: boolean;
|
||||||
|
tagSlugs?: string[];
|
||||||
} & TProjectPermission;
|
} & TProjectPermission;
|
||||||
|
|
||||||
export type TGetASecretRawDTO = {
|
export type TGetASecretRawDTO = {
|
||||||
|
|||||||
@@ -404,6 +404,10 @@ func CallGetRawSecretsV3(httpClient *resty.Client, request GetRawSecretsV3Reques
|
|||||||
SetQueryParam("environment", request.Environment).
|
SetQueryParam("environment", request.Environment).
|
||||||
SetQueryParam("secretPath", request.SecretPath)
|
SetQueryParam("secretPath", request.SecretPath)
|
||||||
|
|
||||||
|
if request.TagSlugs != "" {
|
||||||
|
req.SetQueryParam("tagSlugs", request.TagSlugs)
|
||||||
|
}
|
||||||
|
|
||||||
if request.IncludeImport {
|
if request.IncludeImport {
|
||||||
req.SetQueryParam("include_imports", "true")
|
req.SetQueryParam("include_imports", "true")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -574,6 +574,7 @@ type GetRawSecretsV3Request struct {
|
|||||||
SecretPath string `json:"secretPath"`
|
SecretPath string `json:"secretPath"`
|
||||||
IncludeImport bool `json:"include_imports"`
|
IncludeImport bool `json:"include_imports"`
|
||||||
Recursive bool `json:"recursive"`
|
Recursive bool `json:"recursive"`
|
||||||
|
TagSlugs string `json:"tagSlugs,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type GetRawSecretsV3Response struct {
|
type GetRawSecretsV3Response struct {
|
||||||
|
|||||||
@@ -312,7 +312,7 @@ func ParseAgentConfig(configFile []byte) (*Config, error) {
|
|||||||
|
|
||||||
func secretTemplateFunction(accessToken string, existingEtag string, currentEtag *string) func(string, string, string) ([]models.SingleEnvironmentVariable, error) {
|
func secretTemplateFunction(accessToken string, existingEtag string, currentEtag *string) func(string, string, string) ([]models.SingleEnvironmentVariable, error) {
|
||||||
return func(projectID, envSlug, secretPath string) ([]models.SingleEnvironmentVariable, error) {
|
return func(projectID, envSlug, secretPath string) ([]models.SingleEnvironmentVariable, error) {
|
||||||
res, err := util.GetPlainTextSecretsV3(accessToken, projectID, envSlug, secretPath, false, false)
|
res, err := util.GetPlainTextSecretsV3(accessToken, projectID, envSlug, secretPath, false, false, "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import (
|
|||||||
"github.com/Infisical/infisical-merge/packages/util"
|
"github.com/Infisical/infisical-merge/packages/util"
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
"gopkg.in/yaml.v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -188,7 +189,7 @@ func formatEnvs(envs []models.SingleEnvironmentVariable, format string) (string,
|
|||||||
case FormatCSV:
|
case FormatCSV:
|
||||||
return formatAsCSV(envs), nil
|
return formatAsCSV(envs), nil
|
||||||
case FormatYaml:
|
case FormatYaml:
|
||||||
return formatAsYaml(envs), nil
|
return formatAsYaml(envs)
|
||||||
default:
|
default:
|
||||||
return "", fmt.Errorf("invalid format type: %s. Available format types are [%s]", format, []string{FormatDotenv, FormatJson, FormatCSV, FormatYaml, FormatDotEnvExport})
|
return "", fmt.Errorf("invalid format type: %s. Available format types are [%s]", format, []string{FormatDotenv, FormatJson, FormatCSV, FormatYaml, FormatDotEnvExport})
|
||||||
}
|
}
|
||||||
@@ -224,12 +225,18 @@ func formatAsDotEnvExport(envs []models.SingleEnvironmentVariable) string {
|
|||||||
return dotenv
|
return dotenv
|
||||||
}
|
}
|
||||||
|
|
||||||
func formatAsYaml(envs []models.SingleEnvironmentVariable) string {
|
func formatAsYaml(envs []models.SingleEnvironmentVariable) (string, error) {
|
||||||
var dotenv string
|
m := make(map[string]string)
|
||||||
for _, env := range envs {
|
for _, env := range envs {
|
||||||
dotenv += fmt.Sprintf("%s: %s\n", env.Key, env.Value)
|
m[env.Key] = env.Value
|
||||||
}
|
}
|
||||||
return dotenv
|
|
||||||
|
yamlBytes, err := yaml.Marshal(m)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("failed to format environment variables as YAML: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return string(yamlBytes), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Format environment variables as a JSON file
|
// Format environment variables as a JSON file
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user