1
0
mirror of https://github.com/Infisical/infisical.git synced 2025-03-25 14:05:03 +00:00

Compare commits

..

71 Commits

Author SHA1 Message Date
7622cac07e Update high-availability.mdx 2024-07-18 09:58:48 +02:00
a101602e0a Update overview.mdx 2024-07-18 08:25:37 +02:00
ca63a7baa7 Standalone binary docs 2024-07-18 08:25:31 +02:00
ff4f15c437 HA deployment docs 2024-07-18 08:25:26 +02:00
d6c2715852 Update mint.json 2024-07-18 08:25:14 +02:00
fc386c0cbc Create haproxy-stats.png 2024-07-18 08:25:11 +02:00
263a88379f Create ha-stack.png 2024-07-18 08:25:07 +02:00
95a4661787 Merge pull request from Infisical/maidul-dqwdfffwr312
add ips for whitelisting
2024-07-17 19:14:49 -04:00
7e9c846ba3 add ips for whitelisting 2024-07-17 19:11:55 -04:00
c141b916d3 Merge pull request from Infisical/further-scim-smoothening
Further SCIM Smoothening
2024-07-17 18:04:25 +07:00
1ae375188b Correct database error message 2024-07-17 11:27:09 +07:00
22b954b657 Further smoothen scim 2024-07-17 11:24:57 +07:00
9efeb8926f Merge pull request from Infisical/maidul-dewfewfqwef
Address vanta postcss update
2024-07-16 21:46:14 -04:00
389bbfcade fix vanta postcss 2024-07-16 21:44:33 -04:00
0b8427a004 Merge pull request from Infisical/feat/added-support-for-oidc-auth-in-cli
feat: added support for oidc auth in cli
2024-07-17 00:51:51 +08:00
8a470772e3 Merge pull request from Infisical/polish-scim-groups
Add SCIM user activation/deactivation
2024-07-16 12:09:50 -04:00
853f3c40bc Adjustments to migration file 2024-07-16 22:20:56 +07:00
fed44f328d Merge pull request from akhilmhdh/feat/aws-kms-sm
fix: slug too big for project fixed
2024-07-16 09:50:08 -04:00
a1d00f2c41 Add SCIM user activation/deactivation 2024-07-16 20:19:27 +07:00
95a68f2c2d Merge pull request from Infisical/improve-auth-method-errors
Improve Native Auth Method Forbidden Errors
2024-07-16 15:00:12 +07:00
db7c0c45f6 Merge pull request from Infisical/fix-identity-projects
Fix Identity-Project Provisioning Modal — Filter Current Org Projects
2024-07-16 14:59:41 +07:00
82bca03162 Filter out only projects that are part of current org in identity project modal 2024-07-16 14:31:40 +07:00
043c04778f Improve native auth method unauthorized errors 2024-07-16 13:47:46 +07:00
=
560cd81a1c fix: slug too big for project fixed 2024-07-16 11:26:45 +05:30
df3a87fabf Merge pull request from Infisical/daniel/operator-azure-fix
feat(k8-operator): customizable azure auth resource url
2024-07-16 06:29:13 +02:00
6eae98c1d4 Update login.mdx 2024-07-16 05:45:48 +02:00
6ceeccf583 Update kubernetes.mdx 2024-07-16 05:25:30 +02:00
9b0b14b847 Merge pull request from Infisical/daniel/azure-fix
fix(auth): Azure audience formatting bug
2024-07-16 04:50:49 +02:00
78f4c0f002 Update Chart.yaml 2024-07-16 04:46:32 +02:00
6cff2f0437 Update values.yaml 2024-07-16 04:46:24 +02:00
6cefb180d6 Update SDK and go mod tidy 2024-07-16 04:44:32 +02:00
59a44155c5 Azure resource 2024-07-16 04:43:53 +02:00
d0ad9c6b17 Update sample.yaml 2024-07-16 04:43:46 +02:00
58a406b114 Update secrets.infisical.com_infisicalsecrets.yaml 2024-07-16 04:43:42 +02:00
8a85695dc5 Custom azure resource 2024-07-16 04:43:38 +02:00
7ed8feee6f Update identity-azure-auth-fns.ts 2024-07-16 04:15:04 +02:00
de67c0ad9f Merge pull request from akhilmhdh/feat/folder-improvement-tf
New folder endpoints for terraform
2024-07-15 21:40:24 -04:00
b8d11d31a6 Merge pull request from Infisical/handbook-update
updated hiring handbook
2024-07-15 21:38:33 -04:00
d630ceaffe updated hiring handbook 2024-07-15 17:19:56 -07:00
a89e60f296 Merge pull request from Infisical/maidul-sddwqdwdwqe123
Remove org read check on project fetch
2024-07-15 16:38:21 -04:00
a5d9abf1c8 remove org read check on projects fetch 2024-07-15 16:33:11 -04:00
d97dea2573 Merge pull request from Infisical/misc/removed-aws-global-config-update
misc: moved aws creds to constructor
2024-07-16 02:38:57 +08:00
bc58f6b988 misc: moved aws creds to constructor 2024-07-16 02:31:31 +08:00
ed8e3f34fb Merge pull request from akhilmhdh/feat/aws-kms-sm
aws kms support base setup
2024-07-15 12:47:30 -04:00
91315c88c3 Merge pull request from Infisical/misc/displayed-project-name-in-slack-webhook
misc: displayed project name in slack webhook
2024-07-16 00:18:42 +08:00
9267f881d6 misc: displayed project name in slack webhook 2024-07-15 16:49:06 +08:00
c2ddb7e2fe misc: updated go-sdk version 2024-07-15 12:26:31 +08:00
c90ecd336c Merge pull request from Infisical/fix-scim-groups
Fix SCIM PATCH /Groups Fn
2024-07-15 00:57:46 +07:00
d8b1da3ddd Fix SCIM patch group fn 2024-07-15 00:29:18 +07:00
58e86382fe Merge pull request from Infisical/update-used-count-on-prem
Update dynamic seat count on prem
2024-07-14 20:13:42 +07:00
2080c4419e Update dynamic seat count on prem 2024-07-14 14:25:32 +07:00
b582a4a06d Merge pull request from DDDASHXD/sebastian/ui-fix
Fix: Features card overflow
2024-07-12 21:05:56 +02:00
a5c6a864de Fix: Features card overflow 2024-07-12 18:57:08 +00:00
5082c1ba3b Merge pull request from Infisical/misc/soft-delete-shared-secrets
misc: soft delete shared secrets upon expiry
2024-07-12 12:56:26 -04:00
cceb08b1b5 Merge pull request from Infisical/misc/address-ws-vulnerability-via-package-update
misc: addressed ws vulnerability via package update
2024-07-12 12:20:15 -04:00
4c34e58945 Merge pull request from Infisical/dependabot/npm_and_yarn/frontend/axios-0.28.0
build(deps): bump axios from 0.27.2 to 0.28.0 in /frontend
2024-07-12 19:17:08 +05:30
72de1901a1 build(deps): bump axios from 0.27.2 to 0.28.0 in /frontend
Bumps [axios](https://github.com/axios/axios) from 0.27.2 to 0.28.0.
- [Release notes](https://github.com/axios/axios/releases)
- [Changelog](https://github.com/axios/axios/blob/v0.28.0/CHANGELOG.md)
- [Commits](https://github.com/axios/axios/compare/v0.27.2...v0.28.0)

---
updated-dependencies:
- dependency-name: axios
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-07-12 13:37:13 +00:00
65fefcdd87 Merge pull request from Infisical/dependabot/npm_and_yarn/frontend/postcss-8.4.39
build(deps): bump postcss from 8.4.14 to 8.4.39 in /frontend
2024-07-12 19:03:59 +05:30
7137c94fa2 build(deps): bump postcss from 8.4.14 to 8.4.39 in /frontend
Bumps [postcss](https://github.com/postcss/postcss) from 8.4.14 to 8.4.39.
- [Release notes](https://github.com/postcss/postcss/releases)
- [Changelog](https://github.com/postcss/postcss/blob/main/CHANGELOG.md)
- [Commits](https://github.com/postcss/postcss/compare/8.4.14...8.4.39)

---
updated-dependencies:
- dependency-name: postcss
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-07-12 13:24:54 +00:00
52ea7dfa61 Merge pull request from Infisical/dependabot/go_modules/cli/github.com/rs/cors-1.11.0
build(deps): bump github.com/rs/cors from 1.9.0 to 1.11.0 in /cli
2024-07-12 18:52:25 +05:30
093925ed0e build(deps): bump github.com/rs/cors from 1.9.0 to 1.11.0 in /cli
Bumps [github.com/rs/cors](https://github.com/rs/cors) from 1.9.0 to 1.11.0.
- [Commits](https://github.com/rs/cors/compare/v1.9.0...v1.11.0)

---
updated-dependencies:
- dependency-name: github.com/rs/cors
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-07-12 13:20:45 +00:00
356afd18c4 feat: added support for oidc auth in cli 2024-07-12 18:28:09 +08:00
=
539785acae docs: updated api reference docs for the new folder endpoint 2024-07-12 14:29:19 +05:30
=
3c63346d3a feat: new get-by-id for folder for tf 2024-07-12 14:24:13 +05:30
0c673f6cca misc: addressed ws vulnerability via package update 2024-07-12 15:36:49 +08:00
=
5d4c7c2cbf feat: added encrypt/decrypt with key for kms service and changed kms encrytion to hoc to avoid back to back db calls 2024-07-10 15:23:02 +05:30
=
08f0bf9c67 feat: fixed migration down missing orgid 2024-07-10 14:47:44 +05:30
=
654dd97793 feat: external kms router defined not plugged in 2024-07-10 12:48:36 +05:30
=
2e7baf8c89 feat: added external kms router but not connected with the server yet 2024-07-10 12:48:35 +05:30
=
7ca7a95070 feat: kms service changes for db change 2024-07-10 12:48:35 +05:30
=
71c49c8b90 feat: kms db schema changes to support external and internal kms uniformly 2024-07-10 12:48:35 +05:30
95 changed files with 4166 additions and 564 deletions
backend
package-lock.jsonpackage.json
src
@types
db
ee
lib/api-docs
server/routes
services
cli
company
docs
api-reference/endpoints/folders
cli/commands
images/self-hosting/deployment-options/native
integrations/platforms
mint.json
self-hosting
frontend
package-lock.jsonpackage.json
src
hooks/api/users
pages/org/[id]/overview
views/Org
IdentityPage/components/IdentityProjectsSection
MembersPage/components/OrgMembersTab/components/OrgMembersSection
helm-charts/secrets-operator
k8-operator

1062
backend/package-lock.json generated

File diff suppressed because it is too large Load Diff

@ -106,6 +106,7 @@
},
"dependencies": {
"@aws-sdk/client-iam": "^3.525.0",
"@aws-sdk/client-kms": "^3.609.0",
"@aws-sdk/client-secrets-manager": "^3.504.0",
"@aws-sdk/client-sts": "^3.600.0",
"@casl/ability": "^6.5.0",

@ -9,6 +9,7 @@ import { TAuditLogStreamServiceFactory } from "@app/ee/services/audit-log-stream
import { TCertificateAuthorityCrlServiceFactory } from "@app/ee/services/certificate-authority-crl/certificate-authority-crl-service";
import { TDynamicSecretServiceFactory } from "@app/ee/services/dynamic-secret/dynamic-secret-service";
import { TDynamicSecretLeaseServiceFactory } from "@app/ee/services/dynamic-secret-lease/dynamic-secret-lease-service";
import { TExternalKmsServiceFactory } from "@app/ee/services/external-kms/external-kms-service";
import { TGroupServiceFactory } from "@app/ee/services/group/group-service";
import { TIdentityProjectAdditionalPrivilegeServiceFactory } from "@app/ee/services/identity-project-additional-privilege/identity-project-additional-privilege-service";
import { TLdapConfigServiceFactory } from "@app/ee/services/ldap-config/ldap-config-service";
@ -163,6 +164,7 @@ declare module "fastify" {
secretSharing: TSecretSharingServiceFactory;
rateLimit: TRateLimitServiceFactory;
userEngagement: TUserEngagementServiceFactory;
externalKms: TExternalKmsServiceFactory;
};
// this is exclusive use for middlewares in which we need to inject data
// everywhere else access using service layer

@ -59,6 +59,9 @@ import {
TDynamicSecrets,
TDynamicSecretsInsert,
TDynamicSecretsUpdate,
TExternalKms,
TExternalKmsInsert,
TExternalKmsUpdate,
TGitAppInstallSessions,
TGitAppInstallSessionsInsert,
TGitAppInstallSessionsUpdate,
@ -125,6 +128,9 @@ import {
TIntegrations,
TIntegrationsInsert,
TIntegrationsUpdate,
TInternalKms,
TInternalKmsInsert,
TInternalKmsUpdate,
TKmsKeys,
TKmsKeysInsert,
TKmsKeysUpdate,
@ -656,6 +662,8 @@ declare module "knex/types/tables" {
TKmsRootConfigInsert,
TKmsRootConfigUpdate
>;
[TableName.InternalKms]: KnexOriginal.CompositeTableType<TInternalKms, TInternalKmsInsert, TInternalKmsUpdate>;
[TableName.ExternalKms]: KnexOriginal.CompositeTableType<TExternalKms, TExternalKmsInsert, TExternalKmsUpdate>;
[TableName.KmsKey]: KnexOriginal.CompositeTableType<TKmsKeys, TKmsKeysInsert, TKmsKeysUpdate>;
[TableName.KmsKeyVersion]: KnexOriginal.CompositeTableType<
TKmsKeyVersions,

@ -0,0 +1,256 @@
import slugify from "@sindresorhus/slugify";
import { Knex } from "knex";
import { alphaNumericNanoId } from "@app/lib/nanoid";
import { TableName } from "../schemas";
const createInternalKmsTableAndBackfillData = async (knex: Knex) => {
const doesOldKmsKeyTableExist = await knex.schema.hasTable(TableName.KmsKey);
const doesInternalKmsTableExist = await knex.schema.hasTable(TableName.InternalKms);
// building the internal kms table by filling from old kms table
if (doesOldKmsKeyTableExist && !doesInternalKmsTableExist) {
await knex.schema.createTable(TableName.InternalKms, (tb) => {
tb.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
tb.binary("encryptedKey").notNullable();
tb.string("encryptionAlgorithm").notNullable();
tb.integer("version").defaultTo(1).notNullable();
tb.uuid("kmsKeyId").unique().notNullable();
tb.foreign("kmsKeyId").references("id").inTable(TableName.KmsKey).onDelete("CASCADE");
});
// copy the old kms and backfill
const oldKmsKey = await knex(TableName.KmsKey).select("version", "encryptedKey", "encryptionAlgorithm", "id");
if (oldKmsKey.length) {
await knex(TableName.InternalKms).insert(
oldKmsKey.map((el) => ({
encryptionAlgorithm: el.encryptionAlgorithm,
encryptedKey: el.encryptedKey,
kmsKeyId: el.id,
version: el.version
}))
);
}
}
};
const renameKmsKeyVersionTableAsInternalKmsKeyVersion = async (knex: Knex) => {
const doesOldKmsKeyVersionTableExist = await knex.schema.hasTable(TableName.KmsKeyVersion);
const doesNewKmsKeyVersionTableExist = await knex.schema.hasTable(TableName.InternalKmsKeyVersion);
if (doesOldKmsKeyVersionTableExist && !doesNewKmsKeyVersionTableExist) {
// because we haven't started using versioning for kms thus no data exist
await knex.schema.renameTable(TableName.KmsKeyVersion, TableName.InternalKmsKeyVersion);
const hasKmsKeyIdColumn = await knex.schema.hasColumn(TableName.InternalKmsKeyVersion, "kmsKeyId");
const hasInternalKmsIdColumn = await knex.schema.hasColumn(TableName.InternalKmsKeyVersion, "internalKmsId");
await knex.schema.alterTable(TableName.InternalKmsKeyVersion, (tb) => {
if (hasKmsKeyIdColumn) tb.dropColumn("kmsKeyId");
if (!hasInternalKmsIdColumn) {
tb.uuid("internalKmsId").notNullable();
tb.foreign("internalKmsId").references("id").inTable(TableName.InternalKms).onDelete("CASCADE");
}
});
}
};
const createExternalKmsKeyTable = async (knex: Knex) => {
const doesExternalKmsServiceExist = await knex.schema.hasTable(TableName.ExternalKms);
if (!doesExternalKmsServiceExist) {
await knex.schema.createTable(TableName.ExternalKms, (tb) => {
tb.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
tb.string("provider").notNullable();
tb.binary("encryptedProviderInputs").notNullable();
tb.string("status");
tb.string("statusDetails");
tb.uuid("kmsKeyId").unique().notNullable();
tb.foreign("kmsKeyId").references("id").inTable(TableName.KmsKey).onDelete("CASCADE");
});
}
};
const removeNonRequiredFieldsFromKmsKeyTableAndBackfillRequiredData = async (knex: Knex) => {
const doesOldKmsKeyTableExist = await knex.schema.hasTable(TableName.KmsKey);
// building the internal kms table by filling from old kms table
if (doesOldKmsKeyTableExist) {
const hasSlugColumn = await knex.schema.hasColumn(TableName.KmsKey, "slug");
const hasEncryptedKeyColumn = await knex.schema.hasColumn(TableName.KmsKey, "encryptedKey");
const hasEncryptionAlgorithmColumn = await knex.schema.hasColumn(TableName.KmsKey, "encryptionAlgorithm");
const hasVersionColumn = await knex.schema.hasColumn(TableName.KmsKey, "version");
const hasTimestamps = await knex.schema.hasColumn(TableName.KmsKey, "createdAt");
const hasProjectId = await knex.schema.hasColumn(TableName.KmsKey, "projectId");
const hasOrgId = await knex.schema.hasColumn(TableName.KmsKey, "orgId");
await knex.schema.alterTable(TableName.KmsKey, (tb) => {
if (!hasSlugColumn) tb.string("slug", 32);
if (hasEncryptedKeyColumn) tb.dropColumn("encryptedKey");
if (hasEncryptionAlgorithmColumn) tb.dropColumn("encryptionAlgorithm");
if (hasVersionColumn) tb.dropColumn("version");
if (!hasTimestamps) tb.timestamps(true, true, true);
});
// backfill all org id in kms key because its gonna be changed to non nullable
if (hasProjectId && hasOrgId) {
await knex(TableName.KmsKey)
.whereNull("orgId")
.update({
// eslint-disable-next-line
// @ts-ignore because generate schema happens after this
orgId: knex(TableName.Project)
.select("orgId")
.where("id", knex.raw("??", [`${TableName.KmsKey}.projectId`]))
});
}
// backfill slugs in kms
const missingSlugs = await knex(TableName.KmsKey).whereNull("slug").select("id");
if (missingSlugs.length) {
await knex(TableName.KmsKey)
// eslint-disable-next-line
// @ts-ignore because generate schema happens after this
.insert(missingSlugs.map(({ id }) => ({ id, slug: slugify(alphaNumericNanoId(8).toLowerCase()) })))
.onConflict("id")
.merge();
}
await knex.schema.alterTable(TableName.KmsKey, (tb) => {
if (hasOrgId) tb.uuid("orgId").notNullable().alter();
tb.string("slug", 32).notNullable().alter();
if (hasProjectId) tb.dropColumn("projectId");
if (hasOrgId) tb.unique(["orgId", "slug"]);
});
}
};
/*
* The goal for this migration is split the existing kms key into three table
* the kms-key table would be a container table that contains
* the internal kms key table and external kms table
*/
export async function up(knex: Knex): Promise<void> {
await createInternalKmsTableAndBackfillData(knex);
await renameKmsKeyVersionTableAsInternalKmsKeyVersion(knex);
await removeNonRequiredFieldsFromKmsKeyTableAndBackfillRequiredData(knex);
await createExternalKmsKeyTable(knex);
const doesOrgKmsKeyExist = await knex.schema.hasColumn(TableName.Organization, "kmsDefaultKeyId");
if (!doesOrgKmsKeyExist) {
await knex.schema.alterTable(TableName.Organization, (tb) => {
tb.uuid("kmsDefaultKeyId").nullable();
tb.foreign("kmsDefaultKeyId").references("id").inTable(TableName.KmsKey);
});
}
const doesProjectKmsSecretManagerKeyExist = await knex.schema.hasColumn(TableName.Project, "kmsSecretManagerKeyId");
if (!doesProjectKmsSecretManagerKeyExist) {
await knex.schema.alterTable(TableName.Project, (tb) => {
tb.uuid("kmsSecretManagerKeyId").nullable();
tb.foreign("kmsSecretManagerKeyId").references("id").inTable(TableName.KmsKey);
});
}
}
const renameInternalKmsKeyVersionBackToKmsKeyVersion = async (knex: Knex) => {
const doesInternalKmsKeyVersionTableExist = await knex.schema.hasTable(TableName.InternalKmsKeyVersion);
const doesKmsKeyVersionTableExist = await knex.schema.hasTable(TableName.KmsKeyVersion);
if (doesInternalKmsKeyVersionTableExist && !doesKmsKeyVersionTableExist) {
// because we haven't started using versioning for kms thus no data exist
await knex.schema.renameTable(TableName.InternalKmsKeyVersion, TableName.KmsKeyVersion);
const hasInternalKmsIdColumn = await knex.schema.hasColumn(TableName.KmsKeyVersion, "internalKmsId");
const hasKmsKeyIdColumn = await knex.schema.hasColumn(TableName.KmsKeyVersion, "kmsKeyId");
await knex.schema.alterTable(TableName.KmsKeyVersion, (tb) => {
if (hasInternalKmsIdColumn) tb.dropColumn("internalKmsId");
if (!hasKmsKeyIdColumn) {
tb.uuid("kmsKeyId").notNullable();
tb.foreign("kmsKeyId").references("id").inTable(TableName.KmsKey).onDelete("CASCADE");
}
});
}
};
const bringBackKmsKeyFields = async (knex: Knex) => {
const doesOldKmsKeyTableExist = await knex.schema.hasTable(TableName.KmsKey);
const doesInternalKmsTableExist = await knex.schema.hasTable(TableName.InternalKms);
if (doesOldKmsKeyTableExist && doesInternalKmsTableExist) {
const hasSlug = await knex.schema.hasColumn(TableName.KmsKey, "slug");
const hasEncryptedKeyColumn = await knex.schema.hasColumn(TableName.KmsKey, "encryptedKey");
const hasEncryptionAlgorithmColumn = await knex.schema.hasColumn(TableName.KmsKey, "encryptionAlgorithm");
const hasVersionColumn = await knex.schema.hasColumn(TableName.KmsKey, "version");
const hasNullableOrgId = await knex.schema.hasColumn(TableName.KmsKey, "orgId");
const hasProjectIdColumn = await knex.schema.hasColumn(TableName.KmsKey, "projectId");
await knex.schema.alterTable(TableName.KmsKey, (tb) => {
if (!hasEncryptedKeyColumn) tb.binary("encryptedKey");
if (!hasEncryptionAlgorithmColumn) tb.string("encryptionAlgorithm");
if (!hasVersionColumn) tb.integer("version").defaultTo(1);
if (hasNullableOrgId) tb.uuid("orgId").nullable().alter();
if (!hasProjectIdColumn) {
tb.string("projectId");
tb.foreign("projectId").references("id").inTable(TableName.Project).onDelete("CASCADE");
}
if (hasSlug) tb.dropColumn("slug");
});
}
};
const backfillKmsKeyFromInternalKmsTable = async (knex: Knex) => {
const doesOldKmsKeyTableExist = await knex.schema.hasTable(TableName.KmsKey);
const doesInternalKmsTableExist = await knex.schema.hasTable(TableName.InternalKms);
if (doesInternalKmsTableExist && doesOldKmsKeyTableExist) {
// backfill kms key with internal kms data
await knex(TableName.KmsKey).update({
// eslint-disable-next-line
// @ts-ignore because generate schema happens after this
encryptedKey: knex(TableName.InternalKms)
.select("encryptedKey")
.where("kmsKeyId", knex.raw("??", [`${TableName.KmsKey}.id`])),
// eslint-disable-next-line
// @ts-ignore because generate schema happens after this
encryptionAlgorithm: knex(TableName.InternalKms)
.select("encryptionAlgorithm")
.where("kmsKeyId", knex.raw("??", [`${TableName.KmsKey}.id`])),
// eslint-disable-next-line
// @ts-ignore because generate schema happens after this
projectId: knex(TableName.Project)
.select("id")
.where("kmsCertificateKeyId", knex.raw("??", [`${TableName.KmsKey}.id`]))
});
}
};
export async function down(knex: Knex): Promise<void> {
const doesOrgKmsKeyExist = await knex.schema.hasColumn(TableName.Organization, "kmsDefaultKeyId");
if (doesOrgKmsKeyExist) {
await knex.schema.alterTable(TableName.Organization, (tb) => {
tb.dropColumn("kmsDefaultKeyId");
});
}
const doesProjectKmsSecretManagerKeyExist = await knex.schema.hasColumn(TableName.Project, "kmsSecretManagerKeyId");
if (doesProjectKmsSecretManagerKeyExist) {
await knex.schema.alterTable(TableName.Project, (tb) => {
tb.dropColumn("kmsSecretManagerKeyId");
});
}
await renameInternalKmsKeyVersionBackToKmsKeyVersion(knex);
await bringBackKmsKeyFields(knex);
await backfillKmsKeyFromInternalKmsTable(knex);
const doesOldKmsKeyTableExist = await knex.schema.hasTable(TableName.KmsKey);
if (doesOldKmsKeyTableExist) {
await knex.schema.alterTable(TableName.KmsKey, (tb) => {
tb.binary("encryptedKey").notNullable().alter();
tb.string("encryptionAlgorithm").notNullable().alter();
});
}
const doesInternalKmsTableExist = await knex.schema.hasTable(TableName.InternalKms);
if (doesInternalKmsTableExist) await knex.schema.dropTable(TableName.InternalKms);
const doesExternalKmsServiceExist = await knex.schema.hasTable(TableName.ExternalKms);
if (doesExternalKmsServiceExist) await knex.schema.dropTable(TableName.ExternalKms);
}

@ -0,0 +1,25 @@
import { Knex } from "knex";
import { TableName } from "../schemas";
export async function up(knex: Knex): Promise<void> {
if (await knex.schema.hasTable(TableName.OrgMembership)) {
const doesUserIdExist = await knex.schema.hasColumn(TableName.OrgMembership, "userId");
const doesOrgIdExist = await knex.schema.hasColumn(TableName.OrgMembership, "orgId");
await knex.schema.alterTable(TableName.OrgMembership, (t) => {
t.boolean("isActive").notNullable().defaultTo(true);
if (doesUserIdExist && doesOrgIdExist) t.index(["userId", "orgId"]);
});
}
}
export async function down(knex: Knex): Promise<void> {
if (await knex.schema.hasTable(TableName.OrgMembership)) {
const doesUserIdExist = await knex.schema.hasColumn(TableName.OrgMembership, "userId");
const doesOrgIdExist = await knex.schema.hasColumn(TableName.OrgMembership, "orgId");
await knex.schema.alterTable(TableName.OrgMembership, (t) => {
t.dropColumn("isActive");
if (doesUserIdExist && doesOrgIdExist) t.dropIndex(["userId", "orgId"]);
});
}
}

@ -0,0 +1,23 @@
// 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 { zodBuffer } from "@app/lib/zod";
import { TImmutableDBKeys } from "./models";
export const ExternalKmsSchema = z.object({
id: z.string().uuid(),
provider: z.string(),
encryptedProviderInputs: zodBuffer,
status: z.string().nullable().optional(),
statusDetails: z.string().nullable().optional(),
kmsKeyId: z.string().uuid()
});
export type TExternalKms = z.infer<typeof ExternalKmsSchema>;
export type TExternalKmsInsert = Omit<z.input<typeof ExternalKmsSchema>, TImmutableDBKeys>;
export type TExternalKmsUpdate = Partial<Omit<z.input<typeof ExternalKmsSchema>, TImmutableDBKeys>>;

@ -17,6 +17,7 @@ export * from "./certificate-secrets";
export * from "./certificates";
export * from "./dynamic-secret-leases";
export * from "./dynamic-secrets";
export * from "./external-kms";
export * from "./git-app-install-sessions";
export * from "./git-app-org";
export * from "./group-project-membership-roles";
@ -39,6 +40,7 @@ export * from "./identity-universal-auths";
export * from "./incident-contacts";
export * from "./integration-auths";
export * from "./integrations";
export * from "./internal-kms";
export * from "./kms-key-versions";
export * from "./kms-keys";
export * from "./kms-root-config";

@ -0,0 +1,21 @@
// 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 { zodBuffer } from "@app/lib/zod";
import { TImmutableDBKeys } from "./models";
export const InternalKmsKeyVersionSchema = z.object({
id: z.string().uuid(),
encryptedKey: zodBuffer,
version: z.number(),
internalKmsId: z.string().uuid()
});
export type TInternalKmsKeyVersion = z.infer<typeof InternalKmsKeyVersionSchema>;
export type TInternalKmsKeyVersionInsert = Omit<z.input<typeof InternalKmsKeyVersionSchema>, TImmutableDBKeys>;
export type TInternalKmsKeyVersionUpdate = Partial<Omit<z.input<typeof InternalKmsKeyVersionSchema>, TImmutableDBKeys>>;

@ -0,0 +1,22 @@
// 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 { zodBuffer } from "@app/lib/zod";
import { TImmutableDBKeys } from "./models";
export const InternalKmsSchema = z.object({
id: z.string().uuid(),
encryptedKey: zodBuffer,
encryptionAlgorithm: z.string(),
version: z.number().default(1),
kmsKeyId: z.string().uuid()
});
export type TInternalKms = z.infer<typeof InternalKmsSchema>;
export type TInternalKmsInsert = Omit<z.input<typeof InternalKmsSchema>, TImmutableDBKeys>;
export type TInternalKmsUpdate = Partial<Omit<z.input<typeof InternalKmsSchema>, TImmutableDBKeys>>;

@ -5,20 +5,17 @@
import { z } from "zod";
import { zodBuffer } from "@app/lib/zod";
import { TImmutableDBKeys } from "./models";
export const KmsKeysSchema = z.object({
id: z.string().uuid(),
encryptedKey: zodBuffer,
encryptionAlgorithm: z.string(),
version: z.number().default(1),
description: z.string().nullable().optional(),
isDisabled: z.boolean().default(false).nullable().optional(),
isReserved: z.boolean().default(true).nullable().optional(),
projectId: z.string().nullable().optional(),
orgId: z.string().uuid().nullable().optional()
orgId: z.string().uuid(),
createdAt: z.date(),
updatedAt: z.date(),
slug: z.string()
});
export type TKmsKeys = z.infer<typeof KmsKeysSchema>;

@ -96,6 +96,10 @@ export enum TableName {
// KMS Service
KmsServerRootConfig = "kms_root_config",
KmsKey = "kms_keys",
ExternalKms = "external_kms",
InternalKms = "internal_kms",
InternalKmsKeyVersion = "internal_kms_key_version",
// @depreciated
KmsKeyVersion = "kms_key_versions"
}

@ -17,7 +17,8 @@ export const OrgMembershipsSchema = z.object({
userId: z.string().uuid().nullable().optional(),
orgId: z.string().uuid(),
roleId: z.string().uuid().nullable().optional(),
projectFavorites: z.string().array().nullable().optional()
projectFavorites: z.string().array().nullable().optional(),
isActive: z.boolean()
});
export type TOrgMemberships = z.infer<typeof OrgMembershipsSchema>;

@ -15,7 +15,8 @@ export const OrganizationsSchema = z.object({
createdAt: z.date(),
updatedAt: z.date(),
authEnforced: z.boolean().default(false).nullable().optional(),
scimEnabled: z.boolean().default(false).nullable().optional()
scimEnabled: z.boolean().default(false).nullable().optional(),
kmsDefaultKeyId: z.string().uuid().nullable().optional()
});
export type TOrganizations = z.infer<typeof OrganizationsSchema>;

@ -19,7 +19,8 @@ export const ProjectsSchema = z.object({
upgradeStatus: z.string().nullable().optional(),
pitVersionLimit: z.number().default(10),
kmsCertificateKeyId: z.string().uuid().nullable().optional(),
auditLogsRetentionDays: z.number().nullable().optional()
auditLogsRetentionDays: z.number().nullable().optional(),
kmsSecretManagerKeyId: z.string().uuid().nullable().optional()
});
export type TProjects = z.infer<typeof ProjectsSchema>;

@ -29,7 +29,8 @@ export async function seed(knex: Knex): Promise<void> {
role: OrgMembershipRole.Admin,
orgId: org.id,
status: OrgMembershipStatus.Accepted,
userId: user.id
userId: user.id,
isActive: true
}
]);
}

@ -0,0 +1,190 @@
import { z } from "zod";
import { ExternalKmsSchema, KmsKeysSchema } from "@app/db/schemas";
import {
ExternalKmsAwsSchema,
ExternalKmsInputSchema,
ExternalKmsInputUpdateSchema
} from "@app/ee/services/external-kms/providers/model";
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";
const sanitizedExternalSchema = KmsKeysSchema.extend({
external: ExternalKmsSchema.pick({
id: true,
status: true,
statusDetails: true,
provider: true
})
});
const sanitizedExternalSchemaForGetById = KmsKeysSchema.extend({
external: ExternalKmsSchema.pick({
id: true,
status: true,
statusDetails: true,
provider: true
}).extend({
providerInput: ExternalKmsAwsSchema
})
});
export const registerExternalKmsRouter = async (server: FastifyZodProvider) => {
server.route({
method: "POST",
url: "/",
config: {
rateLimit: writeLimit
},
schema: {
body: z.object({
slug: z.string().min(1).trim().toLowerCase().optional(),
description: z.string().min(1).trim().optional(),
provider: ExternalKmsInputSchema
}),
response: {
200: z.object({
externalKms: sanitizedExternalSchema
})
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const externalKms = await server.services.externalKms.create({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
slug: req.body.slug,
provider: req.body.provider,
description: req.body.description
});
return { externalKms };
}
});
server.route({
method: "PATCH",
url: "/:id",
config: {
rateLimit: writeLimit
},
schema: {
params: z.object({
id: z.string().trim().min(1)
}),
body: z.object({
slug: z.string().min(1).trim().toLowerCase().optional(),
description: z.string().min(1).trim().optional(),
provider: ExternalKmsInputUpdateSchema
}),
response: {
200: z.object({
externalKms: sanitizedExternalSchema
})
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const externalKms = await server.services.externalKms.updateById({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
slug: req.body.slug,
provider: req.body.provider,
description: req.body.description,
id: req.params.id
});
return { externalKms };
}
});
server.route({
method: "DELETE",
url: "/:id",
config: {
rateLimit: writeLimit
},
schema: {
params: z.object({
id: z.string().trim().min(1)
}),
response: {
200: z.object({
externalKms: sanitizedExternalSchema
})
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const externalKms = await server.services.externalKms.deleteById({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
id: req.params.id
});
return { externalKms };
}
});
server.route({
method: "GET",
url: "/:id",
config: {
rateLimit: readLimit
},
schema: {
params: z.object({
id: z.string().trim().min(1)
}),
response: {
200: z.object({
externalKms: sanitizedExternalSchemaForGetById
})
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const externalKms = await server.services.externalKms.findById({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
id: req.params.id
});
return { externalKms };
}
});
server.route({
method: "GET",
url: "/slug/:slug",
config: {
rateLimit: readLimit
},
schema: {
params: z.object({
slug: z.string().trim().min(1)
}),
response: {
200: z.object({
externalKms: sanitizedExternalSchemaForGetById
})
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const externalKms = await server.services.externalKms.findBySlug({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
slug: req.params.slug
});
return { externalKms };
}
});
};

@ -186,7 +186,13 @@ export const registerScimRouter = async (server: FastifyZodProvider) => {
})
),
displayName: z.string().trim(),
active: z.boolean()
active: z.boolean(),
groups: z.array(
z.object({
value: z.string().trim(),
display: z.string().trim()
})
)
})
}
},
@ -344,7 +350,12 @@ export const registerScimRouter = async (server: FastifyZodProvider) => {
schemas: z.array(z.string()),
id: z.string().trim(),
displayName: z.string().trim(),
members: z.array(z.any()).length(0),
members: z.array(
z.object({
value: z.string(),
display: z.string()
})
),
meta: z.object({
resourceType: z.string().trim()
})
@ -417,7 +428,7 @@ export const registerScimRouter = async (server: FastifyZodProvider) => {
displayName: z.string().trim(),
members: z.array(
z.object({
value: z.string(), // infisical orgMembershipId
value: z.string(),
display: z.string()
})
)
@ -475,10 +486,13 @@ export const registerScimRouter = async (server: FastifyZodProvider) => {
}),
z.object({
op: z.literal("add"),
value: z.object({
value: z.string().trim(),
display: z.string().trim().optional()
})
path: z.string().trim(),
value: z.array(
z.object({
value: z.string().trim(),
display: z.string().trim().optional()
})
)
})
])
)
@ -569,7 +583,13 @@ export const registerScimRouter = async (server: FastifyZodProvider) => {
})
),
displayName: z.string().trim(),
active: z.boolean()
active: z.boolean(),
groups: z.array(
z.object({
value: z.string().trim(),
display: z.string().trim()
})
)
})
}
},

@ -17,7 +17,7 @@ type TCertificateAuthorityCrlServiceFactoryDep = {
certificateAuthorityDAL: Pick<TCertificateAuthorityDALFactory, "findById">;
certificateAuthorityCrlDAL: Pick<TCertificateAuthorityCrlDALFactory, "findOne">;
projectDAL: Pick<TProjectDALFactory, "findOne" | "updateById" | "transaction">;
kmsService: Pick<TKmsServiceFactory, "decrypt" | "generateKmsKey">;
kmsService: Pick<TKmsServiceFactory, "decryptWithKmsKey" | "generateKmsKey">;
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission">;
licenseService: Pick<TLicenseServiceFactory, "getPlan">;
};
@ -68,11 +68,11 @@ export const certificateAuthorityCrlServiceFactory = ({
kmsService
});
const decryptedCrl = await kmsService.decrypt({
kmsId: keyId,
cipherTextBlob: caCrl.encryptedCrl
const kmsDecryptor = await kmsService.decryptWithKmsKey({
kmsId: keyId
});
const decryptedCrl = kmsDecryptor({ cipherTextBlob: caCrl.encryptedCrl });
const crl = new x509.X509Crl(decryptedCrl);
const base64crl = crl.toString("base64");

@ -0,0 +1,47 @@
import { Knex } from "knex";
import { TDbClient } from "@app/db";
import { TableName, TKmsKeys } from "@app/db/schemas";
import { DatabaseError } from "@app/lib/errors";
import { ormify, selectAllTableCols } from "@app/lib/knex";
export type TExternalKmsDALFactory = ReturnType<typeof externalKmsDALFactory>;
export const externalKmsDALFactory = (db: TDbClient) => {
const externalKmsOrm = ormify(db, TableName.ExternalKms);
const find = async (filter: Partial<TKmsKeys>, tx?: Knex) => {
try {
const result = await (tx || db.replicaNode())(TableName.ExternalKms)
.join(TableName.KmsKey, `${TableName.KmsKey}.id`, `${TableName.ExternalKms}.kmsKeyId`)
.where(filter)
.select(selectAllTableCols(TableName.KmsKey))
.select(
db.ref("id").withSchema(TableName.ExternalKms).as("externalKmsId"),
db.ref("provider").withSchema(TableName.ExternalKms).as("externalKmsProvider"),
db.ref("encryptedProviderInputs").withSchema(TableName.ExternalKms).as("externalKmsEncryptedProviderInput"),
db.ref("status").withSchema(TableName.ExternalKms).as("externalKmsStatus"),
db.ref("statusDetails").withSchema(TableName.ExternalKms).as("externalKmsStatusDetails")
);
return result.map((el) => ({
id: el.id,
description: el.description,
isDisabled: el.isDisabled,
isReserved: el.isReserved,
orgId: el.orgId,
slug: el.slug,
externalKms: {
id: el.externalKmsId,
provider: el.externalKmsProvider,
status: el.externalKmsStatus,
statusDetails: el.externalKmsStatusDetails
}
}));
} catch (error) {
throw new DatabaseError({ error, name: "Find" });
}
};
return { ...externalKmsOrm, find };
};

@ -0,0 +1,309 @@
import { ForbiddenError } from "@casl/ability";
import slugify from "@sindresorhus/slugify";
import { BadRequestError } from "@app/lib/errors";
import { alphaNumericNanoId } from "@app/lib/nanoid";
import { TKmsKeyDALFactory } from "@app/services/kms/kms-key-dal";
import { TKmsServiceFactory } from "@app/services/kms/kms-service";
import { OrgPermissionActions, OrgPermissionSubjects } from "../permission/org-permission";
import { TPermissionServiceFactory } from "../permission/permission-service";
import { TExternalKmsDALFactory } from "./external-kms-dal";
import {
TCreateExternalKmsDTO,
TDeleteExternalKmsDTO,
TGetExternalKmsByIdDTO,
TGetExternalKmsBySlugDTO,
TListExternalKmsDTO,
TUpdateExternalKmsDTO
} from "./external-kms-types";
import { AwsKmsProviderFactory } from "./providers/aws-kms";
import { ExternalKmsAwsSchema, KmsProviders } from "./providers/model";
type TExternalKmsServiceFactoryDep = {
externalKmsDAL: TExternalKmsDALFactory;
kmsService: Pick<TKmsServiceFactory, "getOrgKmsKeyId" | "encryptWithKmsKey" | "decryptWithKmsKey">;
kmsDAL: Pick<TKmsKeyDALFactory, "create" | "updateById" | "findById" | "deleteById" | "findOne">;
permissionService: Pick<TPermissionServiceFactory, "getOrgPermission">;
};
export type TExternalKmsServiceFactory = ReturnType<typeof externalKmsServiceFactory>;
export const externalKmsServiceFactory = ({
externalKmsDAL,
permissionService,
kmsService,
kmsDAL
}: TExternalKmsServiceFactoryDep) => {
const create = async ({
provider,
description,
actor,
slug,
actorId,
actorOrgId,
actorAuthMethod
}: TCreateExternalKmsDTO) => {
const { permission } = await permissionService.getOrgPermission(
actor,
actorId,
actorOrgId,
actorAuthMethod,
actorOrgId
);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Settings);
const kmsSlug = slug ? slugify(slug) : slugify(alphaNumericNanoId(8).toLowerCase());
let sanitizedProviderInput = "";
switch (provider.type) {
case KmsProviders.Aws:
{
const externalKms = await AwsKmsProviderFactory({ inputs: provider.inputs });
await externalKms.validateConnection();
// if missing kms key this generate a new kms key id and returns new provider input
const newProviderInput = await externalKms.generateInputKmsKey();
sanitizedProviderInput = JSON.stringify(newProviderInput);
}
break;
default:
throw new BadRequestError({ message: "external kms provided is invalid" });
}
const orgKmsKeyId = await kmsService.getOrgKmsKeyId(actorOrgId);
const kmsEncryptor = await kmsService.encryptWithKmsKey({
kmsId: orgKmsKeyId
});
const { cipherTextBlob: encryptedProviderInputs } = kmsEncryptor({
plainText: Buffer.from(sanitizedProviderInput, "utf8")
});
const externalKms = await externalKmsDAL.transaction(async (tx) => {
const kms = await kmsDAL.create(
{
isReserved: false,
description,
slug: kmsSlug,
orgId: actorOrgId
},
tx
);
const externalKmsCfg = await externalKmsDAL.create(
{
provider: provider.type,
encryptedProviderInputs,
kmsKeyId: kms.id
},
tx
);
return { ...kms, external: externalKmsCfg };
});
return externalKms;
};
const updateById = async ({
provider,
description,
actor,
id: kmsId,
slug,
actorId,
actorOrgId,
actorAuthMethod
}: TUpdateExternalKmsDTO) => {
const kmsDoc = await kmsDAL.findById(kmsId);
const { permission } = await permissionService.getOrgPermission(
actor,
actorId,
kmsDoc.orgId,
actorAuthMethod,
actorOrgId
);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Settings);
const kmsSlug = slug ? slugify(slug) : undefined;
const externalKmsDoc = await externalKmsDAL.findOne({ kmsKeyId: kmsDoc.id });
if (!externalKmsDoc) throw new BadRequestError({ message: "External kms not found" });
const orgDefaultKmsId = await kmsService.getOrgKmsKeyId(kmsDoc.orgId);
let sanitizedProviderInput = "";
if (provider) {
const kmsDecryptor = await kmsService.decryptWithKmsKey({
kmsId: orgDefaultKmsId
});
const decryptedProviderInputBlob = kmsDecryptor({
cipherTextBlob: externalKmsDoc.encryptedProviderInputs
});
switch (provider.type) {
case KmsProviders.Aws:
{
const decryptedProviderInput = await ExternalKmsAwsSchema.parseAsync(
JSON.parse(decryptedProviderInputBlob.toString("utf8"))
);
const updatedProviderInput = { ...decryptedProviderInput, ...provider.inputs };
const externalKms = await AwsKmsProviderFactory({ inputs: updatedProviderInput });
await externalKms.validateConnection();
sanitizedProviderInput = JSON.stringify(updatedProviderInput);
}
break;
default:
throw new BadRequestError({ message: "external kms provided is invalid" });
}
}
let encryptedProviderInputs: Buffer | undefined;
if (sanitizedProviderInput) {
const kmsEncryptor = await kmsService.encryptWithKmsKey({
kmsId: orgDefaultKmsId
});
const { cipherTextBlob } = kmsEncryptor({
plainText: Buffer.from(sanitizedProviderInput, "utf8")
});
encryptedProviderInputs = cipherTextBlob;
}
const externalKms = await externalKmsDAL.transaction(async (tx) => {
const kms = await kmsDAL.updateById(
kmsDoc.id,
{
description,
slug: kmsSlug
},
tx
);
if (encryptedProviderInputs) {
const externalKmsCfg = await externalKmsDAL.updateById(
externalKmsDoc.id,
{
encryptedProviderInputs
},
tx
);
return { ...kms, external: externalKmsCfg };
}
return { ...kms, external: externalKmsDoc };
});
return externalKms;
};
const deleteById = async ({ actor, id: kmsId, actorId, actorOrgId, actorAuthMethod }: TDeleteExternalKmsDTO) => {
const kmsDoc = await kmsDAL.findById(kmsId);
const { permission } = await permissionService.getOrgPermission(
actor,
actorId,
kmsDoc.orgId,
actorAuthMethod,
actorOrgId
);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Settings);
const externalKmsDoc = await externalKmsDAL.findOne({ kmsKeyId: kmsDoc.id });
if (!externalKmsDoc) throw new BadRequestError({ message: "External kms not found" });
const externalKms = await externalKmsDAL.transaction(async (tx) => {
const kms = await kmsDAL.deleteById(kmsDoc.id, tx);
return { ...kms, external: externalKmsDoc };
});
return externalKms;
};
const list = async ({ actor, actorId, actorOrgId, actorAuthMethod }: TListExternalKmsDTO) => {
const { permission } = await permissionService.getOrgPermission(
actor,
actorId,
actorOrgId,
actorAuthMethod,
actorOrgId
);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Settings);
const externalKmsDocs = await externalKmsDAL.find({ orgId: actorOrgId });
return externalKmsDocs;
};
const findById = async ({ actor, actorId, actorOrgId, actorAuthMethod, id: kmsId }: TGetExternalKmsByIdDTO) => {
const kmsDoc = await kmsDAL.findById(kmsId);
const { permission } = await permissionService.getOrgPermission(
actor,
actorId,
kmsDoc.orgId,
actorAuthMethod,
actorOrgId
);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Settings);
const externalKmsDoc = await externalKmsDAL.findOne({ kmsKeyId: kmsDoc.id });
if (!externalKmsDoc) throw new BadRequestError({ message: "External kms not found" });
const orgDefaultKmsId = await kmsService.getOrgKmsKeyId(kmsDoc.orgId);
const kmsDecryptor = await kmsService.decryptWithKmsKey({
kmsId: orgDefaultKmsId
});
const decryptedProviderInputBlob = kmsDecryptor({
cipherTextBlob: externalKmsDoc.encryptedProviderInputs
});
switch (externalKmsDoc.provider) {
case KmsProviders.Aws: {
const decryptedProviderInput = await ExternalKmsAwsSchema.parseAsync(
JSON.parse(decryptedProviderInputBlob.toString("utf8"))
);
return { ...kmsDoc, external: { ...externalKmsDoc, providerInput: decryptedProviderInput } };
}
default:
throw new BadRequestError({ message: "external kms provided is invalid" });
}
};
const findBySlug = async ({
actor,
actorId,
actorOrgId,
actorAuthMethod,
slug: kmsSlug
}: TGetExternalKmsBySlugDTO) => {
const kmsDoc = await kmsDAL.findOne({ slug: kmsSlug, orgId: actorOrgId });
const { permission } = await permissionService.getOrgPermission(
actor,
actorId,
kmsDoc.orgId,
actorAuthMethod,
actorOrgId
);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Settings);
const externalKmsDoc = await externalKmsDAL.findOne({ kmsKeyId: kmsDoc.id });
if (!externalKmsDoc) throw new BadRequestError({ message: "External kms not found" });
const orgDefaultKmsId = await kmsService.getOrgKmsKeyId(kmsDoc.orgId);
const kmsDecryptor = await kmsService.decryptWithKmsKey({
kmsId: orgDefaultKmsId
});
const decryptedProviderInputBlob = kmsDecryptor({
cipherTextBlob: externalKmsDoc.encryptedProviderInputs
});
switch (externalKmsDoc.provider) {
case KmsProviders.Aws: {
const decryptedProviderInput = await ExternalKmsAwsSchema.parseAsync(
JSON.parse(decryptedProviderInputBlob.toString("utf8"))
);
return { ...kmsDoc, external: { ...externalKmsDoc, providerInput: decryptedProviderInput } };
}
default:
throw new BadRequestError({ message: "external kms provided is invalid" });
}
};
return {
create,
updateById,
deleteById,
list,
findById,
findBySlug
};
};

