mirror of
https://github.com/Infisical/infisical.git
synced 2025-08-05 07:30:33 +00:00
Compare commits
183 Commits
misc/updat
...
daniel/age
Author | SHA1 | Date | |
---|---|---|---|
|
074446df1f | ||
|
0b6bc4c1f0 | ||
|
abbe7bbd0c | ||
|
565340dc50 | ||
|
36c428f152 | ||
|
f97826ea82 | ||
|
0f5cbf055c | ||
|
b960ee61d7 | ||
|
0b98a214a7 | ||
|
599c2226e4 | ||
|
27486e7600 | ||
|
979e9efbcb | ||
|
1097ec64b2 | ||
|
93fe9929b7 | ||
|
aca654a993 | ||
|
b5cf237a4a | ||
|
6efb630200 | ||
|
151ede6cbf | ||
|
931ee1e8da | ||
|
0401793d38 | ||
|
0613c12508 | ||
|
60d3ffac5d | ||
|
5e192539a1 | ||
|
021a8ddace | ||
|
f92aba14cd | ||
|
fdeefcdfcf | ||
|
645f70f770 | ||
|
923feb81f3 | ||
|
16c51af340 | ||
|
9fd37ca456 | ||
|
92bebf7d84 | ||
|
df053bbae9 | ||
|
42319f01a7 | ||
|
0ea9f9b60d | ||
|
16eefe5bac | ||
|
b984111a73 | ||
|
677ff62b5c | ||
|
8cc2e08f24 | ||
|
d90178f49a | ||
|
ad50cff184 | ||
|
8e43d2a994 | ||
|
7074fdbac3 | ||
|
ef70de1e0b | ||
|
7e9ee7b5e3 | ||
|
517c613d05 | ||
|
ae8cf06ec6 | ||
|
818778ddc5 | ||
|
2e12d9a13c | ||
|
e678c9d1cf | ||
|
da0b07ce2a | ||
|
3306a9ca69 | ||
|
e9af34a6ba | ||
|
3de8ed169f | ||
|
d1eb350bdd | ||
|
0c1ccf7c2e | ||
|
d268f52a1c | ||
|
c519cee5d1 | ||
|
b55a39dd24 | ||
|
7b880f85cc | ||
|
c7dc595e1a | ||
|
6e494f198b | ||
|
e1f3eaf1a0 | ||
|
be26dc9872 | ||
|
aaeb6e73fe | ||
|
1e11702c58 | ||
|
3b81cdb16e | ||
|
6584166815 | ||
|
827cb35194 | ||
|
89a6a0ba13 | ||
|
3b9a50d65d | ||
|
beb7200233 | ||
|
18e3d132a2 | ||
|
3f74d3a80d | ||
|
4a44dc6119 | ||
|
dd4bc4bc73 | ||
|
6188de43e4 | ||
|
36310387e0 | ||
|
43f3960225 | ||
|
2f0a442866 | ||
|
7e05bc86a9 | ||
|
b0c4fddf86 | ||
|
f5578d39a6 | ||
|
cd028ae133 | ||
|
63c71fabcd | ||
|
e90166f1f0 | ||
|
5a3fbc0401 | ||
|
7c52e000cd | ||
|
cccd4ba9e5 | ||
|
63f0f8e299 | ||
|
c8a3837432 | ||
|
2dd407b136 | ||
|
4e1a5565d8 | ||
|
bae62421ae | ||
|
d397002704 | ||
|
f5b1f671e3 | ||
|
0597c5f0c0 | ||
|
eb3afc8034 | ||
|
b67457fe93 | ||
|
75abdbe938 | ||
|
9b6a315825 | ||
|
13b2f65b7e | ||
|
6cf1e046b0 | ||
|
f6e1441dc0 | ||
|
7ed96164e5 | ||
|
9eeb72ac80 | ||
|
f6e566a028 | ||
|
a34c74e958 | ||
|
eef7a875a1 | ||
|
09938a911b | ||
|
af08c41008 | ||
|
443c8854ea | ||
|
f7a25e7601 | ||
|
4c6e5c9c4c | ||
|
98a4e6c96d | ||
|
c93ce06409 | ||
|
672e4baec4 | ||
|
8adf4787b9 | ||
|
a12522db55 | ||
|
49ab487dc2 | ||
|
daf0731580 | ||
|
b5ef2a6837 | ||
|
52858dad79 | ||
|
1d7a6ea50e | ||
|
c031233247 | ||
|
70fff1f2da | ||
|
3f8eaa0679 | ||
|
50d0035d7b | ||
|
9743ad02d5 | ||
|
50f5248e3e | ||
|
8d7b573988 | ||
|
26d0ab1dc2 | ||
|
4acdbd24e9 | ||
|
c3c907788a | ||
|
bf833a57cd | ||
|
e8519f6612 | ||
|
0b4675e7b5 | ||
|
091e521180 | ||
|
d5dbc7d7e0 | ||
|
0af9415aa6 | ||
|
fb2b64cb19 | ||
|
2793ac22aa | ||
|
31fad03af8 | ||
|
ce612877b8 | ||
|
4ad8b468d5 | ||
|
5742fc648b | ||
|
aa68a3ef58 | ||
|
578a0d7d93 | ||
|
cd71db416d | ||
|
9d682ca874 | ||
|
9054db80ad | ||
|
5bb8756c67 | ||
|
8b7cb4c4eb | ||
|
a6ee6fc4ea | ||
|
b21c17572d | ||
|
44c7be54cf | ||
|
45c08b3f09 | ||
|
57a29577fe | ||
|
2700a96df4 | ||
|
7457ef3b66 | ||
|
806df70dd7 | ||
|
8eda358c17 | ||
|
b34aabe72b | ||
|
dfaed3c513 | ||
|
5b7627585f | ||
|
800ea5ce78 | ||
|
531607dcb7 | ||
|
182de009b2 | ||
|
f1651ce171 | ||
|
e1f563dbd4 | ||
|
107cca0b62 | ||
|
72abc08f04 | ||
|
d6b31cde44 | ||
|
2c94f9ec3c | ||
|
42ad63b58d | ||
|
f2d5112585 | ||
|
9c7b25de49 | ||
|
36954a9df9 | ||
|
581840a701 | ||
|
326742c2d5 | ||
|
c891b8f5d3 | ||
|
a32bb95703 | ||
|
0410c83cef | ||
|
cf4f2ea6b1 |
@@ -28,3 +28,15 @@ frontend/src/pages/secret-manager/OverviewPage/components/SecretOverviewTableRow
|
|||||||
docs/cli/commands/user.mdx:generic-api-key:51
|
docs/cli/commands/user.mdx:generic-api-key:51
|
||||||
frontend/src/pages/secret-manager/OverviewPage/components/SecretOverviewTableRow/SecretOverviewTableRow.tsx:generic-api-key:76
|
frontend/src/pages/secret-manager/OverviewPage/components/SecretOverviewTableRow/SecretOverviewTableRow.tsx:generic-api-key:76
|
||||||
docs/integrations/app-connections/hashicorp-vault.mdx:generic-api-key:188
|
docs/integrations/app-connections/hashicorp-vault.mdx:generic-api-key:188
|
||||||
|
cli/detect/config/gitleaks.toml:gcp-api-key:567
|
||||||
|
cli/detect/config/gitleaks.toml:gcp-api-key:569
|
||||||
|
cli/detect/config/gitleaks.toml:gcp-api-key:570
|
||||||
|
cli/detect/config/gitleaks.toml:gcp-api-key:572
|
||||||
|
cli/detect/config/gitleaks.toml:gcp-api-key:574
|
||||||
|
cli/detect/config/gitleaks.toml:gcp-api-key:575
|
||||||
|
cli/detect/config/gitleaks.toml:gcp-api-key:576
|
||||||
|
cli/detect/config/gitleaks.toml:gcp-api-key:577
|
||||||
|
cli/detect/config/gitleaks.toml:gcp-api-key:578
|
||||||
|
cli/detect/config/gitleaks.toml:gcp-api-key:579
|
||||||
|
cli/detect/config/gitleaks.toml:gcp-api-key:581
|
||||||
|
cli/detect/config/gitleaks.toml:gcp-api-key:582
|
||||||
|
1941
backend/package-lock.json
generated
1941
backend/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -38,8 +38,8 @@
|
|||||||
"build:frontend": "npm run build --prefix ../frontend",
|
"build:frontend": "npm run build --prefix ../frontend",
|
||||||
"start": "node --enable-source-maps dist/main.mjs",
|
"start": "node --enable-source-maps dist/main.mjs",
|
||||||
"type:check": "tsc --noEmit",
|
"type:check": "tsc --noEmit",
|
||||||
"lint:fix": "eslint --fix --ext js,ts ./src",
|
"lint:fix": "node --max-old-space-size=8192 ./node_modules/.bin/eslint --fix --ext js,ts ./src",
|
||||||
"lint": "eslint 'src/**/*.ts'",
|
"lint": "node --max-old-space-size=8192 ./node_modules/.bin/eslint 'src/**/*.ts'",
|
||||||
"test:unit": "vitest run -c vitest.unit.config.ts",
|
"test:unit": "vitest run -c vitest.unit.config.ts",
|
||||||
"test:e2e": "vitest run -c vitest.e2e.config.ts --bail=1",
|
"test:e2e": "vitest run -c vitest.e2e.config.ts --bail=1",
|
||||||
"test:e2e-watch": "vitest -c vitest.e2e.config.ts --bail=1",
|
"test:e2e-watch": "vitest -c vitest.e2e.config.ts --bail=1",
|
||||||
@@ -209,6 +209,7 @@
|
|||||||
"mysql2": "^3.9.8",
|
"mysql2": "^3.9.8",
|
||||||
"nanoid": "^3.3.8",
|
"nanoid": "^3.3.8",
|
||||||
"nodemailer": "^6.9.9",
|
"nodemailer": "^6.9.9",
|
||||||
|
"oci-sdk": "^2.108.0",
|
||||||
"odbc": "^2.4.9",
|
"odbc": "^2.4.9",
|
||||||
"openid-client": "^5.6.5",
|
"openid-client": "^5.6.5",
|
||||||
"ora": "^7.0.1",
|
"ora": "^7.0.1",
|
||||||
|
4
backend/src/@types/fastify.d.ts
vendored
4
backend/src/@types/fastify.d.ts
vendored
@@ -68,6 +68,7 @@ import { TIdentityJwtAuthServiceFactory } from "@app/services/identity-jwt-auth/
|
|||||||
import { TIdentityKubernetesAuthServiceFactory } from "@app/services/identity-kubernetes-auth/identity-kubernetes-auth-service";
|
import { TIdentityKubernetesAuthServiceFactory } from "@app/services/identity-kubernetes-auth/identity-kubernetes-auth-service";
|
||||||
import { TIdentityLdapAuthServiceFactory } from "@app/services/identity-ldap-auth/identity-ldap-auth-service";
|
import { TIdentityLdapAuthServiceFactory } from "@app/services/identity-ldap-auth/identity-ldap-auth-service";
|
||||||
import { TAllowedFields } from "@app/services/identity-ldap-auth/identity-ldap-auth-types";
|
import { TAllowedFields } from "@app/services/identity-ldap-auth/identity-ldap-auth-types";
|
||||||
|
import { TIdentityOciAuthServiceFactory } from "@app/services/identity-oci-auth/identity-oci-auth-service";
|
||||||
import { TIdentityOidcAuthServiceFactory } from "@app/services/identity-oidc-auth/identity-oidc-auth-service";
|
import { TIdentityOidcAuthServiceFactory } from "@app/services/identity-oidc-auth/identity-oidc-auth-service";
|
||||||
import { TIdentityProjectServiceFactory } from "@app/services/identity-project/identity-project-service";
|
import { TIdentityProjectServiceFactory } from "@app/services/identity-project/identity-project-service";
|
||||||
import { TIdentityTokenAuthServiceFactory } from "@app/services/identity-token-auth/identity-token-auth-service";
|
import { TIdentityTokenAuthServiceFactory } from "@app/services/identity-token-auth/identity-token-auth-service";
|
||||||
@@ -80,6 +81,7 @@ import { TOrgServiceFactory } from "@app/services/org/org-service";
|
|||||||
import { TOrgAdminServiceFactory } from "@app/services/org-admin/org-admin-service";
|
import { TOrgAdminServiceFactory } from "@app/services/org-admin/org-admin-service";
|
||||||
import { TPkiAlertServiceFactory } from "@app/services/pki-alert/pki-alert-service";
|
import { TPkiAlertServiceFactory } from "@app/services/pki-alert/pki-alert-service";
|
||||||
import { TPkiCollectionServiceFactory } from "@app/services/pki-collection/pki-collection-service";
|
import { TPkiCollectionServiceFactory } from "@app/services/pki-collection/pki-collection-service";
|
||||||
|
import { TPkiSubscriberServiceFactory } from "@app/services/pki-subscriber/pki-subscriber-service";
|
||||||
import { TProjectServiceFactory } from "@app/services/project/project-service";
|
import { TProjectServiceFactory } from "@app/services/project/project-service";
|
||||||
import { TProjectBotServiceFactory } from "@app/services/project-bot/project-bot-service";
|
import { TProjectBotServiceFactory } from "@app/services/project-bot/project-bot-service";
|
||||||
import { TProjectEnvServiceFactory } from "@app/services/project-env/project-env-service";
|
import { TProjectEnvServiceFactory } from "@app/services/project-env/project-env-service";
|
||||||
@@ -208,6 +210,7 @@ declare module "fastify" {
|
|||||||
identityGcpAuth: TIdentityGcpAuthServiceFactory;
|
identityGcpAuth: TIdentityGcpAuthServiceFactory;
|
||||||
identityAwsAuth: TIdentityAwsAuthServiceFactory;
|
identityAwsAuth: TIdentityAwsAuthServiceFactory;
|
||||||
identityAzureAuth: TIdentityAzureAuthServiceFactory;
|
identityAzureAuth: TIdentityAzureAuthServiceFactory;
|
||||||
|
identityOciAuth: TIdentityOciAuthServiceFactory;
|
||||||
identityOidcAuth: TIdentityOidcAuthServiceFactory;
|
identityOidcAuth: TIdentityOidcAuthServiceFactory;
|
||||||
identityJwtAuth: TIdentityJwtAuthServiceFactory;
|
identityJwtAuth: TIdentityJwtAuthServiceFactory;
|
||||||
identityLdapAuth: TIdentityLdapAuthServiceFactory;
|
identityLdapAuth: TIdentityLdapAuthServiceFactory;
|
||||||
@@ -232,6 +235,7 @@ declare module "fastify" {
|
|||||||
certificateAuthorityCrl: TCertificateAuthorityCrlServiceFactory;
|
certificateAuthorityCrl: TCertificateAuthorityCrlServiceFactory;
|
||||||
certificateEst: TCertificateEstServiceFactory;
|
certificateEst: TCertificateEstServiceFactory;
|
||||||
pkiCollection: TPkiCollectionServiceFactory;
|
pkiCollection: TPkiCollectionServiceFactory;
|
||||||
|
pkiSubscriber: TPkiSubscriberServiceFactory;
|
||||||
secretScanning: TSecretScanningServiceFactory;
|
secretScanning: TSecretScanningServiceFactory;
|
||||||
license: TLicenseServiceFactory;
|
license: TLicenseServiceFactory;
|
||||||
trustedIp: TTrustedIpServiceFactory;
|
trustedIp: TTrustedIpServiceFactory;
|
||||||
|
16
backend/src/@types/knex.d.ts
vendored
16
backend/src/@types/knex.d.ts
vendored
@@ -119,6 +119,9 @@ import {
|
|||||||
TIdentityMetadata,
|
TIdentityMetadata,
|
||||||
TIdentityMetadataInsert,
|
TIdentityMetadataInsert,
|
||||||
TIdentityMetadataUpdate,
|
TIdentityMetadataUpdate,
|
||||||
|
TIdentityOciAuths,
|
||||||
|
TIdentityOciAuthsInsert,
|
||||||
|
TIdentityOciAuthsUpdate,
|
||||||
TIdentityOidcAuths,
|
TIdentityOidcAuths,
|
||||||
TIdentityOidcAuthsInsert,
|
TIdentityOidcAuthsInsert,
|
||||||
TIdentityOidcAuthsUpdate,
|
TIdentityOidcAuthsUpdate,
|
||||||
@@ -209,6 +212,9 @@ import {
|
|||||||
TPkiCollections,
|
TPkiCollections,
|
||||||
TPkiCollectionsInsert,
|
TPkiCollectionsInsert,
|
||||||
TPkiCollectionsUpdate,
|
TPkiCollectionsUpdate,
|
||||||
|
TPkiSubscribers,
|
||||||
|
TPkiSubscribersInsert,
|
||||||
|
TPkiSubscribersUpdate,
|
||||||
TProjectBots,
|
TProjectBots,
|
||||||
TProjectBotsInsert,
|
TProjectBotsInsert,
|
||||||
TProjectBotsUpdate,
|
TProjectBotsUpdate,
|
||||||
@@ -564,6 +570,11 @@ declare module "knex/types/tables" {
|
|||||||
TPkiCollectionItemsInsert,
|
TPkiCollectionItemsInsert,
|
||||||
TPkiCollectionItemsUpdate
|
TPkiCollectionItemsUpdate
|
||||||
>;
|
>;
|
||||||
|
[TableName.PkiSubscriber]: KnexOriginal.CompositeTableType<
|
||||||
|
TPkiSubscribers,
|
||||||
|
TPkiSubscribersInsert,
|
||||||
|
TPkiSubscribersUpdate
|
||||||
|
>;
|
||||||
[TableName.UserGroupMembership]: KnexOriginal.CompositeTableType<
|
[TableName.UserGroupMembership]: KnexOriginal.CompositeTableType<
|
||||||
TUserGroupMembership,
|
TUserGroupMembership,
|
||||||
TUserGroupMembershipInsert,
|
TUserGroupMembershipInsert,
|
||||||
@@ -730,6 +741,11 @@ declare module "knex/types/tables" {
|
|||||||
TIdentityAzureAuthsInsert,
|
TIdentityAzureAuthsInsert,
|
||||||
TIdentityAzureAuthsUpdate
|
TIdentityAzureAuthsUpdate
|
||||||
>;
|
>;
|
||||||
|
[TableName.IdentityOciAuth]: KnexOriginal.CompositeTableType<
|
||||||
|
TIdentityOciAuths,
|
||||||
|
TIdentityOciAuthsInsert,
|
||||||
|
TIdentityOciAuthsUpdate
|
||||||
|
>;
|
||||||
[TableName.IdentityOidcAuth]: KnexOriginal.CompositeTableType<
|
[TableName.IdentityOidcAuth]: KnexOriginal.CompositeTableType<
|
||||||
TIdentityOidcAuths,
|
TIdentityOidcAuths,
|
||||||
TIdentityOidcAuthsInsert,
|
TIdentityOidcAuthsInsert,
|
||||||
|
46
backend/src/db/migrations/20250508160957_pki-subscriber.ts
Normal file
46
backend/src/db/migrations/20250508160957_pki-subscriber.ts
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
import { Knex } from "knex";
|
||||||
|
|
||||||
|
import { TableName } from "../schemas";
|
||||||
|
import { createOnUpdateTrigger, dropOnUpdateTrigger } from "../utils";
|
||||||
|
|
||||||
|
export async function up(knex: Knex): Promise<void> {
|
||||||
|
if (!(await knex.schema.hasTable(TableName.PkiSubscriber))) {
|
||||||
|
await knex.schema.createTable(TableName.PkiSubscriber, (t) => {
|
||||||
|
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
|
||||||
|
t.timestamps(true, true, true);
|
||||||
|
t.string("projectId").notNullable();
|
||||||
|
t.foreign("projectId").references("id").inTable(TableName.Project).onDelete("CASCADE");
|
||||||
|
t.uuid("caId").nullable();
|
||||||
|
t.foreign("caId").references("id").inTable(TableName.CertificateAuthority).onDelete("SET NULL");
|
||||||
|
t.string("name").notNullable();
|
||||||
|
t.string("commonName").notNullable();
|
||||||
|
t.specificType("subjectAlternativeNames", "text[]").notNullable();
|
||||||
|
t.string("ttl").notNullable();
|
||||||
|
t.specificType("keyUsages", "text[]").notNullable();
|
||||||
|
t.specificType("extendedKeyUsages", "text[]").notNullable();
|
||||||
|
t.string("status").notNullable(); // active / disabled
|
||||||
|
t.unique(["projectId", "name"]);
|
||||||
|
});
|
||||||
|
await createOnUpdateTrigger(knex, TableName.PkiSubscriber);
|
||||||
|
}
|
||||||
|
|
||||||
|
const hasSubscriberCol = await knex.schema.hasColumn(TableName.Certificate, "pkiSubscriberId");
|
||||||
|
if (!hasSubscriberCol) {
|
||||||
|
await knex.schema.alterTable(TableName.Certificate, (t) => {
|
||||||
|
t.uuid("pkiSubscriberId").nullable();
|
||||||
|
t.foreign("pkiSubscriberId").references("id").inTable(TableName.PkiSubscriber).onDelete("SET NULL");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function down(knex: Knex): Promise<void> {
|
||||||
|
const hasSubscriberCol = await knex.schema.hasColumn(TableName.Certificate, "pkiSubscriberId");
|
||||||
|
if (hasSubscriberCol) {
|
||||||
|
await knex.schema.alterTable(TableName.Certificate, (t) => {
|
||||||
|
t.dropColumn("pkiSubscriberId");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
await knex.schema.dropTableIfExists(TableName.PkiSubscriber);
|
||||||
|
await dropOnUpdateTrigger(knex, TableName.PkiSubscriber);
|
||||||
|
}
|
@@ -0,0 +1,30 @@
|
|||||||
|
import { Knex } from "knex";
|
||||||
|
|
||||||
|
import { TableName } from "../schemas";
|
||||||
|
import { createOnUpdateTrigger, dropOnUpdateTrigger } from "../utils";
|
||||||
|
|
||||||
|
export async function up(knex: Knex): Promise<void> {
|
||||||
|
if (!(await knex.schema.hasTable(TableName.IdentityOciAuth))) {
|
||||||
|
await knex.schema.createTable(TableName.IdentityOciAuth, (t) => {
|
||||||
|
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
|
||||||
|
t.bigInteger("accessTokenTTL").defaultTo(7200).notNullable();
|
||||||
|
t.bigInteger("accessTokenMaxTTL").defaultTo(7200).notNullable();
|
||||||
|
t.bigInteger("accessTokenNumUsesLimit").defaultTo(0).notNullable();
|
||||||
|
t.jsonb("accessTokenTrustedIps").notNullable();
|
||||||
|
t.timestamps(true, true, true);
|
||||||
|
t.uuid("identityId").notNullable().unique();
|
||||||
|
t.foreign("identityId").references("id").inTable(TableName.Identity).onDelete("CASCADE");
|
||||||
|
t.string("type").notNullable();
|
||||||
|
|
||||||
|
t.string("tenancyOcid").notNullable();
|
||||||
|
t.string("allowedUsernames").nullable();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
await createOnUpdateTrigger(knex, TableName.IdentityOciAuth);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function down(knex: Knex): Promise<void> {
|
||||||
|
await knex.schema.dropTableIfExists(TableName.IdentityOciAuth);
|
||||||
|
await dropOnUpdateTrigger(knex, TableName.IdentityOciAuth);
|
||||||
|
}
|
@@ -0,0 +1,25 @@
|
|||||||
|
import { Knex } from "knex";
|
||||||
|
|
||||||
|
import { TableName } from "../schemas";
|
||||||
|
|
||||||
|
export async function up(knex: Knex): Promise<void> {
|
||||||
|
const hasGatewayIdColumn = await knex.schema.hasColumn(TableName.IdentityKubernetesAuth, "gatewayId");
|
||||||
|
|
||||||
|
if (!hasGatewayIdColumn) {
|
||||||
|
await knex.schema.alterTable(TableName.IdentityKubernetesAuth, (table) => {
|
||||||
|
table.uuid("gatewayId").nullable();
|
||||||
|
table.foreign("gatewayId").references("id").inTable(TableName.Gateway).onDelete("SET NULL");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function down(knex: Knex): Promise<void> {
|
||||||
|
const hasGatewayIdColumn = await knex.schema.hasColumn(TableName.IdentityKubernetesAuth, "gatewayId");
|
||||||
|
|
||||||
|
if (hasGatewayIdColumn) {
|
||||||
|
await knex.schema.alterTable(TableName.IdentityKubernetesAuth, (table) => {
|
||||||
|
table.dropForeign("gatewayId");
|
||||||
|
table.dropColumn("gatewayId");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,110 @@
|
|||||||
|
import { Knex } from "knex";
|
||||||
|
|
||||||
|
import { inMemoryKeyStore } from "@app/keystore/memory";
|
||||||
|
import { selectAllTableCols } from "@app/lib/knex";
|
||||||
|
import { initLogger } from "@app/lib/logger";
|
||||||
|
import { KmsDataKey } from "@app/services/kms/kms-types";
|
||||||
|
|
||||||
|
import { TableName } from "../schemas";
|
||||||
|
import { getMigrationEnvConfig } from "./utils/env-config";
|
||||||
|
import { getMigrationEncryptionServices } from "./utils/services";
|
||||||
|
|
||||||
|
// Note(daniel): We aren't dropping tables or columns in this migrations so we can easily rollback if needed.
|
||||||
|
// In the future we need to drop the projectGatewayId on the dynamic secrets table, and drop the project_gateways table entirely.
|
||||||
|
|
||||||
|
const BATCH_SIZE = 500;
|
||||||
|
|
||||||
|
export async function up(knex: Knex): Promise<void> {
|
||||||
|
// eslint-disable-next-line no-param-reassign
|
||||||
|
knex.replicaNode = () => {
|
||||||
|
return knex;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!(await knex.schema.hasColumn(TableName.DynamicSecret, "gatewayId"))) {
|
||||||
|
await knex.schema.alterTable(TableName.DynamicSecret, (table) => {
|
||||||
|
table.uuid("gatewayId").nullable();
|
||||||
|
table.foreign("gatewayId").references("id").inTable(TableName.Gateway).onDelete("SET NULL");
|
||||||
|
|
||||||
|
table.index("gatewayId");
|
||||||
|
});
|
||||||
|
|
||||||
|
const existingDynamicSecretsWithProjectGatewayId = await knex(TableName.DynamicSecret)
|
||||||
|
.select(selectAllTableCols(TableName.DynamicSecret))
|
||||||
|
.whereNotNull(`${TableName.DynamicSecret}.projectGatewayId`)
|
||||||
|
.join(TableName.ProjectGateway, `${TableName.ProjectGateway}.id`, `${TableName.DynamicSecret}.projectGatewayId`)
|
||||||
|
.whereNotNull(`${TableName.ProjectGateway}.gatewayId`)
|
||||||
|
.select(
|
||||||
|
knex.ref("projectId").withSchema(TableName.ProjectGateway).as("projectId"),
|
||||||
|
knex.ref("gatewayId").withSchema(TableName.ProjectGateway).as("projectGatewayGatewayId")
|
||||||
|
);
|
||||||
|
|
||||||
|
initLogger();
|
||||||
|
const envConfig = getMigrationEnvConfig();
|
||||||
|
const keyStore = inMemoryKeyStore();
|
||||||
|
const { kmsService } = await getMigrationEncryptionServices({ envConfig, keyStore, db: knex });
|
||||||
|
|
||||||
|
const updatedDynamicSecrets = await Promise.all(
|
||||||
|
existingDynamicSecretsWithProjectGatewayId.map(async (existingDynamicSecret) => {
|
||||||
|
if (!existingDynamicSecret.projectGatewayGatewayId) {
|
||||||
|
const result = {
|
||||||
|
...existingDynamicSecret,
|
||||||
|
gatewayId: null
|
||||||
|
};
|
||||||
|
|
||||||
|
const { projectId, projectGatewayGatewayId, ...rest } = result;
|
||||||
|
return rest;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { decryptor: secretManagerDecryptor } = await kmsService.createCipherPairWithDataKey({
|
||||||
|
type: KmsDataKey.SecretManager,
|
||||||
|
projectId: existingDynamicSecret.projectId
|
||||||
|
});
|
||||||
|
const { encryptor: secretManagerEncryptor } = await kmsService.createCipherPairWithDataKey({
|
||||||
|
type: KmsDataKey.SecretManager,
|
||||||
|
projectId: existingDynamicSecret.projectId
|
||||||
|
});
|
||||||
|
|
||||||
|
let decryptedStoredInput = JSON.parse(
|
||||||
|
secretManagerDecryptor({ cipherTextBlob: Buffer.from(existingDynamicSecret.encryptedInput) }).toString()
|
||||||
|
) as object;
|
||||||
|
|
||||||
|
// We're not removing the existing projectGatewayId from the input so we can easily rollback without having to re-encrypt the input
|
||||||
|
decryptedStoredInput = {
|
||||||
|
...decryptedStoredInput,
|
||||||
|
gatewayId: existingDynamicSecret.projectGatewayGatewayId
|
||||||
|
};
|
||||||
|
|
||||||
|
const encryptedInput = secretManagerEncryptor({
|
||||||
|
plainText: Buffer.from(JSON.stringify(decryptedStoredInput))
|
||||||
|
}).cipherTextBlob;
|
||||||
|
|
||||||
|
const result = {
|
||||||
|
...existingDynamicSecret,
|
||||||
|
encryptedInput,
|
||||||
|
gatewayId: existingDynamicSecret.projectGatewayGatewayId
|
||||||
|
};
|
||||||
|
|
||||||
|
const { projectId, projectGatewayGatewayId, ...rest } = result;
|
||||||
|
return rest;
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
for (let i = 0; i < updatedDynamicSecrets.length; i += BATCH_SIZE) {
|
||||||
|
// eslint-disable-next-line no-await-in-loop
|
||||||
|
await knex(TableName.DynamicSecret)
|
||||||
|
.insert(updatedDynamicSecrets.slice(i, i + BATCH_SIZE))
|
||||||
|
.onConflict("id")
|
||||||
|
.merge();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function down(knex: Knex): Promise<void> {
|
||||||
|
// no re-encryption needed as we keep the old projectGatewayId in the input
|
||||||
|
if (await knex.schema.hasColumn(TableName.DynamicSecret, "gatewayId")) {
|
||||||
|
await knex.schema.alterTable(TableName.DynamicSecret, (table) => {
|
||||||
|
table.dropForeign("gatewayId");
|
||||||
|
table.dropColumn("gatewayId");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,53 @@
|
|||||||
|
import { Knex } from "knex";
|
||||||
|
|
||||||
|
import { TableName } from "../schemas";
|
||||||
|
|
||||||
|
export async function up(knex: Knex): Promise<void> {
|
||||||
|
const columns = await knex.table(TableName.Organization).columnInfo();
|
||||||
|
|
||||||
|
await knex.schema.alterTable(TableName.Organization, (t) => {
|
||||||
|
if (!columns.secretsProductEnabled) {
|
||||||
|
t.boolean("secretsProductEnabled").defaultTo(true);
|
||||||
|
}
|
||||||
|
if (!columns.pkiProductEnabled) {
|
||||||
|
t.boolean("pkiProductEnabled").defaultTo(true);
|
||||||
|
}
|
||||||
|
if (!columns.kmsProductEnabled) {
|
||||||
|
t.boolean("kmsProductEnabled").defaultTo(true);
|
||||||
|
}
|
||||||
|
if (!columns.sshProductEnabled) {
|
||||||
|
t.boolean("sshProductEnabled").defaultTo(true);
|
||||||
|
}
|
||||||
|
if (!columns.scannerProductEnabled) {
|
||||||
|
t.boolean("scannerProductEnabled").defaultTo(true);
|
||||||
|
}
|
||||||
|
if (!columns.shareSecretsProductEnabled) {
|
||||||
|
t.boolean("shareSecretsProductEnabled").defaultTo(true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function down(knex: Knex): Promise<void> {
|
||||||
|
const columns = await knex.table(TableName.Organization).columnInfo();
|
||||||
|
|
||||||
|
await knex.schema.alterTable(TableName.Organization, (t) => {
|
||||||
|
if (columns.secretsProductEnabled) {
|
||||||
|
t.dropColumn("secretsProductEnabled");
|
||||||
|
}
|
||||||
|
if (columns.pkiProductEnabled) {
|
||||||
|
t.dropColumn("pkiProductEnabled");
|
||||||
|
}
|
||||||
|
if (columns.kmsProductEnabled) {
|
||||||
|
t.dropColumn("kmsProductEnabled");
|
||||||
|
}
|
||||||
|
if (columns.sshProductEnabled) {
|
||||||
|
t.dropColumn("sshProductEnabled");
|
||||||
|
}
|
||||||
|
if (columns.scannerProductEnabled) {
|
||||||
|
t.dropColumn("scannerProductEnabled");
|
||||||
|
}
|
||||||
|
if (columns.shareSecretsProductEnabled) {
|
||||||
|
t.dropColumn("shareSecretsProductEnabled");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
@@ -0,0 +1,21 @@
|
|||||||
|
import { Knex } from "knex";
|
||||||
|
|
||||||
|
import { TableName } from "../schemas";
|
||||||
|
|
||||||
|
export async function up(knex: Knex): Promise<void> {
|
||||||
|
const hasSecretSharingColumn = await knex.schema.hasColumn(TableName.Project, "secretSharing");
|
||||||
|
if (!hasSecretSharingColumn) {
|
||||||
|
await knex.schema.table(TableName.Project, (table) => {
|
||||||
|
table.boolean("secretSharing").notNullable().defaultTo(true);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function down(knex: Knex): Promise<void> {
|
||||||
|
const hasSecretSharingColumn = await knex.schema.hasColumn(TableName.Project, "secretSharing");
|
||||||
|
if (hasSecretSharingColumn) {
|
||||||
|
await knex.schema.table(TableName.Project, (table) => {
|
||||||
|
table.dropColumn("secretSharing");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,35 @@
|
|||||||
|
import { Knex } from "knex";
|
||||||
|
|
||||||
|
import { TableName } from "../schemas";
|
||||||
|
|
||||||
|
export async function up(knex: Knex): Promise<void> {
|
||||||
|
const hasLifetimeColumn = await knex.schema.hasColumn(TableName.Organization, "maxSharedSecretLifetime");
|
||||||
|
const hasViewLimitColumn = await knex.schema.hasColumn(TableName.Organization, "maxSharedSecretViewLimit");
|
||||||
|
|
||||||
|
if (!hasLifetimeColumn || !hasViewLimitColumn) {
|
||||||
|
await knex.schema.alterTable(TableName.Organization, (t) => {
|
||||||
|
if (!hasLifetimeColumn) {
|
||||||
|
t.integer("maxSharedSecretLifetime").nullable().defaultTo(2592000); // 30 days in seconds
|
||||||
|
}
|
||||||
|
if (!hasViewLimitColumn) {
|
||||||
|
t.integer("maxSharedSecretViewLimit").nullable();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function down(knex: Knex): Promise<void> {
|
||||||
|
const hasLifetimeColumn = await knex.schema.hasColumn(TableName.Organization, "maxSharedSecretLifetime");
|
||||||
|
const hasViewLimitColumn = await knex.schema.hasColumn(TableName.Organization, "maxSharedSecretViewLimit");
|
||||||
|
|
||||||
|
if (hasLifetimeColumn || hasViewLimitColumn) {
|
||||||
|
await knex.schema.alterTable(TableName.Organization, (t) => {
|
||||||
|
if (hasLifetimeColumn) {
|
||||||
|
t.dropColumn("maxSharedSecretLifetime");
|
||||||
|
}
|
||||||
|
if (hasViewLimitColumn) {
|
||||||
|
t.dropColumn("maxSharedSecretViewLimit");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,43 @@
|
|||||||
|
import { Knex } from "knex";
|
||||||
|
|
||||||
|
import { TableName } from "../schemas";
|
||||||
|
|
||||||
|
export async function up(knex: Knex): Promise<void> {
|
||||||
|
if (await knex.schema.hasTable(TableName.SecretSharing)) {
|
||||||
|
const hasEncryptedSalt = await knex.schema.hasColumn(TableName.SecretSharing, "encryptedSalt");
|
||||||
|
const hasAuthorizedEmails = await knex.schema.hasColumn(TableName.SecretSharing, "authorizedEmails");
|
||||||
|
|
||||||
|
if (!hasEncryptedSalt || !hasAuthorizedEmails) {
|
||||||
|
await knex.schema.alterTable(TableName.SecretSharing, (t) => {
|
||||||
|
// These two columns are only needed when secrets are shared with a specific list of emails
|
||||||
|
|
||||||
|
if (!hasEncryptedSalt) {
|
||||||
|
t.binary("encryptedSalt").nullable();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!hasAuthorizedEmails) {
|
||||||
|
t.json("authorizedEmails").nullable();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function down(knex: Knex): Promise<void> {
|
||||||
|
if (await knex.schema.hasTable(TableName.SecretSharing)) {
|
||||||
|
const hasEncryptedSalt = await knex.schema.hasColumn(TableName.SecretSharing, "encryptedSalt");
|
||||||
|
const hasAuthorizedEmails = await knex.schema.hasColumn(TableName.SecretSharing, "authorizedEmails");
|
||||||
|
|
||||||
|
if (hasEncryptedSalt || hasAuthorizedEmails) {
|
||||||
|
await knex.schema.alterTable(TableName.SecretSharing, (t) => {
|
||||||
|
if (hasEncryptedSalt) {
|
||||||
|
t.dropColumn("encryptedSalt");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasAuthorizedEmails) {
|
||||||
|
t.dropColumn("authorizedEmails");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -24,7 +24,8 @@ export const CertificatesSchema = z.object({
|
|||||||
caCertId: z.string().uuid(),
|
caCertId: z.string().uuid(),
|
||||||
certificateTemplateId: z.string().uuid().nullable().optional(),
|
certificateTemplateId: z.string().uuid().nullable().optional(),
|
||||||
keyUsages: z.string().array().nullable().optional(),
|
keyUsages: z.string().array().nullable().optional(),
|
||||||
extendedKeyUsages: z.string().array().nullable().optional()
|
extendedKeyUsages: z.string().array().nullable().optional(),
|
||||||
|
pkiSubscriberId: z.string().uuid().nullable().optional()
|
||||||
});
|
});
|
||||||
|
|
||||||
export type TCertificates = z.infer<typeof CertificatesSchema>;
|
export type TCertificates = z.infer<typeof CertificatesSchema>;
|
||||||
|
@@ -27,7 +27,8 @@ export const DynamicSecretsSchema = z.object({
|
|||||||
createdAt: z.date(),
|
createdAt: z.date(),
|
||||||
updatedAt: z.date(),
|
updatedAt: z.date(),
|
||||||
encryptedInput: zodBuffer,
|
encryptedInput: zodBuffer,
|
||||||
projectGatewayId: z.string().uuid().nullable().optional()
|
projectGatewayId: z.string().uuid().nullable().optional(),
|
||||||
|
gatewayId: z.string().uuid().nullable().optional()
|
||||||
});
|
});
|
||||||
|
|
||||||
export type TDynamicSecrets = z.infer<typeof DynamicSecretsSchema>;
|
export type TDynamicSecrets = z.infer<typeof DynamicSecretsSchema>;
|
||||||
|
@@ -29,7 +29,8 @@ export const IdentityKubernetesAuthsSchema = z.object({
|
|||||||
allowedNames: z.string(),
|
allowedNames: z.string(),
|
||||||
allowedAudience: z.string(),
|
allowedAudience: z.string(),
|
||||||
encryptedKubernetesTokenReviewerJwt: zodBuffer.nullable().optional(),
|
encryptedKubernetesTokenReviewerJwt: zodBuffer.nullable().optional(),
|
||||||
encryptedKubernetesCaCertificate: zodBuffer.nullable().optional()
|
encryptedKubernetesCaCertificate: zodBuffer.nullable().optional(),
|
||||||
|
gatewayId: z.string().uuid().nullable().optional()
|
||||||
});
|
});
|
||||||
|
|
||||||
export type TIdentityKubernetesAuths = z.infer<typeof IdentityKubernetesAuthsSchema>;
|
export type TIdentityKubernetesAuths = z.infer<typeof IdentityKubernetesAuthsSchema>;
|
||||||
|
26
backend/src/db/schemas/identity-oci-auths.ts
Normal file
26
backend/src/db/schemas/identity-oci-auths.ts
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
// 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 IdentityOciAuthsSchema = z.object({
|
||||||
|
id: z.string().uuid(),
|
||||||
|
accessTokenTTL: z.coerce.number().default(7200),
|
||||||
|
accessTokenMaxTTL: z.coerce.number().default(7200),
|
||||||
|
accessTokenNumUsesLimit: z.coerce.number().default(0),
|
||||||
|
accessTokenTrustedIps: z.unknown(),
|
||||||
|
createdAt: z.date(),
|
||||||
|
updatedAt: z.date(),
|
||||||
|
identityId: z.string().uuid(),
|
||||||
|
type: z.string(),
|
||||||
|
tenancyOcid: z.string(),
|
||||||
|
allowedUsernames: z.string().nullable().optional()
|
||||||
|
});
|
||||||
|
|
||||||
|
export type TIdentityOciAuths = z.infer<typeof IdentityOciAuthsSchema>;
|
||||||
|
export type TIdentityOciAuthsInsert = Omit<z.input<typeof IdentityOciAuthsSchema>, TImmutableDBKeys>;
|
||||||
|
export type TIdentityOciAuthsUpdate = Partial<Omit<z.input<typeof IdentityOciAuthsSchema>, TImmutableDBKeys>>;
|
@@ -37,6 +37,7 @@ export * from "./identity-gcp-auths";
|
|||||||
export * from "./identity-jwt-auths";
|
export * from "./identity-jwt-auths";
|
||||||
export * from "./identity-kubernetes-auths";
|
export * from "./identity-kubernetes-auths";
|
||||||
export * from "./identity-metadata";
|
export * from "./identity-metadata";
|
||||||
|
export * from "./identity-oci-auths";
|
||||||
export * from "./identity-oidc-auths";
|
export * from "./identity-oidc-auths";
|
||||||
export * from "./identity-org-memberships";
|
export * from "./identity-org-memberships";
|
||||||
export * from "./identity-project-additional-privilege";
|
export * from "./identity-project-additional-privilege";
|
||||||
@@ -69,6 +70,7 @@ export * from "./organizations";
|
|||||||
export * from "./pki-alerts";
|
export * from "./pki-alerts";
|
||||||
export * from "./pki-collection-items";
|
export * from "./pki-collection-items";
|
||||||
export * from "./pki-collections";
|
export * from "./pki-collections";
|
||||||
|
export * from "./pki-subscribers";
|
||||||
export * from "./project-bots";
|
export * from "./project-bots";
|
||||||
export * from "./project-environments";
|
export * from "./project-environments";
|
||||||
export * from "./project-gateways";
|
export * from "./project-gateways";
|
||||||
|
@@ -21,6 +21,7 @@ export enum TableName {
|
|||||||
CertificateBody = "certificate_bodies",
|
CertificateBody = "certificate_bodies",
|
||||||
CertificateSecret = "certificate_secrets",
|
CertificateSecret = "certificate_secrets",
|
||||||
CertificateTemplate = "certificate_templates",
|
CertificateTemplate = "certificate_templates",
|
||||||
|
PkiSubscriber = "pki_subscribers",
|
||||||
PkiAlert = "pki_alerts",
|
PkiAlert = "pki_alerts",
|
||||||
PkiCollection = "pki_collections",
|
PkiCollection = "pki_collections",
|
||||||
PkiCollectionItem = "pki_collection_items",
|
PkiCollectionItem = "pki_collection_items",
|
||||||
@@ -78,6 +79,7 @@ export enum TableName {
|
|||||||
IdentityAzureAuth = "identity_azure_auths",
|
IdentityAzureAuth = "identity_azure_auths",
|
||||||
IdentityUaClientSecret = "identity_ua_client_secrets",
|
IdentityUaClientSecret = "identity_ua_client_secrets",
|
||||||
IdentityAwsAuth = "identity_aws_auths",
|
IdentityAwsAuth = "identity_aws_auths",
|
||||||
|
IdentityOciAuth = "identity_oci_auths",
|
||||||
IdentityOidcAuth = "identity_oidc_auths",
|
IdentityOidcAuth = "identity_oidc_auths",
|
||||||
IdentityJwtAuth = "identity_jwt_auths",
|
IdentityJwtAuth = "identity_jwt_auths",
|
||||||
IdentityLdapAuth = "identity_ldap_auths",
|
IdentityLdapAuth = "identity_ldap_auths",
|
||||||
@@ -232,6 +234,7 @@ export enum IdentityAuthMethod {
|
|||||||
GCP_AUTH = "gcp-auth",
|
GCP_AUTH = "gcp-auth",
|
||||||
AWS_AUTH = "aws-auth",
|
AWS_AUTH = "aws-auth",
|
||||||
AZURE_AUTH = "azure-auth",
|
AZURE_AUTH = "azure-auth",
|
||||||
|
OCI_AUTH = "oci-auth",
|
||||||
OIDC_AUTH = "oidc-auth",
|
OIDC_AUTH = "oidc-auth",
|
||||||
JWT_AUTH = "jwt-auth",
|
JWT_AUTH = "jwt-auth",
|
||||||
LDAP_AUTH = "ldap-auth"
|
LDAP_AUTH = "ldap-auth"
|
||||||
|
@@ -28,7 +28,15 @@ export const OrganizationsSchema = z.object({
|
|||||||
privilegeUpgradeInitiatedByUsername: z.string().nullable().optional(),
|
privilegeUpgradeInitiatedByUsername: z.string().nullable().optional(),
|
||||||
privilegeUpgradeInitiatedAt: z.date().nullable().optional(),
|
privilegeUpgradeInitiatedAt: z.date().nullable().optional(),
|
||||||
bypassOrgAuthEnabled: z.boolean().default(false),
|
bypassOrgAuthEnabled: z.boolean().default(false),
|
||||||
userTokenExpiration: z.string().nullable().optional()
|
userTokenExpiration: z.string().nullable().optional(),
|
||||||
|
secretsProductEnabled: z.boolean().default(true).nullable().optional(),
|
||||||
|
pkiProductEnabled: z.boolean().default(true).nullable().optional(),
|
||||||
|
kmsProductEnabled: z.boolean().default(true).nullable().optional(),
|
||||||
|
sshProductEnabled: z.boolean().default(true).nullable().optional(),
|
||||||
|
scannerProductEnabled: z.boolean().default(true).nullable().optional(),
|
||||||
|
shareSecretsProductEnabled: z.boolean().default(true).nullable().optional(),
|
||||||
|
maxSharedSecretLifetime: z.number().default(2592000).nullable().optional(),
|
||||||
|
maxSharedSecretViewLimit: z.number().nullable().optional()
|
||||||
});
|
});
|
||||||
|
|
||||||
export type TOrganizations = z.infer<typeof OrganizationsSchema>;
|
export type TOrganizations = z.infer<typeof OrganizationsSchema>;
|
||||||
|
27
backend/src/db/schemas/pki-subscribers.ts
Normal file
27
backend/src/db/schemas/pki-subscribers.ts
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
// Code generated by automation script, DO NOT EDIT.
|
||||||
|
// Automated by pulling database and generating zod schema
|
||||||
|
// To update. Just run npm run generate:schema
|
||||||
|
// Written by akhilmhdh.
|
||||||
|
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { TImmutableDBKeys } from "./models";
|
||||||
|
|
||||||
|
export const PkiSubscribersSchema = z.object({
|
||||||
|
id: z.string().uuid(),
|
||||||
|
createdAt: z.date(),
|
||||||
|
updatedAt: z.date(),
|
||||||
|
projectId: z.string(),
|
||||||
|
caId: z.string().uuid().nullable().optional(),
|
||||||
|
name: z.string(),
|
||||||
|
commonName: z.string(),
|
||||||
|
subjectAlternativeNames: z.string().array(),
|
||||||
|
ttl: z.string(),
|
||||||
|
keyUsages: z.string().array(),
|
||||||
|
extendedKeyUsages: z.string().array(),
|
||||||
|
status: z.string()
|
||||||
|
});
|
||||||
|
|
||||||
|
export type TPkiSubscribers = z.infer<typeof PkiSubscribersSchema>;
|
||||||
|
export type TPkiSubscribersInsert = Omit<z.input<typeof PkiSubscribersSchema>, TImmutableDBKeys>;
|
||||||
|
export type TPkiSubscribersUpdate = Partial<Omit<z.input<typeof PkiSubscribersSchema>, TImmutableDBKeys>>;
|
@@ -27,7 +27,8 @@ export const ProjectsSchema = z.object({
|
|||||||
description: z.string().nullable().optional(),
|
description: z.string().nullable().optional(),
|
||||||
type: z.string(),
|
type: z.string(),
|
||||||
enforceCapitalization: z.boolean().default(false),
|
enforceCapitalization: z.boolean().default(false),
|
||||||
hasDeleteProtection: z.boolean().default(false).nullable().optional()
|
hasDeleteProtection: z.boolean().default(false).nullable().optional(),
|
||||||
|
secretSharing: z.boolean().default(true)
|
||||||
});
|
});
|
||||||
|
|
||||||
export type TProjects = z.infer<typeof ProjectsSchema>;
|
export type TProjects = z.infer<typeof ProjectsSchema>;
|
||||||
|
@@ -27,7 +27,9 @@ export const SecretSharingSchema = z.object({
|
|||||||
password: z.string().nullable().optional(),
|
password: z.string().nullable().optional(),
|
||||||
encryptedSecret: zodBuffer.nullable().optional(),
|
encryptedSecret: zodBuffer.nullable().optional(),
|
||||||
identifier: z.string().nullable().optional(),
|
identifier: z.string().nullable().optional(),
|
||||||
type: z.string().default("share")
|
type: z.string().default("share"),
|
||||||
|
encryptedSalt: zodBuffer.nullable().optional(),
|
||||||
|
authorizedEmails: z.unknown().nullable().optional()
|
||||||
});
|
});
|
||||||
|
|
||||||
export type TSecretSharing = z.infer<typeof SecretSharingSchema>;
|
export type TSecretSharing = z.infer<typeof SecretSharingSchema>;
|
||||||
|
@@ -121,14 +121,7 @@ export const registerGatewayRouter = async (server: FastifyZodProvider) => {
|
|||||||
identity: z.object({
|
identity: z.object({
|
||||||
name: z.string(),
|
name: z.string(),
|
||||||
id: z.string()
|
id: z.string()
|
||||||
}),
|
})
|
||||||
projects: z
|
|
||||||
.object({
|
|
||||||
name: z.string(),
|
|
||||||
id: z.string(),
|
|
||||||
slug: z.string()
|
|
||||||
})
|
|
||||||
.array()
|
|
||||||
}).array()
|
}).array()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -158,17 +151,15 @@ export const registerGatewayRouter = async (server: FastifyZodProvider) => {
|
|||||||
identity: z.object({
|
identity: z.object({
|
||||||
name: z.string(),
|
name: z.string(),
|
||||||
id: z.string()
|
id: z.string()
|
||||||
}),
|
})
|
||||||
projectGatewayId: z.string()
|
|
||||||
}).array()
|
}).array()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onRequest: verifyAuth([AuthMode.IDENTITY_ACCESS_TOKEN, AuthMode.JWT]),
|
onRequest: verifyAuth([AuthMode.IDENTITY_ACCESS_TOKEN, AuthMode.JWT]),
|
||||||
handler: async (req) => {
|
handler: async (req) => {
|
||||||
const gateways = await server.services.gateway.getProjectGateways({
|
const gateways = await server.services.gateway.listGateways({
|
||||||
projectId: req.params.projectId,
|
orgPermission: req.permission
|
||||||
projectPermission: req.permission
|
|
||||||
});
|
});
|
||||||
return { gateways };
|
return { gateways };
|
||||||
}
|
}
|
||||||
@@ -216,8 +207,7 @@ export const registerGatewayRouter = async (server: FastifyZodProvider) => {
|
|||||||
id: z.string()
|
id: z.string()
|
||||||
}),
|
}),
|
||||||
body: z.object({
|
body: z.object({
|
||||||
name: slugSchema({ field: "name" }).optional(),
|
name: slugSchema({ field: "name" }).optional()
|
||||||
projectIds: z.string().array().optional()
|
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
@@ -230,8 +220,7 @@ export const registerGatewayRouter = async (server: FastifyZodProvider) => {
|
|||||||
const gateway = await server.services.gateway.updateGatewayById({
|
const gateway = await server.services.gateway.updateGatewayById({
|
||||||
orgPermission: req.permission,
|
orgPermission: req.permission,
|
||||||
id: req.params.id,
|
id: req.params.id,
|
||||||
name: req.body.name,
|
name: req.body.name
|
||||||
projectIds: req.body.projectIds
|
|
||||||
});
|
});
|
||||||
return { gateway };
|
return { gateway };
|
||||||
}
|
}
|
||||||
|
@@ -97,7 +97,7 @@ export const registerSshCertificateTemplateRouter = async (server: FastifyZodPro
|
|||||||
allowCustomKeyIds: z.boolean().describe(SSH_CERTIFICATE_TEMPLATES.CREATE.allowCustomKeyIds)
|
allowCustomKeyIds: z.boolean().describe(SSH_CERTIFICATE_TEMPLATES.CREATE.allowCustomKeyIds)
|
||||||
})
|
})
|
||||||
.refine((data) => ms(data.maxTTL) >= ms(data.ttl), {
|
.refine((data) => ms(data.maxTTL) >= ms(data.ttl), {
|
||||||
message: "Max TLL must be greater than or equal to TTL",
|
message: "Max TTL must be greater than or equal to TTL",
|
||||||
path: ["maxTTL"]
|
path: ["maxTTL"]
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
|
@@ -73,7 +73,7 @@ export const registerSshHostRouter = async (server: FastifyZodProvider) => {
|
|||||||
},
|
},
|
||||||
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
handler: async (req) => {
|
handler: async (req) => {
|
||||||
const host = await server.services.sshHost.getSshHost({
|
const host = await server.services.sshHost.getSshHostById({
|
||||||
sshHostId: req.params.sshHostId,
|
sshHostId: req.params.sshHostId,
|
||||||
actor: req.permission.type,
|
actor: req.permission.type,
|
||||||
actorId: req.permission.id,
|
actorId: req.permission.id,
|
||||||
|
@@ -19,7 +19,7 @@ import { TProjectPermission } from "@app/lib/types";
|
|||||||
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
|
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
|
||||||
import { TCreateAppConnectionDTO, TUpdateAppConnectionDTO } from "@app/services/app-connection/app-connection-types";
|
import { TCreateAppConnectionDTO, TUpdateAppConnectionDTO } from "@app/services/app-connection/app-connection-types";
|
||||||
import { ActorType } from "@app/services/auth/auth-type";
|
import { ActorType } from "@app/services/auth/auth-type";
|
||||||
import { CertKeyAlgorithm } from "@app/services/certificate/certificate-types";
|
import { CertExtendedKeyUsage, CertKeyAlgorithm, CertKeyUsage } from "@app/services/certificate/certificate-types";
|
||||||
import { CaStatus } from "@app/services/certificate-authority/certificate-authority-types";
|
import { CaStatus } from "@app/services/certificate-authority/certificate-authority-types";
|
||||||
import { TIdentityTrustedIp } from "@app/services/identity/identity-types";
|
import { TIdentityTrustedIp } from "@app/services/identity/identity-types";
|
||||||
import { TAllowedFields } from "@app/services/identity-ldap-auth/identity-ldap-auth-types";
|
import { TAllowedFields } from "@app/services/identity-ldap-auth/identity-ldap-auth-types";
|
||||||
@@ -162,6 +162,12 @@ export enum EventType {
|
|||||||
REVOKE_IDENTITY_AWS_AUTH = "revoke-identity-aws-auth",
|
REVOKE_IDENTITY_AWS_AUTH = "revoke-identity-aws-auth",
|
||||||
GET_IDENTITY_AWS_AUTH = "get-identity-aws-auth",
|
GET_IDENTITY_AWS_AUTH = "get-identity-aws-auth",
|
||||||
|
|
||||||
|
LOGIN_IDENTITY_OCI_AUTH = "login-identity-oci-auth",
|
||||||
|
ADD_IDENTITY_OCI_AUTH = "add-identity-oci-auth",
|
||||||
|
UPDATE_IDENTITY_OCI_AUTH = "update-identity-oci-auth",
|
||||||
|
REVOKE_IDENTITY_OCI_AUTH = "revoke-identity-oci-auth",
|
||||||
|
GET_IDENTITY_OCI_AUTH = "get-identity-oci-auth",
|
||||||
|
|
||||||
LOGIN_IDENTITY_AZURE_AUTH = "login-identity-azure-auth",
|
LOGIN_IDENTITY_AZURE_AUTH = "login-identity-azure-auth",
|
||||||
ADD_IDENTITY_AZURE_AUTH = "add-identity-azure-auth",
|
ADD_IDENTITY_AZURE_AUTH = "add-identity-azure-auth",
|
||||||
UPDATE_IDENTITY_AZURE_AUTH = "update-identity-azure-auth",
|
UPDATE_IDENTITY_AZURE_AUTH = "update-identity-azure-auth",
|
||||||
@@ -254,6 +260,13 @@ export enum EventType {
|
|||||||
GET_PKI_COLLECTION_ITEMS = "get-pki-collection-items",
|
GET_PKI_COLLECTION_ITEMS = "get-pki-collection-items",
|
||||||
ADD_PKI_COLLECTION_ITEM = "add-pki-collection-item",
|
ADD_PKI_COLLECTION_ITEM = "add-pki-collection-item",
|
||||||
DELETE_PKI_COLLECTION_ITEM = "delete-pki-collection-item",
|
DELETE_PKI_COLLECTION_ITEM = "delete-pki-collection-item",
|
||||||
|
CREATE_PKI_SUBSCRIBER = "create-pki-subscriber",
|
||||||
|
UPDATE_PKI_SUBSCRIBER = "update-pki-subscriber",
|
||||||
|
DELETE_PKI_SUBSCRIBER = "delete-pki-subscriber",
|
||||||
|
GET_PKI_SUBSCRIBER = "get-pki-subscriber",
|
||||||
|
ISSUE_PKI_SUBSCRIBER_CERT = "issue-pki-subscriber-cert",
|
||||||
|
SIGN_PKI_SUBSCRIBER_CERT = "sign-pki-subscriber-cert",
|
||||||
|
LIST_PKI_SUBSCRIBER_CERTS = "list-pki-subscriber-certs",
|
||||||
CREATE_KMS = "create-kms",
|
CREATE_KMS = "create-kms",
|
||||||
UPDATE_KMS = "update-kms",
|
UPDATE_KMS = "update-kms",
|
||||||
DELETE_KMS = "delete-kms",
|
DELETE_KMS = "delete-kms",
|
||||||
@@ -1002,6 +1015,55 @@ interface GetIdentityAwsAuthEvent {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface LoginIdentityOciAuthEvent {
|
||||||
|
type: EventType.LOGIN_IDENTITY_OCI_AUTH;
|
||||||
|
metadata: {
|
||||||
|
identityId: string;
|
||||||
|
identityOciAuthId: string;
|
||||||
|
identityAccessTokenId: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
interface AddIdentityOciAuthEvent {
|
||||||
|
type: EventType.ADD_IDENTITY_OCI_AUTH;
|
||||||
|
metadata: {
|
||||||
|
identityId: string;
|
||||||
|
tenancyOcid: string;
|
||||||
|
allowedUsernames: string | null;
|
||||||
|
accessTokenTTL: number;
|
||||||
|
accessTokenMaxTTL: number;
|
||||||
|
accessTokenNumUsesLimit: number;
|
||||||
|
accessTokenTrustedIps: Array<TIdentityTrustedIp>;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
interface DeleteIdentityOciAuthEvent {
|
||||||
|
type: EventType.REVOKE_IDENTITY_OCI_AUTH;
|
||||||
|
metadata: {
|
||||||
|
identityId: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
interface UpdateIdentityOciAuthEvent {
|
||||||
|
type: EventType.UPDATE_IDENTITY_OCI_AUTH;
|
||||||
|
metadata: {
|
||||||
|
identityId: string;
|
||||||
|
tenancyOcid?: string;
|
||||||
|
allowedUsernames: string | null;
|
||||||
|
accessTokenTTL?: number;
|
||||||
|
accessTokenMaxTTL?: number;
|
||||||
|
accessTokenNumUsesLimit?: number;
|
||||||
|
accessTokenTrustedIps?: Array<TIdentityTrustedIp>;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
interface GetIdentityOciAuthEvent {
|
||||||
|
type: EventType.GET_IDENTITY_OCI_AUTH;
|
||||||
|
metadata: {
|
||||||
|
identityId: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
interface LoginIdentityAzureAuthEvent {
|
interface LoginIdentityAzureAuthEvent {
|
||||||
type: EventType.LOGIN_IDENTITY_AZURE_AUTH;
|
type: EventType.LOGIN_IDENTITY_AZURE_AUTH;
|
||||||
metadata: {
|
metadata: {
|
||||||
@@ -1965,6 +2027,77 @@ interface DeletePkiCollectionItem {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface CreatePkiSubscriber {
|
||||||
|
type: EventType.CREATE_PKI_SUBSCRIBER;
|
||||||
|
metadata: {
|
||||||
|
pkiSubscriberId: string;
|
||||||
|
caId?: string;
|
||||||
|
name: string;
|
||||||
|
commonName: string;
|
||||||
|
ttl: string;
|
||||||
|
subjectAlternativeNames: string[];
|
||||||
|
keyUsages: CertKeyUsage[];
|
||||||
|
extendedKeyUsages: CertExtendedKeyUsage[];
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
interface UpdatePkiSubscriber {
|
||||||
|
type: EventType.UPDATE_PKI_SUBSCRIBER;
|
||||||
|
metadata: {
|
||||||
|
pkiSubscriberId: string;
|
||||||
|
caId?: string;
|
||||||
|
name?: string;
|
||||||
|
commonName?: string;
|
||||||
|
ttl?: string;
|
||||||
|
subjectAlternativeNames?: string[];
|
||||||
|
keyUsages?: CertKeyUsage[];
|
||||||
|
extendedKeyUsages?: CertExtendedKeyUsage[];
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
interface DeletePkiSubscriber {
|
||||||
|
type: EventType.DELETE_PKI_SUBSCRIBER;
|
||||||
|
metadata: {
|
||||||
|
pkiSubscriberId: string;
|
||||||
|
name: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
interface GetPkiSubscriber {
|
||||||
|
type: EventType.GET_PKI_SUBSCRIBER;
|
||||||
|
metadata: {
|
||||||
|
pkiSubscriberId: string;
|
||||||
|
name: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IssuePkiSubscriberCert {
|
||||||
|
type: EventType.ISSUE_PKI_SUBSCRIBER_CERT;
|
||||||
|
metadata: {
|
||||||
|
subscriberId: string;
|
||||||
|
name: string;
|
||||||
|
serialNumber: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
interface SignPkiSubscriberCert {
|
||||||
|
type: EventType.SIGN_PKI_SUBSCRIBER_CERT;
|
||||||
|
metadata: {
|
||||||
|
subscriberId: string;
|
||||||
|
name: string;
|
||||||
|
serialNumber: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ListPkiSubscriberCerts {
|
||||||
|
type: EventType.LIST_PKI_SUBSCRIBER_CERTS;
|
||||||
|
metadata: {
|
||||||
|
subscriberId: string;
|
||||||
|
name: string;
|
||||||
|
projectId: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
interface CreateKmsEvent {
|
interface CreateKmsEvent {
|
||||||
type: EventType.CREATE_KMS;
|
type: EventType.CREATE_KMS;
|
||||||
metadata: {
|
metadata: {
|
||||||
@@ -2836,6 +2969,11 @@ export type Event =
|
|||||||
| UpdateIdentityAwsAuthEvent
|
| UpdateIdentityAwsAuthEvent
|
||||||
| GetIdentityAwsAuthEvent
|
| GetIdentityAwsAuthEvent
|
||||||
| DeleteIdentityAwsAuthEvent
|
| DeleteIdentityAwsAuthEvent
|
||||||
|
| LoginIdentityOciAuthEvent
|
||||||
|
| AddIdentityOciAuthEvent
|
||||||
|
| UpdateIdentityOciAuthEvent
|
||||||
|
| GetIdentityOciAuthEvent
|
||||||
|
| DeleteIdentityOciAuthEvent
|
||||||
| LoginIdentityAzureAuthEvent
|
| LoginIdentityAzureAuthEvent
|
||||||
| AddIdentityAzureAuthEvent
|
| AddIdentityAzureAuthEvent
|
||||||
| DeleteIdentityAzureAuthEvent
|
| DeleteIdentityAzureAuthEvent
|
||||||
@@ -2928,6 +3066,13 @@ export type Event =
|
|||||||
| GetPkiCollectionItems
|
| GetPkiCollectionItems
|
||||||
| AddPkiCollectionItem
|
| AddPkiCollectionItem
|
||||||
| DeletePkiCollectionItem
|
| DeletePkiCollectionItem
|
||||||
|
| CreatePkiSubscriber
|
||||||
|
| UpdatePkiSubscriber
|
||||||
|
| DeletePkiSubscriber
|
||||||
|
| GetPkiSubscriber
|
||||||
|
| IssuePkiSubscriberCert
|
||||||
|
| SignPkiSubscriberCert
|
||||||
|
| ListPkiSubscriberCerts
|
||||||
| CreateKmsEvent
|
| CreateKmsEvent
|
||||||
| UpdateKmsEvent
|
| UpdateKmsEvent
|
||||||
| DeleteKmsEvent
|
| DeleteKmsEvent
|
||||||
|
@@ -17,7 +17,8 @@ import { TSecretFolderDALFactory } from "@app/services/secret-folder/secret-fold
|
|||||||
|
|
||||||
import { TDynamicSecretLeaseDALFactory } from "../dynamic-secret-lease/dynamic-secret-lease-dal";
|
import { TDynamicSecretLeaseDALFactory } from "../dynamic-secret-lease/dynamic-secret-lease-dal";
|
||||||
import { TDynamicSecretLeaseQueueServiceFactory } from "../dynamic-secret-lease/dynamic-secret-lease-queue";
|
import { TDynamicSecretLeaseQueueServiceFactory } from "../dynamic-secret-lease/dynamic-secret-lease-queue";
|
||||||
import { TProjectGatewayDALFactory } from "../gateway/project-gateway-dal";
|
import { TGatewayDALFactory } from "../gateway/gateway-dal";
|
||||||
|
import { OrgPermissionGatewayActions, OrgPermissionSubjects } from "../permission/org-permission";
|
||||||
import { TDynamicSecretDALFactory } from "./dynamic-secret-dal";
|
import { TDynamicSecretDALFactory } from "./dynamic-secret-dal";
|
||||||
import {
|
import {
|
||||||
DynamicSecretStatus,
|
DynamicSecretStatus,
|
||||||
@@ -44,9 +45,9 @@ type TDynamicSecretServiceFactoryDep = {
|
|||||||
licenseService: Pick<TLicenseServiceFactory, "getPlan">;
|
licenseService: Pick<TLicenseServiceFactory, "getPlan">;
|
||||||
folderDAL: Pick<TSecretFolderDALFactory, "findBySecretPath" | "findBySecretPathMultiEnv">;
|
folderDAL: Pick<TSecretFolderDALFactory, "findBySecretPath" | "findBySecretPathMultiEnv">;
|
||||||
projectDAL: Pick<TProjectDALFactory, "findProjectBySlug">;
|
projectDAL: Pick<TProjectDALFactory, "findProjectBySlug">;
|
||||||
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission">;
|
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission" | "getOrgPermission">;
|
||||||
kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey">;
|
kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey">;
|
||||||
projectGatewayDAL: Pick<TProjectGatewayDALFactory, "findOne">;
|
gatewayDAL: Pick<TGatewayDALFactory, "findOne" | "find">;
|
||||||
resourceMetadataDAL: Pick<TResourceMetadataDALFactory, "insertMany" | "delete">;
|
resourceMetadataDAL: Pick<TResourceMetadataDALFactory, "insertMany" | "delete">;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -62,7 +63,7 @@ export const dynamicSecretServiceFactory = ({
|
|||||||
dynamicSecretQueueService,
|
dynamicSecretQueueService,
|
||||||
projectDAL,
|
projectDAL,
|
||||||
kmsService,
|
kmsService,
|
||||||
projectGatewayDAL,
|
gatewayDAL,
|
||||||
resourceMetadataDAL
|
resourceMetadataDAL
|
||||||
}: TDynamicSecretServiceFactoryDep) => {
|
}: TDynamicSecretServiceFactoryDep) => {
|
||||||
const create = async ({
|
const create = async ({
|
||||||
@@ -117,15 +118,31 @@ export const dynamicSecretServiceFactory = ({
|
|||||||
const inputs = await selectedProvider.validateProviderInputs(provider.inputs);
|
const inputs = await selectedProvider.validateProviderInputs(provider.inputs);
|
||||||
|
|
||||||
let selectedGatewayId: string | null = null;
|
let selectedGatewayId: string | null = null;
|
||||||
if (inputs && typeof inputs === "object" && "projectGatewayId" in inputs && inputs.projectGatewayId) {
|
if (inputs && typeof inputs === "object" && "gatewayId" in inputs && inputs.gatewayId) {
|
||||||
const projectGatewayId = inputs.projectGatewayId as string;
|
const gatewayId = inputs.gatewayId as string;
|
||||||
|
|
||||||
const projectGateway = await projectGatewayDAL.findOne({ id: projectGatewayId, projectId });
|
const [gateway] = await gatewayDAL.find({ id: gatewayId, orgId: actorOrgId });
|
||||||
if (!projectGateway)
|
|
||||||
|
if (!gateway) {
|
||||||
throw new NotFoundError({
|
throw new NotFoundError({
|
||||||
message: `Project gateway with ${projectGatewayId} not found`
|
message: `Gateway with ID ${gatewayId} not found`
|
||||||
});
|
});
|
||||||
selectedGatewayId = projectGateway.id;
|
}
|
||||||
|
|
||||||
|
const { permission: orgPermission } = await permissionService.getOrgPermission(
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
gateway.orgId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId
|
||||||
|
);
|
||||||
|
|
||||||
|
ForbiddenError.from(orgPermission).throwUnlessCan(
|
||||||
|
OrgPermissionGatewayActions.AttachGateways,
|
||||||
|
OrgPermissionSubjects.Gateway
|
||||||
|
);
|
||||||
|
|
||||||
|
selectedGatewayId = gateway.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
const isConnected = await selectedProvider.validateConnection(provider.inputs);
|
const isConnected = await selectedProvider.validateConnection(provider.inputs);
|
||||||
@@ -146,7 +163,7 @@ export const dynamicSecretServiceFactory = ({
|
|||||||
defaultTTL,
|
defaultTTL,
|
||||||
folderId: folder.id,
|
folderId: folder.id,
|
||||||
name,
|
name,
|
||||||
projectGatewayId: selectedGatewayId
|
gatewayId: selectedGatewayId
|
||||||
},
|
},
|
||||||
tx
|
tx
|
||||||
);
|
);
|
||||||
@@ -255,20 +272,30 @@ export const dynamicSecretServiceFactory = ({
|
|||||||
const updatedInput = await selectedProvider.validateProviderInputs(newInput);
|
const updatedInput = await selectedProvider.validateProviderInputs(newInput);
|
||||||
|
|
||||||
let selectedGatewayId: string | null = null;
|
let selectedGatewayId: string | null = null;
|
||||||
if (
|
if (updatedInput && typeof updatedInput === "object" && "gatewayId" in updatedInput && updatedInput?.gatewayId) {
|
||||||
updatedInput &&
|
const gatewayId = updatedInput.gatewayId as string;
|
||||||
typeof updatedInput === "object" &&
|
|
||||||
"projectGatewayId" in updatedInput &&
|
|
||||||
updatedInput?.projectGatewayId
|
|
||||||
) {
|
|
||||||
const projectGatewayId = updatedInput.projectGatewayId as string;
|
|
||||||
|
|
||||||
const projectGateway = await projectGatewayDAL.findOne({ id: projectGatewayId, projectId });
|
const [gateway] = await gatewayDAL.find({ id: gatewayId, orgId: actorOrgId });
|
||||||
if (!projectGateway)
|
if (!gateway) {
|
||||||
throw new NotFoundError({
|
throw new NotFoundError({
|
||||||
message: `Project gateway with ${projectGatewayId} not found`
|
message: `Gateway with ID ${gatewayId} not found`
|
||||||
});
|
});
|
||||||
selectedGatewayId = projectGateway.id;
|
}
|
||||||
|
|
||||||
|
const { permission: orgPermission } = await permissionService.getOrgPermission(
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
gateway.orgId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId
|
||||||
|
);
|
||||||
|
|
||||||
|
ForbiddenError.from(orgPermission).throwUnlessCan(
|
||||||
|
OrgPermissionGatewayActions.AttachGateways,
|
||||||
|
OrgPermissionSubjects.Gateway
|
||||||
|
);
|
||||||
|
|
||||||
|
selectedGatewayId = gateway.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
const isConnected = await selectedProvider.validateConnection(newInput);
|
const isConnected = await selectedProvider.validateConnection(newInput);
|
||||||
@@ -284,7 +311,7 @@ export const dynamicSecretServiceFactory = ({
|
|||||||
defaultTTL,
|
defaultTTL,
|
||||||
name: newName ?? name,
|
name: newName ?? name,
|
||||||
status: null,
|
status: null,
|
||||||
projectGatewayId: selectedGatewayId
|
gatewayId: selectedGatewayId
|
||||||
},
|
},
|
||||||
tx
|
tx
|
||||||
);
|
);
|
||||||
|
@@ -18,7 +18,7 @@ import { SqlDatabaseProvider } from "./sql-database";
|
|||||||
import { TotpProvider } from "./totp";
|
import { TotpProvider } from "./totp";
|
||||||
|
|
||||||
type TBuildDynamicSecretProviderDTO = {
|
type TBuildDynamicSecretProviderDTO = {
|
||||||
gatewayService: Pick<TGatewayServiceFactory, "fnGetGatewayClientTls">;
|
gatewayService: Pick<TGatewayServiceFactory, "fnGetGatewayClientTlsByGatewayId">;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const buildDynamicSecretProviders = ({
|
export const buildDynamicSecretProviders = ({
|
||||||
|
@@ -137,7 +137,7 @@ export const DynamicSecretSqlDBSchema = z.object({
|
|||||||
revocationStatement: z.string().trim(),
|
revocationStatement: z.string().trim(),
|
||||||
renewStatement: z.string().trim().optional(),
|
renewStatement: z.string().trim().optional(),
|
||||||
ca: z.string().optional(),
|
ca: z.string().optional(),
|
||||||
projectGatewayId: z.string().nullable().optional()
|
gatewayId: z.string().nullable().optional()
|
||||||
});
|
});
|
||||||
|
|
||||||
export const DynamicSecretCassandraSchema = z.object({
|
export const DynamicSecretCassandraSchema = z.object({
|
||||||
|
@@ -112,14 +112,14 @@ const generateUsername = (provider: SqlProviders) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
type TSqlDatabaseProviderDTO = {
|
type TSqlDatabaseProviderDTO = {
|
||||||
gatewayService: Pick<TGatewayServiceFactory, "fnGetGatewayClientTls">;
|
gatewayService: Pick<TGatewayServiceFactory, "fnGetGatewayClientTlsByGatewayId">;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const SqlDatabaseProvider = ({ gatewayService }: TSqlDatabaseProviderDTO): TDynamicProviderFns => {
|
export const SqlDatabaseProvider = ({ gatewayService }: TSqlDatabaseProviderDTO): TDynamicProviderFns => {
|
||||||
const validateProviderInputs = async (inputs: unknown) => {
|
const validateProviderInputs = async (inputs: unknown) => {
|
||||||
const providerInputs = await DynamicSecretSqlDBSchema.parseAsync(inputs);
|
const providerInputs = await DynamicSecretSqlDBSchema.parseAsync(inputs);
|
||||||
|
|
||||||
const [hostIp] = await verifyHostInputValidity(providerInputs.host, Boolean(providerInputs.projectGatewayId));
|
const [hostIp] = await verifyHostInputValidity(providerInputs.host, Boolean(providerInputs.gatewayId));
|
||||||
validateHandlebarTemplate("SQL creation", providerInputs.creationStatement, {
|
validateHandlebarTemplate("SQL creation", providerInputs.creationStatement, {
|
||||||
allowedExpressions: (val) => ["username", "password", "expiration", "database"].includes(val)
|
allowedExpressions: (val) => ["username", "password", "expiration", "database"].includes(val)
|
||||||
});
|
});
|
||||||
@@ -168,7 +168,7 @@ export const SqlDatabaseProvider = ({ gatewayService }: TSqlDatabaseProviderDTO)
|
|||||||
providerInputs: z.infer<typeof DynamicSecretSqlDBSchema>,
|
providerInputs: z.infer<typeof DynamicSecretSqlDBSchema>,
|
||||||
gatewayCallback: (host: string, port: number) => Promise<void>
|
gatewayCallback: (host: string, port: number) => Promise<void>
|
||||||
) => {
|
) => {
|
||||||
const relayDetails = await gatewayService.fnGetGatewayClientTls(providerInputs.projectGatewayId as string);
|
const relayDetails = await gatewayService.fnGetGatewayClientTlsByGatewayId(providerInputs.gatewayId as string);
|
||||||
const [relayHost, relayPort] = relayDetails.relayAddress.split(":");
|
const [relayHost, relayPort] = relayDetails.relayAddress.split(":");
|
||||||
await withGatewayProxy(
|
await withGatewayProxy(
|
||||||
async (port) => {
|
async (port) => {
|
||||||
@@ -202,7 +202,7 @@ export const SqlDatabaseProvider = ({ gatewayService }: TSqlDatabaseProviderDTO)
|
|||||||
await db.destroy();
|
await db.destroy();
|
||||||
};
|
};
|
||||||
|
|
||||||
if (providerInputs.projectGatewayId) {
|
if (providerInputs.gatewayId) {
|
||||||
await gatewayProxyWrapper(providerInputs, gatewayCallback);
|
await gatewayProxyWrapper(providerInputs, gatewayCallback);
|
||||||
} else {
|
} else {
|
||||||
await gatewayCallback();
|
await gatewayCallback();
|
||||||
@@ -238,7 +238,7 @@ export const SqlDatabaseProvider = ({ gatewayService }: TSqlDatabaseProviderDTO)
|
|||||||
await db.destroy();
|
await db.destroy();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
if (providerInputs.projectGatewayId) {
|
if (providerInputs.gatewayId) {
|
||||||
await gatewayProxyWrapper(providerInputs, gatewayCallback);
|
await gatewayProxyWrapper(providerInputs, gatewayCallback);
|
||||||
} else {
|
} else {
|
||||||
await gatewayCallback();
|
await gatewayCallback();
|
||||||
@@ -265,7 +265,7 @@ export const SqlDatabaseProvider = ({ gatewayService }: TSqlDatabaseProviderDTO)
|
|||||||
await db.destroy();
|
await db.destroy();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
if (providerInputs.projectGatewayId) {
|
if (providerInputs.gatewayId) {
|
||||||
await gatewayProxyWrapper(providerInputs, gatewayCallback);
|
await gatewayProxyWrapper(providerInputs, gatewayCallback);
|
||||||
} else {
|
} else {
|
||||||
await gatewayCallback();
|
await gatewayCallback();
|
||||||
@@ -301,7 +301,7 @@ export const SqlDatabaseProvider = ({ gatewayService }: TSqlDatabaseProviderDTO)
|
|||||||
await db.destroy();
|
await db.destroy();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
if (providerInputs.projectGatewayId) {
|
if (providerInputs.gatewayId) {
|
||||||
await gatewayProxyWrapper(providerInputs, gatewayCallback);
|
await gatewayProxyWrapper(providerInputs, gatewayCallback);
|
||||||
} else {
|
} else {
|
||||||
await gatewayCallback();
|
await gatewayCallback();
|
||||||
|
@@ -1,37 +1,34 @@
|
|||||||
import { Knex } from "knex";
|
|
||||||
|
|
||||||
import { TDbClient } from "@app/db";
|
import { TDbClient } from "@app/db";
|
||||||
import { GatewaysSchema, TableName, TGateways } from "@app/db/schemas";
|
import { GatewaysSchema, TableName, TGateways } from "@app/db/schemas";
|
||||||
import { DatabaseError } from "@app/lib/errors";
|
import { DatabaseError } from "@app/lib/errors";
|
||||||
import {
|
import { buildFindFilter, ormify, selectAllTableCols, TFindFilter, TFindOpt } from "@app/lib/knex";
|
||||||
buildFindFilter,
|
|
||||||
ormify,
|
|
||||||
selectAllTableCols,
|
|
||||||
sqlNestRelationships,
|
|
||||||
TFindFilter,
|
|
||||||
TFindOpt
|
|
||||||
} from "@app/lib/knex";
|
|
||||||
|
|
||||||
export type TGatewayDALFactory = ReturnType<typeof gatewayDALFactory>;
|
export type TGatewayDALFactory = ReturnType<typeof gatewayDALFactory>;
|
||||||
|
|
||||||
export const gatewayDALFactory = (db: TDbClient) => {
|
export const gatewayDALFactory = (db: TDbClient) => {
|
||||||
const orm = ormify(db, TableName.Gateway);
|
const orm = ormify(db, TableName.Gateway);
|
||||||
|
|
||||||
const find = async (filter: TFindFilter<TGateways>, { offset, limit, sort, tx }: TFindOpt<TGateways> = {}) => {
|
const find = async (
|
||||||
|
filter: TFindFilter<TGateways> & { orgId?: string },
|
||||||
|
{ offset, limit, sort, tx }: TFindOpt<TGateways> = {}
|
||||||
|
) => {
|
||||||
try {
|
try {
|
||||||
const query = (tx || db)(TableName.Gateway)
|
const query = (tx || db)(TableName.Gateway)
|
||||||
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
||||||
.where(buildFindFilter(filter))
|
.where(buildFindFilter(filter, TableName.Gateway, ["orgId"]))
|
||||||
.join(TableName.Identity, `${TableName.Identity}.id`, `${TableName.Gateway}.identityId`)
|
.join(TableName.Identity, `${TableName.Identity}.id`, `${TableName.Gateway}.identityId`)
|
||||||
.leftJoin(TableName.ProjectGateway, `${TableName.ProjectGateway}.gatewayId`, `${TableName.Gateway}.id`)
|
.join(
|
||||||
.leftJoin(TableName.Project, `${TableName.Project}.id`, `${TableName.ProjectGateway}.projectId`)
|
TableName.IdentityOrgMembership,
|
||||||
|
`${TableName.IdentityOrgMembership}.identityId`,
|
||||||
|
`${TableName.Gateway}.identityId`
|
||||||
|
)
|
||||||
.select(selectAllTableCols(TableName.Gateway))
|
.select(selectAllTableCols(TableName.Gateway))
|
||||||
.select(
|
.select(db.ref("orgId").withSchema(TableName.IdentityOrgMembership).as("identityOrgId"))
|
||||||
db.ref("name").withSchema(TableName.Identity).as("identityName"),
|
.select(db.ref("name").withSchema(TableName.Identity).as("identityName"));
|
||||||
db.ref("name").withSchema(TableName.Project).as("projectName"),
|
|
||||||
db.ref("slug").withSchema(TableName.Project).as("projectSlug"),
|
if (filter.orgId) {
|
||||||
db.ref("id").withSchema(TableName.Project).as("projectId")
|
void query.where(`${TableName.IdentityOrgMembership}.orgId`, filter.orgId);
|
||||||
);
|
}
|
||||||
if (limit) void query.limit(limit);
|
if (limit) void query.limit(limit);
|
||||||
if (offset) void query.offset(offset);
|
if (offset) void query.offset(offset);
|
||||||
if (sort) {
|
if (sort) {
|
||||||
@@ -39,48 +36,16 @@ export const gatewayDALFactory = (db: TDbClient) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const docs = await query;
|
const docs = await query;
|
||||||
return sqlNestRelationships({
|
|
||||||
data: docs,
|
return docs.map((el) => ({
|
||||||
key: "id",
|
...GatewaysSchema.parse(el),
|
||||||
parentMapper: (data) => ({
|
orgId: el.identityOrgId as string, // todo(daniel): figure out why typescript is not inferring this as a string
|
||||||
...GatewaysSchema.parse(data),
|
identity: { id: el.identityId, name: el.identityName }
|
||||||
identity: { id: data.identityId, name: data.identityName }
|
}));
|
||||||
}),
|
|
||||||
childrenMapper: [
|
|
||||||
{
|
|
||||||
key: "projectId",
|
|
||||||
label: "projects" as const,
|
|
||||||
mapper: ({ projectId, projectName, projectSlug }) => ({
|
|
||||||
id: projectId,
|
|
||||||
name: projectName,
|
|
||||||
slug: projectSlug
|
|
||||||
})
|
|
||||||
}
|
|
||||||
]
|
|
||||||
});
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw new DatabaseError({ error, name: `${TableName.Gateway}: Find` });
|
throw new DatabaseError({ error, name: `${TableName.Gateway}: Find` });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const findByProjectId = async (projectId: string, tx?: Knex) => {
|
return { ...orm, find };
|
||||||
try {
|
|
||||||
const query = (tx || db)(TableName.Gateway)
|
|
||||||
.join(TableName.Identity, `${TableName.Identity}.id`, `${TableName.Gateway}.identityId`)
|
|
||||||
.join(TableName.ProjectGateway, `${TableName.ProjectGateway}.gatewayId`, `${TableName.Gateway}.id`)
|
|
||||||
.select(selectAllTableCols(TableName.Gateway))
|
|
||||||
.select(
|
|
||||||
db.ref("name").withSchema(TableName.Identity).as("identityName"),
|
|
||||||
db.ref("id").withSchema(TableName.ProjectGateway).as("projectGatewayId")
|
|
||||||
)
|
|
||||||
.where({ [`${TableName.ProjectGateway}.projectId` as "projectId"]: projectId });
|
|
||||||
|
|
||||||
const docs = await query;
|
|
||||||
return docs.map((el) => ({ ...el, identity: { id: el.identityId, name: el.identityName } }));
|
|
||||||
} catch (error) {
|
|
||||||
throw new DatabaseError({ error, name: `${TableName.Gateway}: Find by project id` });
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return { ...orm, find, findByProjectId };
|
|
||||||
};
|
};
|
||||||
|
@@ -4,7 +4,6 @@ import { ForbiddenError } from "@casl/ability";
|
|||||||
import * as x509 from "@peculiar/x509";
|
import * as x509 from "@peculiar/x509";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import { ActionProjectType } from "@app/db/schemas";
|
|
||||||
import { KeyStorePrefixes, PgSqlLock, TKeyStoreFactory } from "@app/keystore/keystore";
|
import { KeyStorePrefixes, PgSqlLock, TKeyStoreFactory } from "@app/keystore/keystore";
|
||||||
import { getConfig } from "@app/lib/config/env";
|
import { getConfig } from "@app/lib/config/env";
|
||||||
import { BadRequestError, NotFoundError } from "@app/lib/errors";
|
import { BadRequestError, NotFoundError } from "@app/lib/errors";
|
||||||
@@ -27,17 +26,14 @@ import { TGatewayDALFactory } from "./gateway-dal";
|
|||||||
import {
|
import {
|
||||||
TExchangeAllocatedRelayAddressDTO,
|
TExchangeAllocatedRelayAddressDTO,
|
||||||
TGetGatewayByIdDTO,
|
TGetGatewayByIdDTO,
|
||||||
TGetProjectGatewayByIdDTO,
|
|
||||||
THeartBeatDTO,
|
THeartBeatDTO,
|
||||||
TListGatewaysDTO,
|
TListGatewaysDTO,
|
||||||
TUpdateGatewayByIdDTO
|
TUpdateGatewayByIdDTO
|
||||||
} from "./gateway-types";
|
} from "./gateway-types";
|
||||||
import { TOrgGatewayConfigDALFactory } from "./org-gateway-config-dal";
|
import { TOrgGatewayConfigDALFactory } from "./org-gateway-config-dal";
|
||||||
import { TProjectGatewayDALFactory } from "./project-gateway-dal";
|
|
||||||
|
|
||||||
type TGatewayServiceFactoryDep = {
|
type TGatewayServiceFactoryDep = {
|
||||||
gatewayDAL: TGatewayDALFactory;
|
gatewayDAL: TGatewayDALFactory;
|
||||||
projectGatewayDAL: TProjectGatewayDALFactory;
|
|
||||||
orgGatewayConfigDAL: Pick<TOrgGatewayConfigDALFactory, "findOne" | "create" | "transaction" | "findById">;
|
orgGatewayConfigDAL: Pick<TOrgGatewayConfigDALFactory, "findOne" | "create" | "transaction" | "findById">;
|
||||||
licenseService: Pick<TLicenseServiceFactory, "onPremFeatures" | "getPlan">;
|
licenseService: Pick<TLicenseServiceFactory, "onPremFeatures" | "getPlan">;
|
||||||
kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey" | "decryptWithRootKey">;
|
kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey" | "decryptWithRootKey">;
|
||||||
@@ -57,8 +53,7 @@ export const gatewayServiceFactory = ({
|
|||||||
kmsService,
|
kmsService,
|
||||||
permissionService,
|
permissionService,
|
||||||
orgGatewayConfigDAL,
|
orgGatewayConfigDAL,
|
||||||
keyStore,
|
keyStore
|
||||||
projectGatewayDAL
|
|
||||||
}: TGatewayServiceFactoryDep) => {
|
}: TGatewayServiceFactoryDep) => {
|
||||||
const $validateOrgAccessToGateway = async (orgId: string, actorId: string, actorAuthMethod: ActorAuthMethod) => {
|
const $validateOrgAccessToGateway = async (orgId: string, actorId: string, actorAuthMethod: ActorAuthMethod) => {
|
||||||
// if (!licenseService.onPremFeatures.gateway) {
|
// if (!licenseService.onPremFeatures.gateway) {
|
||||||
@@ -526,7 +521,7 @@ export const gatewayServiceFactory = ({
|
|||||||
return gateway;
|
return gateway;
|
||||||
};
|
};
|
||||||
|
|
||||||
const updateGatewayById = async ({ orgPermission, id, name, projectIds }: TUpdateGatewayByIdDTO) => {
|
const updateGatewayById = async ({ orgPermission, id, name }: TUpdateGatewayByIdDTO) => {
|
||||||
const { permission } = await permissionService.getOrgPermission(
|
const { permission } = await permissionService.getOrgPermission(
|
||||||
orgPermission.type,
|
orgPermission.type,
|
||||||
orgPermission.id,
|
orgPermission.id,
|
||||||
@@ -543,15 +538,6 @@ export const gatewayServiceFactory = ({
|
|||||||
|
|
||||||
const [gateway] = await gatewayDAL.update({ id, orgGatewayRootCaId: orgGatewayConfig.id }, { name });
|
const [gateway] = await gatewayDAL.update({ id, orgGatewayRootCaId: orgGatewayConfig.id }, { name });
|
||||||
if (!gateway) throw new NotFoundError({ message: `Gateway with ID ${id} not found.` });
|
if (!gateway) throw new NotFoundError({ message: `Gateway with ID ${id} not found.` });
|
||||||
if (projectIds) {
|
|
||||||
await projectGatewayDAL.transaction(async (tx) => {
|
|
||||||
await projectGatewayDAL.delete({ gatewayId: gateway.id }, tx);
|
|
||||||
await projectGatewayDAL.insertMany(
|
|
||||||
projectIds.map((el) => ({ gatewayId: gateway.id, projectId: el })),
|
|
||||||
tx
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return gateway;
|
return gateway;
|
||||||
};
|
};
|
||||||
@@ -576,27 +562,7 @@ export const gatewayServiceFactory = ({
|
|||||||
return gateway;
|
return gateway;
|
||||||
};
|
};
|
||||||
|
|
||||||
const getProjectGateways = async ({ projectId, projectPermission }: TGetProjectGatewayByIdDTO) => {
|
const fnGetGatewayClientTlsByGatewayId = async (gatewayId: string) => {
|
||||||
await permissionService.getProjectPermission({
|
|
||||||
projectId,
|
|
||||||
actor: projectPermission.type,
|
|
||||||
actorId: projectPermission.id,
|
|
||||||
actorOrgId: projectPermission.orgId,
|
|
||||||
actorAuthMethod: projectPermission.authMethod,
|
|
||||||
actionProjectType: ActionProjectType.Any
|
|
||||||
});
|
|
||||||
|
|
||||||
const gateways = await gatewayDAL.findByProjectId(projectId);
|
|
||||||
return gateways;
|
|
||||||
};
|
|
||||||
|
|
||||||
// this has no permission check and used for dynamic secrets directly
|
|
||||||
// assumes permission check is already done
|
|
||||||
const fnGetGatewayClientTls = async (projectGatewayId: string) => {
|
|
||||||
const projectGateway = await projectGatewayDAL.findById(projectGatewayId);
|
|
||||||
if (!projectGateway) throw new NotFoundError({ message: `Project gateway with ID ${projectGatewayId} not found.` });
|
|
||||||
|
|
||||||
const { gatewayId } = projectGateway;
|
|
||||||
const gateway = await gatewayDAL.findById(gatewayId);
|
const gateway = await gatewayDAL.findById(gatewayId);
|
||||||
if (!gateway) throw new NotFoundError({ message: `Gateway with ID ${gatewayId} not found.` });
|
if (!gateway) throw new NotFoundError({ message: `Gateway with ID ${gatewayId} not found.` });
|
||||||
|
|
||||||
@@ -645,8 +611,7 @@ export const gatewayServiceFactory = ({
|
|||||||
getGatewayById,
|
getGatewayById,
|
||||||
updateGatewayById,
|
updateGatewayById,
|
||||||
deleteGatewayById,
|
deleteGatewayById,
|
||||||
getProjectGateways,
|
fnGetGatewayClientTlsByGatewayId,
|
||||||
fnGetGatewayClientTls,
|
|
||||||
heartbeat
|
heartbeat
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@@ -20,7 +20,6 @@ export type TGetGatewayByIdDTO = {
|
|||||||
export type TUpdateGatewayByIdDTO = {
|
export type TUpdateGatewayByIdDTO = {
|
||||||
id: string;
|
id: string;
|
||||||
name?: string;
|
name?: string;
|
||||||
projectIds?: string[];
|
|
||||||
orgPermission: OrgServiceActor;
|
orgPermission: OrgServiceActor;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@@ -1,10 +0,0 @@
|
|||||||
import { TDbClient } from "@app/db";
|
|
||||||
import { TableName } from "@app/db/schemas";
|
|
||||||
import { ormify } from "@app/lib/knex";
|
|
||||||
|
|
||||||
export type TProjectGatewayDALFactory = ReturnType<typeof projectGatewayDALFactory>;
|
|
||||||
|
|
||||||
export const projectGatewayDALFactory = (db: TDbClient) => {
|
|
||||||
const orm = ormify(db, TableName.ProjectGateway);
|
|
||||||
return orm;
|
|
||||||
};
|
|
@@ -714,13 +714,15 @@ export const oidcConfigServiceFactory = ({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const groups = typeof claims.groups === "string" ? [claims.groups] : (claims.groups as string[] | undefined);
|
||||||
|
|
||||||
oidcLogin({
|
oidcLogin({
|
||||||
email: claims.email,
|
email: claims.email,
|
||||||
externalId: claims.sub,
|
externalId: claims.sub,
|
||||||
firstName: claims.given_name ?? "",
|
firstName: claims.given_name ?? "",
|
||||||
lastName: claims.family_name ?? "",
|
lastName: claims.family_name ?? "",
|
||||||
orgId: org.id,
|
orgId: org.id,
|
||||||
groups: claims.groups as string[] | undefined,
|
groups,
|
||||||
callbackPort,
|
callbackPort,
|
||||||
manageGroupMemberships: oidcCfg.manageGroupMemberships
|
manageGroupMemberships: oidcCfg.manageGroupMemberships
|
||||||
})
|
})
|
||||||
|
@@ -9,6 +9,7 @@ import {
|
|||||||
ProjectPermissionIdentityActions,
|
ProjectPermissionIdentityActions,
|
||||||
ProjectPermissionKmipActions,
|
ProjectPermissionKmipActions,
|
||||||
ProjectPermissionMemberActions,
|
ProjectPermissionMemberActions,
|
||||||
|
ProjectPermissionPkiSubscriberActions,
|
||||||
ProjectPermissionSecretActions,
|
ProjectPermissionSecretActions,
|
||||||
ProjectPermissionSecretRotationActions,
|
ProjectPermissionSecretRotationActions,
|
||||||
ProjectPermissionSecretSyncActions,
|
ProjectPermissionSecretSyncActions,
|
||||||
@@ -76,6 +77,18 @@ const buildAdminPermissionRules = () => {
|
|||||||
ProjectPermissionSub.SshHosts
|
ProjectPermissionSub.SshHosts
|
||||||
);
|
);
|
||||||
|
|
||||||
|
can(
|
||||||
|
[
|
||||||
|
ProjectPermissionPkiSubscriberActions.Edit,
|
||||||
|
ProjectPermissionPkiSubscriberActions.Read,
|
||||||
|
ProjectPermissionPkiSubscriberActions.Create,
|
||||||
|
ProjectPermissionPkiSubscriberActions.Delete,
|
||||||
|
ProjectPermissionPkiSubscriberActions.IssueCert,
|
||||||
|
ProjectPermissionPkiSubscriberActions.ListCerts
|
||||||
|
],
|
||||||
|
ProjectPermissionSub.PkiSubscribers
|
||||||
|
);
|
||||||
|
|
||||||
can(
|
can(
|
||||||
[
|
[
|
||||||
ProjectPermissionMemberActions.Create,
|
ProjectPermissionMemberActions.Create,
|
||||||
@@ -113,7 +126,6 @@ const buildAdminPermissionRules = () => {
|
|||||||
|
|
||||||
can(
|
can(
|
||||||
[
|
[
|
||||||
ProjectPermissionSecretActions.DescribeAndReadValue,
|
|
||||||
ProjectPermissionSecretActions.DescribeSecret,
|
ProjectPermissionSecretActions.DescribeSecret,
|
||||||
ProjectPermissionSecretActions.ReadValue,
|
ProjectPermissionSecretActions.ReadValue,
|
||||||
ProjectPermissionSecretActions.Create,
|
ProjectPermissionSecretActions.Create,
|
||||||
@@ -194,7 +206,6 @@ const buildMemberPermissionRules = () => {
|
|||||||
|
|
||||||
can(
|
can(
|
||||||
[
|
[
|
||||||
ProjectPermissionSecretActions.DescribeAndReadValue,
|
|
||||||
ProjectPermissionSecretActions.DescribeSecret,
|
ProjectPermissionSecretActions.DescribeSecret,
|
||||||
ProjectPermissionSecretActions.ReadValue,
|
ProjectPermissionSecretActions.ReadValue,
|
||||||
ProjectPermissionSecretActions.Edit,
|
ProjectPermissionSecretActions.Edit,
|
||||||
@@ -338,6 +349,7 @@ const buildMemberPermissionRules = () => {
|
|||||||
can([ProjectPermissionActions.Read], ProjectPermissionSub.SshCertificateTemplates);
|
can([ProjectPermissionActions.Read], ProjectPermissionSub.SshCertificateTemplates);
|
||||||
|
|
||||||
can([ProjectPermissionSshHostActions.Read], ProjectPermissionSub.SshHosts);
|
can([ProjectPermissionSshHostActions.Read], ProjectPermissionSub.SshHosts);
|
||||||
|
can([ProjectPermissionPkiSubscriberActions.Read], ProjectPermissionSub.PkiSubscribers);
|
||||||
|
|
||||||
can(
|
can(
|
||||||
[
|
[
|
||||||
@@ -372,9 +384,10 @@ const buildMemberPermissionRules = () => {
|
|||||||
const buildViewerPermissionRules = () => {
|
const buildViewerPermissionRules = () => {
|
||||||
const { can, rules } = new AbilityBuilder<MongoAbility<ProjectPermissionSet>>(createMongoAbility);
|
const { can, rules } = new AbilityBuilder<MongoAbility<ProjectPermissionSet>>(createMongoAbility);
|
||||||
|
|
||||||
can(ProjectPermissionSecretActions.DescribeAndReadValue, ProjectPermissionSub.Secrets);
|
can(
|
||||||
can(ProjectPermissionSecretActions.DescribeSecret, ProjectPermissionSub.Secrets);
|
[ProjectPermissionSecretActions.DescribeSecret, ProjectPermissionSecretActions.ReadValue],
|
||||||
can(ProjectPermissionSecretActions.ReadValue, ProjectPermissionSub.Secrets);
|
ProjectPermissionSub.Secrets
|
||||||
|
);
|
||||||
can(ProjectPermissionActions.Read, ProjectPermissionSub.SecretFolders);
|
can(ProjectPermissionActions.Read, ProjectPermissionSub.SecretFolders);
|
||||||
can(ProjectPermissionDynamicSecretActions.ReadRootCredential, ProjectPermissionSub.DynamicSecrets);
|
can(ProjectPermissionDynamicSecretActions.ReadRootCredential, ProjectPermissionSub.DynamicSecrets);
|
||||||
can(ProjectPermissionActions.Read, ProjectPermissionSub.SecretImports);
|
can(ProjectPermissionActions.Read, ProjectPermissionSub.SecretImports);
|
||||||
|
@@ -41,7 +41,8 @@ export enum OrgPermissionGatewayActions {
|
|||||||
CreateGateways = "create-gateways",
|
CreateGateways = "create-gateways",
|
||||||
ListGateways = "list-gateways",
|
ListGateways = "list-gateways",
|
||||||
EditGateways = "edit-gateways",
|
EditGateways = "edit-gateways",
|
||||||
DeleteGateways = "delete-gateways"
|
DeleteGateways = "delete-gateways",
|
||||||
|
AttachGateways = "attach-gateways"
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum OrgPermissionIdentityActions {
|
export enum OrgPermissionIdentityActions {
|
||||||
@@ -337,6 +338,7 @@ const buildAdminPermission = () => {
|
|||||||
can(OrgPermissionGatewayActions.CreateGateways, OrgPermissionSubjects.Gateway);
|
can(OrgPermissionGatewayActions.CreateGateways, OrgPermissionSubjects.Gateway);
|
||||||
can(OrgPermissionGatewayActions.EditGateways, OrgPermissionSubjects.Gateway);
|
can(OrgPermissionGatewayActions.EditGateways, OrgPermissionSubjects.Gateway);
|
||||||
can(OrgPermissionGatewayActions.DeleteGateways, OrgPermissionSubjects.Gateway);
|
can(OrgPermissionGatewayActions.DeleteGateways, OrgPermissionSubjects.Gateway);
|
||||||
|
can(OrgPermissionGatewayActions.AttachGateways, OrgPermissionSubjects.Gateway);
|
||||||
|
|
||||||
can(OrgPermissionAdminConsoleAction.AccessAllProjects, OrgPermissionSubjects.AdminConsole);
|
can(OrgPermissionAdminConsoleAction.AccessAllProjects, OrgPermissionSubjects.AdminConsole);
|
||||||
|
|
||||||
@@ -378,6 +380,7 @@ const buildMemberPermission = () => {
|
|||||||
can(OrgPermissionAppConnectionActions.Connect, OrgPermissionSubjects.AppConnections);
|
can(OrgPermissionAppConnectionActions.Connect, OrgPermissionSubjects.AppConnections);
|
||||||
can(OrgPermissionGatewayActions.ListGateways, OrgPermissionSubjects.Gateway);
|
can(OrgPermissionGatewayActions.ListGateways, OrgPermissionSubjects.Gateway);
|
||||||
can(OrgPermissionGatewayActions.CreateGateways, OrgPermissionSubjects.Gateway);
|
can(OrgPermissionGatewayActions.CreateGateways, OrgPermissionSubjects.Gateway);
|
||||||
|
can(OrgPermissionGatewayActions.AttachGateways, OrgPermissionSubjects.Gateway);
|
||||||
|
|
||||||
return rules;
|
return rules;
|
||||||
};
|
};
|
||||||
|
@@ -87,6 +87,15 @@ export enum ProjectPermissionSshHostActions {
|
|||||||
IssueHostCert = "issue-host-cert"
|
IssueHostCert = "issue-host-cert"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum ProjectPermissionPkiSubscriberActions {
|
||||||
|
Read = "read",
|
||||||
|
Create = "create",
|
||||||
|
Edit = "edit",
|
||||||
|
Delete = "delete",
|
||||||
|
IssueCert = "issue-cert",
|
||||||
|
ListCerts = "list-certs"
|
||||||
|
}
|
||||||
|
|
||||||
export enum ProjectPermissionSecretSyncActions {
|
export enum ProjectPermissionSecretSyncActions {
|
||||||
Read = "read",
|
Read = "read",
|
||||||
Create = "create",
|
Create = "create",
|
||||||
@@ -143,6 +152,7 @@ export enum ProjectPermissionSub {
|
|||||||
SshCertificateTemplates = "ssh-certificate-templates",
|
SshCertificateTemplates = "ssh-certificate-templates",
|
||||||
SshHosts = "ssh-hosts",
|
SshHosts = "ssh-hosts",
|
||||||
SshHostGroups = "ssh-host-groups",
|
SshHostGroups = "ssh-host-groups",
|
||||||
|
PkiSubscribers = "pki-subscribers",
|
||||||
PkiAlerts = "pki-alerts",
|
PkiAlerts = "pki-alerts",
|
||||||
PkiCollections = "pki-collections",
|
PkiCollections = "pki-collections",
|
||||||
Kms = "kms",
|
Kms = "kms",
|
||||||
@@ -190,6 +200,11 @@ export type SshHostSubjectFields = {
|
|||||||
hostname: string;
|
hostname: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type PkiSubscriberSubjectFields = {
|
||||||
|
name: string;
|
||||||
|
// (dangtony98): consider adding [commonName] as a subject field in the future
|
||||||
|
};
|
||||||
|
|
||||||
export type ProjectPermissionSet =
|
export type ProjectPermissionSet =
|
||||||
| [
|
| [
|
||||||
ProjectPermissionSecretActions,
|
ProjectPermissionSecretActions,
|
||||||
@@ -249,6 +264,13 @@ export type ProjectPermissionSet =
|
|||||||
ProjectPermissionSshHostActions,
|
ProjectPermissionSshHostActions,
|
||||||
ProjectPermissionSub.SshHosts | (ForcedSubject<ProjectPermissionSub.SshHosts> & SshHostSubjectFields)
|
ProjectPermissionSub.SshHosts | (ForcedSubject<ProjectPermissionSub.SshHosts> & SshHostSubjectFields)
|
||||||
]
|
]
|
||||||
|
| [
|
||||||
|
ProjectPermissionPkiSubscriberActions,
|
||||||
|
(
|
||||||
|
| ProjectPermissionSub.PkiSubscribers
|
||||||
|
| (ForcedSubject<ProjectPermissionSub.PkiSubscribers> & PkiSubscriberSubjectFields)
|
||||||
|
)
|
||||||
|
]
|
||||||
| [ProjectPermissionActions, ProjectPermissionSub.SshHostGroups]
|
| [ProjectPermissionActions, ProjectPermissionSub.SshHostGroups]
|
||||||
| [ProjectPermissionActions, ProjectPermissionSub.PkiAlerts]
|
| [ProjectPermissionActions, ProjectPermissionSub.PkiAlerts]
|
||||||
| [ProjectPermissionActions, ProjectPermissionSub.PkiCollections]
|
| [ProjectPermissionActions, ProjectPermissionSub.PkiCollections]
|
||||||
@@ -399,6 +421,21 @@ const SshHostConditionSchema = z
|
|||||||
})
|
})
|
||||||
.partial();
|
.partial();
|
||||||
|
|
||||||
|
const PkiSubscriberConditionSchema = z
|
||||||
|
.object({
|
||||||
|
name: z.union([
|
||||||
|
z.string(),
|
||||||
|
z
|
||||||
|
.object({
|
||||||
|
[PermissionConditionOperators.$EQ]: PermissionConditionSchema[PermissionConditionOperators.$EQ],
|
||||||
|
[PermissionConditionOperators.$GLOB]: PermissionConditionSchema[PermissionConditionOperators.$GLOB],
|
||||||
|
[PermissionConditionOperators.$IN]: PermissionConditionSchema[PermissionConditionOperators.$IN]
|
||||||
|
})
|
||||||
|
.partial()
|
||||||
|
])
|
||||||
|
})
|
||||||
|
.partial();
|
||||||
|
|
||||||
const GeneralPermissionSchema = [
|
const GeneralPermissionSchema = [
|
||||||
z.object({
|
z.object({
|
||||||
subject: z.literal(ProjectPermissionSub.SecretApproval).describe("The entity this permission pertains to."),
|
subject: z.literal(ProjectPermissionSub.SecretApproval).describe("The entity this permission pertains to."),
|
||||||
@@ -663,6 +700,16 @@ export const ProjectPermissionV2Schema = z.discriminatedUnion("subject", [
|
|||||||
"When specified, only matching conditions will be allowed to access given resource."
|
"When specified, only matching conditions will be allowed to access given resource."
|
||||||
).optional()
|
).optional()
|
||||||
}),
|
}),
|
||||||
|
z.object({
|
||||||
|
subject: z.literal(ProjectPermissionSub.PkiSubscribers).describe("The entity this permission pertains to."),
|
||||||
|
action: CASL_ACTION_SCHEMA_NATIVE_ENUM(ProjectPermissionPkiSubscriberActions).describe(
|
||||||
|
"Describe what action an entity can take."
|
||||||
|
),
|
||||||
|
inverted: z.boolean().optional().describe("Whether rule allows or forbids."),
|
||||||
|
conditions: PkiSubscriberConditionSchema.describe(
|
||||||
|
"When specified, only matching conditions will be allowed to access given resource."
|
||||||
|
).optional()
|
||||||
|
}),
|
||||||
z.object({
|
z.object({
|
||||||
subject: z.literal(ProjectPermissionSub.SecretRotation).describe("The entity this permission pertains to."),
|
subject: z.literal(ProjectPermissionSub.SecretRotation).describe("The entity this permission pertains to."),
|
||||||
inverted: z.boolean().optional().describe("Whether rule allows or forbids."),
|
inverted: z.boolean().optional().describe("Whether rule allows or forbids."),
|
||||||
|
@@ -186,13 +186,42 @@ export const sshHostGroupServiceFactory = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const updatedSshHostGroup = await sshHostGroupDAL.transaction(async (tx) => {
|
const updatedSshHostGroup = await sshHostGroupDAL.transaction(async (tx) => {
|
||||||
await sshHostGroupDAL.updateById(
|
if (name && name !== sshHostGroup.name) {
|
||||||
sshHostGroupId,
|
// (dangtony98): room to optimize check to ensure that
|
||||||
{
|
// the SSH host group name is unique across the whole org
|
||||||
name
|
const project = await projectDAL.findById(sshHostGroup.projectId, tx);
|
||||||
},
|
if (!project) throw new NotFoundError({ message: `Project with ID '${sshHostGroup.projectId}' not found` });
|
||||||
tx
|
const projects = await projectDAL.find(
|
||||||
);
|
{
|
||||||
|
orgId: project.orgId
|
||||||
|
},
|
||||||
|
{ tx }
|
||||||
|
);
|
||||||
|
|
||||||
|
const existingSshHostGroup = await sshHostGroupDAL.find(
|
||||||
|
{
|
||||||
|
name,
|
||||||
|
$in: {
|
||||||
|
projectId: projects.map((p) => p.id)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ tx }
|
||||||
|
);
|
||||||
|
|
||||||
|
if (existingSshHostGroup.length) {
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: `SSH host group with name '${name}' already exists in the organization`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
await sshHostGroupDAL.updateById(
|
||||||
|
sshHostGroupId,
|
||||||
|
{
|
||||||
|
name
|
||||||
|
},
|
||||||
|
tx
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (loginMappings) {
|
if (loginMappings) {
|
||||||
await sshHostLoginUserDAL.delete({ sshHostGroupId: sshHostGroup.id }, tx);
|
await sshHostLoginUserDAL.delete({ sshHostGroupId: sshHostGroup.id }, tx);
|
||||||
if (loginMappings.length) {
|
if (loginMappings.length) {
|
||||||
|
@@ -335,7 +335,7 @@ export const sshHostServiceFactory = ({
|
|||||||
return host;
|
return host;
|
||||||
};
|
};
|
||||||
|
|
||||||
const getSshHost = async ({ sshHostId, actorId, actorAuthMethod, actor, actorOrgId }: TGetSshHostDTO) => {
|
const getSshHostById = async ({ sshHostId, actorId, actorAuthMethod, actor, actorOrgId }: TGetSshHostDTO) => {
|
||||||
const host = await sshHostDAL.findSshHostByIdWithLoginMappings(sshHostId);
|
const host = await sshHostDAL.findSshHostByIdWithLoginMappings(sshHostId);
|
||||||
if (!host) {
|
if (!host) {
|
||||||
throw new NotFoundError({
|
throw new NotFoundError({
|
||||||
@@ -631,7 +631,7 @@ export const sshHostServiceFactory = ({
|
|||||||
createSshHost,
|
createSshHost,
|
||||||
updateSshHost,
|
updateSshHost,
|
||||||
deleteSshHost,
|
deleteSshHost,
|
||||||
getSshHost,
|
getSshHostById,
|
||||||
issueSshHostUserCert,
|
issueSshHostUserCert,
|
||||||
issueSshHostHostCert,
|
issueSshHostHostCert,
|
||||||
getSshHostUserCaPk,
|
getSshHostUserCaPk,
|
||||||
|
@@ -14,6 +14,7 @@ export enum ApiDocsTags {
|
|||||||
UniversalAuth = "Universal Auth",
|
UniversalAuth = "Universal Auth",
|
||||||
GcpAuth = "GCP Auth",
|
GcpAuth = "GCP Auth",
|
||||||
AwsAuth = "AWS Auth",
|
AwsAuth = "AWS Auth",
|
||||||
|
OciAuth = "OCI Auth",
|
||||||
AzureAuth = "Azure Auth",
|
AzureAuth = "Azure Auth",
|
||||||
KubernetesAuth = "Kubernetes Auth",
|
KubernetesAuth = "Kubernetes Auth",
|
||||||
JwtAuth = "JWT Auth",
|
JwtAuth = "JWT Auth",
|
||||||
@@ -46,6 +47,7 @@ export enum ApiDocsTags {
|
|||||||
PkiCertificateTemplates = "PKI Certificate Templates",
|
PkiCertificateTemplates = "PKI Certificate Templates",
|
||||||
PkiCertificateCollections = "PKI Certificate Collections",
|
PkiCertificateCollections = "PKI Certificate Collections",
|
||||||
PkiAlerting = "PKI Alerting",
|
PkiAlerting = "PKI Alerting",
|
||||||
|
PkiSubscribers = "PKI Subscribers",
|
||||||
SshCertificates = "SSH Certificates",
|
SshCertificates = "SSH Certificates",
|
||||||
SshCertificateAuthorities = "SSH Certificate Authorities",
|
SshCertificateAuthorities = "SSH Certificate Authorities",
|
||||||
SshCertificateTemplates = "SSH Certificate Templates",
|
SshCertificateTemplates = "SSH Certificate Templates",
|
||||||
@@ -270,6 +272,40 @@ export const AWS_AUTH = {
|
|||||||
}
|
}
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
|
export const OCI_AUTH = {
|
||||||
|
LOGIN: {
|
||||||
|
identityId: "The ID of the identity to login.",
|
||||||
|
userOcid: "The OCID of the user attempting login.",
|
||||||
|
headers: "The headers of the signed request."
|
||||||
|
},
|
||||||
|
ATTACH: {
|
||||||
|
identityId: "The ID of the identity to attach the configuration onto.",
|
||||||
|
tenancyOcid: "The OCID of your tenancy.",
|
||||||
|
allowedUsernames:
|
||||||
|
"The comma-separated list of trusted OCI account usernames that are allowed to authenticate with Infisical.",
|
||||||
|
accessTokenTTL: "The lifetime for an access token in seconds.",
|
||||||
|
accessTokenMaxTTL: "The maximum lifetime for an access token in seconds.",
|
||||||
|
accessTokenNumUsesLimit: "The maximum number of times that an access token can be used.",
|
||||||
|
accessTokenTrustedIps: "The IPs or CIDR ranges that access tokens can be used from."
|
||||||
|
},
|
||||||
|
UPDATE: {
|
||||||
|
identityId: "The ID of the identity to update the auth method for.",
|
||||||
|
tenancyOcid: "The OCID of your tenancy.",
|
||||||
|
allowedUsernames:
|
||||||
|
"The comma-separated list of trusted OCI account usernames that are allowed to authenticate with Infisical.",
|
||||||
|
accessTokenTTL: "The new lifetime for an access token in seconds.",
|
||||||
|
accessTokenMaxTTL: "The new maximum lifetime for an access token in seconds.",
|
||||||
|
accessTokenNumUsesLimit: "The new maximum number of times that an access token can be used.",
|
||||||
|
accessTokenTrustedIps: "The new IPs or CIDR ranges that access tokens can be used from."
|
||||||
|
},
|
||||||
|
RETRIEVE: {
|
||||||
|
identityId: "The ID of the identity to retrieve the auth method for."
|
||||||
|
},
|
||||||
|
REVOKE: {
|
||||||
|
identityId: "The ID of the identity to revoke the auth method for."
|
||||||
|
}
|
||||||
|
} as const;
|
||||||
|
|
||||||
export const AZURE_AUTH = {
|
export const AZURE_AUTH = {
|
||||||
LOGIN: {
|
LOGIN: {
|
||||||
identityId: "The ID of the identity to login."
|
identityId: "The ID of the identity to login."
|
||||||
@@ -357,6 +393,7 @@ export const KUBERNETES_AUTH = {
|
|||||||
allowedNames: "The comma-separated list of trusted service account names that can authenticate with Infisical.",
|
allowedNames: "The comma-separated list of trusted service account names that can authenticate with Infisical.",
|
||||||
allowedAudience:
|
allowedAudience:
|
||||||
"The optional audience claim that the service account JWT token must have to authenticate with Infisical.",
|
"The optional audience claim that the service account JWT token must have to authenticate with Infisical.",
|
||||||
|
gatewayId: "The ID of the gateway to use when performing kubernetes API requests.",
|
||||||
accessTokenTrustedIps: "The IPs or CIDR ranges that access tokens can be used from.",
|
accessTokenTrustedIps: "The IPs or CIDR ranges that access tokens can be used from.",
|
||||||
accessTokenTTL: "The lifetime for an access token in seconds.",
|
accessTokenTTL: "The lifetime for an access token in seconds.",
|
||||||
accessTokenMaxTTL: "The maximum lifetime for an access token in seconds.",
|
accessTokenMaxTTL: "The maximum lifetime for an access token in seconds.",
|
||||||
@@ -373,6 +410,7 @@ export const KUBERNETES_AUTH = {
|
|||||||
allowedNames: "The new comma-separated list of trusted service account names that can authenticate with Infisical.",
|
allowedNames: "The new comma-separated list of trusted service account names that can authenticate with Infisical.",
|
||||||
allowedAudience:
|
allowedAudience:
|
||||||
"The new optional audience claim that the service account JWT token must have to authenticate with Infisical.",
|
"The new optional audience claim that the service account JWT token must have to authenticate with Infisical.",
|
||||||
|
gatewayId: "The ID of the gateway to use when performing kubernetes API requests.",
|
||||||
accessTokenTrustedIps: "The new IPs or CIDR ranges that access tokens can be used from.",
|
accessTokenTrustedIps: "The new IPs or CIDR ranges that access tokens can be used from.",
|
||||||
accessTokenTTL: "The new lifetime for an acccess token in seconds.",
|
accessTokenTTL: "The new lifetime for an acccess token in seconds.",
|
||||||
accessTokenMaxTTL: "The new maximum lifetime for an acccess token in seconds.",
|
accessTokenMaxTTL: "The new maximum lifetime for an acccess token in seconds.",
|
||||||
@@ -570,7 +608,8 @@ export const PROJECTS = {
|
|||||||
projectDescription: "An optional description label for the project.",
|
projectDescription: "An optional description label for the project.",
|
||||||
autoCapitalization: "Disable or enable auto-capitalization for the project.",
|
autoCapitalization: "Disable or enable auto-capitalization for the project.",
|
||||||
slug: "An optional slug for the project. (must be unique within the organization)",
|
slug: "An optional slug for the project. (must be unique within the organization)",
|
||||||
hasDeleteProtection: "Enable or disable delete protection for the project."
|
hasDeleteProtection: "Enable or disable delete protection for the project.",
|
||||||
|
secretSharing: "Enable or disable secret sharing for the project."
|
||||||
},
|
},
|
||||||
GET_KEY: {
|
GET_KEY: {
|
||||||
workspaceId: "The ID of the project to get the key from."
|
workspaceId: "The ID of the project to get the key from."
|
||||||
@@ -639,6 +678,9 @@ export const PROJECTS = {
|
|||||||
commonName: "The common name of the certificate to filter by.",
|
commonName: "The common name of the certificate to filter by.",
|
||||||
offset: "The offset to start from. If you enter 10, it will start from the 10th certificate.",
|
offset: "The offset to start from. If you enter 10, it will start from the 10th certificate.",
|
||||||
limit: "The number of certificates to return."
|
limit: "The number of certificates to return."
|
||||||
|
},
|
||||||
|
LIST_PKI_SUBSCRIBERS: {
|
||||||
|
projectId: "The ID of the project to list PKI subscribers for."
|
||||||
}
|
}
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
@@ -1731,6 +1773,67 @@ export const ALERTS = {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const PKI_SUBSCRIBERS = {
|
||||||
|
GET: {
|
||||||
|
subscriberName: "The name of the PKI subscriber to get.",
|
||||||
|
projectId: "The ID of the project to get the PKI subscriber for."
|
||||||
|
},
|
||||||
|
CREATE: {
|
||||||
|
projectId: "The ID of the project to create the PKI subscriber in.",
|
||||||
|
caId: "The ID of the CA that will issue certificates for the PKI subscriber.",
|
||||||
|
name: "The name of the PKI subscriber.",
|
||||||
|
commonName: "The common name (CN) to be used on certificates issued for this subscriber.",
|
||||||
|
status: "The status of the PKI subscriber. This can be one of active or disabled.",
|
||||||
|
ttl: "The time to live for the certificates issued for this subscriber such as 1m, 1h, 1d, 1y, ...",
|
||||||
|
subjectAlternativeNames:
|
||||||
|
"A list of Subject Alternative Names (SANs) to be used on certificates issued for this subscriber; these can be host names or email addresses.",
|
||||||
|
keyUsages: "The key usage extension to be used on certificates issued for this subscriber.",
|
||||||
|
extendedKeyUsages: "The extended key usage extension to be used on certificates issued for this subscriber."
|
||||||
|
},
|
||||||
|
UPDATE: {
|
||||||
|
projectId: "The ID of the project to update the PKI subscriber in.",
|
||||||
|
subscriberName: "The name of the PKI subscriber to update.",
|
||||||
|
caId: "The ID of the CA that will issue certificates for the PKI subscriber to update to.",
|
||||||
|
name: "The name of the PKI subscriber to update to.",
|
||||||
|
commonName: "The common name (CN) to be used on certificates issued for this subscriber to update to.",
|
||||||
|
status: "The status of the PKI subscriber to update to. This can be one of active or disabled.",
|
||||||
|
ttl: "The time to live for the certificates issued for this subscriber such as 1m, 1h, 1d, 1y, ...",
|
||||||
|
subjectAlternativeNames:
|
||||||
|
"A comma-delimited list of Subject Alternative Names (SANs) to be used on certificates issued for this subscriber; these can be host names or email addresses.",
|
||||||
|
keyUsages: "The key usage extension to be used on certificates issued for this subscriber to update to.",
|
||||||
|
extendedKeyUsages:
|
||||||
|
"The extended key usage extension to be used on certificates issued for this subscriber to update to."
|
||||||
|
},
|
||||||
|
DELETE: {
|
||||||
|
subscriberName: "The name of the PKI subscriber to delete.",
|
||||||
|
projectId: "The ID of the project of the PKI subscriber to delete."
|
||||||
|
},
|
||||||
|
ISSUE_CERT: {
|
||||||
|
subscriberName: "The name of the PKI subscriber to issue the certificate for.",
|
||||||
|
projectId: "The ID of the project of the PKI subscriber to issue the certificate for.",
|
||||||
|
certificate: "The issued certificate.",
|
||||||
|
issuingCaCertificate: "The certificate of the issuing CA.",
|
||||||
|
certificateChain: "The certificate chain of the issued certificate.",
|
||||||
|
privateKey: "The private key of the issued certificate.",
|
||||||
|
serialNumber: "The serial number of the issued certificate."
|
||||||
|
},
|
||||||
|
SIGN_CERT: {
|
||||||
|
subscriberName: "The name of the PKI subscriber to sign the certificate for.",
|
||||||
|
projectId: "The ID of the project of the PKI subscriber to sign the certificate for.",
|
||||||
|
csr: "The CSR to be used to sign the certificate.",
|
||||||
|
certificate: "The signed certificate.",
|
||||||
|
issuingCaCertificate: "The certificate of the issuing CA.",
|
||||||
|
certificateChain: "The certificate chain of the signed certificate.",
|
||||||
|
serialNumber: "The serial number of the signed certificate."
|
||||||
|
},
|
||||||
|
LIST_CERTS: {
|
||||||
|
subscriberName: "The name of the PKI subscriber to list the certificates for.",
|
||||||
|
projectId: "The ID of the project of the PKI subscriber to list the certificates for.",
|
||||||
|
offset: "The offset to start from.",
|
||||||
|
limit: "The number of certificates to return."
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
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.",
|
||||||
@@ -1974,6 +2077,13 @@ export const AppConnections = {
|
|||||||
AZURE_CLIENT_SECRETS: {
|
AZURE_CLIENT_SECRETS: {
|
||||||
code: "The OAuth code to use to connect with Azure Client Secrets.",
|
code: "The OAuth code to use to connect with Azure Client Secrets.",
|
||||||
tenantId: "The Tenant ID to use to connect with Azure Client Secrets."
|
tenantId: "The Tenant ID to use to connect with Azure Client Secrets."
|
||||||
|
},
|
||||||
|
OCI: {
|
||||||
|
userOcid: "The OCID (Oracle Cloud Identifier) of the user making the request.",
|
||||||
|
tenancyOcid: "The OCID (Oracle Cloud Identifier) of the tenancy in Oracle Cloud Infrastructure.",
|
||||||
|
region: "The region identifier in Oracle Cloud Infrastructure where the vault is located.",
|
||||||
|
fingerprint: "The fingerprint of the public key uploaded to the user's API keys.",
|
||||||
|
privateKey: "The private key content in PEM format used to sign API requests."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -2037,6 +2147,7 @@ export const SecretSyncs = {
|
|||||||
const destinationName = SECRET_SYNC_NAME_MAP[destination];
|
const destinationName = SECRET_SYNC_NAME_MAP[destination];
|
||||||
return {
|
return {
|
||||||
initialSyncBehavior: `Specify how Infisical should resolve the initial sync to the ${destinationName} destination.`,
|
initialSyncBehavior: `Specify how Infisical should resolve the initial sync to the ${destinationName} destination.`,
|
||||||
|
keySchema: `Specify the format to use for structuring secret keys in the ${destinationName} destination.`,
|
||||||
disableSecretDeletion: `Enable this flag to prevent removal of secrets from the ${destinationName} destination when syncing.`
|
disableSecretDeletion: `Enable this flag to prevent removal of secrets from the ${destinationName} destination when syncing.`
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
@@ -2121,6 +2232,11 @@ export const SecretSyncs = {
|
|||||||
TEAMCITY: {
|
TEAMCITY: {
|
||||||
project: "The TeamCity project to sync secrets to.",
|
project: "The TeamCity project to sync secrets to.",
|
||||||
buildConfig: "The TeamCity build configuration to sync secrets to."
|
buildConfig: "The TeamCity build configuration to sync secrets to."
|
||||||
|
},
|
||||||
|
OCI_VAULT: {
|
||||||
|
compartmentOcid: "The OCID (Oracle Cloud Identifier) of the compartment where the vault is located.",
|
||||||
|
vaultOcid: "The OCID (Oracle Cloud Identifier) of the vault to sync secrets to.",
|
||||||
|
keyOcid: "The OCID (Oracle Cloud Identifier) of the encryption key to use when creating secrets in the vault."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@@ -174,6 +174,8 @@ const setupProxyServer = async ({
|
|||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const server = net.createServer();
|
const server = net.createServer();
|
||||||
|
|
||||||
|
let streamClosed = false;
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
||||||
server.on("connection", async (clientConn) => {
|
server.on("connection", async (clientConn) => {
|
||||||
try {
|
try {
|
||||||
@@ -202,9 +204,15 @@ const setupProxyServer = async ({
|
|||||||
|
|
||||||
// Handle client connection close
|
// Handle client connection close
|
||||||
clientConn.on("end", () => {
|
clientConn.on("end", () => {
|
||||||
writer.close().catch((err) => {
|
if (!streamClosed) {
|
||||||
logger.error(err);
|
try {
|
||||||
});
|
writer.close().catch((err) => {
|
||||||
|
logger.debug(err, "Error closing writer (already closed)");
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
logger.debug(error, "Error in writer close");
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
clientConn.on("error", (clientConnErr) => {
|
clientConn.on("error", (clientConnErr) => {
|
||||||
@@ -249,14 +257,29 @@ const setupProxyServer = async ({
|
|||||||
setupCopy();
|
setupCopy();
|
||||||
// Handle connection closure
|
// Handle connection closure
|
||||||
clientConn.on("close", () => {
|
clientConn.on("close", () => {
|
||||||
stream.destroy().catch((err) => {
|
if (!streamClosed) {
|
||||||
proxyErrorMsg.push((err as Error)?.message);
|
streamClosed = true;
|
||||||
});
|
stream.destroy().catch((err) => {
|
||||||
|
logger.debug(err, "Stream already destroyed during close event");
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const cleanup = async () => {
|
const cleanup = async () => {
|
||||||
clientConn?.destroy();
|
try {
|
||||||
await stream.destroy();
|
clientConn?.destroy();
|
||||||
|
} catch (err) {
|
||||||
|
logger.debug(err, "Error destroying client connection");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!streamClosed) {
|
||||||
|
streamClosed = true;
|
||||||
|
try {
|
||||||
|
await stream.destroy();
|
||||||
|
} catch (err) {
|
||||||
|
logger.debug(err, "Error destroying stream (might be already closed)");
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
clientConn.on("error", (clientConnErr) => {
|
clientConn.on("error", (clientConnErr) => {
|
||||||
@@ -301,8 +324,17 @@ const setupProxyServer = async ({
|
|||||||
server,
|
server,
|
||||||
port: address.port,
|
port: address.port,
|
||||||
cleanup: async () => {
|
cleanup: async () => {
|
||||||
server.close();
|
try {
|
||||||
await quicClient?.destroy();
|
server.close();
|
||||||
|
} catch (err) {
|
||||||
|
logger.debug(err, "Error closing server");
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await quicClient?.destroy();
|
||||||
|
} catch (err) {
|
||||||
|
logger.debug(err, "Error destroying QUIC client");
|
||||||
|
}
|
||||||
},
|
},
|
||||||
getProxyError: () => proxyErrorMsg.join(",")
|
getProxyError: () => proxyErrorMsg.join(",")
|
||||||
});
|
});
|
||||||
@@ -320,10 +352,10 @@ interface ProxyOptions {
|
|||||||
orgId: string;
|
orgId: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const withGatewayProxy = async (
|
export const withGatewayProxy = async <T>(
|
||||||
callback: (port: number) => Promise<void>,
|
callback: (port: number) => Promise<T>,
|
||||||
options: ProxyOptions
|
options: ProxyOptions
|
||||||
): Promise<void> => {
|
): Promise<T> => {
|
||||||
const { relayHost, relayPort, targetHost, targetPort, tlsOptions, identityId, orgId } = options;
|
const { relayHost, relayPort, targetHost, targetPort, tlsOptions, identityId, orgId } = options;
|
||||||
|
|
||||||
// Setup the proxy server
|
// Setup the proxy server
|
||||||
@@ -339,7 +371,7 @@ export const withGatewayProxy = async (
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
// Execute the callback with the allocated port
|
// Execute the callback with the allocated port
|
||||||
await callback(port);
|
return await callback(port);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
const proxyErrorMessage = getProxyError();
|
const proxyErrorMessage = getProxyError();
|
||||||
if (proxyErrorMessage) {
|
if (proxyErrorMessage) {
|
||||||
|
@@ -32,13 +32,13 @@ export const buildFindFilter =
|
|||||||
<R extends object = object>(
|
<R extends object = object>(
|
||||||
{ $in, $notNull, $search, $complex, ...filter }: TFindFilter<R>,
|
{ $in, $notNull, $search, $complex, ...filter }: TFindFilter<R>,
|
||||||
tableName?: TableName,
|
tableName?: TableName,
|
||||||
excludeKeys?: Array<keyof R>
|
excludeKeys?: string[]
|
||||||
) =>
|
) =>
|
||||||
(bd: Knex.QueryBuilder<R, R>) => {
|
(bd: Knex.QueryBuilder<R, R>) => {
|
||||||
const processedFilter = tableName
|
const processedFilter = tableName
|
||||||
? Object.fromEntries(
|
? Object.fromEntries(
|
||||||
Object.entries(filter)
|
Object.entries(filter)
|
||||||
.filter(([key]) => !excludeKeys || !excludeKeys.includes(key as keyof R))
|
.filter(([key]) => !excludeKeys || !excludeKeys.includes(key))
|
||||||
.map(([key, value]) => [`${tableName}.${key}`, value])
|
.map(([key, value]) => [`${tableName}.${key}`, value])
|
||||||
)
|
)
|
||||||
: filter;
|
: filter;
|
||||||
|
@@ -57,7 +57,9 @@ export const registerServeUI = async (
|
|||||||
reply.callNotFound();
|
reply.callNotFound();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
return reply.sendFile("index.html");
|
// reference: https://github.com/fastify/fastify-static?tab=readme-ov-file#managing-cache-control-headers
|
||||||
|
// to avoid ui bundle skew on new deployment
|
||||||
|
return reply.sendFile("index.html", { maxAge: 0, immutable: false });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@@ -32,7 +32,6 @@ import { externalKmsServiceFactory } from "@app/ee/services/external-kms/externa
|
|||||||
import { gatewayDALFactory } from "@app/ee/services/gateway/gateway-dal";
|
import { gatewayDALFactory } from "@app/ee/services/gateway/gateway-dal";
|
||||||
import { gatewayServiceFactory } from "@app/ee/services/gateway/gateway-service";
|
import { gatewayServiceFactory } from "@app/ee/services/gateway/gateway-service";
|
||||||
import { orgGatewayConfigDALFactory } from "@app/ee/services/gateway/org-gateway-config-dal";
|
import { orgGatewayConfigDALFactory } from "@app/ee/services/gateway/org-gateway-config-dal";
|
||||||
import { projectGatewayDALFactory } from "@app/ee/services/gateway/project-gateway-dal";
|
|
||||||
import { githubOrgSyncDALFactory } from "@app/ee/services/github-org-sync/github-org-sync-dal";
|
import { githubOrgSyncDALFactory } from "@app/ee/services/github-org-sync/github-org-sync-dal";
|
||||||
import { githubOrgSyncServiceFactory } from "@app/ee/services/github-org-sync/github-org-sync-service";
|
import { githubOrgSyncServiceFactory } from "@app/ee/services/github-org-sync/github-org-sync-service";
|
||||||
import { groupDALFactory } from "@app/ee/services/group/group-dal";
|
import { groupDALFactory } from "@app/ee/services/group/group-dal";
|
||||||
@@ -162,6 +161,8 @@ import { identityKubernetesAuthDALFactory } from "@app/services/identity-kuberne
|
|||||||
import { identityKubernetesAuthServiceFactory } from "@app/services/identity-kubernetes-auth/identity-kubernetes-auth-service";
|
import { identityKubernetesAuthServiceFactory } from "@app/services/identity-kubernetes-auth/identity-kubernetes-auth-service";
|
||||||
import { identityLdapAuthDALFactory } from "@app/services/identity-ldap-auth/identity-ldap-auth-dal";
|
import { identityLdapAuthDALFactory } from "@app/services/identity-ldap-auth/identity-ldap-auth-dal";
|
||||||
import { identityLdapAuthServiceFactory } from "@app/services/identity-ldap-auth/identity-ldap-auth-service";
|
import { identityLdapAuthServiceFactory } from "@app/services/identity-ldap-auth/identity-ldap-auth-service";
|
||||||
|
import { identityOciAuthDALFactory } from "@app/services/identity-oci-auth/identity-oci-auth-dal";
|
||||||
|
import { identityOciAuthServiceFactory } from "@app/services/identity-oci-auth/identity-oci-auth-service";
|
||||||
import { identityOidcAuthDALFactory } from "@app/services/identity-oidc-auth/identity-oidc-auth-dal";
|
import { identityOidcAuthDALFactory } from "@app/services/identity-oidc-auth/identity-oidc-auth-dal";
|
||||||
import { identityOidcAuthServiceFactory } from "@app/services/identity-oidc-auth/identity-oidc-auth-service";
|
import { identityOidcAuthServiceFactory } from "@app/services/identity-oidc-auth/identity-oidc-auth-service";
|
||||||
import { identityProjectDALFactory } from "@app/services/identity-project/identity-project-dal";
|
import { identityProjectDALFactory } from "@app/services/identity-project/identity-project-dal";
|
||||||
@@ -197,6 +198,8 @@ import { pkiAlertServiceFactory } from "@app/services/pki-alert/pki-alert-servic
|
|||||||
import { pkiCollectionDALFactory } from "@app/services/pki-collection/pki-collection-dal";
|
import { pkiCollectionDALFactory } from "@app/services/pki-collection/pki-collection-dal";
|
||||||
import { pkiCollectionItemDALFactory } from "@app/services/pki-collection/pki-collection-item-dal";
|
import { pkiCollectionItemDALFactory } from "@app/services/pki-collection/pki-collection-item-dal";
|
||||||
import { pkiCollectionServiceFactory } from "@app/services/pki-collection/pki-collection-service";
|
import { pkiCollectionServiceFactory } from "@app/services/pki-collection/pki-collection-service";
|
||||||
|
import { pkiSubscriberDALFactory } from "@app/services/pki-subscriber/pki-subscriber-dal";
|
||||||
|
import { pkiSubscriberServiceFactory } from "@app/services/pki-subscriber/pki-subscriber-service";
|
||||||
import { projectDALFactory } from "@app/services/project/project-dal";
|
import { projectDALFactory } from "@app/services/project/project-dal";
|
||||||
import { projectQueueFactory } from "@app/services/project/project-queue";
|
import { projectQueueFactory } from "@app/services/project/project-queue";
|
||||||
import { projectServiceFactory } from "@app/services/project/project-service";
|
import { projectServiceFactory } from "@app/services/project/project-service";
|
||||||
@@ -353,6 +356,7 @@ export const registerRoutes = async (
|
|||||||
const identityUaClientSecretDAL = identityUaClientSecretDALFactory(db);
|
const identityUaClientSecretDAL = identityUaClientSecretDALFactory(db);
|
||||||
const identityAwsAuthDAL = identityAwsAuthDALFactory(db);
|
const identityAwsAuthDAL = identityAwsAuthDALFactory(db);
|
||||||
const identityGcpAuthDAL = identityGcpAuthDALFactory(db);
|
const identityGcpAuthDAL = identityGcpAuthDALFactory(db);
|
||||||
|
const identityOciAuthDAL = identityOciAuthDALFactory(db);
|
||||||
const identityOidcAuthDAL = identityOidcAuthDALFactory(db);
|
const identityOidcAuthDAL = identityOidcAuthDALFactory(db);
|
||||||
const identityJwtAuthDAL = identityJwtAuthDALFactory(db);
|
const identityJwtAuthDAL = identityJwtAuthDALFactory(db);
|
||||||
const identityAzureAuthDAL = identityAzureAuthDALFactory(db);
|
const identityAzureAuthDAL = identityAzureAuthDALFactory(db);
|
||||||
@@ -434,7 +438,6 @@ export const registerRoutes = async (
|
|||||||
|
|
||||||
const orgGatewayConfigDAL = orgGatewayConfigDALFactory(db);
|
const orgGatewayConfigDAL = orgGatewayConfigDALFactory(db);
|
||||||
const gatewayDAL = gatewayDALFactory(db);
|
const gatewayDAL = gatewayDALFactory(db);
|
||||||
const projectGatewayDAL = projectGatewayDALFactory(db);
|
|
||||||
const secretReminderRecipientsDAL = secretReminderRecipientsDALFactory(db);
|
const secretReminderRecipientsDAL = secretReminderRecipientsDALFactory(db);
|
||||||
const githubOrgSyncDAL = githubOrgSyncDALFactory(db);
|
const githubOrgSyncDAL = githubOrgSyncDALFactory(db);
|
||||||
|
|
||||||
@@ -828,6 +831,7 @@ export const registerRoutes = async (
|
|||||||
const pkiAlertDAL = pkiAlertDALFactory(db);
|
const pkiAlertDAL = pkiAlertDALFactory(db);
|
||||||
const pkiCollectionDAL = pkiCollectionDALFactory(db);
|
const pkiCollectionDAL = pkiCollectionDALFactory(db);
|
||||||
const pkiCollectionItemDAL = pkiCollectionItemDALFactory(db);
|
const pkiCollectionItemDAL = pkiCollectionItemDALFactory(db);
|
||||||
|
const pkiSubscriberDAL = pkiSubscriberDALFactory(db);
|
||||||
|
|
||||||
const certificateService = certificateServiceFactory({
|
const certificateService = certificateServiceFactory({
|
||||||
certificateDAL,
|
certificateDAL,
|
||||||
@@ -962,6 +966,20 @@ export const registerRoutes = async (
|
|||||||
projectDAL
|
projectDAL
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const pkiSubscriberService = pkiSubscriberServiceFactory({
|
||||||
|
pkiSubscriberDAL,
|
||||||
|
certificateAuthorityDAL,
|
||||||
|
certificateAuthorityCertDAL,
|
||||||
|
certificateAuthoritySecretDAL,
|
||||||
|
certificateAuthorityCrlDAL,
|
||||||
|
certificateDAL,
|
||||||
|
certificateBodyDAL,
|
||||||
|
certificateSecretDAL,
|
||||||
|
projectDAL,
|
||||||
|
kmsService,
|
||||||
|
permissionService
|
||||||
|
});
|
||||||
|
|
||||||
const projectTemplateService = projectTemplateServiceFactory({
|
const projectTemplateService = projectTemplateServiceFactory({
|
||||||
licenseService,
|
licenseService,
|
||||||
permissionService,
|
permissionService,
|
||||||
@@ -1059,6 +1077,7 @@ export const registerRoutes = async (
|
|||||||
projectRoleDAL,
|
projectRoleDAL,
|
||||||
folderDAL,
|
folderDAL,
|
||||||
licenseService,
|
licenseService,
|
||||||
|
pkiSubscriberDAL,
|
||||||
certificateAuthorityDAL,
|
certificateAuthorityDAL,
|
||||||
certificateDAL,
|
certificateDAL,
|
||||||
pkiAlertDAL,
|
pkiAlertDAL,
|
||||||
@@ -1401,12 +1420,24 @@ export const registerRoutes = async (
|
|||||||
identityUaDAL,
|
identityUaDAL,
|
||||||
licenseService
|
licenseService
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const gatewayService = gatewayServiceFactory({
|
||||||
|
permissionService,
|
||||||
|
gatewayDAL,
|
||||||
|
kmsService,
|
||||||
|
licenseService,
|
||||||
|
orgGatewayConfigDAL,
|
||||||
|
keyStore
|
||||||
|
});
|
||||||
|
|
||||||
const identityKubernetesAuthService = identityKubernetesAuthServiceFactory({
|
const identityKubernetesAuthService = identityKubernetesAuthServiceFactory({
|
||||||
identityKubernetesAuthDAL,
|
identityKubernetesAuthDAL,
|
||||||
identityOrgMembershipDAL,
|
identityOrgMembershipDAL,
|
||||||
identityAccessTokenDAL,
|
identityAccessTokenDAL,
|
||||||
permissionService,
|
permissionService,
|
||||||
licenseService,
|
licenseService,
|
||||||
|
gatewayService,
|
||||||
|
gatewayDAL,
|
||||||
kmsService
|
kmsService
|
||||||
});
|
});
|
||||||
const identityGcpAuthService = identityGcpAuthServiceFactory({
|
const identityGcpAuthService = identityGcpAuthServiceFactory({
|
||||||
@@ -1433,6 +1464,14 @@ export const registerRoutes = async (
|
|||||||
licenseService
|
licenseService
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const identityOciAuthService = identityOciAuthServiceFactory({
|
||||||
|
identityAccessTokenDAL,
|
||||||
|
identityOciAuthDAL,
|
||||||
|
identityOrgMembershipDAL,
|
||||||
|
licenseService,
|
||||||
|
permissionService
|
||||||
|
});
|
||||||
|
|
||||||
const identityOidcAuthService = identityOidcAuthServiceFactory({
|
const identityOidcAuthService = identityOidcAuthServiceFactory({
|
||||||
identityOidcAuthDAL,
|
identityOidcAuthDAL,
|
||||||
identityOrgMembershipDAL,
|
identityOrgMembershipDAL,
|
||||||
@@ -1461,16 +1500,6 @@ export const registerRoutes = async (
|
|||||||
identityDAL
|
identityDAL
|
||||||
});
|
});
|
||||||
|
|
||||||
const gatewayService = gatewayServiceFactory({
|
|
||||||
permissionService,
|
|
||||||
gatewayDAL,
|
|
||||||
kmsService,
|
|
||||||
licenseService,
|
|
||||||
orgGatewayConfigDAL,
|
|
||||||
keyStore,
|
|
||||||
projectGatewayDAL
|
|
||||||
});
|
|
||||||
|
|
||||||
const dynamicSecretProviders = buildDynamicSecretProviders({
|
const dynamicSecretProviders = buildDynamicSecretProviders({
|
||||||
gatewayService
|
gatewayService
|
||||||
});
|
});
|
||||||
@@ -1492,7 +1521,7 @@ export const registerRoutes = async (
|
|||||||
permissionService,
|
permissionService,
|
||||||
licenseService,
|
licenseService,
|
||||||
kmsService,
|
kmsService,
|
||||||
projectGatewayDAL,
|
gatewayDAL,
|
||||||
resourceMetadataDAL
|
resourceMetadataDAL
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1719,6 +1748,7 @@ export const registerRoutes = async (
|
|||||||
identityGcpAuth: identityGcpAuthService,
|
identityGcpAuth: identityGcpAuthService,
|
||||||
identityAwsAuth: identityAwsAuthService,
|
identityAwsAuth: identityAwsAuthService,
|
||||||
identityAzureAuth: identityAzureAuthService,
|
identityAzureAuth: identityAzureAuthService,
|
||||||
|
identityOciAuth: identityOciAuthService,
|
||||||
identityOidcAuth: identityOidcAuthService,
|
identityOidcAuth: identityOidcAuthService,
|
||||||
identityJwtAuth: identityJwtAuthService,
|
identityJwtAuth: identityJwtAuthService,
|
||||||
identityLdapAuth: identityLdapAuthService,
|
identityLdapAuth: identityLdapAuthService,
|
||||||
@@ -1745,6 +1775,7 @@ export const registerRoutes = async (
|
|||||||
certificateEst: certificateEstService,
|
certificateEst: certificateEstService,
|
||||||
pkiAlert: pkiAlertService,
|
pkiAlert: pkiAlertService,
|
||||||
pkiCollection: pkiCollectionService,
|
pkiCollection: pkiCollectionService,
|
||||||
|
pkiSubscriber: pkiSubscriberService,
|
||||||
secretScanning: secretScanningService,
|
secretScanning: secretScanningService,
|
||||||
license: licenseService,
|
license: licenseService,
|
||||||
trustedIp: trustedIpService,
|
trustedIp: trustedIpService,
|
||||||
|
@@ -261,7 +261,8 @@ export const SanitizedProjectSchema = ProjectsSchema.pick({
|
|||||||
pitVersionLimit: true,
|
pitVersionLimit: true,
|
||||||
kmsCertificateKeyId: true,
|
kmsCertificateKeyId: true,
|
||||||
auditLogsRetentionDays: true,
|
auditLogsRetentionDays: true,
|
||||||
hasDeleteProtection: true
|
hasDeleteProtection: true,
|
||||||
|
secretSharing: true
|
||||||
});
|
});
|
||||||
|
|
||||||
export const SanitizedTagSchema = SecretTagsSchema.pick({
|
export const SanitizedTagSchema = SecretTagsSchema.pick({
|
||||||
|
@@ -38,6 +38,7 @@ import {
|
|||||||
} from "@app/services/app-connection/humanitec";
|
} from "@app/services/app-connection/humanitec";
|
||||||
import { LdapConnectionListItemSchema, SanitizedLdapConnectionSchema } from "@app/services/app-connection/ldap";
|
import { LdapConnectionListItemSchema, SanitizedLdapConnectionSchema } from "@app/services/app-connection/ldap";
|
||||||
import { MsSqlConnectionListItemSchema, SanitizedMsSqlConnectionSchema } from "@app/services/app-connection/mssql";
|
import { MsSqlConnectionListItemSchema, SanitizedMsSqlConnectionSchema } from "@app/services/app-connection/mssql";
|
||||||
|
import { OCIConnectionListItemSchema, SanitizedOCIConnectionSchema } from "@app/services/app-connection/oci";
|
||||||
import {
|
import {
|
||||||
PostgresConnectionListItemSchema,
|
PostgresConnectionListItemSchema,
|
||||||
SanitizedPostgresConnectionSchema
|
SanitizedPostgresConnectionSchema
|
||||||
@@ -76,7 +77,8 @@ const SanitizedAppConnectionSchema = z.union([
|
|||||||
...SanitizedAzureClientSecretsConnectionSchema.options,
|
...SanitizedAzureClientSecretsConnectionSchema.options,
|
||||||
...SanitizedWindmillConnectionSchema.options,
|
...SanitizedWindmillConnectionSchema.options,
|
||||||
...SanitizedLdapConnectionSchema.options,
|
...SanitizedLdapConnectionSchema.options,
|
||||||
...SanitizedTeamCityConnectionSchema.options
|
...SanitizedTeamCityConnectionSchema.options,
|
||||||
|
...SanitizedOCIConnectionSchema.options
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const AppConnectionOptionsSchema = z.discriminatedUnion("app", [
|
const AppConnectionOptionsSchema = z.discriminatedUnion("app", [
|
||||||
@@ -97,7 +99,8 @@ const AppConnectionOptionsSchema = z.discriminatedUnion("app", [
|
|||||||
AzureClientSecretsConnectionListItemSchema,
|
AzureClientSecretsConnectionListItemSchema,
|
||||||
WindmillConnectionListItemSchema,
|
WindmillConnectionListItemSchema,
|
||||||
LdapConnectionListItemSchema,
|
LdapConnectionListItemSchema,
|
||||||
TeamCityConnectionListItemSchema
|
TeamCityConnectionListItemSchema,
|
||||||
|
OCIConnectionListItemSchema
|
||||||
]);
|
]);
|
||||||
|
|
||||||
export const registerAppConnectionRouter = async (server: FastifyZodProvider) => {
|
export const registerAppConnectionRouter = async (server: FastifyZodProvider) => {
|
||||||
|
@@ -13,6 +13,7 @@ import { registerHCVaultConnectionRouter } from "./hc-vault-connection-router";
|
|||||||
import { registerHumanitecConnectionRouter } from "./humanitec-connection-router";
|
import { registerHumanitecConnectionRouter } from "./humanitec-connection-router";
|
||||||
import { registerLdapConnectionRouter } from "./ldap-connection-router";
|
import { registerLdapConnectionRouter } from "./ldap-connection-router";
|
||||||
import { registerMsSqlConnectionRouter } from "./mssql-connection-router";
|
import { registerMsSqlConnectionRouter } from "./mssql-connection-router";
|
||||||
|
import { registerOCIConnectionRouter } from "./oci-connection-router";
|
||||||
import { registerPostgresConnectionRouter } from "./postgres-connection-router";
|
import { registerPostgresConnectionRouter } from "./postgres-connection-router";
|
||||||
import { registerTeamCityConnectionRouter } from "./teamcity-connection-router";
|
import { registerTeamCityConnectionRouter } from "./teamcity-connection-router";
|
||||||
import { registerTerraformCloudConnectionRouter } from "./terraform-cloud-router";
|
import { registerTerraformCloudConnectionRouter } from "./terraform-cloud-router";
|
||||||
@@ -40,5 +41,6 @@ export const APP_CONNECTION_REGISTER_ROUTER_MAP: Record<AppConnection, (server:
|
|||||||
[AppConnection.Auth0]: registerAuth0ConnectionRouter,
|
[AppConnection.Auth0]: registerAuth0ConnectionRouter,
|
||||||
[AppConnection.HCVault]: registerHCVaultConnectionRouter,
|
[AppConnection.HCVault]: registerHCVaultConnectionRouter,
|
||||||
[AppConnection.LDAP]: registerLdapConnectionRouter,
|
[AppConnection.LDAP]: registerLdapConnectionRouter,
|
||||||
[AppConnection.TeamCity]: registerTeamCityConnectionRouter
|
[AppConnection.TeamCity]: registerTeamCityConnectionRouter,
|
||||||
|
[AppConnection.OCI]: registerOCIConnectionRouter
|
||||||
};
|
};
|
||||||
|
@@ -0,0 +1,123 @@
|
|||||||
|
import z from "zod";
|
||||||
|
|
||||||
|
import { readLimit } from "@app/server/config/rateLimiter";
|
||||||
|
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||||
|
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
|
||||||
|
import {
|
||||||
|
CreateOCIConnectionSchema,
|
||||||
|
SanitizedOCIConnectionSchema,
|
||||||
|
UpdateOCIConnectionSchema
|
||||||
|
} from "@app/services/app-connection/oci";
|
||||||
|
import { AuthMode } from "@app/services/auth/auth-type";
|
||||||
|
|
||||||
|
import { registerAppConnectionEndpoints } from "./app-connection-endpoints";
|
||||||
|
|
||||||
|
export const registerOCIConnectionRouter = async (server: FastifyZodProvider) => {
|
||||||
|
registerAppConnectionEndpoints({
|
||||||
|
app: AppConnection.OCI,
|
||||||
|
server,
|
||||||
|
sanitizedResponseSchema: SanitizedOCIConnectionSchema,
|
||||||
|
createSchema: CreateOCIConnectionSchema,
|
||||||
|
updateSchema: UpdateOCIConnectionSchema
|
||||||
|
});
|
||||||
|
|
||||||
|
// The following endpoints are for internal Infisical App use only and not part of the public API
|
||||||
|
server.route({
|
||||||
|
method: "GET",
|
||||||
|
url: `/:connectionId/compartments`,
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
params: z.object({
|
||||||
|
connectionId: z.string().uuid()
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z
|
||||||
|
.object({
|
||||||
|
id: z.string(),
|
||||||
|
name: z.string()
|
||||||
|
})
|
||||||
|
.array()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const { connectionId } = req.params;
|
||||||
|
|
||||||
|
const compartments = await server.services.appConnection.oci.listCompartments(connectionId, req.permission);
|
||||||
|
return compartments;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: "GET",
|
||||||
|
url: `/:connectionId/vaults`,
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
params: z.object({
|
||||||
|
connectionId: z.string().uuid()
|
||||||
|
}),
|
||||||
|
querystring: z.object({
|
||||||
|
compartmentOcid: z.string().min(1, "Compartment OCID required")
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z
|
||||||
|
.object({
|
||||||
|
id: z.string(),
|
||||||
|
displayName: z.string()
|
||||||
|
})
|
||||||
|
.array()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const { connectionId } = req.params;
|
||||||
|
const { compartmentOcid } = req.query;
|
||||||
|
|
||||||
|
const vaults = await server.services.appConnection.oci.listVaults(
|
||||||
|
{ connectionId, compartmentOcid },
|
||||||
|
req.permission
|
||||||
|
);
|
||||||
|
return vaults;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: "GET",
|
||||||
|
url: `/:connectionId/vault-keys`,
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
params: z.object({
|
||||||
|
connectionId: z.string().uuid()
|
||||||
|
}),
|
||||||
|
querystring: z.object({
|
||||||
|
compartmentOcid: z.string().min(1, "Compartment OCID required"),
|
||||||
|
vaultOcid: z.string().min(1, "Vault OCID required")
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z
|
||||||
|
.object({
|
||||||
|
id: z.string(),
|
||||||
|
displayName: z.string()
|
||||||
|
})
|
||||||
|
.array()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const { connectionId } = req.params;
|
||||||
|
const { compartmentOcid, vaultOcid } = req.query;
|
||||||
|
|
||||||
|
const keys = await server.services.appConnection.oci.listVaultKeys(
|
||||||
|
{ connectionId, compartmentOcid, vaultOcid },
|
||||||
|
req.permission
|
||||||
|
);
|
||||||
|
return keys;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
@@ -131,8 +131,8 @@ export const registerCertRouter = async (server: FastifyZodProvider) => {
|
|||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
certificate: z.string().trim().describe(CERTIFICATES.GET_CERT.certificate),
|
certificate: z.string().trim().describe(CERTIFICATES.GET_CERT.certificate),
|
||||||
certificateChain: z.string().trim().nullish().describe(CERTIFICATES.GET_CERT.certificateChain),
|
certificateChain: z.string().trim().nullable().describe(CERTIFICATES.GET_CERT.certificateChain),
|
||||||
privateKey: z.string().trim().describe(CERTIFICATES.GET_CERT.privateKey),
|
privateKey: z.string().trim().nullable().describe(CERTIFICATES.GET_CERT.privateKey),
|
||||||
serialNumber: z.string().trim().describe(CERTIFICATES.GET_CERT.serialNumberRes)
|
serialNumber: z.string().trim().describe(CERTIFICATES.GET_CERT.serialNumberRes)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -518,7 +518,7 @@ export const registerCertRouter = async (server: FastifyZodProvider) => {
|
|||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
certificate: z.string().trim().describe(CERTIFICATES.GET_CERT.certificate),
|
certificate: z.string().trim().describe(CERTIFICATES.GET_CERT.certificate),
|
||||||
certificateChain: z.string().trim().nullish().describe(CERTIFICATES.GET_CERT.certificateChain),
|
certificateChain: z.string().trim().nullable().describe(CERTIFICATES.GET_CERT.certificateChain),
|
||||||
serialNumber: z.string().trim().describe(CERTIFICATES.GET_CERT.serialNumberRes)
|
serialNumber: z.string().trim().describe(CERTIFICATES.GET_CERT.serialNumberRes)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@@ -3,6 +3,7 @@ import { z } from "zod";
|
|||||||
import { IdentityKubernetesAuthsSchema } from "@app/db/schemas";
|
import { IdentityKubernetesAuthsSchema } 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 { ApiDocsTags, KUBERNETES_AUTH } from "@app/lib/api-docs";
|
import { ApiDocsTags, KUBERNETES_AUTH } from "@app/lib/api-docs";
|
||||||
|
import { CharacterType, characterValidator } from "@app/lib/validator/validate-string";
|
||||||
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";
|
||||||
@@ -21,7 +22,8 @@ const IdentityKubernetesAuthResponseSchema = IdentityKubernetesAuthsSchema.pick(
|
|||||||
kubernetesHost: true,
|
kubernetesHost: true,
|
||||||
allowedNamespaces: true,
|
allowedNamespaces: true,
|
||||||
allowedNames: true,
|
allowedNames: true,
|
||||||
allowedAudience: true
|
allowedAudience: true,
|
||||||
|
gatewayId: true
|
||||||
}).extend({
|
}).extend({
|
||||||
caCert: z.string(),
|
caCert: z.string(),
|
||||||
tokenReviewerJwt: z.string().optional().nullable()
|
tokenReviewerJwt: z.string().optional().nullable()
|
||||||
@@ -100,12 +102,32 @@ export const registerIdentityKubernetesRouter = async (server: FastifyZodProvide
|
|||||||
}),
|
}),
|
||||||
body: z
|
body: z
|
||||||
.object({
|
.object({
|
||||||
kubernetesHost: z.string().trim().min(1).describe(KUBERNETES_AUTH.ATTACH.kubernetesHost),
|
kubernetesHost: z
|
||||||
|
.string()
|
||||||
|
.trim()
|
||||||
|
.min(1)
|
||||||
|
.describe(KUBERNETES_AUTH.ATTACH.kubernetesHost)
|
||||||
|
.refine(
|
||||||
|
(val) =>
|
||||||
|
characterValidator([
|
||||||
|
CharacterType.Alphabets,
|
||||||
|
CharacterType.Numbers,
|
||||||
|
CharacterType.Colon,
|
||||||
|
CharacterType.Period,
|
||||||
|
CharacterType.ForwardSlash,
|
||||||
|
CharacterType.Hyphen
|
||||||
|
])(val),
|
||||||
|
{
|
||||||
|
message:
|
||||||
|
"Kubernetes host must only contain alphabets, numbers, colons, periods, hyphen, and forward slashes."
|
||||||
|
}
|
||||||
|
),
|
||||||
caCert: z.string().trim().default("").describe(KUBERNETES_AUTH.ATTACH.caCert),
|
caCert: z.string().trim().default("").describe(KUBERNETES_AUTH.ATTACH.caCert),
|
||||||
tokenReviewerJwt: z.string().trim().optional().describe(KUBERNETES_AUTH.ATTACH.tokenReviewerJwt),
|
tokenReviewerJwt: z.string().trim().optional().describe(KUBERNETES_AUTH.ATTACH.tokenReviewerJwt),
|
||||||
allowedNamespaces: z.string().describe(KUBERNETES_AUTH.ATTACH.allowedNamespaces), // TODO: validation
|
allowedNamespaces: z.string().describe(KUBERNETES_AUTH.ATTACH.allowedNamespaces), // TODO: validation
|
||||||
allowedNames: z.string().describe(KUBERNETES_AUTH.ATTACH.allowedNames),
|
allowedNames: z.string().describe(KUBERNETES_AUTH.ATTACH.allowedNames),
|
||||||
allowedAudience: z.string().describe(KUBERNETES_AUTH.ATTACH.allowedAudience),
|
allowedAudience: z.string().describe(KUBERNETES_AUTH.ATTACH.allowedAudience),
|
||||||
|
gatewayId: z.string().uuid().optional().nullable().describe(KUBERNETES_AUTH.ATTACH.gatewayId),
|
||||||
accessTokenTrustedIps: z
|
accessTokenTrustedIps: z
|
||||||
.object({
|
.object({
|
||||||
ipAddress: z.string().trim()
|
ipAddress: z.string().trim()
|
||||||
@@ -199,12 +221,36 @@ export const registerIdentityKubernetesRouter = async (server: FastifyZodProvide
|
|||||||
}),
|
}),
|
||||||
body: z
|
body: z
|
||||||
.object({
|
.object({
|
||||||
kubernetesHost: z.string().trim().min(1).optional().describe(KUBERNETES_AUTH.UPDATE.kubernetesHost),
|
kubernetesHost: z
|
||||||
|
.string()
|
||||||
|
.trim()
|
||||||
|
.min(1)
|
||||||
|
.optional()
|
||||||
|
.describe(KUBERNETES_AUTH.UPDATE.kubernetesHost)
|
||||||
|
.refine(
|
||||||
|
(val) => {
|
||||||
|
if (!val) return true;
|
||||||
|
|
||||||
|
return characterValidator([
|
||||||
|
CharacterType.Alphabets,
|
||||||
|
CharacterType.Numbers,
|
||||||
|
CharacterType.Colon,
|
||||||
|
CharacterType.Period,
|
||||||
|
CharacterType.ForwardSlash,
|
||||||
|
CharacterType.Hyphen
|
||||||
|
])(val);
|
||||||
|
},
|
||||||
|
{
|
||||||
|
message:
|
||||||
|
"Kubernetes host must only contain alphabets, numbers, colons, periods, hyphen, and forward slashes."
|
||||||
|
}
|
||||||
|
),
|
||||||
caCert: z.string().trim().optional().describe(KUBERNETES_AUTH.UPDATE.caCert),
|
caCert: z.string().trim().optional().describe(KUBERNETES_AUTH.UPDATE.caCert),
|
||||||
tokenReviewerJwt: z.string().trim().nullable().optional().describe(KUBERNETES_AUTH.UPDATE.tokenReviewerJwt),
|
tokenReviewerJwt: z.string().trim().nullable().optional().describe(KUBERNETES_AUTH.UPDATE.tokenReviewerJwt),
|
||||||
allowedNamespaces: z.string().optional().describe(KUBERNETES_AUTH.UPDATE.allowedNamespaces), // TODO: validation
|
allowedNamespaces: z.string().optional().describe(KUBERNETES_AUTH.UPDATE.allowedNamespaces), // TODO: validation
|
||||||
allowedNames: z.string().optional().describe(KUBERNETES_AUTH.UPDATE.allowedNames),
|
allowedNames: z.string().optional().describe(KUBERNETES_AUTH.UPDATE.allowedNames),
|
||||||
allowedAudience: z.string().optional().describe(KUBERNETES_AUTH.UPDATE.allowedAudience),
|
allowedAudience: z.string().optional().describe(KUBERNETES_AUTH.UPDATE.allowedAudience),
|
||||||
|
gatewayId: z.string().uuid().optional().nullable().describe(KUBERNETES_AUTH.UPDATE.gatewayId),
|
||||||
accessTokenTrustedIps: z
|
accessTokenTrustedIps: z
|
||||||
.object({
|
.object({
|
||||||
ipAddress: z.string().trim()
|
ipAddress: z.string().trim()
|
||||||
|
338
backend/src/server/routes/v1/identity-oci-auth-router.ts
Normal file
338
backend/src/server/routes/v1/identity-oci-auth-router.ts
Normal file
@@ -0,0 +1,338 @@
|
|||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { IdentityOciAuthsSchema } from "@app/db/schemas";
|
||||||
|
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
|
||||||
|
import { ApiDocsTags, OCI_AUTH } 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 { TIdentityTrustedIp } from "@app/services/identity/identity-types";
|
||||||
|
import { validateTenancy, validateUsernames } from "@app/services/identity-oci-auth/identity-oci-auth-validators";
|
||||||
|
import { isSuperAdmin } from "@app/services/super-admin/super-admin-fns";
|
||||||
|
|
||||||
|
export const registerIdentityOciAuthRouter = async (server: FastifyZodProvider) => {
|
||||||
|
server.route({
|
||||||
|
method: "POST",
|
||||||
|
url: "/oci-auth/login",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
hide: false,
|
||||||
|
tags: [ApiDocsTags.OciAuth],
|
||||||
|
description: "Login with OCI Auth",
|
||||||
|
body: z.object({
|
||||||
|
identityId: z.string().trim().describe(OCI_AUTH.LOGIN.identityId),
|
||||||
|
userOcid: z.string().trim().describe(OCI_AUTH.LOGIN.userOcid),
|
||||||
|
headers: z
|
||||||
|
.object({
|
||||||
|
authorization: z.string(),
|
||||||
|
host: z.string(),
|
||||||
|
"x-date": z.string()
|
||||||
|
})
|
||||||
|
.describe(OCI_AUTH.LOGIN.headers)
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
accessToken: z.string(),
|
||||||
|
expiresIn: z.coerce.number(),
|
||||||
|
accessTokenMaxTTL: z.coerce.number(),
|
||||||
|
tokenType: z.literal("Bearer")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handler: async (req) => {
|
||||||
|
const { identityOciAuth, accessToken, identityAccessToken, identityMembershipOrg } =
|
||||||
|
await server.services.identityOciAuth.login(req.body);
|
||||||
|
|
||||||
|
await server.services.auditLog.createAuditLog({
|
||||||
|
...req.auditLogInfo,
|
||||||
|
orgId: identityMembershipOrg?.orgId,
|
||||||
|
event: {
|
||||||
|
type: EventType.LOGIN_IDENTITY_OCI_AUTH,
|
||||||
|
metadata: {
|
||||||
|
identityId: identityOciAuth.identityId,
|
||||||
|
identityAccessTokenId: identityAccessToken.id,
|
||||||
|
identityOciAuthId: identityOciAuth.id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
accessToken,
|
||||||
|
tokenType: "Bearer" as const,
|
||||||
|
expiresIn: identityOciAuth.accessTokenTTL,
|
||||||
|
accessTokenMaxTTL: identityOciAuth.accessTokenMaxTTL
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: "POST",
|
||||||
|
url: "/oci-auth/identities/:identityId",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
|
schema: {
|
||||||
|
hide: false,
|
||||||
|
tags: [ApiDocsTags.OciAuth],
|
||||||
|
description: "Attach OCI Auth configuration onto identity",
|
||||||
|
security: [
|
||||||
|
{
|
||||||
|
bearerAuth: []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
params: z.object({
|
||||||
|
identityId: z.string().trim().describe(OCI_AUTH.ATTACH.identityId)
|
||||||
|
}),
|
||||||
|
body: z
|
||||||
|
.object({
|
||||||
|
tenancyOcid: validateTenancy.describe(OCI_AUTH.ATTACH.tenancyOcid),
|
||||||
|
allowedUsernames: validateUsernames.describe(OCI_AUTH.ATTACH.allowedUsernames),
|
||||||
|
accessTokenTrustedIps: z
|
||||||
|
.object({
|
||||||
|
ipAddress: z.string().trim()
|
||||||
|
})
|
||||||
|
.array()
|
||||||
|
.min(1)
|
||||||
|
.default([{ ipAddress: "0.0.0.0/0" }, { ipAddress: "::/0" }])
|
||||||
|
.describe(OCI_AUTH.ATTACH.accessTokenTrustedIps),
|
||||||
|
accessTokenTTL: z
|
||||||
|
.number()
|
||||||
|
.int()
|
||||||
|
.min(0)
|
||||||
|
.max(315360000)
|
||||||
|
.default(2592000)
|
||||||
|
.describe(OCI_AUTH.ATTACH.accessTokenTTL),
|
||||||
|
accessTokenMaxTTL: z
|
||||||
|
.number()
|
||||||
|
.int()
|
||||||
|
.min(1)
|
||||||
|
.max(315360000)
|
||||||
|
.default(2592000)
|
||||||
|
.describe(OCI_AUTH.ATTACH.accessTokenMaxTTL),
|
||||||
|
accessTokenNumUsesLimit: z.number().int().min(0).default(0).describe(OCI_AUTH.ATTACH.accessTokenNumUsesLimit)
|
||||||
|
})
|
||||||
|
.refine(
|
||||||
|
(val) => val.accessTokenTTL <= val.accessTokenMaxTTL,
|
||||||
|
"Access Token TTL cannot be greater than Access Token Max TTL."
|
||||||
|
),
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
identityOciAuth: IdentityOciAuthsSchema
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handler: async (req) => {
|
||||||
|
const identityOciAuth = await server.services.identityOciAuth.attachOciAuth({
|
||||||
|
actor: req.permission.type,
|
||||||
|
actorId: req.permission.id,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
|
actorOrgId: req.permission.orgId,
|
||||||
|
...req.body,
|
||||||
|
identityId: req.params.identityId,
|
||||||
|
isActorSuperAdmin: isSuperAdmin(req.auth)
|
||||||
|
});
|
||||||
|
|
||||||
|
await server.services.auditLog.createAuditLog({
|
||||||
|
...req.auditLogInfo,
|
||||||
|
orgId: identityOciAuth.orgId,
|
||||||
|
event: {
|
||||||
|
type: EventType.ADD_IDENTITY_OCI_AUTH,
|
||||||
|
metadata: {
|
||||||
|
identityId: identityOciAuth.identityId,
|
||||||
|
tenancyOcid: identityOciAuth.tenancyOcid,
|
||||||
|
allowedUsernames: identityOciAuth.allowedUsernames || null,
|
||||||
|
accessTokenTTL: identityOciAuth.accessTokenTTL,
|
||||||
|
accessTokenMaxTTL: identityOciAuth.accessTokenMaxTTL,
|
||||||
|
accessTokenTrustedIps: identityOciAuth.accessTokenTrustedIps as TIdentityTrustedIp[],
|
||||||
|
accessTokenNumUsesLimit: identityOciAuth.accessTokenNumUsesLimit
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return { identityOciAuth };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: "PATCH",
|
||||||
|
url: "/oci-auth/identities/:identityId",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
|
schema: {
|
||||||
|
hide: false,
|
||||||
|
tags: [ApiDocsTags.OciAuth],
|
||||||
|
description: "Update OCI Auth configuration on identity",
|
||||||
|
security: [
|
||||||
|
{
|
||||||
|
bearerAuth: []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
params: z.object({
|
||||||
|
identityId: z.string().describe(OCI_AUTH.UPDATE.identityId)
|
||||||
|
}),
|
||||||
|
body: z
|
||||||
|
.object({
|
||||||
|
tenancyOcid: validateTenancy.describe(OCI_AUTH.UPDATE.tenancyOcid),
|
||||||
|
allowedUsernames: validateUsernames.describe(OCI_AUTH.UPDATE.allowedUsernames),
|
||||||
|
accessTokenTrustedIps: z
|
||||||
|
.object({
|
||||||
|
ipAddress: z.string().trim()
|
||||||
|
})
|
||||||
|
.array()
|
||||||
|
.min(1)
|
||||||
|
.optional()
|
||||||
|
.describe(OCI_AUTH.UPDATE.accessTokenTrustedIps),
|
||||||
|
accessTokenTTL: z.number().int().min(0).max(315360000).optional().describe(OCI_AUTH.UPDATE.accessTokenTTL),
|
||||||
|
accessTokenNumUsesLimit: z.number().int().min(0).optional().describe(OCI_AUTH.UPDATE.accessTokenNumUsesLimit),
|
||||||
|
accessTokenMaxTTL: z
|
||||||
|
.number()
|
||||||
|
.int()
|
||||||
|
.max(315360000)
|
||||||
|
.min(0)
|
||||||
|
.optional()
|
||||||
|
.describe(OCI_AUTH.UPDATE.accessTokenMaxTTL)
|
||||||
|
})
|
||||||
|
.refine(
|
||||||
|
(val) => (val.accessTokenMaxTTL && val.accessTokenTTL ? val.accessTokenTTL <= val.accessTokenMaxTTL : true),
|
||||||
|
"Access Token TTL cannot be greater than Access Token Max TTL."
|
||||||
|
),
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
identityOciAuth: IdentityOciAuthsSchema
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handler: async (req) => {
|
||||||
|
const identityOciAuth = await server.services.identityOciAuth.updateOciAuth({
|
||||||
|
actor: req.permission.type,
|
||||||
|
actorId: req.permission.id,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
|
actorOrgId: req.permission.orgId,
|
||||||
|
...req.body,
|
||||||
|
identityId: req.params.identityId,
|
||||||
|
allowedUsernames: req.body.allowedUsernames || null
|
||||||
|
});
|
||||||
|
|
||||||
|
await server.services.auditLog.createAuditLog({
|
||||||
|
...req.auditLogInfo,
|
||||||
|
orgId: identityOciAuth.orgId,
|
||||||
|
event: {
|
||||||
|
type: EventType.UPDATE_IDENTITY_OCI_AUTH,
|
||||||
|
metadata: {
|
||||||
|
identityId: identityOciAuth.identityId,
|
||||||
|
tenancyOcid: identityOciAuth.tenancyOcid,
|
||||||
|
allowedUsernames: identityOciAuth.allowedUsernames || null,
|
||||||
|
accessTokenTTL: identityOciAuth.accessTokenTTL,
|
||||||
|
accessTokenMaxTTL: identityOciAuth.accessTokenMaxTTL,
|
||||||
|
accessTokenTrustedIps: identityOciAuth.accessTokenTrustedIps as TIdentityTrustedIp[],
|
||||||
|
accessTokenNumUsesLimit: identityOciAuth.accessTokenNumUsesLimit
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return { identityOciAuth };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: "GET",
|
||||||
|
url: "/oci-auth/identities/:identityId",
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
|
schema: {
|
||||||
|
hide: false,
|
||||||
|
tags: [ApiDocsTags.OciAuth],
|
||||||
|
description: "Retrieve OCI Auth configuration on identity",
|
||||||
|
security: [
|
||||||
|
{
|
||||||
|
bearerAuth: []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
params: z.object({
|
||||||
|
identityId: z.string().describe(OCI_AUTH.RETRIEVE.identityId)
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
identityOciAuth: IdentityOciAuthsSchema
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handler: async (req) => {
|
||||||
|
const identityOciAuth = await server.services.identityOciAuth.getOciAuth({
|
||||||
|
identityId: req.params.identityId,
|
||||||
|
actor: req.permission.type,
|
||||||
|
actorId: req.permission.id,
|
||||||
|
actorOrgId: req.permission.orgId,
|
||||||
|
actorAuthMethod: req.permission.authMethod
|
||||||
|
});
|
||||||
|
|
||||||
|
await server.services.auditLog.createAuditLog({
|
||||||
|
...req.auditLogInfo,
|
||||||
|
orgId: identityOciAuth.orgId,
|
||||||
|
event: {
|
||||||
|
type: EventType.GET_IDENTITY_OCI_AUTH,
|
||||||
|
metadata: {
|
||||||
|
identityId: identityOciAuth.identityId
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return { identityOciAuth };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: "DELETE",
|
||||||
|
url: "/oci-auth/identities/:identityId",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
|
schema: {
|
||||||
|
hide: false,
|
||||||
|
tags: [ApiDocsTags.OciAuth],
|
||||||
|
description: "Delete OCI Auth configuration on identity",
|
||||||
|
security: [
|
||||||
|
{
|
||||||
|
bearerAuth: []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
params: z.object({
|
||||||
|
identityId: z.string().describe(OCI_AUTH.REVOKE.identityId)
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
identityOciAuth: IdentityOciAuthsSchema
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handler: async (req) => {
|
||||||
|
const identityOciAuth = await server.services.identityOciAuth.revokeIdentityOciAuth({
|
||||||
|
actor: req.permission.type,
|
||||||
|
actorId: req.permission.id,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
|
actorOrgId: req.permission.orgId,
|
||||||
|
identityId: req.params.identityId
|
||||||
|
});
|
||||||
|
|
||||||
|
await server.services.auditLog.createAuditLog({
|
||||||
|
...req.auditLogInfo,
|
||||||
|
orgId: identityOciAuth.orgId,
|
||||||
|
event: {
|
||||||
|
type: EventType.REVOKE_IDENTITY_OCI_AUTH,
|
||||||
|
metadata: {
|
||||||
|
identityId: identityOciAuth.identityId
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return { identityOciAuth };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
@@ -52,7 +52,8 @@ export const registerIdentityRouter = async (server: FastifyZodProvider) => {
|
|||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
identity: IdentitiesSchema.extend({
|
identity: IdentitiesSchema.extend({
|
||||||
authMethods: z.array(z.string())
|
authMethods: z.array(z.string()),
|
||||||
|
metadata: z.object({ id: z.string(), key: z.string(), value: z.string() }).array()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -123,7 +124,9 @@ export const registerIdentityRouter = async (server: FastifyZodProvider) => {
|
|||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
identity: IdentitiesSchema
|
identity: IdentitiesSchema.extend({
|
||||||
|
metadata: z.object({ id: z.string(), key: z.string(), value: z.string() }).array()
|
||||||
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -227,8 +230,8 @@ export const registerIdentityRouter = async (server: FastifyZodProvider) => {
|
|||||||
identity: IdentityOrgMembershipsSchema.extend({
|
identity: IdentityOrgMembershipsSchema.extend({
|
||||||
metadata: z
|
metadata: z
|
||||||
.object({
|
.object({
|
||||||
key: z.string().trim().min(1),
|
|
||||||
id: z.string().trim().min(1),
|
id: z.string().trim().min(1),
|
||||||
|
key: z.string().trim().min(1),
|
||||||
value: z.string().trim().min(1)
|
value: z.string().trim().min(1)
|
||||||
})
|
})
|
||||||
.array()
|
.array()
|
||||||
|
@@ -20,6 +20,7 @@ import { registerIdentityGcpAuthRouter } from "./identity-gcp-auth-router";
|
|||||||
import { registerIdentityJwtAuthRouter } from "./identity-jwt-auth-router";
|
import { registerIdentityJwtAuthRouter } from "./identity-jwt-auth-router";
|
||||||
import { registerIdentityKubernetesRouter } from "./identity-kubernetes-auth-router";
|
import { registerIdentityKubernetesRouter } from "./identity-kubernetes-auth-router";
|
||||||
import { registerIdentityLdapAuthRouter } from "./identity-ldap-auth-router";
|
import { registerIdentityLdapAuthRouter } from "./identity-ldap-auth-router";
|
||||||
|
import { registerIdentityOciAuthRouter } from "./identity-oci-auth-router";
|
||||||
import { registerIdentityOidcAuthRouter } from "./identity-oidc-auth-router";
|
import { registerIdentityOidcAuthRouter } from "./identity-oidc-auth-router";
|
||||||
import { registerIdentityRouter } from "./identity-router";
|
import { registerIdentityRouter } from "./identity-router";
|
||||||
import { registerIdentityTokenAuthRouter } from "./identity-token-auth-router";
|
import { registerIdentityTokenAuthRouter } from "./identity-token-auth-router";
|
||||||
@@ -33,6 +34,7 @@ import { registerOrgRouter } from "./organization-router";
|
|||||||
import { registerPasswordRouter } from "./password-router";
|
import { registerPasswordRouter } from "./password-router";
|
||||||
import { registerPkiAlertRouter } from "./pki-alert-router";
|
import { registerPkiAlertRouter } from "./pki-alert-router";
|
||||||
import { registerPkiCollectionRouter } from "./pki-collection-router";
|
import { registerPkiCollectionRouter } from "./pki-collection-router";
|
||||||
|
import { registerPkiSubscriberRouter } from "./pki-subscriber-router";
|
||||||
import { registerProjectEnvRouter } from "./project-env-router";
|
import { registerProjectEnvRouter } from "./project-env-router";
|
||||||
import { registerProjectKeyRouter } from "./project-key-router";
|
import { registerProjectKeyRouter } from "./project-key-router";
|
||||||
import { registerProjectMembershipRouter } from "./project-membership-router";
|
import { registerProjectMembershipRouter } from "./project-membership-router";
|
||||||
@@ -62,6 +64,7 @@ export const registerV1Routes = async (server: FastifyZodProvider) => {
|
|||||||
await authRouter.register(registerIdentityAccessTokenRouter);
|
await authRouter.register(registerIdentityAccessTokenRouter);
|
||||||
await authRouter.register(registerIdentityAwsAuthRouter);
|
await authRouter.register(registerIdentityAwsAuthRouter);
|
||||||
await authRouter.register(registerIdentityAzureAuthRouter);
|
await authRouter.register(registerIdentityAzureAuthRouter);
|
||||||
|
await authRouter.register(registerIdentityOciAuthRouter);
|
||||||
await authRouter.register(registerIdentityOidcAuthRouter);
|
await authRouter.register(registerIdentityOidcAuthRouter);
|
||||||
await authRouter.register(registerIdentityJwtAuthRouter);
|
await authRouter.register(registerIdentityJwtAuthRouter);
|
||||||
await authRouter.register(registerIdentityLdapAuthRouter);
|
await authRouter.register(registerIdentityLdapAuthRouter);
|
||||||
@@ -105,6 +108,7 @@ export const registerV1Routes = async (server: FastifyZodProvider) => {
|
|||||||
await pkiRouter.register(registerCertificateTemplateRouter, { prefix: "/certificate-templates" });
|
await pkiRouter.register(registerCertificateTemplateRouter, { prefix: "/certificate-templates" });
|
||||||
await pkiRouter.register(registerPkiAlertRouter, { prefix: "/alerts" });
|
await pkiRouter.register(registerPkiAlertRouter, { prefix: "/alerts" });
|
||||||
await pkiRouter.register(registerPkiCollectionRouter, { prefix: "/collections" });
|
await pkiRouter.register(registerPkiCollectionRouter, { prefix: "/collections" });
|
||||||
|
await pkiRouter.register(registerPkiSubscriberRouter, { prefix: "/subscribers" });
|
||||||
},
|
},
|
||||||
{ prefix: "/pki" }
|
{ prefix: "/pki" }
|
||||||
);
|
);
|
||||||
|
@@ -275,6 +275,23 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => {
|
|||||||
},
|
},
|
||||||
{ message: "Duration value must be at least 1" }
|
{ message: "Duration value must be at least 1" }
|
||||||
)
|
)
|
||||||
|
.optional(),
|
||||||
|
secretsProductEnabled: z.boolean().optional(),
|
||||||
|
pkiProductEnabled: z.boolean().optional(),
|
||||||
|
kmsProductEnabled: z.boolean().optional(),
|
||||||
|
sshProductEnabled: z.boolean().optional(),
|
||||||
|
scannerProductEnabled: z.boolean().optional(),
|
||||||
|
shareSecretsProductEnabled: z.boolean().optional(),
|
||||||
|
maxSharedSecretLifetime: z
|
||||||
|
.number()
|
||||||
|
.min(300, "Max Shared Secret lifetime cannot be under 5 minutes")
|
||||||
|
.max(2592000, "Max Shared Secret lifetime cannot exceed 30 days")
|
||||||
|
.optional(),
|
||||||
|
maxSharedSecretViewLimit: z
|
||||||
|
.number()
|
||||||
|
.min(1, "Max Shared Secret view count cannot be lower than 1")
|
||||||
|
.max(1000, "Max Shared Secret view count cannot exceed 1000")
|
||||||
|
.nullable()
|
||||||
.optional()
|
.optional()
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
|
478
backend/src/server/routes/v1/pki-subscriber-router.ts
Normal file
478
backend/src/server/routes/v1/pki-subscriber-router.ts
Normal file
@@ -0,0 +1,478 @@
|
|||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { CertificatesSchema } from "@app/db/schemas";
|
||||||
|
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
|
||||||
|
import { ApiDocsTags, PKI_SUBSCRIBERS } from "@app/lib/api-docs";
|
||||||
|
import { ms } from "@app/lib/ms";
|
||||||
|
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
||||||
|
import { slugSchema } from "@app/server/lib/schemas";
|
||||||
|
import { getTelemetryDistinctId } from "@app/server/lib/telemetry";
|
||||||
|
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||||
|
import { AuthMode } from "@app/services/auth/auth-type";
|
||||||
|
import { CertExtendedKeyUsage, CertKeyUsage } from "@app/services/certificate/certificate-types";
|
||||||
|
import { validateAltNameField } from "@app/services/certificate-authority/certificate-authority-validators";
|
||||||
|
import { sanitizedPkiSubscriber } from "@app/services/pki-subscriber/pki-subscriber-schema";
|
||||||
|
import { PkiSubscriberStatus } from "@app/services/pki-subscriber/pki-subscriber-types";
|
||||||
|
import { PostHogEventTypes } from "@app/services/telemetry/telemetry-types";
|
||||||
|
|
||||||
|
export const registerPkiSubscriberRouter = async (server: FastifyZodProvider) => {
|
||||||
|
server.route({
|
||||||
|
method: "GET",
|
||||||
|
url: "/:subscriberName",
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
hide: false,
|
||||||
|
tags: [ApiDocsTags.PkiSubscribers],
|
||||||
|
description: "Get PKI Subscriber",
|
||||||
|
params: z.object({
|
||||||
|
subscriberName: z.string().describe(PKI_SUBSCRIBERS.GET.subscriberName)
|
||||||
|
}),
|
||||||
|
querystring: z.object({
|
||||||
|
projectId: z.string().describe(PKI_SUBSCRIBERS.GET.projectId)
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: sanitizedPkiSubscriber
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const subscriber = await server.services.pkiSubscriber.getSubscriber({
|
||||||
|
subscriberName: req.params.subscriberName,
|
||||||
|
projectId: req.query.projectId,
|
||||||
|
actor: req.permission.type,
|
||||||
|
actorId: req.permission.id,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
|
actorOrgId: req.permission.orgId
|
||||||
|
});
|
||||||
|
|
||||||
|
await server.services.auditLog.createAuditLog({
|
||||||
|
...req.auditLogInfo,
|
||||||
|
projectId: subscriber.projectId,
|
||||||
|
event: {
|
||||||
|
type: EventType.GET_PKI_SUBSCRIBER,
|
||||||
|
metadata: {
|
||||||
|
pkiSubscriberId: subscriber.id,
|
||||||
|
name: subscriber.name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return subscriber;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: "POST",
|
||||||
|
url: "/",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
hide: false,
|
||||||
|
tags: [ApiDocsTags.PkiSubscribers],
|
||||||
|
description: "Create PKI Subscriber",
|
||||||
|
body: z.object({
|
||||||
|
projectId: z.string().trim().describe(PKI_SUBSCRIBERS.CREATE.projectId),
|
||||||
|
caId: z
|
||||||
|
.string()
|
||||||
|
.trim()
|
||||||
|
.uuid("CA ID must be a valid UUID")
|
||||||
|
.min(1, "CA ID is required")
|
||||||
|
.describe(PKI_SUBSCRIBERS.CREATE.caId),
|
||||||
|
name: slugSchema({ min: 1, max: 64, field: "name" }).describe(PKI_SUBSCRIBERS.CREATE.name),
|
||||||
|
commonName: z.string().trim().min(1).describe(PKI_SUBSCRIBERS.CREATE.commonName),
|
||||||
|
status: z
|
||||||
|
.nativeEnum(PkiSubscriberStatus)
|
||||||
|
.default(PkiSubscriberStatus.ACTIVE)
|
||||||
|
.describe(PKI_SUBSCRIBERS.CREATE.status),
|
||||||
|
ttl: z
|
||||||
|
.string()
|
||||||
|
.trim()
|
||||||
|
.refine((val) => ms(val) > 0, "TTL must be a positive number")
|
||||||
|
.describe(PKI_SUBSCRIBERS.CREATE.ttl),
|
||||||
|
subjectAlternativeNames: validateAltNameField
|
||||||
|
.array()
|
||||||
|
.default([])
|
||||||
|
.transform((arr) => Array.from(new Set(arr)))
|
||||||
|
.describe(PKI_SUBSCRIBERS.CREATE.subjectAlternativeNames),
|
||||||
|
keyUsages: z
|
||||||
|
.nativeEnum(CertKeyUsage)
|
||||||
|
.array()
|
||||||
|
.default([CertKeyUsage.DIGITAL_SIGNATURE, CertKeyUsage.KEY_ENCIPHERMENT])
|
||||||
|
.transform((arr) => Array.from(new Set(arr)))
|
||||||
|
.describe(PKI_SUBSCRIBERS.CREATE.keyUsages),
|
||||||
|
extendedKeyUsages: z
|
||||||
|
.nativeEnum(CertExtendedKeyUsage)
|
||||||
|
.array()
|
||||||
|
.default([])
|
||||||
|
.transform((arr) => Array.from(new Set(arr)))
|
||||||
|
.describe(PKI_SUBSCRIBERS.CREATE.extendedKeyUsages)
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: sanitizedPkiSubscriber
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const subscriber = await server.services.pkiSubscriber.createSubscriber({
|
||||||
|
...req.body,
|
||||||
|
actor: req.permission.type,
|
||||||
|
actorId: req.permission.id,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
|
actorOrgId: req.permission.orgId
|
||||||
|
});
|
||||||
|
|
||||||
|
await server.services.auditLog.createAuditLog({
|
||||||
|
...req.auditLogInfo,
|
||||||
|
projectId: subscriber.projectId,
|
||||||
|
event: {
|
||||||
|
type: EventType.CREATE_PKI_SUBSCRIBER,
|
||||||
|
metadata: {
|
||||||
|
pkiSubscriberId: subscriber.id,
|
||||||
|
caId: subscriber.caId ?? undefined,
|
||||||
|
name: subscriber.name,
|
||||||
|
commonName: subscriber.commonName,
|
||||||
|
ttl: subscriber.ttl,
|
||||||
|
subjectAlternativeNames: subscriber.subjectAlternativeNames,
|
||||||
|
keyUsages: subscriber.keyUsages as CertKeyUsage[],
|
||||||
|
extendedKeyUsages: subscriber.extendedKeyUsages as CertExtendedKeyUsage[]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return subscriber;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: "PATCH",
|
||||||
|
url: "/:subscriberName",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
|
schema: {
|
||||||
|
hide: false,
|
||||||
|
tags: [ApiDocsTags.PkiSubscribers],
|
||||||
|
description: "Update PKI Subscriber",
|
||||||
|
params: z.object({
|
||||||
|
subscriberName: z.string().trim().describe(PKI_SUBSCRIBERS.UPDATE.subscriberName)
|
||||||
|
}),
|
||||||
|
body: z.object({
|
||||||
|
projectId: z.string().trim().describe(PKI_SUBSCRIBERS.UPDATE.projectId),
|
||||||
|
caId: z
|
||||||
|
.string()
|
||||||
|
.trim()
|
||||||
|
.uuid("CA ID must be a valid UUID")
|
||||||
|
.min(1, "CA ID is required")
|
||||||
|
.optional()
|
||||||
|
.describe(PKI_SUBSCRIBERS.UPDATE.caId),
|
||||||
|
name: slugSchema({ min: 1, max: 64, field: "name" }).describe(PKI_SUBSCRIBERS.UPDATE.name).optional(),
|
||||||
|
commonName: z.string().trim().min(1).describe(PKI_SUBSCRIBERS.UPDATE.commonName).optional(),
|
||||||
|
status: z.nativeEnum(PkiSubscriberStatus).optional().describe(PKI_SUBSCRIBERS.UPDATE.status),
|
||||||
|
subjectAlternativeNames: validateAltNameField
|
||||||
|
.array()
|
||||||
|
.optional()
|
||||||
|
.describe(PKI_SUBSCRIBERS.UPDATE.subjectAlternativeNames),
|
||||||
|
ttl: z
|
||||||
|
.string()
|
||||||
|
.trim()
|
||||||
|
.refine((val) => ms(val) > 0, "TTL must be a positive number")
|
||||||
|
.optional()
|
||||||
|
.describe(PKI_SUBSCRIBERS.UPDATE.ttl),
|
||||||
|
keyUsages: z
|
||||||
|
.nativeEnum(CertKeyUsage)
|
||||||
|
.array()
|
||||||
|
.transform((arr) => Array.from(new Set(arr)))
|
||||||
|
.optional()
|
||||||
|
.describe(PKI_SUBSCRIBERS.UPDATE.keyUsages),
|
||||||
|
extendedKeyUsages: z
|
||||||
|
.nativeEnum(CertExtendedKeyUsage)
|
||||||
|
.array()
|
||||||
|
.transform((arr) => Array.from(new Set(arr)))
|
||||||
|
.optional()
|
||||||
|
.describe(PKI_SUBSCRIBERS.UPDATE.extendedKeyUsages)
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: sanitizedPkiSubscriber
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handler: async (req) => {
|
||||||
|
const subscriber = await server.services.pkiSubscriber.updateSubscriber({
|
||||||
|
subscriberName: req.params.subscriberName,
|
||||||
|
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: subscriber.projectId,
|
||||||
|
event: {
|
||||||
|
type: EventType.UPDATE_PKI_SUBSCRIBER,
|
||||||
|
metadata: {
|
||||||
|
pkiSubscriberId: subscriber.id,
|
||||||
|
caId: subscriber.caId ?? undefined,
|
||||||
|
name: subscriber.name,
|
||||||
|
commonName: subscriber.commonName,
|
||||||
|
ttl: subscriber.ttl,
|
||||||
|
subjectAlternativeNames: subscriber.subjectAlternativeNames,
|
||||||
|
keyUsages: subscriber.keyUsages as CertKeyUsage[],
|
||||||
|
extendedKeyUsages: subscriber.extendedKeyUsages as CertExtendedKeyUsage[]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return subscriber;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: "DELETE",
|
||||||
|
url: "/:subscriberName",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
hide: false,
|
||||||
|
tags: [ApiDocsTags.PkiSubscribers],
|
||||||
|
description: "Delete PKI Subscriber",
|
||||||
|
params: z.object({
|
||||||
|
subscriberName: z.string().describe(PKI_SUBSCRIBERS.DELETE.subscriberName)
|
||||||
|
}),
|
||||||
|
body: z.object({
|
||||||
|
projectId: z.string().trim().describe(PKI_SUBSCRIBERS.DELETE.projectId)
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: sanitizedPkiSubscriber
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const subscriber = await server.services.pkiSubscriber.deleteSubscriber({
|
||||||
|
subscriberName: req.params.subscriberName,
|
||||||
|
projectId: req.body.projectId,
|
||||||
|
actor: req.permission.type,
|
||||||
|
actorId: req.permission.id,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
|
actorOrgId: req.permission.orgId
|
||||||
|
});
|
||||||
|
|
||||||
|
await server.services.auditLog.createAuditLog({
|
||||||
|
...req.auditLogInfo,
|
||||||
|
projectId: subscriber.projectId,
|
||||||
|
event: {
|
||||||
|
type: EventType.DELETE_PKI_SUBSCRIBER,
|
||||||
|
metadata: {
|
||||||
|
pkiSubscriberId: subscriber.id,
|
||||||
|
name: subscriber.name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return subscriber;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: "POST",
|
||||||
|
url: "/:subscriberName/issue-certificate",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
|
schema: {
|
||||||
|
hide: false,
|
||||||
|
tags: [ApiDocsTags.PkiSubscribers],
|
||||||
|
description: "Issue certificate",
|
||||||
|
params: z.object({
|
||||||
|
subscriberName: z.string().describe(PKI_SUBSCRIBERS.ISSUE_CERT.subscriberName)
|
||||||
|
}),
|
||||||
|
body: z.object({
|
||||||
|
projectId: z.string().trim().describe(PKI_SUBSCRIBERS.ISSUE_CERT.projectId)
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
certificate: z.string().trim().describe(PKI_SUBSCRIBERS.ISSUE_CERT.certificate),
|
||||||
|
issuingCaCertificate: z.string().trim().describe(PKI_SUBSCRIBERS.ISSUE_CERT.issuingCaCertificate),
|
||||||
|
certificateChain: z.string().trim().describe(PKI_SUBSCRIBERS.ISSUE_CERT.certificateChain),
|
||||||
|
privateKey: z.string().trim().describe(PKI_SUBSCRIBERS.ISSUE_CERT.privateKey),
|
||||||
|
serialNumber: z.string().trim().describe(PKI_SUBSCRIBERS.ISSUE_CERT.serialNumber)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handler: async (req) => {
|
||||||
|
const { certificate, certificateChain, issuingCaCertificate, privateKey, serialNumber, subscriber } =
|
||||||
|
await server.services.pkiSubscriber.issueSubscriberCert({
|
||||||
|
subscriberName: req.params.subscriberName,
|
||||||
|
projectId: req.body.projectId,
|
||||||
|
actor: req.permission.type,
|
||||||
|
actorId: req.permission.id,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
|
actorOrgId: req.permission.orgId
|
||||||
|
});
|
||||||
|
|
||||||
|
await server.services.auditLog.createAuditLog({
|
||||||
|
...req.auditLogInfo,
|
||||||
|
projectId: subscriber.projectId,
|
||||||
|
event: {
|
||||||
|
type: EventType.ISSUE_PKI_SUBSCRIBER_CERT,
|
||||||
|
metadata: {
|
||||||
|
subscriberId: subscriber.id,
|
||||||
|
name: subscriber.name,
|
||||||
|
serialNumber
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
await server.services.telemetry.sendPostHogEvents({
|
||||||
|
event: PostHogEventTypes.IssueCert,
|
||||||
|
distinctId: getTelemetryDistinctId(req),
|
||||||
|
properties: {
|
||||||
|
subscriberId: subscriber.id,
|
||||||
|
commonName: subscriber.commonName,
|
||||||
|
...req.auditLogInfo
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
certificate,
|
||||||
|
certificateChain,
|
||||||
|
issuingCaCertificate,
|
||||||
|
privateKey,
|
||||||
|
serialNumber
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: "POST",
|
||||||
|
url: "/:subscriberName/sign-certificate",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
|
schema: {
|
||||||
|
hide: false,
|
||||||
|
tags: [ApiDocsTags.PkiSubscribers],
|
||||||
|
description: "Sign certificate",
|
||||||
|
params: z.object({
|
||||||
|
subscriberName: z.string().describe(PKI_SUBSCRIBERS.SIGN_CERT.subscriberName)
|
||||||
|
}),
|
||||||
|
body: z.object({
|
||||||
|
projectId: z.string().trim().describe(PKI_SUBSCRIBERS.SIGN_CERT.projectId),
|
||||||
|
csr: z.string().trim().min(1).max(3000).describe(PKI_SUBSCRIBERS.SIGN_CERT.csr)
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
certificate: z.string().trim().describe(PKI_SUBSCRIBERS.SIGN_CERT.certificate),
|
||||||
|
issuingCaCertificate: z.string().trim().describe(PKI_SUBSCRIBERS.SIGN_CERT.issuingCaCertificate),
|
||||||
|
certificateChain: z.string().trim().describe(PKI_SUBSCRIBERS.SIGN_CERT.certificateChain),
|
||||||
|
serialNumber: z.string().trim().describe(PKI_SUBSCRIBERS.ISSUE_CERT.serialNumber)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handler: async (req) => {
|
||||||
|
const { certificate, certificateChain, issuingCaCertificate, serialNumber, subscriber } =
|
||||||
|
await server.services.pkiSubscriber.signSubscriberCert({
|
||||||
|
subscriberName: req.params.subscriberName,
|
||||||
|
projectId: req.body.projectId,
|
||||||
|
csr: req.body.csr,
|
||||||
|
actor: req.permission.type,
|
||||||
|
actorId: req.permission.id,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
|
actorOrgId: req.permission.orgId
|
||||||
|
});
|
||||||
|
|
||||||
|
await server.services.auditLog.createAuditLog({
|
||||||
|
...req.auditLogInfo,
|
||||||
|
projectId: subscriber.projectId,
|
||||||
|
event: {
|
||||||
|
type: EventType.SIGN_PKI_SUBSCRIBER_CERT,
|
||||||
|
metadata: {
|
||||||
|
subscriberId: subscriber.id,
|
||||||
|
name: subscriber.name,
|
||||||
|
serialNumber
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
await server.services.telemetry.sendPostHogEvents({
|
||||||
|
event: PostHogEventTypes.SignCert,
|
||||||
|
distinctId: getTelemetryDistinctId(req),
|
||||||
|
properties: {
|
||||||
|
subscriberId: subscriber.id,
|
||||||
|
commonName: subscriber.commonName,
|
||||||
|
...req.auditLogInfo
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
certificate,
|
||||||
|
certificateChain,
|
||||||
|
issuingCaCertificate,
|
||||||
|
serialNumber
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: "GET",
|
||||||
|
url: "/:subscriberName/certificates",
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
hide: false,
|
||||||
|
tags: [ApiDocsTags.PkiSubscribers],
|
||||||
|
description: "List PKI Subscriber certificates",
|
||||||
|
params: z.object({
|
||||||
|
subscriberName: z.string().describe(PKI_SUBSCRIBERS.GET.subscriberName)
|
||||||
|
}),
|
||||||
|
querystring: z.object({
|
||||||
|
projectId: z.string().trim().describe(PKI_SUBSCRIBERS.LIST_CERTS.projectId),
|
||||||
|
offset: z.coerce.number().min(0).max(100).default(0).describe(PKI_SUBSCRIBERS.LIST_CERTS.offset),
|
||||||
|
limit: z.coerce.number().min(1).max(100).default(25).describe(PKI_SUBSCRIBERS.LIST_CERTS.limit)
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
certificates: z.array(CertificatesSchema),
|
||||||
|
totalCount: z.number()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const { totalCount, certificates } = await server.services.pkiSubscriber.listSubscriberCerts({
|
||||||
|
subscriberName: req.params.subscriberName,
|
||||||
|
actor: req.permission.type,
|
||||||
|
actorId: req.permission.id,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
|
actorOrgId: req.permission.orgId,
|
||||||
|
...req.query
|
||||||
|
});
|
||||||
|
|
||||||
|
await server.services.auditLog.createAuditLog({
|
||||||
|
...req.auditLogInfo,
|
||||||
|
projectId: req.query.projectId,
|
||||||
|
event: {
|
||||||
|
type: EventType.LIST_PKI_SUBSCRIBER_CERTS,
|
||||||
|
metadata: {
|
||||||
|
subscriberId: req.params.subscriberName,
|
||||||
|
name: req.params.subscriberName,
|
||||||
|
projectId: req.query.projectId
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
certificates,
|
||||||
|
totalCount
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
@@ -346,7 +346,8 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
|
|||||||
"Project slug can only contain lowercase letters and numbers, with optional single hyphens (-) or underscores (_) between words. Cannot start or end with a hyphen or underscore."
|
"Project slug can only contain lowercase letters and numbers, with optional single hyphens (-) or underscores (_) between words. Cannot start or end with a hyphen or underscore."
|
||||||
})
|
})
|
||||||
.optional()
|
.optional()
|
||||||
.describe(PROJECTS.UPDATE.slug)
|
.describe(PROJECTS.UPDATE.slug),
|
||||||
|
secretSharing: z.boolean().optional().describe(PROJECTS.UPDATE.secretSharing)
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
@@ -366,7 +367,8 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
|
|||||||
description: req.body.description,
|
description: req.body.description,
|
||||||
autoCapitalization: req.body.autoCapitalization,
|
autoCapitalization: req.body.autoCapitalization,
|
||||||
hasDeleteProtection: req.body.hasDeleteProtection,
|
hasDeleteProtection: req.body.hasDeleteProtection,
|
||||||
slug: req.body.slug
|
slug: req.body.slug,
|
||||||
|
secretSharing: req.body.secretSharing
|
||||||
},
|
},
|
||||||
actorAuthMethod: req.permission.authMethod,
|
actorAuthMethod: req.permission.authMethod,
|
||||||
actorId: req.permission.id,
|
actorId: req.permission.id,
|
||||||
@@ -511,7 +513,7 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onRequest: verifyAuth([AuthMode.JWT]),
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
handler: async (req) => {
|
handler: async (req) => {
|
||||||
const workspace = await server.services.project.updateAuditLogsRetention({
|
const workspace = await server.services.project.updateAuditLogsRetention({
|
||||||
actorId: req.permission.id,
|
actorId: req.permission.id,
|
||||||
|
@@ -62,7 +62,9 @@ export const registerSecretSharingRouter = async (server: FastifyZodProvider) =>
|
|||||||
}),
|
}),
|
||||||
body: z.object({
|
body: z.object({
|
||||||
hashedHex: z.string().min(1).optional(),
|
hashedHex: z.string().min(1).optional(),
|
||||||
password: z.string().optional()
|
password: z.string().optional(),
|
||||||
|
email: z.string().optional(),
|
||||||
|
hash: z.string().optional()
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
@@ -88,7 +90,9 @@ export const registerSecretSharingRouter = async (server: FastifyZodProvider) =>
|
|||||||
sharedSecretId: req.params.id,
|
sharedSecretId: req.params.id,
|
||||||
hashedHex: req.body.hashedHex,
|
hashedHex: req.body.hashedHex,
|
||||||
password: req.body.password,
|
password: req.body.password,
|
||||||
orgId: req.permission?.orgId
|
orgId: req.permission?.orgId,
|
||||||
|
email: req.body.email,
|
||||||
|
hash: req.body.hash
|
||||||
});
|
});
|
||||||
|
|
||||||
if (sharedSecret.secret?.orgId) {
|
if (sharedSecret.secret?.orgId) {
|
||||||
@@ -151,7 +155,8 @@ export const registerSecretSharingRouter = async (server: FastifyZodProvider) =>
|
|||||||
secretValue: z.string(),
|
secretValue: z.string(),
|
||||||
expiresAt: z.string(),
|
expiresAt: z.string(),
|
||||||
expiresAfterViews: z.number().min(1).optional(),
|
expiresAfterViews: z.number().min(1).optional(),
|
||||||
accessType: z.nativeEnum(SecretSharingAccessType).default(SecretSharingAccessType.Organization)
|
accessType: z.nativeEnum(SecretSharingAccessType).default(SecretSharingAccessType.Organization),
|
||||||
|
emails: z.string().email().array().max(100).optional()
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
|
@@ -10,6 +10,7 @@ import { registerGcpSyncRouter } from "./gcp-sync-router";
|
|||||||
import { registerGitHubSyncRouter } from "./github-sync-router";
|
import { registerGitHubSyncRouter } from "./github-sync-router";
|
||||||
import { registerHCVaultSyncRouter } from "./hc-vault-sync-router";
|
import { registerHCVaultSyncRouter } from "./hc-vault-sync-router";
|
||||||
import { registerHumanitecSyncRouter } from "./humanitec-sync-router";
|
import { registerHumanitecSyncRouter } from "./humanitec-sync-router";
|
||||||
|
import { registerOCIVaultSyncRouter } from "./oci-vault-sync-router";
|
||||||
import { registerTeamCitySyncRouter } from "./teamcity-sync-router";
|
import { registerTeamCitySyncRouter } from "./teamcity-sync-router";
|
||||||
import { registerTerraformCloudSyncRouter } from "./terraform-cloud-sync-router";
|
import { registerTerraformCloudSyncRouter } from "./terraform-cloud-sync-router";
|
||||||
import { registerVercelSyncRouter } from "./vercel-sync-router";
|
import { registerVercelSyncRouter } from "./vercel-sync-router";
|
||||||
@@ -31,5 +32,6 @@ export const SECRET_SYNC_REGISTER_ROUTER_MAP: Record<SecretSync, (server: Fastif
|
|||||||
[SecretSync.Vercel]: registerVercelSyncRouter,
|
[SecretSync.Vercel]: registerVercelSyncRouter,
|
||||||
[SecretSync.Windmill]: registerWindmillSyncRouter,
|
[SecretSync.Windmill]: registerWindmillSyncRouter,
|
||||||
[SecretSync.HCVault]: registerHCVaultSyncRouter,
|
[SecretSync.HCVault]: registerHCVaultSyncRouter,
|
||||||
[SecretSync.TeamCity]: registerTeamCitySyncRouter
|
[SecretSync.TeamCity]: registerTeamCitySyncRouter,
|
||||||
|
[SecretSync.OCIVault]: registerOCIVaultSyncRouter
|
||||||
};
|
};
|
||||||
|
@@ -0,0 +1,17 @@
|
|||||||
|
import {
|
||||||
|
CreateOCIVaultSyncSchema,
|
||||||
|
OCIVaultSyncSchema,
|
||||||
|
UpdateOCIVaultSyncSchema
|
||||||
|
} from "@app/services/secret-sync/oci-vault";
|
||||||
|
import { SecretSync } from "@app/services/secret-sync/secret-sync-enums";
|
||||||
|
|
||||||
|
import { registerSyncSecretsEndpoints } from "./secret-sync-endpoints";
|
||||||
|
|
||||||
|
export const registerOCIVaultSyncRouter = async (server: FastifyZodProvider) =>
|
||||||
|
registerSyncSecretsEndpoints({
|
||||||
|
destination: SecretSync.OCIVault,
|
||||||
|
server,
|
||||||
|
responseSchema: OCIVaultSyncSchema,
|
||||||
|
createSchema: CreateOCIVaultSyncSchema,
|
||||||
|
updateSchema: UpdateOCIVaultSyncSchema
|
||||||
|
});
|
@@ -24,6 +24,7 @@ import { GcpSyncListItemSchema, GcpSyncSchema } from "@app/services/secret-sync/
|
|||||||
import { GitHubSyncListItemSchema, GitHubSyncSchema } from "@app/services/secret-sync/github";
|
import { GitHubSyncListItemSchema, GitHubSyncSchema } from "@app/services/secret-sync/github";
|
||||||
import { HCVaultSyncListItemSchema, HCVaultSyncSchema } from "@app/services/secret-sync/hc-vault";
|
import { HCVaultSyncListItemSchema, HCVaultSyncSchema } from "@app/services/secret-sync/hc-vault";
|
||||||
import { HumanitecSyncListItemSchema, HumanitecSyncSchema } from "@app/services/secret-sync/humanitec";
|
import { HumanitecSyncListItemSchema, HumanitecSyncSchema } from "@app/services/secret-sync/humanitec";
|
||||||
|
import { OCIVaultSyncListItemSchema, OCIVaultSyncSchema } from "@app/services/secret-sync/oci-vault";
|
||||||
import { TeamCitySyncListItemSchema, TeamCitySyncSchema } from "@app/services/secret-sync/teamcity";
|
import { TeamCitySyncListItemSchema, TeamCitySyncSchema } from "@app/services/secret-sync/teamcity";
|
||||||
import { TerraformCloudSyncListItemSchema, TerraformCloudSyncSchema } from "@app/services/secret-sync/terraform-cloud";
|
import { TerraformCloudSyncListItemSchema, TerraformCloudSyncSchema } from "@app/services/secret-sync/terraform-cloud";
|
||||||
import { VercelSyncListItemSchema, VercelSyncSchema } from "@app/services/secret-sync/vercel";
|
import { VercelSyncListItemSchema, VercelSyncSchema } from "@app/services/secret-sync/vercel";
|
||||||
@@ -43,7 +44,8 @@ const SecretSyncSchema = z.discriminatedUnion("destination", [
|
|||||||
VercelSyncSchema,
|
VercelSyncSchema,
|
||||||
WindmillSyncSchema,
|
WindmillSyncSchema,
|
||||||
HCVaultSyncSchema,
|
HCVaultSyncSchema,
|
||||||
TeamCitySyncSchema
|
TeamCitySyncSchema,
|
||||||
|
OCIVaultSyncSchema
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const SecretSyncOptionsSchema = z.discriminatedUnion("destination", [
|
const SecretSyncOptionsSchema = z.discriminatedUnion("destination", [
|
||||||
@@ -60,7 +62,8 @@ const SecretSyncOptionsSchema = z.discriminatedUnion("destination", [
|
|||||||
VercelSyncListItemSchema,
|
VercelSyncListItemSchema,
|
||||||
WindmillSyncListItemSchema,
|
WindmillSyncListItemSchema,
|
||||||
HCVaultSyncListItemSchema,
|
HCVaultSyncListItemSchema,
|
||||||
TeamCitySyncListItemSchema
|
TeamCitySyncListItemSchema,
|
||||||
|
OCIVaultSyncListItemSchema
|
||||||
]);
|
]);
|
||||||
|
|
||||||
export const registerSecretSyncRouter = async (server: FastifyZodProvider) => {
|
export const registerSecretSyncRouter = async (server: FastifyZodProvider) => {
|
||||||
|
@@ -24,6 +24,7 @@ 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 { sanitizedCertificateTemplate } from "@app/services/certificate-template/certificate-template-schema";
|
||||||
|
import { sanitizedPkiSubscriber } from "@app/services/pki-subscriber/pki-subscriber-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";
|
||||||
|
|
||||||
@@ -490,6 +491,38 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: "GET",
|
||||||
|
url: "/:projectId/pki-subscribers",
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
hide: false,
|
||||||
|
tags: [ApiDocsTags.PkiSubscribers],
|
||||||
|
params: z.object({
|
||||||
|
projectId: z.string().trim().describe(PROJECTS.LIST_PKI_SUBSCRIBERS.projectId)
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
subscribers: z.array(sanitizedPkiSubscriber)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const subscribers = await server.services.project.listProjectPkiSubscribers({
|
||||||
|
actorId: req.permission.id,
|
||||||
|
actorOrgId: req.permission.orgId,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
|
actor: req.permission.type,
|
||||||
|
projectId: req.params.projectId
|
||||||
|
});
|
||||||
|
|
||||||
|
return { subscribers };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
server.route({
|
server.route({
|
||||||
method: "GET",
|
method: "GET",
|
||||||
url: "/:projectId/certificate-templates",
|
url: "/:projectId/certificate-templates",
|
||||||
@@ -628,6 +661,8 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
|
|||||||
rateLimit: readLimit
|
rateLimit: readLimit
|
||||||
},
|
},
|
||||||
schema: {
|
schema: {
|
||||||
|
hide: false,
|
||||||
|
tags: [ApiDocsTags.SshHosts],
|
||||||
params: z.object({
|
params: z.object({
|
||||||
projectId: z.string().trim().describe(PROJECTS.LIST_SSH_HOSTS.projectId)
|
projectId: z.string().trim().describe(PROJECTS.LIST_SSH_HOSTS.projectId)
|
||||||
}),
|
}),
|
||||||
@@ -666,6 +701,8 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
|
|||||||
rateLimit: readLimit
|
rateLimit: readLimit
|
||||||
},
|
},
|
||||||
schema: {
|
schema: {
|
||||||
|
hide: false,
|
||||||
|
tags: [ApiDocsTags.SshHostGroups],
|
||||||
params: z.object({
|
params: z.object({
|
||||||
projectId: z.string().trim().describe(PROJECTS.LIST_SSH_HOST_GROUPS.projectId)
|
projectId: z.string().trim().describe(PROJECTS.LIST_SSH_HOST_GROUPS.projectId)
|
||||||
}),
|
}),
|
||||||
|
@@ -16,7 +16,8 @@ export enum AppConnection {
|
|||||||
Auth0 = "auth0",
|
Auth0 = "auth0",
|
||||||
HCVault = "hashicorp-vault",
|
HCVault = "hashicorp-vault",
|
||||||
LDAP = "ldap",
|
LDAP = "ldap",
|
||||||
TeamCity = "teamcity"
|
TeamCity = "teamcity",
|
||||||
|
OCI = "oci"
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum AWSRegion {
|
export enum AWSRegion {
|
||||||
|
@@ -53,6 +53,7 @@ import {
|
|||||||
} from "./humanitec";
|
} from "./humanitec";
|
||||||
import { getLdapConnectionListItem, LdapConnectionMethod, validateLdapConnectionCredentials } from "./ldap";
|
import { getLdapConnectionListItem, LdapConnectionMethod, validateLdapConnectionCredentials } from "./ldap";
|
||||||
import { getMsSqlConnectionListItem, MsSqlConnectionMethod } from "./mssql";
|
import { getMsSqlConnectionListItem, MsSqlConnectionMethod } from "./mssql";
|
||||||
|
import { getOCIConnectionListItem, OCIConnectionMethod, validateOCIConnectionCredentials } from "./oci";
|
||||||
import { getPostgresConnectionListItem, PostgresConnectionMethod } from "./postgres";
|
import { getPostgresConnectionListItem, PostgresConnectionMethod } from "./postgres";
|
||||||
import {
|
import {
|
||||||
getTeamCityConnectionListItem,
|
getTeamCityConnectionListItem,
|
||||||
@@ -91,7 +92,8 @@ export const listAppConnectionOptions = () => {
|
|||||||
getAuth0ConnectionListItem(),
|
getAuth0ConnectionListItem(),
|
||||||
getHCVaultConnectionListItem(),
|
getHCVaultConnectionListItem(),
|
||||||
getLdapConnectionListItem(),
|
getLdapConnectionListItem(),
|
||||||
getTeamCityConnectionListItem()
|
getTeamCityConnectionListItem(),
|
||||||
|
getOCIConnectionListItem()
|
||||||
].sort((a, b) => a.name.localeCompare(b.name));
|
].sort((a, b) => a.name.localeCompare(b.name));
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -160,7 +162,8 @@ export const validateAppConnectionCredentials = async (
|
|||||||
[AppConnection.Windmill]: validateWindmillConnectionCredentials as TAppConnectionCredentialsValidator,
|
[AppConnection.Windmill]: validateWindmillConnectionCredentials as TAppConnectionCredentialsValidator,
|
||||||
[AppConnection.HCVault]: validateHCVaultConnectionCredentials as TAppConnectionCredentialsValidator,
|
[AppConnection.HCVault]: validateHCVaultConnectionCredentials as TAppConnectionCredentialsValidator,
|
||||||
[AppConnection.LDAP]: validateLdapConnectionCredentials as TAppConnectionCredentialsValidator,
|
[AppConnection.LDAP]: validateLdapConnectionCredentials as TAppConnectionCredentialsValidator,
|
||||||
[AppConnection.TeamCity]: validateTeamCityConnectionCredentials as TAppConnectionCredentialsValidator
|
[AppConnection.TeamCity]: validateTeamCityConnectionCredentials as TAppConnectionCredentialsValidator,
|
||||||
|
[AppConnection.OCI]: validateOCIConnectionCredentials as TAppConnectionCredentialsValidator
|
||||||
};
|
};
|
||||||
|
|
||||||
return VALIDATE_APP_CONNECTION_CREDENTIALS_MAP[appConnection.app](appConnection);
|
return VALIDATE_APP_CONNECTION_CREDENTIALS_MAP[appConnection.app](appConnection);
|
||||||
@@ -176,6 +179,7 @@ export const getAppConnectionMethodName = (method: TAppConnection["method"]) =>
|
|||||||
case GitHubConnectionMethod.OAuth:
|
case GitHubConnectionMethod.OAuth:
|
||||||
return "OAuth";
|
return "OAuth";
|
||||||
case AwsConnectionMethod.AccessKey:
|
case AwsConnectionMethod.AccessKey:
|
||||||
|
case OCIConnectionMethod.AccessKey:
|
||||||
return "Access Key";
|
return "Access Key";
|
||||||
case AwsConnectionMethod.AssumeRole:
|
case AwsConnectionMethod.AssumeRole:
|
||||||
return "Assume Role";
|
return "Assume Role";
|
||||||
@@ -250,5 +254,6 @@ export const TRANSITION_CONNECTION_CREDENTIALS_TO_PLATFORM: Record<
|
|||||||
[AppConnection.Auth0]: platformManagedCredentialsNotSupported,
|
[AppConnection.Auth0]: platformManagedCredentialsNotSupported,
|
||||||
[AppConnection.HCVault]: platformManagedCredentialsNotSupported,
|
[AppConnection.HCVault]: platformManagedCredentialsNotSupported,
|
||||||
[AppConnection.LDAP]: platformManagedCredentialsNotSupported, // we could support this in the future
|
[AppConnection.LDAP]: platformManagedCredentialsNotSupported, // we could support this in the future
|
||||||
[AppConnection.TeamCity]: platformManagedCredentialsNotSupported
|
[AppConnection.TeamCity]: platformManagedCredentialsNotSupported,
|
||||||
|
[AppConnection.OCI]: platformManagedCredentialsNotSupported
|
||||||
};
|
};
|
||||||
|
@@ -18,5 +18,6 @@ export const APP_CONNECTION_NAME_MAP: Record<AppConnection, string> = {
|
|||||||
[AppConnection.Auth0]: "Auth0",
|
[AppConnection.Auth0]: "Auth0",
|
||||||
[AppConnection.HCVault]: "Hashicorp Vault",
|
[AppConnection.HCVault]: "Hashicorp Vault",
|
||||||
[AppConnection.LDAP]: "LDAP",
|
[AppConnection.LDAP]: "LDAP",
|
||||||
[AppConnection.TeamCity]: "TeamCity"
|
[AppConnection.TeamCity]: "TeamCity",
|
||||||
|
[AppConnection.OCI]: "OCI"
|
||||||
};
|
};
|
||||||
|
@@ -49,6 +49,8 @@ import { ValidateHumanitecConnectionCredentialsSchema } from "./humanitec";
|
|||||||
import { humanitecConnectionService } from "./humanitec/humanitec-connection-service";
|
import { humanitecConnectionService } from "./humanitec/humanitec-connection-service";
|
||||||
import { ValidateLdapConnectionCredentialsSchema } from "./ldap";
|
import { ValidateLdapConnectionCredentialsSchema } from "./ldap";
|
||||||
import { ValidateMsSqlConnectionCredentialsSchema } from "./mssql";
|
import { ValidateMsSqlConnectionCredentialsSchema } from "./mssql";
|
||||||
|
import { ValidateOCIConnectionCredentialsSchema } from "./oci";
|
||||||
|
import { ociConnectionService } from "./oci/oci-connection-service";
|
||||||
import { ValidatePostgresConnectionCredentialsSchema } from "./postgres";
|
import { ValidatePostgresConnectionCredentialsSchema } from "./postgres";
|
||||||
import { ValidateTeamCityConnectionCredentialsSchema } from "./teamcity";
|
import { ValidateTeamCityConnectionCredentialsSchema } from "./teamcity";
|
||||||
import { teamcityConnectionService } from "./teamcity/teamcity-connection-service";
|
import { teamcityConnectionService } from "./teamcity/teamcity-connection-service";
|
||||||
@@ -85,7 +87,8 @@ const VALIDATE_APP_CONNECTION_CREDENTIALS_MAP: Record<AppConnection, TValidateAp
|
|||||||
[AppConnection.Auth0]: ValidateAuth0ConnectionCredentialsSchema,
|
[AppConnection.Auth0]: ValidateAuth0ConnectionCredentialsSchema,
|
||||||
[AppConnection.HCVault]: ValidateHCVaultConnectionCredentialsSchema,
|
[AppConnection.HCVault]: ValidateHCVaultConnectionCredentialsSchema,
|
||||||
[AppConnection.LDAP]: ValidateLdapConnectionCredentialsSchema,
|
[AppConnection.LDAP]: ValidateLdapConnectionCredentialsSchema,
|
||||||
[AppConnection.TeamCity]: ValidateTeamCityConnectionCredentialsSchema
|
[AppConnection.TeamCity]: ValidateTeamCityConnectionCredentialsSchema,
|
||||||
|
[AppConnection.OCI]: ValidateOCIConnectionCredentialsSchema
|
||||||
};
|
};
|
||||||
|
|
||||||
export const appConnectionServiceFactory = ({
|
export const appConnectionServiceFactory = ({
|
||||||
@@ -464,6 +467,7 @@ export const appConnectionServiceFactory = ({
|
|||||||
auth0: auth0ConnectionService(connectAppConnectionById, appConnectionDAL, kmsService),
|
auth0: auth0ConnectionService(connectAppConnectionById, appConnectionDAL, kmsService),
|
||||||
hcvault: hcVaultConnectionService(connectAppConnectionById),
|
hcvault: hcVaultConnectionService(connectAppConnectionById),
|
||||||
windmill: windmillConnectionService(connectAppConnectionById),
|
windmill: windmillConnectionService(connectAppConnectionById),
|
||||||
teamcity: teamcityConnectionService(connectAppConnectionById)
|
teamcity: teamcityConnectionService(connectAppConnectionById),
|
||||||
|
oci: ociConnectionService(connectAppConnectionById)
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@@ -76,6 +76,12 @@ import {
|
|||||||
TValidateLdapConnectionCredentialsSchema
|
TValidateLdapConnectionCredentialsSchema
|
||||||
} from "./ldap";
|
} from "./ldap";
|
||||||
import { TMsSqlConnection, TMsSqlConnectionInput, TValidateMsSqlConnectionCredentialsSchema } from "./mssql";
|
import { TMsSqlConnection, TMsSqlConnectionInput, TValidateMsSqlConnectionCredentialsSchema } from "./mssql";
|
||||||
|
import {
|
||||||
|
TOCIConnection,
|
||||||
|
TOCIConnectionConfig,
|
||||||
|
TOCIConnectionInput,
|
||||||
|
TValidateOCIConnectionCredentialsSchema
|
||||||
|
} from "./oci";
|
||||||
import {
|
import {
|
||||||
TPostgresConnection,
|
TPostgresConnection,
|
||||||
TPostgresConnectionInput,
|
TPostgresConnectionInput,
|
||||||
@@ -125,6 +131,7 @@ export type TAppConnection = { id: string } & (
|
|||||||
| THCVaultConnection
|
| THCVaultConnection
|
||||||
| TLdapConnection
|
| TLdapConnection
|
||||||
| TTeamCityConnection
|
| TTeamCityConnection
|
||||||
|
| TOCIConnection
|
||||||
);
|
);
|
||||||
|
|
||||||
export type TAppConnectionRaw = NonNullable<Awaited<ReturnType<TAppConnectionDALFactory["findById"]>>>;
|
export type TAppConnectionRaw = NonNullable<Awaited<ReturnType<TAppConnectionDALFactory["findById"]>>>;
|
||||||
@@ -150,6 +157,7 @@ export type TAppConnectionInput = { id: string } & (
|
|||||||
| THCVaultConnectionInput
|
| THCVaultConnectionInput
|
||||||
| TLdapConnectionInput
|
| TLdapConnectionInput
|
||||||
| TTeamCityConnectionInput
|
| TTeamCityConnectionInput
|
||||||
|
| TOCIConnectionInput
|
||||||
);
|
);
|
||||||
|
|
||||||
export type TSqlConnectionInput = TPostgresConnectionInput | TMsSqlConnectionInput;
|
export type TSqlConnectionInput = TPostgresConnectionInput | TMsSqlConnectionInput;
|
||||||
@@ -180,7 +188,8 @@ export type TAppConnectionConfig =
|
|||||||
| TAuth0ConnectionConfig
|
| TAuth0ConnectionConfig
|
||||||
| THCVaultConnectionConfig
|
| THCVaultConnectionConfig
|
||||||
| TLdapConnectionConfig
|
| TLdapConnectionConfig
|
||||||
| TTeamCityConnectionConfig;
|
| TTeamCityConnectionConfig
|
||||||
|
| TOCIConnectionConfig;
|
||||||
|
|
||||||
export type TValidateAppConnectionCredentialsSchema =
|
export type TValidateAppConnectionCredentialsSchema =
|
||||||
| TValidateAwsConnectionCredentialsSchema
|
| TValidateAwsConnectionCredentialsSchema
|
||||||
@@ -200,7 +209,8 @@ export type TValidateAppConnectionCredentialsSchema =
|
|||||||
| TValidateAuth0ConnectionCredentialsSchema
|
| TValidateAuth0ConnectionCredentialsSchema
|
||||||
| TValidateHCVaultConnectionCredentialsSchema
|
| TValidateHCVaultConnectionCredentialsSchema
|
||||||
| TValidateLdapConnectionCredentialsSchema
|
| TValidateLdapConnectionCredentialsSchema
|
||||||
| TValidateTeamCityConnectionCredentialsSchema;
|
| TValidateTeamCityConnectionCredentialsSchema
|
||||||
|
| TValidateOCIConnectionCredentialsSchema;
|
||||||
|
|
||||||
export type TListAwsConnectionKmsKeys = {
|
export type TListAwsConnectionKmsKeys = {
|
||||||
connectionId: string;
|
connectionId: string;
|
||||||
|
4
backend/src/services/app-connection/oci/index.ts
Normal file
4
backend/src/services/app-connection/oci/index.ts
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
export * from "./oci-connection-enums";
|
||||||
|
export * from "./oci-connection-fns";
|
||||||
|
export * from "./oci-connection-schemas";
|
||||||
|
export * from "./oci-connection-types";
|
@@ -0,0 +1,3 @@
|
|||||||
|
export enum OCIConnectionMethod {
|
||||||
|
AccessKey = "access-key"
|
||||||
|
}
|
139
backend/src/services/app-connection/oci/oci-connection-fns.ts
Normal file
139
backend/src/services/app-connection/oci/oci-connection-fns.ts
Normal file
@@ -0,0 +1,139 @@
|
|||||||
|
import { common, identity, keymanagement } from "oci-sdk";
|
||||||
|
|
||||||
|
import { BadRequestError } from "@app/lib/errors";
|
||||||
|
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
|
||||||
|
|
||||||
|
import { OCIConnectionMethod } from "./oci-connection-enums";
|
||||||
|
import { TOCIConnection, TOCIConnectionConfig } from "./oci-connection-types";
|
||||||
|
|
||||||
|
export const getOCIProvider = async (config: TOCIConnectionConfig) => {
|
||||||
|
const {
|
||||||
|
credentials: { fingerprint, privateKey, region, tenancyOcid, userOcid }
|
||||||
|
} = config;
|
||||||
|
|
||||||
|
const provider = new common.SimpleAuthenticationDetailsProvider(
|
||||||
|
tenancyOcid,
|
||||||
|
userOcid,
|
||||||
|
fingerprint,
|
||||||
|
privateKey,
|
||||||
|
null,
|
||||||
|
common.Region.fromRegionId(region)
|
||||||
|
);
|
||||||
|
|
||||||
|
return provider;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getOCIConnectionListItem = () => {
|
||||||
|
return {
|
||||||
|
name: "OCI" as const,
|
||||||
|
app: AppConnection.OCI as const,
|
||||||
|
methods: Object.values(OCIConnectionMethod) as [OCIConnectionMethod.AccessKey]
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const validateOCIConnectionCredentials = async (config: TOCIConnectionConfig) => {
|
||||||
|
const provider = await getOCIProvider(config);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const identityClient = new identity.IdentityClient({
|
||||||
|
authenticationDetailsProvider: provider
|
||||||
|
});
|
||||||
|
|
||||||
|
// Get user details - a lightweight call that validates all credentials
|
||||||
|
await identityClient.getUser({ userId: config.credentials.userOcid });
|
||||||
|
} catch (error: unknown) {
|
||||||
|
if (error instanceof Error) {
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: `Failed to validate credentials: ${error.message || "Unknown error"}`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: "Unable to validate connection: verify credentials"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return config.credentials;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const listOCICompartments = async (appConnection: TOCIConnection) => {
|
||||||
|
const provider = await getOCIProvider(appConnection);
|
||||||
|
|
||||||
|
const identityClient = new identity.IdentityClient({ authenticationDetailsProvider: provider });
|
||||||
|
const keyManagementClient = new keymanagement.KmsVaultClient({
|
||||||
|
authenticationDetailsProvider: provider
|
||||||
|
});
|
||||||
|
|
||||||
|
const rootCompartment = await identityClient
|
||||||
|
.getTenancy({
|
||||||
|
tenancyId: appConnection.credentials.tenancyOcid
|
||||||
|
})
|
||||||
|
.then((response) => ({
|
||||||
|
...response.tenancy,
|
||||||
|
id: appConnection.credentials.tenancyOcid,
|
||||||
|
name: response.tenancy.name ? `${response.tenancy.name} (root)` : "root"
|
||||||
|
}));
|
||||||
|
|
||||||
|
const compartments = await identityClient.listCompartments({
|
||||||
|
compartmentId: appConnection.credentials.tenancyOcid,
|
||||||
|
compartmentIdInSubtree: true,
|
||||||
|
accessLevel: identity.requests.ListCompartmentsRequest.AccessLevel.Any,
|
||||||
|
lifecycleState: identity.models.Compartment.LifecycleState.Active
|
||||||
|
});
|
||||||
|
|
||||||
|
const allCompartments = [rootCompartment, ...compartments.items];
|
||||||
|
const filteredCompartments = [];
|
||||||
|
|
||||||
|
for await (const compartment of allCompartments) {
|
||||||
|
try {
|
||||||
|
// Check if user can list vaults in this compartment
|
||||||
|
await keyManagementClient.listVaults({
|
||||||
|
compartmentId: compartment.id,
|
||||||
|
limit: 1
|
||||||
|
});
|
||||||
|
|
||||||
|
filteredCompartments.push(compartment);
|
||||||
|
} catch (error) {
|
||||||
|
// Do nothing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return filteredCompartments;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const listOCIVaults = async (appConnection: TOCIConnection, compartmentOcid: string) => {
|
||||||
|
const provider = await getOCIProvider(appConnection);
|
||||||
|
|
||||||
|
const keyManagementClient = new keymanagement.KmsVaultClient({
|
||||||
|
authenticationDetailsProvider: provider
|
||||||
|
});
|
||||||
|
|
||||||
|
const vaults = await keyManagementClient.listVaults({
|
||||||
|
compartmentId: compartmentOcid
|
||||||
|
});
|
||||||
|
|
||||||
|
return vaults.items.filter((v) => v.lifecycleState === keymanagement.models.Vault.LifecycleState.Active);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const listOCIVaultKeys = async (appConnection: TOCIConnection, compartmentOcid: string, vaultOcid: string) => {
|
||||||
|
const provider = await getOCIProvider(appConnection);
|
||||||
|
|
||||||
|
const kmsVaultClient = new keymanagement.KmsVaultClient({
|
||||||
|
authenticationDetailsProvider: provider
|
||||||
|
});
|
||||||
|
|
||||||
|
const vault = await kmsVaultClient.getVault({
|
||||||
|
vaultId: vaultOcid
|
||||||
|
});
|
||||||
|
|
||||||
|
const keyManagementClient = new keymanagement.KmsManagementClient({
|
||||||
|
authenticationDetailsProvider: provider
|
||||||
|
});
|
||||||
|
|
||||||
|
keyManagementClient.endpoint = vault.vault.managementEndpoint;
|
||||||
|
|
||||||
|
const keys = await keyManagementClient.listKeys({
|
||||||
|
compartmentId: compartmentOcid
|
||||||
|
});
|
||||||
|
|
||||||
|
return keys.items.filter((v) => v.lifecycleState === keymanagement.models.KeySummary.LifecycleState.Enabled);
|
||||||
|
};
|
@@ -0,0 +1,65 @@
|
|||||||
|
import z from "zod";
|
||||||
|
|
||||||
|
import { AppConnections } from "@app/lib/api-docs";
|
||||||
|
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
|
||||||
|
import {
|
||||||
|
BaseAppConnectionSchema,
|
||||||
|
GenericCreateAppConnectionFieldsSchema,
|
||||||
|
GenericUpdateAppConnectionFieldsSchema
|
||||||
|
} from "@app/services/app-connection/app-connection-schemas";
|
||||||
|
|
||||||
|
import { OCIConnectionMethod } from "./oci-connection-enums";
|
||||||
|
|
||||||
|
export const OCIConnectionAccessTokenCredentialsSchema = z.object({
|
||||||
|
userOcid: z.string().trim().min(1, "User OCID required").describe(AppConnections.CREDENTIALS.OCI.userOcid),
|
||||||
|
tenancyOcid: z.string().trim().min(1, "Tenancy OCID required").describe(AppConnections.CREDENTIALS.OCI.tenancyOcid),
|
||||||
|
region: z.string().trim().min(1, "Region required").describe(AppConnections.CREDENTIALS.OCI.region),
|
||||||
|
fingerprint: z.string().trim().min(1, "Fingerprint required").describe(AppConnections.CREDENTIALS.OCI.fingerprint),
|
||||||
|
privateKey: z.string().trim().min(1, "Private Key required").describe(AppConnections.CREDENTIALS.OCI.privateKey)
|
||||||
|
});
|
||||||
|
|
||||||
|
const BaseOCIConnectionSchema = BaseAppConnectionSchema.extend({ app: z.literal(AppConnection.OCI) });
|
||||||
|
|
||||||
|
export const OCIConnectionSchema = BaseOCIConnectionSchema.extend({
|
||||||
|
method: z.literal(OCIConnectionMethod.AccessKey),
|
||||||
|
credentials: OCIConnectionAccessTokenCredentialsSchema
|
||||||
|
});
|
||||||
|
|
||||||
|
export const SanitizedOCIConnectionSchema = z.discriminatedUnion("method", [
|
||||||
|
BaseOCIConnectionSchema.extend({
|
||||||
|
method: z.literal(OCIConnectionMethod.AccessKey),
|
||||||
|
credentials: OCIConnectionAccessTokenCredentialsSchema.pick({
|
||||||
|
userOcid: true,
|
||||||
|
tenancyOcid: true,
|
||||||
|
region: true,
|
||||||
|
fingerprint: true
|
||||||
|
})
|
||||||
|
})
|
||||||
|
]);
|
||||||
|
|
||||||
|
export const ValidateOCIConnectionCredentialsSchema = z.discriminatedUnion("method", [
|
||||||
|
z.object({
|
||||||
|
method: z.literal(OCIConnectionMethod.AccessKey).describe(AppConnections.CREATE(AppConnection.OCI).method),
|
||||||
|
credentials: OCIConnectionAccessTokenCredentialsSchema.describe(
|
||||||
|
AppConnections.CREATE(AppConnection.OCI).credentials
|
||||||
|
)
|
||||||
|
})
|
||||||
|
]);
|
||||||
|
|
||||||
|
export const CreateOCIConnectionSchema = ValidateOCIConnectionCredentialsSchema.and(
|
||||||
|
GenericCreateAppConnectionFieldsSchema(AppConnection.OCI)
|
||||||
|
);
|
||||||
|
|
||||||
|
export const UpdateOCIConnectionSchema = z
|
||||||
|
.object({
|
||||||
|
credentials: OCIConnectionAccessTokenCredentialsSchema.optional().describe(
|
||||||
|
AppConnections.UPDATE(AppConnection.OCI).credentials
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.and(GenericUpdateAppConnectionFieldsSchema(AppConnection.OCI));
|
||||||
|
|
||||||
|
export const OCIConnectionListItemSchema = z.object({
|
||||||
|
name: z.literal("OCI"),
|
||||||
|
app: z.literal(AppConnection.OCI),
|
||||||
|
methods: z.nativeEnum(OCIConnectionMethod).array()
|
||||||
|
});
|
@@ -0,0 +1,70 @@
|
|||||||
|
import { logger } from "@app/lib/logger";
|
||||||
|
import { OrgServiceActor } from "@app/lib/types";
|
||||||
|
|
||||||
|
import { AppConnection } from "../app-connection-enums";
|
||||||
|
import { listOCICompartments, listOCIVaultKeys, listOCIVaults } from "./oci-connection-fns";
|
||||||
|
import { TOCIConnection } from "./oci-connection-types";
|
||||||
|
|
||||||
|
type TGetAppConnectionFunc = (
|
||||||
|
app: AppConnection,
|
||||||
|
connectionId: string,
|
||||||
|
actor: OrgServiceActor
|
||||||
|
) => Promise<TOCIConnection>;
|
||||||
|
|
||||||
|
type TListOCIVaultsDTO = {
|
||||||
|
connectionId: string;
|
||||||
|
compartmentOcid: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type TListOCIVaultKeysDTO = {
|
||||||
|
connectionId: string;
|
||||||
|
compartmentOcid: string;
|
||||||
|
vaultOcid: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ociConnectionService = (getAppConnection: TGetAppConnectionFunc) => {
|
||||||
|
const listCompartments = async (connectionId: string, actor: OrgServiceActor) => {
|
||||||
|
const appConnection = await getAppConnection(AppConnection.OCI, connectionId, actor);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const compartments = await listOCICompartments(appConnection);
|
||||||
|
return compartments;
|
||||||
|
} catch (error) {
|
||||||
|
logger.error(error, "Failed to establish connection with OCI");
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const listVaults = async ({ connectionId, compartmentOcid }: TListOCIVaultsDTO, actor: OrgServiceActor) => {
|
||||||
|
const appConnection = await getAppConnection(AppConnection.OCI, connectionId, actor);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const vaults = await listOCIVaults(appConnection, compartmentOcid);
|
||||||
|
return vaults;
|
||||||
|
} catch (error) {
|
||||||
|
logger.error(error, "Failed to establish connection with OCI");
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const listVaultKeys = async (
|
||||||
|
{ connectionId, compartmentOcid, vaultOcid }: TListOCIVaultKeysDTO,
|
||||||
|
actor: OrgServiceActor
|
||||||
|
) => {
|
||||||
|
const appConnection = await getAppConnection(AppConnection.OCI, connectionId, actor);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const keys = await listOCIVaultKeys(appConnection, compartmentOcid, vaultOcid);
|
||||||
|
return keys;
|
||||||
|
} catch (error) {
|
||||||
|
logger.error(error, "Failed to establish connection with OCI");
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
listCompartments,
|
||||||
|
listVaults,
|
||||||
|
listVaultKeys
|
||||||
|
};
|
||||||
|
};
|
@@ -0,0 +1,22 @@
|
|||||||
|
import z from "zod";
|
||||||
|
|
||||||
|
import { DiscriminativePick } from "@app/lib/types";
|
||||||
|
|
||||||
|
import { AppConnection } from "../app-connection-enums";
|
||||||
|
import {
|
||||||
|
CreateOCIConnectionSchema,
|
||||||
|
OCIConnectionSchema,
|
||||||
|
ValidateOCIConnectionCredentialsSchema
|
||||||
|
} from "./oci-connection-schemas";
|
||||||
|
|
||||||
|
export type TOCIConnection = z.infer<typeof OCIConnectionSchema>;
|
||||||
|
|
||||||
|
export type TOCIConnectionInput = z.infer<typeof CreateOCIConnectionSchema> & {
|
||||||
|
app: AppConnection.OCI;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TValidateOCIConnectionCredentialsSchema = typeof ValidateOCIConnectionCredentialsSchema;
|
||||||
|
|
||||||
|
export type TOCIConnectionConfig = DiscriminativePick<TOCIConnectionInput, "method" | "app" | "credentials"> & {
|
||||||
|
orgId: string;
|
||||||
|
};
|
@@ -1169,7 +1169,7 @@ export const certificateAuthorityServiceFactory = ({
|
|||||||
ProjectPermissionSub.Certificates
|
ProjectPermissionSub.Certificates
|
||||||
);
|
);
|
||||||
|
|
||||||
if (ca.status === CaStatus.DISABLED) throw new BadRequestError({ message: "CA is disabled" });
|
if (ca.status !== CaStatus.ACTIVE) throw new BadRequestError({ message: "CA is not active" });
|
||||||
if (!ca.activeCaCertId) throw new BadRequestError({ message: "CA does not have a certificate installed" });
|
if (!ca.activeCaCertId) throw new BadRequestError({ message: "CA does not have a certificate installed" });
|
||||||
if (ca.requireTemplateForIssuance && !certificateTemplate) {
|
if (ca.requireTemplateForIssuance && !certificateTemplate) {
|
||||||
throw new BadRequestError({ message: "Certificate template is required for issuance" });
|
throw new BadRequestError({ message: "Certificate template is required for issuance" });
|
||||||
@@ -1520,7 +1520,7 @@ export const certificateAuthorityServiceFactory = ({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ca.status === CaStatus.DISABLED) throw new BadRequestError({ message: "CA is disabled" });
|
if (ca.status !== CaStatus.ACTIVE) throw new BadRequestError({ message: "CA is not active" });
|
||||||
if (!ca.activeCaCertId) throw new BadRequestError({ message: "CA does not have a certificate installed" });
|
if (!ca.activeCaCertId) throw new BadRequestError({ message: "CA does not have a certificate installed" });
|
||||||
if (ca.requireTemplateForIssuance && !certificateTemplate) {
|
if (ca.requireTemplateForIssuance && !certificateTemplate) {
|
||||||
throw new BadRequestError({ message: "Certificate template is required for issuance" });
|
throw new BadRequestError({ message: "Certificate template is required for issuance" });
|
||||||
|
@@ -10,6 +10,18 @@ const isValidDate = (dateString: string) => {
|
|||||||
|
|
||||||
export const validateCaDateField = z.string().trim().refine(isValidDate, { message: "Invalid date format" });
|
export const validateCaDateField = z.string().trim().refine(isValidDate, { message: "Invalid date format" });
|
||||||
|
|
||||||
|
export const validateAltNameField = z
|
||||||
|
.string()
|
||||||
|
.trim()
|
||||||
|
.refine(
|
||||||
|
(name) => {
|
||||||
|
return isFQDN(name) || z.string().email().safeParse(name).success || isValidIp(name);
|
||||||
|
},
|
||||||
|
{
|
||||||
|
message: "SAN must be a valid hostname, email address, or IP address"
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
export const validateAltNamesField = z
|
export const validateAltNamesField = z
|
||||||
.string()
|
.string()
|
||||||
.trim()
|
.trim()
|
||||||
|
@@ -44,8 +44,27 @@ export const certificateDALFactory = (db: TDbClient) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const countCertificatesForPkiSubscriber = async (subscriberId: string) => {
|
||||||
|
try {
|
||||||
|
interface CountResult {
|
||||||
|
count: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const query = db
|
||||||
|
.replicaNode()(TableName.Certificate)
|
||||||
|
.where(`${TableName.Certificate}.pkiSubscriberId`, subscriberId);
|
||||||
|
|
||||||
|
const count = await query.count("*").first();
|
||||||
|
|
||||||
|
return parseInt((count as unknown as CountResult).count || "0", 10);
|
||||||
|
} catch (error) {
|
||||||
|
throw new DatabaseError({ error, name: "Count all subscriber certificates" });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...certificateOrm,
|
...certificateOrm,
|
||||||
countCertificatesInProject
|
countCertificatesInProject,
|
||||||
|
countCertificatesForPkiSubscriber
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@@ -105,7 +105,7 @@ export const buildCertificateChain = async ({
|
|||||||
kmsService,
|
kmsService,
|
||||||
kmsId
|
kmsId
|
||||||
}: TBuildCertificateChainDTO) => {
|
}: TBuildCertificateChainDTO) => {
|
||||||
if (!encryptedCertificateChain && (!caCert || !caCertChain)) {
|
if (!encryptedCertificateChain && !caCert) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -29,6 +29,7 @@ import {
|
|||||||
TGetCertPrivateKeyDTO,
|
TGetCertPrivateKeyDTO,
|
||||||
TRevokeCertDTO
|
TRevokeCertDTO
|
||||||
} from "./certificate-types";
|
} from "./certificate-types";
|
||||||
|
import { NotFoundError } from "@app/lib/errors";
|
||||||
|
|
||||||
type TCertificateServiceFactoryDep = {
|
type TCertificateServiceFactoryDep = {
|
||||||
certificateDAL: Pick<TCertificateDALFactory, "findOne" | "deleteById" | "update" | "find">;
|
certificateDAL: Pick<TCertificateDALFactory, "findOne" | "deleteById" | "update" | "find">;
|
||||||
@@ -337,18 +338,27 @@ export const certificateServiceFactory = ({
|
|||||||
encryptedCertificateChain: certBody.encryptedCertificateChain || undefined
|
encryptedCertificateChain: certBody.encryptedCertificateChain || undefined
|
||||||
});
|
});
|
||||||
|
|
||||||
const { certPrivateKey } = await getCertificateCredentials({
|
let privateKey: string | null = null;
|
||||||
certId: cert.id,
|
try {
|
||||||
projectId: ca.projectId,
|
const { certPrivateKey } = await getCertificateCredentials({
|
||||||
certificateSecretDAL,
|
certId: cert.id,
|
||||||
projectDAL,
|
projectId: ca.projectId,
|
||||||
kmsService
|
certificateSecretDAL,
|
||||||
});
|
projectDAL,
|
||||||
|
kmsService
|
||||||
|
});
|
||||||
|
privateKey = certPrivateKey;
|
||||||
|
} catch (e) {
|
||||||
|
// Skip NotFound errors but throw all others
|
||||||
|
if (!(e instanceof NotFoundError)) {
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
certificate,
|
certificate,
|
||||||
certificateChain,
|
certificateChain,
|
||||||
privateKey: certPrivateKey,
|
privateKey,
|
||||||
serialNumber,
|
serialNumber,
|
||||||
cert,
|
cert,
|
||||||
ca
|
ca
|
||||||
|
@@ -36,6 +36,7 @@ export const identityAccessTokenDALFactory = (db: TDbClient) => {
|
|||||||
`${TableName.Identity}.id`,
|
`${TableName.Identity}.id`,
|
||||||
`${TableName.IdentityKubernetesAuth}.identityId`
|
`${TableName.IdentityKubernetesAuth}.identityId`
|
||||||
)
|
)
|
||||||
|
.leftJoin(TableName.IdentityOciAuth, `${TableName.Identity}.id`, `${TableName.IdentityOciAuth}.identityId`)
|
||||||
.leftJoin(TableName.IdentityOidcAuth, `${TableName.Identity}.id`, `${TableName.IdentityOidcAuth}.identityId`)
|
.leftJoin(TableName.IdentityOidcAuth, `${TableName.Identity}.id`, `${TableName.IdentityOidcAuth}.identityId`)
|
||||||
.leftJoin(TableName.IdentityTokenAuth, `${TableName.Identity}.id`, `${TableName.IdentityTokenAuth}.identityId`)
|
.leftJoin(TableName.IdentityTokenAuth, `${TableName.Identity}.id`, `${TableName.IdentityTokenAuth}.identityId`)
|
||||||
.leftJoin(TableName.IdentityJwtAuth, `${TableName.Identity}.id`, `${TableName.IdentityJwtAuth}.identityId`)
|
.leftJoin(TableName.IdentityJwtAuth, `${TableName.Identity}.id`, `${TableName.IdentityJwtAuth}.identityId`)
|
||||||
@@ -46,6 +47,7 @@ export const identityAccessTokenDALFactory = (db: TDbClient) => {
|
|||||||
db.ref("accessTokenTrustedIps").withSchema(TableName.IdentityAwsAuth).as("accessTokenTrustedIpsAws"),
|
db.ref("accessTokenTrustedIps").withSchema(TableName.IdentityAwsAuth).as("accessTokenTrustedIpsAws"),
|
||||||
db.ref("accessTokenTrustedIps").withSchema(TableName.IdentityAzureAuth).as("accessTokenTrustedIpsAzure"),
|
db.ref("accessTokenTrustedIps").withSchema(TableName.IdentityAzureAuth).as("accessTokenTrustedIpsAzure"),
|
||||||
db.ref("accessTokenTrustedIps").withSchema(TableName.IdentityKubernetesAuth).as("accessTokenTrustedIpsK8s"),
|
db.ref("accessTokenTrustedIps").withSchema(TableName.IdentityKubernetesAuth).as("accessTokenTrustedIpsK8s"),
|
||||||
|
db.ref("accessTokenTrustedIps").withSchema(TableName.IdentityOciAuth).as("accessTokenTrustedIpsOci"),
|
||||||
db.ref("accessTokenTrustedIps").withSchema(TableName.IdentityOidcAuth).as("accessTokenTrustedIpsOidc"),
|
db.ref("accessTokenTrustedIps").withSchema(TableName.IdentityOidcAuth).as("accessTokenTrustedIpsOidc"),
|
||||||
db.ref("accessTokenTrustedIps").withSchema(TableName.IdentityTokenAuth).as("accessTokenTrustedIpsToken"),
|
db.ref("accessTokenTrustedIps").withSchema(TableName.IdentityTokenAuth).as("accessTokenTrustedIpsToken"),
|
||||||
db.ref("accessTokenTrustedIps").withSchema(TableName.IdentityJwtAuth).as("accessTokenTrustedIpsJwt"),
|
db.ref("accessTokenTrustedIps").withSchema(TableName.IdentityJwtAuth).as("accessTokenTrustedIpsJwt"),
|
||||||
@@ -63,6 +65,7 @@ export const identityAccessTokenDALFactory = (db: TDbClient) => {
|
|||||||
trustedIpsAwsAuth: doc.accessTokenTrustedIpsAws,
|
trustedIpsAwsAuth: doc.accessTokenTrustedIpsAws,
|
||||||
trustedIpsAzureAuth: doc.accessTokenTrustedIpsAzure,
|
trustedIpsAzureAuth: doc.accessTokenTrustedIpsAzure,
|
||||||
trustedIpsKubernetesAuth: doc.accessTokenTrustedIpsK8s,
|
trustedIpsKubernetesAuth: doc.accessTokenTrustedIpsK8s,
|
||||||
|
trustedIpsOciAuth: doc.accessTokenTrustedIpsOci,
|
||||||
trustedIpsOidcAuth: doc.accessTokenTrustedIpsOidc,
|
trustedIpsOidcAuth: doc.accessTokenTrustedIpsOidc,
|
||||||
trustedIpsAccessTokenAuth: doc.accessTokenTrustedIpsToken,
|
trustedIpsAccessTokenAuth: doc.accessTokenTrustedIpsToken,
|
||||||
trustedIpsAccessJwtAuth: doc.accessTokenTrustedIpsJwt,
|
trustedIpsAccessJwtAuth: doc.accessTokenTrustedIpsJwt,
|
||||||
|
@@ -182,6 +182,7 @@ export const identityAccessTokenServiceFactory = ({
|
|||||||
[IdentityAuthMethod.UNIVERSAL_AUTH]: identityAccessToken.trustedIpsUniversalAuth,
|
[IdentityAuthMethod.UNIVERSAL_AUTH]: identityAccessToken.trustedIpsUniversalAuth,
|
||||||
[IdentityAuthMethod.GCP_AUTH]: identityAccessToken.trustedIpsGcpAuth,
|
[IdentityAuthMethod.GCP_AUTH]: identityAccessToken.trustedIpsGcpAuth,
|
||||||
[IdentityAuthMethod.AWS_AUTH]: identityAccessToken.trustedIpsAwsAuth,
|
[IdentityAuthMethod.AWS_AUTH]: identityAccessToken.trustedIpsAwsAuth,
|
||||||
|
[IdentityAuthMethod.OCI_AUTH]: identityAccessToken.trustedIpsOciAuth,
|
||||||
[IdentityAuthMethod.AZURE_AUTH]: identityAccessToken.trustedIpsAzureAuth,
|
[IdentityAuthMethod.AZURE_AUTH]: identityAccessToken.trustedIpsAzureAuth,
|
||||||
[IdentityAuthMethod.KUBERNETES_AUTH]: identityAccessToken.trustedIpsKubernetesAuth,
|
[IdentityAuthMethod.KUBERNETES_AUTH]: identityAccessToken.trustedIpsKubernetesAuth,
|
||||||
[IdentityAuthMethod.OIDC_AUTH]: identityAccessToken.trustedIpsOidcAuth,
|
[IdentityAuthMethod.OIDC_AUTH]: identityAccessToken.trustedIpsOidcAuth,
|
||||||
|
@@ -4,8 +4,14 @@ import https from "https";
|
|||||||
import jwt from "jsonwebtoken";
|
import jwt from "jsonwebtoken";
|
||||||
|
|
||||||
import { IdentityAuthMethod, TIdentityKubernetesAuthsUpdate } from "@app/db/schemas";
|
import { IdentityAuthMethod, TIdentityKubernetesAuthsUpdate } from "@app/db/schemas";
|
||||||
|
import { TGatewayDALFactory } from "@app/ee/services/gateway/gateway-dal";
|
||||||
|
import { TGatewayServiceFactory } from "@app/ee/services/gateway/gateway-service";
|
||||||
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
|
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
|
||||||
import { OrgPermissionIdentityActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission";
|
import {
|
||||||
|
OrgPermissionGatewayActions,
|
||||||
|
OrgPermissionIdentityActions,
|
||||||
|
OrgPermissionSubjects
|
||||||
|
} from "@app/ee/services/permission/org-permission";
|
||||||
import {
|
import {
|
||||||
constructPermissionErrorMessage,
|
constructPermissionErrorMessage,
|
||||||
validatePrivilegeChangeOperation
|
validatePrivilegeChangeOperation
|
||||||
@@ -13,6 +19,7 @@ import {
|
|||||||
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
||||||
import { getConfig } from "@app/lib/config/env";
|
import { getConfig } from "@app/lib/config/env";
|
||||||
import { BadRequestError, NotFoundError, PermissionBoundaryError, UnauthorizedError } from "@app/lib/errors";
|
import { BadRequestError, NotFoundError, PermissionBoundaryError, UnauthorizedError } from "@app/lib/errors";
|
||||||
|
import { withGatewayProxy } from "@app/lib/gateway";
|
||||||
import { extractIPDetails, isValidIpOrCidr } from "@app/lib/ip";
|
import { extractIPDetails, isValidIpOrCidr } from "@app/lib/ip";
|
||||||
|
|
||||||
import { ActorType, AuthTokenType } from "../auth/auth-type";
|
import { ActorType, AuthTokenType } from "../auth/auth-type";
|
||||||
@@ -43,6 +50,8 @@ type TIdentityKubernetesAuthServiceFactoryDep = {
|
|||||||
permissionService: Pick<TPermissionServiceFactory, "getOrgPermission">;
|
permissionService: Pick<TPermissionServiceFactory, "getOrgPermission">;
|
||||||
licenseService: Pick<TLicenseServiceFactory, "getPlan">;
|
licenseService: Pick<TLicenseServiceFactory, "getPlan">;
|
||||||
kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey">;
|
kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey">;
|
||||||
|
gatewayService: TGatewayServiceFactory;
|
||||||
|
gatewayDAL: Pick<TGatewayDALFactory, "find">;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TIdentityKubernetesAuthServiceFactory = ReturnType<typeof identityKubernetesAuthServiceFactory>;
|
export type TIdentityKubernetesAuthServiceFactory = ReturnType<typeof identityKubernetesAuthServiceFactory>;
|
||||||
@@ -53,8 +62,45 @@ export const identityKubernetesAuthServiceFactory = ({
|
|||||||
identityAccessTokenDAL,
|
identityAccessTokenDAL,
|
||||||
permissionService,
|
permissionService,
|
||||||
licenseService,
|
licenseService,
|
||||||
|
gatewayService,
|
||||||
|
gatewayDAL,
|
||||||
kmsService
|
kmsService
|
||||||
}: TIdentityKubernetesAuthServiceFactoryDep) => {
|
}: TIdentityKubernetesAuthServiceFactoryDep) => {
|
||||||
|
const $gatewayProxyWrapper = async <T>(
|
||||||
|
inputs: {
|
||||||
|
gatewayId: string;
|
||||||
|
targetHost: string;
|
||||||
|
targetPort: number;
|
||||||
|
},
|
||||||
|
gatewayCallback: (host: string, port: number) => Promise<T>
|
||||||
|
): Promise<T> => {
|
||||||
|
const relayDetails = await gatewayService.fnGetGatewayClientTlsByGatewayId(inputs.gatewayId);
|
||||||
|
const [relayHost, relayPort] = relayDetails.relayAddress.split(":");
|
||||||
|
|
||||||
|
const callbackResult = await withGatewayProxy(
|
||||||
|
async (port) => {
|
||||||
|
// Needs to be https protocol or the kubernetes API server will fail with "Client sent an HTTP request to an HTTPS server"
|
||||||
|
const res = await gatewayCallback("https://localhost", port);
|
||||||
|
return res;
|
||||||
|
},
|
||||||
|
{
|
||||||
|
targetHost: inputs.targetHost,
|
||||||
|
targetPort: inputs.targetPort,
|
||||||
|
relayHost,
|
||||||
|
relayPort: Number(relayPort),
|
||||||
|
identityId: relayDetails.identityId,
|
||||||
|
orgId: relayDetails.orgId,
|
||||||
|
tlsOptions: {
|
||||||
|
ca: relayDetails.certChain,
|
||||||
|
cert: relayDetails.certificate,
|
||||||
|
key: relayDetails.privateKey.toString()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return callbackResult;
|
||||||
|
};
|
||||||
|
|
||||||
const login = async ({ identityId, jwt: serviceAccountJwt }: TLoginKubernetesAuthDTO) => {
|
const login = async ({ identityId, jwt: serviceAccountJwt }: TLoginKubernetesAuthDTO) => {
|
||||||
const identityKubernetesAuth = await identityKubernetesAuthDAL.findOne({ identityId });
|
const identityKubernetesAuth = await identityKubernetesAuthDAL.findOne({ identityId });
|
||||||
if (!identityKubernetesAuth) {
|
if (!identityKubernetesAuth) {
|
||||||
@@ -92,46 +138,65 @@ export const identityKubernetesAuthServiceFactory = ({
|
|||||||
tokenReviewerJwt = serviceAccountJwt;
|
tokenReviewerJwt = serviceAccountJwt;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { data } = await axios
|
const tokenReviewCallback = async (host: string = identityKubernetesAuth.kubernetesHost, port?: number) => {
|
||||||
.post<TCreateTokenReviewResponse>(
|
const baseUrl = port ? `${host}:${port}` : host;
|
||||||
`${identityKubernetesAuth.kubernetesHost}/apis/authentication.k8s.io/v1/tokenreviews`,
|
|
||||||
{
|
|
||||||
apiVersion: "authentication.k8s.io/v1",
|
|
||||||
kind: "TokenReview",
|
|
||||||
spec: {
|
|
||||||
token: serviceAccountJwt,
|
|
||||||
...(identityKubernetesAuth.allowedAudience ? { audiences: [identityKubernetesAuth.allowedAudience] } : {})
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
Authorization: `Bearer ${tokenReviewerJwt}`
|
|
||||||
},
|
|
||||||
signal: AbortSignal.timeout(10000),
|
|
||||||
timeout: 10000,
|
|
||||||
// if ca cert, rejectUnauthorized: true
|
|
||||||
httpsAgent: new https.Agent({
|
|
||||||
ca: caCert,
|
|
||||||
rejectUnauthorized: !!caCert
|
|
||||||
})
|
|
||||||
}
|
|
||||||
)
|
|
||||||
.catch((err) => {
|
|
||||||
if (err instanceof AxiosError) {
|
|
||||||
if (err.response) {
|
|
||||||
const { message } = err?.response?.data as unknown as { message?: string };
|
|
||||||
|
|
||||||
if (message) {
|
const res = await axios
|
||||||
throw new UnauthorizedError({
|
.post<TCreateTokenReviewResponse>(
|
||||||
message,
|
`${baseUrl}/apis/authentication.k8s.io/v1/tokenreviews`,
|
||||||
name: "KubernetesTokenReviewRequestError"
|
{
|
||||||
});
|
apiVersion: "authentication.k8s.io/v1",
|
||||||
|
kind: "TokenReview",
|
||||||
|
spec: {
|
||||||
|
token: serviceAccountJwt,
|
||||||
|
...(identityKubernetesAuth.allowedAudience ? { audiences: [identityKubernetesAuth.allowedAudience] } : {})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
Authorization: `Bearer ${tokenReviewerJwt}`
|
||||||
|
},
|
||||||
|
signal: AbortSignal.timeout(10000),
|
||||||
|
timeout: 10000,
|
||||||
|
// if ca cert, rejectUnauthorized: true
|
||||||
|
httpsAgent: new https.Agent({
|
||||||
|
ca: caCert,
|
||||||
|
rejectUnauthorized: !!caCert
|
||||||
|
})
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.catch((err) => {
|
||||||
|
if (err instanceof AxiosError) {
|
||||||
|
if (err.response) {
|
||||||
|
const { message } = err?.response?.data as unknown as { message?: string };
|
||||||
|
|
||||||
|
if (message) {
|
||||||
|
throw new UnauthorizedError({
|
||||||
|
message,
|
||||||
|
name: "KubernetesTokenReviewRequestError"
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
throw err;
|
||||||
throw err;
|
});
|
||||||
});
|
|
||||||
|
return res.data;
|
||||||
|
};
|
||||||
|
|
||||||
|
const [k8sHost, k8sPort] = identityKubernetesAuth.kubernetesHost.split(":");
|
||||||
|
|
||||||
|
const data = identityKubernetesAuth.gatewayId
|
||||||
|
? await $gatewayProxyWrapper(
|
||||||
|
{
|
||||||
|
gatewayId: identityKubernetesAuth.gatewayId,
|
||||||
|
targetHost: k8sHost,
|
||||||
|
targetPort: k8sPort ? Number(k8sPort) : 443
|
||||||
|
},
|
||||||
|
tokenReviewCallback
|
||||||
|
)
|
||||||
|
: await tokenReviewCallback();
|
||||||
|
|
||||||
if ("error" in data.status)
|
if ("error" in data.status)
|
||||||
throw new UnauthorizedError({ message: data.status.error, name: "KubernetesTokenReviewError" });
|
throw new UnauthorizedError({ message: data.status.error, name: "KubernetesTokenReviewError" });
|
||||||
@@ -222,6 +287,7 @@ export const identityKubernetesAuthServiceFactory = ({
|
|||||||
|
|
||||||
const attachKubernetesAuth = async ({
|
const attachKubernetesAuth = async ({
|
||||||
identityId,
|
identityId,
|
||||||
|
gatewayId,
|
||||||
kubernetesHost,
|
kubernetesHost,
|
||||||
caCert,
|
caCert,
|
||||||
tokenReviewerJwt,
|
tokenReviewerJwt,
|
||||||
@@ -280,6 +346,27 @@ export const identityKubernetesAuthServiceFactory = ({
|
|||||||
return extractIPDetails(accessTokenTrustedIp.ipAddress);
|
return extractIPDetails(accessTokenTrustedIp.ipAddress);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (gatewayId) {
|
||||||
|
const [gateway] = await gatewayDAL.find({ id: gatewayId, orgId: identityMembershipOrg.orgId });
|
||||||
|
if (!gateway) {
|
||||||
|
throw new NotFoundError({
|
||||||
|
message: `Gateway with ID ${gatewayId} not found`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const { permission: orgPermission } = await permissionService.getOrgPermission(
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
identityMembershipOrg.orgId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId
|
||||||
|
);
|
||||||
|
ForbiddenError.from(orgPermission).throwUnlessCan(
|
||||||
|
OrgPermissionGatewayActions.AttachGateways,
|
||||||
|
OrgPermissionSubjects.Gateway
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const { encryptor } = await kmsService.createCipherPairWithDataKey({
|
const { encryptor } = await kmsService.createCipherPairWithDataKey({
|
||||||
type: KmsDataKey.Organization,
|
type: KmsDataKey.Organization,
|
||||||
orgId: identityMembershipOrg.orgId
|
orgId: identityMembershipOrg.orgId
|
||||||
@@ -296,6 +383,7 @@ export const identityKubernetesAuthServiceFactory = ({
|
|||||||
accessTokenMaxTTL,
|
accessTokenMaxTTL,
|
||||||
accessTokenTTL,
|
accessTokenTTL,
|
||||||
accessTokenNumUsesLimit,
|
accessTokenNumUsesLimit,
|
||||||
|
gatewayId,
|
||||||
accessTokenTrustedIps: JSON.stringify(reformattedAccessTokenTrustedIps),
|
accessTokenTrustedIps: JSON.stringify(reformattedAccessTokenTrustedIps),
|
||||||
encryptedKubernetesTokenReviewerJwt: tokenReviewerJwt
|
encryptedKubernetesTokenReviewerJwt: tokenReviewerJwt
|
||||||
? encryptor({ plainText: Buffer.from(tokenReviewerJwt) }).cipherTextBlob
|
? encryptor({ plainText: Buffer.from(tokenReviewerJwt) }).cipherTextBlob
|
||||||
@@ -318,6 +406,7 @@ export const identityKubernetesAuthServiceFactory = ({
|
|||||||
allowedNamespaces,
|
allowedNamespaces,
|
||||||
allowedNames,
|
allowedNames,
|
||||||
allowedAudience,
|
allowedAudience,
|
||||||
|
gatewayId,
|
||||||
accessTokenTTL,
|
accessTokenTTL,
|
||||||
accessTokenMaxTTL,
|
accessTokenMaxTTL,
|
||||||
accessTokenNumUsesLimit,
|
accessTokenNumUsesLimit,
|
||||||
@@ -373,11 +462,33 @@ export const identityKubernetesAuthServiceFactory = ({
|
|||||||
return extractIPDetails(accessTokenTrustedIp.ipAddress);
|
return extractIPDetails(accessTokenTrustedIp.ipAddress);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (gatewayId) {
|
||||||
|
const [gateway] = await gatewayDAL.find({ id: gatewayId, orgId: identityMembershipOrg.orgId });
|
||||||
|
if (!gateway) {
|
||||||
|
throw new NotFoundError({
|
||||||
|
message: `Gateway with ID ${gatewayId} not found`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const { permission: orgPermission } = await permissionService.getOrgPermission(
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
identityMembershipOrg.orgId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId
|
||||||
|
);
|
||||||
|
ForbiddenError.from(orgPermission).throwUnlessCan(
|
||||||
|
OrgPermissionGatewayActions.AttachGateways,
|
||||||
|
OrgPermissionSubjects.Gateway
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const updateQuery: TIdentityKubernetesAuthsUpdate = {
|
const updateQuery: TIdentityKubernetesAuthsUpdate = {
|
||||||
kubernetesHost,
|
kubernetesHost,
|
||||||
allowedNamespaces,
|
allowedNamespaces,
|
||||||
allowedNames,
|
allowedNames,
|
||||||
allowedAudience,
|
allowedAudience,
|
||||||
|
gatewayId,
|
||||||
accessTokenMaxTTL,
|
accessTokenMaxTTL,
|
||||||
accessTokenTTL,
|
accessTokenTTL,
|
||||||
accessTokenNumUsesLimit,
|
accessTokenNumUsesLimit,
|
||||||
|
@@ -13,6 +13,7 @@ export type TAttachKubernetesAuthDTO = {
|
|||||||
allowedNamespaces: string;
|
allowedNamespaces: string;
|
||||||
allowedNames: string;
|
allowedNames: string;
|
||||||
allowedAudience: string;
|
allowedAudience: string;
|
||||||
|
gatewayId?: string | null;
|
||||||
accessTokenTTL: number;
|
accessTokenTTL: number;
|
||||||
accessTokenMaxTTL: number;
|
accessTokenMaxTTL: number;
|
||||||
accessTokenNumUsesLimit: number;
|
accessTokenNumUsesLimit: number;
|
||||||
@@ -28,6 +29,7 @@ export type TUpdateKubernetesAuthDTO = {
|
|||||||
allowedNamespaces?: string;
|
allowedNamespaces?: string;
|
||||||
allowedNames?: string;
|
allowedNames?: string;
|
||||||
allowedAudience?: string;
|
allowedAudience?: string;
|
||||||
|
gatewayId?: string | null;
|
||||||
accessTokenTTL?: number;
|
accessTokenTTL?: number;
|
||||||
accessTokenMaxTTL?: number;
|
accessTokenMaxTTL?: number;
|
||||||
accessTokenNumUsesLimit?: number;
|
accessTokenNumUsesLimit?: number;
|
||||||
|
@@ -0,0 +1,9 @@
|
|||||||
|
import { TDbClient } from "@app/db";
|
||||||
|
import { TableName } from "@app/db/schemas";
|
||||||
|
import { ormify } from "@app/lib/knex";
|
||||||
|
|
||||||
|
export type TIdentityOciAuthDALFactory = ReturnType<typeof identityOciAuthDALFactory>;
|
||||||
|
|
||||||
|
export const identityOciAuthDALFactory = (db: TDbClient) => {
|
||||||
|
return ormify(db, TableName.IdentityOciAuth);
|
||||||
|
};
|
@@ -0,0 +1,368 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||||
|
import { ForbiddenError } from "@casl/ability";
|
||||||
|
import { AxiosError } from "axios";
|
||||||
|
import jwt from "jsonwebtoken";
|
||||||
|
import RE2 from "re2";
|
||||||
|
|
||||||
|
import { IdentityAuthMethod } from "@app/db/schemas";
|
||||||
|
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
|
||||||
|
import { OrgPermissionIdentityActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission";
|
||||||
|
import {
|
||||||
|
constructPermissionErrorMessage,
|
||||||
|
validatePrivilegeChangeOperation
|
||||||
|
} from "@app/ee/services/permission/permission-fns";
|
||||||
|
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
||||||
|
import { getConfig } from "@app/lib/config/env";
|
||||||
|
import { request } from "@app/lib/config/request";
|
||||||
|
import { BadRequestError, NotFoundError, PermissionBoundaryError, UnauthorizedError } from "@app/lib/errors";
|
||||||
|
import { extractIPDetails, isValidIpOrCidr } from "@app/lib/ip";
|
||||||
|
import { logger } from "@app/lib/logger";
|
||||||
|
|
||||||
|
import { ActorType, AuthTokenType } from "../auth/auth-type";
|
||||||
|
import { TIdentityOrgDALFactory } from "../identity/identity-org-dal";
|
||||||
|
import { TIdentityAccessTokenDALFactory } from "../identity-access-token/identity-access-token-dal";
|
||||||
|
import { TIdentityAccessTokenJwtPayload } from "../identity-access-token/identity-access-token-types";
|
||||||
|
import { validateIdentityUpdateForSuperAdminPrivileges } from "../super-admin/super-admin-fns";
|
||||||
|
import { TIdentityOciAuthDALFactory } from "./identity-oci-auth-dal";
|
||||||
|
import {
|
||||||
|
TAttachOciAuthDTO,
|
||||||
|
TGetOciAuthDTO,
|
||||||
|
TLoginOciAuthDTO,
|
||||||
|
TOciGetUserResponse,
|
||||||
|
TRevokeOciAuthDTO,
|
||||||
|
TUpdateOciAuthDTO
|
||||||
|
} from "./identity-oci-auth-types";
|
||||||
|
|
||||||
|
type TIdentityOciAuthServiceFactoryDep = {
|
||||||
|
identityAccessTokenDAL: Pick<TIdentityAccessTokenDALFactory, "create" | "delete">;
|
||||||
|
identityOciAuthDAL: Pick<TIdentityOciAuthDALFactory, "findOne" | "transaction" | "create" | "updateById" | "delete">;
|
||||||
|
identityOrgMembershipDAL: Pick<TIdentityOrgDALFactory, "findOne">;
|
||||||
|
licenseService: Pick<TLicenseServiceFactory, "getPlan">;
|
||||||
|
permissionService: Pick<TPermissionServiceFactory, "getOrgPermission">;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TIdentityOciAuthServiceFactory = ReturnType<typeof identityOciAuthServiceFactory>;
|
||||||
|
|
||||||
|
export const identityOciAuthServiceFactory = ({
|
||||||
|
identityAccessTokenDAL,
|
||||||
|
identityOciAuthDAL,
|
||||||
|
identityOrgMembershipDAL,
|
||||||
|
licenseService,
|
||||||
|
permissionService
|
||||||
|
}: TIdentityOciAuthServiceFactoryDep) => {
|
||||||
|
const login = async ({ identityId, headers, userOcid }: TLoginOciAuthDTO) => {
|
||||||
|
const identityOciAuth = await identityOciAuthDAL.findOne({ identityId });
|
||||||
|
if (!identityOciAuth) {
|
||||||
|
throw new NotFoundError({ message: "OCI auth method not found for identity, did you configure OCI auth?" });
|
||||||
|
}
|
||||||
|
|
||||||
|
const identityMembershipOrg = await identityOrgMembershipDAL.findOne({ identityId: identityOciAuth.identityId });
|
||||||
|
|
||||||
|
// Validate OCI host format. Ensures that the host is in "identity.<region>.oraclecloud.com" format.
|
||||||
|
if (!headers.host || !new RE2("^identity\\.([a-z]{2}-[a-z]+-[1-9])\\.oraclecloud\\.com$").test(headers.host)) {
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: "Invalid OCI host format. Expected format: identity.<region>.oraclecloud.com"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const { data } = await request
|
||||||
|
.get<TOciGetUserResponse>(`https://${headers.host}/20160918/users/${userOcid}`, {
|
||||||
|
headers
|
||||||
|
})
|
||||||
|
.catch((err: AxiosError) => {
|
||||||
|
logger.error(err.response, "OciIdentityLogin: Failed to authenticate with Oracle Cloud");
|
||||||
|
throw err;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (data.compartmentId !== identityOciAuth.tenancyOcid) {
|
||||||
|
throw new UnauthorizedError({
|
||||||
|
message: "Access denied: OCI account isn't part of tenancy."
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (identityOciAuth.allowedUsernames) {
|
||||||
|
const isAccountAllowed = identityOciAuth.allowedUsernames.split(",").some((name) => name.trim() === data.name);
|
||||||
|
|
||||||
|
if (!isAccountAllowed)
|
||||||
|
throw new UnauthorizedError({
|
||||||
|
message: "Access denied: OCI account username not allowed."
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate the token
|
||||||
|
const identityAccessToken = await identityOciAuthDAL.transaction(async (tx) => {
|
||||||
|
const newToken = await identityAccessTokenDAL.create(
|
||||||
|
{
|
||||||
|
identityId: identityOciAuth.identityId,
|
||||||
|
isAccessTokenRevoked: false,
|
||||||
|
accessTokenTTL: identityOciAuth.accessTokenTTL,
|
||||||
|
accessTokenMaxTTL: identityOciAuth.accessTokenMaxTTL,
|
||||||
|
accessTokenNumUses: 0,
|
||||||
|
accessTokenNumUsesLimit: identityOciAuth.accessTokenNumUsesLimit,
|
||||||
|
authMethod: IdentityAuthMethod.OCI_AUTH
|
||||||
|
},
|
||||||
|
tx
|
||||||
|
);
|
||||||
|
return newToken;
|
||||||
|
});
|
||||||
|
|
||||||
|
const appCfg = getConfig();
|
||||||
|
const accessToken = jwt.sign(
|
||||||
|
{
|
||||||
|
identityId: identityOciAuth.identityId,
|
||||||
|
identityAccessTokenId: identityAccessToken.id,
|
||||||
|
authTokenType: AuthTokenType.IDENTITY_ACCESS_TOKEN
|
||||||
|
} as TIdentityAccessTokenJwtPayload,
|
||||||
|
appCfg.AUTH_SECRET,
|
||||||
|
Number(identityAccessToken.accessTokenTTL) === 0
|
||||||
|
? undefined
|
||||||
|
: {
|
||||||
|
expiresIn: Number(identityAccessToken.accessTokenTTL)
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
identityOciAuth,
|
||||||
|
accessToken,
|
||||||
|
identityAccessToken,
|
||||||
|
identityMembershipOrg
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const attachOciAuth = async ({
|
||||||
|
identityId,
|
||||||
|
tenancyOcid,
|
||||||
|
allowedUsernames,
|
||||||
|
accessTokenTTL,
|
||||||
|
accessTokenMaxTTL,
|
||||||
|
accessTokenNumUsesLimit,
|
||||||
|
accessTokenTrustedIps,
|
||||||
|
actorId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actor,
|
||||||
|
actorOrgId,
|
||||||
|
isActorSuperAdmin
|
||||||
|
}: TAttachOciAuthDTO) => {
|
||||||
|
await validateIdentityUpdateForSuperAdminPrivileges(identityId, isActorSuperAdmin);
|
||||||
|
|
||||||
|
const identityMembershipOrg = await identityOrgMembershipDAL.findOne({ identityId });
|
||||||
|
if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` });
|
||||||
|
|
||||||
|
if (identityMembershipOrg.identity.authMethods.includes(IdentityAuthMethod.OCI_AUTH)) {
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: "Failed to add OCI Auth to already configured identity"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (accessTokenMaxTTL > 0 && accessTokenTTL > accessTokenMaxTTL) {
|
||||||
|
throw new BadRequestError({ message: "Access token TTL cannot be greater than max TTL" });
|
||||||
|
}
|
||||||
|
|
||||||
|
const { permission } = await permissionService.getOrgPermission(
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
identityMembershipOrg.orgId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId
|
||||||
|
);
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Create, OrgPermissionSubjects.Identity);
|
||||||
|
|
||||||
|
const plan = await licenseService.getPlan(identityMembershipOrg.orgId);
|
||||||
|
const reformattedAccessTokenTrustedIps = accessTokenTrustedIps.map((accessTokenTrustedIp) => {
|
||||||
|
if (
|
||||||
|
!plan.ipAllowlisting &&
|
||||||
|
accessTokenTrustedIp.ipAddress !== "0.0.0.0/0" &&
|
||||||
|
accessTokenTrustedIp.ipAddress !== "::/0"
|
||||||
|
)
|
||||||
|
throw new BadRequestError({
|
||||||
|
message:
|
||||||
|
"Failed to add IP access range to access token due to plan restriction. Upgrade plan to add IP access range."
|
||||||
|
});
|
||||||
|
if (!isValidIpOrCidr(accessTokenTrustedIp.ipAddress))
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: "The IP is not a valid IPv4, IPv6, or CIDR block"
|
||||||
|
});
|
||||||
|
return extractIPDetails(accessTokenTrustedIp.ipAddress);
|
||||||
|
});
|
||||||
|
|
||||||
|
const identityOciAuth = await identityOciAuthDAL.transaction(async (tx) => {
|
||||||
|
const doc = await identityOciAuthDAL.create(
|
||||||
|
{
|
||||||
|
identityId: identityMembershipOrg.identityId,
|
||||||
|
type: "iam",
|
||||||
|
tenancyOcid,
|
||||||
|
allowedUsernames,
|
||||||
|
accessTokenMaxTTL,
|
||||||
|
accessTokenTTL,
|
||||||
|
accessTokenNumUsesLimit,
|
||||||
|
accessTokenTrustedIps: JSON.stringify(reformattedAccessTokenTrustedIps)
|
||||||
|
},
|
||||||
|
tx
|
||||||
|
);
|
||||||
|
return doc;
|
||||||
|
});
|
||||||
|
return { ...identityOciAuth, orgId: identityMembershipOrg.orgId };
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateOciAuth = async ({
|
||||||
|
identityId,
|
||||||
|
tenancyOcid,
|
||||||
|
allowedUsernames,
|
||||||
|
accessTokenTTL,
|
||||||
|
accessTokenMaxTTL,
|
||||||
|
accessTokenNumUsesLimit,
|
||||||
|
accessTokenTrustedIps,
|
||||||
|
actorId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actor,
|
||||||
|
actorOrgId
|
||||||
|
}: TUpdateOciAuthDTO) => {
|
||||||
|
const identityMembershipOrg = await identityOrgMembershipDAL.findOne({ identityId });
|
||||||
|
if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` });
|
||||||
|
|
||||||
|
if (!identityMembershipOrg.identity.authMethods.includes(IdentityAuthMethod.OCI_AUTH)) {
|
||||||
|
throw new NotFoundError({
|
||||||
|
message: "The identity does not have OCI Auth attached"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const identityOciAuth = await identityOciAuthDAL.findOne({ identityId });
|
||||||
|
|
||||||
|
if (
|
||||||
|
(accessTokenMaxTTL || identityOciAuth.accessTokenMaxTTL) > 0 &&
|
||||||
|
(accessTokenTTL || identityOciAuth.accessTokenTTL) > (accessTokenMaxTTL || identityOciAuth.accessTokenMaxTTL)
|
||||||
|
) {
|
||||||
|
throw new BadRequestError({ message: "Access token TTL cannot be greater than max TTL" });
|
||||||
|
}
|
||||||
|
|
||||||
|
const { permission } = await permissionService.getOrgPermission(
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
identityMembershipOrg.orgId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId
|
||||||
|
);
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Edit, OrgPermissionSubjects.Identity);
|
||||||
|
|
||||||
|
const plan = await licenseService.getPlan(identityMembershipOrg.orgId);
|
||||||
|
const reformattedAccessTokenTrustedIps = accessTokenTrustedIps?.map((accessTokenTrustedIp) => {
|
||||||
|
if (
|
||||||
|
!plan.ipAllowlisting &&
|
||||||
|
accessTokenTrustedIp.ipAddress !== "0.0.0.0/0" &&
|
||||||
|
accessTokenTrustedIp.ipAddress !== "::/0"
|
||||||
|
)
|
||||||
|
throw new BadRequestError({
|
||||||
|
message:
|
||||||
|
"Failed to add IP access range to access token due to plan restriction. Upgrade plan to add IP access range."
|
||||||
|
});
|
||||||
|
if (!isValidIpOrCidr(accessTokenTrustedIp.ipAddress))
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: "The IP is not a valid IPv4, IPv6, or CIDR block"
|
||||||
|
});
|
||||||
|
return extractIPDetails(accessTokenTrustedIp.ipAddress);
|
||||||
|
});
|
||||||
|
|
||||||
|
const updatedOciAuth = await identityOciAuthDAL.updateById(identityOciAuth.id, {
|
||||||
|
tenancyOcid,
|
||||||
|
allowedUsernames,
|
||||||
|
accessTokenMaxTTL,
|
||||||
|
accessTokenTTL,
|
||||||
|
accessTokenNumUsesLimit,
|
||||||
|
accessTokenTrustedIps: reformattedAccessTokenTrustedIps
|
||||||
|
? JSON.stringify(reformattedAccessTokenTrustedIps)
|
||||||
|
: undefined
|
||||||
|
});
|
||||||
|
|
||||||
|
return { ...updatedOciAuth, orgId: identityMembershipOrg.orgId };
|
||||||
|
};
|
||||||
|
|
||||||
|
const getOciAuth = async ({ identityId, actorId, actor, actorAuthMethod, actorOrgId }: TGetOciAuthDTO) => {
|
||||||
|
const identityMembershipOrg = await identityOrgMembershipDAL.findOne({ identityId });
|
||||||
|
if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` });
|
||||||
|
|
||||||
|
if (!identityMembershipOrg.identity.authMethods.includes(IdentityAuthMethod.OCI_AUTH)) {
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: "The identity does not have OCI Auth attached"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const ociIdentityAuth = await identityOciAuthDAL.findOne({ identityId });
|
||||||
|
|
||||||
|
const { permission } = await permissionService.getOrgPermission(
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
identityMembershipOrg.orgId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId
|
||||||
|
);
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Read, OrgPermissionSubjects.Identity);
|
||||||
|
return { ...ociIdentityAuth, orgId: identityMembershipOrg.orgId };
|
||||||
|
};
|
||||||
|
|
||||||
|
const revokeIdentityOciAuth = async ({
|
||||||
|
identityId,
|
||||||
|
actorId,
|
||||||
|
actor,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId
|
||||||
|
}: TRevokeOciAuthDTO) => {
|
||||||
|
const identityMembershipOrg = await identityOrgMembershipDAL.findOne({ identityId });
|
||||||
|
if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` });
|
||||||
|
if (!identityMembershipOrg.identity.authMethods.includes(IdentityAuthMethod.OCI_AUTH)) {
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: "The identity does not have OCI auth"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
const { permission, membership } = await permissionService.getOrgPermission(
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
identityMembershipOrg.orgId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId
|
||||||
|
);
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Edit, OrgPermissionSubjects.Identity);
|
||||||
|
|
||||||
|
const { permission: rolePermission } = await permissionService.getOrgPermission(
|
||||||
|
ActorType.IDENTITY,
|
||||||
|
identityMembershipOrg.identityId,
|
||||||
|
identityMembershipOrg.orgId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId
|
||||||
|
);
|
||||||
|
|
||||||
|
const permissionBoundary = validatePrivilegeChangeOperation(
|
||||||
|
membership.shouldUseNewPrivilegeSystem,
|
||||||
|
OrgPermissionIdentityActions.RevokeAuth,
|
||||||
|
OrgPermissionSubjects.Identity,
|
||||||
|
permission,
|
||||||
|
rolePermission
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!permissionBoundary.isValid)
|
||||||
|
throw new PermissionBoundaryError({
|
||||||
|
message: constructPermissionErrorMessage(
|
||||||
|
"Failed to revoke OCI auth of identity with more privileged role",
|
||||||
|
membership.shouldUseNewPrivilegeSystem,
|
||||||
|
OrgPermissionIdentityActions.RevokeAuth,
|
||||||
|
OrgPermissionSubjects.Identity
|
||||||
|
),
|
||||||
|
details: { missingPermissions: permissionBoundary.missingPermissions }
|
||||||
|
});
|
||||||
|
|
||||||
|
const revokedIdentityOciAuth = await identityOciAuthDAL.transaction(async (tx) => {
|
||||||
|
const deletedOciAuth = await identityOciAuthDAL.delete({ identityId }, tx);
|
||||||
|
await identityAccessTokenDAL.delete({ identityId, authMethod: IdentityAuthMethod.OCI_AUTH }, tx);
|
||||||
|
|
||||||
|
return { ...deletedOciAuth?.[0], orgId: identityMembershipOrg.orgId };
|
||||||
|
});
|
||||||
|
return revokedIdentityOciAuth;
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
login,
|
||||||
|
attachOciAuth,
|
||||||
|
updateOciAuth,
|
||||||
|
getOciAuth,
|
||||||
|
revokeIdentityOciAuth
|
||||||
|
};
|
||||||
|
};
|
@@ -0,0 +1,53 @@
|
|||||||
|
import { TProjectPermission } from "@app/lib/types";
|
||||||
|
|
||||||
|
export type TLoginOciAuthDTO = {
|
||||||
|
identityId: string;
|
||||||
|
userOcid: string;
|
||||||
|
headers: {
|
||||||
|
authorization: string;
|
||||||
|
host: string;
|
||||||
|
"x-date": string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TAttachOciAuthDTO = {
|
||||||
|
identityId: string;
|
||||||
|
tenancyOcid: string;
|
||||||
|
allowedUsernames: string | null;
|
||||||
|
accessTokenTTL: number;
|
||||||
|
accessTokenMaxTTL: number;
|
||||||
|
accessTokenNumUsesLimit: number;
|
||||||
|
accessTokenTrustedIps: { ipAddress: string }[];
|
||||||
|
isActorSuperAdmin?: boolean;
|
||||||
|
} & Omit<TProjectPermission, "projectId">;
|
||||||
|
|
||||||
|
export type TUpdateOciAuthDTO = {
|
||||||
|
identityId: string;
|
||||||
|
tenancyOcid: string;
|
||||||
|
allowedUsernames: string | null;
|
||||||
|
accessTokenTTL?: number;
|
||||||
|
accessTokenMaxTTL?: number;
|
||||||
|
accessTokenNumUsesLimit?: number;
|
||||||
|
accessTokenTrustedIps?: { ipAddress: string }[];
|
||||||
|
} & Omit<TProjectPermission, "projectId">;
|
||||||
|
|
||||||
|
export type TGetOciAuthDTO = {
|
||||||
|
identityId: string;
|
||||||
|
} & Omit<TProjectPermission, "projectId">;
|
||||||
|
|
||||||
|
export type TRevokeOciAuthDTO = {
|
||||||
|
identityId: string;
|
||||||
|
} & Omit<TProjectPermission, "projectId">;
|
||||||
|
|
||||||
|
export type TOciGetUserResponse = {
|
||||||
|
email: string;
|
||||||
|
emailVerified: boolean;
|
||||||
|
timeModified: string;
|
||||||
|
isMfaActivated: boolean;
|
||||||
|
id: string;
|
||||||
|
compartmentId: string;
|
||||||
|
name: string;
|
||||||
|
timeCreated: string;
|
||||||
|
freeformTags: { [key: string]: string };
|
||||||
|
lifecycleState: string;
|
||||||
|
};
|
@@ -0,0 +1,32 @@
|
|||||||
|
import RE2 from "re2";
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
const usernameSchema = z
|
||||||
|
.string()
|
||||||
|
.min(1, "Username cannot be empty")
|
||||||
|
.refine((val) => new RE2("^[a-zA-Z0-9._@-]+$").test(val), "Invalid OCI username format");
|
||||||
|
export const validateUsernames = z
|
||||||
|
.string()
|
||||||
|
.trim()
|
||||||
|
.max(500, "Input exceeds the maximum limit of 500 characters")
|
||||||
|
.nullish()
|
||||||
|
.transform((val) => {
|
||||||
|
if (!val) return [];
|
||||||
|
return val
|
||||||
|
.split(",")
|
||||||
|
.map((s) => s.trim())
|
||||||
|
.filter(Boolean);
|
||||||
|
})
|
||||||
|
.refine((arr) => arr.every((name) => usernameSchema.safeParse(name).success), {
|
||||||
|
message: "One or more usernames are invalid"
|
||||||
|
})
|
||||||
|
.transform((arr) => (arr.length > 0 ? arr.join(", ") : null));
|
||||||
|
|
||||||
|
export const validateTenancy = z
|
||||||
|
.string()
|
||||||
|
.trim()
|
||||||
|
.min(1, "Tenancy OCID cannot be empty.")
|
||||||
|
.refine(
|
||||||
|
(val) => new RE2("^ocid1\\.tenancy\\.oc1\\..+$").test(val),
|
||||||
|
"Invalid Tenancy OCID format. Must start with ocid1.tenancy.oc1."
|
||||||
|
);
|
@@ -8,6 +8,7 @@ import {
|
|||||||
TIdentityAzureAuths,
|
TIdentityAzureAuths,
|
||||||
TIdentityGcpAuths,
|
TIdentityGcpAuths,
|
||||||
TIdentityKubernetesAuths,
|
TIdentityKubernetesAuths,
|
||||||
|
TIdentityOciAuths,
|
||||||
TIdentityOidcAuths,
|
TIdentityOidcAuths,
|
||||||
TIdentityTokenAuths,
|
TIdentityTokenAuths,
|
||||||
TIdentityUniversalAuths
|
TIdentityUniversalAuths
|
||||||
@@ -66,6 +67,11 @@ export const identityProjectDALFactory = (db: TDbClient) => {
|
|||||||
`${TableName.IdentityProjectMembership}.identityId`,
|
`${TableName.IdentityProjectMembership}.identityId`,
|
||||||
`${TableName.IdentityKubernetesAuth}.identityId`
|
`${TableName.IdentityKubernetesAuth}.identityId`
|
||||||
)
|
)
|
||||||
|
.leftJoin(
|
||||||
|
TableName.IdentityOciAuth,
|
||||||
|
`${TableName.IdentityProjectMembership}.identityId`,
|
||||||
|
`${TableName.IdentityOciAuth}.identityId`
|
||||||
|
)
|
||||||
.leftJoin(
|
.leftJoin(
|
||||||
TableName.IdentityOidcAuth,
|
TableName.IdentityOidcAuth,
|
||||||
`${TableName.IdentityProjectMembership}.identityId`,
|
`${TableName.IdentityProjectMembership}.identityId`,
|
||||||
@@ -107,6 +113,7 @@ export const identityProjectDALFactory = (db: TDbClient) => {
|
|||||||
db.ref("id").as("gcpId").withSchema(TableName.IdentityGcpAuth),
|
db.ref("id").as("gcpId").withSchema(TableName.IdentityGcpAuth),
|
||||||
db.ref("id").as("awsId").withSchema(TableName.IdentityAwsAuth),
|
db.ref("id").as("awsId").withSchema(TableName.IdentityAwsAuth),
|
||||||
db.ref("id").as("kubernetesId").withSchema(TableName.IdentityKubernetesAuth),
|
db.ref("id").as("kubernetesId").withSchema(TableName.IdentityKubernetesAuth),
|
||||||
|
db.ref("id").as("ociId").withSchema(TableName.IdentityOciAuth),
|
||||||
db.ref("id").as("oidcId").withSchema(TableName.IdentityOidcAuth),
|
db.ref("id").as("oidcId").withSchema(TableName.IdentityOidcAuth),
|
||||||
db.ref("id").as("azureId").withSchema(TableName.IdentityAzureAuth),
|
db.ref("id").as("azureId").withSchema(TableName.IdentityAzureAuth),
|
||||||
db.ref("id").as("tokenId").withSchema(TableName.IdentityTokenAuth)
|
db.ref("id").as("tokenId").withSchema(TableName.IdentityTokenAuth)
|
||||||
@@ -270,6 +277,11 @@ export const identityProjectDALFactory = (db: TDbClient) => {
|
|||||||
`${TableName.Identity}.id`,
|
`${TableName.Identity}.id`,
|
||||||
`${TableName.IdentityKubernetesAuth}.identityId`
|
`${TableName.IdentityKubernetesAuth}.identityId`
|
||||||
)
|
)
|
||||||
|
.leftJoin<TIdentityOciAuths>(
|
||||||
|
TableName.IdentityOciAuth,
|
||||||
|
`${TableName.Identity}.id`,
|
||||||
|
`${TableName.IdentityOciAuth}.identityId`
|
||||||
|
)
|
||||||
.leftJoin<TIdentityOidcAuths>(
|
.leftJoin<TIdentityOidcAuths>(
|
||||||
TableName.IdentityOidcAuth,
|
TableName.IdentityOidcAuth,
|
||||||
`${TableName.Identity}.id`,
|
`${TableName.Identity}.id`,
|
||||||
@@ -309,6 +321,7 @@ export const identityProjectDALFactory = (db: TDbClient) => {
|
|||||||
db.ref("id").as("gcpId").withSchema(TableName.IdentityGcpAuth),
|
db.ref("id").as("gcpId").withSchema(TableName.IdentityGcpAuth),
|
||||||
db.ref("id").as("awsId").withSchema(TableName.IdentityAwsAuth),
|
db.ref("id").as("awsId").withSchema(TableName.IdentityAwsAuth),
|
||||||
db.ref("id").as("kubernetesId").withSchema(TableName.IdentityKubernetesAuth),
|
db.ref("id").as("kubernetesId").withSchema(TableName.IdentityKubernetesAuth),
|
||||||
|
db.ref("id").as("ociId").withSchema(TableName.IdentityOciAuth),
|
||||||
db.ref("id").as("oidcId").withSchema(TableName.IdentityOidcAuth),
|
db.ref("id").as("oidcId").withSchema(TableName.IdentityOidcAuth),
|
||||||
db.ref("id").as("azureId").withSchema(TableName.IdentityAzureAuth),
|
db.ref("id").as("azureId").withSchema(TableName.IdentityAzureAuth),
|
||||||
db.ref("id").as("tokenId").withSchema(TableName.IdentityTokenAuth)
|
db.ref("id").as("tokenId").withSchema(TableName.IdentityTokenAuth)
|
||||||
@@ -336,6 +349,7 @@ export const identityProjectDALFactory = (db: TDbClient) => {
|
|||||||
awsId,
|
awsId,
|
||||||
gcpId,
|
gcpId,
|
||||||
kubernetesId,
|
kubernetesId,
|
||||||
|
ociId,
|
||||||
oidcId,
|
oidcId,
|
||||||
azureId,
|
azureId,
|
||||||
tokenId,
|
tokenId,
|
||||||
@@ -356,6 +370,7 @@ export const identityProjectDALFactory = (db: TDbClient) => {
|
|||||||
awsId,
|
awsId,
|
||||||
gcpId,
|
gcpId,
|
||||||
kubernetesId,
|
kubernetesId,
|
||||||
|
ociId,
|
||||||
oidcId,
|
oidcId,
|
||||||
azureId,
|
azureId,
|
||||||
tokenId
|
tokenId
|
||||||
|
@@ -5,6 +5,7 @@ export const buildAuthMethods = ({
|
|||||||
gcpId,
|
gcpId,
|
||||||
awsId,
|
awsId,
|
||||||
kubernetesId,
|
kubernetesId,
|
||||||
|
ociId,
|
||||||
oidcId,
|
oidcId,
|
||||||
azureId,
|
azureId,
|
||||||
tokenId,
|
tokenId,
|
||||||
@@ -15,6 +16,7 @@ export const buildAuthMethods = ({
|
|||||||
gcpId?: string;
|
gcpId?: string;
|
||||||
awsId?: string;
|
awsId?: string;
|
||||||
kubernetesId?: string;
|
kubernetesId?: string;
|
||||||
|
ociId?: string;
|
||||||
oidcId?: string;
|
oidcId?: string;
|
||||||
azureId?: string;
|
azureId?: string;
|
||||||
tokenId?: string;
|
tokenId?: string;
|
||||||
@@ -26,6 +28,7 @@ export const buildAuthMethods = ({
|
|||||||
...[gcpId ? IdentityAuthMethod.GCP_AUTH : null],
|
...[gcpId ? IdentityAuthMethod.GCP_AUTH : null],
|
||||||
...[awsId ? IdentityAuthMethod.AWS_AUTH : null],
|
...[awsId ? IdentityAuthMethod.AWS_AUTH : null],
|
||||||
...[kubernetesId ? IdentityAuthMethod.KUBERNETES_AUTH : null],
|
...[kubernetesId ? IdentityAuthMethod.KUBERNETES_AUTH : null],
|
||||||
|
...[ociId ? IdentityAuthMethod.OCI_AUTH : null],
|
||||||
...[oidcId ? IdentityAuthMethod.OIDC_AUTH : null],
|
...[oidcId ? IdentityAuthMethod.OIDC_AUTH : null],
|
||||||
...[azureId ? IdentityAuthMethod.AZURE_AUTH : null],
|
...[azureId ? IdentityAuthMethod.AZURE_AUTH : null],
|
||||||
...[tokenId ? IdentityAuthMethod.TOKEN_AUTH : null],
|
...[tokenId ? IdentityAuthMethod.TOKEN_AUTH : null],
|
||||||
|
@@ -8,6 +8,7 @@ import {
|
|||||||
TIdentityGcpAuths,
|
TIdentityGcpAuths,
|
||||||
TIdentityJwtAuths,
|
TIdentityJwtAuths,
|
||||||
TIdentityKubernetesAuths,
|
TIdentityKubernetesAuths,
|
||||||
|
TIdentityOciAuths,
|
||||||
TIdentityOidcAuths,
|
TIdentityOidcAuths,
|
||||||
TIdentityOrgMemberships,
|
TIdentityOrgMemberships,
|
||||||
TIdentityTokenAuths,
|
TIdentityTokenAuths,
|
||||||
@@ -62,6 +63,11 @@ export const identityOrgDALFactory = (db: TDbClient) => {
|
|||||||
`${TableName.IdentityOrgMembership}.identityId`,
|
`${TableName.IdentityOrgMembership}.identityId`,
|
||||||
`${TableName.IdentityKubernetesAuth}.identityId`
|
`${TableName.IdentityKubernetesAuth}.identityId`
|
||||||
)
|
)
|
||||||
|
.leftJoin<TIdentityOciAuths>(
|
||||||
|
TableName.IdentityOciAuth,
|
||||||
|
`${TableName.IdentityOrgMembership}.identityId`,
|
||||||
|
`${TableName.IdentityOciAuth}.identityId`
|
||||||
|
)
|
||||||
.leftJoin<TIdentityOidcAuths>(
|
.leftJoin<TIdentityOidcAuths>(
|
||||||
TableName.IdentityOidcAuth,
|
TableName.IdentityOidcAuth,
|
||||||
`${TableName.IdentityOrgMembership}.identityId`,
|
`${TableName.IdentityOrgMembership}.identityId`,
|
||||||
@@ -95,6 +101,7 @@ export const identityOrgDALFactory = (db: TDbClient) => {
|
|||||||
db.ref("id").as("gcpId").withSchema(TableName.IdentityGcpAuth),
|
db.ref("id").as("gcpId").withSchema(TableName.IdentityGcpAuth),
|
||||||
db.ref("id").as("awsId").withSchema(TableName.IdentityAwsAuth),
|
db.ref("id").as("awsId").withSchema(TableName.IdentityAwsAuth),
|
||||||
db.ref("id").as("kubernetesId").withSchema(TableName.IdentityKubernetesAuth),
|
db.ref("id").as("kubernetesId").withSchema(TableName.IdentityKubernetesAuth),
|
||||||
|
db.ref("id").as("ociId").withSchema(TableName.IdentityOciAuth),
|
||||||
db.ref("id").as("oidcId").withSchema(TableName.IdentityOidcAuth),
|
db.ref("id").as("oidcId").withSchema(TableName.IdentityOidcAuth),
|
||||||
db.ref("id").as("azureId").withSchema(TableName.IdentityAzureAuth),
|
db.ref("id").as("azureId").withSchema(TableName.IdentityAzureAuth),
|
||||||
db.ref("id").as("tokenId").withSchema(TableName.IdentityTokenAuth),
|
db.ref("id").as("tokenId").withSchema(TableName.IdentityTokenAuth),
|
||||||
@@ -186,6 +193,11 @@ export const identityOrgDALFactory = (db: TDbClient) => {
|
|||||||
"paginatedIdentity.identityId",
|
"paginatedIdentity.identityId",
|
||||||
`${TableName.IdentityKubernetesAuth}.identityId`
|
`${TableName.IdentityKubernetesAuth}.identityId`
|
||||||
)
|
)
|
||||||
|
.leftJoin<TIdentityOciAuths>(
|
||||||
|
TableName.IdentityOciAuth,
|
||||||
|
"paginatedIdentity.identityId",
|
||||||
|
`${TableName.IdentityOciAuth}.identityId`
|
||||||
|
)
|
||||||
.leftJoin<TIdentityOidcAuths>(
|
.leftJoin<TIdentityOidcAuths>(
|
||||||
TableName.IdentityOidcAuth,
|
TableName.IdentityOidcAuth,
|
||||||
"paginatedIdentity.identityId",
|
"paginatedIdentity.identityId",
|
||||||
@@ -226,6 +238,7 @@ export const identityOrgDALFactory = (db: TDbClient) => {
|
|||||||
db.ref("id").as("gcpId").withSchema(TableName.IdentityGcpAuth),
|
db.ref("id").as("gcpId").withSchema(TableName.IdentityGcpAuth),
|
||||||
db.ref("id").as("awsId").withSchema(TableName.IdentityAwsAuth),
|
db.ref("id").as("awsId").withSchema(TableName.IdentityAwsAuth),
|
||||||
db.ref("id").as("kubernetesId").withSchema(TableName.IdentityKubernetesAuth),
|
db.ref("id").as("kubernetesId").withSchema(TableName.IdentityKubernetesAuth),
|
||||||
|
db.ref("id").as("ociId").withSchema(TableName.IdentityOciAuth),
|
||||||
db.ref("id").as("oidcId").withSchema(TableName.IdentityOidcAuth),
|
db.ref("id").as("oidcId").withSchema(TableName.IdentityOidcAuth),
|
||||||
db.ref("id").as("azureId").withSchema(TableName.IdentityAzureAuth),
|
db.ref("id").as("azureId").withSchema(TableName.IdentityAzureAuth),
|
||||||
db.ref("id").as("tokenId").withSchema(TableName.IdentityTokenAuth),
|
db.ref("id").as("tokenId").withSchema(TableName.IdentityTokenAuth),
|
||||||
@@ -269,6 +282,7 @@ export const identityOrgDALFactory = (db: TDbClient) => {
|
|||||||
gcpId,
|
gcpId,
|
||||||
jwtId,
|
jwtId,
|
||||||
kubernetesId,
|
kubernetesId,
|
||||||
|
ociId,
|
||||||
oidcId,
|
oidcId,
|
||||||
azureId,
|
azureId,
|
||||||
tokenId,
|
tokenId,
|
||||||
@@ -301,6 +315,7 @@ export const identityOrgDALFactory = (db: TDbClient) => {
|
|||||||
awsId,
|
awsId,
|
||||||
gcpId,
|
gcpId,
|
||||||
kubernetesId,
|
kubernetesId,
|
||||||
|
ociId,
|
||||||
oidcId,
|
oidcId,
|
||||||
azureId,
|
azureId,
|
||||||
tokenId,
|
tokenId,
|
||||||
@@ -401,6 +416,11 @@ export const identityOrgDALFactory = (db: TDbClient) => {
|
|||||||
`${TableName.IdentityOrgMembership}.identityId`,
|
`${TableName.IdentityOrgMembership}.identityId`,
|
||||||
`${TableName.IdentityKubernetesAuth}.identityId`
|
`${TableName.IdentityKubernetesAuth}.identityId`
|
||||||
)
|
)
|
||||||
|
.leftJoin(
|
||||||
|
TableName.IdentityOciAuth,
|
||||||
|
`${TableName.IdentityOrgMembership}.identityId`,
|
||||||
|
`${TableName.IdentityOciAuth}.identityId`
|
||||||
|
)
|
||||||
.leftJoin(
|
.leftJoin(
|
||||||
TableName.IdentityOidcAuth,
|
TableName.IdentityOidcAuth,
|
||||||
`${TableName.IdentityOrgMembership}.identityId`,
|
`${TableName.IdentityOrgMembership}.identityId`,
|
||||||
@@ -441,6 +461,7 @@ export const identityOrgDALFactory = (db: TDbClient) => {
|
|||||||
db.ref("id").as("gcpId").withSchema(TableName.IdentityGcpAuth),
|
db.ref("id").as("gcpId").withSchema(TableName.IdentityGcpAuth),
|
||||||
db.ref("id").as("awsId").withSchema(TableName.IdentityAwsAuth),
|
db.ref("id").as("awsId").withSchema(TableName.IdentityAwsAuth),
|
||||||
db.ref("id").as("kubernetesId").withSchema(TableName.IdentityKubernetesAuth),
|
db.ref("id").as("kubernetesId").withSchema(TableName.IdentityKubernetesAuth),
|
||||||
|
db.ref("id").as("ociId").withSchema(TableName.IdentityOciAuth),
|
||||||
db.ref("id").as("oidcId").withSchema(TableName.IdentityOidcAuth),
|
db.ref("id").as("oidcId").withSchema(TableName.IdentityOidcAuth),
|
||||||
db.ref("id").as("azureId").withSchema(TableName.IdentityAzureAuth),
|
db.ref("id").as("azureId").withSchema(TableName.IdentityAzureAuth),
|
||||||
db.ref("id").as("tokenId").withSchema(TableName.IdentityTokenAuth),
|
db.ref("id").as("tokenId").withSchema(TableName.IdentityTokenAuth),
|
||||||
@@ -485,6 +506,7 @@ export const identityOrgDALFactory = (db: TDbClient) => {
|
|||||||
gcpId,
|
gcpId,
|
||||||
jwtId,
|
jwtId,
|
||||||
kubernetesId,
|
kubernetesId,
|
||||||
|
ociId,
|
||||||
oidcId,
|
oidcId,
|
||||||
azureId,
|
azureId,
|
||||||
tokenId,
|
tokenId,
|
||||||
@@ -517,6 +539,7 @@ export const identityOrgDALFactory = (db: TDbClient) => {
|
|||||||
awsId,
|
awsId,
|
||||||
gcpId,
|
gcpId,
|
||||||
kubernetesId,
|
kubernetesId,
|
||||||
|
ociId,
|
||||||
oidcId,
|
oidcId,
|
||||||
azureId,
|
azureId,
|
||||||
tokenId,
|
tokenId,
|
||||||
|
@@ -106,18 +106,29 @@ export const identityServiceFactory = ({
|
|||||||
},
|
},
|
||||||
tx
|
tx
|
||||||
);
|
);
|
||||||
|
|
||||||
|
let insertedMetadata: Array<{
|
||||||
|
id: string;
|
||||||
|
key: string;
|
||||||
|
value: string;
|
||||||
|
}> = [];
|
||||||
|
|
||||||
if (metadata && metadata.length) {
|
if (metadata && metadata.length) {
|
||||||
await identityMetadataDAL.insertMany(
|
const rowsToInsert = metadata.map(({ key, value }) => ({
|
||||||
metadata.map(({ key, value }) => ({
|
identityId: newIdentity.id,
|
||||||
identityId: newIdentity.id,
|
orgId,
|
||||||
orgId,
|
key,
|
||||||
key,
|
value
|
||||||
value
|
}));
|
||||||
})),
|
|
||||||
tx
|
insertedMetadata = await identityMetadataDAL.insertMany(rowsToInsert, tx);
|
||||||
);
|
|
||||||
}
|
}
|
||||||
return { ...newIdentity, authMethods: [] };
|
|
||||||
|
return {
|
||||||
|
...newIdentity,
|
||||||
|
authMethods: [],
|
||||||
|
metadata: insertedMetadata
|
||||||
|
};
|
||||||
});
|
});
|
||||||
await licenseService.updateSubscriptionOrgMemberCount(orgId);
|
await licenseService.updateSubscriptionOrgMemberCount(orgId);
|
||||||
|
|
||||||
@@ -189,21 +200,31 @@ export const identityServiceFactory = ({
|
|||||||
tx
|
tx
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
let insertedMetadata: Array<{
|
||||||
|
id: string;
|
||||||
|
key: string;
|
||||||
|
value: string;
|
||||||
|
}> = [];
|
||||||
|
|
||||||
if (metadata) {
|
if (metadata) {
|
||||||
await identityMetadataDAL.delete({ orgId: identityOrgMembership.orgId, identityId: id }, tx);
|
await identityMetadataDAL.delete({ orgId: identityOrgMembership.orgId, identityId: id }, tx);
|
||||||
|
|
||||||
if (metadata.length) {
|
if (metadata.length) {
|
||||||
await identityMetadataDAL.insertMany(
|
const rowsToInsert = metadata.map(({ key, value }) => ({
|
||||||
metadata.map(({ key, value }) => ({
|
identityId: newIdentity.id,
|
||||||
identityId: newIdentity.id,
|
orgId: identityOrgMembership.orgId,
|
||||||
orgId: identityOrgMembership.orgId,
|
key,
|
||||||
key,
|
value
|
||||||
value
|
}));
|
||||||
})),
|
|
||||||
tx
|
insertedMetadata = await identityMetadataDAL.insertMany(rowsToInsert, tx);
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return newIdentity;
|
|
||||||
|
return {
|
||||||
|
...newIdentity,
|
||||||
|
metadata: insertedMetadata
|
||||||
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
return { ...identity, orgId: identityOrgMembership.orgId };
|
return { ...identity, orgId: identityOrgMembership.orgId };
|
||||||
@@ -224,6 +245,7 @@ export const identityServiceFactory = ({
|
|||||||
actorOrgId
|
actorOrgId
|
||||||
);
|
);
|
||||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Read, OrgPermissionSubjects.Identity);
|
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Read, OrgPermissionSubjects.Identity);
|
||||||
|
|
||||||
return identity;
|
return identity;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@@ -18,5 +18,13 @@ export const sanitizedOrganizationSchema = OrganizationsSchema.pick({
|
|||||||
privilegeUpgradeInitiatedByUsername: true,
|
privilegeUpgradeInitiatedByUsername: true,
|
||||||
privilegeUpgradeInitiatedAt: true,
|
privilegeUpgradeInitiatedAt: true,
|
||||||
bypassOrgAuthEnabled: true,
|
bypassOrgAuthEnabled: true,
|
||||||
userTokenExpiration: true
|
userTokenExpiration: true,
|
||||||
|
secretsProductEnabled: true,
|
||||||
|
pkiProductEnabled: true,
|
||||||
|
kmsProductEnabled: true,
|
||||||
|
sshProductEnabled: true,
|
||||||
|
scannerProductEnabled: true,
|
||||||
|
shareSecretsProductEnabled: true,
|
||||||
|
maxSharedSecretLifetime: true,
|
||||||
|
maxSharedSecretViewLimit: true
|
||||||
});
|
});
|
||||||
|
@@ -355,7 +355,15 @@ export const orgServiceFactory = ({
|
|||||||
selectedMfaMethod,
|
selectedMfaMethod,
|
||||||
allowSecretSharingOutsideOrganization,
|
allowSecretSharingOutsideOrganization,
|
||||||
bypassOrgAuthEnabled,
|
bypassOrgAuthEnabled,
|
||||||
userTokenExpiration
|
userTokenExpiration,
|
||||||
|
secretsProductEnabled,
|
||||||
|
pkiProductEnabled,
|
||||||
|
kmsProductEnabled,
|
||||||
|
sshProductEnabled,
|
||||||
|
scannerProductEnabled,
|
||||||
|
shareSecretsProductEnabled,
|
||||||
|
maxSharedSecretLifetime,
|
||||||
|
maxSharedSecretViewLimit
|
||||||
}
|
}
|
||||||
}: TUpdateOrgDTO) => {
|
}: TUpdateOrgDTO) => {
|
||||||
const appCfg = getConfig();
|
const appCfg = getConfig();
|
||||||
@@ -457,7 +465,15 @@ export const orgServiceFactory = ({
|
|||||||
selectedMfaMethod,
|
selectedMfaMethod,
|
||||||
allowSecretSharingOutsideOrganization,
|
allowSecretSharingOutsideOrganization,
|
||||||
bypassOrgAuthEnabled,
|
bypassOrgAuthEnabled,
|
||||||
userTokenExpiration
|
userTokenExpiration,
|
||||||
|
secretsProductEnabled,
|
||||||
|
pkiProductEnabled,
|
||||||
|
kmsProductEnabled,
|
||||||
|
sshProductEnabled,
|
||||||
|
scannerProductEnabled,
|
||||||
|
shareSecretsProductEnabled,
|
||||||
|
maxSharedSecretLifetime,
|
||||||
|
maxSharedSecretViewLimit
|
||||||
});
|
});
|
||||||
if (!org) throw new NotFoundError({ message: `Organization with ID '${orgId}' not found` });
|
if (!org) throw new NotFoundError({ message: `Organization with ID '${orgId}' not found` });
|
||||||
return org;
|
return org;
|
||||||
|
@@ -75,6 +75,14 @@ export type TUpdateOrgDTO = {
|
|||||||
allowSecretSharingOutsideOrganization: boolean;
|
allowSecretSharingOutsideOrganization: boolean;
|
||||||
bypassOrgAuthEnabled: boolean;
|
bypassOrgAuthEnabled: boolean;
|
||||||
userTokenExpiration: string;
|
userTokenExpiration: string;
|
||||||
|
secretsProductEnabled: boolean;
|
||||||
|
pkiProductEnabled: boolean;
|
||||||
|
kmsProductEnabled: boolean;
|
||||||
|
sshProductEnabled: boolean;
|
||||||
|
scannerProductEnabled: boolean;
|
||||||
|
shareSecretsProductEnabled: boolean;
|
||||||
|
maxSharedSecretLifetime: number;
|
||||||
|
maxSharedSecretViewLimit: number | null;
|
||||||
}>;
|
}>;
|
||||||
} & TOrgPermission;
|
} & TOrgPermission;
|
||||||
|
|
||||||
|
10
backend/src/services/pki-subscriber/pki-subscriber-dal.ts
Normal file
10
backend/src/services/pki-subscriber/pki-subscriber-dal.ts
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import { TDbClient } from "@app/db";
|
||||||
|
import { TableName } from "@app/db/schemas";
|
||||||
|
import { ormify } from "@app/lib/knex";
|
||||||
|
|
||||||
|
export type TPkiSubscriberDALFactory = ReturnType<typeof pkiSubscriberDALFactory>;
|
||||||
|
|
||||||
|
export const pkiSubscriberDALFactory = (db: TDbClient) => {
|
||||||
|
const pkiSubscriberOrm = ormify(db, TableName.PkiSubscriber);
|
||||||
|
return pkiSubscriberOrm;
|
||||||
|
};
|
14
backend/src/services/pki-subscriber/pki-subscriber-schema.ts
Normal file
14
backend/src/services/pki-subscriber/pki-subscriber-schema.ts
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import { PkiSubscribersSchema } from "@app/db/schemas";
|
||||||
|
|
||||||
|
export const sanitizedPkiSubscriber = PkiSubscribersSchema.pick({
|
||||||
|
id: true,
|
||||||
|
projectId: true,
|
||||||
|
caId: true,
|
||||||
|
name: true,
|
||||||
|
commonName: true,
|
||||||
|
status: true,
|
||||||
|
subjectAlternativeNames: true,
|
||||||
|
ttl: true,
|
||||||
|
keyUsages: true,
|
||||||
|
extendedKeyUsages: true
|
||||||
|
});
|
805
backend/src/services/pki-subscriber/pki-subscriber-service.ts
Normal file
805
backend/src/services/pki-subscriber/pki-subscriber-service.ts
Normal file
@@ -0,0 +1,805 @@
|
|||||||
|
/* eslint-disable no-bitwise */
|
||||||
|
import { ForbiddenError, subject } from "@casl/ability";
|
||||||
|
import * as x509 from "@peculiar/x509";
|
||||||
|
import crypto, { KeyObject } from "crypto";
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { ActionProjectType } from "@app/db/schemas";
|
||||||
|
import { TCertificateAuthorityCrlDALFactory } from "@app/ee/services/certificate-authority-crl/certificate-authority-crl-dal";
|
||||||
|
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
||||||
|
import {
|
||||||
|
ProjectPermissionPkiSubscriberActions,
|
||||||
|
ProjectPermissionSub
|
||||||
|
} from "@app/ee/services/permission/project-permission";
|
||||||
|
import { getConfig } from "@app/lib/config/env";
|
||||||
|
import { BadRequestError, NotFoundError } from "@app/lib/errors";
|
||||||
|
import { ms } from "@app/lib/ms";
|
||||||
|
import { isFQDN } from "@app/lib/validator/validate-url";
|
||||||
|
import { TCertificateBodyDALFactory } from "@app/services/certificate/certificate-body-dal";
|
||||||
|
import { TCertificateDALFactory } from "@app/services/certificate/certificate-dal";
|
||||||
|
import { TCertificateSecretDALFactory } from "@app/services/certificate/certificate-secret-dal";
|
||||||
|
import {
|
||||||
|
CertExtendedKeyUsage,
|
||||||
|
CertExtendedKeyUsageOIDToName,
|
||||||
|
CertKeyAlgorithm,
|
||||||
|
CertKeyUsage,
|
||||||
|
CertStatus
|
||||||
|
} from "@app/services/certificate/certificate-types";
|
||||||
|
import { TCertificateAuthorityCertDALFactory } from "@app/services/certificate-authority/certificate-authority-cert-dal";
|
||||||
|
import { TCertificateAuthorityDALFactory } from "@app/services/certificate-authority/certificate-authority-dal";
|
||||||
|
import {
|
||||||
|
createSerialNumber,
|
||||||
|
getCaCertChain,
|
||||||
|
getCaCredentials,
|
||||||
|
keyAlgorithmToAlgCfg,
|
||||||
|
parseDistinguishedName
|
||||||
|
} from "@app/services/certificate-authority/certificate-authority-fns";
|
||||||
|
import { TCertificateAuthoritySecretDALFactory } from "@app/services/certificate-authority/certificate-authority-secret-dal";
|
||||||
|
import { CaStatus } from "@app/services/certificate-authority/certificate-authority-types";
|
||||||
|
import { TKmsServiceFactory } from "@app/services/kms/kms-service";
|
||||||
|
import { TPkiSubscriberDALFactory } from "@app/services/pki-subscriber/pki-subscriber-dal";
|
||||||
|
import { TProjectDALFactory } from "@app/services/project/project-dal";
|
||||||
|
import { getProjectKmsCertificateKeyId } from "@app/services/project/project-fns";
|
||||||
|
|
||||||
|
import {
|
||||||
|
PkiSubscriberStatus,
|
||||||
|
TCreatePkiSubscriberDTO,
|
||||||
|
TDeletePkiSubscriberDTO,
|
||||||
|
TGetPkiSubscriberDTO,
|
||||||
|
TIssuePkiSubscriberCertDTO,
|
||||||
|
TListPkiSubscriberCertsDTO,
|
||||||
|
TSignPkiSubscriberCertDTO,
|
||||||
|
TUpdatePkiSubscriberDTO
|
||||||
|
} from "./pki-subscriber-types";
|
||||||
|
|
||||||
|
type TPkiSubscriberServiceFactoryDep = {
|
||||||
|
pkiSubscriberDAL: Pick<
|
||||||
|
TPkiSubscriberDALFactory,
|
||||||
|
"create" | "findById" | "updateById" | "deleteById" | "transaction" | "find" | "findOne"
|
||||||
|
>;
|
||||||
|
certificateAuthorityDAL: Pick<TCertificateAuthorityDALFactory, "findById">;
|
||||||
|
certificateAuthorityCertDAL: Pick<TCertificateAuthorityCertDALFactory, "findById">;
|
||||||
|
certificateAuthoritySecretDAL: Pick<TCertificateAuthoritySecretDALFactory, "findOne">;
|
||||||
|
certificateAuthorityCrlDAL: Pick<TCertificateAuthorityCrlDALFactory, "findOne">;
|
||||||
|
certificateDAL: Pick<TCertificateDALFactory, "create" | "transaction" | "countCertificatesForPkiSubscriber" | "find">;
|
||||||
|
certificateBodyDAL: Pick<TCertificateBodyDALFactory, "create">;
|
||||||
|
certificateSecretDAL: Pick<TCertificateSecretDALFactory, "create">;
|
||||||
|
projectDAL: Pick<TProjectDALFactory, "findOne" | "updateById" | "transaction" | "findById" | "find">;
|
||||||
|
kmsService: Pick<TKmsServiceFactory, "generateKmsKey" | "decryptWithKmsKey" | "encryptWithKmsKey">;
|
||||||
|
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission">;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TPkiSubscriberServiceFactory = ReturnType<typeof pkiSubscriberServiceFactory>;
|
||||||
|
|
||||||
|
export const pkiSubscriberServiceFactory = ({
|
||||||
|
pkiSubscriberDAL,
|
||||||
|
certificateAuthorityDAL,
|
||||||
|
certificateAuthorityCertDAL,
|
||||||
|
certificateAuthoritySecretDAL,
|
||||||
|
certificateAuthorityCrlDAL,
|
||||||
|
certificateDAL,
|
||||||
|
certificateBodyDAL,
|
||||||
|
certificateSecretDAL,
|
||||||
|
projectDAL,
|
||||||
|
kmsService,
|
||||||
|
permissionService
|
||||||
|
}: TPkiSubscriberServiceFactoryDep) => {
|
||||||
|
const createSubscriber = async ({
|
||||||
|
name,
|
||||||
|
commonName,
|
||||||
|
status,
|
||||||
|
caId,
|
||||||
|
ttl,
|
||||||
|
subjectAlternativeNames,
|
||||||
|
keyUsages,
|
||||||
|
extendedKeyUsages,
|
||||||
|
projectId,
|
||||||
|
actorId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actor,
|
||||||
|
actorOrgId
|
||||||
|
}: TCreatePkiSubscriberDTO) => {
|
||||||
|
const { permission } = await permissionService.getProjectPermission({
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
projectId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId,
|
||||||
|
actionProjectType: ActionProjectType.CertificateManager
|
||||||
|
});
|
||||||
|
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
|
ProjectPermissionPkiSubscriberActions.Create,
|
||||||
|
subject(ProjectPermissionSub.PkiSubscribers, {
|
||||||
|
name
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
const newSubscriber = await pkiSubscriberDAL.create({
|
||||||
|
caId,
|
||||||
|
projectId,
|
||||||
|
name,
|
||||||
|
commonName,
|
||||||
|
status,
|
||||||
|
ttl,
|
||||||
|
subjectAlternativeNames,
|
||||||
|
keyUsages,
|
||||||
|
extendedKeyUsages
|
||||||
|
});
|
||||||
|
|
||||||
|
return newSubscriber;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getSubscriber = async ({
|
||||||
|
subscriberName,
|
||||||
|
projectId,
|
||||||
|
actorId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actor,
|
||||||
|
actorOrgId
|
||||||
|
}: TGetPkiSubscriberDTO) => {
|
||||||
|
const subscriber = await pkiSubscriberDAL.findOne({
|
||||||
|
name: subscriberName,
|
||||||
|
projectId
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!subscriber) throw new NotFoundError({ message: `PKI subscriber named '${subscriberName}' not found` });
|
||||||
|
|
||||||
|
const { permission } = await permissionService.getProjectPermission({
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
projectId: subscriber.projectId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId,
|
||||||
|
actionProjectType: ActionProjectType.CertificateManager
|
||||||
|
});
|
||||||
|
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
|
ProjectPermissionPkiSubscriberActions.Read,
|
||||||
|
subject(ProjectPermissionSub.PkiSubscribers, {
|
||||||
|
name: subscriber.name
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
return subscriber;
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateSubscriber = async ({
|
||||||
|
subscriberName,
|
||||||
|
projectId,
|
||||||
|
name,
|
||||||
|
commonName,
|
||||||
|
status,
|
||||||
|
caId,
|
||||||
|
ttl,
|
||||||
|
subjectAlternativeNames,
|
||||||
|
keyUsages,
|
||||||
|
extendedKeyUsages,
|
||||||
|
actorId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actor,
|
||||||
|
actorOrgId
|
||||||
|
}: TUpdatePkiSubscriberDTO) => {
|
||||||
|
const subscriber = await pkiSubscriberDAL.findOne({
|
||||||
|
name: subscriberName,
|
||||||
|
projectId
|
||||||
|
});
|
||||||
|
if (!subscriber) throw new NotFoundError({ message: `PKI subscriber named '${subscriberName}' not found` });
|
||||||
|
|
||||||
|
const { permission } = await permissionService.getProjectPermission({
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
projectId: subscriber.projectId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId,
|
||||||
|
actionProjectType: ActionProjectType.CertificateManager
|
||||||
|
});
|
||||||
|
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
|
ProjectPermissionPkiSubscriberActions.Edit,
|
||||||
|
subject(ProjectPermissionSub.PkiSubscribers, {
|
||||||
|
name: subscriber.name
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
const updatedSubscriber = await pkiSubscriberDAL.updateById(subscriber.id, {
|
||||||
|
caId,
|
||||||
|
name,
|
||||||
|
commonName,
|
||||||
|
status,
|
||||||
|
ttl,
|
||||||
|
subjectAlternativeNames,
|
||||||
|
keyUsages,
|
||||||
|
extendedKeyUsages
|
||||||
|
});
|
||||||
|
|
||||||
|
return updatedSubscriber;
|
||||||
|
};
|
||||||
|
|
||||||
|
const deleteSubscriber = async ({
|
||||||
|
subscriberName,
|
||||||
|
projectId,
|
||||||
|
actorId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actor,
|
||||||
|
actorOrgId
|
||||||
|
}: TDeletePkiSubscriberDTO) => {
|
||||||
|
const subscriber = await pkiSubscriberDAL.findOne({
|
||||||
|
name: subscriberName,
|
||||||
|
projectId
|
||||||
|
});
|
||||||
|
if (!subscriber) throw new NotFoundError({ message: `PKI subscriber named '${subscriberName}' not found` });
|
||||||
|
|
||||||
|
const { permission } = await permissionService.getProjectPermission({
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
projectId: subscriber.projectId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId,
|
||||||
|
actionProjectType: ActionProjectType.CertificateManager
|
||||||
|
});
|
||||||
|
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
|
ProjectPermissionPkiSubscriberActions.Delete,
|
||||||
|
subject(ProjectPermissionSub.PkiSubscribers, {
|
||||||
|
name: subscriber.name
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
await pkiSubscriberDAL.deleteById(subscriber.id);
|
||||||
|
|
||||||
|
return subscriber;
|
||||||
|
};
|
||||||
|
|
||||||
|
const issueSubscriberCert = async ({
|
||||||
|
subscriberName,
|
||||||
|
projectId,
|
||||||
|
actorId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actor,
|
||||||
|
actorOrgId
|
||||||
|
}: TIssuePkiSubscriberCertDTO) => {
|
||||||
|
const subscriber = await pkiSubscriberDAL.findOne({
|
||||||
|
name: subscriberName,
|
||||||
|
projectId
|
||||||
|
});
|
||||||
|
if (!subscriber) throw new NotFoundError({ message: `PKI subscriber named '${subscriberName}' not found` });
|
||||||
|
if (!subscriber.caId) throw new BadRequestError({ message: "Subscriber does not have an assigned issuing CA" });
|
||||||
|
|
||||||
|
const ca = await certificateAuthorityDAL.findById(subscriber.caId);
|
||||||
|
if (!ca) throw new NotFoundError({ message: `CA with ID '${subscriber.caId}' not found` });
|
||||||
|
|
||||||
|
const { permission } = await permissionService.getProjectPermission({
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
projectId: ca.projectId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId,
|
||||||
|
actionProjectType: ActionProjectType.CertificateManager
|
||||||
|
});
|
||||||
|
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
|
ProjectPermissionPkiSubscriberActions.IssueCert,
|
||||||
|
subject(ProjectPermissionSub.PkiSubscribers, {
|
||||||
|
name: subscriber.name
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
if (subscriber.status !== PkiSubscriberStatus.ACTIVE)
|
||||||
|
throw new BadRequestError({ message: "Subscriber is not active" });
|
||||||
|
if (ca.status !== CaStatus.ACTIVE) throw new BadRequestError({ message: "CA is not active" });
|
||||||
|
if (!ca.activeCaCertId) throw new BadRequestError({ message: "CA does not have a certificate installed" });
|
||||||
|
if (ca.requireTemplateForIssuance) {
|
||||||
|
throw new BadRequestError({ message: "Certificate template is required for issuance" });
|
||||||
|
}
|
||||||
|
const caCert = await certificateAuthorityCertDAL.findById(ca.activeCaCertId);
|
||||||
|
|
||||||
|
const certificateManagerKmsId = await getProjectKmsCertificateKeyId({
|
||||||
|
projectId: ca.projectId,
|
||||||
|
projectDAL,
|
||||||
|
kmsService
|
||||||
|
});
|
||||||
|
const kmsDecryptor = await kmsService.decryptWithKmsKey({
|
||||||
|
kmsId: certificateManagerKmsId
|
||||||
|
});
|
||||||
|
|
||||||
|
const decryptedCaCert = await kmsDecryptor({
|
||||||
|
cipherTextBlob: caCert.encryptedCertificate
|
||||||
|
});
|
||||||
|
|
||||||
|
const caCertObj = new x509.X509Certificate(decryptedCaCert);
|
||||||
|
const notBeforeDate = new Date();
|
||||||
|
const notAfterDate = new Date(new Date().getTime() + ms(subscriber.ttl));
|
||||||
|
const caCertNotBeforeDate = new Date(caCertObj.notBefore);
|
||||||
|
const caCertNotAfterDate = new Date(caCertObj.notAfter);
|
||||||
|
|
||||||
|
// check not before constraint
|
||||||
|
if (notBeforeDate < caCertNotBeforeDate) {
|
||||||
|
throw new BadRequestError({ message: "notBefore date is before CA certificate's notBefore date" });
|
||||||
|
}
|
||||||
|
|
||||||
|
// check not after constraint
|
||||||
|
if (notAfterDate > caCertNotAfterDate) {
|
||||||
|
throw new BadRequestError({ message: "notAfter date is after CA certificate's notAfter date" });
|
||||||
|
}
|
||||||
|
|
||||||
|
const alg = keyAlgorithmToAlgCfg(ca.keyAlgorithm as CertKeyAlgorithm);
|
||||||
|
const leafKeys = await crypto.subtle.generateKey(alg, true, ["sign", "verify"]);
|
||||||
|
|
||||||
|
const csrObj = await x509.Pkcs10CertificateRequestGenerator.create({
|
||||||
|
name: `CN=${subscriber.commonName}`,
|
||||||
|
keys: leafKeys,
|
||||||
|
signingAlgorithm: alg,
|
||||||
|
extensions: [
|
||||||
|
// eslint-disable-next-line no-bitwise
|
||||||
|
new x509.KeyUsagesExtension(x509.KeyUsageFlags.digitalSignature | x509.KeyUsageFlags.keyEncipherment)
|
||||||
|
],
|
||||||
|
attributes: [new x509.ChallengePasswordAttribute("password")]
|
||||||
|
});
|
||||||
|
|
||||||
|
const { caPrivateKey, caSecret } = await getCaCredentials({
|
||||||
|
caId: ca.id,
|
||||||
|
certificateAuthorityDAL,
|
||||||
|
certificateAuthoritySecretDAL,
|
||||||
|
projectDAL,
|
||||||
|
kmsService
|
||||||
|
});
|
||||||
|
|
||||||
|
const caCrl = await certificateAuthorityCrlDAL.findOne({ caSecretId: caSecret.id });
|
||||||
|
const appCfg = getConfig();
|
||||||
|
|
||||||
|
const distributionPointUrl = `${appCfg.SITE_URL}/api/v1/pki/crl/${caCrl.id}/der`;
|
||||||
|
const caIssuerUrl = `${appCfg.SITE_URL}/api/v1/pki/ca/${ca.id}/certificates/${caCert.id}/der`;
|
||||||
|
|
||||||
|
const extensions: x509.Extension[] = [
|
||||||
|
new x509.BasicConstraintsExtension(false),
|
||||||
|
new x509.CRLDistributionPointsExtension([distributionPointUrl]),
|
||||||
|
await x509.AuthorityKeyIdentifierExtension.create(caCertObj, false),
|
||||||
|
await x509.SubjectKeyIdentifierExtension.create(csrObj.publicKey),
|
||||||
|
new x509.AuthorityInfoAccessExtension({
|
||||||
|
caIssuers: new x509.GeneralName("url", caIssuerUrl)
|
||||||
|
}),
|
||||||
|
new x509.CertificatePolicyExtension(["2.5.29.32.0"]) // anyPolicy
|
||||||
|
];
|
||||||
|
|
||||||
|
const selectedKeyUsages = subscriber.keyUsages as CertKeyUsage[];
|
||||||
|
const keyUsagesBitValue = selectedKeyUsages.reduce((accum, keyUsage) => accum | x509.KeyUsageFlags[keyUsage], 0);
|
||||||
|
if (keyUsagesBitValue) {
|
||||||
|
extensions.push(new x509.KeyUsagesExtension(keyUsagesBitValue, true));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (subscriber.extendedKeyUsages.length) {
|
||||||
|
const extendedKeyUsagesExtension = new x509.ExtendedKeyUsageExtension(
|
||||||
|
subscriber.extendedKeyUsages.map((eku) => x509.ExtendedKeyUsage[eku as CertExtendedKeyUsage]),
|
||||||
|
true
|
||||||
|
);
|
||||||
|
extensions.push(extendedKeyUsagesExtension);
|
||||||
|
}
|
||||||
|
|
||||||
|
let altNamesArray: { type: "email" | "dns"; value: string }[] = [];
|
||||||
|
|
||||||
|
if (subscriber.subjectAlternativeNames?.length) {
|
||||||
|
altNamesArray = subscriber.subjectAlternativeNames.map((altName) => {
|
||||||
|
if (z.string().email().safeParse(altName).success) {
|
||||||
|
return { type: "email", value: altName };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isFQDN(altName, { allow_wildcard: true })) {
|
||||||
|
return { type: "dns", value: altName };
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new BadRequestError({ message: `Invalid SAN entry: ${altName}` });
|
||||||
|
});
|
||||||
|
|
||||||
|
const altNamesExtension = new x509.SubjectAlternativeNameExtension(altNamesArray, false);
|
||||||
|
extensions.push(altNamesExtension);
|
||||||
|
}
|
||||||
|
|
||||||
|
const serialNumber = createSerialNumber();
|
||||||
|
const leafCert = await x509.X509CertificateGenerator.create({
|
||||||
|
serialNumber,
|
||||||
|
subject: csrObj.subject,
|
||||||
|
issuer: caCertObj.subject,
|
||||||
|
notBefore: notBeforeDate,
|
||||||
|
notAfter: notAfterDate,
|
||||||
|
signingKey: caPrivateKey,
|
||||||
|
publicKey: csrObj.publicKey,
|
||||||
|
signingAlgorithm: alg,
|
||||||
|
extensions
|
||||||
|
});
|
||||||
|
|
||||||
|
const skLeafObj = KeyObject.from(leafKeys.privateKey);
|
||||||
|
const skLeaf = skLeafObj.export({ format: "pem", type: "pkcs8" }) as string;
|
||||||
|
|
||||||
|
const kmsEncryptor = await kmsService.encryptWithKmsKey({
|
||||||
|
kmsId: certificateManagerKmsId
|
||||||
|
});
|
||||||
|
const { cipherTextBlob: encryptedCertificate } = await kmsEncryptor({
|
||||||
|
plainText: Buffer.from(new Uint8Array(leafCert.rawData))
|
||||||
|
});
|
||||||
|
const { cipherTextBlob: encryptedPrivateKey } = await kmsEncryptor({
|
||||||
|
plainText: Buffer.from(skLeaf)
|
||||||
|
});
|
||||||
|
|
||||||
|
const { caCert: issuingCaCertificate, caCertChain } = await getCaCertChain({
|
||||||
|
caCertId: caCert.id,
|
||||||
|
certificateAuthorityDAL,
|
||||||
|
certificateAuthorityCertDAL,
|
||||||
|
projectDAL,
|
||||||
|
kmsService
|
||||||
|
});
|
||||||
|
|
||||||
|
const certificateChainPem = `${issuingCaCertificate}\n${caCertChain}`.trim();
|
||||||
|
|
||||||
|
const { cipherTextBlob: encryptedCertificateChain } = await kmsEncryptor({
|
||||||
|
plainText: Buffer.from(certificateChainPem)
|
||||||
|
});
|
||||||
|
|
||||||
|
await certificateDAL.transaction(async (tx) => {
|
||||||
|
const cert = await certificateDAL.create(
|
||||||
|
{
|
||||||
|
caId: ca.id,
|
||||||
|
caCertId: caCert.id,
|
||||||
|
pkiSubscriberId: subscriber.id,
|
||||||
|
status: CertStatus.ACTIVE,
|
||||||
|
friendlyName: subscriber.commonName,
|
||||||
|
commonName: subscriber.commonName,
|
||||||
|
altNames: subscriber.subjectAlternativeNames.join(","),
|
||||||
|
serialNumber,
|
||||||
|
notBefore: notBeforeDate,
|
||||||
|
notAfter: notAfterDate,
|
||||||
|
keyUsages: selectedKeyUsages,
|
||||||
|
extendedKeyUsages: subscriber.extendedKeyUsages as CertExtendedKeyUsage[]
|
||||||
|
},
|
||||||
|
tx
|
||||||
|
);
|
||||||
|
|
||||||
|
await certificateBodyDAL.create(
|
||||||
|
{
|
||||||
|
certId: cert.id,
|
||||||
|
encryptedCertificate,
|
||||||
|
encryptedCertificateChain
|
||||||
|
},
|
||||||
|
tx
|
||||||
|
);
|
||||||
|
|
||||||
|
await certificateSecretDAL.create(
|
||||||
|
{
|
||||||
|
certId: cert.id,
|
||||||
|
encryptedPrivateKey
|
||||||
|
},
|
||||||
|
tx
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
certificate: leafCert.toString("pem"),
|
||||||
|
certificateChain: certificateChainPem,
|
||||||
|
issuingCaCertificate,
|
||||||
|
privateKey: skLeaf,
|
||||||
|
serialNumber,
|
||||||
|
ca,
|
||||||
|
subscriber
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const signSubscriberCert = async ({
|
||||||
|
subscriberName,
|
||||||
|
projectId,
|
||||||
|
csr,
|
||||||
|
actorId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actor,
|
||||||
|
actorOrgId
|
||||||
|
}: TSignPkiSubscriberCertDTO) => {
|
||||||
|
const appCfg = getConfig();
|
||||||
|
const subscriber = await pkiSubscriberDAL.findOne({
|
||||||
|
name: subscriberName,
|
||||||
|
projectId
|
||||||
|
});
|
||||||
|
if (!subscriber) throw new NotFoundError({ message: `PKI subscriber named '${subscriberName}' not found` });
|
||||||
|
if (!subscriber.caId) throw new BadRequestError({ message: "Subscriber does not have an assigned issuing CA" });
|
||||||
|
|
||||||
|
const ca = await certificateAuthorityDAL.findById(subscriber.caId);
|
||||||
|
if (!ca) throw new NotFoundError({ message: `CA with ID '${subscriber.caId}' not found` });
|
||||||
|
|
||||||
|
const { permission } = await permissionService.getProjectPermission({
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
projectId: ca.projectId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId,
|
||||||
|
actionProjectType: ActionProjectType.CertificateManager
|
||||||
|
});
|
||||||
|
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
|
ProjectPermissionPkiSubscriberActions.IssueCert,
|
||||||
|
subject(ProjectPermissionSub.PkiSubscribers, {
|
||||||
|
name: subscriber.name
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
if (subscriber.status !== PkiSubscriberStatus.ACTIVE)
|
||||||
|
throw new BadRequestError({ message: "Subscriber is not active" });
|
||||||
|
if (ca.status !== CaStatus.ACTIVE) throw new BadRequestError({ message: "CA is not active" });
|
||||||
|
if (!ca.activeCaCertId) throw new BadRequestError({ message: "CA does not have a certificate installed" });
|
||||||
|
if (ca.requireTemplateForIssuance) {
|
||||||
|
throw new BadRequestError({ message: "Certificate template is required for issuance" });
|
||||||
|
}
|
||||||
|
const caCert = await certificateAuthorityCertDAL.findById(ca.activeCaCertId);
|
||||||
|
|
||||||
|
const certificateManagerKmsId = await getProjectKmsCertificateKeyId({
|
||||||
|
projectId: ca.projectId,
|
||||||
|
projectDAL,
|
||||||
|
kmsService
|
||||||
|
});
|
||||||
|
const kmsDecryptor = await kmsService.decryptWithKmsKey({
|
||||||
|
kmsId: certificateManagerKmsId
|
||||||
|
});
|
||||||
|
|
||||||
|
const decryptedCaCert = await kmsDecryptor({
|
||||||
|
cipherTextBlob: caCert.encryptedCertificate
|
||||||
|
});
|
||||||
|
|
||||||
|
const caCertObj = new x509.X509Certificate(decryptedCaCert);
|
||||||
|
const notBeforeDate = new Date();
|
||||||
|
const notAfterDate = new Date(new Date().getTime() + ms(subscriber.ttl));
|
||||||
|
const caCertNotBeforeDate = new Date(caCertObj.notBefore);
|
||||||
|
const caCertNotAfterDate = new Date(caCertObj.notAfter);
|
||||||
|
|
||||||
|
// check not before constraint
|
||||||
|
if (notBeforeDate < caCertNotBeforeDate) {
|
||||||
|
throw new BadRequestError({ message: "notBefore date is before CA certificate's notBefore date" });
|
||||||
|
}
|
||||||
|
|
||||||
|
// check not after constraint
|
||||||
|
if (notAfterDate > caCertNotAfterDate) {
|
||||||
|
throw new BadRequestError({ message: "notAfter date is after CA certificate's notAfter date" });
|
||||||
|
}
|
||||||
|
|
||||||
|
const alg = keyAlgorithmToAlgCfg(ca.keyAlgorithm as CertKeyAlgorithm);
|
||||||
|
|
||||||
|
const csrObj = new x509.Pkcs10CertificateRequest(csr);
|
||||||
|
|
||||||
|
const dn = parseDistinguishedName(csrObj.subject);
|
||||||
|
const cn = dn.commonName;
|
||||||
|
if (cn !== subscriber.commonName) {
|
||||||
|
throw new BadRequestError({ message: "Common name (CN) in the CSR does not match the subscriber's common name" });
|
||||||
|
}
|
||||||
|
|
||||||
|
const { caPrivateKey, caSecret } = await getCaCredentials({
|
||||||
|
caId: ca.id,
|
||||||
|
certificateAuthorityDAL,
|
||||||
|
certificateAuthoritySecretDAL,
|
||||||
|
projectDAL,
|
||||||
|
kmsService
|
||||||
|
});
|
||||||
|
|
||||||
|
const caCrl = await certificateAuthorityCrlDAL.findOne({ caSecretId: caSecret.id });
|
||||||
|
const distributionPointUrl = `${appCfg.SITE_URL}/api/v1/pki/crl/${caCrl.id}/der`;
|
||||||
|
const caIssuerUrl = `${appCfg.SITE_URL}/api/v1/pki/ca/${ca.id}/certificates/${caCert.id}/der`;
|
||||||
|
|
||||||
|
const extensions: x509.Extension[] = [
|
||||||
|
new x509.BasicConstraintsExtension(false),
|
||||||
|
await x509.AuthorityKeyIdentifierExtension.create(caCertObj, false),
|
||||||
|
await x509.SubjectKeyIdentifierExtension.create(csrObj.publicKey),
|
||||||
|
new x509.CRLDistributionPointsExtension([distributionPointUrl]),
|
||||||
|
new x509.AuthorityInfoAccessExtension({
|
||||||
|
caIssuers: new x509.GeneralName("url", caIssuerUrl)
|
||||||
|
}),
|
||||||
|
new x509.CertificatePolicyExtension(["2.5.29.32.0"]) // anyPolicy
|
||||||
|
];
|
||||||
|
|
||||||
|
// handle key usages
|
||||||
|
const csrKeyUsageExtension = csrObj.getExtension("2.5.29.15") as x509.KeyUsagesExtension;
|
||||||
|
let csrKeyUsages: CertKeyUsage[] = [];
|
||||||
|
if (csrKeyUsageExtension) {
|
||||||
|
csrKeyUsages = Object.values(CertKeyUsage).filter(
|
||||||
|
(keyUsage) => (x509.KeyUsageFlags[keyUsage] & csrKeyUsageExtension.usages) !== 0
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const selectedKeyUsages = subscriber.keyUsages as CertKeyUsage[];
|
||||||
|
|
||||||
|
if (csrKeyUsages.some((keyUsage) => !selectedKeyUsages.includes(keyUsage))) {
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: "Invalid key usage value based on subscriber's specified key usages"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const keyUsagesBitValue = selectedKeyUsages.reduce((accum, keyUsage) => accum | x509.KeyUsageFlags[keyUsage], 0);
|
||||||
|
if (keyUsagesBitValue) {
|
||||||
|
extensions.push(new x509.KeyUsagesExtension(keyUsagesBitValue, true));
|
||||||
|
}
|
||||||
|
|
||||||
|
// handle extended key usages
|
||||||
|
const csrExtendedKeyUsageExtension = csrObj.getExtension("2.5.29.37") as x509.ExtendedKeyUsageExtension;
|
||||||
|
let csrExtendedKeyUsages: CertExtendedKeyUsage[] = [];
|
||||||
|
if (csrExtendedKeyUsageExtension) {
|
||||||
|
csrExtendedKeyUsages = csrExtendedKeyUsageExtension.usages.map(
|
||||||
|
(ekuOid) => CertExtendedKeyUsageOIDToName[ekuOid as string]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const selectedExtendedKeyUsages = subscriber.extendedKeyUsages as CertExtendedKeyUsage[];
|
||||||
|
if (csrExtendedKeyUsages.some((eku) => !selectedExtendedKeyUsages.includes(eku))) {
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: "Invalid extended key usage value based on subscriber's specified extended key usages"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (selectedExtendedKeyUsages.length) {
|
||||||
|
extensions.push(
|
||||||
|
new x509.ExtendedKeyUsageExtension(
|
||||||
|
selectedExtendedKeyUsages.map((eku) => x509.ExtendedKeyUsage[eku]),
|
||||||
|
true
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// attempt to read from CSR if altNames is not explicitly provided
|
||||||
|
let altNamesArray: {
|
||||||
|
type: "email" | "dns";
|
||||||
|
value: string;
|
||||||
|
}[] = [];
|
||||||
|
|
||||||
|
const sanExtension = csrObj.extensions.find((ext) => ext.type === "2.5.29.17");
|
||||||
|
if (sanExtension) {
|
||||||
|
const sanNames = new x509.GeneralNames(sanExtension.value);
|
||||||
|
|
||||||
|
altNamesArray = sanNames.items
|
||||||
|
.filter((value) => value.type === "email" || value.type === "dns")
|
||||||
|
.map((name) => ({
|
||||||
|
type: name.type as "email" | "dns",
|
||||||
|
value: name.value
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
altNamesArray
|
||||||
|
.map((altName) => altName.value)
|
||||||
|
.some((altName) => !subscriber.subjectAlternativeNames.includes(altName))
|
||||||
|
) {
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: "Invalid subject alternative name based on subscriber's specified subject alternative names"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (altNamesArray.length) {
|
||||||
|
const altNamesExtension = new x509.SubjectAlternativeNameExtension(altNamesArray, false);
|
||||||
|
extensions.push(altNamesExtension);
|
||||||
|
}
|
||||||
|
|
||||||
|
const serialNumber = createSerialNumber();
|
||||||
|
const leafCert = await x509.X509CertificateGenerator.create({
|
||||||
|
serialNumber,
|
||||||
|
subject: csrObj.subject,
|
||||||
|
issuer: caCertObj.subject,
|
||||||
|
notBefore: notBeforeDate,
|
||||||
|
notAfter: notAfterDate,
|
||||||
|
signingKey: caPrivateKey,
|
||||||
|
publicKey: csrObj.publicKey,
|
||||||
|
signingAlgorithm: alg,
|
||||||
|
extensions
|
||||||
|
});
|
||||||
|
|
||||||
|
const kmsEncryptor = await kmsService.encryptWithKmsKey({
|
||||||
|
kmsId: certificateManagerKmsId
|
||||||
|
});
|
||||||
|
const { cipherTextBlob: encryptedCertificate } = await kmsEncryptor({
|
||||||
|
plainText: Buffer.from(new Uint8Array(leafCert.rawData))
|
||||||
|
});
|
||||||
|
|
||||||
|
const { caCert: issuingCaCertificate, caCertChain } = await getCaCertChain({
|
||||||
|
caCertId: ca.activeCaCertId,
|
||||||
|
certificateAuthorityDAL,
|
||||||
|
certificateAuthorityCertDAL,
|
||||||
|
projectDAL,
|
||||||
|
kmsService
|
||||||
|
});
|
||||||
|
|
||||||
|
const certificateChainPem = `${issuingCaCertificate}\n${caCertChain}`.trim();
|
||||||
|
|
||||||
|
const { cipherTextBlob: encryptedCertificateChain } = await kmsEncryptor({
|
||||||
|
plainText: Buffer.from(certificateChainPem)
|
||||||
|
});
|
||||||
|
|
||||||
|
await certificateDAL.transaction(async (tx) => {
|
||||||
|
const cert = await certificateDAL.create(
|
||||||
|
{
|
||||||
|
caId: ca.id,
|
||||||
|
caCertId: caCert.id,
|
||||||
|
pkiSubscriberId: subscriber.id,
|
||||||
|
status: CertStatus.ACTIVE,
|
||||||
|
friendlyName: subscriber.commonName,
|
||||||
|
commonName: subscriber.commonName,
|
||||||
|
altNames: subscriber.subjectAlternativeNames.join(","),
|
||||||
|
serialNumber,
|
||||||
|
notBefore: notBeforeDate,
|
||||||
|
notAfter: notAfterDate,
|
||||||
|
keyUsages: selectedKeyUsages,
|
||||||
|
extendedKeyUsages: selectedExtendedKeyUsages
|
||||||
|
},
|
||||||
|
tx
|
||||||
|
);
|
||||||
|
|
||||||
|
await certificateBodyDAL.create(
|
||||||
|
{
|
||||||
|
certId: cert.id,
|
||||||
|
encryptedCertificate,
|
||||||
|
encryptedCertificateChain
|
||||||
|
},
|
||||||
|
tx
|
||||||
|
);
|
||||||
|
|
||||||
|
return cert;
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
certificate: leafCert.toString("pem"),
|
||||||
|
certificateChain: `${issuingCaCertificate}\n${caCertChain}`.trim(),
|
||||||
|
issuingCaCertificate,
|
||||||
|
serialNumber,
|
||||||
|
ca,
|
||||||
|
commonName: subscriber.commonName,
|
||||||
|
subscriber
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const listSubscriberCerts = async ({
|
||||||
|
subscriberName,
|
||||||
|
projectId,
|
||||||
|
offset,
|
||||||
|
limit,
|
||||||
|
actorId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actor,
|
||||||
|
actorOrgId
|
||||||
|
}: TListPkiSubscriberCertsDTO) => {
|
||||||
|
const subscriber = await pkiSubscriberDAL.findOne({
|
||||||
|
name: subscriberName,
|
||||||
|
projectId
|
||||||
|
});
|
||||||
|
if (!subscriber) throw new NotFoundError({ message: `PKI subscriber named '${subscriberName}' not found` });
|
||||||
|
|
||||||
|
const { permission } = await permissionService.getProjectPermission({
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
projectId: subscriber.projectId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId,
|
||||||
|
actionProjectType: ActionProjectType.CertificateManager
|
||||||
|
});
|
||||||
|
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
|
ProjectPermissionPkiSubscriberActions.ListCerts,
|
||||||
|
subject(ProjectPermissionSub.PkiSubscribers, {
|
||||||
|
name: subscriber.name
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
const certificates = await certificateDAL.find(
|
||||||
|
{
|
||||||
|
pkiSubscriberId: subscriber.id
|
||||||
|
},
|
||||||
|
{ offset, limit, sort: [["updatedAt", "desc"]] }
|
||||||
|
);
|
||||||
|
|
||||||
|
const count = await certificateDAL.countCertificatesForPkiSubscriber(subscriber.id);
|
||||||
|
|
||||||
|
return {
|
||||||
|
certificates,
|
||||||
|
totalCount: count
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
createSubscriber,
|
||||||
|
getSubscriber,
|
||||||
|
updateSubscriber,
|
||||||
|
deleteSubscriber,
|
||||||
|
issueSubscriberCert,
|
||||||
|
signSubscriberCert,
|
||||||
|
listSubscriberCerts
|
||||||
|
};
|
||||||
|
};
|
54
backend/src/services/pki-subscriber/pki-subscriber-types.ts
Normal file
54
backend/src/services/pki-subscriber/pki-subscriber-types.ts
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
import { TProjectPermission } from "@app/lib/types";
|
||||||
|
|
||||||
|
import { CertExtendedKeyUsage, CertKeyUsage } from "../certificate/certificate-types";
|
||||||
|
|
||||||
|
export enum PkiSubscriberStatus {
|
||||||
|
ACTIVE = "active",
|
||||||
|
DISABLED = "disabled"
|
||||||
|
}
|
||||||
|
|
||||||
|
export type TCreatePkiSubscriberDTO = {
|
||||||
|
caId: string;
|
||||||
|
name: string;
|
||||||
|
commonName: string;
|
||||||
|
status: PkiSubscriberStatus;
|
||||||
|
ttl: string;
|
||||||
|
subjectAlternativeNames: string[];
|
||||||
|
keyUsages: CertKeyUsage[];
|
||||||
|
extendedKeyUsages: CertExtendedKeyUsage[];
|
||||||
|
} & TProjectPermission;
|
||||||
|
|
||||||
|
export type TGetPkiSubscriberDTO = {
|
||||||
|
subscriberName: string;
|
||||||
|
} & TProjectPermission;
|
||||||
|
|
||||||
|
export type TUpdatePkiSubscriberDTO = {
|
||||||
|
subscriberName: string;
|
||||||
|
caId?: string;
|
||||||
|
name?: string;
|
||||||
|
commonName?: string;
|
||||||
|
status?: PkiSubscriberStatus;
|
||||||
|
ttl?: string;
|
||||||
|
subjectAlternativeNames?: string[];
|
||||||
|
keyUsages?: CertKeyUsage[];
|
||||||
|
extendedKeyUsages?: CertExtendedKeyUsage[];
|
||||||
|
} & TProjectPermission;
|
||||||
|
|
||||||
|
export type TDeletePkiSubscriberDTO = {
|
||||||
|
subscriberName: string;
|
||||||
|
} & TProjectPermission;
|
||||||
|
|
||||||
|
export type TIssuePkiSubscriberCertDTO = {
|
||||||
|
subscriberName: string;
|
||||||
|
} & TProjectPermission;
|
||||||
|
|
||||||
|
export type TSignPkiSubscriberCertDTO = {
|
||||||
|
subscriberName: string;
|
||||||
|
csr: string;
|
||||||
|
} & TProjectPermission;
|
||||||
|
|
||||||
|
export type TListPkiSubscriberCertsDTO = {
|
||||||
|
subscriberName: string;
|
||||||
|
offset: number;
|
||||||
|
limit: number;
|
||||||
|
} & TProjectPermission;
|
@@ -15,6 +15,7 @@ import { TPermissionServiceFactory } from "@app/ee/services/permission/permissio
|
|||||||
import {
|
import {
|
||||||
ProjectPermissionActions,
|
ProjectPermissionActions,
|
||||||
ProjectPermissionCertificateActions,
|
ProjectPermissionCertificateActions,
|
||||||
|
ProjectPermissionPkiSubscriberActions,
|
||||||
ProjectPermissionSecretActions,
|
ProjectPermissionSecretActions,
|
||||||
ProjectPermissionSshHostActions,
|
ProjectPermissionSshHostActions,
|
||||||
ProjectPermissionSub
|
ProjectPermissionSub
|
||||||
@@ -35,6 +36,7 @@ import { groupBy } from "@app/lib/fn";
|
|||||||
import { alphaNumericNanoId } from "@app/lib/nanoid";
|
import { alphaNumericNanoId } from "@app/lib/nanoid";
|
||||||
import { TProjectPermission } from "@app/lib/types";
|
import { TProjectPermission } from "@app/lib/types";
|
||||||
import { TQueueServiceFactory } from "@app/queue";
|
import { TQueueServiceFactory } from "@app/queue";
|
||||||
|
import { TPkiSubscriberDALFactory } from "@app/services/pki-subscriber/pki-subscriber-dal";
|
||||||
|
|
||||||
import { ActorType } from "../auth/auth-type";
|
import { ActorType } from "../auth/auth-type";
|
||||||
import { TCertificateDALFactory } from "../certificate/certificate-dal";
|
import { TCertificateDALFactory } from "../certificate/certificate-dal";
|
||||||
@@ -86,6 +88,7 @@ import {
|
|||||||
TListProjectCasDTO,
|
TListProjectCasDTO,
|
||||||
TListProjectCertificateTemplatesDTO,
|
TListProjectCertificateTemplatesDTO,
|
||||||
TListProjectCertsDTO,
|
TListProjectCertsDTO,
|
||||||
|
TListProjectPkiSubscribersDTO,
|
||||||
TListProjectsDTO,
|
TListProjectsDTO,
|
||||||
TListProjectSshCasDTO,
|
TListProjectSshCasDTO,
|
||||||
TListProjectSshCertificatesDTO,
|
TListProjectSshCertificatesDTO,
|
||||||
@@ -145,6 +148,7 @@ type TProjectServiceFactoryDep = {
|
|||||||
"findById" | "findByIdWithWorkflowIntegrationDetails"
|
"findById" | "findByIdWithWorkflowIntegrationDetails"
|
||||||
>;
|
>;
|
||||||
projectUserMembershipRoleDAL: Pick<TProjectUserMembershipRoleDALFactory, "create">;
|
projectUserMembershipRoleDAL: Pick<TProjectUserMembershipRoleDALFactory, "create">;
|
||||||
|
pkiSubscriberDAL: Pick<TPkiSubscriberDALFactory, "find">;
|
||||||
certificateAuthorityDAL: Pick<TCertificateAuthorityDALFactory, "find">;
|
certificateAuthorityDAL: Pick<TCertificateAuthorityDALFactory, "find">;
|
||||||
certificateDAL: Pick<TCertificateDALFactory, "find" | "countCertificatesInProject">;
|
certificateDAL: Pick<TCertificateDALFactory, "find" | "countCertificatesInProject">;
|
||||||
certificateTemplateDAL: Pick<TCertificateTemplateDALFactory, "getCertTemplatesByProjectId">;
|
certificateTemplateDAL: Pick<TCertificateTemplateDALFactory, "getCertTemplatesByProjectId">;
|
||||||
@@ -207,6 +211,7 @@ export const projectServiceFactory = ({
|
|||||||
certificateTemplateDAL,
|
certificateTemplateDAL,
|
||||||
pkiCollectionDAL,
|
pkiCollectionDAL,
|
||||||
pkiAlertDAL,
|
pkiAlertDAL,
|
||||||
|
pkiSubscriberDAL,
|
||||||
sshCertificateAuthorityDAL,
|
sshCertificateAuthorityDAL,
|
||||||
sshCertificateAuthoritySecretDAL,
|
sshCertificateAuthoritySecretDAL,
|
||||||
sshCertificateDAL,
|
sshCertificateDAL,
|
||||||
@@ -653,7 +658,8 @@ export const projectServiceFactory = ({
|
|||||||
autoCapitalization: update.autoCapitalization,
|
autoCapitalization: update.autoCapitalization,
|
||||||
enforceCapitalization: update.autoCapitalization,
|
enforceCapitalization: update.autoCapitalization,
|
||||||
hasDeleteProtection: update.hasDeleteProtection,
|
hasDeleteProtection: update.hasDeleteProtection,
|
||||||
slug: update.slug
|
slug: update.slug,
|
||||||
|
secretSharing: update.secretSharing
|
||||||
});
|
});
|
||||||
|
|
||||||
return updatedProject;
|
return updatedProject;
|
||||||
@@ -1057,6 +1063,45 @@ export const projectServiceFactory = ({
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return list of PKI subscribers for project
|
||||||
|
*/
|
||||||
|
const listProjectPkiSubscribers = async ({
|
||||||
|
actorId,
|
||||||
|
actorOrgId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actor,
|
||||||
|
projectId
|
||||||
|
}: TListProjectPkiSubscribersDTO) => {
|
||||||
|
const { permission } = await permissionService.getProjectPermission({
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
projectId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId,
|
||||||
|
actionProjectType: ActionProjectType.CertificateManager
|
||||||
|
});
|
||||||
|
|
||||||
|
const allowedSubscribers = [];
|
||||||
|
|
||||||
|
// (dangtony98): room to optimize
|
||||||
|
const subscribers = await pkiSubscriberDAL.find({ projectId });
|
||||||
|
|
||||||
|
for (const subscriber of subscribers) {
|
||||||
|
const canRead = permission.can(
|
||||||
|
ProjectPermissionPkiSubscriberActions.Read,
|
||||||
|
subject(ProjectPermissionSub.PkiSubscribers, {
|
||||||
|
name: subscriber.name
|
||||||
|
})
|
||||||
|
);
|
||||||
|
if (canRead) {
|
||||||
|
allowedSubscribers.push(subscriber);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return allowedSubscribers;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return list of certificate templates for project
|
* Return list of certificate templates for project
|
||||||
*/
|
*/
|
||||||
@@ -1156,17 +1201,15 @@ export const projectServiceFactory = ({
|
|||||||
const hosts = await sshHostDAL.findSshHostsWithLoginMappings(projectId);
|
const hosts = await sshHostDAL.findSshHostsWithLoginMappings(projectId);
|
||||||
|
|
||||||
for (const host of hosts) {
|
for (const host of hosts) {
|
||||||
try {
|
const canRead = permission.can(
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ProjectPermissionSshHostActions.Read,
|
||||||
ProjectPermissionSshHostActions.Read,
|
subject(ProjectPermissionSub.SshHosts, {
|
||||||
subject(ProjectPermissionSub.SshHosts, {
|
hostname: host.hostname
|
||||||
hostname: host.hostname
|
})
|
||||||
})
|
);
|
||||||
);
|
|
||||||
|
|
||||||
|
if (canRead) {
|
||||||
allowedHosts.push(host);
|
allowedHosts.push(host);
|
||||||
} catch {
|
|
||||||
// intentionally ignore projects where user lacks access
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1930,6 +1973,7 @@ export const projectServiceFactory = ({
|
|||||||
listProjectSshCas,
|
listProjectSshCas,
|
||||||
listProjectSshHosts,
|
listProjectSshHosts,
|
||||||
listProjectSshHostGroups,
|
listProjectSshHostGroups,
|
||||||
|
listProjectPkiSubscribers,
|
||||||
listProjectSshCertificates,
|
listProjectSshCertificates,
|
||||||
listProjectSshCertificateTemplates,
|
listProjectSshCertificateTemplates,
|
||||||
updateVersionLimit,
|
updateVersionLimit,
|
||||||
|
@@ -93,6 +93,7 @@ export type TUpdateProjectDTO = {
|
|||||||
autoCapitalization?: boolean;
|
autoCapitalization?: boolean;
|
||||||
hasDeleteProtection?: boolean;
|
hasDeleteProtection?: boolean;
|
||||||
slug?: string;
|
slug?: string;
|
||||||
|
secretSharing?: boolean;
|
||||||
};
|
};
|
||||||
} & Omit<TProjectPermission, "projectId">;
|
} & Omit<TProjectPermission, "projectId">;
|
||||||
|
|
||||||
@@ -155,6 +156,7 @@ export type TListProjectCertificateTemplatesDTO = TProjectPermission;
|
|||||||
export type TListProjectSshCasDTO = TProjectPermission;
|
export type TListProjectSshCasDTO = TProjectPermission;
|
||||||
export type TListProjectSshHostsDTO = TProjectPermission;
|
export type TListProjectSshHostsDTO = TProjectPermission;
|
||||||
export type TListProjectSshCertificateTemplatesDTO = TProjectPermission;
|
export type TListProjectSshCertificateTemplatesDTO = TProjectPermission;
|
||||||
|
export type TListProjectPkiSubscribersDTO = TProjectPermission;
|
||||||
export type TListProjectSshCertificatesDTO = {
|
export type TListProjectSshCertificatesDTO = {
|
||||||
offset: number;
|
offset: number;
|
||||||
limit: number;
|
limit: number;
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user