@ -0,0 +1,30 @@
import { TOrgPermission } from "@app/lib/types";
import { TExternalKmsInputSchema, TExternalKmsInputUpdateSchema } from "./providers/model";
export type TCreateExternalKmsDTO = {
slug?: string;
description?: string;
provider: TExternalKmsInputSchema;
} & Omit<TOrgPermission, "orgId">;
export type TUpdateExternalKmsDTO = {
id: string;
slug?: string;
description?: string;
provider?: TExternalKmsInputUpdateSchema;
} & Omit<TOrgPermission, "orgId">;
export type TDeleteExternalKmsDTO = {
id: string;
} & Omit<TOrgPermission, "orgId">;
export type TListExternalKmsDTO = Omit<TOrgPermission, "orgId">;
export type TGetExternalKmsByIdDTO = {
id: string;
} & Omit<TOrgPermission, "orgId">;
export type TGetExternalKmsBySlugDTO = {
slug: string;
} & Omit<TOrgPermission, "orgId">;

@ -0,0 +1,102 @@
import { CreateKeyCommand, DecryptCommand, DescribeKeyCommand, EncryptCommand, KMSClient } from "@aws-sdk/client-kms";
import { AssumeRoleCommand, STSClient } from "@aws-sdk/client-sts";
import { randomUUID } from "crypto";
import { ExternalKmsAwsSchema, KmsAwsCredentialType, TExternalKmsAwsSchema, TExternalKmsProviderFns } from "./model";
const getAwsKmsClient = async (providerInputs: TExternalKmsAwsSchema) => {
if (providerInputs.credential.type === KmsAwsCredentialType.AssumeRole) {
const awsCredential = providerInputs.credential.data;
const stsClient = new STSClient({
region: providerInputs.awsRegion
});
const command = new AssumeRoleCommand({
RoleArn: awsCredential.assumeRoleArn,
RoleSessionName: `infisical-kms-${randomUUID()}`,
DurationSeconds: 900, // 15mins
ExternalId: awsCredential.externalId
});
const response = await stsClient.send(command);
if (!response.Credentials?.AccessKeyId || !response.Credentials?.SecretAccessKey)
throw new Error("Failed to assume role");
const kmsClient = new KMSClient({
region: providerInputs.awsRegion,
credentials: {
accessKeyId: response.Credentials.AccessKeyId,
secretAccessKey: response.Credentials.SecretAccessKey,
sessionToken: response.Credentials.SessionToken,
expiration: response.Credentials.Expiration
}
});
return kmsClient;
}
const awsCredential = providerInputs.credential.data;
const kmsClient = new KMSClient({
region: providerInputs.awsRegion,
credentials: {
accessKeyId: awsCredential.accessKey,
secretAccessKey: awsCredential.secretKey
}
});
return kmsClient;
};
type AwsKmsProviderArgs = {
inputs: unknown;
};
type TAwsKmsProviderFactoryReturn = TExternalKmsProviderFns & {
generateInputKmsKey: () => Promise<TExternalKmsAwsSchema>;
};
export const AwsKmsProviderFactory = async ({ inputs }: AwsKmsProviderArgs): Promise<TAwsKmsProviderFactoryReturn> => {
const providerInputs = await ExternalKmsAwsSchema.parseAsync(inputs);
const awsClient = await getAwsKmsClient(providerInputs);
const generateInputKmsKey = async () => {
if (providerInputs.kmsKeyId) return providerInputs;
const command = new CreateKeyCommand({ Tags: [{ TagKey: "author", TagValue: "infisical" }] });
const kmsKey = await awsClient.send(command);
if (!kmsKey.KeyMetadata?.KeyId) throw new Error("Failed to generate kms key");
return { ...providerInputs, kmsKeyId: kmsKey.KeyMetadata?.KeyId };
};
const validateConnection = async () => {
const command = new DescribeKeyCommand({
KeyId: providerInputs.kmsKeyId
});
const isConnected = await awsClient.send(command).then(() => true);
return isConnected;
};
const encrypt = async (data: Buffer) => {
const command = new EncryptCommand({
KeyId: providerInputs.kmsKeyId,
Plaintext: data
});
const encryptionCommand = await awsClient.send(command);
if (!encryptionCommand.CiphertextBlob) throw new Error("encryption failed");
return { encryptedBlob: Buffer.from(encryptionCommand.CiphertextBlob) };
};
const decrypt = async (encryptedBlob: Buffer) => {
const command = new DecryptCommand({
KeyId: providerInputs.kmsKeyId,
CiphertextBlob: encryptedBlob
});
const decryptionCommand = await awsClient.send(command);
if (!decryptionCommand.Plaintext) throw new Error("decryption failed");
return { data: Buffer.from(decryptionCommand.Plaintext) };
};
return {
generateInputKmsKey,
validateConnection,
encrypt,
decrypt
};
};

@ -0,0 +1,61 @@
import { z } from "zod";
export enum KmsProviders {
Aws = "aws"
}
export enum KmsAwsCredentialType {
AssumeRole = "assume-role",
AccessKey = "access-key"
}
export const ExternalKmsAwsSchema = z.object({
credential: z
.discriminatedUnion("type", [
z.object({
type: z.literal(KmsAwsCredentialType.AccessKey),
data: z.object({
accessKey: z.string().trim().min(1).describe("AWS user account access key"),
secretKey: z.string().trim().min(1).describe("AWS user account secret key")
})
}),
z.object({
type: z.literal(KmsAwsCredentialType.AssumeRole),
data: z.object({
assumeRoleArn: z.string().trim().min(1).describe("AWS user role to be assumed by infisical"),
externalId: z
.string()
.trim()
.min(1)
.optional()
.describe("AWS assume role external id for furthur security in authentication")
})
})
])
.describe("AWS credential information to connect"),
awsRegion: z.string().min(1).trim().describe("AWS region to connect"),
kmsKeyId: z
.string()
.trim()
.optional()
.describe("A pre existing AWS KMS key id to be used for encryption. If not provided a kms key will be generated.")
});
export type TExternalKmsAwsSchema = z.infer<typeof ExternalKmsAwsSchema>;
// The root schema of the JSON
export const ExternalKmsInputSchema = z.discriminatedUnion("type", [
z.object({ type: z.literal(KmsProviders.Aws), inputs: ExternalKmsAwsSchema })
]);
export type TExternalKmsInputSchema = z.infer<typeof ExternalKmsInputSchema>;
export const ExternalKmsInputUpdateSchema = z.discriminatedUnion("type", [
z.object({ type: z.literal(KmsProviders.Aws), inputs: ExternalKmsAwsSchema.partial() })
]);
export type TExternalKmsInputUpdateSchema = z.infer<typeof ExternalKmsInputUpdateSchema>;
// generic function shared by all provider
export type TExternalKmsProviderFns = {
validateConnection: () => Promise<boolean>;
encrypt: (data: Buffer) => Promise<{ encryptedBlob: Buffer }>;
decrypt: (encryptedBlob: Buffer) => Promise<{ data: Buffer }>;
};

@ -162,11 +162,60 @@ export const userGroupMembershipDALFactory = (db: TDbClient) => {
}
};
const findGroupMembershipsByUserIdInOrg = async (userId: string, orgId: string) => {
try {
const docs = await db
.replicaNode()(TableName.UserGroupMembership)
.join(TableName.Groups, `${TableName.UserGroupMembership}.groupId`, `${TableName.Groups}.id`)
.join(TableName.OrgMembership, `${TableName.UserGroupMembership}.userId`, `${TableName.OrgMembership}.userId`)
.join(TableName.Users, `${TableName.UserGroupMembership}.userId`, `${TableName.Users}.id`)
.where(`${TableName.UserGroupMembership}.userId`, userId)
.where(`${TableName.Groups}.orgId`, orgId)
.select(
db.ref("id").withSchema(TableName.UserGroupMembership),
db.ref("groupId").withSchema(TableName.UserGroupMembership),
db.ref("name").withSchema(TableName.Groups).as("groupName"),
db.ref("id").withSchema(TableName.OrgMembership).as("orgMembershipId"),
db.ref("firstName").withSchema(TableName.Users).as("firstName"),
db.ref("lastName").withSchema(TableName.Users).as("lastName")
);
return docs;
} catch (error) {
throw new DatabaseError({ error, name: "Find group memberships by user id in org" });
}
};
const findGroupMembershipsByGroupIdInOrg = async (groupId: string, orgId: string) => {
try {
const docs = await db
.replicaNode()(TableName.UserGroupMembership)
.join(TableName.Groups, `${TableName.UserGroupMembership}.groupId`, `${TableName.Groups}.id`)
.join(TableName.OrgMembership, `${TableName.UserGroupMembership}.userId`, `${TableName.OrgMembership}.userId`)
.join(TableName.Users, `${TableName.UserGroupMembership}.userId`, `${TableName.Users}.id`)
.where(`${TableName.Groups}.id`, groupId)
.where(`${TableName.Groups}.orgId`, orgId)
.select(
db.ref("id").withSchema(TableName.UserGroupMembership),
db.ref("groupId").withSchema(TableName.UserGroupMembership),
db.ref("name").withSchema(TableName.Groups).as("groupName"),
db.ref("id").withSchema(TableName.OrgMembership).as("orgMembershipId"),
db.ref("firstName").withSchema(TableName.Users).as("firstName"),
db.ref("lastName").withSchema(TableName.Users).as("lastName")
);
return docs;
} catch (error) {
throw new DatabaseError({ error, name: "Find group memberships by group id in org" });
}
};
return {
...userGroupMembershipOrm,
filterProjectsByUserMembership,
findUserGroupMembershipsInProject,
findGroupMembersNotInProject,
deletePendingUserGroupMembershipsByUserIds
deletePendingUserGroupMembershipsByUserIds,
findGroupMembershipsByUserIdInOrg,
findGroupMembershipsByGroupIdInOrg
};
};

@ -449,7 +449,8 @@ export const ldapConfigServiceFactory = ({
userId: userAlias.userId,
orgId,
role: OrgMembershipRole.Member,
status: OrgMembershipStatus.Accepted
status: OrgMembershipStatus.Accepted,
isActive: true
},
tx
);
@ -534,7 +535,8 @@ export const ldapConfigServiceFactory = ({
inviteEmail: email,
orgId,
role: OrgMembershipRole.Member,
status: newUser.isAccepted ? OrgMembershipStatus.Accepted : OrgMembershipStatus.Invited // if user is fully completed, then set status to accepted, otherwise set it to invited so we can update it later
status: newUser.isAccepted ? OrgMembershipStatus.Accepted : OrgMembershipStatus.Invited, // if user is fully completed, then set status to accepted, otherwise set it to invited so we can update it later
isActive: true
},
tx
);

@ -218,6 +218,8 @@ export const licenseServiceFactory = ({
} else if (instanceType === InstanceType.EnterpriseOnPrem) {
const usedSeats = await licenseDAL.countOfOrgMembers(null, tx);
const usedIdentitySeats = await licenseDAL.countOrgUsersAndIdentities(null, tx);
onPremFeatures.membersUsed = usedSeats;
onPremFeatures.identitiesUsed = usedIdentitySeats;
await licenseServerOnPremApi.request.patch(`/api/license/v1/license`, {
usedSeats,
usedIdentitySeats

@ -30,9 +30,9 @@ export type TFeatureSet = {
workspacesUsed: 0;
dynamicSecret: false;
memberLimit: null;
membersUsed: 0;
membersUsed: number;
identityLimit: null;
identitiesUsed: 0;
identitiesUsed: number;
environmentLimit: null;
environmentsUsed: 0;
secretVersioning: true;

@ -193,7 +193,8 @@ export const oidcConfigServiceFactory = ({
inviteEmail: email,
orgId,
role: OrgMembershipRole.Member,
status: foundUser.isAccepted ? OrgMembershipStatus.Accepted : OrgMembershipStatus.Invited // if user is fully completed, then set status to accepted, otherwise set it to invited so we can update it later
status: foundUser.isAccepted ? OrgMembershipStatus.Accepted : OrgMembershipStatus.Invited, // if user is fully completed, then set status to accepted, otherwise set it to invited so we can update it later
isActive: true
},
tx
);
@ -266,7 +267,8 @@ export const oidcConfigServiceFactory = ({
inviteEmail: email,
orgId,
role: OrgMembershipRole.Member,
status: newUser.isAccepted ? OrgMembershipStatus.Accepted : OrgMembershipStatus.Invited // if user is fully completed, then set status to accepted, otherwise set it to invited so we can update it later
status: newUser.isAccepted ? OrgMembershipStatus.Accepted : OrgMembershipStatus.Invited, // if user is fully completed, then set status to accepted, otherwise set it to invited so we can update it later
isActive: true
},
tx
);

@ -109,6 +109,9 @@ export const permissionServiceFactory = ({
authMethod: ActorAuthMethod,
userOrgId?: string
) => {
// when token is scoped, ensure the passed org id is same as user org id
if (userOrgId && userOrgId !== orgId)
throw new BadRequestError({ message: "Invalid user token. Scoped to different organization." });
const membership = await permissionDAL.getOrgPermission(userId, orgId);
if (!membership) throw new UnauthorizedError({ name: "User not in org" });
if (membership.role === OrgMembershipRole.Custom && !membership.permissions) {

@ -370,7 +370,8 @@ export const samlConfigServiceFactory = ({
inviteEmail: email,
orgId,
role: OrgMembershipRole.Member,
status: foundUser.isAccepted ? OrgMembershipStatus.Accepted : OrgMembershipStatus.Invited // if user is fully completed, then set status to accepted, otherwise set it to invited so we can update it later
status: foundUser.isAccepted ? OrgMembershipStatus.Accepted : OrgMembershipStatus.Invited, // if user is fully completed, then set status to accepted, otherwise set it to invited so we can update it later
isActive: true
},
tx
);
@ -457,7 +458,8 @@ export const samlConfigServiceFactory = ({
inviteEmail: email,
orgId,
role: OrgMembershipRole.Member,
status: newUser.isAccepted ? OrgMembershipStatus.Accepted : OrgMembershipStatus.Invited // if user is fully completed, then set status to accepted, otherwise set it to invited so we can update it later
status: newUser.isAccepted ? OrgMembershipStatus.Accepted : OrgMembershipStatus.Invited, // if user is fully completed, then set status to accepted, otherwise set it to invited so we can update it later
isActive: true
},
tx
);

@ -32,12 +32,19 @@ export const parseScimFilter = (filterToParse: string | undefined) => {
return { [attributeName]: parsedValue.replace(/"/g, "") };
};
export function extractScimValueFromPath(path: string): string | null {
const regex = /members\[value eq "([^"]+)"\]/;
const match = path.match(regex);
return match ? match[1] : null;
}
export const buildScimUser = ({
orgMembershipId,
username,
email,
firstName,
lastName,
groups = [],
active
}: {
orgMembershipId: string;
@ -45,6 +52,10 @@ export const buildScimUser = ({
email?: string | null;
firstName: string;
lastName: string;
groups?: {
value: string;
display: string;
}[];
active: boolean;
}): TScimUser => {
const scimUser = {
@ -67,7 +78,7 @@ export const buildScimUser = ({
]
: [],
active,
groups: [],
groups,
meta: {
resourceType: "User",
location: null

@ -2,13 +2,14 @@ import { ForbiddenError } from "@casl/ability";
import slugify from "@sindresorhus/slugify";
import jwt from "jsonwebtoken";
import { OrgMembershipRole, OrgMembershipStatus, TableName, TGroups, TOrgMemberships, TUsers } from "@app/db/schemas";
import { OrgMembershipRole, OrgMembershipStatus, TableName, TOrgMemberships, TUsers } from "@app/db/schemas";
import { TGroupDALFactory } from "@app/ee/services/group/group-dal";
import { addUsersToGroupByUserIds, removeUsersFromGroupByUserIds } from "@app/ee/services/group/group-fns";
import { TUserGroupMembershipDALFactory } from "@app/ee/services/group/user-group-membership-dal";
import { TScimDALFactory } from "@app/ee/services/scim/scim-dal";
import { getConfig } from "@app/lib/config/env";
import { BadRequestError, ScimRequestError, UnauthorizedError } from "@app/lib/errors";
import { logger } from "@app/lib/logger";
import { alphaNumericNanoId } from "@app/lib/nanoid";
import { TOrgPermission } from "@app/lib/types";
import { AuthTokenType } from "@app/services/auth/auth-type";
@ -30,7 +31,14 @@ import { UserAliasType } from "@app/services/user-alias/user-alias-types";
import { TLicenseServiceFactory } from "../license/license-service";
import { OrgPermissionActions, OrgPermissionSubjects } from "../permission/org-permission";
import { TPermissionServiceFactory } from "../permission/permission-service";
import { buildScimGroup, buildScimGroupList, buildScimUser, buildScimUserList, parseScimFilter } from "./scim-fns";
import {
buildScimGroup,
buildScimGroupList,
buildScimUser,
buildScimUserList,
extractScimValueFromPath,
parseScimFilter
} from "./scim-fns";
import {
TCreateScimGroupDTO,
TCreateScimTokenDTO,
@ -44,6 +52,7 @@ import {
TListScimUsers,
TListScimUsersDTO,
TReplaceScimUserDTO,
TScimGroup,
TScimTokenJwtPayload,
TUpdateScimGroupNamePatchDTO,
TUpdateScimGroupNamePutDTO,
@ -61,17 +70,23 @@ type TScimServiceFactoryDep = {
TOrgDALFactory,
"createMembership" | "findById" | "findMembership" | "deleteMembershipById" | "transaction" | "updateMembershipById"
>;
orgMembershipDAL: Pick<TOrgMembershipDALFactory, "find" | "findOne" | "create" | "updateById">;
orgMembershipDAL: Pick<TOrgMembershipDALFactory, "find" | "findOne" | "create" | "updateById" | "findById">;
projectDAL: Pick<TProjectDALFactory, "find" | "findProjectGhostUser">;
projectMembershipDAL: Pick<TProjectMembershipDALFactory, "find" | "delete" | "findProjectMembershipsByUserId">;
groupDAL: Pick<
TGroupDALFactory,
"create" | "findOne" | "findAllGroupMembers" | "update" | "delete" | "findGroups" | "transaction"
"create" | "findOne" | "findAllGroupMembers" | "delete" | "findGroups" | "transaction" | "updateById" | "update"
>;
groupProjectDAL: Pick<TGroupProjectDALFactory, "find">;
userGroupMembershipDAL: Pick<
TUserGroupMembershipDALFactory,
"find" | "transaction" | "insertMany" | "filterProjectsByUserMembership" | "delete"
| "find"
| "transaction"
| "insertMany"
| "filterProjectsByUserMembership"
| "delete"
| "findGroupMembershipsByUserIdInOrg"
| "findGroupMembershipsByGroupIdInOrg"
>;
projectKeyDAL: Pick<TProjectKeyDALFactory, "find" | "findLatestProjectKey" | "insertMany" | "delete">;
projectBotDAL: Pick<TProjectBotDALFactory, "findOne">;
@ -197,14 +212,14 @@ export const scimServiceFactory = ({
findOpts
);
const scimUsers = users.map(({ id, externalId, username, firstName, lastName, email }) =>
const scimUsers = users.map(({ id, externalId, username, firstName, lastName, email, isActive }) =>
buildScimUser({
orgMembershipId: id ?? "",
username: externalId ?? username,
firstName: firstName ?? "",
lastName: lastName ?? "",
email,
active: true
active: isActive
})
);
@ -240,13 +255,22 @@ export const scimServiceFactory = ({
status: 403
});
const groupMembershipsInOrg = await userGroupMembershipDAL.findGroupMembershipsByUserIdInOrg(
membership.userId,
orgId
);
return buildScimUser({
orgMembershipId: membership.id,
username: membership.externalId ?? membership.username,
email: membership.email ?? "",
firstName: membership.firstName as string,
lastName: membership.lastName as string,
active: true
active: membership.isActive,
groups: groupMembershipsInOrg.map((group) => ({
value: group.groupId,
display: group.groupName
}))
});
};
@ -296,7 +320,8 @@ export const scimServiceFactory = ({
inviteEmail: email,
orgId,
role: OrgMembershipRole.Member,
status: user.isAccepted ? OrgMembershipStatus.Accepted : OrgMembershipStatus.Invited // if user is fully completed, then set status to accepted, otherwise set it to invited so we can update it later
status: user.isAccepted ? OrgMembershipStatus.Accepted : OrgMembershipStatus.Invited, // if user is fully completed, then set status to accepted, otherwise set it to invited so we can update it later
isActive: true
},
tx
);
@ -364,7 +389,8 @@ export const scimServiceFactory = ({
inviteEmail: email,
orgId,
role: OrgMembershipRole.Member,
status: user.isAccepted ? OrgMembershipStatus.Accepted : OrgMembershipStatus.Invited // if user is fully completed, then set status to accepted, otherwise set it to invited so we can update it later
status: user.isAccepted ? OrgMembershipStatus.Accepted : OrgMembershipStatus.Invited, // if user is fully completed, then set status to accepted, otherwise set it to invited so we can update it later
isActive: true
},
tx
);
@ -401,7 +427,7 @@ export const scimServiceFactory = ({
firstName: createdUser.firstName as string,
lastName: createdUser.lastName as string,
email: createdUser.email ?? "",
active: true
active: createdOrgMembership.isActive
});
};
@ -445,14 +471,8 @@ export const scimServiceFactory = ({
});
if (!active) {
await deleteOrgMembershipFn({
orgMembershipId: membership.id,
orgId: membership.orgId,
orgDAL,
projectMembershipDAL,
projectKeyDAL,
userAliasDAL,
licenseService
await orgMembershipDAL.updateById(membership.id, {
isActive: false
});
}
@ -491,17 +511,14 @@ export const scimServiceFactory = ({
status: 403
});
if (!active) {
await deleteOrgMembershipFn({
orgMembershipId: membership.id,
orgId: membership.orgId,
orgDAL,
projectMembershipDAL,
projectKeyDAL,
userAliasDAL,
licenseService
});
}
await orgMembershipDAL.updateById(membership.id, {
isActive: active
});
const groupMembershipsInOrg = await userGroupMembershipDAL.findGroupMembershipsByUserIdInOrg(
membership.userId,
orgId
);
return buildScimUser({
orgMembershipId: membership.id,
@ -509,7 +526,11 @@ export const scimServiceFactory = ({
email: membership.email,
firstName: membership.firstName as string,
lastName: membership.lastName as string,
active
active,
groups: groupMembershipsInOrg.map((group) => ({
value: group.groupId,
display: group.groupName
}))
});
};
@ -577,13 +598,20 @@ export const scimServiceFactory = ({
}
);
const scimGroups = groups.map((group) =>
buildScimGroup({
const scimGroups: TScimGroup[] = [];
for await (const group of groups) {
const members = await userGroupMembershipDAL.findGroupMembershipsByGroupIdInOrg(group.id, orgId);
const scimGroup = buildScimGroup({
groupId: group.id,
name: group.name,
members: [] // does this need to be populated?
})
);
members: members.map((member) => ({
value: member.orgMembershipId,
display: `${member.firstName ?? ""} ${member.lastName ?? ""}`
}))
});
scimGroups.push(scimGroup);
}
return buildScimGroupList({
scimGroups,
@ -817,7 +845,6 @@ export const scimServiceFactory = ({
});
};
// TODO: add support for add/remove op
const updateScimGroupNamePatch = async ({ groupId, orgId, operations }: TUpdateScimGroupNamePatchDTO) => {
const plan = await licenseService.getPlan(orgId);
if (!plan.groups)
@ -840,27 +867,64 @@ export const scimServiceFactory = ({
status: 403
});
let group: TGroups | undefined;
let group = await groupDAL.findOne({
id: groupId,
orgId
});
if (!group) {
throw new ScimRequestError({
detail: "Group Not Found",
status: 404
});
}
for await (const operation of operations) {
switch (operation.op) {
case "replace": {
await groupDAL.update(
{
id: groupId,
orgId
},
{
name: operation.value.displayName
}
);
group = await groupDAL.updateById(group.id, {
name: operation.value.displayName
});
break;
}
case "add": {
// TODO
try {
const orgMemberships = await orgMembershipDAL.find({
$in: {
id: operation.value.map((member) => member.value)
}
});
await addUsersToGroupByUserIds({
group,
userIds: orgMemberships.map((membership) => membership.userId as string),
userDAL,
userGroupMembershipDAL,
orgDAL,
groupProjectDAL,
projectKeyDAL,
projectDAL,
projectBotDAL
});
} catch {
logger.info("Repeat SCIM user-group add operation");
}
break;
}
case "remove": {
// TODO
const orgMembershipId = extractScimValueFromPath(operation.path);
if (!orgMembershipId) throw new ScimRequestError({ detail: "Invalid path value", status: 400 });
const orgMembership = await orgMembershipDAL.findById(orgMembershipId);
if (!orgMembership) throw new ScimRequestError({ detail: "Org Membership Not Found", status: 400 });
await removeUsersFromGroupByUserIds({
group,
userIds: [orgMembership.userId as string],
userDAL,
userGroupMembershipDAL,
groupProjectDAL,
projectKeyDAL
});
break;
}
default: {
@ -872,17 +936,15 @@ export const scimServiceFactory = ({
}
}
if (!group) {
throw new ScimRequestError({
detail: "Group Not Found",
status: 404
});
}
const members = await userGroupMembershipDAL.findGroupMembershipsByGroupIdInOrg(group.id, orgId);
return buildScimGroup({
groupId: group.id,
name: group.name,
members: []
members: members.map((member) => ({
value: member.orgMembershipId,
display: `${member.firstName ?? ""} ${member.lastName ?? ""}`
}))
});
};

@ -125,10 +125,11 @@ type TRemoveOp = {
type TAddOp = {
op: "add";
path: string;
value: {
value: string;
display?: string;
};
}[];
};
export type TDeleteScimGroupDTO = {
@ -157,7 +158,10 @@ export type TScimUser = {
type: string;
}[];
active: boolean;
groups: string[];
groups: {
value: string;
display: string;
}[];
meta: {
resourceType: string;
location: null;

@ -515,6 +515,9 @@ export const FOLDERS = {
path: "The path to list folders from.",
directory: "The directory to list folders from. (Deprecated in favor of path)"
},
GET_BY_ID: {
folderId: "The id of the folder to get details."
},
CREATE: {
workspaceId: "The ID of the project to create the folder in.",
environment: "The slug of the environment to create the folder in.",

@ -22,6 +22,8 @@ import { buildDynamicSecretProviders } from "@app/ee/services/dynamic-secret/pro
import { dynamicSecretLeaseDALFactory } from "@app/ee/services/dynamic-secret-lease/dynamic-secret-lease-dal";
import { dynamicSecretLeaseQueueServiceFactory } from "@app/ee/services/dynamic-secret-lease/dynamic-secret-lease-queue";
import { dynamicSecretLeaseServiceFactory } from "@app/ee/services/dynamic-secret-lease/dynamic-secret-lease-service";
import { externalKmsDALFactory } from "@app/ee/services/external-kms/external-kms-dal";
import { externalKmsServiceFactory } from "@app/ee/services/external-kms/external-kms-service";
import { groupDALFactory } from "@app/ee/services/group/group-dal";
import { groupServiceFactory } from "@app/ee/services/group/group-service";
import { userGroupMembershipDALFactory } from "@app/ee/services/group/user-group-membership-dal";
@ -116,7 +118,8 @@ import { integrationDALFactory } from "@app/services/integration/integration-dal
import { integrationServiceFactory } from "@app/services/integration/integration-service";
import { integrationAuthDALFactory } from "@app/services/integration-auth/integration-auth-dal";
import { integrationAuthServiceFactory } from "@app/services/integration-auth/integration-auth-service";
import { kmsDALFactory } from "@app/services/kms/kms-dal";
import { internalKmsDALFactory } from "@app/services/kms/internal-kms-dal";
import { kmskeyDALFactory } from "@app/services/kms/kms-key-dal";
import { kmsRootConfigDALFactory } from "@app/services/kms/kms-root-config-dal";
import { kmsServiceFactory } from "@app/services/kms/kms-service";
import { incidentContactDALFactory } from "@app/services/org/incident-contacts-dal";
@ -288,7 +291,9 @@ export const registerRoutes = async (
const dynamicSecretDAL = dynamicSecretDALFactory(db);
const dynamicSecretLeaseDAL = dynamicSecretLeaseDALFactory(db);
const kmsDAL = kmsDALFactory(db);
const kmsDAL = kmskeyDALFactory(db);
const internalKmsDAL = internalKmsDALFactory(db);
const externalKmsDAL = externalKmsDALFactory(db);
const kmsRootConfigDAL = kmsRootConfigDALFactory(db);
const permissionService = permissionServiceFactory({
@ -302,7 +307,16 @@ export const registerRoutes = async (
const kmsService = kmsServiceFactory({
kmsRootConfigDAL,
keyStore,
kmsDAL
kmsDAL,
internalKmsDAL,
orgDAL,
projectDAL
});
const externalKmsService = externalKmsServiceFactory({
kmsDAL,
kmsService,
permissionService,
externalKmsDAL
});
const trustedIpService = trustedIpServiceFactory({
@ -331,7 +345,7 @@ export const registerRoutes = async (
permissionService,
secretApprovalPolicyDAL
});
const tokenService = tokenServiceFactory({ tokenDAL: authTokenDAL, userDAL });
const tokenService = tokenServiceFactory({ tokenDAL: authTokenDAL, userDAL, orgMembershipDAL });
const samlService = samlConfigServiceFactory({
permissionService,
@ -645,7 +659,8 @@ export const registerRoutes = async (
const webhookService = webhookServiceFactory({
permissionService,
webhookDAL,
projectEnvDAL
projectEnvDAL,
projectDAL
});
const secretTagService = secretTagServiceFactory({ secretTagDAL, permissionService });
@ -1030,7 +1045,8 @@ export const registerRoutes = async (
projectUserAdditionalPrivilege: projectUserAdditionalPrivilegeService,
identityProjectAdditionalPrivilege: identityProjectAdditionalPrivilegeService,
secretSharing: secretSharingService,
userEngagement: userEngagementService
userEngagement: userEngagementService,
externalKms: externalKmsService
});
const cronJobs: CronJob[] = [];

@ -292,4 +292,39 @@ export const registerSecretFolderRouter = async (server: FastifyZodProvider) =>
return { folders };
}
});
server.route({
method: "GET",
url: "/:id",
config: {
rateLimit: readLimit
},
schema: {
description: "Get folder by id",
security: [
{
bearerAuth: []
}
],
params: z.object({
id: z.string().trim().describe(FOLDERS.GET_BY_ID.folderId)
}),
response: {
200: z.object({
folder: SecretFoldersSchema
})
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY, AuthMode.SERVICE_TOKEN, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const folder = await server.services.folder.getFolderById({
actorId: req.permission.id,
actor: req.permission.type,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
id: req.params.id
});
return { folder };
}
});
};

@ -4,7 +4,8 @@ import bcrypt from "bcrypt";
import { TAuthTokens, TAuthTokenSessions } from "@app/db/schemas";
import { getConfig } from "@app/lib/config/env";
import { UnauthorizedError } from "@app/lib/errors";
import { ForbiddenRequestError, UnauthorizedError } from "@app/lib/errors";
import { TOrgMembershipDALFactory } from "@app/services/org-membership/org-membership-dal";
import { AuthModeJwtTokenPayload } from "../auth/auth-type";
import { TUserDALFactory } from "../user/user-dal";
@ -14,6 +15,7 @@ import { TCreateTokenForUserDTO, TIssueAuthTokenDTO, TokenType, TValidateTokenFo
type TAuthTokenServiceFactoryDep = {
tokenDAL: TTokenDALFactory;
userDAL: Pick<TUserDALFactory, "findById" | "transaction">;
orgMembershipDAL: Pick<TOrgMembershipDALFactory, "findOne">;
};
export type TAuthTokenServiceFactory = ReturnType<typeof tokenServiceFactory>;
@ -67,7 +69,7 @@ export const getTokenConfig = (tokenType: TokenType) => {
}
};
export const tokenServiceFactory = ({ tokenDAL, userDAL }: TAuthTokenServiceFactoryDep) => {
export const tokenServiceFactory = ({ tokenDAL, userDAL, orgMembershipDAL }: TAuthTokenServiceFactoryDep) => {
const createTokenForUser = async ({ type, userId, orgId }: TCreateTokenForUserDTO) => {
const { token, ...tkCfg } = getTokenConfig(type);
const appCfg = getConfig();
@ -154,6 +156,16 @@ export const tokenServiceFactory = ({ tokenDAL, userDAL }: TAuthTokenServiceFact
const user = await userDAL.findById(session.userId);
if (!user || !user.isAccepted) throw new UnauthorizedError({ name: "Token user not found" });
if (token.organizationId) {
const orgMembership = await orgMembershipDAL.findOne({
userId: user.id,
orgId: token.organizationId
});
if (!orgMembership) throw new ForbiddenRequestError({ message: "User not member of organization" });
if (!orgMembership.isActive) throw new ForbiddenRequestError({ message: "User not active in organization" });
}
return { user, tokenVersionId: token.tokenVersionId, orgId: token.organizationId };
};

@ -75,8 +75,10 @@ export const getCaCredentials = async ({
kmsService
});
const decryptedPrivateKey = await kmsService.decrypt({
kmsId: keyId,
const kmsDecryptor = await kmsService.decryptWithKmsKey({
kmsId: keyId
});
const decryptedPrivateKey = kmsDecryptor({
cipherTextBlob: caSecret.encryptedPrivateKey
});
@ -123,15 +125,17 @@ export const getCaCertChain = async ({
kmsService
});
const decryptedCaCert = await kmsService.decrypt({
kmsId: keyId,
const kmsDecryptor = await kmsService.decryptWithKmsKey({
kmsId: keyId
});
const decryptedCaCert = kmsDecryptor({
cipherTextBlob: caCert.encryptedCertificate
});
const caCertObj = new x509.X509Certificate(decryptedCaCert);
const decryptedChain = await kmsService.decrypt({
kmsId: keyId,
const decryptedChain = kmsDecryptor({
cipherTextBlob: caCert.encryptedCertificateChain
});
@ -168,8 +172,11 @@ export const rebuildCaCrl = async ({
kmsService
});
const privateKey = await kmsService.decrypt({
kmsId: keyId,
const kmsDecryptor = await kmsService.decryptWithKmsKey({
kmsId: keyId
});
const privateKey = kmsDecryptor({
cipherTextBlob: caSecret.encryptedPrivateKey
});
@ -200,8 +207,10 @@ export const rebuildCaCrl = async ({
signingKey: sk
});
const { cipherTextBlob: encryptedCrl } = await kmsService.encrypt({
kmsId: keyId,
const kmsEncryptor = await kmsService.encryptWithKmsKey({
kmsId: keyId
});
const { cipherTextBlob: encryptedCrl } = kmsEncryptor({
plainText: Buffer.from(new Uint8Array(crl.rawData))
});

@ -25,7 +25,7 @@ type TCertificateAuthorityQueueFactoryDep = {
certificateAuthoritySecretDAL: TCertificateAuthoritySecretDALFactory;
certificateDAL: TCertificateDALFactory;
projectDAL: Pick<TProjectDALFactory, "findProjectBySlug" | "findOne" | "updateById" | "findById" | "transaction">;
kmsService: Pick<TKmsServiceFactory, "generateKmsKey" | "encrypt" | "decrypt">;
kmsService: Pick<TKmsServiceFactory, "generateKmsKey" | "encryptWithKmsKey" | "decryptWithKmsKey">;
queueService: TQueueServiceFactory;
};
export type TCertificateAuthorityQueueFactory = ReturnType<typeof certificateAuthorityQueueFactory>;
@ -88,8 +88,10 @@ export const certificateAuthorityQueueFactory = ({
kmsService
});
const privateKey = await kmsService.decrypt({
kmsId: keyId,
const kmsDecryptor = await kmsService.decryptWithKmsKey({
kmsId: keyId
});
const privateKey = kmsDecryptor({
cipherTextBlob: caSecret.encryptedPrivateKey
});
@ -120,8 +122,10 @@ export const certificateAuthorityQueueFactory = ({
signingKey: sk
});
const { cipherTextBlob: encryptedCrl } = await kmsService.encrypt({
kmsId: keyId,
const kmsEncryptor = await kmsService.encryptWithKmsKey({
kmsId: keyId
});
const { cipherTextBlob: encryptedCrl } = kmsEncryptor({
plainText: Buffer.from(new Uint8Array(crl.rawData))
});

@ -53,7 +53,7 @@ type TCertificateAuthorityServiceFactoryDep = {
certificateDAL: Pick<TCertificateDALFactory, "transaction" | "create" | "find">;
certificateBodyDAL: Pick<TCertificateBodyDALFactory, "create">;
projectDAL: Pick<TProjectDALFactory, "findProjectBySlug" | "findOne" | "updateById" | "findById" | "transaction">;
kmsService: Pick<TKmsServiceFactory, "generateKmsKey" | "encrypt" | "decrypt">;
kmsService: Pick<TKmsServiceFactory, "generateKmsKey" | "encryptWithKmsKey" | "decryptWithKmsKey">;
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission">;
};
@ -154,11 +154,14 @@ export const certificateAuthorityServiceFactory = ({
tx
);
const keyId = await getProjectKmsCertificateKeyId({
const certificateManagerKmsId = await getProjectKmsCertificateKeyId({
projectId: project.id,
projectDAL,
kmsService
});
const kmsEncryptor = await kmsService.encryptWithKmsKey({
kmsId: certificateManagerKmsId
});
if (type === CaType.ROOT) {
// note: create self-signed cert only applicable for root CA
@ -178,13 +181,11 @@ export const certificateAuthorityServiceFactory = ({
]
});
const { cipherTextBlob: encryptedCertificate } = await kmsService.encrypt({
kmsId: keyId,
const { cipherTextBlob: encryptedCertificate } = kmsEncryptor({
plainText: Buffer.from(new Uint8Array(cert.rawData))
});
const { cipherTextBlob: encryptedCertificateChain } = await kmsService.encrypt({
kmsId: keyId,
const { cipherTextBlob: encryptedCertificateChain } = kmsEncryptor({
plainText: Buffer.alloc(0)
});
@ -208,8 +209,7 @@ export const certificateAuthorityServiceFactory = ({
signingKey: keys.privateKey
});
const { cipherTextBlob: encryptedCrl } = await kmsService.encrypt({
kmsId: keyId,
const { cipherTextBlob: encryptedCrl } = kmsEncryptor({
plainText: Buffer.from(new Uint8Array(crl.rawData))
});
@ -224,8 +224,7 @@ export const certificateAuthorityServiceFactory = ({
// https://nodejs.org/api/crypto.html#static-method-keyobjectfromkey
const skObj = KeyObject.from(keys.privateKey);
const { cipherTextBlob: encryptedPrivateKey } = await kmsService.encrypt({
kmsId: keyId,
const { cipherTextBlob: encryptedPrivateKey } = kmsEncryptor({
plainText: skObj.export({
type: "pkcs8",
format: "der"
@ -449,15 +448,17 @@ export const certificateAuthorityServiceFactory = ({
const alg = keyAlgorithmToAlgCfg(ca.keyAlgorithm as CertKeyAlgorithm);
const keyId = await getProjectKmsCertificateKeyId({
const certificateManagerKmsId = await getProjectKmsCertificateKeyId({
projectId: ca.projectId,
projectDAL,
kmsService
});
const kmsDecryptor = await kmsService.decryptWithKmsKey({
kmsId: certificateManagerKmsId
});
const caCert = await certificateAuthorityCertDAL.findOne({ caId: ca.id });
const decryptedCaCert = await kmsService.decrypt({
kmsId: keyId,
const decryptedCaCert = kmsDecryptor({
cipherTextBlob: caCert.encryptedCertificate
});
@ -605,19 +606,20 @@ export const certificateAuthorityServiceFactory = ({
dn: parentCertSubject
});
const keyId = await getProjectKmsCertificateKeyId({
const certificateManagerKmsId = await getProjectKmsCertificateKeyId({
projectId: ca.projectId,
projectDAL,
kmsService
});
const kmsEncryptor = await kmsService.encryptWithKmsKey({
kmsId: certificateManagerKmsId
});
const { cipherTextBlob: encryptedCertificate } = await kmsService.encrypt({
kmsId: keyId,
const { cipherTextBlob: encryptedCertificate } = kmsEncryptor({
plainText: Buffer.from(new Uint8Array(certObj.rawData))
});
const { cipherTextBlob: encryptedCertificateChain } = await kmsService.encrypt({
kmsId: keyId,
const { cipherTextBlob: encryptedCertificateChain } = kmsEncryptor({
plainText: Buffer.from(certificateChain)
});
@ -682,14 +684,16 @@ export const certificateAuthorityServiceFactory = ({
const caCert = await certificateAuthorityCertDAL.findOne({ caId: ca.id });
if (!caCert) throw new BadRequestError({ message: "CA does not have a certificate installed" });
const keyId = await getProjectKmsCertificateKeyId({
const certificateManagerKmsId = await getProjectKmsCertificateKeyId({
projectId: ca.projectId,
projectDAL,
kmsService
});
const kmsDecryptor = await kmsService.decryptWithKmsKey({
kmsId: certificateManagerKmsId
});
const decryptedCaCert = await kmsService.decrypt({
kmsId: keyId,
const decryptedCaCert = kmsDecryptor({
cipherTextBlob: caCert.encryptedCertificate
});
@ -796,8 +800,10 @@ export const certificateAuthorityServiceFactory = ({
const skLeafObj = KeyObject.from(leafKeys.privateKey);
const skLeaf = skLeafObj.export({ format: "pem", type: "pkcs8" }) as string;
const { cipherTextBlob: encryptedCertificate } = await kmsService.encrypt({
kmsId: keyId,
const kmsEncryptor = await kmsService.encryptWithKmsKey({
kmsId: certificateManagerKmsId
});
const { cipherTextBlob: encryptedCertificate } = kmsEncryptor({
plainText: Buffer.from(new Uint8Array(leafCert.rawData))
});

@ -95,7 +95,7 @@ export type TGetCaCredentialsDTO = {
certificateAuthorityDAL: Pick<TCertificateAuthorityDALFactory, "findById">;
certificateAuthoritySecretDAL: Pick<TCertificateAuthoritySecretDALFactory, "findOne">;
projectDAL: Pick<TProjectDALFactory, "findOne" | "updateById" | "transaction">;
kmsService: Pick<TKmsServiceFactory, "decrypt" | "generateKmsKey">;
kmsService: Pick<TKmsServiceFactory, "decryptWithKmsKey" | "generateKmsKey">;
};
export type TGetCaCertChainDTO = {
@ -103,7 +103,7 @@ export type TGetCaCertChainDTO = {
certificateAuthorityDAL: Pick<TCertificateAuthorityDALFactory, "findById">;
certificateAuthorityCertDAL: Pick<TCertificateAuthorityCertDALFactory, "findOne">;
projectDAL: Pick<TProjectDALFactory, "findOne" | "updateById" | "transaction">;
kmsService: Pick<TKmsServiceFactory, "decrypt" | "generateKmsKey">;
kmsService: Pick<TKmsServiceFactory, "decryptWithKmsKey" | "generateKmsKey">;
};
export type TRebuildCaCrlDTO = {
@ -113,7 +113,7 @@ export type TRebuildCaCrlDTO = {
certificateAuthoritySecretDAL: Pick<TCertificateAuthoritySecretDALFactory, "findOne">;
projectDAL: Pick<TProjectDALFactory, "findOne" | "updateById" | "transaction">;
certificateDAL: Pick<TCertificateDALFactory, "find">;
kmsService: Pick<TKmsServiceFactory, "generateKmsKey" | "decrypt" | "encrypt">;
kmsService: Pick<TKmsServiceFactory, "generateKmsKey" | "decryptWithKmsKey" | "encryptWithKmsKey">;
};
export type TRotateCaCrlTriggerDTO = {

@ -25,7 +25,7 @@ type TCertificateServiceFactoryDep = {
certificateAuthorityCrlDAL: Pick<TCertificateAuthorityCrlDALFactory, "update">;
certificateAuthoritySecretDAL: Pick<TCertificateAuthoritySecretDALFactory, "findOne">;
projectDAL: Pick<TProjectDALFactory, "findOne" | "updateById" | "findById" | "transaction">;
kmsService: Pick<TKmsServiceFactory, "generateKmsKey" | "encrypt" | "decrypt">;
kmsService: Pick<TKmsServiceFactory, "generateKmsKey" | "encryptWithKmsKey" | "decryptWithKmsKey">;
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission">;
};
@ -164,14 +164,16 @@ export const certificateServiceFactory = ({
const certBody = await certificateBodyDAL.findOne({ certId: cert.id });
const keyId = await getProjectKmsCertificateKeyId({
const certificateManagerKeyId = await getProjectKmsCertificateKeyId({
projectId: ca.projectId,
projectDAL,
kmsService
});
const decryptedCert = await kmsService.decrypt({
kmsId: keyId,
const kmsDecryptor = await kmsService.decryptWithKmsKey({
kmsId: certificateManagerKeyId
});
const decryptedCert = kmsDecryptor({
cipherTextBlob: certBody.encryptedCertificate
});

@ -78,7 +78,10 @@ export const identityAwsAuthServiceFactory = ({
.map((accountId) => accountId.trim())
.some((accountId) => accountId === Account);
if (!isAccountAllowed) throw new UnauthorizedError();
if (!isAccountAllowed)
throw new ForbiddenRequestError({
message: "Access denied: AWS account ID not allowed."
});
}
if (identityAwsAuth.allowedPrincipalArns) {
@ -94,7 +97,10 @@ export const identityAwsAuthServiceFactory = ({
return regex.test(extractPrincipalArn(Arn));
});
if (!isArnAllowed) throw new UnauthorizedError();
if (!isArnAllowed)
throw new ForbiddenRequestError({
message: "Access denied: AWS principal ARN not allowed."
});
}
const identityAccessToken = await identityAwsAuthDAL.transaction(async (tx) => {

@ -17,6 +17,7 @@ export const validateAzureIdentity = async ({
const jwksUri = `https://login.microsoftonline.com/${tenantId}/discovery/keys`;
const decodedJwt = jwt.decode(azureJwt, { complete: true }) as TDecodedAzureAuthJwt;
const { kid } = decodedJwt.header;
const { data }: { data: TAzureJwksUriResponse } = await axios.get(jwksUri);
@ -27,6 +28,13 @@ export const validateAzureIdentity = async ({
const publicKey = `-----BEGIN CERTIFICATE-----\n${signingKey.x5c[0]}\n-----END CERTIFICATE-----`;
// Case: This can happen when the user uses a custom resource (such as https://management.azure.com&client_id=value).
// In this case, the audience in the decoded JWT will not have a trailing slash, but the resource will.
if (!decodedJwt.payload.aud.endsWith("/") && resource.endsWith("/")) {
// eslint-disable-next-line no-param-reassign
resource = resource.slice(0, -1);
}
return jwt.verify(azureJwt, publicKey, {
audience: resource,
issuer: `https://sts.windows.net/${tenantId}/`

@ -81,7 +81,10 @@ export const identityGcpAuthServiceFactory = ({
.map((serviceAccount) => serviceAccount.trim())
.some((serviceAccount) => serviceAccount === gcpIdentityDetails.email);
if (!isServiceAccountAllowed) throw new UnauthorizedError();
if (!isServiceAccountAllowed)
throw new ForbiddenRequestError({
message: "Access denied: GCP service account not allowed."
});
}
if (identityGcpAuth.type === "gce" && identityGcpAuth.allowedProjects && gcpIdentityDetails.computeEngineDetails) {
@ -92,7 +95,10 @@ export const identityGcpAuthServiceFactory = ({
.map((project) => project.trim())
.some((project) => project === gcpIdentityDetails.computeEngineDetails?.project_id);
if (!isProjectAllowed) throw new UnauthorizedError();
if (!isProjectAllowed)
throw new ForbiddenRequestError({
message: "Access denied: GCP project not allowed."
});
}
if (identityGcpAuth.type === "gce" && identityGcpAuth.allowedZones && gcpIdentityDetails.computeEngineDetails) {
@ -101,7 +107,10 @@ export const identityGcpAuthServiceFactory = ({
.map((zone) => zone.trim())
.some((zone) => zone === gcpIdentityDetails.computeEngineDetails?.zone);
if (!isZoneAllowed) throw new UnauthorizedError();
if (!isZoneAllowed)
throw new ForbiddenRequestError({
message: "Access denied: GCP zone not allowed."
});
}
const identityAccessToken = await identityGcpAuthDAL.transaction(async (tx) => {

@ -139,7 +139,10 @@ export const identityKubernetesAuthServiceFactory = ({
.map((namespace) => namespace.trim())
.some((namespace) => namespace === targetNamespace);
if (!isNamespaceAllowed) throw new UnauthorizedError();
if (!isNamespaceAllowed)
throw new ForbiddenRequestError({
message: "Access denied: K8s namespace not allowed."
});
}
if (identityKubernetesAuth.allowedNames) {
@ -150,7 +153,10 @@ export const identityKubernetesAuthServiceFactory = ({
.map((name) => name.trim())
.some((name) => name === targetName);
if (!isNameAllowed) throw new UnauthorizedError();
if (!isNameAllowed)
throw new ForbiddenRequestError({
message: "Access denied: K8s name not allowed."
});
}
if (identityKubernetesAuth.allowedAudience) {
@ -159,7 +165,10 @@ export const identityKubernetesAuthServiceFactory = ({
(audience) => audience === identityKubernetesAuth.allowedAudience
);
if (!isAudienceAllowed) throw new UnauthorizedError();
if (!isAudienceAllowed)
throw new ForbiddenRequestError({
message: "Access denied: K8s audience not allowed."
});
}
const identityAccessToken = await identityKubernetesAuthDAL.transaction(async (tx) => {

@ -124,13 +124,17 @@ export const identityOidcAuthServiceFactory = ({
if (identityOidcAuth.boundSubject) {
if (tokenData.sub !== identityOidcAuth.boundSubject) {
throw new UnauthorizedError();
throw new ForbiddenRequestError({
message: "Access denied: OIDC subject not allowed."
});
}
}
if (identityOidcAuth.boundAudiences) {
if (!identityOidcAuth.boundAudiences.split(", ").includes(tokenData.aud)) {
throw new UnauthorizedError();
throw new ForbiddenRequestError({
message: "Access denied: OIDC audience not allowed."
});
}
}
@ -139,7 +143,9 @@ export const identityOidcAuthServiceFactory = ({
const claimValue = (identityOidcAuth.boundClaims as Record<string, string>)[claimKey];
// handle both single and multi-valued claims
if (!claimValue.split(", ").some((claimEntry) => tokenData[claimKey] === claimEntry)) {
throw new UnauthorizedError();
throw new ForbiddenRequestError({
message: "Access denied: OIDC claim not allowed."
});
}
});
}

@ -574,14 +574,14 @@ export const integrationAuthServiceFactory = ({
const botKey = await projectBotService.getBotKey(integrationAuth.projectId);
const { accessId, accessToken } = await getIntegrationAccessToken(integrationAuth, botKey);
AWS.config.update({
const kms = new AWS.KMS({
region,
credentials: {
accessKeyId: String(accessId),
secretAccessKey: accessToken
}
});
const kms = new AWS.KMS();
const aliases = await kms.listAliases({}).promise();
const keyAliases = aliases.Aliases!.filter((alias) => {

@ -0,0 +1,10 @@
import { TDbClient } from "@app/db";
import { TableName } from "@app/db/schemas";
import { ormify } from "@app/lib/knex";
export type TInternalKmsDALFactory = ReturnType<typeof internalKmsDALFactory>;
export const internalKmsDALFactory = (db: TDbClient) => {
const internalKmsOrm = ormify(db, TableName.InternalKms);
return internalKmsOrm;
};

@ -1,10 +0,0 @@
import { TDbClient } from "@app/db";
import { TableName } from "@app/db/schemas";
import { ormify } from "@app/lib/knex";
export type TKmsDALFactory = ReturnType<typeof kmsDALFactory>;
export const kmsDALFactory = (db: TDbClient) => {
const kmsOrm = ormify(db, TableName.KmsKey);
return kmsOrm;
};

@ -0,0 +1,64 @@
import { Knex } from "knex";
import { TDbClient } from "@app/db";
import { KmsKeysSchema, TableName } from "@app/db/schemas";
import { DatabaseError } from "@app/lib/errors";
import { ormify, selectAllTableCols } from "@app/lib/knex";
export type TKmsKeyDALFactory = ReturnType<typeof kmskeyDALFactory>;
export const kmskeyDALFactory = (db: TDbClient) => {
const kmsOrm = ormify(db, TableName.KmsKey);
const findByIdWithAssociatedKms = async (id: string, tx?: Knex) => {
try {
const result = await (tx || db.replicaNode())(TableName.KmsKey)
.where({ [`${TableName.KmsKey}.id` as "id"]: id })
.leftJoin(TableName.InternalKms, `${TableName.KmsKey}.id`, `${TableName.InternalKms}.kmsKeyId`)
.leftJoin(TableName.ExternalKms, `${TableName.KmsKey}.id`, `${TableName.ExternalKms}.kmsKeyId`)
.first()
.select(selectAllTableCols(TableName.KmsKey))
.select(
db.ref("id").withSchema(TableName.InternalKms).as("internalKmsId"),
db.ref("encryptedKey").withSchema(TableName.InternalKms).as("internalKmsEncryptedKey"),
db.ref("encryptionAlgorithm").withSchema(TableName.InternalKms).as("internalKmsEncryptionAlgorithm"),
db.ref("version").withSchema(TableName.InternalKms).as("internalKmsVersion"),
db.ref("id").withSchema(TableName.InternalKms).as("internalKmsId")
)
.select(
db.ref("id").withSchema(TableName.ExternalKms).as("externalKmsId"),
db.ref("provider").withSchema(TableName.ExternalKms).as("externalKmsProvider"),
db.ref("encryptedProviderInputs").withSchema(TableName.ExternalKms).as("externalKmsEncryptedProviderInput"),
db.ref("status").withSchema(TableName.ExternalKms).as("externalKmsStatus"),
db.ref("statusDetails").withSchema(TableName.ExternalKms).as("externalKmsStatusDetails")
);
const data = {
...KmsKeysSchema.parse(result),
isExternal: Boolean(result?.externalKmsId),
externalKms: result?.externalKmsId
? {
id: result.externalKmsId,
provider: result.externalKmsProvider,
encryptedProviderInput: result.externalKmsEncryptedProviderInput,
status: result.externalKmsStatus,
statusDetails: result.externalKmsStatusDetails
}
: undefined,
internalKms: result?.internalKmsId
? {
id: result.internalKmsId,
encryptedKey: result.internalKmsEncryptedKey,
encryptionAlgorithm: result.internalKmsEncryptionAlgorithm,
version: result.internalKmsVersion
}
: undefined
};
return data;
} catch (error) {
throw new DatabaseError({ error, name: "Find by id" });
}
};
return { ...kmsOrm, findByIdWithAssociatedKms };
};

@ -1,18 +1,34 @@
import slugify from "@sindresorhus/slugify";
import { Knex } from "knex";
import { TKeyStoreFactory } from "@app/keystore/keystore";
import { getConfig } from "@app/lib/config/env";
import { randomSecureBytes } from "@app/lib/crypto";
import { symmetricCipherService, SymmetricEncryption } from "@app/lib/crypto/cipher";
import { BadRequestError } from "@app/lib/errors";
import { logger } from "@app/lib/logger";
import { alphaNumericNanoId } from "@app/lib/nanoid";
import { TKmsDALFactory } from "./kms-dal";
import { TOrgDALFactory } from "../org/org-dal";
import { TProjectDALFactory } from "../project/project-dal";
import { TInternalKmsDALFactory } from "./internal-kms-dal";
import { TKmsKeyDALFactory } from "./kms-key-dal";
import { TKmsRootConfigDALFactory } from "./kms-root-config-dal";
import { TDecryptWithKmsDTO, TEncryptWithKmsDTO, TGenerateKMSDTO } from "./kms-types";
import {
TDecryptWithKeyDTO,
TDecryptWithKmsDTO,
TEncryptionWithKeyDTO,
TEncryptWithKmsDTO,
TGenerateKMSDTO
} from "./kms-types";
type TKmsServiceFactoryDep = {
kmsDAL: TKmsDALFactory;
kmsDAL: TKmsKeyDALFactory;
projectDAL: Pick<TProjectDALFactory, "findById" | "updateById" | "transaction">;
orgDAL: Pick<TOrgDALFactory, "findById" | "updateById" | "transaction">;
kmsRootConfigDAL: Pick<TKmsRootConfigDALFactory, "findById" | "create">;
keyStore: Pick<TKeyStoreFactory, "acquireLock" | "waitTillReady" | "setItemWithExpiry">;
internalKmsDAL: Pick<TInternalKmsDALFactory, "create">;
};
export type TKmsServiceFactory = ReturnType<typeof kmsServiceFactory>;
@ -25,54 +41,161 @@ const KMS_ROOT_CREATION_WAIT_TIME = 10;
// akhilmhdh: Don't edit this value. This is measured for blob concatination in kms
const KMS_VERSION = "v01";
const KMS_VERSION_BLOB_LENGTH = 3;
export const kmsServiceFactory = ({ kmsDAL, kmsRootConfigDAL, keyStore }: TKmsServiceFactoryDep) => {
export const kmsServiceFactory = ({
kmsDAL,
kmsRootConfigDAL,
keyStore,
internalKmsDAL,
orgDAL,
projectDAL
}: TKmsServiceFactoryDep) => {
let ROOT_ENCRYPTION_KEY = Buffer.alloc(0);
// this is used symmetric encryption
const generateKmsKey = async ({ scopeId, scopeType, isReserved = true, tx }: TGenerateKMSDTO) => {
const generateKmsKey = async ({ orgId, isReserved = true, tx, slug }: TGenerateKMSDTO) => {
const cipher = symmetricCipherService(SymmetricEncryption.AES_GCM_256);
const kmsKeyMaterial = randomSecureBytes(32);
const encryptedKeyMaterial = cipher.encrypt(kmsKeyMaterial, ROOT_ENCRYPTION_KEY);
const sanitizedSlug = slug ? slugify(slug) : slugify(alphaNumericNanoId(8).toLowerCase());
const dbQuery = async (db: Knex) => {
const kmsDoc = await kmsDAL.create(
{
slug: sanitizedSlug,
orgId,
isReserved
},
db
);
const { encryptedKey, ...doc } = await kmsDAL.create(
{
version: 1,
encryptedKey: encryptedKeyMaterial,
encryptionAlgorithm: SymmetricEncryption.AES_GCM_256,
isReserved,
orgId: scopeType === "org" ? scopeId : undefined,
projectId: scopeType === "project" ? scopeId : undefined
},
tx
);
await internalKmsDAL.create(
{
version: 1,
encryptedKey: encryptedKeyMaterial,
encryptionAlgorithm: SymmetricEncryption.AES_GCM_256,
kmsKeyId: kmsDoc.id
},
db
);
return kmsDoc;
};
if (tx) return dbQuery(tx);
const doc = await kmsDAL.transaction(async (tx2) => dbQuery(tx2));
return doc;
};
const encrypt = async ({ kmsId, plainText }: TEncryptWithKmsDTO) => {
const kmsDoc = await kmsDAL.findById(kmsId);
const encryptWithKmsKey = async ({ kmsId }: Omit<TEncryptWithKmsDTO, "plainText">) => {
const kmsDoc = await kmsDAL.findByIdWithAssociatedKms(kmsId);
if (!kmsDoc) throw new BadRequestError({ message: "KMS ID not found" });
// akhilmhdh: as more encryption are added do a check here on kmsDoc.encryptionAlgorithm
const cipher = symmetricCipherService(SymmetricEncryption.AES_GCM_256);
return ({ plainText }: Pick<TEncryptWithKmsDTO, "plainText">) => {
const kmsKey = cipher.decrypt(kmsDoc.internalKms?.encryptedKey as Buffer, ROOT_ENCRYPTION_KEY);
const encryptedPlainTextBlob = cipher.encrypt(plainText, kmsKey);
const kmsKey = cipher.decrypt(kmsDoc.encryptedKey, ROOT_ENCRYPTION_KEY);
const encryptedPlainTextBlob = cipher.encrypt(plainText, kmsKey);
// Buffer#1 encrypted text + Buffer#2 version number
const versionBlob = Buffer.from(KMS_VERSION, "utf8"); // length is 3
const cipherTextBlob = Buffer.concat([encryptedPlainTextBlob, versionBlob]);
return { cipherTextBlob };
// Buffer#1 encrypted text + Buffer#2 version number
const versionBlob = Buffer.from(KMS_VERSION, "utf8"); // length is 3
const cipherTextBlob = Buffer.concat([encryptedPlainTextBlob, versionBlob]);
return { cipherTextBlob };
};
};
const decrypt = async ({ cipherTextBlob: versionedCipherTextBlob, kmsId }: TDecryptWithKmsDTO) => {
const kmsDoc = await kmsDAL.findById(kmsId);
if (!kmsDoc) throw new BadRequestError({ message: "KMS ID not found" });
const encryptWithInputKey = async ({ key }: Omit<TEncryptionWithKeyDTO, "plainText">) => {
// akhilmhdh: as more encryption are added do a check here on kmsDoc.encryptionAlgorithm
const cipher = symmetricCipherService(SymmetricEncryption.AES_GCM_256);
const kmsKey = cipher.decrypt(kmsDoc.encryptedKey, ROOT_ENCRYPTION_KEY);
return ({ plainText }: Pick<TEncryptWithKmsDTO, "plainText">) => {
const encryptedPlainTextBlob = cipher.encrypt(plainText, key);
// Buffer#1 encrypted text + Buffer#2 version number
const versionBlob = Buffer.from(KMS_VERSION, "utf8"); // length is 3
const cipherTextBlob = Buffer.concat([encryptedPlainTextBlob, versionBlob]);
return { cipherTextBlob };
};
};
const cipherTextBlob = versionedCipherTextBlob.subarray(0, -KMS_VERSION_BLOB_LENGTH);
const decryptedBlob = cipher.decrypt(cipherTextBlob, kmsKey);
return decryptedBlob;
const decryptWithKmsKey = async ({ kmsId }: Omit<TDecryptWithKmsDTO, "cipherTextBlob">) => {
const kmsDoc = await kmsDAL.findByIdWithAssociatedKms(kmsId);
if (!kmsDoc) throw new BadRequestError({ message: "KMS ID not found" });
const cipher = symmetricCipherService(SymmetricEncryption.AES_GCM_256);
const kmsKey = cipher.decrypt(kmsDoc.internalKms?.encryptedKey as Buffer, ROOT_ENCRYPTION_KEY);
return ({ cipherTextBlob: versionedCipherTextBlob }: Pick<TDecryptWithKmsDTO, "cipherTextBlob">) => {
const cipherTextBlob = versionedCipherTextBlob.subarray(0, -KMS_VERSION_BLOB_LENGTH);
const decryptedBlob = cipher.decrypt(cipherTextBlob, kmsKey);
return decryptedBlob;
};
};
const decryptWithInputKey = async ({ key }: Omit<TDecryptWithKeyDTO, "cipherTextBlob">) => {
const cipher = symmetricCipherService(SymmetricEncryption.AES_GCM_256);
return ({ cipherTextBlob: versionedCipherTextBlob }: Pick<TDecryptWithKeyDTO, "cipherTextBlob">) => {
const cipherTextBlob = versionedCipherTextBlob.subarray(0, -KMS_VERSION_BLOB_LENGTH);
const decryptedBlob = cipher.decrypt(cipherTextBlob, key);
return decryptedBlob;
};
};
const getOrgKmsKeyId = async (orgId: string) => {
const keyId = await orgDAL.transaction(async (tx) => {
const org = await orgDAL.findById(orgId, tx);
if (!org) {
throw new BadRequestError({ message: "Org not found" });
}
if (!org.kmsDefaultKeyId) {
// create default kms key for certificate service
const key = await generateKmsKey({
isReserved: true,
orgId: org.id,
tx
});
await orgDAL.updateById(
org.id,
{
kmsDefaultKeyId: key.id
},
tx
);
return key.id;
}
return org.kmsDefaultKeyId;
});
return keyId;
};
const getProjectSecretManagerKmsKeyId = async (projectId: string) => {
const keyId = await projectDAL.transaction(async (tx) => {
const project = await projectDAL.findById(projectId, tx);
if (!project) {
throw new BadRequestError({ message: "Project not found" });
}
if (!project.kmsSecretManagerKeyId) {
// create default kms key for certificate service
const key = await generateKmsKey({
isReserved: true,
orgId: project.orgId,
tx
});
await projectDAL.updateById(
projectId,
{
kmsSecretManagerKeyId: key.id
},
tx
);
return key.id;
}
return project.kmsSecretManagerKeyId;
});
return keyId;
};
const startService = async () => {
@ -123,7 +246,11 @@ export const kmsServiceFactory = ({ kmsDAL, kmsRootConfigDAL, keyStore }: TKmsSe
return {
startService,
generateKmsKey,
encrypt,
decrypt
encryptWithKmsKey,
encryptWithInputKey,
decryptWithKmsKey,
decryptWithInputKey,
getOrgKmsKeyId,
getProjectSecretManagerKmsKeyId
};
};

@ -1,9 +1,9 @@
import { Knex } from "knex";
export type TGenerateKMSDTO = {
scopeType: "project" | "org";
scopeId: string;
orgId: string;
isReserved?: boolean;
slug?: string;
tx?: Knex;
};
@ -12,7 +12,17 @@ export type TEncryptWithKmsDTO = {
plainText: Buffer;
};
export type TEncryptionWithKeyDTO = {
key: Buffer;
plainText: Buffer;
};
export type TDecryptWithKmsDTO = {
kmsId: string;
cipherTextBlob: Buffer;
};
export type TDecryptWithKeyDTO = {
key: Buffer;
cipherTextBlob: Buffer;
};

@ -74,6 +74,7 @@ export const orgDALFactory = (db: TDbClient) => {
db.ref("role").withSchema(TableName.OrgMembership),
db.ref("roleId").withSchema(TableName.OrgMembership),
db.ref("status").withSchema(TableName.OrgMembership),
db.ref("isActive").withSchema(TableName.OrgMembership),
db.ref("email").withSchema(TableName.Users),
db.ref("username").withSchema(TableName.Users),
db.ref("firstName").withSchema(TableName.Users),
@ -207,9 +208,9 @@ export const orgDALFactory = (db: TDbClient) => {
}
};
const updateById = async (orgId: string, data: Partial<TOrganizations>) => {
const updateById = async (orgId: string, data: Partial<TOrganizations>, tx?: Knex) => {
try {
const [org] = await db(TableName.Organization)
const [org] = await (tx || db)(TableName.Organization)
.where({ id: orgId })
.update({ ...data })
.returning("*");

@ -144,10 +144,7 @@ export const orgServiceFactory = ({
return members;
};
const findAllWorkspaces = async ({ actor, actorId, actorOrgId, actorAuthMethod, orgId }: TFindAllWorkspacesDTO) => {
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Workspace);
const findAllWorkspaces = async ({ actor, actorId, orgId }: TFindAllWorkspacesDTO) => {
const organizationWorkspaceIds = new Set((await projectDAL.find({ orgId })).map((workspace) => workspace.id));
let workspaces: (TProjects & { organization: string } & {
@ -207,7 +204,8 @@ export const orgServiceFactory = ({
orgId,
userId: user.id,
role: OrgMembershipRole.Admin,
status: OrgMembershipStatus.Accepted
status: OrgMembershipStatus.Accepted,
isActive: true
};
await orgDAL.createMembership(createMembershipData, tx);
@ -311,7 +309,8 @@ export const orgServiceFactory = ({
userId,
orgId: org.id,
role: OrgMembershipRole.Admin,
status: OrgMembershipStatus.Accepted
status: OrgMembershipStatus.Accepted,
isActive: true
},
tx
);
@ -460,7 +459,8 @@ export const orgServiceFactory = ({
inviteEmail: inviteeEmail,
orgId,
role: OrgMembershipRole.Member,
status: OrgMembershipStatus.Invited
status: OrgMembershipStatus.Invited,
isActive: true
},
tx
);
@ -491,7 +491,8 @@ export const orgServiceFactory = ({
orgId,
userId: user.id,
role: OrgMembershipRole.Member,
status: OrgMembershipStatus.Invited
status: OrgMembershipStatus.Invited,
isActive: true
},
tx
);

@ -71,9 +71,8 @@ export const getProjectKmsCertificateKeyId = async ({
if (!project.kmsCertificateKeyId) {
// create default kms key for certificate service
const key = await kmsService.generateKmsKey({
scopeId: projectId,
scopeType: "project",
isReserved: true,
orgId: project.orgId,
tx
});

@ -322,7 +322,7 @@ export const secretFolderDALFactory = (db: TDbClient) => {
.first();
if (folder) {
const { envId, envName, envSlug, ...el } = folder;
return { ...el, environment: { envId, envName, envSlug } };
return { ...el, environment: { envId, envName, envSlug }, envId };
}
} catch (error) {
throw new DatabaseError({ error, name: "Find by id" });

@ -6,7 +6,7 @@ import { TSecretFoldersInsert } from "@app/db/schemas";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
import { TSecretSnapshotServiceFactory } from "@app/ee/services/secret-snapshot/secret-snapshot-service";
import { BadRequestError } from "@app/lib/errors";
import { BadRequestError, NotFoundError } from "@app/lib/errors";
import { TProjectDALFactory } from "../project/project-dal";
import { TProjectEnvDALFactory } from "../project-env/project-env-dal";
@ -14,6 +14,7 @@ import { TSecretFolderDALFactory } from "./secret-folder-dal";
import {
TCreateFolderDTO,
TDeleteFolderDTO,
TGetFolderByIdDTO,
TGetFolderDTO,
TUpdateFolderDTO,
TUpdateManyFoldersDTO
@ -368,11 +369,22 @@ export const secretFolderServiceFactory = ({
return folders;
};
const getFolderById = async ({ actor, actorId, actorOrgId, actorAuthMethod, id }: TGetFolderByIdDTO) => {
const folder = await folderDAL.findById(id);
if (!folder) throw new NotFoundError({ message: "folder not found" });
// folder list is allowed to be read by anyone
// permission to check does user has access
await permissionService.getProjectPermission(actor, actorId, folder.projectId, actorAuthMethod, actorOrgId);
return folder;
};
return {
createFolder,
updateFolder,
updateManyFolders,
deleteFolder,
getFolders
getFolders,
getFolderById
};
};

@ -37,3 +37,7 @@ export type TGetFolderDTO = {
environment: string;
path: string;
} & TProjectPermission;
export type TGetFolderByIdDTO = {
id: string;
} & Omit<TProjectPermission, "projectId">;

@ -642,7 +642,7 @@ export const secretQueueFactory = ({
});
queueService.start(QueueName.SecretWebhook, async (job) => {
await fnTriggerWebhook({ ...job.data, projectEnvDAL, webhookDAL });
await fnTriggerWebhook({ ...job.data, projectEnvDAL, webhookDAL, projectDAL });
});
return {

@ -9,6 +9,7 @@ import { infisicalSymmetricDecrypt } from "@app/lib/crypto/encryption";
import { BadRequestError } from "@app/lib/errors";
import { logger } from "@app/lib/logger";
import { TProjectDALFactory } from "../project/project-dal";
import { TProjectEnvDALFactory } from "../project-env/project-env-dal";
import { TWebhookDALFactory } from "./webhook-dal";
import { WebhookType } from "./webhook-types";
@ -66,11 +67,16 @@ export const triggerWebhookRequest = async (webhook: TWebhooks, data: Record<str
export const getWebhookPayload = (
eventName: string,
workspaceId: string,
environment: string,
secretPath?: string,
type?: string | null
details: {
workspaceName: string;
workspaceId: string;
environment: string;
secretPath?: string;
type?: string | null;
}
) => {
const { workspaceName, workspaceId, environment, secretPath, type } = details;
switch (type) {
case WebhookType.SLACK:
return {
@ -80,8 +86,8 @@ export const getWebhookPayload = (
color: "#E7F256",
fields: [
{
title: "Workspace ID",
value: workspaceId,
title: "Project",
value: workspaceName,
short: false
},
{
@ -117,7 +123,9 @@ export type TFnTriggerWebhookDTO = {
environment: string;
webhookDAL: Pick<TWebhookDALFactory, "findAllWebhooks" | "transaction" | "update" | "bulkUpdate">;
projectEnvDAL: Pick<TProjectEnvDALFactory, "findOne">;
projectDAL: Pick<TProjectDALFactory, "findById">;
};
// this is reusable function
// used in secret queue to trigger webhook and update status when secrets changes
export const fnTriggerWebhook = async ({
@ -125,7 +133,8 @@ export const fnTriggerWebhook = async ({
secretPath,
projectId,
webhookDAL,
projectEnvDAL
projectEnvDAL,
projectDAL
}: TFnTriggerWebhookDTO) => {
const webhooks = await webhookDAL.findAllWebhooks(projectId, environment);
const toBeTriggeredHooks = webhooks.filter(
@ -134,9 +143,19 @@ export const fnTriggerWebhook = async ({
);
if (!toBeTriggeredHooks.length) return;
logger.info("Secret webhook job started", { environment, secretPath, projectId });
const project = await projectDAL.findById(projectId);
const webhooksTriggered = await Promise.allSettled(
toBeTriggeredHooks.map((hook) =>
triggerWebhookRequest(hook, getWebhookPayload("secrets.modified", projectId, environment, secretPath, hook.type))
triggerWebhookRequest(
hook,
getWebhookPayload("secrets.modified", {
workspaceName: project.name,
workspaceId: projectId,
environment,
secretPath,
type: hook.type
})
)
)
);

@ -6,6 +6,7 @@ import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services
import { infisicalSymmetricEncypt } from "@app/lib/crypto/encryption";
import { BadRequestError } from "@app/lib/errors";
import { TProjectDALFactory } from "../project/project-dal";
import { TProjectEnvDALFactory } from "../project-env/project-env-dal";
import { TWebhookDALFactory } from "./webhook-dal";
import { decryptWebhookDetails, getWebhookPayload, triggerWebhookRequest } from "./webhook-fns";
@ -20,12 +21,18 @@ import {
type TWebhookServiceFactoryDep = {
webhookDAL: TWebhookDALFactory;
projectEnvDAL: TProjectEnvDALFactory;
projectDAL: Pick<TProjectDALFactory, "findById">;
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission">;
};
export type TWebhookServiceFactory = ReturnType<typeof webhookServiceFactory>;
export const webhookServiceFactory = ({ webhookDAL, projectEnvDAL, permissionService }: TWebhookServiceFactoryDep) => {
export const webhookServiceFactory = ({
webhookDAL,
projectEnvDAL,
permissionService,
projectDAL
}: TWebhookServiceFactoryDep) => {
const createWebhook = async ({
actor,
actorId,
@ -124,13 +131,21 @@ export const webhookServiceFactory = ({ webhookDAL, projectEnvDAL, permissionSer
actorAuthMethod,
actorOrgId
);
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Webhooks);
const project = await projectDAL.findById(webhook.projectId);
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Webhooks);
let webhookError: string | undefined;
try {
await triggerWebhookRequest(
webhook,
getWebhookPayload("test", webhook.projectId, webhook.environment.slug, webhook.secretPath, webhook.type)
getWebhookPayload("test", {
workspaceName: project.name,
workspaceId: webhook.projectId,
environment: webhook.environment.slug,
secretPath: webhook.secretPath,
type: webhook.type
})
);
} catch (err) {
webhookError = (err as Error).message;

@ -10,7 +10,7 @@ require (
github.com/fatih/semgroup v1.2.0
github.com/gitleaks/go-gitdiff v0.8.0
github.com/h2non/filetype v1.1.3
github.com/infisical/go-sdk v0.2.0
github.com/infisical/go-sdk v0.3.0
github.com/mattn/go-isatty v0.0.14
github.com/muesli/ansi v0.0.0-20221106050444-61f0cd9a192a
github.com/muesli/mango-cobra v1.2.0
@ -19,7 +19,7 @@ require (
github.com/petar-dambovaliev/aho-corasick v0.0.0-20211021192214-5ab2d9280aa9
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8
github.com/posthog/posthog-go v0.0.0-20221221115252-24dfed35d71a
github.com/rs/cors v1.9.0
github.com/rs/cors v1.11.0
github.com/rs/zerolog v1.26.1
github.com/spf13/cobra v1.6.1
github.com/spf13/viper v1.8.1

@ -263,8 +263,8 @@ github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/inconshreveable/mousetrap v1.0.1 h1:U3uMjPSQEBMNp1lFxmllqCPM6P5u/Xq7Pgzkat/bFNc=
github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/infisical/go-sdk v0.2.0 h1:n1/KNdYpeQavSqVwC9BfeV8VRzf3N2X9zO1tzQOSj5Q=
github.com/infisical/go-sdk v0.2.0/go.mod h1:vHTDVw3k+wfStXab513TGk1n53kaKF2xgLqpw/xvtl4=
github.com/infisical/go-sdk v0.3.0 h1:Ls71t227F4CWVQWdStcwv8WDyfHe8eRlyAuMRNHsmlQ=
github.com/infisical/go-sdk v0.3.0/go.mod h1:vHTDVw3k+wfStXab513TGk1n53kaKF2xgLqpw/xvtl4=
github.com/jedib0t/go-pretty v4.3.0+incompatible h1:CGs8AVhEKg/n9YbUenWmNStRW2PHJzaeDodcfvRAbIo=
github.com/jedib0t/go-pretty v4.3.0+incompatible/go.mod h1:XemHduiw8R651AF9Pt4FwCTKeG3oo7hrHJAoznj9nag=
github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
@ -356,8 +356,8 @@ github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rs/cors v1.9.0 h1:l9HGsTsHJcvW14Nk7J9KFz8bzeAWXn3CG6bgt7LsrAE=
github.com/rs/cors v1.9.0/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU=
github.com/rs/cors v1.11.0 h1:0B9GE/r9Bc2UxRMMtymBkHTenPkHDv0CW4Y98GBY+po=
github.com/rs/cors v1.11.0/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU=
github.com/rs/xid v1.3.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
github.com/rs/zerolog v1.26.1 h1:/ihwxqH+4z8UxyI70wM1z9yCvkWcfz/a3mj48k/Zngc=
github.com/rs/zerolog v1.26.1/go.mod h1:/wSSJWX7lVrsOwlbyTRSOJvqRlc+WjWlfes+CiJ+tmc=

@ -122,6 +122,21 @@ func handleAwsIamAuthLogin(cmd *cobra.Command, infisicalClient infisicalSdk.Infi
return infisicalClient.Auth().AwsIamAuthLogin(identityId)
}
func handleOidcAuthLogin(cmd *cobra.Command, infisicalClient infisicalSdk.InfisicalClientInterface) (credential infisicalSdk.MachineIdentityCredential, e error) {
identityId, err := util.GetCmdFlagOrEnv(cmd, "machine-identity-id", util.INFISICAL_MACHINE_IDENTITY_ID_NAME)
if err != nil {
return infisicalSdk.MachineIdentityCredential{}, err
}
jwt, err := util.GetCmdFlagOrEnv(cmd, "oidc-jwt", util.INFISICAL_OIDC_AUTH_JWT_NAME)
if err != nil {
return infisicalSdk.MachineIdentityCredential{}, err
}
return infisicalClient.Auth().OidcAuthLogin(identityId, jwt)
}
func formatAuthMethod(authMethod string) string {
return strings.ReplaceAll(authMethod, "-", " ")
}
@ -257,6 +272,7 @@ var loginCmd = &cobra.Command{
util.AuthStrategy.GCP_ID_TOKEN_AUTH: handleGcpIdTokenAuthLogin,
util.AuthStrategy.GCP_IAM_AUTH: handleGcpIamAuthLogin,
util.AuthStrategy.AWS_IAM_AUTH: handleAwsIamAuthLogin,
util.AuthStrategy.OIDC_AUTH: handleOidcAuthLogin,
}
credential, err := authStrategies[strategy](cmd, infisicalClient)
@ -456,6 +472,7 @@ func init() {
loginCmd.Flags().String("machine-identity-id", "", "machine identity id for kubernetes, azure, gcp-id-token, gcp-iam, and aws-iam auth methods")
loginCmd.Flags().String("service-account-token-path", "", "service account token path for kubernetes auth")
loginCmd.Flags().String("service-account-key-file-path", "", "service account key file path for GCP IAM auth")
loginCmd.Flags().String("oidc-jwt", "", "JWT for OIDC authentication")
}
func DomainOverridePrompt() (bool, error) {
@ -616,7 +633,7 @@ func getFreshUserCredentials(email string, password string) (*api.GetLoginOneV2R
loginTwoResponseResult, err := api.CallLogin2V2(httpClient, api.GetLoginTwoV2Request{
Email: email,
ClientProof: hex.EncodeToString(srpM1),
Password: password,
Password: password,
})
if err != nil {

@ -9,6 +9,7 @@ var AuthStrategy = struct {
GCP_ID_TOKEN_AUTH AuthStrategyType
GCP_IAM_AUTH AuthStrategyType
AWS_IAM_AUTH AuthStrategyType
OIDC_AUTH AuthStrategyType
}{
UNIVERSAL_AUTH: "universal-auth",
KUBERNETES_AUTH: "kubernetes",
@ -16,6 +17,7 @@ var AuthStrategy = struct {
GCP_ID_TOKEN_AUTH: "gcp-id-token",
GCP_IAM_AUTH: "gcp-iam",
AWS_IAM_AUTH: "aws-iam",
OIDC_AUTH: "oidc-auth",
}
var AVAILABLE_AUTH_STRATEGIES = []AuthStrategyType{
@ -25,6 +27,7 @@ var AVAILABLE_AUTH_STRATEGIES = []AuthStrategyType{
AuthStrategy.GCP_ID_TOKEN_AUTH,
AuthStrategy.GCP_IAM_AUTH,
AuthStrategy.AWS_IAM_AUTH,
AuthStrategy.OIDC_AUTH,
}
func IsAuthMethodValid(authMethod string, allowUserAuth bool) (isValid bool, strategy AuthStrategyType) {

@ -19,6 +19,9 @@ const (
// GCP Auth
INFISICAL_GCP_IAM_SERVICE_ACCOUNT_KEY_FILE_PATH_NAME = "INFISICAL_GCP_IAM_SERVICE_ACCOUNT_KEY_FILE_PATH"
// OIDC Auth
INFISICAL_OIDC_AUTH_JWT_NAME = "INFISICAL_OIDC_AUTH_JWT"
// Generic env variable used for auth methods that require a machine identity ID
INFISICAL_MACHINE_IDENTITY_ID_NAME = "INFISICAL_MACHINE_IDENTITY_ID"

@ -0,0 +1,50 @@
---
title: "Hiring"
sidebarTitle: "Hiring"
description: "The guide to hiring at Infisical."
---
Infisical is actively growing and we are hiring for many positions at any given time. This page describes some details of the hiring process we have.
## Strategy
Infisical recruitment strategy relies on 100% inbound interest by default. Many of our team members have previously used Infisical or contributed to our [open source project](https://github.com/Infisical/infisical). This allows us to hire the best candidates who are most interested in working at Infisical.
## Geography
Infisical is a remote-first company, and we have team members across the whole globe. That being said, there are some legal and accounting limitations that we need to abide by. As a result, we are currently only open to hiring from the following countries:
- Australia
- Austria
- Belgium
- Brazil
- Canada
- Chile
- Costa Rica
- Denmark
- Finland
- France
- Germany
- India
- Ireland
- Israel
- Italy
- Japan
- Kenya
- Latvia
- Luxembourg
- Mexico
- Netherlands
- New Zealand
- Philippines
- Poland
- Portugal
- Singapore
- South Africa
- South Korea
- Spain
- Switzerland
- Sweden
- UAE
- United Kingdom
- United States

@ -58,7 +58,8 @@
"pages": [
"handbook/onboarding",
"handbook/spending-money",
"handbook/time-off"
"handbook/time-off",
"handbook/hiring"
]
}
],

@ -0,0 +1,4 @@
---
title: "Get by ID"
openapi: "GET /api/v1/folders/{id}"
---

@ -8,7 +8,8 @@ infisical login
```
### Description
The CLI uses authentication to verify your identity. When you enter the correct email and password for your account, a token is generated and saved in your system Keyring to allow you to make future interactions with the CLI.
The CLI uses authentication to verify your identity. When you enter the correct email and password for your account, a token is generated and saved in your system Keyring to allow you to make future interactions with the CLI.
To change where the login credentials are stored, visit the [vaults command](./vault).
@ -17,12 +18,12 @@ If you have added multiple users, you can switch between the users by using the
<Info>
When you authenticate with **any other method than `user`**, an access token will be printed to the console upon successful login. This token can be used to authenticate with the Infisical API and the CLI by passing it in the `--token` flag when applicable.
Use flag `--plain` along with `--silent` to print only the token in plain text when using a machine identity auth method.
Use flag `--plain` along with `--silent` to print only the token in plain text when using a machine identity auth method.
</Info>
### Flags
The login command supports a number of flags that you can use for different authentication methods. Below is a list of all the flags that can be used with the login command.
<AccordionGroup>
@ -52,6 +53,7 @@ The login command supports a number of flags that you can use for different auth
<Tip>
The `client-id` flag can be substituted with the `INFISICAL_UNIVERSAL_AUTH_CLIENT_ID` environment variable.
</Tip>
</Accordion>
<Accordion title="--client-secret">
```bash
@ -63,6 +65,7 @@ The login command supports a number of flags that you can use for different auth
<Tip>
The `client-secret` flag can be substituted with the `INFISICAL_UNIVERSAL_AUTH_CLIENT_SECRET` environment variable.
</Tip>
</Accordion>
<Accordion title="--machine-identity-id">
```bash
@ -75,6 +78,7 @@ The login command supports a number of flags that you can use for different auth
<Tip>
The `machine-identity-id` flag can be substituted with the `INFISICAL_MACHINE_IDENTITY_ID` environment variable.
</Tip>
</Accordion>
<Accordion title="--service-account-token-path">
```bash
@ -88,6 +92,7 @@ The login command supports a number of flags that you can use for different auth
<Tip>
The `service-account-token-path` flag can be substituted with the `INFISICAL_KUBERNETES_SERVICE_ACCOUNT_TOKEN_PATH` environment variable.
</Tip>
</Accordion>
<Accordion title="--service-account-key-file-path">
```bash
@ -100,9 +105,23 @@ The login command supports a number of flags that you can use for different auth
<Tip>
The `service-account-key-path` flag can be substituted with the `INFISICAL_GCP_IAM_SERVICE_ACCOUNT_KEY_FILE_PATH` environment variable.
</Tip>
</Accordion>
</AccordionGroup>
<Accordion title="--oidc-jwt">
```bash
infisical login --oidc-jwt=<oidc-jwt-token>
```
#### Description
The JWT provided by an identity provider for OIDC authentication.
<Tip>
The `oidc-jwt` flag can be substituted with the `INFISICAL_OIDC_AUTH_JWT` environment variable.
</Tip>
</Accordion>
### Authentication Methods
@ -121,6 +140,7 @@ The Infisical CLI supports multiple authentication methods. Below are the availa
Your machine identity client secret.
</ParamField>
</Expandable>
</ParamField>
<Steps>
@ -134,6 +154,7 @@ The Infisical CLI supports multiple authentication methods. Below are the availa
infisical login --method=universal-auth --client-id=<client-id> --client-secret=<client-secret>
```
</Step>
</Steps>
</Accordion>
<Accordion title="Native Kubernetes">
@ -148,6 +169,7 @@ The Infisical CLI supports multiple authentication methods. Below are the availa
Path to the Kubernetes service account token to use. Default: `/var/run/secrets/kubernetes.io/serviceaccount/token`.
</ParamField>
</Expandable>
</ParamField>
<Steps>
@ -162,6 +184,7 @@ The Infisical CLI supports multiple authentication methods. Below are the availa
infisical login --method=kubernetes --machine-identity-id=<machine-identity-id> --service-account-token-path=<service-account-token-path>
```
</Step>
</Steps>
</Accordion>
@ -213,6 +236,7 @@ The Infisical CLI supports multiple authentication methods. Below are the availa
```
</Step>
</Steps>
</Accordion>
<Accordion title="GCP IAM">
The GCP IAM method is used to authenticate with Infisical with a GCP service account key.
@ -235,11 +259,12 @@ The Infisical CLI supports multiple authentication methods. Below are the availa
<Step title="Obtain an access token">
Run the `login` command with the following flags to obtain an access token:
```bash
```bash
infisical login --method=gcp-iam --machine-identity-id=<machine-identity-id> --service-account-key-file-path=<service-account-key-file-path>
```
</Step>
</Steps>
</Accordion>
<Accordion title="Native AWS IAM">
The AWS IAM method is used to authenticate with Infisical with an AWS IAM role while running in an AWS environment like EC2, Lambda, etc.
@ -264,10 +289,40 @@ The Infisical CLI supports multiple authentication methods. Below are the availa
```
</Step>
</Steps>
</Accordion>
<Accordion title="OIDC Auth">
The OIDC Auth method is used to authenticate with Infisical via identity tokens with OIDC.
<ParamField query="Flags">
<Expandable title="properties">
<ParamField query="machine-identity-id" type="string" required>
Your machine identity ID.
</ParamField>
<ParamField query="oidc-jwt" type="string" required>
The OIDC JWT from the identity provider.
</ParamField>
</Expandable>
</ParamField>
<Steps>
<Step title="Create an OIDC machine identity">
To create an OIDC machine identity, follow the step by step guide outlined [here](/documentation/platform/identities/oidc-auth/general).
</Step>
<Step title="Obtain an access token">
Run the `login` command with the following flags to obtain an access token:
```bash
infisical login --method=oidc-auth --machine-identity-id=<machine-identity-id> --oidc-jwt=<oidc-jwt>
```
</Step>
</Steps>
</Accordion>
</AccordionGroup>
### Machine Identity Authentication Quick Start
In this example we'll be using the `universal-auth` method to login to obtain an Infisical access token, which we will then use to fetch secrets with.
<Steps>
@ -277,8 +332,8 @@ In this example we'll be using the `universal-auth` method to login to obtain an
```
Now that we've set the `INFISICAL_TOKEN` environment variable, we can use the CLI to interact with Infisical. The CLI will automatically check for the presence of the `INFISICAL_TOKEN` environment variable and use it for authentication.
Alternatively, if you would rather use the `--token` flag to pass the token directly, you can do so by running the following command:
```bash
@ -297,6 +352,7 @@ In this example we'll be using the `universal-auth` method to login to obtain an
The `--recursive`, and `--env` flag is optional and will fetch all secrets in subfolders. The default environment is `dev` if no `--env` flag is provided.
</Info>
</Step>
</Steps>
And that's it! Now you're ready to start using the Infisical CLI to interact with your secrets, with the use of Machine Identities.

Binary file not shown.

After

(image error) Size: 67 KiB

Binary file not shown.

After

(image error) Size: 330 KiB

@ -122,6 +122,7 @@ spec:
# Azure Auth
azureAuth:
identityId: <your-machine-identity-id>
resource: https://management.azure.com/&client_id=CLIENT_ID # (Optional) This is the Azure resource that you want to access. For example, "https://management.azure.com/". If no value is provided, it will default to "https://management.azure.com/"
# secretsScope is identical to the secrets scope in the universalAuth field in this sample.
secretsScope:

@ -217,7 +217,14 @@
"pages": [
"self-hosting/overview",
{
"group": "Installation methods",
"group": "Native installation methods",
"pages": [
"self-hosting/deployment-options/native/standalone-binary",
"self-hosting/deployment-options/native/high-availability"
]
},
{
"group": "Containerized installation methods",
"pages": [
"self-hosting/deployment-options/standalone-infisical",
"self-hosting/deployment-options/docker-swarm",
@ -576,6 +583,7 @@
"group": "Folders",
"pages": [
"api-reference/endpoints/folders/list",
"api-reference/endpoints/folders/get-by-id",
"api-reference/endpoints/folders/create",
"api-reference/endpoints/folders/update",
"api-reference/endpoints/folders/delete"

@ -0,0 +1,520 @@
---
title: "Automatically deploy Infisical with High Availability"
sidebarTitle: "High Availability"
---
# Self-Hosting Infisical with a native High Availability (HA) deployment
This page describes the Infisical architecture designed to provide high availability (HA) and how to deploy Infisical with high availability. The high availability deployment is designed to ensure that Infisical services are always available and can handle service failures gracefully, without causing service disruptions.
<Info>
This deployment option is currently only available for Debian-based nodes (e.g., Ubuntu, Debian).
We plan on adding support for other operating systems in the future.
</Info>
## High availability architecture
| Service | Nodes | Configuration | GCP | AWS |
|----------------------------------|----------------|------------------------------|---------------|--------------|
| External load balancer$^1$ | 1 | 4 vCPU, 3.6 GB memory | n1-highcpu-4 | c5n.xlarge |
| Internal load balancer$^2$ | 1 | 4 vCPU, 3.6 GB memory | n1-highcpu-4 | c5n.xlarge |
| Etcd cluster$^3$ | 3 | 4 vCPU, 3.6 GB memory | n1-highcpu-4 | c5n.xlarge |
| PostgreSQL$^4$ | 3 | 2 vCPU, 7.5 GB memory | n1-standard-2 | m5.large |
| Sentinel$^4$ | 3 | 2 vCPU, 7.5 GB memory | n1-standard-2 | m5.large |
| Redis$^4$ | 3 | 2 vCPU, 7.5 GB memory | n1-standard-2 | m5.large |
| Infisical Core | 3 | 8 vCPU, 7.2 GB memory | n1-highcpu-8 | c5.2xlarge |
**Footnotes:**
1. External load balancer: If you wish to have multiple instances of the internal load balancer, you will need to use an external load balancer to distribute incoming traffic across multiple internal load balancers.
Using multiple internal load balancers is recommended for high-traffic environments. In the following guide we will use a single internal load balancer, as external load balancing falls outside the scope of this guide.
2. Internal load balancer: The internal load balancer (a HAProxy instance) is used to distribute incoming traffic across multiple Infisical Core instances, Postgres nodes, and Redis nodes. The internal load balancer exposes a set of ports _(80 for Infiscial, 5000 for Read/Write postgres, 5001 for Read-only postgres, and 6379 for Redis)_. Where these ports route to is determained by the internal load balancer based on the availability and health of the service nodes.
The internal load balancer is only accessible from within the same network, and is not exposed to the public internet.
3. Etcd cluster: Etcd is a distributed key-value store used to store and distribute data between the PostgreSQL nodes. Etcd is dependent on high disk I/O performance, therefore it is highly recommended to use highly performant SSD disks for the Etcd nodes, with _at least_ 80GB of disk space.
4. The Redis and PostgreSQL nodes will automatically be configured for high availability and used in your Infisical Core instances. However, you can optionally choose to bring your own database (BYOD), and skip these nodes. See more on how to [provide your own databases](#provide-your-own-databases).
<Info>
For all services that require multiple nodes, it is recommended to deploy them across multiple availability zones (AZs) to ensure high availability and fault tolerance. This will help prevent service disruptions in the event of an AZ failure.
</Info>
![High availability stack](../../images/self-hosting/deployment-options/native/ha-stack.png)
The image above shows how a high availability deployment of Infisical is structured. In this example, an external load balancer is used to distribute incoming traffic across multiple internal load balancers. The internal load balancers. The external load balancer isn't required, and it will require additional configuration to set up.
### Fault Tolerance
This setup provides N+1 redundancy, meaning it can tolerate the failure of any single node without service interruption.
## Ansible
### What is Ansible
Ansible is an open-source automation tool that simplifies application deployment, configuration management, and task automation.
At Infisical, we use Ansible to automate the deployment of Infisical services. The Ansible roles are designed to make it easy to deploy Infisical services in a high availability environment.
### Installing Ansible
<Steps>
<Step title="Install using the pipx Python package manager">
```bash
pipx install --include-deps ansible
```
</Step>
<Step title="Verify the installation">
```bash
ansible --version
```
</Step>
</Steps>
### Understanding Ansible Concepts
* Inventory _(inventory.ini)_: A file that lists your target hosts.
* Playbook _(playbook.yml)_: YAML file containing a set of tasks to be executed on hosts.
* Roles: Reusable units of organization for playbooks. Roles are used to group tasks together in a structured and reusable manner.
### Basic Ansible Commands
Running a playbook with with an invetory file:
```bash
ansible-playbook -i inventory.ini playbook.yml
```
This is how you would run the playbook containing the roles for setting up Infisical in a high availability environment.
### Installing the Infisical High Availability Deployment Ansible Role
The Infisical Ansible role is available on Ansible Galaxy. You can install the role by running the following command:
```bash
ansible-galaxy collection install infisical.infisical_core_ha_deployment
```
## Set up components
1. External load balancer (optional, and not covered in this guide)
2. [Configure Etcd cluster](#configure-etcd-cluster)
3. [Configure PostgreSQL database](#configure-postgresql-database)
4. [Configure Redis/Sentinel](#configure-redis-and-sentinel)
5. [Configure Infisical Core](#configure-infisical-core)
The servers start on the same 52.1.0.0/24 private network range, and can connect to each other freely on these addresses.
The following list includes descriptions of each server and its assigned IP:
52.1.0.1: External Load Balancer
52.1.0.2: Internal Load Balancer
52.1.0.3: Etcd 1
52.1.0.4: Etcd 2
52.1.0.5: Etcd 3
52.1.0.6: PostgreSQL 1
52.1.0.7: PostgreSQL 2
52.1.0.8: PostgreSQL 3
52.1.0.9: Redis 1
52.1.0.10: Redis 2
52.1.0.11: Redis 3
52.1.0.12: Sentinel 1
52.1.0.13: Sentinel 2
52.1.0.14: Sentinel 3
52.1.0.15: Infisical Core 1
52.1.0.16: Infisical Core 2
52.1.0.17: Infisical Core 3
### Configure Etcd cluster
Configuring the ETCD cluster is the first step in setting up a high availability deployment of Infisical.
The ETCD cluster is used to store and distribute data between the PostgreSQL nodes. The ETCD cluster is a distributed key-value store that is highly available and fault-tolerant.
```yaml example.playbook.yml
- hosts: all
gather_facts: true
- name: Set up etcd cluster
hosts: etcd
become: true
collections:
- infisical.infisical_core_ha_deployment
roles:
- role: etcd
```
```ini example.inventory.ini
[etcd]
etcd1 ansible_host=52.1.0.3
etcd2 ansible_host=52.1.0.4
etcd3 ansible_host=52.1.0.5
[etcd:vars]
ansible_user=ubuntu
ansible_ssh_private_key_file=./ssh-key.pem
ansible_ssh_common_args='-o StrictHostKeyChecking=no'
```
### Configure PostgreSQL database
The Postgres role takes a set of parameters that are used to configure your PostgreSQL database.
Make sure to set the following variables in your playbook.yml file:
- `postgres_super_user_password`: The password for the 'postgres' database user.
- `postgres_db_name`: The name of the database that will be created on the leader node and replicated to the secondary nodes.
- `postgres_user`: The name of the user that will be created on the leader node and replicated to the secondary nodes.
- `postgres_user_password`: The password for the user that will be created on the leader node and replicated to the secondary nodes.
- `etcd_hosts`: The list of etcd hosts that the PostgreSQL nodes will use to communicate with etcd. By default you want to keep this value set to `"{{ groups['etcd'] }}"`
```yaml example.playbook.yml
- hosts: all
gather_facts: true
- name: Set up PostgreSQL with Patroni
hosts: postgres
become: true
collections:
- infisical.infisical_core_ha_deployment
roles:
- role: postgres
vars:
postgres_super_user_password: "your-super-user-password"
postgres_user: infisical-user
postgres_user_password: "your-password"
postgres_db_name: infisical-db
etcd_hosts: "{{ groups['etcd'] }}"
```
```ini example.inventory.ini
[postgres]
postgres1 ansible_host=52.1.0.6
postgres2 ansible_host=52.1.0.7
postgres3 ansible_host=52.1.0.8
```
### Configure Redis and Sentinel
The Redis role takes a single variable as input, which is the redis password.
The Sentinel and Redis hosts will run the same role, therefore we are running the task for both the sentinel and redis hosts, `hosts: redis:sentinel`.
- `redis_password`: The password that will be set for the Redis instance.
```yaml example.playbook.yml
- hosts: all
gather_facts: true
- name: Setup Redis and Sentinel
hosts: redis:sentinel
become: true
collections:
- infisical.infisical_core_ha_deployment
roles:
- role: redis
vars:
redis_password: "REDIS_PASSWORD"
```
```ini example.inventory.ini
[redis]
redis1 ansible_host=52.1.0.9
redis2 ansible_host=52.1.0.10
redis3 ansible_host=52.1.0.11
[sentinel]
sentinel1 ansible_host=52.1.0.12
sentinel2 ansible_host=52.1.0.13
sentinel3 ansible_host=52.1.0.14
```
### Configure Internal Load Balancer
The internal load balancer used is HAProxy. HAProxy will expose a set of ports as listed below. Each port will route to a different service based on the availability and health of the service nodes.
- Port 80: Infisical Core
- Port 5000: Read/Write PostgreSQL
- Port 5001: Read-only PostgreSQL
- Port 6379: Redis
- Port 7000: HAProxy monitoring
These ports will need to be exposed on your network to become accessible from the outside world.
The HAProxy configuration file is generated by the Infisical Core role, and is located at `/etc/haproxy/haproxy.cfg` on your internal load balancer node.
The HAProxy setup comes with a monitoring panel. You have to set the username/password combination for the monitoring panel by setting the `stats_user` and `stats_password` variables in the HAProxy role.
Once the HAProxy role has fully executed, you can monitor your HA setup by navigating to `http://52.1.0.2:7000/haproxy?stats` in your browser.
```ini example.inventory.ini
[haproxy]
internal_lb ansible_host=52.1.0.2
```
```yaml example.playbook.yml
- name: Set up HAProxy
hosts: haproxy
become: true
collections:
- infisical.infisical_core_ha_deployment
roles:
- role: haproxy
vars:
stats_user: "stats-username"
stats_password: "stats-password!"
postgres_servers: "{{ groups['postgres'] }}"
infisical_servers: "{{ groups['infisical'] }}"
redis_servers: "{{ groups['redis'] }}"
```
### Configure Infisical Core
The Infisical Core role will set up your actual Infisical instances.
The `env_vars` variable is used to set the environment variables that Infisical will use. The minimum required environment variables are `ENCRYPTION_KEY` and `AUTH_SECRET`. You can find a list of all available environment variables [here](/docs/self-hosting/configuration/envars#general-platform).
The `DB_CONNECTION_URI` and `REDIS_URL` variables will automatically be set if you're running the full playbook. However, you can choose to set them yourself, and skip the Postgres, etcd, redis/sentinel roles entirely.
<Info>
If you later need to add new environment varibles to your Infisical deployments, it's important you add the variables to **all** your Infisical nodes.<br/>
You can find the environment file for Infisical at `/etc/infisical/environment`.<br/>
After editing the environment file, you need to reload the Infisical service by doing `systemctl restart infisical`.
</Info>
```yaml example.playbook.yml
- hosts: all
gather_facts: true
- name: Setup Infisical
hosts: infisical
become: true
collections:
- infisical.infisical_core_ha_deployment
roles:
- role: infisical
env_vars:
ENCRYPTION_KEY: "YOUR_ENCRYPTION_KEY" # openssl rand -hex 16
AUTH_SECRET: "YOUR_AUTH_SECRET" # openssl rand -base64 32
```
```ini example.inventory.ini
[infisical]
infisical1 ansible_host=52.1.0.15
infisical2 ansible_host=52.1.0.16
infisical3 ansible_host=52.1.0.17
```
## Provide your own databases
Bringing your own database is an option using the Infisical Core deployment role.
By bringing your own database, you're able to skip the Etcd, Postgres, and Redis/Sentinel roles entirely.
To bring your own database, you need to set the `DB_CONNECTION_URI` and `REDIS_URL` environment variables in the Infisical Core role.
```yaml example.playbook.yml
- hosts: all
gather_facts: true
- name: Setup Infisical
hosts: infisical
become: true
collections:
- infisical.infisical_core_ha_deployment
roles:
- role: infisical
env_vars:
ENCRYPTION_KEY: "YOUR_ENCRYPTION_KEY" # openssl rand -hex 16
AUTH_SECRET: "YOUR_AUTH_SECRET" # openssl rand -base64 32
DB_CONNECTION_URI: "postgres://user:password@localhost:5432/infisical"
REDIS_URL: "redis://localhost:6379"
```
```ini example.inventory.ini
[infisical]
infisical1 ansible_host=52.1.0.15
infisical2 ansible_host=52.1.0.16
infisical3 ansible_host=52.1.0.17
```
## Full deployment example
To make it easier to get started, we've provided a full deployment example that you can use to deploy Infisical in a high availability environment.
- This deployment does not use an external load balancer.
- You **must** change the environment variables defined in the `playbook.yml` example.
- You have update the IP addresses in the `inventory.ini` file to match your own network configuration.
- You need to set the SSH key and ssh user in the `inventory.ini` file.
<Steps>
<Step title="Install Ansible">
Install Ansible using the pipx Python package manager.
```bash
pipx install --include-deps ansible
```
</Step>
<Step title="Install the Infisical deployment Ansible Role">
Install the Infisical deployment role from Ansible Galaxy.
```bash
ansible-galaxy collection install infisical.infisical_core_ha_deployment
```
</Step>
<Step title="Setup your hosts">
Create an `inventory.ini` file, and define your hosts and their IP addresses. You can use the example below as a template, and update the IP addresses to match your own network configuration.
Make sure to set the SSH key and ssh user in the `inventory.ini` file. Please see the example below.
```ini example.inventory.ini
[etcd]
etcd1 ansible_host=52.1.0.3
etcd2 ansible_host=52.1.0.4
etcd3 ansible_host=52.1.0.5
[postgres]
postgres1 ansible_host=52.1.0.6
postgres2 ansible_host=52.1.0.7
postgres3 ansible_host=52.1.0.8
[infisical]
infisical1 ansible_host=52.1.0.15
infisical2 ansible_host=52.1.0.16
infisical3 ansible_host=52.1.0.17
[redis]
redis1 ansible_host=52.1.0.9
redis2 ansible_host=52.1.0.10
redis3 ansible_host=52.1.0.11
[sentinel]
sentinel1 ansible_host=52.1.0.12
sentinel2 ansible_host=52.1.0.13
sentinel3 ansible_host=52.1.0.14
[haproxy]
internal_lb ansible_host=52.1.0.2
; This can be defined individually for each host, or globally for all hosts.
; In this case the credentials are the same for all hosts, so we define them globally as seen below ([all:vars]).
[all:vars]
ansible_user=ubuntu
ansible_ssh_private_key_file=./your-ssh-key.pem
ansible_ssh_common_args='-o StrictHostKeyChecking=no'
```
</Step>
<Step title="Setup your Ansible playbook">
The Ansible playbook is where you define which roles/tasks to execute on which hosts.
```yaml example.playbook.yml
---
# Important, we must gather facts from all hosts prior to running the roles to ensure we have all the information we need.
- hosts: all
gather_facts: true
- name: Set up etcd cluster
hosts: etcd
become: true
collections:
- infisical.infisical_core_ha_deployment
roles:
- role: etcd
- name: Set up PostgreSQL with Patroni
hosts: postgres
become: true
collections:
- infisical.infisical_core_ha_deployment
roles:
- role: postgres
vars:
postgres_super_user_password: "<ENTER_SUPERUSER_PASSWORD>" # Password for the 'postgres' database user
# A database with these credentials will be created on the leader node, and replicated to the secondary nodes.
postgres_db_name: <ENTER_DB_NAME>
postgres_user: <ENTER_DB_USER>
postgres_user_password: <ENTER_DB_USER_PASSWORD>
etcd_hosts: "{{ groups['etcd'] }}"
- name: Setup Redis and Sentinel
hosts: redis:sentinel
become: true
collections:
- infisical.infisical_core_ha_deployment
roles:
- role: redis
vars:
redis_password: "<ENTER_REDIS_PASSWORD>"
- name: Set up HAProxy
hosts: haproxy
become: true
collections:
- infisical.infisical_core_ha_deployment
roles:
- role: haproxy
vars:
stats_user: "<ENTER_HAPROXY_STATS_USERNAME>"
stats_password: "<ENTER_HAPROXY_STATS_PASSWORD>"
postgres_servers: "{{ groups['postgres'] }}"
infisical_servers: "{{ groups['infisical'] }}"
redis_servers: "{{ groups['redis'] }}"
- name: Setup Infisical
hosts: infisical
become: true
collections:
- infisical.infisical_core_ha_deployment
roles:
- role: infisical
env_vars:
ENCRYPTION_KEY: "YOUR_ENCRYPTION_KEY" # openssl rand -hex 16
AUTH_SECRET: "YOUR_AUTH_SECRET" # openssl rand -base64 32
```
</Step>
<Step title="Run the Ansible playbook">
After creating the `playbook.yml` and `inventory.ini` files, you can run the playbook using the following command
```bash
ansible-playbook -i inventory.ini playbook.yml
```
This step may take upwards of 10 minutes to complete, depending on the number of nodes and the network speed.
Once the playbook has completed, you should have a fully deployed high availability Infisical environment.
To access Infisical, you can try navigating to `http://52.1.0.2`, in order to view your newly deployed Infisical instance.
</Step>
</Steps>
## Post-deployment steps
After deploying Infisical in a high availability environment, you should perform the following post-deployment steps:
- Check your deployment to ensure that all services are running as expected. You can use the HAProxy monitoring panel to check the status of your services (http://52.1.0.2:7000/haproxy?stats)
- Attempt to access the Infisical Core instances to ensure that they are accessible from the internal load balancer. (http://52.1.0.2)
A HAProxy stats page indicating success will look like the image below
![HAProxy stats page](../../images/self-hosting/deployment-options/native/haproxy-stats.png)
## Security Considerations
### Network Security
Secure the network that your instances run on. While this falls outside the scope of Infisical deployment, it's crucial for overall security.
AWS-specific recommendations:
Use Virtual Private Cloud (VPC) to isolate your infrastructure.
Configure security groups to restrict inbound and outbound traffic.
Use Network Access Control Lists (NACLs) for additional network-level security.
<Note>
Please take note that the Infisical team cannot provide infrastructure support for **free self-hosted** deployments.<br/>If you need help with infrastructure, we recommend upgrading to a [paid plan](https://infisical.com/pricing) which includes infrastructure support.
You can also join our community [Slack](https://infisical.com/slack) for help and support from the community.
</Note>
### Troubleshooting
<Accordion title="Ansible: Failed to set permissions on the temporary files Ansible needs to create when becoming an unprivileged user">
If you encounter this issue, please update your ansible config (`ansible.cfg`) file with the following configuration:
```ini
[defaults]
allow_world_readable_tmpfiles = true
```
You can read more about the solution [here](https://docs.ansible.com/ansible/latest/collections/ansible/builtin/sh_shell.html#parameter-world_readable_temp)
</Accordion>
<Accordion title="I'm unable to connect to access the Infisical instance on the web">
This issue can be caused by a number of reasons, mostly realted to the network configuration. Here are a few things you can check:
1. Ensure that the firewall is not blocking the connection. You can check this by running `ufw status`. Ensure that port 80 is open.
2. If you're using a cloud provider like AWS or GCP, ensure that the security group allows traffic on port 80.
3. Ensure that the HAProxy service is running. You can check this by running `systemctl status haproxy`.
4. Ensure that the Infisical service is running. You can check this by running `systemctl status infisical`.
</Accordion>

@ -0,0 +1,203 @@
---
title: "Standalone"
description: "Learn how to deploy Infisical in a standalone environment."
---
# Self-Hosting Infisical with Standalone Infisical
Deploying Infisical in a standalone environment is a great way to get started with Infisical without having to use containers. This guide will walk you through the process of deploying Infisical in a standalone environment.
This is one of the easiest ways to deploy Infisical. It is a single executable, currently only supported on Debian-based systems.
The standalone deployment implements the "bring your own database" (BYOD) approach. This means that you will need to provide your own databases (specifically Postgres and Redis) for the Infisical services to use. The standalone deployment does not include any databases.
If you wish to streamline the deployment process, we recommend using the Ansible role for Infisical. The Ansible role automates the deployment process and includes the databases:
- [Automated Deployment](https://google.com)
- [Automated Deployment with high availability (HA)](https://google.com)
## Prerequisites
- A server running a Debian-based operating system (e.g., Ubuntu, Debian)
- A Postgres database
- A Redis database
## Installing Infisical
Installing Infisical is as simple as running a single command. You can install Infisical by running the following command:
```bash
$ curl -1sLf 'https://dl.cloudsmith.io/public/infisical/infisical-core/cfg/setup/bash.deb.sh' | sudo bash && sudo apt-get install -y infisical-core
```
## Running Infisical
Running Infisical and serving it to the web has a few steps. Below are the steps to get you started with running Infisical in a standalone environment.
* Setup environment variables
* Running Postgres migrations
* Create system daemon
* Exposing Infisical to the internet
<Steps>
<Step title="Setup environment variables">
To use Infisical you'll need to configure the environment variables beforehand. You can acheive this by creating an environment file to be used by Infisical.
#### Create environment file
```bash
$ mkdir -p /etc/infisical && touch /etc/infisical/environment
```
After creating the environment file, you'll need to fill it out with your environment variables.
#### Edit environment file
```bash
$ nano /etc/infisical/environment
```
```bash
DB_CONNECTION_URI=postgres://user:password@localhost:5432/infisical # Replace with your Postgres database connection URI
REDIS_URL=redis://localhost:6379 # Replace with your Redis connection URI
ENCRYPTION_KEY=your_encryption_key # Replace with your encryption key (can be generated with: openssl rand -hex 16)
AUTH_SECRET=your_auth_secret # Replace with your auth secret (can be generated with: openssl rand -base64 32)
```
<Info>
The minimum required environment variables are `DB_CONNECTION_URI`, `REDIS_URL`, `ENCRYPTION_KEY`, and `AUTH_SECRET`. We recommend You take a look at our [list of all available environment variables](/docs/self-hosting/configuration/envars#general-platform), and configure the ones you need.
</Info>
</Step>
<Step title="Running Postgres migrations">
Assuming you're starting with a fresh Postgres database, you'll need to run the Postgres migrations to syncronize the database schema.
The migration command will use the environment variables you configured in the previous step.
```bash
$ eval $(cat /etc/infisical/environment) infisical-core migration:latest
```
<Info>
This step will need to be repeated if you update Infisical in the future.
</Info>
</Step>
<Step title="Create service file">
```bash
$ nano /etc/systemd/system/infisical.service
```
</Step>
<Step title="Create Infisical service">
Create a systemd service file for Infisical. Creating a systemd service file will allow Infisical to start automatically when the system boots or in case of a crash.
```bash
$ nano /etc/systemd/system/infisical.service
```
```ini
[Unit]
Description=Infisical Service
After=network.target
[Service]
# The path to the environment file we created in the previous step
EnvironmentFile=/etc/infisical/environment
Type=simple
# Change the user to the user you want to run Infisical as
User=root
ExecStart=/usr/local/bin/infisical-core
Restart=always
RestartSec=30
[Install]
WantedBy=multi-user.target
```
Now we need to reload the systemd daemon and start the Infisical service.
```bash
$ systemctl daemon-reload
$ systemctl start infisical
$ systemctl enable infisical
```
<Info>
You can check the status of the Infisical service by running `systemctl status infisical`.
It is also a good idea to check the logs for any errors by running `journalctl --no-pager -u infisical`.
</Info>
</Step>
<Step title="Exposing Infisical to the internet">
Exposing Infisical to the internet requires setting up a reverse proxy. You can use any reverse proxy of your choice, but we recommend using HAProxy or Nginx. Below is an example of how to set up a reverse proxy using HAProxy.
#### Install HAProxy
```bash
$ apt-get install -y haproxy
```
#### Edit HAProxy configuration
```bash
$ nano /etc/haproxy/haproxy.cfg
```
```ini
global
log /dev/log local0
log /dev/log local1 notice
chroot /var/lib/haproxy
stats socket /run/haproxy/admin.sock mode 660 level admin expose-fd listeners
stats timeout 30s
user haproxy
group haproxy
daemon
defaults
log global
mode http
option httplog
option dontlognull
timeout connect 5000
timeout client 50000
timeout server 50000
frontend http-in
bind *:80
default_backend infisical
backend infisical
server infisicalapp 127.0.0.1:8080 check
```
<Warning>
If you decide to use Nginx, then please be aware that the configuration will be different. **Infisical listens on port 8080**.
</Warning>
#### Restart HAProxy
```bash
$ systemctl restart haproxy
```
</Step>
</Steps>
And that's it! You have successfully deployed Infisical in a standalone environment. You can now access Infisical by visiting `http://your-server-ip`.
<Note>
Please take note that the Infisical team cannot provide infrastructure support for **free self-hosted** deployments.<br/>If you need help with infrastructure, we recommend upgrading to a [paid plan](https://infisical.com/pricing) which includes infrastructure support.
You can also join our community [Slack](https://infisical.com/slack) for help and support from the community.
</Note>
## Troubleshooting
<Accordion title="I'm getting a error related to the HAProxy (Missing LF on last line, file might have been truncated at position X)">
This is a common issue related to the HAProxy configuration file. The error is caused by the missing newline character at the end of the file. You can fix this by adding a newline character at the end of the file.
```bash
$ echo "" >> /etc/haproxy/haproxy.cfg
```
</Accordion>
<Accordion title="I'm unable to connect to access the Infisical instance on the web">
This issue can be caused by a number of reasons, mostly realted to the network configuration. Here are a few things you can check:
1. Ensure that the firewall is not blocking the connection. You can check this by running `ufw status`. Ensure that port 80 is open.
2. If you're using a cloud provider like AWS or GCP, ensure that the security group allows traffic on port 80.
3. Ensure that the HAProxy service is running. You can check this by running `systemctl status haproxy`.
4. Ensure that the Infisical service is running. You can check this by running `systemctl status infisical`.
</Accordion>

@ -15,15 +15,30 @@ This guide walks through how you can use these paid features on a self hosted in
</Step>
<Step title="Activate the license">
Depending on whether or not the environment where Infisical is deployed has internet access, you may be issued a regular license or an offline license.
- If using a regular license, you should set the value of the environment variable `LICENSE_KEY` in Infisical to the issued license key.
- If using an offline license, you should set the value of the environment variable `LICENSE_KEY_OFFLINE` in Infisical to the issued license key.
<Note>
How you set the environment variable will depend on the deployment method you used. Please refer to the documentation of your deployment method for specific instructions.
</Note>
<Tabs>
<Tab title="Regular License">
- Assign the issued license key to the `LICENSE_KEY` environment variable in your Infisical instance.
- Your Infisical instance will need to communicate with the Infisical license server to validate the license key.
If you want to limit outgoing connections only to the Infisical license server, you can use the following IP addresses: `13.248.249.247` and `35.71.190.59`
<Note>
Ensure that your firewall or network settings allow outbound connections to these IP addresses to avoid any issues with license validation.
</Note>
</Tab>
<Tab title="Offline License">
- Assign the issued license key to the `LICENSE_KEY_OFFLINE` environment variable in your Infisical instance.
<Note>
How you set the environment variable will depend on the deployment method you used. Please refer to the documentation of your deployment method for specific instructions.
</Note>
</Tab>
</Tabs>
Once your instance starts up, the license key will be validated and youll be able to use the paid features.
However, when the license expires, Infisical will continue to run, but EE features will be disabled until the license is renewed or a new one is purchased.
</Step>
</Steps>

@ -33,3 +33,21 @@ Choose from a number of deployment options listed below to get started.
Use our Helm chart to Install Infisical on your Kubernetes cluster.
</Card>
</CardGroup>
<CardGroup cols={2}>
<Card
title="Native Deployment"
color="#000000"
icon="box"
href="deployment-options/native/standalone-binary"
>
Install Infisical on your Debian-based system without containers using our standalone binary.
</Card>
<Card
title="Native Deployment, High Availability"
color="#000000"
icon="boxes-stacked"
href="deployment-options/native/high-availability"
>
Install Infisical on your Debian-based instances without containers using our standalone binary with high availability out of the box.
</Card>
</CardGroup>

@ -47,7 +47,7 @@
"@ucast/mongo2js": "^1.3.4",
"add": "^2.0.6",
"argon2-browser": "^1.18.0",
"axios": "^0.27.2",
"axios": "^0.28.0",
"axios-auth-refresh": "^3.3.6",
"base64-loader": "^1.0.0",
"classnames": "^2.3.1",
@ -136,10 +136,10 @@
"eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-simple-import-sort": "^8.0.0",
"eslint-plugin-storybook": "^0.6.12",
"postcss": "^8.4.14",
"postcss": "^8.4.39",
"prettier": "^2.8.3",
"prettier-plugin-tailwindcss": "^0.2.2",
"storybook": "^7.5.2",
"storybook": "^7.6.20",
"storybook-dark-mode": "^3.0.0",
"tailwindcss": "3.2",
"typescript": "^4.9.3"
@ -6200,15 +6200,15 @@
}
},
"node_modules/@storybook/builder-manager": {
"version": "7.6.19",
"resolved": "https://registry.npmjs.org/@storybook/builder-manager/-/builder-manager-7.6.19.tgz",
"integrity": "sha512-Dt5OLh97xeWh4h2mk9uG0SbCxBKHPhIiHLHAKEIDzIZBdwUhuyncVNDPHW2NlXM+S7U0/iKs2tw05waqh2lHvg==",
"version": "7.6.20",
"resolved": "https://registry.npmjs.org/@storybook/builder-manager/-/builder-manager-7.6.20.tgz",
"integrity": "sha512-e2GzpjLaw6CM/XSmc4qJRzBF8GOoOyotyu3JrSPTYOt4RD8kjUsK4QlismQM1DQRu8i39aIexxmRbiJyD74xzQ==",
"dev": true,
"dependencies": {
"@fal-works/esbuild-plugin-global-externals": "^2.1.2",
"@storybook/core-common": "7.6.19",
"@storybook/manager": "7.6.19",
"@storybook/node-logger": "7.6.19",
"@storybook/core-common": "7.6.20",
"@storybook/manager": "7.6.20",
"@storybook/node-logger": "7.6.20",
"@types/ejs": "^3.1.1",
"@types/find-cache-dir": "^3.2.1",
"@yarnpkg/esbuild-plugin-pnp": "^3.0.0-rc.10",
@ -6228,13 +6228,13 @@
}
},
"node_modules/@storybook/builder-manager/node_modules/@storybook/channels": {
"version": "7.6.19",
"resolved": "https://registry.npmjs.org/@storybook/channels/-/channels-7.6.19.tgz",
"integrity": "sha512-2JGh+i95GwjtjqWqhtEh15jM5ifwbRGmXeFqkY7dpdHH50EEWafYHr2mg3opK3heVDwg0rJ/VBptkmshloXuvA==",
"version": "7.6.20",
"resolved": "https://registry.npmjs.org/@storybook/channels/-/channels-7.6.20.tgz",
"integrity": "sha512-4hkgPSH6bJclB2OvLnkZOGZW1WptJs09mhQ6j6qLjgBZzL/ZdD6priWSd7iXrmPiN5TzUobkG4P4Dp7FjkiO7A==",
"dev": true,
"dependencies": {
"@storybook/client-logger": "7.6.19",
"@storybook/core-events": "7.6.19",
"@storybook/client-logger": "7.6.20",
"@storybook/core-events": "7.6.20",
"@storybook/global": "^5.0.0",
"qs": "^6.10.0",
"telejson": "^7.2.0",
@ -6246,9 +6246,9 @@
}
},
"node_modules/@storybook/builder-manager/node_modules/@storybook/client-logger": {
"version": "7.6.19",
"resolved": "https://registry.npmjs.org/@storybook/client-logger/-/client-logger-7.6.19.tgz",
"integrity": "sha512-oGzOxbmLmciSIfd5gsxDzPmX8DttWhoYdPKxjMuCuWLTO2TWpkCWp1FTUMWO72mm/6V/FswT/aqpJJBBvdZ3RQ==",
"version": "7.6.20",
"resolved": "https://registry.npmjs.org/@storybook/client-logger/-/client-logger-7.6.20.tgz",
"integrity": "sha512-NwG0VIJQCmKrSaN5GBDFyQgTAHLNishUPLW1NrzqTDNAhfZUoef64rPQlinbopa0H4OXmlB+QxbQIb3ubeXmSQ==",
"dev": true,
"dependencies": {
"@storybook/global": "^5.0.0"
@ -6259,14 +6259,14 @@
}
},
"node_modules/@storybook/builder-manager/node_modules/@storybook/core-common": {
"version": "7.6.19",
"resolved": "https://registry.npmjs.org/@storybook/core-common/-/core-common-7.6.19.tgz",
"integrity": "sha512-njwpGzFJrfbJr/AFxGP8KMrfPfxN85KOfSlxYnQwRm5Z0H1D/lT33LhEBf5m37gaGawHeG7KryxO6RvaioMt2Q==",
"version": "7.6.20",
"resolved": "https://registry.npmjs.org/@storybook/core-common/-/core-common-7.6.20.tgz",
"integrity": "sha512-8H1zPWPjcmeD4HbDm4FDD0WLsfAKGVr566IZ4hG+h3iWVW57II9JW9MLBtiR2LPSd8u7o0kw64lwRGmtCO1qAw==",
"dev": true,
"dependencies": {
"@storybook/core-events": "7.6.19",
"@storybook/node-logger": "7.6.19",
"@storybook/types": "7.6.19",
"@storybook/core-events": "7.6.20",
"@storybook/node-logger": "7.6.20",
"@storybook/types": "7.6.20",
"@types/find-cache-dir": "^3.2.1",
"@types/node": "^18.0.0",
"@types/node-fetch": "^2.6.4",
@ -6294,9 +6294,9 @@
}
},
"node_modules/@storybook/builder-manager/node_modules/@storybook/core-events": {
"version": "7.6.19",
"resolved": "https://registry.npmjs.org/@storybook/core-events/-/core-events-7.6.19.tgz",
"integrity": "sha512-K/W6Uvum0ocZSgjbi8hiotpe+wDEHDZlvN+KlPqdh9ae9xDK8aBNBq9IelCoqM+uKO1Zj+dDfSQds7CD781DJg==",
"version": "7.6.20",
"resolved": "https://registry.npmjs.org/@storybook/core-events/-/core-events-7.6.20.tgz",
"integrity": "sha512-tlVDuVbDiNkvPDFAu+0ou3xBBYbx9zUURQz4G9fAq0ScgBOs/bpzcRrFb4mLpemUViBAd47tfZKdH4MAX45KVQ==",
"dev": true,
"dependencies": {
"ts-dedent": "^2.0.0"
@ -6307,9 +6307,9 @@
}
},
"node_modules/@storybook/builder-manager/node_modules/@storybook/node-logger": {
"version": "7.6.19",
"resolved": "https://registry.npmjs.org/@storybook/node-logger/-/node-logger-7.6.19.tgz",
"integrity": "sha512-2g29QC44Zl1jKY37DmQ0/dO7+VSKnGgPI/x0mwVwQffypSapxH3rwLLT5Q5XLHeFyD+fhRu5w9Cj4vTGynJgpA==",
"version": "7.6.20",
"resolved": "https://registry.npmjs.org/@storybook/node-logger/-/node-logger-7.6.20.tgz",
"integrity": "sha512-l2i4qF1bscJkOplNffcRTsgQWYR7J51ewmizj5YrTM8BK6rslWT1RntgVJWB1RgPqvx6VsCz1gyP3yW1oKxvYw==",
"dev": true,
"funding": {
"type": "opencollective",
@ -6317,12 +6317,12 @@
}
},
"node_modules/@storybook/builder-manager/node_modules/@storybook/types": {
"version": "7.6.19",
"resolved": "https://registry.npmjs.org/@storybook/types/-/types-7.6.19.tgz",
"integrity": "sha512-DeGYrRPRMGTVfT7o2rEZtRzyLT2yKTI2exgpnxbwPWEFAduZCSfzBrcBXZ/nb5B0pjA9tUNWls1YzGkJGlkhpg==",
"version": "7.6.20",
"resolved": "https://registry.npmjs.org/@storybook/types/-/types-7.6.20.tgz",
"integrity": "sha512-GncdY3x0LpbhmUAAJwXYtJDUQEwfF175gsjH0/fxPkxPoV7Sef9TM41jQLJW/5+6TnZoCZP/+aJZTJtq3ni23Q==",
"dev": true,
"dependencies": {
"@storybook/channels": "7.6.19",
"@storybook/channels": "7.6.20",
"@types/babel__core": "^7.0.0",
"@types/express": "^4.7.0",
"file-system-cache": "2.3.0"
@ -6438,23 +6438,23 @@
}
},
"node_modules/@storybook/cli": {
"version": "7.6.19",
"resolved": "https://registry.npmjs.org/@storybook/cli/-/cli-7.6.19.tgz",
"integrity": "sha512-7OVy7nPgkLfgivv6/dmvoyU6pKl9EzWFk+g9izyQHiM/jS8jOiEyn6akG8Ebj6k5pWslo5lgiXUSW+cEEZUnqQ==",
"version": "7.6.20",
"resolved": "https://registry.npmjs.org/@storybook/cli/-/cli-7.6.20.tgz",
"integrity": "sha512-ZlP+BJyqg7HlnXf7ypjG2CKMI/KVOn03jFIiClItE/jQfgR6kRFgtjRU7uajh427HHfjv9DRiur8nBzuO7vapA==",
"dev": true,
"dependencies": {
"@babel/core": "^7.23.2",
"@babel/preset-env": "^7.23.2",
"@babel/types": "^7.23.0",
"@ndelangen/get-tarball": "^3.0.7",
"@storybook/codemod": "7.6.19",
"@storybook/core-common": "7.6.19",
"@storybook/core-events": "7.6.19",
"@storybook/core-server": "7.6.19",
"@storybook/csf-tools": "7.6.19",
"@storybook/node-logger": "7.6.19",
"@storybook/telemetry": "7.6.19",
"@storybook/types": "7.6.19",
"@storybook/codemod": "7.6.20",
"@storybook/core-common": "7.6.20",
"@storybook/core-events": "7.6.20",
"@storybook/core-server": "7.6.20",
"@storybook/csf-tools": "7.6.20",
"@storybook/node-logger": "7.6.20",
"@storybook/telemetry": "7.6.20",
"@storybook/types": "7.6.20",
"@types/semver": "^7.3.4",
"@yarnpkg/fslib": "2.10.3",
"@yarnpkg/libzip": "2.3.0",
@ -6494,13 +6494,13 @@
}
},
"node_modules/@storybook/cli/node_modules/@storybook/channels": {
"version": "7.6.19",
"resolved": "https://registry.npmjs.org/@storybook/channels/-/channels-7.6.19.tgz",
"integrity": "sha512-2JGh+i95GwjtjqWqhtEh15jM5ifwbRGmXeFqkY7dpdHH50EEWafYHr2mg3opK3heVDwg0rJ/VBptkmshloXuvA==",
"version": "7.6.20",
"resolved": "https://registry.npmjs.org/@storybook/channels/-/channels-7.6.20.tgz",
"integrity": "sha512-4hkgPSH6bJclB2OvLnkZOGZW1WptJs09mhQ6j6qLjgBZzL/ZdD6priWSd7iXrmPiN5TzUobkG4P4Dp7FjkiO7A==",
"dev": true,
"dependencies": {
"@storybook/client-logger": "7.6.19",
"@storybook/core-events": "7.6.19",
"@storybook/client-logger": "7.6.20",
"@storybook/core-events": "7.6.20",
"@storybook/global": "^5.0.0",
"qs": "^6.10.0",
"telejson": "^7.2.0",
@ -6512,9 +6512,9 @@
}
},
"node_modules/@storybook/cli/node_modules/@storybook/client-logger": {
"version": "7.6.19",
"resolved": "https://registry.npmjs.org/@storybook/client-logger/-/client-logger-7.6.19.tgz",
"integrity": "sha512-oGzOxbmLmciSIfd5gsxDzPmX8DttWhoYdPKxjMuCuWLTO2TWpkCWp1FTUMWO72mm/6V/FswT/aqpJJBBvdZ3RQ==",
"version": "7.6.20",
"resolved": "https://registry.npmjs.org/@storybook/client-logger/-/client-logger-7.6.20.tgz",
"integrity": "sha512-NwG0VIJQCmKrSaN5GBDFyQgTAHLNishUPLW1NrzqTDNAhfZUoef64rPQlinbopa0H4OXmlB+QxbQIb3ubeXmSQ==",
"dev": true,
"dependencies": {
"@storybook/global": "^5.0.0"
@ -6525,14 +6525,14 @@
}
},
"node_modules/@storybook/cli/node_modules/@storybook/core-common": {
"version": "7.6.19",
"resolved": "https://registry.npmjs.org/@storybook/core-common/-/core-common-7.6.19.tgz",
"integrity": "sha512-njwpGzFJrfbJr/AFxGP8KMrfPfxN85KOfSlxYnQwRm5Z0H1D/lT33LhEBf5m37gaGawHeG7KryxO6RvaioMt2Q==",
"version": "7.6.20",
"resolved": "https://registry.npmjs.org/@storybook/core-common/-/core-common-7.6.20.tgz",
"integrity": "sha512-8H1zPWPjcmeD4HbDm4FDD0WLsfAKGVr566IZ4hG+h3iWVW57II9JW9MLBtiR2LPSd8u7o0kw64lwRGmtCO1qAw==",
"dev": true,
"dependencies": {
"@storybook/core-events": "7.6.19",
"@storybook/node-logger": "7.6.19",
"@storybook/types": "7.6.19",
"@storybook/core-events": "7.6.20",
"@storybook/node-logger": "7.6.20",
"@storybook/types": "7.6.20",
"@types/find-cache-dir": "^3.2.1",
"@types/node": "^18.0.0",
"@types/node-fetch": "^2.6.4",
@ -6560,9 +6560,9 @@
}
},
"node_modules/@storybook/cli/node_modules/@storybook/core-events": {
"version": "7.6.19",
"resolved": "https://registry.npmjs.org/@storybook/core-events/-/core-events-7.6.19.tgz",
"integrity": "sha512-K/W6Uvum0ocZSgjbi8hiotpe+wDEHDZlvN+KlPqdh9ae9xDK8aBNBq9IelCoqM+uKO1Zj+dDfSQds7CD781DJg==",
"version": "7.6.20",
"resolved": "https://registry.npmjs.org/@storybook/core-events/-/core-events-7.6.20.tgz",
"integrity": "sha512-tlVDuVbDiNkvPDFAu+0ou3xBBYbx9zUURQz4G9fAq0ScgBOs/bpzcRrFb4mLpemUViBAd47tfZKdH4MAX45KVQ==",
"dev": true,
"dependencies": {
"ts-dedent": "^2.0.0"
@ -6573,9 +6573,9 @@
}
},
"node_modules/@storybook/cli/node_modules/@storybook/csf-tools": {
"version": "7.6.19",
"resolved": "https://registry.npmjs.org/@storybook/csf-tools/-/csf-tools-7.6.19.tgz",
"integrity": "sha512-8Vzia3cHhDdGHuS3XKXJReCRxmfRq3vmTm/Te9yKZnPSAsC58CCKcMh8FNEFJ44vxYF9itKTkRutjGs+DprKLQ==",
"version": "7.6.20",
"resolved": "https://registry.npmjs.org/@storybook/csf-tools/-/csf-tools-7.6.20.tgz",
"integrity": "sha512-rwcwzCsAYh/m/WYcxBiEtLpIW5OH1ingxNdF/rK9mtGWhJxXRDV8acPkFrF8rtFWIVKoOCXu5USJYmc3f2gdYQ==",
"dev": true,
"dependencies": {
"@babel/generator": "^7.23.0",
@ -6583,7 +6583,7 @@
"@babel/traverse": "^7.23.2",
"@babel/types": "^7.23.0",
"@storybook/csf": "^0.1.2",
"@storybook/types": "7.6.19",
"@storybook/types": "7.6.20",
"fs-extra": "^11.1.0",
"recast": "^0.23.1",
"ts-dedent": "^2.0.0"
@ -6594,9 +6594,9 @@
}
},
"node_modules/@storybook/cli/node_modules/@storybook/node-logger": {
"version": "7.6.19",
"resolved": "https://registry.npmjs.org/@storybook/node-logger/-/node-logger-7.6.19.tgz",
"integrity": "sha512-2g29QC44Zl1jKY37DmQ0/dO7+VSKnGgPI/x0mwVwQffypSapxH3rwLLT5Q5XLHeFyD+fhRu5w9Cj4vTGynJgpA==",
"version": "7.6.20",
"resolved": "https://registry.npmjs.org/@storybook/node-logger/-/node-logger-7.6.20.tgz",
"integrity": "sha512-l2i4qF1bscJkOplNffcRTsgQWYR7J51ewmizj5YrTM8BK6rslWT1RntgVJWB1RgPqvx6VsCz1gyP3yW1oKxvYw==",
"dev": true,
"funding": {
"type": "opencollective",
@ -6604,12 +6604,12 @@
}
},
"node_modules/@storybook/cli/node_modules/@storybook/types": {
"version": "7.6.19",
"resolved": "https://registry.npmjs.org/@storybook/types/-/types-7.6.19.tgz",
"integrity": "sha512-DeGYrRPRMGTVfT7o2rEZtRzyLT2yKTI2exgpnxbwPWEFAduZCSfzBrcBXZ/nb5B0pjA9tUNWls1YzGkJGlkhpg==",
"version": "7.6.20",
"resolved": "https://registry.npmjs.org/@storybook/types/-/types-7.6.20.tgz",
"integrity": "sha512-GncdY3x0LpbhmUAAJwXYtJDUQEwfF175gsjH0/fxPkxPoV7Sef9TM41jQLJW/5+6TnZoCZP/+aJZTJtq3ni23Q==",
"dev": true,
"dependencies": {
"@storybook/channels": "7.6.19",
"@storybook/channels": "7.6.20",
"@types/babel__core": "^7.0.0",
"@types/express": "^4.7.0",
"file-system-cache": "2.3.0"
@ -6703,18 +6703,18 @@
}
},
"node_modules/@storybook/codemod": {
"version": "7.6.19",
"resolved": "https://registry.npmjs.org/@storybook/codemod/-/codemod-7.6.19.tgz",
"integrity": "sha512-bmHE0iEEgWZ65dXCmasd+GreChjPiWkXu2FEa0cJmNz/PqY12GsXGls4ke1TkNTj4gdSZnbtJxbclPZZnib2tQ==",
"version": "7.6.20",
"resolved": "https://registry.npmjs.org/@storybook/codemod/-/codemod-7.6.20.tgz",
"integrity": "sha512-8vmSsksO4XukNw0TmqylPmk7PxnfNfE21YsxFa7mnEBmEKQcZCQsNil4ZgWfG0IzdhTfhglAN4r++Ew0WE+PYA==",
"dev": true,
"dependencies": {
"@babel/core": "^7.23.2",
"@babel/preset-env": "^7.23.2",
"@babel/types": "^7.23.0",
"@storybook/csf": "^0.1.2",
"@storybook/csf-tools": "7.6.19",
"@storybook/node-logger": "7.6.19",
"@storybook/types": "7.6.19",
"@storybook/csf-tools": "7.6.20",
"@storybook/node-logger": "7.6.20",
"@storybook/types": "7.6.20",
"@types/cross-spawn": "^6.0.2",
"cross-spawn": "^7.0.3",
"globby": "^11.0.2",
@ -6729,13 +6729,13 @@
}
},
"node_modules/@storybook/codemod/node_modules/@storybook/channels": {
"version": "7.6.19",
"resolved": "https://registry.npmjs.org/@storybook/channels/-/channels-7.6.19.tgz",
"integrity": "sha512-2JGh+i95GwjtjqWqhtEh15jM5ifwbRGmXeFqkY7dpdHH50EEWafYHr2mg3opK3heVDwg0rJ/VBptkmshloXuvA==",
"version": "7.6.20",
"resolved": "https://registry.npmjs.org/@storybook/channels/-/channels-7.6.20.tgz",
"integrity": "sha512-4hkgPSH6bJclB2OvLnkZOGZW1WptJs09mhQ6j6qLjgBZzL/ZdD6priWSd7iXrmPiN5TzUobkG4P4Dp7FjkiO7A==",
"dev": true,
"dependencies": {
"@storybook/client-logger": "7.6.19",
"@storybook/core-events": "7.6.19",
"@storybook/client-logger": "7.6.20",
"@storybook/core-events": "7.6.20",
"@storybook/global": "^5.0.0",
"qs": "^6.10.0",
"telejson": "^7.2.0",
@ -6747,9 +6747,9 @@
}
},
"node_modules/@storybook/codemod/node_modules/@storybook/client-logger": {
"version": "7.6.19",
"resolved": "https://registry.npmjs.org/@storybook/client-logger/-/client-logger-7.6.19.tgz",
"integrity": "sha512-oGzOxbmLmciSIfd5gsxDzPmX8DttWhoYdPKxjMuCuWLTO2TWpkCWp1FTUMWO72mm/6V/FswT/aqpJJBBvdZ3RQ==",
"version": "7.6.20",
"resolved": "https://registry.npmjs.org/@storybook/client-logger/-/client-logger-7.6.20.tgz",
"integrity": "sha512-NwG0VIJQCmKrSaN5GBDFyQgTAHLNishUPLW1NrzqTDNAhfZUoef64rPQlinbopa0H4OXmlB+QxbQIb3ubeXmSQ==",
"dev": true,
"dependencies": {
"@storybook/global": "^5.0.0"
@ -6760,9 +6760,9 @@
}
},
"node_modules/@storybook/codemod/node_modules/@storybook/core-events": {
"version": "7.6.19",
"resolved": "https://registry.npmjs.org/@storybook/core-events/-/core-events-7.6.19.tgz",
"integrity": "sha512-K/W6Uvum0ocZSgjbi8hiotpe+wDEHDZlvN+KlPqdh9ae9xDK8aBNBq9IelCoqM+uKO1Zj+dDfSQds7CD781DJg==",
"version": "7.6.20",
"resolved": "https://registry.npmjs.org/@storybook/core-events/-/core-events-7.6.20.tgz",
"integrity": "sha512-tlVDuVbDiNkvPDFAu+0ou3xBBYbx9zUURQz4G9fAq0ScgBOs/bpzcRrFb4mLpemUViBAd47tfZKdH4MAX45KVQ==",
"dev": true,
"dependencies": {
"ts-dedent": "^2.0.0"
@ -6773,9 +6773,9 @@
}
},
"node_modules/@storybook/codemod/node_modules/@storybook/csf-tools": {
"version": "7.6.19",
"resolved": "https://registry.npmjs.org/@storybook/csf-tools/-/csf-tools-7.6.19.tgz",
"integrity": "sha512-8Vzia3cHhDdGHuS3XKXJReCRxmfRq3vmTm/Te9yKZnPSAsC58CCKcMh8FNEFJ44vxYF9itKTkRutjGs+DprKLQ==",
"version": "7.6.20",
"resolved": "https://registry.npmjs.org/@storybook/csf-tools/-/csf-tools-7.6.20.tgz",
"integrity": "sha512-rwcwzCsAYh/m/WYcxBiEtLpIW5OH1ingxNdF/rK9mtGWhJxXRDV8acPkFrF8rtFWIVKoOCXu5USJYmc3f2gdYQ==",
"dev": true,
"dependencies": {
"@babel/generator": "^7.23.0",
@ -6783,7 +6783,7 @@
"@babel/traverse": "^7.23.2",
"@babel/types": "^7.23.0",
"@storybook/csf": "^0.1.2",
"@storybook/types": "7.6.19",
"@storybook/types": "7.6.20",
"fs-extra": "^11.1.0",
"recast": "^0.23.1",
"ts-dedent": "^2.0.0"
@ -6794,9 +6794,9 @@
}
},
"node_modules/@storybook/codemod/node_modules/@storybook/node-logger": {
"version": "7.6.19",
"resolved": "https://registry.npmjs.org/@storybook/node-logger/-/node-logger-7.6.19.tgz",
"integrity": "sha512-2g29QC44Zl1jKY37DmQ0/dO7+VSKnGgPI/x0mwVwQffypSapxH3rwLLT5Q5XLHeFyD+fhRu5w9Cj4vTGynJgpA==",
"version": "7.6.20",
"resolved": "https://registry.npmjs.org/@storybook/node-logger/-/node-logger-7.6.20.tgz",
"integrity": "sha512-l2i4qF1bscJkOplNffcRTsgQWYR7J51ewmizj5YrTM8BK6rslWT1RntgVJWB1RgPqvx6VsCz1gyP3yW1oKxvYw==",
"dev": true,
"funding": {
"type": "opencollective",
@ -6804,12 +6804,12 @@
}
},
"node_modules/@storybook/codemod/node_modules/@storybook/types": {
"version": "7.6.19",
"resolved": "https://registry.npmjs.org/@storybook/types/-/types-7.6.19.tgz",
"integrity": "sha512-DeGYrRPRMGTVfT7o2rEZtRzyLT2yKTI2exgpnxbwPWEFAduZCSfzBrcBXZ/nb5B0pjA9tUNWls1YzGkJGlkhpg==",
"version": "7.6.20",
"resolved": "https://registry.npmjs.org/@storybook/types/-/types-7.6.20.tgz",
"integrity": "sha512-GncdY3x0LpbhmUAAJwXYtJDUQEwfF175gsjH0/fxPkxPoV7Sef9TM41jQLJW/5+6TnZoCZP/+aJZTJtq3ni23Q==",
"dev": true,
"dependencies": {
"@storybook/channels": "7.6.19",
"@storybook/channels": "7.6.20",
"@types/babel__core": "^7.0.0",
"@types/express": "^4.7.0",
"file-system-cache": "2.3.0"
@ -7063,26 +7063,26 @@
}
},
"node_modules/@storybook/core-server": {
"version": "7.6.19",
"resolved": "https://registry.npmjs.org/@storybook/core-server/-/core-server-7.6.19.tgz",
"integrity": "sha512-7mKL73Wv5R2bEl0kJ6QJ9bOu5YY53Idu24QgvTnUdNsQazp2yUONBNwHIrNDnNEXm8SfCi4Mc9o0mmNRMIoiRA==",
"version": "7.6.20",
"resolved": "https://registry.npmjs.org/@storybook/core-server/-/core-server-7.6.20.tgz",
"integrity": "sha512-qC5BdbqqwMLTdCwMKZ1Hbc3+3AaxHYWLiJaXL9e8s8nJw89xV8c8l30QpbJOGvcDmsgY6UTtXYaJ96OsTr7MrA==",
"dev": true,
"dependencies": {
"@aw-web-design/x-default-browser": "1.4.126",
"@discoveryjs/json-ext": "^0.5.3",
"@storybook/builder-manager": "7.6.19",
"@storybook/channels": "7.6.19",
"@storybook/core-common": "7.6.19",
"@storybook/core-events": "7.6.19",
"@storybook/builder-manager": "7.6.20",
"@storybook/channels": "7.6.20",
"@storybook/core-common": "7.6.20",
"@storybook/core-events": "7.6.20",
"@storybook/csf": "^0.1.2",
"@storybook/csf-tools": "7.6.19",
"@storybook/csf-tools": "7.6.20",
"@storybook/docs-mdx": "^0.1.0",
"@storybook/global": "^5.0.0",
"@storybook/manager": "7.6.19",
"@storybook/node-logger": "7.6.19",
"@storybook/preview-api": "7.6.19",
"@storybook/telemetry": "7.6.19",
"@storybook/types": "7.6.19",
"@storybook/manager": "7.6.20",
"@storybook/node-logger": "7.6.20",
"@storybook/preview-api": "7.6.20",
"@storybook/telemetry": "7.6.20",
"@storybook/types": "7.6.20",
"@types/detect-port": "^1.3.0",
"@types/node": "^18.0.0",
"@types/pretty-hrtime": "^1.0.0",
@ -7095,7 +7095,6 @@
"express": "^4.17.3",
"fs-extra": "^11.1.0",
"globby": "^11.0.2",
"ip": "^2.0.1",
"lodash": "^4.17.21",
"open": "^8.4.0",
"pretty-hrtime": "^1.0.3",
@ -7116,13 +7115,13 @@
}
},
"node_modules/@storybook/core-server/node_modules/@storybook/channels": {
"version": "7.6.19",
"resolved": "https://registry.npmjs.org/@storybook/channels/-/channels-7.6.19.tgz",
"integrity": "sha512-2JGh+i95GwjtjqWqhtEh15jM5ifwbRGmXeFqkY7dpdHH50EEWafYHr2mg3opK3heVDwg0rJ/VBptkmshloXuvA==",
"version": "7.6.20",
"resolved": "https://registry.npmjs.org/@storybook/channels/-/channels-7.6.20.tgz",
"integrity": "sha512-4hkgPSH6bJclB2OvLnkZOGZW1WptJs09mhQ6j6qLjgBZzL/ZdD6priWSd7iXrmPiN5TzUobkG4P4Dp7FjkiO7A==",
"dev": true,
"dependencies": {
"@storybook/client-logger": "7.6.19",
"@storybook/core-events": "7.6.19",
"@storybook/client-logger": "7.6.20",
"@storybook/core-events": "7.6.20",
"@storybook/global": "^5.0.0",
"qs": "^6.10.0",
"telejson": "^7.2.0",
@ -7134,9 +7133,9 @@
}
},
"node_modules/@storybook/core-server/node_modules/@storybook/client-logger": {
"version": "7.6.19",
"resolved": "https://registry.npmjs.org/@storybook/client-logger/-/client-logger-7.6.19.tgz",
"integrity": "sha512-oGzOxbmLmciSIfd5gsxDzPmX8DttWhoYdPKxjMuCuWLTO2TWpkCWp1FTUMWO72mm/6V/FswT/aqpJJBBvdZ3RQ==",
"version": "7.6.20",
"resolved": "https://registry.npmjs.org/@storybook/client-logger/-/client-logger-7.6.20.tgz",
"integrity": "sha512-NwG0VIJQCmKrSaN5GBDFyQgTAHLNishUPLW1NrzqTDNAhfZUoef64rPQlinbopa0H4OXmlB+QxbQIb3ubeXmSQ==",
"dev": true,
"dependencies": {
"@storybook/global": "^5.0.0"
@ -7147,14 +7146,14 @@
}
},
"node_modules/@storybook/core-server/node_modules/@storybook/core-common": {
"version": "7.6.19",
"resolved": "https://registry.npmjs.org/@storybook/core-common/-/core-common-7.6.19.tgz",
"integrity": "sha512-njwpGzFJrfbJr/AFxGP8KMrfPfxN85KOfSlxYnQwRm5Z0H1D/lT33LhEBf5m37gaGawHeG7KryxO6RvaioMt2Q==",
"version": "7.6.20",
"resolved": "https://registry.npmjs.org/@storybook/core-common/-/core-common-7.6.20.tgz",
"integrity": "sha512-8H1zPWPjcmeD4HbDm4FDD0WLsfAKGVr566IZ4hG+h3iWVW57II9JW9MLBtiR2LPSd8u7o0kw64lwRGmtCO1qAw==",
"dev": true,
"dependencies": {
"@storybook/core-events": "7.6.19",
"@storybook/node-logger": "7.6.19",
"@storybook/types": "7.6.19",
"@storybook/core-events": "7.6.20",
"@storybook/node-logger": "7.6.20",
"@storybook/types": "7.6.20",
"@types/find-cache-dir": "^3.2.1",
"@types/node": "^18.0.0",
"@types/node-fetch": "^2.6.4",
@ -7182,9 +7181,9 @@
}
},
"node_modules/@storybook/core-server/node_modules/@storybook/core-events": {
"version": "7.6.19",
"resolved": "https://registry.npmjs.org/@storybook/core-events/-/core-events-7.6.19.tgz",
"integrity": "sha512-K/W6Uvum0ocZSgjbi8hiotpe+wDEHDZlvN+KlPqdh9ae9xDK8aBNBq9IelCoqM+uKO1Zj+dDfSQds7CD781DJg==",
"version": "7.6.20",
"resolved": "https://registry.npmjs.org/@storybook/core-events/-/core-events-7.6.20.tgz",
"integrity": "sha512-tlVDuVbDiNkvPDFAu+0ou3xBBYbx9zUURQz4G9fAq0ScgBOs/bpzcRrFb4mLpemUViBAd47tfZKdH4MAX45KVQ==",
"dev": true,
"dependencies": {
"ts-dedent": "^2.0.0"
@ -7195,9 +7194,9 @@
}
},
"node_modules/@storybook/core-server/node_modules/@storybook/csf-tools": {
"version": "7.6.19",
"resolved": "https://registry.npmjs.org/@storybook/csf-tools/-/csf-tools-7.6.19.tgz",
"integrity": "sha512-8Vzia3cHhDdGHuS3XKXJReCRxmfRq3vmTm/Te9yKZnPSAsC58CCKcMh8FNEFJ44vxYF9itKTkRutjGs+DprKLQ==",
"version": "7.6.20",
"resolved": "https://registry.npmjs.org/@storybook/csf-tools/-/csf-tools-7.6.20.tgz",
"integrity": "sha512-rwcwzCsAYh/m/WYcxBiEtLpIW5OH1ingxNdF/rK9mtGWhJxXRDV8acPkFrF8rtFWIVKoOCXu5USJYmc3f2gdYQ==",
"dev": true,
"dependencies": {
"@babel/generator": "^7.23.0",
@ -7205,7 +7204,7 @@
"@babel/traverse": "^7.23.2",
"@babel/types": "^7.23.0",
"@storybook/csf": "^0.1.2",
"@storybook/types": "7.6.19",
"@storybook/types": "7.6.20",
"fs-extra": "^11.1.0",
"recast": "^0.23.1",
"ts-dedent": "^2.0.0"
@ -7216,9 +7215,9 @@
}
},
"node_modules/@storybook/core-server/node_modules/@storybook/node-logger": {
"version": "7.6.19",
"resolved": "https://registry.npmjs.org/@storybook/node-logger/-/node-logger-7.6.19.tgz",
"integrity": "sha512-2g29QC44Zl1jKY37DmQ0/dO7+VSKnGgPI/x0mwVwQffypSapxH3rwLLT5Q5XLHeFyD+fhRu5w9Cj4vTGynJgpA==",
"version": "7.6.20",
"resolved": "https://registry.npmjs.org/@storybook/node-logger/-/node-logger-7.6.20.tgz",
"integrity": "sha512-l2i4qF1bscJkOplNffcRTsgQWYR7J51ewmizj5YrTM8BK6rslWT1RntgVJWB1RgPqvx6VsCz1gyP3yW1oKxvYw==",
"dev": true,
"funding": {
"type": "opencollective",
@ -7226,17 +7225,17 @@
}
},
"node_modules/@storybook/core-server/node_modules/@storybook/preview-api": {
"version": "7.6.19",
"resolved": "https://registry.npmjs.org/@storybook/preview-api/-/preview-api-7.6.19.tgz",
"integrity": "sha512-04hdMSQucroJT4dBjQzRd7ZwH2hij8yx2nm5qd4HYGkd1ORkvlH6GOLph4XewNJl5Um3xfzFQzBhvkqvG0WaCQ==",
"version": "7.6.20",
"resolved": "https://registry.npmjs.org/@storybook/preview-api/-/preview-api-7.6.20.tgz",
"integrity": "sha512-3ic2m9LDZEPwZk02wIhNc3n3rNvbi7VDKn52hDXfAxnL5EYm7yDICAkaWcVaTfblru2zn0EDJt7ROpthscTW5w==",
"dev": true,
"dependencies": {
"@storybook/channels": "7.6.19",
"@storybook/client-logger": "7.6.19",
"@storybook/core-events": "7.6.19",
"@storybook/channels": "7.6.20",
"@storybook/client-logger": "7.6.20",
"@storybook/core-events": "7.6.20",
"@storybook/csf": "^0.1.2",
"@storybook/global": "^5.0.0",
"@storybook/types": "7.6.19",
"@storybook/types": "7.6.20",
"@types/qs": "^6.9.5",
"dequal": "^2.0.2",
"lodash": "^4.17.21",
@ -7252,12 +7251,12 @@
}
},
"node_modules/@storybook/core-server/node_modules/@storybook/types": {
"version": "7.6.19",
"resolved": "https://registry.npmjs.org/@storybook/types/-/types-7.6.19.tgz",
"integrity": "sha512-DeGYrRPRMGTVfT7o2rEZtRzyLT2yKTI2exgpnxbwPWEFAduZCSfzBrcBXZ/nb5B0pjA9tUNWls1YzGkJGlkhpg==",
"version": "7.6.20",
"resolved": "https://registry.npmjs.org/@storybook/types/-/types-7.6.20.tgz",
"integrity": "sha512-GncdY3x0LpbhmUAAJwXYtJDUQEwfF175gsjH0/fxPkxPoV7Sef9TM41jQLJW/5+6TnZoCZP/+aJZTJtq3ni23Q==",
"dev": true,
"dependencies": {
"@storybook/channels": "7.6.19",
"@storybook/channels": "7.6.20",
"@types/babel__core": "^7.0.0",
"@types/express": "^4.7.0",
"file-system-cache": "2.3.0"
@ -7372,9 +7371,9 @@
"dev": true
},
"node_modules/@storybook/manager": {
"version": "7.6.19",
"resolved": "https://registry.npmjs.org/@storybook/manager/-/manager-7.6.19.tgz",
"integrity": "sha512-fZWQcf59x4P0iiBhrL74PZrqKJAPuk9sWjP8BIkGbf8wTZtUunbY5Sv4225fOL4NLJbuX9/RYLUPoxQ3nucGHA==",
"version": "7.6.20",
"resolved": "https://registry.npmjs.org/@storybook/manager/-/manager-7.6.20.tgz",
"integrity": "sha512-0Cf6WN0t7yEG2DR29tN5j+i7H/TH5EfPppg9h9/KiQSoFHk+6KLoy2p5do94acFU+Ro4+zzxvdCGbcYGKuArpg==",
"dev": true,
"funding": {
"type": "opencollective",
@ -7810,14 +7809,14 @@
}
},
"node_modules/@storybook/telemetry": {
"version": "7.6.19",
"resolved": "https://registry.npmjs.org/@storybook/telemetry/-/telemetry-7.6.19.tgz",
"integrity": "sha512-rA5xum4I36M57iiD3uzmW0MOdpl0vEpHWBSAa5hK0a0ALPeY9TgAsQlI/0dSyNYJ/K7aczEEN6d4qm1NC4u10A==",
"version": "7.6.20",
"resolved": "https://registry.npmjs.org/@storybook/telemetry/-/telemetry-7.6.20.tgz",
"integrity": "sha512-dmAOCWmOscYN6aMbhCMmszQjoycg7tUPRVy2kTaWg6qX10wtMrvEtBV29W4eMvqdsoRj5kcvoNbzRdYcWBUOHQ==",
"dev": true,
"dependencies": {
"@storybook/client-logger": "7.6.19",
"@storybook/core-common": "7.6.19",
"@storybook/csf-tools": "7.6.19",
"@storybook/client-logger": "7.6.20",
"@storybook/core-common": "7.6.20",
"@storybook/csf-tools": "7.6.20",
"chalk": "^4.1.0",
"detect-package-manager": "^2.0.1",
"fetch-retry": "^5.0.2",
@ -7830,13 +7829,13 @@
}
},
"node_modules/@storybook/telemetry/node_modules/@storybook/channels": {
"version": "7.6.19",
"resolved": "https://registry.npmjs.org/@storybook/channels/-/channels-7.6.19.tgz",
"integrity": "sha512-2JGh+i95GwjtjqWqhtEh15jM5ifwbRGmXeFqkY7dpdHH50EEWafYHr2mg3opK3heVDwg0rJ/VBptkmshloXuvA==",
"version": "7.6.20",
"resolved": "https://registry.npmjs.org/@storybook/channels/-/channels-7.6.20.tgz",
"integrity": "sha512-4hkgPSH6bJclB2OvLnkZOGZW1WptJs09mhQ6j6qLjgBZzL/ZdD6priWSd7iXrmPiN5TzUobkG4P4Dp7FjkiO7A==",
"dev": true,
"dependencies": {
"@storybook/client-logger": "7.6.19",
"@storybook/core-events": "7.6.19",
"@storybook/client-logger": "7.6.20",
"@storybook/core-events": "7.6.20",
"@storybook/global": "^5.0.0",
"qs": "^6.10.0",
"telejson": "^7.2.0",
@ -7848,9 +7847,9 @@
}
},
"node_modules/@storybook/telemetry/node_modules/@storybook/client-logger": {
"version": "7.6.19",
"resolved": "https://registry.npmjs.org/@storybook/client-logger/-/client-logger-7.6.19.tgz",
"integrity": "sha512-oGzOxbmLmciSIfd5gsxDzPmX8DttWhoYdPKxjMuCuWLTO2TWpkCWp1FTUMWO72mm/6V/FswT/aqpJJBBvdZ3RQ==",
"version": "7.6.20",
"resolved": "https://registry.npmjs.org/@storybook/client-logger/-/client-logger-7.6.20.tgz",
"integrity": "sha512-NwG0VIJQCmKrSaN5GBDFyQgTAHLNishUPLW1NrzqTDNAhfZUoef64rPQlinbopa0H4OXmlB+QxbQIb3ubeXmSQ==",
"dev": true,
"dependencies": {
"@storybook/global": "^5.0.0"
@ -7861,14 +7860,14 @@
}
},
"node_modules/@storybook/telemetry/node_modules/@storybook/core-common": {
"version": "7.6.19",
"resolved": "https://registry.npmjs.org/@storybook/core-common/-/core-common-7.6.19.tgz",
"integrity": "sha512-njwpGzFJrfbJr/AFxGP8KMrfPfxN85KOfSlxYnQwRm5Z0H1D/lT33LhEBf5m37gaGawHeG7KryxO6RvaioMt2Q==",
"version": "7.6.20",
"resolved": "https://registry.npmjs.org/@storybook/core-common/-/core-common-7.6.20.tgz",
"integrity": "sha512-8H1zPWPjcmeD4HbDm4FDD0WLsfAKGVr566IZ4hG+h3iWVW57II9JW9MLBtiR2LPSd8u7o0kw64lwRGmtCO1qAw==",
"dev": true,
"dependencies": {
"@storybook/core-events": "7.6.19",
"@storybook/node-logger": "7.6.19",
"@storybook/types": "7.6.19",
"@storybook/core-events": "7.6.20",
"@storybook/node-logger": "7.6.20",
"@storybook/types": "7.6.20",
"@types/find-cache-dir": "^3.2.1",
"@types/node": "^18.0.0",
"@types/node-fetch": "^2.6.4",
@ -7896,9 +7895,9 @@
}
},
"node_modules/@storybook/telemetry/node_modules/@storybook/core-events": {
"version": "7.6.19",
"resolved": "https://registry.npmjs.org/@storybook/core-events/-/core-events-7.6.19.tgz",
"integrity": "sha512-K/W6Uvum0ocZSgjbi8hiotpe+wDEHDZlvN+KlPqdh9ae9xDK8aBNBq9IelCoqM+uKO1Zj+dDfSQds7CD781DJg==",
"version": "7.6.20",
"resolved": "https://registry.npmjs.org/@storybook/core-events/-/core-events-7.6.20.tgz",
"integrity": "sha512-tlVDuVbDiNkvPDFAu+0ou3xBBYbx9zUURQz4G9fAq0ScgBOs/bpzcRrFb4mLpemUViBAd47tfZKdH4MAX45KVQ==",
"dev": true,
"dependencies": {
"ts-dedent": "^2.0.0"
@ -7909,9 +7908,9 @@
}
},
"node_modules/@storybook/telemetry/node_modules/@storybook/csf-tools": {
"version": "7.6.19",
"resolved": "https://registry.npmjs.org/@storybook/csf-tools/-/csf-tools-7.6.19.tgz",
"integrity": "sha512-8Vzia3cHhDdGHuS3XKXJReCRxmfRq3vmTm/Te9yKZnPSAsC58CCKcMh8FNEFJ44vxYF9itKTkRutjGs+DprKLQ==",
"version": "7.6.20",
"resolved": "https://registry.npmjs.org/@storybook/csf-tools/-/csf-tools-7.6.20.tgz",
"integrity": "sha512-rwcwzCsAYh/m/WYcxBiEtLpIW5OH1ingxNdF/rK9mtGWhJxXRDV8acPkFrF8rtFWIVKoOCXu5USJYmc3f2gdYQ==",
"dev": true,
"dependencies": {
"@babel/generator": "^7.23.0",
@ -7919,7 +7918,7 @@
"@babel/traverse": "^7.23.2",
"@babel/types": "^7.23.0",
"@storybook/csf": "^0.1.2",
"@storybook/types": "7.6.19",
"@storybook/types": "7.6.20",
"fs-extra": "^11.1.0",
"recast": "^0.23.1",
"ts-dedent": "^2.0.0"
@ -7930,9 +7929,9 @@
}
},
"node_modules/@storybook/telemetry/node_modules/@storybook/node-logger": {
"version": "7.6.19",
"resolved": "https://registry.npmjs.org/@storybook/node-logger/-/node-logger-7.6.19.tgz",
"integrity": "sha512-2g29QC44Zl1jKY37DmQ0/dO7+VSKnGgPI/x0mwVwQffypSapxH3rwLLT5Q5XLHeFyD+fhRu5w9Cj4vTGynJgpA==",
"version": "7.6.20",
"resolved": "https://registry.npmjs.org/@storybook/node-logger/-/node-logger-7.6.20.tgz",
"integrity": "sha512-l2i4qF1bscJkOplNffcRTsgQWYR7J51ewmizj5YrTM8BK6rslWT1RntgVJWB1RgPqvx6VsCz1gyP3yW1oKxvYw==",
"dev": true,
"funding": {
"type": "opencollective",
@ -7940,12 +7939,12 @@
}
},
"node_modules/@storybook/telemetry/node_modules/@storybook/types": {
"version": "7.6.19",
"resolved": "https://registry.npmjs.org/@storybook/types/-/types-7.6.19.tgz",
"integrity": "sha512-DeGYrRPRMGTVfT7o2rEZtRzyLT2yKTI2exgpnxbwPWEFAduZCSfzBrcBXZ/nb5B0pjA9tUNWls1YzGkJGlkhpg==",
"version": "7.6.20",
"resolved": "https://registry.npmjs.org/@storybook/types/-/types-7.6.20.tgz",
"integrity": "sha512-GncdY3x0LpbhmUAAJwXYtJDUQEwfF175gsjH0/fxPkxPoV7Sef9TM41jQLJW/5+6TnZoCZP/+aJZTJtq3ni23Q==",
"dev": true,
"dependencies": {
"@storybook/channels": "7.6.19",
"@storybook/channels": "7.6.20",
"@types/babel__core": "^7.0.0",
"@types/express": "^4.7.0",
"file-system-cache": "2.3.0"
@ -10040,12 +10039,13 @@
}
},
"node_modules/axios": {
"version": "0.27.2",
"resolved": "https://registry.npmjs.org/axios/-/axios-0.27.2.tgz",
"integrity": "sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ==",
"version": "0.28.0",
"resolved": "https://registry.npmjs.org/axios/-/axios-0.28.0.tgz",
"integrity": "sha512-Tu7NYoGY4Yoc7I+Npf9HhUMtEEpV7ZiLH9yndTCoNhcpBH0kwcvFbzYN9/u5QKI5A6uefjsNNWaz5olJVYS62Q==",
"dependencies": {
"follow-redirects": "^1.14.9",
"form-data": "^4.0.0"
"follow-redirects": "^1.15.0",
"form-data": "^4.0.0",
"proxy-from-env": "^1.1.0"
}
},
"node_modules/axios-auth-refresh": {
@ -10056,6 +10056,11 @@
"axios": ">= 0.18 < 0.19.0 || >= 0.19.1"
}
},
"node_modules/axios/node_modules/proxy-from-env": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="
},
"node_modules/axobject-query": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-3.2.1.tgz",
@ -11451,6 +11456,12 @@
"safe-buffer": "~5.1.0"
}
},
"node_modules/confbox": {
"version": "0.1.7",
"resolved": "https://registry.npmjs.org/confbox/-/confbox-0.1.7.tgz",
"integrity": "sha512-uJcB/FKZtBMCJpK8MQji6bJHgu1tixKPxRLeGkNzBoOZzpnZUJm0jm2/sBDWcuBx1dYgxV4JU+g5hmNxCyAmdA==",
"dev": true
},
"node_modules/confusing-browser-globals": {
"version": "1.0.11",
"resolved": "https://registry.npmjs.org/confusing-browser-globals/-/confusing-browser-globals-1.0.11.tgz",
@ -14389,9 +14400,9 @@
"dev": true
},
"node_modules/flow-parser": {
"version": "0.237.2",
"resolved": "https://registry.npmjs.org/flow-parser/-/flow-parser-0.237.2.tgz",
"integrity": "sha512-mvI/kdfr3l1waaPbThPA8dJa77nHXrfZIun+SWvFwSwDjmeByU7mGJGRmv1+7guU6ccyLV8e1lqZA1lD4iMGnQ==",
"version": "0.239.1",
"resolved": "https://registry.npmjs.org/flow-parser/-/flow-parser-0.239.1.tgz",
"integrity": "sha512-topOrETNxJ6T2gAnQiWqAlzGPj8uI2wtmNOlDIMNB+qyvGJZ6R++STbUOTAYmvPhOMz2gXnXPH0hOvURYmrBow==",
"dev": true,
"engines": {
"node": ">=0.4.0"
@ -18147,6 +18158,30 @@
"integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==",
"dev": true
},
"node_modules/mlly": {
"version": "1.7.1",
"resolved": "https://registry.npmjs.org/mlly/-/mlly-1.7.1.tgz",
"integrity": "sha512-rrVRZRELyQzrIUAVMHxP97kv+G786pHmOKzuFII8zDYahFBS7qnHh2AlYSl1GAHhaMPCz6/oHjVMcfFYgFYHgA==",
"dev": true,
"dependencies": {
"acorn": "^8.11.3",
"pathe": "^1.1.2",
"pkg-types": "^1.1.1",
"ufo": "^1.5.3"
}
},
"node_modules/mlly/node_modules/acorn": {
"version": "8.12.1",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz",
"integrity": "sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==",
"dev": true,
"bin": {
"acorn": "bin/acorn"
},
"engines": {
"node": ">=0.4.0"
}
},
"node_modules/mri": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz",
@ -18621,16 +18656,17 @@
}
},
"node_modules/nypm": {
"version": "0.3.8",
"resolved": "https://registry.npmjs.org/nypm/-/nypm-0.3.8.tgz",
"integrity": "sha512-IGWlC6So2xv6V4cIDmoV0SwwWx7zLG086gyqkyumteH2fIgCAM4nDVFB2iDRszDvmdSVW9xb1N+2KjQ6C7d4og==",
"version": "0.3.9",
"resolved": "https://registry.npmjs.org/nypm/-/nypm-0.3.9.tgz",
"integrity": "sha512-BI2SdqqTHg2d4wJh8P9A1W+bslg33vOE9IZDY6eR2QC+Pu1iNBVZUqczrd43rJb+fMzHU7ltAYKsEFY/kHMFcw==",
"dev": true,
"dependencies": {
"citty": "^0.1.6",
"consola": "^3.2.3",
"execa": "^8.0.1",
"pathe": "^1.1.2",
"ufo": "^1.4.0"
"pkg-types": "^1.1.1",
"ufo": "^1.5.3"
},
"bin": {
"nypm": "dist/cli.mjs"
@ -19360,9 +19396,9 @@
"devOptional": true
},
"node_modules/picocolors": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
"integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ=="
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz",
"integrity": "sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew=="
},
"node_modules/picomatch": {
"version": "2.3.1",
@ -19405,6 +19441,17 @@
"node": ">=10"
}
},
"node_modules/pkg-types": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.1.3.tgz",
"integrity": "sha512-+JrgthZG6m3ckicaOB74TwQ+tBWsFl3qVQg7mN8ulwSOElJ7gBhKzj2VkCPnZ4NlF6kEquYU+RIYNVAvzd54UA==",
"dev": true,
"dependencies": {
"confbox": "^0.1.7",
"mlly": "^1.7.1",
"pathe": "^1.1.2"
}
},
"node_modules/pnp-webpack-plugin": {
"version": "1.7.0",
"resolved": "https://registry.npmjs.org/pnp-webpack-plugin/-/pnp-webpack-plugin-1.7.0.tgz",
@ -19441,9 +19488,9 @@
}
},
"node_modules/postcss": {
"version": "8.4.33",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.33.tgz",
"integrity": "sha512-Kkpbhhdjw2qQs2O2DGX+8m5OVqEcbB9HRBvuYM9pgrjEFUg30A9LmXNlTAUj4S9kgtGyrMbTzVjH7E+s5Re2yg==",
"version": "8.4.39",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.39.tgz",
"integrity": "sha512-0vzE+lAiG7hZl1/9I8yzKLx3aR9Xbof3fBHKunvMfOCYAtMhrsnccJY2iTURb9EZd5+pLuiNV9/c/GZJOHsgIw==",
"funding": [
{
"type": "opencollective",
@ -19460,8 +19507,8 @@
],
"dependencies": {
"nanoid": "^3.3.7",
"picocolors": "^1.0.0",
"source-map-js": "^1.0.2"
"picocolors": "^1.0.1",
"source-map-js": "^1.2.0"
},
"engines": {
"node": "^10 || ^12 || >=14"
@ -20265,9 +20312,9 @@
}
},
"node_modules/puppeteer-core/node_modules/ws": {
"version": "6.2.2",
"resolved": "https://registry.npmjs.org/ws/-/ws-6.2.2.tgz",
"integrity": "sha512-zmhltoSR8u1cnDsD43TX59mzoMZsLKqUweyYBAIvTngR3shc0W6aOZylZmq/7hqyVxPdi+5Ud2QInblgyE72fw==",
"version": "6.2.3",
"resolved": "https://registry.npmjs.org/ws/-/ws-6.2.3.tgz",
"integrity": "sha512-jmTjYU0j60B+vHey6TfR3Z7RD61z/hmxBS3VMSGIrroOWXQEneK1zNuotOUrGyBHQj0yrpsLHPWtigEFd13ndA==",
"dev": true,
"dependencies": {
"async-limiter": "~1.0.0"
@ -22254,9 +22301,9 @@
}
},
"node_modules/source-map-js": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz",
"integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==",
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz",
"integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==",
"engines": {
"node": ">=0.10.0"
}
@ -22420,12 +22467,12 @@
"dev": true
},
"node_modules/storybook": {
"version": "7.6.19",
"resolved": "https://registry.npmjs.org/storybook/-/storybook-7.6.19.tgz",
"integrity": "sha512-xWD1C4vD/4KMffCrBBrUpsLUO/9uNpm8BVW8+Vcb30gkQDfficZ0oziWkmLexpT53VSioa24iazGXMwBqllYjQ==",
"version": "7.6.20",
"resolved": "https://registry.npmjs.org/storybook/-/storybook-7.6.20.tgz",
"integrity": "sha512-Wt04pPTO71pwmRmsgkyZhNo4Bvdb/1pBAMsIFb9nQLykEdzzpXjvingxFFvdOG4nIowzwgxD+CLlyRqVJqnATw==",
"dev": true,
"dependencies": {
"@storybook/cli": "7.6.19"
"@storybook/cli": "7.6.20"
},
"bin": {
"sb": "index.js",
@ -24661,9 +24708,9 @@
}
},
"node_modules/ws": {
"version": "8.17.0",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.17.0.tgz",
"integrity": "sha512-uJq6108EgZMAl20KagGkzCKfMEjxmKvZHG7Tlq0Z6nOky7YF7aq4mOx6xK8TJ/i1LeK4Qus7INktacctDgY8Ow==",
"version": "8.18.0",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz",
"integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==",
"dev": true,
"engines": {
"node": ">=10.0.0"

@ -55,7 +55,7 @@
"@ucast/mongo2js": "^1.3.4",
"add": "^2.0.6",
"argon2-browser": "^1.18.0",
"axios": "^0.27.2",
"axios": "^0.28.0",
"axios-auth-refresh": "^3.3.6",
"base64-loader": "^1.0.0",
"classnames": "^2.3.1",
@ -144,10 +144,10 @@
"eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-simple-import-sort": "^8.0.0",
"eslint-plugin-storybook": "^0.6.12",
"postcss": "^8.4.14",
"postcss": "^8.4.39",
"prettier": "^2.8.3",
"prettier-plugin-tailwindcss": "^0.2.2",
"storybook": "^7.5.2",
"storybook": "^7.6.20",
"storybook-dark-mode": "^3.0.0",
"tailwindcss": "3.2",
"typescript": "^4.9.3"

@ -60,6 +60,7 @@ export type OrgUser = {
status: "invited" | "accepted" | "verified" | "completed";
deniedPermissions: any[];
roleId: string;
isActive: boolean;
};
export type TProjectMembership = {

@ -77,7 +77,7 @@ const features = [
"Pull secrets into your Kubernetes containers and automatically redeploy upon secret changes."
},
{
_id: 1,
id: 1,
name: "Infisical Agent",
link: "https://infisical.com/docs/infisical-agent/overview",
description: "Inject secrets into your apps without modifying any application logic."
@ -889,27 +889,27 @@ const OrganizationPage = withPermission(
<div className="mt-4 grid w-full grid-cols-3 gap-4">
{features.map((feature) => (
<div
key={feature._id}
className="flex h-44 w-full flex-col justify-between rounded-md border border-mineshaft-600 bg-mineshaft-800 p-4"
key={feature.id}
className="relative flex h-full w-full flex-col gap-2 overflow-auto rounded-md border border-mineshaft-600 bg-mineshaft-800 p-4"
>
<div className="mt-0 text-lg text-mineshaft-100">{feature.name}</div>
<div className="mb-4 mt-2 text-[15px] font-light text-mineshaft-300">
<div className="line-clamp overflwo-auto mb-4 mt-2 h-full text-[15px] font-light text-mineshaft-300">
{feature.description}
</div>
<div className="flex w-full items-center">
<div className="text-[15px] font-light text-mineshaft-300">
<div className="flex w-full flex-col items-start gap-2 xl:flex-row xl:items-center">
<p className="left-0 text-[15px] font-light text-mineshaft-300">
Setup time: 20 min
</div>
</p>
<a
target="_blank"
rel="noopener noreferrer"
className="group ml-auto w-max cursor-default rounded-full border border-mineshaft-600 bg-mineshaft-900 py-2 px-4 text-sm text-mineshaft-300 transition-all hover:border-primary-500/80 hover:bg-primary-800/20 hover:text-mineshaft-200"
className="group ml-0 w-max cursor-default rounded-full border border-mineshaft-600 bg-mineshaft-900 py-2 px-4 text-sm text-mineshaft-300 transition-all hover:border-primary-500/80 hover:bg-primary-800/20 hover:text-mineshaft-200 xl:ml-auto"
href={feature.link}
>
Learn more{" "}
<FontAwesomeIcon
icon={faArrowRight}
className="pl-1.5 pr-0.5 duration-200 group-hover:pl-2 group-hover:pr-0"
className="s pl-1.5 pr-0.5 duration-200 group-hover:pl-2 group-hover:pr-0"
/>
</a>
</div>

@ -5,7 +5,7 @@ import { z } from "zod";
import { createNotification } from "@app/components/notifications";
import { Button, FormControl, Modal, ModalContent, Select, SelectItem } from "@app/components/v2";
import { useWorkspace } from "@app/context";
import { useOrganization,useWorkspace } from "@app/context";
import {
useAddIdentityToWorkspace,
useGetIdentityProjectMemberships,
@ -33,6 +33,7 @@ type Props = {
};
export const IdentityAddToProjectModal = ({ identityId, popUp, handlePopUpToggle }: Props) => {
const { currentOrg } = useOrganization();
const { workspaces } = useWorkspace();
const { mutateAsync: addIdentityToWorkspace } = useAddIdentityToWorkspace();
@ -58,7 +59,9 @@ export const IdentityAddToProjectModal = ({ identityId, popUp, handlePopUpToggle
wsWorkspaceIds.set(projectMembership.project.id, true);
});
return (workspaces || []).filter(({ id }) => !wsWorkspaceIds.has(id));
return (workspaces || []).filter(
({ id, orgId }) => !wsWorkspaceIds.has(id) && orgId === currentOrg?.id
);
}, [workspaces, projectMemberships]);
const onFormSubmit = async ({ projectId: workspaceId, role }: FormData) => {

@ -171,14 +171,14 @@ export const OrgMembersTable = ({ handlePopUpOpen, setCompleteInviteLink }: Prop
{isLoading && <TableSkeleton columns={5} innerKey="org-members" />}
{!isLoading &&
filterdUser?.map(
({ user: u, inviteEmail, role, roleId, id: orgMembershipId, status }) => {
({ user: u, inviteEmail, role, roleId, id: orgMembershipId, status, isActive }) => {
const name = u && u.firstName ? `${u.firstName} ${u.lastName}` : "-";
const email = u?.email || inviteEmail;
const username = u?.username ?? inviteEmail ?? "-";
return (
<Tr key={`org-membership-${orgMembershipId}`} className="w-full">
<Td>{name}</Td>
<Td>{username}</Td>
<Td className={isActive ? "" : "text-mineshaft-400"}>{name}</Td>
<Td className={isActive ? "" : "text-mineshaft-400"}>{username}</Td>
<Td>
<OrgPermissionCan
I={OrgPermissionActions.Edit}
@ -186,7 +186,18 @@ export const OrgMembersTable = ({ handlePopUpOpen, setCompleteInviteLink }: Prop
>
{(isAllowed) => (
<>
{status === "accepted" && (
{!isActive && (
<Button
isDisabled
className="w-40"
colorSchema="primary"
variant="outline_bg"
onClick={() => {}}
>
Suspended
</Button>
)}
{isActive && status === "accepted" && (
<Select
value={role === "custom" ? findRoleFromId(roleId)?.slug : role}
isDisabled={userId === u?.id || !isAllowed}
@ -207,7 +218,8 @@ export const OrgMembersTable = ({ handlePopUpOpen, setCompleteInviteLink }: Prop
))}
</Select>
)}
{(status === "invited" || status === "verified") &&
{isActive &&
(status === "invited" || status === "verified") &&
email &&
serverDetails?.emailConfigured && (
<Button

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

@ -64,6 +64,8 @@ spec:
properties:
identityId:
type: string
resource:
type: string
secretsScope:
properties:
envSlug:

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

@ -58,6 +58,8 @@ type AWSIamAuthDetails struct {
type AzureAuthDetails struct {
// +kubebuilder:validation:Required
IdentityID string `json:"identityId"`
// +kubebuilder:validation:Optional
Resource string `json:"resource"`
// +kubebuilder:validation:Required
SecretsScope MachineIdentityScopeInWorkspace `json:"secretsScope"`

@ -64,6 +64,8 @@ spec:
properties:
identityId:
type: string
resource:
type: string
secretsScope:
properties:
envSlug:

@ -60,6 +60,7 @@ spec:
# Azure Auth
azureAuth:
identityId: <your-machine-identity-id>
resource: https://management.azure.com/&client_id=your_client_id # This field is optional, and will default to "https://management.azure.com/" if nothing is provided.
# secretsScope is identical to the secrets scope in the universalAuth field in this sample.
secretsScope:

@ -109,7 +109,7 @@ func (r *InfisicalSecretReconciler) handleAzureAuth(ctx context.Context, infisic
return AuthenticationDetails{}, ErrAuthNotApplicable
}
_, err := infisicalClient.Auth().AzureAuthLogin(azureAuthSpec.IdentityID)
_, err := infisicalClient.Auth().AzureAuthLogin(azureAuthSpec.IdentityID, azureAuthSpec.Resource) // If resource is empty(""), it will default to "https://management.azure.com/" in the SDK.
if err != nil {
return AuthenticationDetails{}, fmt.Errorf("unable to login with Azure auth [err=%s]", err)
}

@ -3,7 +3,7 @@ module github.com/Infisical/infisical/k8-operator
go 1.21
require (
github.com/infisical/go-sdk v0.2.1
github.com/infisical/go-sdk v0.3.2
github.com/onsi/ginkgo/v2 v2.6.0
github.com/onsi/gomega v1.24.1
k8s.io/apimachinery v0.26.1

@ -13,10 +13,6 @@ cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKV
cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=
cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=
cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=
cloud.google.com/go v0.115.0 h1:CnFSK6Xo3lDYRoBKEcAtia6VSC837/ZkJuRduSFnr14=
cloud.google.com/go v0.115.0/go.mod h1:8jIM5vVgoAEoiVxQ/O4BFTfHqulPZgs/ufEzMcFMdWU=
cloud.google.com/go/auth v0.5.1 h1:0QNO7VThG54LUzKiQxv8C6x1YX7lUrzlAa1nVLF8CIw=
cloud.google.com/go/auth v0.5.1/go.mod h1:vbZT8GjzDf3AVqCcQmqeeM32U9HBFc32vVVAbwDsa6s=
cloud.google.com/go/auth v0.6.1 h1:T0Zw1XM5c1GlpN2HYr2s+m3vr1p2wy+8VN+Z1FKxW38=
cloud.google.com/go/auth v0.6.1/go.mod h1:eFHG7zDzbXHKmjJddFG/rBlcGp6t25SwRUiEQSlO4x4=
cloud.google.com/go/auth/oauth2adapt v0.2.2 h1:+TTV8aXpjeChS9M+aTtN/TjdQnzJvmzKFt//oWu7HX4=
@ -27,14 +23,10 @@ cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvf
cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
cloud.google.com/go/compute/metadata v0.3.0 h1:Tz+eQXMEqDIKRsmY3cHTL6FVaynIjX2QxYC4trgAKZc=
cloud.google.com/go/compute/metadata v0.3.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k=
cloud.google.com/go/compute/metadata v0.4.0 h1:vHzJCWaM4g8XIcm8kopr3XmDA4Gy/lblD3EhhSux05c=
cloud.google.com/go/compute/metadata v0.4.0/go.mod h1:SIQh1Kkb4ZJ8zJ874fqVkslA29PRXuleyj6vOzlbK7M=
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
cloud.google.com/go/iam v1.1.8 h1:r7umDwhj+BQyz0ScZMp4QrGXjSTI3ZINnpgU2nlB/K0=
cloud.google.com/go/iam v1.1.8/go.mod h1:GvE6lyMmfxXauzNq8NbgJbeVQNspG+tcdL/W8QO1+zE=
cloud.google.com/go/iam v1.1.10 h1:ZSAr64oEhQSClwBL670MsJAW5/RLiC6kfw3Bqmd5ZDI=
cloud.google.com/go/iam v1.1.10/go.mod h1:iEgMq62sg8zx446GCaijmA2Miwg5o3UbO+nI47WHJps=
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
@ -54,54 +46,30 @@ github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuy
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
github.com/aws/aws-sdk-go-v2 v1.27.2 h1:pLsTXqX93rimAOZG2FIYraDQstZaaGVVN4tNw65v0h8=
github.com/aws/aws-sdk-go-v2 v1.27.2/go.mod h1:ffIFB97e2yNsv4aTSGkqtHnppsIJzw7G7BReUZ3jCXM=
github.com/aws/aws-sdk-go-v2 v1.30.1 h1:4y/5Dvfrhd1MxRDD77SrfsDaj8kUkkljU7XE83NPV+o=
github.com/aws/aws-sdk-go-v2 v1.30.1/go.mod h1:nIQjQVp5sfpQcTc9mPSr1B0PaWK5ByX9MOoDadSN4lc=
github.com/aws/aws-sdk-go-v2/config v1.27.18 h1:wFvAnwOKKe7QAyIxziwSKjmer9JBMH1vzIL6W+fYuKk=
github.com/aws/aws-sdk-go-v2/config v1.27.18/go.mod h1:0xz6cgdX55+kmppvPm2IaKzIXOheGJhAufacPJaXZ7c=
github.com/aws/aws-sdk-go-v2/config v1.27.24 h1:NM9XicZ5o1CBU/MZaHwFtimRpWx9ohAUAqkG6AqSqPo=
github.com/aws/aws-sdk-go-v2/config v1.27.24/go.mod h1:aXzi6QJTuQRVVusAO8/NxpdTeTyr/wRcybdDtfUwJSs=
github.com/aws/aws-sdk-go-v2/credentials v1.17.18 h1:D/ALDWqK4JdY3OFgA2thcPO1c9aYTT5STS/CvnkqY1c=
github.com/aws/aws-sdk-go-v2/credentials v1.17.18/go.mod h1:JuitCWq+F5QGUrmMPsk945rop6bB57jdscu+Glozdnc=
github.com/aws/aws-sdk-go-v2/credentials v1.17.24 h1:YclAsrnb1/GTQNt2nzv+756Iw4mF8AOzcDfweWwwm/M=
github.com/aws/aws-sdk-go-v2/credentials v1.17.24/go.mod h1:Hld7tmnAkoBQdTMNYZGzztzKRdA4fCdn9L83LOoigac=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.5 h1:dDgptDO9dxeFkXy+tEgVkzSClHZje/6JkPW5aZyEvrQ=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.5/go.mod h1:gjvE2KBUgUQhcv89jqxrIxH9GaKs1JbZzWejj/DaHGA=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.9 h1:Aznqksmd6Rfv2HQN9cpqIV/lQRMaIpJkLLaJ1ZI76no=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.9/go.mod h1:WQr3MY7AxGNxaqAtsDWn+fBxmd4XvLkzeqQ8P1VM0/w=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.9 h1:cy8ahBJuhtM8GTTSyOkfy6WVPV1IE+SS5/wfXUYuulw=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.9/go.mod h1:CZBXGLaJnEZI6EVNcPd7a6B5IC5cA/GkRWtu9fp3S6Y=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.13 h1:5SAoZ4jYpGH4721ZNoS1znQrhOfZinOhc4XuTXx/nVc=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.13/go.mod h1:+rdA6ZLpaSeM7tSg/B0IEDinCIBJGmW8rKDFkYpP04g=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.9 h1:A4SYk07ef04+vxZToz9LWvAXl9LW0NClpPpMsi31cz0=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.9/go.mod h1:5jJcHuwDagxN+ErjQ3PU3ocf6Ylc/p9x+BLO/+X4iXw=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.13 h1:WIijqeaAO7TYFLbhsZmi2rgLEAtWOC1LhxCAVTJlSKw=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.13/go.mod h1:i+kbfa76PQbWw/ULoWnp51EYVWH4ENln76fLQE3lXT8=
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0 h1:hT8rVHwugYE2lEfdFE0QWVo81lF7jMrYJVDWI+f+VxU=
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0/go.mod h1:8tu/lYfQfFe6IGnaOdrpVgEL2IrrDOf6/m9RQum4NkY=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.2 h1:Ji0DY1xUsUr3I8cHps0G+XM3WWU16lP6yG8qu1GAZAs=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.2/go.mod h1:5CsjAbs3NlGQyZNFACh+zztPDI7fU6eW9QsxjfnuBKg=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.3 h1:dT3MqvGhSoaIhRseqw2I0yH81l7wiR2vjs57O51EAm8=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.3/go.mod h1:GlAeCkHwugxdHaueRr4nhPuY+WW+gR8UjlcqzPr1SPI=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.11 h1:o4T+fKxA3gTMcluBNZZXE9DNaMkJuUL1O3mffCUjoJo=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.11/go.mod h1:84oZdJ+VjuJKs9v1UTC9NaodRZRseOXCTgku+vQJWR8=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.15 h1:I9zMeF107l0rJrpnHpjEiiTSCKYAIw8mALiXcPsGBiA=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.15/go.mod h1:9xWJ3Q/S6Ojusz1UIkfycgD1mGirJfLLKqq3LPT7WN8=
github.com/aws/aws-sdk-go-v2/service/sso v1.20.11 h1:gEYM2GSpr4YNWc6hCd5nod4+d4kd9vWIAWrmGuLdlMw=
github.com/aws/aws-sdk-go-v2/service/sso v1.20.11/go.mod h1:gVvwPdPNYehHSP9Rs7q27U1EU+3Or2ZpXvzAYJNh63w=
github.com/aws/aws-sdk-go-v2/service/sso v1.22.1 h1:p1GahKIjyMDZtiKoIn0/jAj/TkMzfzndDv5+zi2Mhgc=
github.com/aws/aws-sdk-go-v2/service/sso v1.22.1/go.mod h1:/vWdhoIoYA5hYoPZ6fm7Sv4d8701PiG5VKe8/pPJL60=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.24.5 h1:iXjh3uaH3vsVcnyZX7MqCoCfcyxIrVE9iOQruRaWPrQ=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.24.5/go.mod h1:5ZXesEuy/QcO0WUnt+4sDkxhdXRHTu2yG0uCSH8B6os=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.2 h1:ORnrOK0C4WmYV/uYt3koHEWBLYsRDwk2Np+eEoyV4Z0=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.2/go.mod h1:xyFHA4zGxgYkdD73VeezHt3vSKEG9EmFnGwoKlP00u4=
github.com/aws/aws-sdk-go-v2/service/sts v1.28.12 h1:M/1u4HBpwLuMtjlxuI2y6HoVLzF5e2mfxHCg7ZVMYmk=
github.com/aws/aws-sdk-go-v2/service/sts v1.28.12/go.mod h1:kcfd+eTdEi/40FIbLq4Hif3XMXnl5b/+t/KTfLt9xIk=
github.com/aws/aws-sdk-go-v2/service/sts v1.30.1 h1:+woJ607dllHJQtsnJLi52ycuqHMwlW+Wqm2Ppsfp4nQ=
github.com/aws/aws-sdk-go-v2/service/sts v1.30.1/go.mod h1:jiNR3JqT15Dm+QWq2SRgh0x0bCNSRP2L25+CqPNpJlQ=
github.com/aws/smithy-go v1.20.2 h1:tbp628ireGtzcHDDmLT/6ADHidqnwgF57XOXZe6tp4Q=
github.com/aws/smithy-go v1.20.2/go.mod h1:krry+ya/rV9RDcV/Q16kpu6ypI4K2czasz0NC3qS14E=
github.com/aws/smithy-go v1.20.3 h1:ryHwveWzPV5BIof6fyDvor6V3iUL7nTfiTKXHiW05nE=
github.com/aws/smithy-go v1.20.3/go.mod h1:krry+ya/rV9RDcV/Q16kpu6ypI4K2czasz0NC3qS14E=
github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8=
@ -113,8 +81,6 @@ github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6r
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
@ -155,8 +121,6 @@ github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG
github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs=
github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ=
github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
@ -246,8 +210,6 @@ github.com/googleapis/enterprise-certificate-proxy v0.3.2 h1:Vie5ybvEvT75RniqhfF
github.com/googleapis/enterprise-certificate-proxy v0.3.2/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/googleapis/gax-go/v2 v2.12.4 h1:9gWcmF85Wvq4ryPFvGFaOgPIs1AQX0d0bcbGw4Z96qg=
github.com/googleapis/gax-go/v2 v2.12.4/go.mod h1:KYEYLorsnIGDi/rPC8b5TdlB9kbKoFubselGIoBMCwI=
github.com/googleapis/gax-go/v2 v2.12.5 h1:8gw9KZK8TiVKB6q3zHY3SBzLnrGp6HQjyfYBYGmXdxA=
github.com/googleapis/gax-go/v2 v2.12.5/go.mod h1:BUDKcWo+RaKq5SC9vVYL0wLADa3VcfswbOMMRmB9H3E=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
@ -255,12 +217,8 @@ github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU=
github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
github.com/infisical/go-sdk v0.1.9 h1:o9LUj0Tyn6OHusTEKEKQ4+PulJViAxgOrFa+SlwGJFc=
github.com/infisical/go-sdk v0.1.9/go.mod h1:vHTDVw3k+wfStXab513TGk1n53kaKF2xgLqpw/xvtl4=
github.com/infisical/go-sdk v0.2.0 h1:n1/KNdYpeQavSqVwC9BfeV8VRzf3N2X9zO1tzQOSj5Q=
github.com/infisical/go-sdk v0.2.0/go.mod h1:vHTDVw3k+wfStXab513TGk1n53kaKF2xgLqpw/xvtl4=
github.com/infisical/go-sdk v0.2.1 h1:z8DQ3tLV/MdxTAnXXIkll45nQcK+RK8+eoImW6J7m/g=
github.com/infisical/go-sdk v0.2.1/go.mod h1:vHTDVw3k+wfStXab513TGk1n53kaKF2xgLqpw/xvtl4=
github.com/infisical/go-sdk v0.3.2 h1:BfeQzG7s3qmEGhgXu0d1YNsyaiHucHgI+BaLpx+W8cc=
github.com/infisical/go-sdk v0.3.2/go.mod h1:vHTDVw3k+wfStXab513TGk1n53kaKF2xgLqpw/xvtl4=
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
@ -375,24 +333,14 @@ go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0 h1:4Pp6oUg3+e/6M4C0A/3kJ2VYa++dsWVTtGgLVj5xtHg=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0/go.mod h1:Mjt1i1INqiaoZOMGR1RIUJN+i3ChKoFRqzrRQhlkbs0=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.53.0 h1:9G6E0TXzGFVfTnawRzrPl83iHOAV7L8NJiR8RSGYV1g=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.53.0/go.mod h1:azvtTADFQJA8mX80jIH/akaE7h+dbm/sVuaHqN13w74=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 h1:jq9TW8u3so/bN+JPT166wjOI6/vQPF6Xe7nMNIltagk=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0/go.mod h1:p8pYQP+m5XfbZm9fxtSKAbM6oIllS7s2AfxrChvc7iw=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0 h1:4K4tsIXefpVJtvA/8srF4V4y0akAoPHkIslgAkjixJA=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0/go.mod h1:jjdQuTGVsXV4vSs+CJ2qYDeDPf9yIJV23qlIzBm73Vg=
go.opentelemetry.io/otel v1.24.0 h1:0LAOdjNmQeSTzGBzduGe/rU4tZhMwL5rWgtp9Ku5Jfo=
go.opentelemetry.io/otel v1.24.0/go.mod h1:W7b9Ozg4nkF5tWI5zsXkaKKDjdVjpD4oAt9Qi/MArHo=
go.opentelemetry.io/otel v1.28.0 h1:/SqNcYk+idO0CxKEUOtKQClMK/MimZihKYMruSMViUo=
go.opentelemetry.io/otel v1.28.0/go.mod h1:q68ijF8Fc8CnMHKyzqL6akLO46ePnjkgfIMIjUIX9z4=
go.opentelemetry.io/otel/metric v1.24.0 h1:6EhoGWWK28x1fbpA4tYTOWBkPefTDQnb8WSGXlc88kI=
go.opentelemetry.io/otel/metric v1.24.0/go.mod h1:VYhLe1rFfxuTXLgj4CBiyz+9WYBA8pNGJgDcSFRKBco=
go.opentelemetry.io/otel/metric v1.28.0 h1:f0HGvSl1KRAU1DLgLGFjrwVyismPlnuU6JD6bOeuA5Q=
go.opentelemetry.io/otel/metric v1.28.0/go.mod h1:Fb1eVBFZmLVTMb6PPohq3TO9IIhUisDsbJoL/+uQW4s=
go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y1YELI=
go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU=
go.opentelemetry.io/otel/trace v1.28.0 h1:GhQ9cUuQGmNDd5BTCP2dAvv75RdMxEfTmYejp+lkx9g=
go.opentelemetry.io/otel/trace v1.28.0/go.mod h1:jPyXzNPg6da9+38HEwElrQiHlVMTnVfM3/yv2OlIHaI=
go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw=
@ -413,7 +361,6 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI=
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30=
golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M=
@ -487,7 +434,6 @@ golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac=
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys=
golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE=
@ -559,7 +505,6 @@ golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI=
golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
@ -568,7 +513,6 @@ golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuX
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
golang.org/x/term v0.20.0 h1:VnkxpohqXaOBYJtBmEppKUG6mXpi+4O6purfc2+sMhw=
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
golang.org/x/term v0.22.0 h1:BbsgPEJULsl2fV/AT3v15Mjva5yXKQDyKf+TbDz7QJk=
golang.org/x/term v0.22.0/go.mod h1:F3qCibpT5AMpCRfhfT53vVJwhLtIVHhB9XDjfFvnMI4=
@ -582,7 +526,6 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk=
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
@ -658,8 +601,6 @@ google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0M
google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=
google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
google.golang.org/api v0.183.0 h1:PNMeRDwo1pJdgNcFQ9GstuLe/noWKIc89pRWRLMvLwE=
google.golang.org/api v0.183.0/go.mod h1:q43adC5/pHoSZTx5h2mSmdF7NcyfW9JuDyIOJAgS9ZQ=
google.golang.org/api v0.187.0 h1:Mxs7VATVC2v7CY+7Xwm4ndkX71hpElcvx0D1Ji/p1eo=
google.golang.org/api v0.187.0/go.mod h1:KIHlTc4x7N7gKKuVsdmfBXN13yEEWXWFURWY6SBp2gk=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
@ -698,12 +639,8 @@ google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6D
google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20201019141844-1ed22bb0c154/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto/googleapis/api v0.0.0-20240521202816-d264139d666e h1:SkdGTrROJl2jRGT/Fxv5QUf9jtdKCQh4KQJXbXVLAi0=
google.golang.org/genproto/googleapis/api v0.0.0-20240521202816-d264139d666e/go.mod h1:LweJcLbyVij6rCex8YunD8DYR5VDonap/jYl3ZRxcIU=
google.golang.org/genproto/googleapis/api v0.0.0-20240708141625-4ad9e859172b h1:y/kpOWeX2pWERnbsvh/hF+Zmo69wVmjyZhstreXQQeA=
google.golang.org/genproto/googleapis/api v0.0.0-20240708141625-4ad9e859172b/go.mod h1:mw8MG/Qz5wfgYr6VqVCiZcHe/GJEfI+oGGDCohaVgB0=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240528184218-531527333157 h1:Zy9XzmMEflZ/MAaA7vNcoebnRAld7FsPW1EeBB7V0m8=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240528184218-531527333157/go.mod h1:EfXuqaE1J41VCDicxHzUDm+8rk+7ZdXzHV0IhO/I6s0=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240708141625-4ad9e859172b h1:04+jVzTs2XBnOZcPsLnmrTGqltqJbZQ1Ey26hjYdQQ0=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240708141625-4ad9e859172b/go.mod h1:Ue6ibwXGpU+dqIcODieyLOcgj7z8+IcskoNIgZxtrFY=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
@ -719,8 +656,6 @@ google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3Iji
google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
google.golang.org/grpc v1.64.0 h1:KH3VH9y/MgNQg1dE7b3XfVK0GsPSIzJwdF617gUSbvY=
google.golang.org/grpc v1.64.0/go.mod h1:oxjF8E3FBnjp+/gVFYdWacaLDx9na1aqy9oovLpxQYg=
google.golang.org/grpc v1.65.0 h1:bs/cUb4lp1G5iImFFd3u5ixQzweKizoZJAwBNLR42lc=
google.golang.org/grpc v1.65.0/go.mod h1:WgYC2ypjlB0EiQi6wdKixMqukr6lBc0Vo+oOgjrM5ZQ=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
@ -735,8 +670,6 @@ google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGj
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg=
google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=