Compare commits

...

104 Commits

Author SHA1 Message Date
Sheen Capadngan
a618e0ebf2 fix: resolved gitlab integration creation issue regarding groups 2024-05-28 19:43:00 +08:00
Vladyslav Matsiiako
c638caede5 changed sidebar color 2024-05-27 22:32:54 -07:00
Vladyslav Matsiiako
300deb5607 added time off page to company wiki 2024-05-27 22:26:21 -07:00
Vladyslav Matsiiako
1e63604f1e added more styling to docs 2024-05-27 16:03:42 -07:00
Vladyslav Matsiiako
6ce86c4240 added more styling to docs 2024-05-27 16:00:39 -07:00
Vladyslav Matsiiako
fd65936ae7 Merge branch 'main' of https://github.com/Infisical/infisical 2024-05-27 15:59:14 -07:00
Daniel Hougaard
c894a18797 Merge pull request #1718 from Infisical/daniel/mi-ux-fix-2
Fix: Machine Identities user experience improvement
2024-05-28 00:34:17 +02:00
Vladyslav Matsiiako
c170ba6249 changed docs design 2024-05-27 15:32:56 -07:00
Daniel Hougaard
c344330c93 Merge pull request #1882 from Infisical/daniel/azure-auth-sdk-docs
Docs: Azure & Kubernetes auth SDK documentation
2024-05-28 00:18:16 +02:00
Daniel Hougaard
a6dd36f684 Docs: Azure documentation 2024-05-28 00:11:57 +02:00
Daniel Hougaard
eb8acba037 Docs: Azure documentation 2024-05-28 00:11:55 +02:00
Daniel Hougaard
c7a8e1102e Docs: Azure documentation 2024-05-28 00:11:52 +02:00
Daniel Hougaard
aca71a7b6f Docs: Azure documentation 2024-05-28 00:11:49 +02:00
Daniel Hougaard
ae075df0ec Fix: Old docs 2024-05-28 00:11:33 +02:00
Daniel Hougaard
75927f711c Merge pull request #1863 from Infisical/daniel/auth-sdk-docs
Docs: Updated docs to reflect new SDK structure
2024-05-27 23:29:03 +02:00
BlackMagiq
b1b1ce07a3 Merge pull request #1878 from Infisical/create-pull-request/patch-1716795461
GH Action: rename new migration file timestamp
2024-05-27 11:10:48 -07:00
Sheen Capadngan
81f7884d03 Merge pull request #1881 from Infisical/fix/resolved-identity-deletion-issue-across-projects
fix: resolved identity deletion issue across projects
2024-05-28 00:28:18 +08:00
Sheen Capadngan
b8c35fbf15 fix: resolved identity deletion issue across projects 2024-05-28 00:13:47 +08:00
Sheen Capadngan
42e73d66fc Merge pull request #1873 from Infisical/feat/add-personal-overrides-and-secret-reference-to-download-envs
feat: added personal overrides and support for secret ref during download
2024-05-27 23:09:59 +08:00
github-actions
fe40e4f475 chore: renamed new migration files to latest timestamp (gh-action) 2024-05-27 07:37:39 +00:00
BlackMagiq
b9782c1a85 Merge pull request #1833 from Infisical/azure-auth
Azure Native Authentication Method
2024-05-27 00:37:09 -07:00
Vladyslav Matsiiako
a0be2985dd Added money page 2024-05-26 22:19:42 -07:00
vmatsiiako
86d16c5b9f Merge pull request #1877 from Infisical/sheensantoscapadngan-patch-1
Update onboarding.mdx
2024-05-26 22:05:12 -07:00
Sheen Capadngan
c1c1471439 Update onboarding.mdx 2024-05-27 12:28:14 +08:00
vmatsiiako
527e1d6b79 Merge pull request #1876 from Infisical/aws-integration-patch
added company handbook
2024-05-26 16:16:18 -07:00
Vladyslav Matsiiako
3e32915a82 added company handbook 2024-05-26 16:14:37 -07:00
Maidul Islam
4faa9ced04 Merge pull request #1837 from akhilmhdh/feat/resource-daily-prune
Daily cron for cleaning up expired tokens from db
2024-05-24 12:53:26 -04:00
Maidul Islam
b6ff07b605 revert repete cron 2024-05-24 12:45:19 -04:00
Maidul Islam
1753cd76be update delete access token logic 2024-05-24 12:43:14 -04:00
Sheen Capadngan
f75fc54e10 Merge pull request #1870 from Infisical/doc/updated-gcp-secrets-manager-doc-reminder
doc: added reminder for GCP oauth user permissions
2024-05-25 00:00:15 +08:00
Maidul Islam
966bd77234 Update gcp-secret-manager.mdx 2024-05-24 11:55:29 -04:00
Sheen Capadngan
c782df1176 Merge pull request #1872 from Infisical/fix/resolve-cloudflare-pages-integration
fix: resolved cloudflare pages integration
2024-05-24 23:50:57 +08:00
Sheen Capadngan
efe10e361f feat: added personal overrides and support for secret ref to download envs 2024-05-24 22:14:32 +08:00
Sheen Capadngan
e9c5b7f846 Merge pull request #1871 from Infisical/fix/address-json-drop-behavior
fix: address json drag behavior
2024-05-24 21:46:33 +08:00
Sheen Capadngan
008b37c0f4 fix: resolved cloudflare pages integration 2024-05-24 19:45:20 +08:00
Sheen Capadngan
c9b234dbea fix: address json drag behavior 2024-05-24 17:42:38 +08:00
Sheen Capadngan
049df6abec Merge pull request #1869 from Infisical/misc/made-aws-sm-mapping-plaintext-one-to-one
misc: made aws sm mapping one to one plaintext
2024-05-24 02:15:04 +08:00
Sheen Capadngan
8497182a7b misc: finalized addition 2024-05-24 02:11:03 +08:00
Sheen Capadngan
133841c322 doc: added reminder for oauth user permissions 2024-05-24 01:55:59 +08:00
Sheen Capadngan
e7c5645aa9 misc: made aws sm mapping one to one plaintext 2024-05-24 00:35:55 +08:00
Sheen Capadngan
0bc778b9bf Merge pull request #1865 from Infisical/feat/add-one-to-one-support-for-aws-sm
feat: added one to one support for aws secret manager integration
2024-05-23 23:48:47 +08:00
Sheen Capadngan
b0bc41da14 misc: finalized schema type 2024-05-23 22:18:24 +08:00
Daniel Hougaard
a234b686c2 Merge pull request #1867 from Infisical/daniel/better-no-project-found-error
Fix: Better error message on project not found during bot lookup
2024-05-23 15:54:14 +02:00
Daniel Hougaard
6230167794 Update project-bot-fns.ts 2024-05-23 15:48:54 +02:00
Daniel Hougaard
68d1849ba0 Fix: Better error message on project not found during bot lookup 2024-05-23 15:47:08 +02:00
Daniel Hougaard
5c10427eaf Merge pull request #1866 from Infisical/daniel/fix-no-orgs-selectable
Fix: No orgs selectable if a user has been removed from an organization
2024-05-23 15:19:13 +02:00
Daniel Hougaard
290d99e02c Fix: No orgs selectable if a user has been removed from an organization 2024-05-23 15:11:20 +02:00
Sheen Capadngan
b75d601754 misc: documentation changes and minor UI adjustments 2024-05-23 21:03:48 +08:00
Sheen Capadngan
de2a5b4255 feat: added one to one support for aws SM integration 2024-05-23 20:30:55 +08:00
Daniel Hougaard
3d65d121c0 docs: updated docs to reflect new SDK structure 2024-05-23 04:45:33 +02:00
Maidul Islam
663f8abc51 bring back last updated at for service token 2024-05-22 20:44:07 -04:00
Maidul Islam
941a71efaf add index for expire at needed for pruning 2024-05-22 20:38:04 -04:00
Maidul Islam
19bbc2ab26 add secrets index 2024-05-22 19:04:44 -04:00
Maidul Islam
f4de52e714 add index on secret snapshot folders 2024-05-22 18:15:04 -04:00
Maidul Islam
0b87121b67 add index to secret-snapshot-secret for field snapshotId 2024-05-22 17:46:16 -04:00
Maidul Islam
e649667da8 add indexs to secret versions and secret snapshot secrets 2024-05-22 16:52:18 -04:00
Maidul Islam
6af4b3f64c add index for audit logs 2024-05-22 15:48:24 -04:00
Maidul Islam
efcc248486 add tx to find ghost user 2024-05-22 14:54:20 -04:00
Maidul Islam
82eeae6030 track identity 2024-05-22 13:34:44 -04:00
Maidul Islam
440c77965c add logs to track permission inject 2024-05-21 22:35:13 -04:00
Maidul Islam
880289217e revert identity based rate limit 2024-05-21 22:24:53 -04:00
Maidul Islam
d0947f1040 update service tokens 2024-05-21 22:02:01 -04:00
Sheen Capadngan
303edadb1e Merge pull request #1848 from Infisical/feat/add-integration-sync-status
feat: added integration sync status
2024-05-22 01:19:36 +08:00
Maidul Islam
50155a610d Merge pull request #1858 from Infisical/misc/address-digital-ocean-env-encryption
misc: made digital ocean envs encrypted by default
2024-05-21 13:15:09 -04:00
Sheen Capadngan
c2830a56b6 misc: made digital ocean envs encrypted by default 2024-05-22 01:12:28 +08:00
Maidul Islam
e7f7f271c8 Merge pull request #1857 from Infisical/misc/added-pino-logger-redaction
misc: added logger redaction
2024-05-21 11:49:36 -04:00
Sheen Capadngan
b26e96c5a2 misc: added logger redaction 2024-05-21 23:04:11 +08:00
Tuan Dang
4e06fa3a0c Move azure auth migration file to front 2024-05-20 21:15:42 -07:00
Tuan Dang
0f827fc31a Merge remote-tracking branch 'origin' into azure-auth 2024-05-20 21:14:30 -07:00
vmatsiiako
3d4aa0fdc9 Merge pull request #1853 from Infisical/daniel/jenkins-docs
Docs: Jenkins Plugin
2024-05-20 20:13:42 -07:00
Daniel Hougaard
711e30a6be Docs: Plugin installation 2024-05-21 04:58:24 +02:00
Daniel Hougaard
7b1462fdee Docs: Updated Jenkins docs to reflect new plugin 2024-05-21 04:56:26 +02:00
Daniel Hougaard
50915833ff Images 2024-05-21 04:56:15 +02:00
Maidul Islam
44e37fd531 update distinct id for service tokens 2024-05-20 19:50:52 -04:00
Maidul Islam
fa3f957738 count for null actor 2024-05-20 19:26:03 -04:00
Maidul Islam
224b26ced6 Merge pull request #1852 from Infisical/rate-limit-based-on-identity
rate limit based on identity
2024-05-20 19:04:41 -04:00
Maidul Islam
e833d9e67c revert secret read limit 2024-05-20 19:01:01 -04:00
Maidul Islam
dc08edb7d2 rate limit based on identity 2024-05-20 18:52:23 -04:00
Maidul Islam
0b78e30848 Delete mongo infisical helm 2024-05-20 16:27:15 -04:00
Tuan Dang
7189544705 Merge branch 'azure-auth' of https://github.com/Infisical/infisical into azure-auth 2024-05-20 08:41:19 -07:00
Tuan Dang
a724ab101c Fix identities docs markings 2024-05-20 08:39:10 -07:00
Tuan Dang
dea67e3cb0 Update azure auth based on review 2024-05-19 22:24:26 -07:00
Tuan Dang
ce66cccd8b Fix merge conflicts 2024-05-19 22:19:49 -07:00
Tuan Dang
7e2147f14e Adjust aws iam auth docs 2024-05-19 22:05:38 -07:00
Daniel Hougaard
91eda2419a Update machine-identities.mdx 2024-05-20 00:32:10 +02:00
Akhil Mohan
32f39c98a7 Merge pull request #1842 from akhilmhdh/feat/membership-by-id
Endpoints for retreiving membership details
2024-05-19 23:51:30 +05:30
Maidul Islam
ddf6db5a7e small rephrase 2024-05-19 14:19:42 -04:00
Tuan Dang
b350eef2b9 Add access token trusted ip support for azure auth 2024-05-17 15:43:12 -07:00
Tuan Dang
85725215f2 Merge remote-tracking branch 'origin' into azure-auth 2024-05-17 15:41:58 -07:00
=
ce4cb39a2d docs: added doc for new endpoints of getting membership and some title change 2024-05-17 20:49:58 +05:30
=
84724e5f65 feat: added endpoints to fetch a particule project user membership and identity 2024-05-17 20:45:31 +05:30
=
56c2e12760 feat: added create identity project membership to api reference and support for roles 2024-05-17 17:09:35 +05:30
=
21656a7ab6 docs: seperate project user and identities api into seperate 2024-05-17 16:15:52 +05:30
=
2ccc77ef40 feat: split project api description for identities and users into seperate 2024-05-17 16:15:05 +05:30
=
76c9d642a9 fix: resolved identity check failing due to comma seperated header in ip 2024-05-16 15:46:19 +05:30
=
3ed5dd6109 feat: removed audit log queue and switched to resource clean up queue 2024-05-16 15:46:19 +05:30
=
08e7815ec1 feat: added increment and decrement ops in update knex orm 2024-05-16 15:46:19 +05:30
=
04d961b832 feat: added dal to remove expired token for queue and fixed token validation check missing num uses increment and maxTTL failed check 2024-05-16 15:46:18 +05:30
Tuan Dang
9c0a1b7089 Merge remote-tracking branch 'origin' into azure-auth 2024-05-15 23:23:50 -07:00
Tuan Dang
9352e8bca0 Add docs for Azure auth 2024-05-15 23:13:19 -07:00
Tuan Dang
265932df20 Finish preliminary azure auth method 2024-05-15 16:30:42 -07:00
vmatsiiako
f23056bcbc Update IdentityTable.tsx 2024-05-08 09:20:30 -07:00
Daniel Hougaard
fdf5fcad0a Update IdentityTable.tsx 2024-04-22 23:09:46 +02:00
Daniel Hougaard
a85c59e3e2 Fix: Improve user experience for machine identities 2024-04-22 22:00:37 +02:00
123 changed files with 4137 additions and 1941 deletions

View File

@@ -33,6 +33,7 @@ import { TGroupProjectServiceFactory } from "@app/services/group-project/group-p
import { TIdentityServiceFactory } from "@app/services/identity/identity-service";
import { TIdentityAccessTokenServiceFactory } from "@app/services/identity-access-token/identity-access-token-service";
import { TIdentityAwsAuthServiceFactory } from "@app/services/identity-aws-auth/identity-aws-auth-service";
import { TIdentityAzureAuthServiceFactory } from "@app/services/identity-azure-auth/identity-azure-auth-service";
import { TIdentityGcpAuthServiceFactory } from "@app/services/identity-gcp-auth/identity-gcp-auth-service";
import { TIdentityKubernetesAuthServiceFactory } from "@app/services/identity-kubernetes-auth/identity-kubernetes-auth-service";
import { TIdentityProjectServiceFactory } from "@app/services/identity-project/identity-project-service";
@@ -121,6 +122,7 @@ declare module "fastify" {
identityKubernetesAuth: TIdentityKubernetesAuthServiceFactory;
identityGcpAuth: TIdentityGcpAuthServiceFactory;
identityAwsAuth: TIdentityAwsAuthServiceFactory;
identityAzureAuth: TIdentityAzureAuthServiceFactory;
accessApprovalPolicy: TAccessApprovalPolicyServiceFactory;
accessApprovalRequest: TAccessApprovalRequestServiceFactory;
secretApprovalPolicy: TSecretApprovalPolicyServiceFactory;

View File

@@ -62,6 +62,9 @@ import {
TIdentityAwsAuths,
TIdentityAwsAuthsInsert,
TIdentityAwsAuthsUpdate,
TIdentityAzureAuths,
TIdentityAzureAuthsInsert,
TIdentityAzureAuthsUpdate,
TIdentityGcpAuths,
TIdentityGcpAuthsInsert,
TIdentityGcpAuthsUpdate,
@@ -356,6 +359,11 @@ declare module "knex/types/tables" {
TIdentityAwsAuthsInsert,
TIdentityAwsAuthsUpdate
>;
[TableName.IdentityAzureAuth]: Knex.CompositeTableType<
TIdentityAzureAuths,
TIdentityAzureAuthsInsert,
TIdentityAzureAuthsUpdate
>;
[TableName.IdentityUaClientSecret]: Knex.CompositeTableType<
TIdentityUaClientSecrets,
TIdentityUaClientSecretsInsert,

View File

@@ -0,0 +1,26 @@
import { Knex } from "knex";
import { TableName } from "../schemas";
export async function up(knex: Knex): Promise<void> {
const doesOrgIdExist = await knex.schema.hasColumn(TableName.AuditLog, "orgId");
const doesProjectIdExist = await knex.schema.hasColumn(TableName.AuditLog, "projectId");
if (await knex.schema.hasTable(TableName.AuditLog)) {
await knex.schema.alterTable(TableName.AuditLog, (t) => {
if (doesProjectIdExist) t.index("projectId");
if (doesOrgIdExist) t.index("orgId");
});
}
}
export async function down(knex: Knex): Promise<void> {
const doesOrgIdExist = await knex.schema.hasColumn(TableName.AuditLog, "orgId");
const doesProjectIdExist = await knex.schema.hasColumn(TableName.AuditLog, "projectId");
if (await knex.schema.hasTable(TableName.AuditLog)) {
await knex.schema.alterTable(TableName.AuditLog, (t) => {
if (doesProjectIdExist) t.dropIndex("projectId");
if (doesOrgIdExist) t.dropIndex("orgId");
});
}
}

View File

@@ -0,0 +1,22 @@
import { Knex } from "knex";
import { TableName } from "../schemas";
export async function up(knex: Knex): Promise<void> {
const doesEnvIdExist = await knex.schema.hasColumn(TableName.SnapshotSecret, "envId");
if (await knex.schema.hasTable(TableName.SnapshotSecret)) {
await knex.schema.alterTable(TableName.SnapshotSecret, (t) => {
if (doesEnvIdExist) t.index("envId");
});
}
}
export async function down(knex: Knex): Promise<void> {
const doesEnvIdExist = await knex.schema.hasColumn(TableName.SnapshotSecret, "envId");
if (await knex.schema.hasTable(TableName.SnapshotSecret)) {
await knex.schema.alterTable(TableName.SnapshotSecret, (t) => {
if (doesEnvIdExist) t.dropIndex("envId");
});
}
}

View File

@@ -0,0 +1,22 @@
import { Knex } from "knex";
import { TableName } from "../schemas";
export async function up(knex: Knex): Promise<void> {
const doesEnvIdExist = await knex.schema.hasColumn(TableName.SecretVersion, "envId");
if (await knex.schema.hasTable(TableName.SecretVersion)) {
await knex.schema.alterTable(TableName.SecretVersion, (t) => {
if (doesEnvIdExist) t.index("envId");
});
}
}
export async function down(knex: Knex): Promise<void> {
const doesEnvIdExist = await knex.schema.hasColumn(TableName.SecretVersion, "envId");
if (await knex.schema.hasTable(TableName.SecretVersion)) {
await knex.schema.alterTable(TableName.SecretVersion, (t) => {
if (doesEnvIdExist) t.dropIndex("envId");
});
}
}

View File

@@ -0,0 +1,21 @@
import { Knex } from "knex";
import { TableName } from "../schemas";
export async function up(knex: Knex): Promise<void> {
const doesSnapshotIdExist = await knex.schema.hasColumn(TableName.SnapshotSecret, "snapshotId");
if (await knex.schema.hasTable(TableName.SnapshotSecret)) {
await knex.schema.alterTable(TableName.SnapshotSecret, (t) => {
if (doesSnapshotIdExist) t.index("snapshotId");
});
}
}
export async function down(knex: Knex): Promise<void> {
const doesSnapshotIdExist = await knex.schema.hasColumn(TableName.SnapshotSecret, "snapshotId");
if (await knex.schema.hasTable(TableName.SnapshotSecret)) {
await knex.schema.alterTable(TableName.SnapshotSecret, (t) => {
if (doesSnapshotIdExist) t.dropIndex("snapshotId");
});
}
}

View File

@@ -0,0 +1,21 @@
import { Knex } from "knex";
import { TableName } from "../schemas";
export async function up(knex: Knex): Promise<void> {
const doesSnapshotIdExist = await knex.schema.hasColumn(TableName.SnapshotFolder, "snapshotId");
if (await knex.schema.hasTable(TableName.SnapshotFolder)) {
await knex.schema.alterTable(TableName.SnapshotFolder, (t) => {
if (doesSnapshotIdExist) t.index("snapshotId");
});
}
}
export async function down(knex: Knex): Promise<void> {
const doesSnapshotIdExist = await knex.schema.hasColumn(TableName.SnapshotFolder, "snapshotId");
if (await knex.schema.hasTable(TableName.SnapshotFolder)) {
await knex.schema.alterTable(TableName.SnapshotFolder, (t) => {
if (doesSnapshotIdExist) t.dropIndex("snapshotId");
});
}
}

View File

@@ -0,0 +1,24 @@
import { Knex } from "knex";
import { TableName } from "../schemas";
export async function up(knex: Knex): Promise<void> {
const doesFolderIdExist = await knex.schema.hasColumn(TableName.Secret, "folderId");
const doesUserIdExist = await knex.schema.hasColumn(TableName.Secret, "userId");
if (await knex.schema.hasTable(TableName.Secret)) {
await knex.schema.alterTable(TableName.Secret, (t) => {
if (doesFolderIdExist && doesUserIdExist) t.index(["folderId", "userId"]);
});
}
}
export async function down(knex: Knex): Promise<void> {
const doesFolderIdExist = await knex.schema.hasColumn(TableName.Secret, "folderId");
const doesUserIdExist = await knex.schema.hasColumn(TableName.Secret, "userId");
if (await knex.schema.hasTable(TableName.Secret)) {
await knex.schema.alterTable(TableName.Secret, (t) => {
if (doesUserIdExist && doesFolderIdExist) t.dropIndex(["folderId", "userId"]);
});
}
}

View File

@@ -0,0 +1,22 @@
import { Knex } from "knex";
import { TableName } from "../schemas";
export async function up(knex: Knex): Promise<void> {
const doesExpireAtExist = await knex.schema.hasColumn(TableName.AuditLog, "expiresAt");
if (await knex.schema.hasTable(TableName.AuditLog)) {
await knex.schema.alterTable(TableName.AuditLog, (t) => {
if (doesExpireAtExist) t.index("expiresAt");
});
}
}
export async function down(knex: Knex): Promise<void> {
const doesExpireAtExist = await knex.schema.hasColumn(TableName.AuditLog, "expiresAt");
if (await knex.schema.hasTable(TableName.AuditLog)) {
await knex.schema.alterTable(TableName.AuditLog, (t) => {
if (doesExpireAtExist) t.dropIndex("expiresAt");
});
}
}

View File

@@ -0,0 +1,29 @@
import { Knex } from "knex";
import { TableName } from "../schemas";
import { createOnUpdateTrigger, dropOnUpdateTrigger } from "../utils";
export async function up(knex: Knex): Promise<void> {
if (!(await knex.schema.hasTable(TableName.IdentityAzureAuth))) {
await knex.schema.createTable(TableName.IdentityAzureAuth, (t) => {
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
t.bigInteger("accessTokenTTL").defaultTo(7200).notNullable();
t.bigInteger("accessTokenMaxTTL").defaultTo(7200).notNullable();
t.bigInteger("accessTokenNumUsesLimit").defaultTo(0).notNullable();
t.jsonb("accessTokenTrustedIps").notNullable();
t.timestamps(true, true, true);
t.uuid("identityId").notNullable().unique();
t.foreign("identityId").references("id").inTable(TableName.Identity).onDelete("CASCADE");
t.string("tenantId").notNullable();
t.string("resource").notNullable();
t.string("allowedServicePrincipalIds").notNullable();
});
}
await createOnUpdateTrigger(knex, TableName.IdentityAzureAuth);
}
export async function down(knex: Knex): Promise<void> {
await knex.schema.dropTableIfExists(TableName.IdentityAzureAuth);
await dropOnUpdateTrigger(knex, TableName.IdentityAzureAuth);
}

View File

@@ -0,0 +1,26 @@
// Code generated by automation script, DO NOT EDIT.
// Automated by pulling database and generating zod schema
// To update. Just run npm run generate:schema
// Written by akhilmhdh.
import { z } from "zod";
import { TImmutableDBKeys } from "./models";
export const IdentityAzureAuthsSchema = z.object({
id: z.string().uuid(),
accessTokenTTL: z.coerce.number().default(7200),
accessTokenMaxTTL: z.coerce.number().default(7200),
accessTokenNumUsesLimit: z.coerce.number().default(0),
accessTokenTrustedIps: z.unknown(),
createdAt: z.date(),
updatedAt: z.date(),
identityId: z.string().uuid(),
tenantId: z.string(),
resource: z.string(),
allowedServicePrincipalIds: z.string()
});
export type TIdentityAzureAuths = z.infer<typeof IdentityAzureAuthsSchema>;
export type TIdentityAzureAuthsInsert = Omit<z.input<typeof IdentityAzureAuthsSchema>, TImmutableDBKeys>;
export type TIdentityAzureAuthsUpdate = Partial<Omit<z.input<typeof IdentityAzureAuthsSchema>, TImmutableDBKeys>>;

View File

@@ -18,6 +18,7 @@ export * from "./groups";
export * from "./identities";
export * from "./identity-access-tokens";
export * from "./identity-aws-auths";
export * from "./identity-azure-auths";
export * from "./identity-gcp-auths";
export * from "./identity-kubernetes-auths";
export * from "./identity-org-memberships";

View File

@@ -47,6 +47,7 @@ export enum TableName {
IdentityUniversalAuth = "identity_universal_auths",
IdentityKubernetesAuth = "identity_kubernetes_auths",
IdentityGcpAuth = "identity_gcp_auths",
IdentityAzureAuth = "identity_azure_auths",
IdentityUaClientSecret = "identity_ua_client_secrets",
IdentityAwsAuth = "identity_aws_auths",
IdentityOrgMembership = "identity_org_memberships",
@@ -149,5 +150,6 @@ export enum IdentityAuthMethod {
Univeral = "universal-auth",
KUBERNETES_AUTH = "kubernetes-auth",
GCP_AUTH = "gcp-auth",
AWS_AUTH = "aws-auth"
AWS_AUTH = "aws-auth",
AZURE_AUTH = "azure-auth"
}

View File

@@ -3,7 +3,6 @@ import { RawAxiosRequestHeaders } from "axios";
import { SecretKeyEncoding } from "@app/db/schemas";
import { request } from "@app/lib/config/request";
import { infisicalSymmetricDecrypt } from "@app/lib/crypto/encryption";
import { logger } from "@app/lib/logger";
import { QueueJobs, QueueName, TQueueServiceFactory } from "@app/queue";
import { TProjectDALFactory } from "@app/services/project/project-dal";
@@ -113,35 +112,7 @@ export const auditLogQueueServiceFactory = ({
);
});
queueService.start(QueueName.AuditLogPrune, async () => {
logger.info(`${QueueName.AuditLogPrune}: queue task started`);
await auditLogDAL.pruneAuditLog();
logger.info(`${QueueName.AuditLogPrune}: queue task completed`);
});
// we do a repeat cron job in utc timezone at 12 Midnight each day
const startAuditLogPruneJob = async () => {
// clear previous job
await queueService.stopRepeatableJob(
QueueName.AuditLogPrune,
QueueJobs.AuditLogPrune,
{ pattern: "0 0 * * *", utc: true },
QueueName.AuditLogPrune // just a job id
);
await queueService.queue(QueueName.AuditLogPrune, QueueJobs.AuditLogPrune, undefined, {
delay: 5000,
jobId: QueueName.AuditLogPrune,
repeat: { pattern: "0 0 * * *", utc: true }
});
};
queueService.listen(QueueName.AuditLogPrune, "failed", (err) => {
logger.error(err?.failedReason, `${QueueName.AuditLogPrune}: log pruning failed`);
});
return {
pushToLog,
startAuditLogPruneJob
pushToLog
};
};

View File

@@ -79,6 +79,10 @@ export enum EventType {
ADD_IDENTITY_AWS_AUTH = "add-identity-aws-auth",
UPDATE_IDENTITY_AWS_AUTH = "update-identity-aws-auth",
GET_IDENTITY_AWS_AUTH = "get-identity-aws-auth",
LOGIN_IDENTITY_AZURE_AUTH = "login-identity-azure-auth",
ADD_IDENTITY_AZURE_AUTH = "add-identity-azure-auth",
UPDATE_IDENTITY_AZURE_AUTH = "update-identity-azure-auth",
GET_IDENTITY_AZURE_AUTH = "get-identity-azure-auth",
CREATE_ENVIRONMENT = "create-environment",
UPDATE_ENVIRONMENT = "update-environment",
DELETE_ENVIRONMENT = "delete-environment",
@@ -572,6 +576,48 @@ interface GetIdentityAwsAuthEvent {
};
}
interface LoginIdentityAzureAuthEvent {
type: EventType.LOGIN_IDENTITY_AZURE_AUTH;
metadata: {
identityId: string;
identityAzureAuthId: string;
identityAccessTokenId: string;
};
}
interface AddIdentityAzureAuthEvent {
type: EventType.ADD_IDENTITY_AZURE_AUTH;
metadata: {
identityId: string;
tenantId: string;
resource: string;
accessTokenTTL: number;
accessTokenMaxTTL: number;
accessTokenNumUsesLimit: number;
accessTokenTrustedIps: Array<TIdentityTrustedIp>;
};
}
interface UpdateIdentityAzureAuthEvent {
type: EventType.UPDATE_IDENTITY_AZURE_AUTH;
metadata: {
identityId: string;
tenantId?: string;
resource?: string;
accessTokenTTL?: number;
accessTokenMaxTTL?: number;
accessTokenNumUsesLimit?: number;
accessTokenTrustedIps?: Array<TIdentityTrustedIp>;
};
}
interface GetIdentityAzureAuthEvent {
type: EventType.GET_IDENTITY_AZURE_AUTH;
metadata: {
identityId: string;
};
}
interface CreateEnvironmentEvent {
type: EventType.CREATE_ENVIRONMENT;
metadata: {
@@ -839,6 +885,10 @@ export type Event =
| AddIdentityAwsAuthEvent
| UpdateIdentityAwsAuthEvent
| GetIdentityAwsAuthEvent
| LoginIdentityAzureAuthEvent
| AddIdentityAzureAuthEvent
| UpdateIdentityAzureAuthEvent
| GetIdentityAzureAuthEvent
| CreateEnvironmentEvent
| UpdateEnvironmentEvent
| DeleteEnvironmentEvent

View File

@@ -148,36 +148,6 @@ export const PROJECTS = {
name: "The new name of the project.",
autoCapitalization: "Disable or enable auto-capitalization for the project."
},
INVITE_MEMBER: {
projectId: "The ID of the project to invite the member to.",
emails: "A list of organization member emails to invite to the project.",
usernames: "A list of usernames to invite to the project."
},
REMOVE_MEMBER: {
projectId: "The ID of the project to remove the member from.",
emails: "A list of organization member emails to remove from the project.",
usernames: "A list of usernames to remove from the project."
},
GET_USER_MEMBERSHIPS: {
workspaceId: "The ID of the project to get memberships from."
},
UPDATE_USER_MEMBERSHIP: {
workspaceId: "The ID of the project to update the membership for.",
membershipId: "The ID of the membership to update.",
roles: "A list of roles to update the membership to."
},
LIST_IDENTITY_MEMBERSHIPS: {
projectId: "The ID of the project to get identity memberships from."
},
UPDATE_IDENTITY_MEMBERSHIP: {
projectId: "The ID of the project to update the identity membership for.",
identityId: "The ID of the identity to update the membership for.",
roles: "A list of roles to update the membership to."
},
DELETE_IDENTITY_MEMBERSHIP: {
projectId: "The ID of the project to delete the identity membership from.",
identityId: "The ID of the identity to delete the membership from."
},
GET_KEY: {
workspaceId: "The ID of the project to get the key from."
},
@@ -216,6 +186,70 @@ export const PROJECTS = {
}
} as const;
export const PROJECT_USERS = {
INVITE_MEMBER: {
projectId: "The ID of the project to invite the member to.",
emails: "A list of organization member emails to invite to the project.",
usernames: "A list of usernames to invite to the project."
},
REMOVE_MEMBER: {
projectId: "The ID of the project to remove the member from.",
emails: "A list of organization member emails to remove from the project.",
usernames: "A list of usernames to remove from the project."
},
GET_USER_MEMBERSHIPS: {
workspaceId: "The ID of the project to get memberships from."
},
GET_USER_MEMBERSHIP: {
workspaceId: "The ID of the project to get memberships from.",
username: "The username to get project membership of. Email is the default username."
},
UPDATE_USER_MEMBERSHIP: {
workspaceId: "The ID of the project to update the membership for.",
membershipId: "The ID of the membership to update.",
roles: "A list of roles to update the membership to."
}
};
export const PROJECT_IDENTITIES = {
LIST_IDENTITY_MEMBERSHIPS: {
projectId: "The ID of the project to get identity memberships from."
},
GET_IDENTITY_MEMBERSHIP_BY_ID: {
identityId: "The ID of the identity to get the membership for.",
projectId: "The ID of the project to get the identity membership for."
},
UPDATE_IDENTITY_MEMBERSHIP: {
projectId: "The ID of the project to update the identity membership for.",
identityId: "The ID of the identity to update the membership for.",
roles: {
description: "A list of role slugs to assign to the identity project membership.",
role: "The role slug to assign to the newly created identity project membership.",
isTemporary: "Whether the assigned role is temporary.",
temporaryMode: "Type of temporary expiry.",
temporaryRange: "Expiry time for temporary access. In relative mode it could be 1s,2m,3h",
temporaryAccessStartTime: "Time to which the temporary access starts"
}
},
DELETE_IDENTITY_MEMBERSHIP: {
projectId: "The ID of the project to delete the identity membership from.",
identityId: "The ID of the identity to delete the membership from."
},
CREATE_IDENTITY_MEMBERSHIP: {
projectId: "The ID of the project to create the identity membership from.",
identityId: "The ID of the identity to create the membership from.",
role: "The role slug to assign to the newly created identity project membership.",
roles: {
description: "A list of role slugs to assign to the newly created identity project membership.",
role: "The role slug to assign to the newly created identity project membership.",
isTemporary: "Whether the assigned role is temporary.",
temporaryMode: "Type of temporary expiry.",
temporaryRange: "Expiry time for temporary access. In relative mode it could be 1s,2m,3h",
temporaryAccessStartTime: "Time to which the temporary access starts"
}
}
};
export const ENVIRONMENTS = {
CREATE: {
workspaceId: "The ID of the project to create the environment in.",
@@ -628,6 +662,7 @@ export const INTEGRATION = {
secretPrefix: "The prefix for the saved secret. Used by GCP.",
secretSuffix: "The suffix for the saved secret. Used by GCP.",
initialSyncBehavoir: "Type of syncing behavoir with the integration.",
mappingBehavior: "The mapping behavior of the integration.",
shouldAutoRedeploy: "Used by Render to trigger auto deploy.",
secretGCPLabel: "The label for GCP secrets.",
secretAWSTag: "The tags for AWS secrets.",

View File

@@ -104,24 +104,68 @@ export const ormify = <DbOps extends object, Tname extends keyof Tables>(db: Kne
throw new DatabaseError({ error, name: "Create" });
}
},
updateById: async (id: string, data: Tables[Tname]["update"], tx?: Knex) => {
updateById: async (
id: string,
{
$incr,
$decr,
...data
}: Tables[Tname]["update"] & {
$incr?: { [x in keyof Partial<Tables[Tname]["base"]>]: number };
$decr?: { [x in keyof Partial<Tables[Tname]["base"]>]: number };
},
tx?: Knex
) => {
try {
const [res] = await (tx || db)(tableName)
const query = (tx || db)(tableName)
.where({ id } as never)
.update(data as never)
.returning("*");
return res;
if ($incr) {
Object.entries($incr).forEach(([incrementField, incrementValue]) => {
void query.increment(incrementField, incrementValue);
});
}
if ($decr) {
Object.entries($decr).forEach(([incrementField, incrementValue]) => {
void query.increment(incrementField, incrementValue);
});
}
const [docs] = await query;
return docs;
} catch (error) {
throw new DatabaseError({ error, name: "Update by id" });
}
},
update: async (filter: TFindFilter<Tables[Tname]["base"]>, data: Tables[Tname]["update"], tx?: Knex) => {
update: async (
filter: TFindFilter<Tables[Tname]["base"]>,
{
$incr,
$decr,
...data
}: Tables[Tname]["update"] & {
$incr?: { [x in keyof Partial<Tables[Tname]["base"]>]: number };
$decr?: { [x in keyof Partial<Tables[Tname]["base"]>]: number };
},
tx?: Knex
) => {
try {
const res = await (tx || db)(tableName)
const query = (tx || db)(tableName)
.where(buildFindFilter(filter))
.update(data as never)
.returning("*");
return res;
// increment and decrement operation in update
if ($incr) {
Object.entries($incr).forEach(([incrementField, incrementValue]) => {
void query.increment(incrementField, incrementValue);
});
}
if ($decr) {
Object.entries($decr).forEach(([incrementField, incrementValue]) => {
void query.increment(incrementField, incrementValue);
});
}
return await query;
} catch (error) {
throw new DatabaseError({ error, name: "Update" });
}

View File

@@ -30,6 +30,37 @@ const loggerConfig = z.object({
NODE_ENV: z.enum(["development", "test", "production"]).default("production")
});
const redactedKeys = [
"accessToken",
"authToken",
"serviceToken",
"identityAccessToken",
"token",
"privateKey",
"serverPrivateKey",
"plainPrivateKey",
"plainProjectKey",
"encryptedPrivateKey",
"userPrivateKey",
"protectedKey",
"decryptKey",
"encryptedProjectKey",
"encryptedSymmetricKey",
"encryptedPrivateKey",
"backupPrivateKey",
"secretKey",
"SecretKey",
"botPrivateKey",
"encryptedKey",
"plaintextProjectKey",
"accessKey",
"botKey",
"decryptedSecret",
"secrets",
"key",
"password"
];
export const initLogger = async () => {
const cfg = loggerConfig.parse(process.env);
const targets: pino.TransportMultiOptions["targets"][number][] = [
@@ -74,7 +105,9 @@ export const initLogger = async () => {
hostname: bindings.hostname
// node_version: process.version
})
}
},
// redact until depth of three
redact: [...redactedKeys, ...redactedKeys.map((key) => `*.${key}`), ...redactedKeys.map((key) => `*.*.${key}`)]
},
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
transport

View File

@@ -12,7 +12,9 @@ export enum QueueName {
SecretRotation = "secret-rotation",
SecretReminder = "secret-reminder",
AuditLog = "audit-log",
// TODO(akhilmhdh): This will get removed later. For now this is kept to stop the repeatable queue
AuditLogPrune = "audit-log-prune",
DailyResourceCleanUp = "daily-resource-cleanup",
TelemetryInstanceStats = "telemtry-self-hosted-stats",
IntegrationSync = "sync-integrations",
SecretWebhook = "secret-webhook",
@@ -26,7 +28,9 @@ export enum QueueJobs {
SecretReminder = "secret-reminder-job",
SecretRotation = "secret-rotation-job",
AuditLog = "audit-log-job",
// TODO(akhilmhdh): This will get removed later. For now this is kept to stop the repeatable queue
AuditLogPrune = "audit-log-prune-job",
DailyResourceCleanUp = "daily-resource-cleanup-job",
SecWebhook = "secret-webhook-trigger",
TelemetryInstanceStats = "telemetry-self-hosted-stats",
IntegrationSync = "secret-integration-pull",
@@ -55,6 +59,10 @@ export type TQueueJobTypes = {
name: QueueJobs.AuditLog;
payload: TCreateAuditLogDTO;
};
[QueueName.DailyResourceCleanUp]: {
name: QueueJobs.DailyResourceCleanUp;
payload: undefined;
};
[QueueName.AuditLogPrune]: {
name: QueueJobs.AuditLogPrune;
payload: undefined;
@@ -172,7 +180,9 @@ export const queueServiceFactory = (redisUrl: string) => {
jobId?: string
) => {
const q = queueContainer[name];
return q.removeRepeatable(job, repeatOpt, jobId);
if (q) {
return q.removeRepeatable(job, repeatOpt, jobId);
}
};
const stopRepeatableJobByJobId = async <T extends QueueName>(name: T, jobId: string) => {

View File

@@ -36,7 +36,7 @@ export const writeLimit: RateLimitOptions = {
export const secretsLimit: RateLimitOptions = {
// secrets, folders, secret imports
timeWindow: 60 * 1000,
max: 1000,
max: 60,
keyGenerator: (req) => req.realIp
};

View File

@@ -1,5 +1,6 @@
import fp from "fastify-plugin";
import { logger } from "@app/lib/logger";
import { ActorType } from "@app/services/auth/auth-type";
// inject permission type needed based on auth extracted
@@ -15,6 +16,10 @@ export const injectPermission = fp(async (server) => {
orgId: req.auth.orgId, // if the req.auth.authMode is AuthMode.API_KEY, the orgId will be "API_KEY"
authMethod: req.auth.authMethod // if the req.auth.authMode is AuthMode.API_KEY, the authMethod will be null
};
logger.info(
`injectPermission: Injecting permissions for [permissionsForIdentity=${req.auth.userId}] [type=${ActorType.USER}]`
);
} else if (req.auth.actor === ActorType.IDENTITY) {
req.permission = {
type: ActorType.IDENTITY,
@@ -22,6 +27,10 @@ export const injectPermission = fp(async (server) => {
orgId: req.auth.orgId,
authMethod: null
};
logger.info(
`injectPermission: Injecting permissions for [permissionsForIdentity=${req.auth.identityId}] [type=${ActorType.IDENTITY}]`
);
} else if (req.auth.actor === ActorType.SERVICE) {
req.permission = {
type: ActorType.SERVICE,
@@ -29,6 +38,10 @@ export const injectPermission = fp(async (server) => {
orgId: req.auth.orgId,
authMethod: null
};
logger.info(
`injectPermission: Injecting permissions for [permissionsForIdentity=${req.auth.serviceTokenId}] [type=${ActorType.SERVICE}]`
);
} else if (req.auth.actor === ActorType.SCIM_CLIENT) {
req.permission = {
type: ActorType.SCIM_CLIENT,
@@ -36,6 +49,10 @@ export const injectPermission = fp(async (server) => {
orgId: req.auth.orgId,
authMethod: null
};
logger.info(
`injectPermission: Injecting permissions for [permissionsForIdentity=${req.auth.scimTokenId}] [type=${ActorType.SCIM_CLIENT}]`
);
}
});
});

View File

@@ -6,6 +6,7 @@ const headersOrder = [
"cf-connecting-ip", // Cloudflare
"Cf-Pseudo-IPv4", // Cloudflare
"x-client-ip", // Most common
"x-envoy-external-address", // for envoy
"x-forwarded-for", // Mostly used by proxies
"fastly-client-ip",
"true-client-ip", // Akamai and Cloudflare
@@ -23,7 +24,21 @@ export const fastifyIp = fp(async (fastify) => {
const forwardedIpHeader = headersOrder.find((header) => Boolean(req.headers[header]));
const forwardedIp = forwardedIpHeader ? req.headers[forwardedIpHeader] : undefined;
if (forwardedIp) {
req.realIp = Array.isArray(forwardedIp) ? forwardedIp[0] : forwardedIp;
if (Array.isArray(forwardedIp)) {
// eslint-disable-next-line
req.realIp = forwardedIp[0];
return;
}
if (forwardedIp.includes(",")) {
// the ip header when placed with load balancers that proxy request
// will attach the internal ips to header by appending with comma
// https://github.com/go-chi/chi/blob/master/middleware/realip.go
const clientIPFromProxy = forwardedIp.slice(0, forwardedIp.indexOf(",")).trim();
req.realIp = clientIPFromProxy;
return;
}
req.realIp = forwardedIp;
} else {
req.realIp = req.ip;
}

View File

@@ -80,6 +80,8 @@ import { identityAccessTokenDALFactory } from "@app/services/identity-access-tok
import { identityAccessTokenServiceFactory } from "@app/services/identity-access-token/identity-access-token-service";
import { identityAwsAuthDALFactory } from "@app/services/identity-aws-auth/identity-aws-auth-dal";
import { identityAwsAuthServiceFactory } from "@app/services/identity-aws-auth/identity-aws-auth-service";
import { identityAzureAuthDALFactory } from "@app/services/identity-azure-auth/identity-azure-auth-dal";
import { identityAzureAuthServiceFactory } from "@app/services/identity-azure-auth/identity-azure-auth-service";
import { identityGcpAuthDALFactory } from "@app/services/identity-gcp-auth/identity-gcp-auth-dal";
import { identityGcpAuthServiceFactory } from "@app/services/identity-gcp-auth/identity-gcp-auth-service";
import { identityKubernetesAuthDALFactory } from "@app/services/identity-kubernetes-auth/identity-kubernetes-auth-dal";
@@ -115,6 +117,7 @@ import { projectMembershipServiceFactory } from "@app/services/project-membershi
import { projectUserMembershipRoleDALFactory } from "@app/services/project-membership/project-user-membership-role-dal";
import { projectRoleDALFactory } from "@app/services/project-role/project-role-dal";
import { projectRoleServiceFactory } from "@app/services/project-role/project-role-service";
import { dailyResourceCleanUpQueueServiceFactory } from "@app/services/resource-cleanup/resource-cleanup-queue";
import { secretDALFactory } from "@app/services/secret/secret-dal";
import { secretQueueFactory } from "@app/services/secret/secret-queue";
import { secretServiceFactory } from "@app/services/secret/secret-service";
@@ -212,8 +215,8 @@ export const registerRoutes = async (
const identityKubernetesAuthDAL = identityKubernetesAuthDALFactory(db);
const identityUaClientSecretDAL = identityUaClientSecretDALFactory(db);
const identityAwsAuthDAL = identityAwsAuthDALFactory(db);
const identityGcpAuthDAL = identityGcpAuthDALFactory(db);
const identityAzureAuthDAL = identityAzureAuthDALFactory(db);
const auditLogDAL = auditLogDALFactory(db);
const auditLogStreamDAL = auditLogStreamDALFactory(db);
@@ -742,6 +745,15 @@ export const registerRoutes = async (
permissionService
});
const identityAzureAuthService = identityAzureAuthServiceFactory({
identityAzureAuthDAL,
identityOrgMembershipDAL,
identityAccessTokenDAL,
identityDAL,
permissionService,
licenseService
});
const dynamicSecretProviders = buildDynamicSecretProviders();
const dynamicSecretQueueService = dynamicSecretLeaseQueueServiceFactory({
queueService,
@@ -769,14 +781,19 @@ export const registerRoutes = async (
folderDAL,
licenseService
});
const dailyResourceCleanUp = dailyResourceCleanUpQueueServiceFactory({
auditLogDAL,
queueService,
identityAccessTokenDAL
});
await superAdminService.initServerCfg();
//
// setup the communication with license key server
await licenseService.init();
await auditLogQueue.startAuditLogPruneJob();
await telemetryQueue.startTelemetryCheck();
await dailyResourceCleanUp.startCleanUp();
// inject all services
server.decorate<FastifyZodProvider["services"]>("services", {
@@ -813,6 +830,7 @@ export const registerRoutes = async (
identityKubernetesAuth: identityKubernetesAuthService,
identityGcpAuth: identityGcpAuthService,
identityAwsAuth: identityAwsAuthService,
identityAzureAuth: identityAzureAuthService,
secretApprovalPolicy: sapService,
accessApprovalPolicy: accessApprovalPolicyService,
accessApprovalRequest: accessApprovalRequestService,

View File

@@ -0,0 +1,262 @@
import { z } from "zod";
import { IdentityAzureAuthsSchema } from "@app/db/schemas";
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type";
import { TIdentityTrustedIp } from "@app/services/identity/identity-types";
import { validateAzureAuthField } from "@app/services/identity-azure-auth/identity-azure-auth-validators";
export const registerIdentityAzureAuthRouter = async (server: FastifyZodProvider) => {
server.route({
method: "POST",
url: "/azure-auth/login",
config: {
rateLimit: writeLimit
},
schema: {
description: "Login with Azure Auth",
body: z.object({
identityId: z.string(),
jwt: z.string()
}),
response: {
200: z.object({
accessToken: z.string(),
expiresIn: z.coerce.number(),
accessTokenMaxTTL: z.coerce.number(),
tokenType: z.literal("Bearer")
})
}
},
handler: async (req) => {
const { identityAzureAuth, accessToken, identityAccessToken, identityMembershipOrg } =
await server.services.identityAzureAuth.login(req.body);
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
orgId: identityMembershipOrg.orgId,
event: {
type: EventType.LOGIN_IDENTITY_AZURE_AUTH,
metadata: {
identityId: identityAzureAuth.identityId,
identityAccessTokenId: identityAccessToken.id,
identityAzureAuthId: identityAzureAuth.id
}
}
});
return {
accessToken,
tokenType: "Bearer" as const,
expiresIn: identityAzureAuth.accessTokenTTL,
accessTokenMaxTTL: identityAzureAuth.accessTokenMaxTTL
};
}
});
server.route({
method: "POST",
url: "/azure-auth/identities/:identityId",
config: {
rateLimit: writeLimit
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
schema: {
description: "Attach Azure Auth configuration onto identity",
security: [
{
bearerAuth: []
}
],
params: z.object({
identityId: z.string().trim()
}),
body: z.object({
tenantId: z.string().trim(),
resource: z.string().trim(),
allowedServicePrincipalIds: validateAzureAuthField,
accessTokenTrustedIps: z
.object({
ipAddress: z.string().trim()
})
.array()
.min(1)
.default([{ ipAddress: "0.0.0.0/0" }, { ipAddress: "::/0" }]),
accessTokenTTL: z
.number()
.int()
.min(1)
.refine((value) => value !== 0, {
message: "accessTokenTTL must have a non zero number"
})
.default(2592000),
accessTokenMaxTTL: z
.number()
.int()
.refine((value) => value !== 0, {
message: "accessTokenMaxTTL must have a non zero number"
})
.default(2592000),
accessTokenNumUsesLimit: z.number().int().min(0).default(0)
}),
response: {
200: z.object({
identityAzureAuth: IdentityAzureAuthsSchema
})
}
},
handler: async (req) => {
const identityAzureAuth = await server.services.identityAzureAuth.attachAzureAuth({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
...req.body,
identityId: req.params.identityId
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
orgId: identityAzureAuth.orgId,
event: {
type: EventType.ADD_IDENTITY_AZURE_AUTH,
metadata: {
identityId: identityAzureAuth.identityId,
tenantId: identityAzureAuth.tenantId,
resource: identityAzureAuth.resource,
accessTokenTTL: identityAzureAuth.accessTokenTTL,
accessTokenMaxTTL: identityAzureAuth.accessTokenMaxTTL,
accessTokenTrustedIps: identityAzureAuth.accessTokenTrustedIps as TIdentityTrustedIp[],
accessTokenNumUsesLimit: identityAzureAuth.accessTokenNumUsesLimit
}
}
});
return { identityAzureAuth };
}
});
server.route({
method: "PATCH",
url: "/azure-auth/identities/:identityId",
config: {
rateLimit: writeLimit
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
schema: {
description: "Update Azure Auth configuration on identity",
security: [
{
bearerAuth: []
}
],
params: z.object({
identityId: z.string().trim()
}),
body: z.object({
tenantId: z.string().trim().optional(),
resource: z.string().trim().optional(),
allowedServicePrincipalIds: validateAzureAuthField.optional(),
accessTokenTrustedIps: z
.object({
ipAddress: z.string().trim()
})
.array()
.min(1)
.optional(),
accessTokenTTL: z.number().int().min(0).optional(),
accessTokenNumUsesLimit: z.number().int().min(0).optional(),
accessTokenMaxTTL: z
.number()
.int()
.refine((value) => value !== 0, {
message: "accessTokenMaxTTL must have a non zero number"
})
.optional()
}),
response: {
200: z.object({
identityAzureAuth: IdentityAzureAuthsSchema
})
}
},
handler: async (req) => {
const identityAzureAuth = await server.services.identityAzureAuth.updateAzureAuth({
actor: req.permission.type,
actorId: req.permission.id,
actorOrgId: req.permission.orgId,
actorAuthMethod: req.permission.authMethod,
...req.body,
identityId: req.params.identityId
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
orgId: identityAzureAuth.orgId,
event: {
type: EventType.UPDATE_IDENTITY_AZURE_AUTH,
metadata: {
identityId: identityAzureAuth.identityId,
tenantId: identityAzureAuth.tenantId,
resource: identityAzureAuth.resource,
accessTokenTTL: identityAzureAuth.accessTokenTTL,
accessTokenMaxTTL: identityAzureAuth.accessTokenMaxTTL,
accessTokenTrustedIps: identityAzureAuth.accessTokenTrustedIps as TIdentityTrustedIp[],
accessTokenNumUsesLimit: identityAzureAuth.accessTokenNumUsesLimit
}
}
});
return { identityAzureAuth };
}
});
server.route({
method: "GET",
url: "/azure-auth/identities/:identityId",
config: {
rateLimit: readLimit
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
schema: {
description: "Retrieve Azure Auth configuration on identity",
security: [
{
bearerAuth: []
}
],
params: z.object({
identityId: z.string()
}),
response: {
200: z.object({
identityAzureAuth: IdentityAzureAuthsSchema
})
}
},
handler: async (req) => {
const identityAzureAuth = await server.services.identityAzureAuth.getAzureAuth({
identityId: req.params.identityId,
actor: req.permission.type,
actorId: req.permission.id,
actorOrgId: req.permission.orgId,
actorAuthMethod: req.permission.authMethod
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
orgId: identityAzureAuth.orgId,
event: {
type: EventType.GET_IDENTITY_AZURE_AUTH,
metadata: {
identityId: identityAzureAuth.identityId
}
}
});
return { identityAzureAuth };
}
});
};

View File

@@ -160,9 +160,9 @@ export const registerIdentityGcpAuthRouter = async (server: FastifyZodProvider)
}),
body: z.object({
type: z.enum(["iam", "gce"]).optional(),
allowedServiceAccounts: validateGcpAuthField,
allowedProjects: validateGcpAuthField,
allowedZones: validateGcpAuthField,
allowedServiceAccounts: validateGcpAuthField.optional(),
allowedProjects: validateGcpAuthField.optional(),
allowedZones: validateGcpAuthField.optional(),
accessTokenTrustedIps: z
.object({
ipAddress: z.string().trim()

View File

@@ -3,6 +3,7 @@ import { registerAuthRoutes } from "./auth-router";
import { registerProjectBotRouter } from "./bot-router";
import { registerIdentityAccessTokenRouter } from "./identity-access-token-router";
import { registerIdentityAwsAuthRouter } from "./identity-aws-iam-auth-router";
import { registerIdentityAzureAuthRouter } from "./identity-azure-auth-router";
import { registerIdentityGcpAuthRouter } from "./identity-gcp-auth-router";
import { registerIdentityKubernetesRouter } from "./identity-kubernetes-auth-router";
import { registerIdentityRouter } from "./identity-router";
@@ -34,6 +35,7 @@ export const registerV1Routes = async (server: FastifyZodProvider) => {
await authRouter.register(registerIdentityGcpAuthRouter);
await authRouter.register(registerIdentityAccessTokenRouter);
await authRouter.register(registerIdentityAwsAuthRouter);
await authRouter.register(registerIdentityAzureAuthRouter);
},
{ prefix: "/auth" }
);

View File

@@ -330,7 +330,7 @@ export const registerIntegrationAuthRouter = async (server: FastifyZodProvider)
teams: z
.object({
name: z.string(),
id: z.string().optional()
id: z.string()
})
.array()
})

View File

@@ -8,6 +8,7 @@ import { writeLimit } from "@app/server/config/rateLimiter";
import { getTelemetryDistinctId } from "@app/server/lib/telemetry";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type";
import { IntegrationMappingBehavior } from "@app/services/integration-auth/integration-list";
import { PostHogEventTypes, TIntegrationCreatedEvent } from "@app/services/telemetry/telemetry-types";
export const registerIntegrationRouter = async (server: FastifyZodProvider) => {
@@ -49,6 +50,10 @@ export const registerIntegrationRouter = async (server: FastifyZodProvider) => {
secretPrefix: z.string().optional().describe(INTEGRATION.CREATE.metadata.secretPrefix),
secretSuffix: z.string().optional().describe(INTEGRATION.CREATE.metadata.secretSuffix),
initialSyncBehavior: z.string().optional().describe(INTEGRATION.CREATE.metadata.initialSyncBehavoir),
mappingBehavior: z
.nativeEnum(IntegrationMappingBehavior)
.optional()
.describe(INTEGRATION.CREATE.metadata.mappingBehavior),
shouldAutoRedeploy: z.boolean().optional().describe(INTEGRATION.CREATE.metadata.shouldAutoRedeploy),
secretGCPLabel: z
.object({
@@ -160,6 +165,7 @@ export const registerIntegrationRouter = async (server: FastifyZodProvider) => {
secretPrefix: z.string().optional().describe(INTEGRATION.CREATE.metadata.secretPrefix),
secretSuffix: z.string().optional().describe(INTEGRATION.CREATE.metadata.secretSuffix),
initialSyncBehavior: z.string().optional().describe(INTEGRATION.CREATE.metadata.initialSyncBehavoir),
mappingBehavior: z.string().optional().describe(INTEGRATION.CREATE.metadata.mappingBehavior),
shouldAutoRedeploy: z.boolean().optional().describe(INTEGRATION.CREATE.metadata.shouldAutoRedeploy),
secretGCPLabel: z
.object({

View File

@@ -9,7 +9,7 @@ import {
UsersSchema
} from "@app/db/schemas";
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
import { PROJECTS } from "@app/lib/api-docs";
import { PROJECT_USERS } from "@app/lib/api-docs";
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type";
@@ -30,7 +30,7 @@ export const registerProjectMembershipRouter = async (server: FastifyZodProvider
}
],
params: z.object({
workspaceId: z.string().trim().describe(PROJECTS.GET_USER_MEMBERSHIPS.workspaceId)
workspaceId: z.string().trim().describe(PROJECT_USERS.GET_USER_MEMBERSHIPS.workspaceId)
}),
response: {
200: z.object({
@@ -74,6 +74,66 @@ export const registerProjectMembershipRouter = async (server: FastifyZodProvider
}
});
server.route({
method: "POST",
url: "/:workspaceId/memberships/details",
config: {
rateLimit: readLimit
},
schema: {
description: "Return project user memberships",
security: [
{
bearerAuth: []
}
],
params: z.object({
workspaceId: z.string().min(1).trim().describe(PROJECT_USERS.GET_USER_MEMBERSHIP.workspaceId)
}),
body: z.object({
username: z.string().min(1).trim().describe(PROJECT_USERS.GET_USER_MEMBERSHIP.username)
}),
response: {
200: z.object({
membership: ProjectMembershipsSchema.extend({
user: UsersSchema.pick({
email: true,
firstName: true,
lastName: true,
id: true
}).merge(UserEncryptionKeysSchema.pick({ publicKey: true })),
roles: z.array(
z.object({
id: z.string(),
role: z.string(),
customRoleId: z.string().optional().nullable(),
customRoleName: z.string().optional().nullable(),
customRoleSlug: z.string().optional().nullable(),
isTemporary: z.boolean(),
temporaryMode: z.string().optional().nullable(),
temporaryRange: z.string().nullable().optional(),
temporaryAccessStartTime: z.date().nullable().optional(),
temporaryAccessEndTime: z.date().nullable().optional()
})
)
}).omit({ createdAt: true, updatedAt: true })
})
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const membership = await server.services.projectMembership.getProjectMembershipByUsername({
actorId: req.permission.id,
actor: req.permission.type,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
projectId: req.params.workspaceId,
username: req.body.username
});
return { membership };
}
});
server.route({
method: "POST",
url: "/:workspaceId/memberships",
@@ -142,8 +202,8 @@ export const registerProjectMembershipRouter = async (server: FastifyZodProvider
}
],
params: z.object({
workspaceId: z.string().trim().describe(PROJECTS.UPDATE_USER_MEMBERSHIP.workspaceId),
membershipId: z.string().trim().describe(PROJECTS.UPDATE_USER_MEMBERSHIP.membershipId)
workspaceId: z.string().trim().describe(PROJECT_USERS.UPDATE_USER_MEMBERSHIP.workspaceId),
membershipId: z.string().trim().describe(PROJECT_USERS.UPDATE_USER_MEMBERSHIP.membershipId)
}),
body: z.object({
roles: z
@@ -164,7 +224,7 @@ export const registerProjectMembershipRouter = async (server: FastifyZodProvider
)
.min(1)
.refine((data) => data.some(({ isTemporary }) => !isTemporary), "At least one long lived role is required")
.describe(PROJECTS.UPDATE_USER_MEMBERSHIP.roles)
.describe(PROJECT_USERS.UPDATE_USER_MEMBERSHIP.roles)
}),
response: {
200: z.object({

View File

@@ -7,7 +7,8 @@ import {
ProjectMembershipRole,
ProjectUserMembershipRolesSchema
} from "@app/db/schemas";
import { PROJECTS } from "@app/lib/api-docs";
import { PROJECT_IDENTITIES } from "@app/lib/api-docs";
import { BadRequestError } from "@app/lib/errors";
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";
@@ -22,12 +23,48 @@ export const registerIdentityProjectRouter = async (server: FastifyZodProvider)
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
schema: {
description: "Create project identity membership",
security: [
{
bearerAuth: []
}
],
params: z.object({
projectId: z.string().trim(),
identityId: z.string().trim()
}),
body: z.object({
role: z.string().trim().min(1).default(ProjectMembershipRole.NoAccess)
// @depreciated
role: z.string().trim().optional().default(ProjectMembershipRole.NoAccess),
roles: z
.array(
z.union([
z.object({
role: z.string().describe(PROJECT_IDENTITIES.CREATE_IDENTITY_MEMBERSHIP.roles.role),
isTemporary: z
.literal(false)
.default(false)
.describe(PROJECT_IDENTITIES.CREATE_IDENTITY_MEMBERSHIP.roles.role)
}),
z.object({
role: z.string().describe(PROJECT_IDENTITIES.CREATE_IDENTITY_MEMBERSHIP.roles.role),
isTemporary: z.literal(true).describe(PROJECT_IDENTITIES.CREATE_IDENTITY_MEMBERSHIP.roles.role),
temporaryMode: z
.nativeEnum(ProjectUserMembershipTemporaryMode)
.describe(PROJECT_IDENTITIES.CREATE_IDENTITY_MEMBERSHIP.roles.role),
temporaryRange: z
.string()
.refine((val) => ms(val) > 0, "Temporary range must be a positive number")
.describe(PROJECT_IDENTITIES.CREATE_IDENTITY_MEMBERSHIP.roles.role),
temporaryAccessStartTime: z
.string()
.datetime()
.describe(PROJECT_IDENTITIES.CREATE_IDENTITY_MEMBERSHIP.roles.role)
})
])
)
.describe(PROJECT_IDENTITIES.CREATE_IDENTITY_MEMBERSHIP.roles.description)
.optional()
}),
response: {
200: z.object({
@@ -36,6 +73,9 @@ export const registerIdentityProjectRouter = async (server: FastifyZodProvider)
}
},
handler: async (req) => {
const { role, roles } = req.body;
if (!role && !roles) throw new BadRequestError({ message: "You must provide either role or roles field" });
const identityMembership = await server.services.identityProject.createProjectIdentity({
actor: req.permission.type,
actorId: req.permission.id,
@@ -43,7 +83,7 @@ export const registerIdentityProjectRouter = async (server: FastifyZodProvider)
actorOrgId: req.permission.orgId,
identityId: req.params.identityId,
projectId: req.params.projectId,
role: req.body.role
roles: roles || [{ role }]
});
return { identityMembership };
}
@@ -64,28 +104,39 @@ export const registerIdentityProjectRouter = async (server: FastifyZodProvider)
}
],
params: z.object({
projectId: z.string().trim().describe(PROJECTS.UPDATE_IDENTITY_MEMBERSHIP.projectId),
identityId: z.string().trim().describe(PROJECTS.UPDATE_IDENTITY_MEMBERSHIP.identityId)
projectId: z.string().trim().describe(PROJECT_IDENTITIES.UPDATE_IDENTITY_MEMBERSHIP.projectId),
identityId: z.string().trim().describe(PROJECT_IDENTITIES.UPDATE_IDENTITY_MEMBERSHIP.identityId)
}),
body: z.object({
roles: z
.array(
z.union([
z.object({
role: z.string(),
isTemporary: z.literal(false).default(false)
role: z.string().describe(PROJECT_IDENTITIES.UPDATE_IDENTITY_MEMBERSHIP.roles.role),
isTemporary: z
.literal(false)
.default(false)
.describe(PROJECT_IDENTITIES.UPDATE_IDENTITY_MEMBERSHIP.roles.isTemporary)
}),
z.object({
role: z.string(),
isTemporary: z.literal(true),
temporaryMode: z.nativeEnum(ProjectUserMembershipTemporaryMode),
temporaryRange: z.string().refine((val) => ms(val) > 0, "Temporary range must be a positive number"),
temporaryAccessStartTime: z.string().datetime()
role: z.string().describe(PROJECT_IDENTITIES.UPDATE_IDENTITY_MEMBERSHIP.roles.role),
isTemporary: z.literal(true).describe(PROJECT_IDENTITIES.UPDATE_IDENTITY_MEMBERSHIP.roles.isTemporary),
temporaryMode: z
.nativeEnum(ProjectUserMembershipTemporaryMode)
.describe(PROJECT_IDENTITIES.UPDATE_IDENTITY_MEMBERSHIP.roles.temporaryMode),
temporaryRange: z
.string()
.refine((val) => ms(val) > 0, "Temporary range must be a positive number")
.describe(PROJECT_IDENTITIES.UPDATE_IDENTITY_MEMBERSHIP.roles.temporaryRange),
temporaryAccessStartTime: z
.string()
.datetime()
.describe(PROJECT_IDENTITIES.UPDATE_IDENTITY_MEMBERSHIP.roles.temporaryAccessStartTime)
})
])
)
.min(1)
.describe(PROJECTS.UPDATE_IDENTITY_MEMBERSHIP.roles)
.describe(PROJECT_IDENTITIES.UPDATE_IDENTITY_MEMBERSHIP.roles.description)
}),
response: {
200: z.object({
@@ -122,8 +173,8 @@ export const registerIdentityProjectRouter = async (server: FastifyZodProvider)
}
],
params: z.object({
projectId: z.string().trim().describe(PROJECTS.DELETE_IDENTITY_MEMBERSHIP.projectId),
identityId: z.string().trim().describe(PROJECTS.DELETE_IDENTITY_MEMBERSHIP.identityId)
projectId: z.string().trim().describe(PROJECT_IDENTITIES.DELETE_IDENTITY_MEMBERSHIP.projectId),
identityId: z.string().trim().describe(PROJECT_IDENTITIES.DELETE_IDENTITY_MEMBERSHIP.identityId)
}),
response: {
200: z.object({
@@ -159,7 +210,7 @@ export const registerIdentityProjectRouter = async (server: FastifyZodProvider)
}
],
params: z.object({
projectId: z.string().trim().describe(PROJECTS.LIST_IDENTITY_MEMBERSHIPS.projectId)
projectId: z.string().trim().describe(PROJECT_IDENTITIES.LIST_IDENTITY_MEMBERSHIPS.projectId)
}),
response: {
200: z.object({
@@ -200,4 +251,61 @@ export const registerIdentityProjectRouter = async (server: FastifyZodProvider)
return { identityMemberships };
}
});
server.route({
method: "GET",
url: "/:projectId/identity-memberships/:identityId",
config: {
rateLimit: readLimit
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
schema: {
description: "Return project identity membership",
security: [
{
bearerAuth: []
}
],
params: z.object({
projectId: z.string().trim().describe(PROJECT_IDENTITIES.GET_IDENTITY_MEMBERSHIP_BY_ID.projectId),
identityId: z.string().trim().describe(PROJECT_IDENTITIES.GET_IDENTITY_MEMBERSHIP_BY_ID.identityId)
}),
response: {
200: z.object({
identityMembership: z.object({
id: z.string(),
identityId: z.string(),
createdAt: z.date(),
updatedAt: z.date(),
roles: z.array(
z.object({
id: z.string(),
role: z.string(),
customRoleId: z.string().optional().nullable(),
customRoleName: z.string().optional().nullable(),
customRoleSlug: z.string().optional().nullable(),
isTemporary: z.boolean(),
temporaryMode: z.string().optional().nullable(),
temporaryRange: z.string().nullable().optional(),
temporaryAccessStartTime: z.date().nullable().optional(),
temporaryAccessEndTime: z.date().nullable().optional()
})
),
identity: IdentitiesSchema.pick({ name: true, id: true, authMethod: true })
})
})
}
},
handler: async (req) => {
const identityMembership = await server.services.identityProject.getProjectIdentityByIdentityId({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
projectId: req.params.projectId,
identityId: req.params.identityId
});
return { identityMembership };
}
});
};

View File

@@ -2,7 +2,7 @@ import { z } from "zod";
import { ProjectMembershipsSchema } from "@app/db/schemas";
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
import { PROJECTS } from "@app/lib/api-docs";
import { PROJECT_USERS } from "@app/lib/api-docs";
import { writeLimit } from "@app/server/config/rateLimiter";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type";
@@ -22,11 +22,11 @@ export const registerProjectMembershipRouter = async (server: FastifyZodProvider
}
],
params: z.object({
projectId: z.string().describe(PROJECTS.INVITE_MEMBER.projectId)
projectId: z.string().describe(PROJECT_USERS.INVITE_MEMBER.projectId)
}),
body: z.object({
emails: z.string().email().array().default([]).describe(PROJECTS.INVITE_MEMBER.emails),
usernames: z.string().array().default([]).describe(PROJECTS.INVITE_MEMBER.usernames)
emails: z.string().email().array().default([]).describe(PROJECT_USERS.INVITE_MEMBER.emails),
usernames: z.string().array().default([]).describe(PROJECT_USERS.INVITE_MEMBER.usernames)
}),
response: {
200: z.object({
@@ -77,11 +77,11 @@ export const registerProjectMembershipRouter = async (server: FastifyZodProvider
}
],
params: z.object({
projectId: z.string().describe(PROJECTS.REMOVE_MEMBER.projectId)
projectId: z.string().describe(PROJECT_USERS.REMOVE_MEMBER.projectId)
}),
body: z.object({
emails: z.string().email().array().default([]).describe(PROJECTS.REMOVE_MEMBER.emails),
usernames: z.string().array().default([]).describe(PROJECTS.REMOVE_MEMBER.usernames)
emails: z.string().email().array().default([]).describe(PROJECT_USERS.REMOVE_MEMBER.emails),
usernames: z.string().array().default([]).describe(PROJECT_USERS.REMOVE_MEMBER.usernames)
}),
response: {
200: z.object({

View File

@@ -39,6 +39,12 @@ export const identityAccessTokenDALFactory = (db: TDbClient) => {
`${TableName.IdentityAwsAuth}.identityId`
);
})
.leftJoin(TableName.IdentityAzureAuth, (qb) => {
qb.on(`${TableName.Identity}.authMethod`, db.raw("?", [IdentityAuthMethod.AZURE_AUTH])).andOn(
`${TableName.Identity}.id`,
`${TableName.IdentityAzureAuth}.identityId`
);
})
.leftJoin(TableName.IdentityKubernetesAuth, (qb) => {
qb.on(`${TableName.Identity}.authMethod`, db.raw("?", [IdentityAuthMethod.KUBERNETES_AUTH])).andOn(
`${TableName.Identity}.id`,
@@ -50,6 +56,7 @@ export const identityAccessTokenDALFactory = (db: TDbClient) => {
db.ref("accessTokenTrustedIps").withSchema(TableName.IdentityUniversalAuth).as("accessTokenTrustedIpsUa"),
db.ref("accessTokenTrustedIps").withSchema(TableName.IdentityGcpAuth).as("accessTokenTrustedIpsGcp"),
db.ref("accessTokenTrustedIps").withSchema(TableName.IdentityAwsAuth).as("accessTokenTrustedIpsAws"),
db.ref("accessTokenTrustedIps").withSchema(TableName.IdentityAzureAuth).as("accessTokenTrustedIpsAzure"),
db.ref("accessTokenTrustedIps").withSchema(TableName.IdentityKubernetesAuth).as("accessTokenTrustedIpsK8s"),
db.ref("name").withSchema(TableName.Identity)
)
@@ -63,6 +70,7 @@ export const identityAccessTokenDALFactory = (db: TDbClient) => {
doc.accessTokenTrustedIpsUa ||
doc.accessTokenTrustedIpsGcp ||
doc.accessTokenTrustedIpsAws ||
doc.accessTokenTrustedIpsAzure ||
doc.accessTokenTrustedIpsK8s
};
} catch (error) {
@@ -70,5 +78,48 @@ export const identityAccessTokenDALFactory = (db: TDbClient) => {
}
};
return { ...identityAccessTokenOrm, findOne };
const removeExpiredTokens = async (tx?: Knex) => {
try {
const docs = (tx || db)(TableName.IdentityAccessToken)
.where({
isAccessTokenRevoked: true
})
.orWhere((qb) => {
void qb
.where("accessTokenNumUsesLimit", ">", 0)
.andWhere(
"accessTokenNumUses",
">=",
db.ref("accessTokenNumUsesLimit").withSchema(TableName.IdentityAccessToken)
);
})
.orWhere((qb) => {
void qb.where("accessTokenTTL", ">", 0).andWhere((qb2) => {
void qb2
.where((qb3) => {
void qb3
.whereNotNull("accessTokenLastRenewedAt")
// accessTokenLastRenewedAt + convert_integer_to_seconds(accessTokenTTL) < present_date
.andWhereRaw(
`"${TableName.IdentityAccessToken}"."accessTokenLastRenewedAt" + make_interval(secs => "${TableName.IdentityAccessToken}"."accessTokenTTL") < NOW()`
);
})
.orWhere((qb3) => {
void qb3
.whereNull("accessTokenLastRenewedAt")
// created + convert_integer_to_seconds(accessTokenTTL) < present_date
.andWhereRaw(
`"${TableName.IdentityAccessToken}"."createdAt" + make_interval(secs => "${TableName.IdentityAccessToken}"."accessTokenTTL") < NOW()`
);
});
});
})
.delete();
return await docs;
} catch (error) {
throw new DatabaseError({ error, name: "IdentityAccessTokenPrune" });
}
};
return { ...identityAccessTokenOrm, findOne, removeExpiredTokens };
};

View File

@@ -21,17 +21,18 @@ export const identityAccessTokenServiceFactory = ({
identityAccessTokenDAL,
identityOrgMembershipDAL
}: TIdentityAccessTokenServiceFactoryDep) => {
const validateAccessTokenExp = (identityAccessToken: TIdentityAccessTokens) => {
const validateAccessTokenExp = async (identityAccessToken: TIdentityAccessTokens) => {
const {
id: tokenId,
accessTokenTTL,
accessTokenNumUses,
accessTokenNumUsesLimit,
accessTokenLastRenewedAt,
accessTokenMaxTTL,
createdAt: accessTokenCreatedAt
} = identityAccessToken;
if (accessTokenNumUsesLimit > 0 && accessTokenNumUses > 0 && accessTokenNumUses >= accessTokenNumUsesLimit) {
await identityAccessTokenDAL.deleteById(tokenId);
throw new BadRequestError({
message: "Unable to renew because access token number of uses limit reached"
});
@@ -46,41 +47,26 @@ export const identityAccessTokenServiceFactory = ({
const ttlInMilliseconds = Number(accessTokenTTL) * 1000;
const expirationDate = new Date(accessTokenRenewed.getTime() + ttlInMilliseconds);
if (currentDate > expirationDate)
if (currentDate > expirationDate) {
await identityAccessTokenDAL.deleteById(tokenId);
throw new UnauthorizedError({
message: "Failed to renew MI access token due to TTL expiration"
});
}
} else {
// access token has never been renewed
const accessTokenCreated = new Date(accessTokenCreatedAt);
const ttlInMilliseconds = Number(accessTokenTTL) * 1000;
const expirationDate = new Date(accessTokenCreated.getTime() + ttlInMilliseconds);
if (currentDate > expirationDate)
if (currentDate > expirationDate) {
await identityAccessTokenDAL.deleteById(tokenId);
throw new UnauthorizedError({
message: "Failed to renew MI access token due to TTL expiration"
});
}
}
}
// max ttl checks
if (Number(accessTokenMaxTTL) > 0) {
const accessTokenCreated = new Date(accessTokenCreatedAt);
const ttlInMilliseconds = Number(accessTokenMaxTTL) * 1000;
const currentDate = new Date();
const expirationDate = new Date(accessTokenCreated.getTime() + ttlInMilliseconds);
if (currentDate > expirationDate)
throw new UnauthorizedError({
message: "Failed to renew MI access token due to Max TTL expiration"
});
const extendToDate = new Date(currentDate.getTime() + Number(accessTokenTTL));
if (extendToDate > expirationDate)
throw new UnauthorizedError({
message: "Failed to renew MI access token past its Max TTL expiration"
});
}
};
const renewAccessToken = async ({ accessToken }: TRenewAccessTokenDTO) => {
@@ -97,7 +83,32 @@ export const identityAccessTokenServiceFactory = ({
});
if (!identityAccessToken) throw new UnauthorizedError();
validateAccessTokenExp(identityAccessToken);
await validateAccessTokenExp(identityAccessToken);
const { accessTokenMaxTTL, createdAt: accessTokenCreatedAt, accessTokenTTL } = identityAccessToken;
// max ttl checks - will it go above max ttl
if (Number(accessTokenMaxTTL) > 0) {
const accessTokenCreated = new Date(accessTokenCreatedAt);
const ttlInMilliseconds = Number(accessTokenMaxTTL) * 1000;
const currentDate = new Date();
const expirationDate = new Date(accessTokenCreated.getTime() + ttlInMilliseconds);
if (currentDate > expirationDate) {
await identityAccessTokenDAL.deleteById(identityAccessToken.id);
throw new UnauthorizedError({
message: "Failed to renew MI access token due to Max TTL expiration"
});
}
const extendToDate = new Date(currentDate.getTime() + Number(accessTokenTTL * 1000));
if (extendToDate > expirationDate) {
await identityAccessTokenDAL.deleteById(identityAccessToken.id);
throw new UnauthorizedError({
message: "Failed to renew MI access token past its Max TTL expiration"
});
}
}
const updatedIdentityAccessToken = await identityAccessTokenDAL.updateById(identityAccessToken.id, {
accessTokenLastRenewedAt: new Date()
@@ -131,7 +142,7 @@ export const identityAccessTokenServiceFactory = ({
});
if (!identityAccessToken) throw new UnauthorizedError();
if (ipAddress) {
if (ipAddress && identityAccessToken) {
checkIPAgainstBlocklist({
ipAddress,
trustedIps: identityAccessToken?.accessTokenTrustedIps as TIp[]
@@ -146,7 +157,14 @@ export const identityAccessTokenServiceFactory = ({
throw new UnauthorizedError({ message: "Identity does not belong to any organization" });
}
validateAccessTokenExp(identityAccessToken);
await validateAccessTokenExp(identityAccessToken);
await identityAccessTokenDAL.updateById(identityAccessToken.id, {
accessTokenLastUsedAt: new Date(),
$incr: {
accessTokenNumUses: 1
}
});
return { ...identityAccessToken, orgId: identityOrgMembership.orgId };
};

View File

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

View File

@@ -0,0 +1,34 @@
import axios from "axios";
import jwt from "jsonwebtoken";
import { UnauthorizedError } from "@app/lib/errors";
import { TAzureAuthJwtPayload, TAzureJwksUriResponse, TDecodedAzureAuthJwt } from "./identity-azure-auth-types";
export const validateAzureIdentity = async ({
tenantId,
resource,
jwt: azureJwt
}: {
tenantId: string;
resource: string;
jwt: string;
}) => {
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);
const signingKeys = data.keys;
const signingKey = signingKeys.find((key) => key.kid === kid);
if (!signingKey) throw new UnauthorizedError();
const publicKey = `-----BEGIN CERTIFICATE-----\n${signingKey.x5c[0]}\n-----END CERTIFICATE-----`;
return jwt.verify(azureJwt, publicKey, {
audience: resource,
issuer: `https://sts.windows.net/${tenantId}/`
}) as TAzureAuthJwtPayload;
};

View File

@@ -0,0 +1,286 @@
import { ForbiddenError } from "@casl/ability";
import jwt from "jsonwebtoken";
import { IdentityAuthMethod } from "@app/db/schemas";
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
import { OrgPermissionActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
import { getConfig } from "@app/lib/config/env";
import { BadRequestError, UnauthorizedError } from "@app/lib/errors";
import { extractIPDetails, isValidIpOrCidr } from "@app/lib/ip";
import { AuthTokenType } from "../auth/auth-type";
import { TIdentityDALFactory } from "../identity/identity-dal";
import { TIdentityOrgDALFactory } from "../identity/identity-org-dal";
import { TIdentityAccessTokenDALFactory } from "../identity-access-token/identity-access-token-dal";
import { TIdentityAccessTokenJwtPayload } from "../identity-access-token/identity-access-token-types";
import { TIdentityAzureAuthDALFactory } from "./identity-azure-auth-dal";
import { validateAzureIdentity } from "./identity-azure-auth-fns";
import {
TAttachAzureAuthDTO,
TGetAzureAuthDTO,
TLoginAzureAuthDTO,
TUpdateAzureAuthDTO
} from "./identity-azure-auth-types";
type TIdentityAzureAuthServiceFactoryDep = {
identityAzureAuthDAL: Pick<TIdentityAzureAuthDALFactory, "findOne" | "transaction" | "create" | "updateById">;
identityOrgMembershipDAL: Pick<TIdentityOrgDALFactory, "findOne">;
identityAccessTokenDAL: Pick<TIdentityAccessTokenDALFactory, "create">;
identityDAL: Pick<TIdentityDALFactory, "updateById">;
permissionService: Pick<TPermissionServiceFactory, "getOrgPermission">;
licenseService: Pick<TLicenseServiceFactory, "getPlan">;
};
export type TIdentityAzureAuthServiceFactory = ReturnType<typeof identityAzureAuthServiceFactory>;
export const identityAzureAuthServiceFactory = ({
identityAzureAuthDAL,
identityOrgMembershipDAL,
identityAccessTokenDAL,
identityDAL,
permissionService,
licenseService
}: TIdentityAzureAuthServiceFactoryDep) => {
const login = async ({ identityId, jwt: azureJwt }: TLoginAzureAuthDTO) => {
const identityAzureAuth = await identityAzureAuthDAL.findOne({ identityId });
if (!identityAzureAuth) throw new UnauthorizedError();
const identityMembershipOrg = await identityOrgMembershipDAL.findOne({ identityId: identityAzureAuth.identityId });
if (!identityMembershipOrg) throw new UnauthorizedError();
const azureIdentity = await validateAzureIdentity({
tenantId: identityAzureAuth.tenantId,
resource: identityAzureAuth.resource,
jwt: azureJwt
});
if (azureIdentity.tid !== identityAzureAuth.tenantId) throw new UnauthorizedError();
if (identityAzureAuth.allowedServicePrincipalIds) {
// validate if the service principal id is in the list of allowed service principal ids
const isServicePrincipalAllowed = identityAzureAuth.allowedServicePrincipalIds
.split(",")
.map((servicePrincipalId) => servicePrincipalId.trim())
.some((servicePrincipalId) => servicePrincipalId === azureIdentity.oid);
if (!isServicePrincipalAllowed) throw new UnauthorizedError();
}
const identityAccessToken = await identityAzureAuthDAL.transaction(async (tx) => {
const newToken = await identityAccessTokenDAL.create(
{
identityId: identityAzureAuth.identityId,
isAccessTokenRevoked: false,
accessTokenTTL: identityAzureAuth.accessTokenTTL,
accessTokenMaxTTL: identityAzureAuth.accessTokenMaxTTL,
accessTokenNumUses: 0,
accessTokenNumUsesLimit: identityAzureAuth.accessTokenNumUsesLimit
},
tx
);
return newToken;
});
const appCfg = getConfig();
const accessToken = jwt.sign(
{
identityId: identityAzureAuth.identityId,
identityAccessTokenId: identityAccessToken.id,
authTokenType: AuthTokenType.IDENTITY_ACCESS_TOKEN
} as TIdentityAccessTokenJwtPayload,
appCfg.AUTH_SECRET,
{
expiresIn:
Number(identityAccessToken.accessTokenMaxTTL) === 0
? undefined
: Number(identityAccessToken.accessTokenMaxTTL)
}
);
return { accessToken, identityAzureAuth, identityAccessToken, identityMembershipOrg };
};
const attachAzureAuth = async ({
identityId,
tenantId,
resource,
allowedServicePrincipalIds,
accessTokenTTL,
accessTokenMaxTTL,
accessTokenNumUsesLimit,
accessTokenTrustedIps,
actorId,
actorAuthMethod,
actor,
actorOrgId
}: TAttachAzureAuthDTO) => {
const identityMembershipOrg = await identityOrgMembershipDAL.findOne({ identityId });
if (!identityMembershipOrg) throw new BadRequestError({ message: "Failed to find identity" });
if (identityMembershipOrg.identity.authMethod)
throw new BadRequestError({
message: "Failed to add Azure Auth to already configured identity"
});
if (accessTokenMaxTTL > 0 && accessTokenTTL > accessTokenMaxTTL) {
throw new BadRequestError({ message: "Access token TTL cannot be greater than max TTL" });
}
const { permission } = await permissionService.getOrgPermission(
actor,
actorId,
identityMembershipOrg.orgId,
actorAuthMethod,
actorOrgId
);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.Identity);
const plan = await licenseService.getPlan(identityMembershipOrg.orgId);
const reformattedAccessTokenTrustedIps = accessTokenTrustedIps.map((accessTokenTrustedIp) => {
if (
!plan.ipAllowlisting &&
accessTokenTrustedIp.ipAddress !== "0.0.0.0/0" &&
accessTokenTrustedIp.ipAddress !== "::/0"
)
throw new BadRequestError({
message:
"Failed to add IP access range to access token due to plan restriction. Upgrade plan to add IP access range."
});
if (!isValidIpOrCidr(accessTokenTrustedIp.ipAddress))
throw new BadRequestError({
message: "The IP is not a valid IPv4, IPv6, or CIDR block"
});
return extractIPDetails(accessTokenTrustedIp.ipAddress);
});
const identityAzureAuth = await identityAzureAuthDAL.transaction(async (tx) => {
const doc = await identityAzureAuthDAL.create(
{
identityId: identityMembershipOrg.identityId,
tenantId,
resource,
allowedServicePrincipalIds,
accessTokenMaxTTL,
accessTokenTTL,
accessTokenNumUsesLimit,
accessTokenTrustedIps: JSON.stringify(reformattedAccessTokenTrustedIps)
},
tx
);
await identityDAL.updateById(
identityMembershipOrg.identityId,
{
authMethod: IdentityAuthMethod.AZURE_AUTH
},
tx
);
return doc;
});
return { ...identityAzureAuth, orgId: identityMembershipOrg.orgId };
};
const updateAzureAuth = async ({
identityId,
tenantId,
resource,
allowedServicePrincipalIds,
accessTokenTTL,
accessTokenMaxTTL,
accessTokenNumUsesLimit,
accessTokenTrustedIps,
actorId,
actorAuthMethod,
actor,
actorOrgId
}: TUpdateAzureAuthDTO) => {
const identityMembershipOrg = await identityOrgMembershipDAL.findOne({ identityId });
if (!identityMembershipOrg) throw new BadRequestError({ message: "Failed to find identity" });
if (identityMembershipOrg.identity?.authMethod !== IdentityAuthMethod.AZURE_AUTH)
throw new BadRequestError({
message: "Failed to update Azure Auth"
});
const identityGcpAuth = await identityAzureAuthDAL.findOne({ identityId });
if (
(accessTokenMaxTTL || identityGcpAuth.accessTokenMaxTTL) > 0 &&
(accessTokenTTL || identityGcpAuth.accessTokenMaxTTL) > (accessTokenMaxTTL || identityGcpAuth.accessTokenMaxTTL)
) {
throw new BadRequestError({ message: "Access token TTL cannot be greater than max TTL" });
}
const { permission } = await permissionService.getOrgPermission(
actor,
actorId,
identityMembershipOrg.orgId,
actorAuthMethod,
actorOrgId
);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Identity);
const plan = await licenseService.getPlan(identityMembershipOrg.orgId);
const reformattedAccessTokenTrustedIps = accessTokenTrustedIps?.map((accessTokenTrustedIp) => {
if (
!plan.ipAllowlisting &&
accessTokenTrustedIp.ipAddress !== "0.0.0.0/0" &&
accessTokenTrustedIp.ipAddress !== "::/0"
)
throw new BadRequestError({
message:
"Failed to add IP access range to access token due to plan restriction. Upgrade plan to add IP access range."
});
if (!isValidIpOrCidr(accessTokenTrustedIp.ipAddress))
throw new BadRequestError({
message: "The IP is not a valid IPv4, IPv6, or CIDR block"
});
return extractIPDetails(accessTokenTrustedIp.ipAddress);
});
const updatedAzureAuth = await identityAzureAuthDAL.updateById(identityGcpAuth.id, {
tenantId,
resource,
allowedServicePrincipalIds,
accessTokenMaxTTL,
accessTokenTTL,
accessTokenNumUsesLimit,
accessTokenTrustedIps: reformattedAccessTokenTrustedIps
? JSON.stringify(reformattedAccessTokenTrustedIps)
: undefined
});
return {
...updatedAzureAuth,
orgId: identityMembershipOrg.orgId
};
};
const getAzureAuth = async ({ identityId, actorId, actor, actorAuthMethod, actorOrgId }: TGetAzureAuthDTO) => {
const identityMembershipOrg = await identityOrgMembershipDAL.findOne({ identityId });
if (!identityMembershipOrg) throw new BadRequestError({ message: "Failed to find identity" });
if (identityMembershipOrg.identity?.authMethod !== IdentityAuthMethod.AZURE_AUTH)
throw new BadRequestError({
message: "The identity does not have Azure Auth attached"
});
const identityAzureAuth = await identityAzureAuthDAL.findOne({ identityId });
const { permission } = await permissionService.getOrgPermission(
actor,
actorId,
identityMembershipOrg.orgId,
actorAuthMethod,
actorOrgId
);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Identity);
return { ...identityAzureAuth, orgId: identityMembershipOrg.orgId };
};
return {
login,
attachAzureAuth,
updateAzureAuth,
getAzureAuth
};
};

View File

@@ -0,0 +1,120 @@
import { TProjectPermission } from "@app/lib/types";
export type TLoginAzureAuthDTO = {
identityId: string;
jwt: string;
};
export type TAttachAzureAuthDTO = {
identityId: string;
tenantId: string;
resource: string;
allowedServicePrincipalIds: string;
accessTokenTTL: number;
accessTokenMaxTTL: number;
accessTokenNumUsesLimit: number;
accessTokenTrustedIps: { ipAddress: string }[];
} & Omit<TProjectPermission, "projectId">;
export type TUpdateAzureAuthDTO = {
identityId: string;
tenantId?: string;
resource?: string;
allowedServicePrincipalIds?: string;
accessTokenTTL?: number;
accessTokenMaxTTL?: number;
accessTokenNumUsesLimit?: number;
accessTokenTrustedIps?: { ipAddress: string }[];
} & Omit<TProjectPermission, "projectId">;
export type TGetAzureAuthDTO = {
identityId: string;
} & Omit<TProjectPermission, "projectId">;
export type TAzureJwksUriResponse = {
keys: {
kty: string;
use: string;
kid: string;
x5t: string;
n: string;
e: string;
x5c: string[];
}[];
};
type TUserPayload = {
aud: string;
iss: string;
iat: number;
nbf: number;
exp: number;
acr: string;
aio: string;
amr: string[];
appid: string;
appidacr: string;
family_name: string;
given_name: string;
groups: string[];
idtyp: string;
ipaddr: string;
name: string;
oid: string;
puid: string;
rh: string;
scp: string;
sub: string;
tid: string;
unique_name: string;
upn: string;
uti: string;
ver: string;
wids: string[];
xms_cae: string;
xms_cc: string[];
xms_filter_index: string[];
xms_rd: string;
xms_ssm: string;
xms_tcdt: number;
};
type TAppPayload = {
aud: string;
iss: string;
iat: number;
nbf: number;
exp: number;
aio: string;
appid: string;
appidacr: string;
idp: string;
idtyp: string;
oid: string; // service principal id
rh: string;
sub: string;
tid: string;
uti: string;
ver: string;
xms_cae: string;
xms_cc: string[];
xms_rd: string;
xms_ssm: string;
xms_tcdt: number;
};
export type TAzureAuthJwtPayload = TUserPayload | TAppPayload;
export type TDecodedAzureAuthJwt = {
header: {
type: string;
alg: string;
x5t: string;
kid: string;
};
payload: TAzureAuthJwtPayload;
signature: string;
metadata: {
[key: string]: string;
};
};

View File

@@ -0,0 +1,14 @@
import { z } from "zod";
export const validateAzureAuthField = z
.string()
.trim()
.default("")
.transform((data) => {
if (data === "") return "";
// Trim each ID and join with ', ' to ensure formatting
return data
.split(",")
.map((id) => id.trim())
.join(", ");
});

View File

@@ -10,11 +10,16 @@ export type TIdentityProjectDALFactory = ReturnType<typeof identityProjectDALFac
export const identityProjectDALFactory = (db: TDbClient) => {
const identityProjectOrm = ormify(db, TableName.IdentityProjectMembership);
const findByProjectId = async (projectId: string, tx?: Knex) => {
const findByProjectId = async (projectId: string, filter: { identityId?: string } = {}, tx?: Knex) => {
try {
const docs = await (tx || db)(TableName.IdentityProjectMembership)
.where(`${TableName.IdentityProjectMembership}.projectId`, projectId)
.join(TableName.Identity, `${TableName.IdentityProjectMembership}.identityId`, `${TableName.Identity}.id`)
.where((qb) => {
if (filter.identityId) {
void qb.where("identityId", filter.identityId);
}
})
.join(
TableName.IdentityProjectMembershipRole,
`${TableName.IdentityProjectMembershipRole}.projectMembershipId`,

View File

@@ -18,6 +18,7 @@ import { TIdentityProjectMembershipRoleDALFactory } from "./identity-project-mem
import {
TCreateProjectIdentityDTO,
TDeleteProjectIdentityDTO,
TGetProjectIdentityByIdentityIdDTO,
TListProjectIdentityDTO,
TUpdateProjectIdentityDTO
} from "./identity-project-types";
@@ -51,7 +52,7 @@ export const identityProjectServiceFactory = ({
actorOrgId,
actorAuthMethod,
projectId,
role
roles
}: TCreateProjectIdentityDTO) => {
const { permission } = await permissionService.getProjectPermission(
actor,
@@ -78,18 +79,33 @@ export const identityProjectServiceFactory = ({
message: `Failed to find identity with id ${identityId}`
});
const { permission: rolePermission, role: customRole } = await permissionService.getProjectPermissionByRole(
role,
project.id
for await (const { role: requestedRoleChange } of roles) {
const { permission: rolePermission } = await permissionService.getProjectPermissionByRole(
requestedRoleChange,
projectId
);
const hasRequiredPriviledges = isAtLeastAsPrivileged(permission, rolePermission);
if (!hasRequiredPriviledges) {
throw new ForbiddenRequestError({ message: "Failed to change to a more privileged role" });
}
}
// validate custom roles input
const customInputRoles = roles.filter(
({ role }) => !Object.values(ProjectMembershipRole).includes(role as ProjectMembershipRole)
);
const hasCustomRole = Boolean(customInputRoles.length);
const customRoles = hasCustomRole
? await projectRoleDAL.find({
projectId,
$in: { slug: customInputRoles.map(({ role }) => role) }
})
: [];
if (customRoles.length !== customInputRoles.length) throw new BadRequestError({ message: "Custom role not found" });
const hasPriviledge = isAtLeastAsPrivileged(permission, rolePermission);
if (!hasPriviledge)
throw new ForbiddenRequestError({
message: "Failed to add identity to project with more privileged role"
});
const isCustomRole = Boolean(customRole);
const customRolesGroupBySlug = groupBy(customRoles, ({ slug }) => slug);
const projectIdentity = await identityProjectDAL.transaction(async (tx) => {
const identityProjectMembership = await identityProjectDAL.create(
{
@@ -98,16 +114,32 @@ export const identityProjectServiceFactory = ({
},
tx
);
const sanitizedProjectMembershipRoles = roles.map((inputRole) => {
const isCustomRole = Boolean(customRolesGroupBySlug?.[inputRole.role]?.[0]);
if (!inputRole.isTemporary) {
return {
projectMembershipId: identityProjectMembership.id,
role: isCustomRole ? ProjectMembershipRole.Custom : inputRole.role,
customRoleId: customRolesGroupBySlug[inputRole.role] ? customRolesGroupBySlug[inputRole.role][0].id : null
};
}
await identityProjectMembershipRoleDAL.create(
{
// check cron or relative here later for now its just relative
const relativeTimeInMs = ms(inputRole.temporaryRange);
return {
projectMembershipId: identityProjectMembership.id,
role: isCustomRole ? ProjectMembershipRole.Custom : role,
customRoleId: customRole?.id
},
tx
);
return identityProjectMembership;
role: isCustomRole ? ProjectMembershipRole.Custom : inputRole.role,
customRoleId: customRolesGroupBySlug[inputRole.role] ? customRolesGroupBySlug[inputRole.role][0].id : null,
isTemporary: true,
temporaryMode: ProjectUserMembershipTemporaryMode.Relative,
temporaryRange: inputRole.temporaryRange,
temporaryAccessStartTime: new Date(inputRole.temporaryAccessStartTime),
temporaryAccessEndTime: new Date(new Date(inputRole.temporaryAccessStartTime).getTime() + relativeTimeInMs)
};
});
const identityRoles = await identityProjectMembershipRoleDAL.insertMany(sanitizedProjectMembershipRoles, tx);
return { ...identityProjectMembership, roles: identityRoles };
});
return projectIdentity;
};
@@ -227,7 +259,7 @@ export const identityProjectServiceFactory = ({
if (!hasRequiredPriviledges)
throw new ForbiddenRequestError({ message: "Failed to delete more privileged identity" });
const [deletedIdentity] = await identityProjectDAL.delete({ identityId });
const [deletedIdentity] = await identityProjectDAL.delete({ identityId, projectId });
return deletedIdentity;
};
@@ -251,10 +283,33 @@ export const identityProjectServiceFactory = ({
return identityMemberships;
};
const getProjectIdentityByIdentityId = async ({
projectId,
actor,
actorId,
actorAuthMethod,
actorOrgId,
identityId
}: TGetProjectIdentityByIdentityIdDTO) => {
const { permission } = await permissionService.getProjectPermission(
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Identity);
const [identityMembership] = await identityProjectDAL.findByProjectId(projectId, { identityId });
if (!identityMembership) throw new BadRequestError({ message: `Membership not found for identity ${identityId}` });
return identityMembership;
};
return {
createProjectIdentity,
updateProjectIdentity,
deleteProjectIdentity,
listProjectIdentities
listProjectIdentities,
getProjectIdentityByIdentityId
};
};

View File

@@ -4,7 +4,19 @@ import { ProjectUserMembershipTemporaryMode } from "../project-membership/projec
export type TCreateProjectIdentityDTO = {
identityId: string;
role: string;
roles: (
| {
role: string;
isTemporary?: false;
}
| {
role: string;
isTemporary: true;
temporaryMode: ProjectUserMembershipTemporaryMode.Relative;
temporaryRange: string;
temporaryAccessStartTime: string;
}
)[];
} & TProjectPermission;
export type TUpdateProjectIdentityDTO = {
@@ -29,3 +41,7 @@ export type TDeleteProjectIdentityDTO = {
} & TProjectPermission;
export type TListProjectIdentityDTO = TProjectPermission;
export type TGetProjectIdentityByIdentityIdDTO = {
identityId: string;
} & TProjectPermission;

View File

@@ -43,6 +43,11 @@ export enum IntegrationInitialSyncBehavior {
PREFER_SOURCE = "prefer-source"
}
export enum IntegrationMappingBehavior {
ONE_TO_ONE = "one-to-one",
MANY_TO_ONE = "many-to-one"
}
export enum IntegrationUrls {
// integration oauth endpoints
GCP_TOKEN_URL = "https://oauth2.googleapis.com/token",

View File

@@ -30,7 +30,12 @@ import { BadRequestError } from "@app/lib/errors";
import { TCreateManySecretsRawFn, TUpdateManySecretsRawFn } from "@app/services/secret/secret-types";
import { TIntegrationDALFactory } from "../integration/integration-dal";
import { IntegrationInitialSyncBehavior, Integrations, IntegrationUrls } from "./integration-list";
import {
IntegrationInitialSyncBehavior,
IntegrationMappingBehavior,
Integrations,
IntegrationUrls
} from "./integration-list";
const getSecretKeyValuePair = (secrets: Record<string, { value: string | null; comment?: string } | null>) =>
Object.keys(secrets).reduce<Record<string, string | null | undefined>>((prev, key) => {
@@ -570,134 +575,149 @@ const syncSecretsAWSSecretManager = async ({
accessId: string | null;
accessToken: string;
}) => {
let secretsManager;
const secKeyVal = getSecretKeyValuePair(secrets);
const metadata = z.record(z.any()).parse(integration.metadata || {});
try {
if (!accessId) return;
secretsManager = new SecretsManagerClient({
region: integration.region as string,
credentials: {
accessKeyId: accessId,
secretAccessKey: accessToken
if (!accessId) return;
const secretsManager = new SecretsManagerClient({
region: integration.region as string,
credentials: {
accessKeyId: accessId,
secretAccessKey: accessToken
}
});
const processAwsSecret = async (
secretId: string,
secretValue: Record<string, string | null | undefined> | string
) => {
try {
const awsSecretManagerSecret = await secretsManager.send(
new GetSecretValueCommand({
SecretId: secretId
})
);
let secretToCompare;
if (awsSecretManagerSecret?.SecretString) {
if (typeof secretValue === "string") {
secretToCompare = awsSecretManagerSecret.SecretString;
} else {
secretToCompare = JSON.parse(awsSecretManagerSecret.SecretString);
}
}
});
const awsSecretManagerSecret = await secretsManager.send(
new GetSecretValueCommand({
SecretId: integration.app as string
})
);
if (!isEqual(secretToCompare, secretValue)) {
await secretsManager.send(
new UpdateSecretCommand({
SecretId: secretId,
SecretString: typeof secretValue === "string" ? secretValue : JSON.stringify(secretValue)
})
);
}
let awsSecretManagerSecretObj: { [key: string]: AWS.SecretsManager } = {};
const secretAWSTag = metadata.secretAWSTag as { key: string; value: string }[] | undefined;
if (awsSecretManagerSecret?.SecretString) {
awsSecretManagerSecretObj = JSON.parse(awsSecretManagerSecret.SecretString);
}
if (secretAWSTag && secretAWSTag.length) {
const describedSecret = await secretsManager.send(
// requires secretsmanager:DescribeSecret policy
new DescribeSecretCommand({
SecretId: secretId
})
);
if (!isEqual(awsSecretManagerSecretObj, secKeyVal)) {
await secretsManager.send(
new UpdateSecretCommand({
SecretId: integration.app as string,
SecretString: JSON.stringify(secKeyVal)
})
);
}
if (!describedSecret.Tags) return;
const secretAWSTag = metadata.secretAWSTag as { key: string; value: string }[] | undefined;
const integrationTagObj = secretAWSTag.reduce(
(acc, item) => {
acc[item.key] = item.value;
return acc;
},
{} as Record<string, string>
);
if (secretAWSTag && secretAWSTag.length) {
const describedSecret = await secretsManager.send(
// requires secretsmanager:DescribeSecret policy
new DescribeSecretCommand({
SecretId: integration.app as string
})
);
const awsTagObj = (describedSecret.Tags || []).reduce(
(acc, item) => {
if (item.Key && item.Value) {
acc[item.Key] = item.Value;
}
return acc;
},
{} as Record<string, string>
);
if (!describedSecret.Tags) return;
const tagsToUpdate: { Key: string; Value: string }[] = [];
const tagsToDelete: { Key: string; Value: string }[] = [];
const integrationTagObj = secretAWSTag.reduce(
(acc, item) => {
acc[item.key] = item.value;
return acc;
},
{} as Record<string, string>
);
const awsTagObj = (describedSecret.Tags || []).reduce(
(acc, item) => {
if (item.Key && item.Value) {
acc[item.Key] = item.Value;
describedSecret.Tags?.forEach((tag) => {
if (tag.Key && tag.Value) {
if (!(tag.Key in integrationTagObj)) {
// delete tag from AWS secret manager
tagsToDelete.push({
Key: tag.Key,
Value: tag.Value
});
} else if (tag.Value !== integrationTagObj[tag.Key]) {
// update tag in AWS secret manager
tagsToUpdate.push({
Key: tag.Key,
Value: integrationTagObj[tag.Key]
});
}
}
return acc;
},
{} as Record<string, string>
);
});
const tagsToUpdate: { Key: string; Value: string }[] = [];
const tagsToDelete: { Key: string; Value: string }[] = [];
describedSecret.Tags?.forEach((tag) => {
if (tag.Key && tag.Value) {
if (!(tag.Key in integrationTagObj)) {
// delete tag from AWS secret manager
tagsToDelete.push({
Key: tag.Key,
Value: tag.Value
});
} else if (tag.Value !== integrationTagObj[tag.Key]) {
// update tag in AWS secret manager
secretAWSTag?.forEach((tag) => {
if (!(tag.key in awsTagObj)) {
// create tag in AWS secret manager
tagsToUpdate.push({
Key: tag.Key,
Value: integrationTagObj[tag.Key]
Key: tag.key,
Value: tag.value
});
}
}
});
});
secretAWSTag?.forEach((tag) => {
if (!(tag.key in awsTagObj)) {
// create tag in AWS secret manager
tagsToUpdate.push({
Key: tag.key,
Value: tag.value
});
if (tagsToUpdate.length) {
await secretsManager.send(
new TagResourceCommand({
SecretId: secretId,
Tags: tagsToUpdate
})
);
}
});
if (tagsToUpdate.length) {
await secretsManager.send(
new TagResourceCommand({
SecretId: integration.app as string,
Tags: tagsToUpdate
})
);
if (tagsToDelete.length) {
await secretsManager.send(
new UntagResourceCommand({
SecretId: secretId,
TagKeys: tagsToDelete.map((tag) => tag.Key)
})
);
}
}
if (tagsToDelete.length) {
} catch (err) {
// case when AWS manager can't find the specified secret
if (err instanceof ResourceNotFoundException && secretsManager) {
await secretsManager.send(
new UntagResourceCommand({
SecretId: integration.app as string,
TagKeys: tagsToDelete.map((tag) => tag.Key)
new CreateSecretCommand({
Name: secretId,
SecretString: typeof secretValue === "string" ? secretValue : JSON.stringify(secretValue),
...(metadata.kmsKeyId && { KmsKeyId: metadata.kmsKeyId }),
Tags: metadata.secretAWSTag
? metadata.secretAWSTag.map((tag: { key: string; value: string }) => ({ Key: tag.key, Value: tag.value }))
: []
})
);
}
}
} catch (err) {
// case when AWS manager can't find the specified secret
if (err instanceof ResourceNotFoundException && secretsManager) {
await secretsManager.send(
new CreateSecretCommand({
Name: integration.app as string,
SecretString: JSON.stringify(secKeyVal),
...(metadata.kmsKeyId && { KmsKeyId: metadata.kmsKeyId }),
Tags: metadata.secretAWSTag
? metadata.secretAWSTag.map((tag: { key: string; value: string }) => ({ Key: tag.key, Value: tag.value }))
: []
})
);
};
if (metadata.mappingBehavior === IntegrationMappingBehavior.ONE_TO_ONE) {
for await (const [key, value] of Object.entries(secrets)) {
await processAwsSecret(key, value.value);
}
} else {
await processAwsSecret(integration.app as string, getSecretKeyValuePair(secrets));
}
};
@@ -2676,18 +2696,21 @@ const syncSecretsCloudflarePages = async ({
})
).data.result.deployment_configs[integration.targetEnvironment as string].env_vars;
// copy the secrets object, so we can set deleted keys to null
const secretsObj = Object.fromEntries(
Object.entries(getSecretKeyValuePair(secrets)).map(([key, val]) => [
key,
key in Object.keys(getSecretsRes) ? { type: "secret_text", value: val } : null
])
);
let secretEntries: [string, object | null][] = Object.entries(getSecretKeyValuePair(secrets)).map(([key, val]) => [
key,
{ type: "secret_text", value: val }
]);
if (getSecretsRes) {
const toDeleteKeys = Object.keys(getSecretsRes).filter((key) => !Object.keys(secrets).includes(key));
const toDeleteEntries: [string, null][] = toDeleteKeys.map((key) => [key, null]);
secretEntries = [...secretEntries, ...toDeleteEntries];
}
const data = {
deployment_configs: {
[integration.targetEnvironment as string]: {
env_vars: secretsObj
env_vars: Object.fromEntries(secretEntries)
}
}
};
@@ -2965,7 +2988,7 @@ const syncSecretsDigitalOceanAppPlatform = async ({
spec: {
name: integration.app,
...appSettings,
envs: Object.entries(secrets).map(([key, data]) => ({ key, value: data.value }))
envs: Object.entries(secrets).map(([key, data]) => ({ key, value: data.value, type: "SECRET" }))
}
},
{

View File

@@ -5,7 +5,7 @@ import { Integrations, IntegrationUrls } from "./integration-list";
type Team = {
name: string;
teamId: string;
id: string;
};
const getTeamsGitLab = async ({ url, accessToken }: { url: string; accessToken: string }) => {
const gitLabApiUrl = url ? `${url}/api` : IntegrationUrls.GITLAB_API_URL;
@@ -22,7 +22,7 @@ const getTeamsGitLab = async ({ url, accessToken }: { url: string; accessToken:
teams = res.map((t) => ({
name: t.name,
teamId: t.id
id: t.id.toString()
}));
return teams;

View File

@@ -3,6 +3,7 @@ import { decryptAsymmetric, infisicalSymmetricDecrypt } from "@app/lib/crypto/en
import { BadRequestError } from "@app/lib/errors";
import { TProjectBotDALFactory } from "@app/services/project-bot/project-bot-dal";
import { TProjectDALFactory } from "../project/project-dal";
import { TGetPrivateKeyDTO } from "./project-bot-types";
export const getBotPrivateKey = ({ bot }: TGetPrivateKeyDTO) =>
@@ -13,11 +14,17 @@ export const getBotPrivateKey = ({ bot }: TGetPrivateKeyDTO) =>
ciphertext: bot.encryptedPrivateKey
});
export const getBotKeyFnFactory = (projectBotDAL: TProjectBotDALFactory) => {
export const getBotKeyFnFactory = (
projectBotDAL: TProjectBotDALFactory,
projectDAL: Pick<TProjectDALFactory, "findById">
) => {
const getBotKeyFn = async (projectId: string) => {
const bot = await projectBotDAL.findOne({ projectId });
const project = await projectDAL.findById(projectId);
if (!project) throw new BadRequestError({ message: "Project not found during bot lookup." });
if (!bot) throw new BadRequestError({ message: "failed to find bot key" });
const bot = await projectBotDAL.findOne({ projectId: project.id });
if (!bot) throw new BadRequestError({ message: "Failed to find bot key" });
if (!bot.isActive) throw new BadRequestError({ message: "Bot is not active" });
if (!bot.encryptedProjectKeyNonce || !bot.encryptedProjectKey)
throw new BadRequestError({ message: "Encryption key missing" });

View File

@@ -25,7 +25,7 @@ export const projectBotServiceFactory = ({
projectDAL,
permissionService
}: TProjectBotServiceFactoryDep) => {
const getBotKeyFn = getBotKeyFnFactory(projectBotDAL);
const getBotKeyFn = getBotKeyFnFactory(projectBotDAL, projectDAL);
const getBotKey = async (projectId: string) => {
return getBotKeyFn(projectId);

View File

@@ -1,3 +1,5 @@
import { Knex } from "knex";
import { TDbClient } from "@app/db";
import { TableName, TUserEncryptionKeys } from "@app/db/schemas";
import { DatabaseError } from "@app/lib/errors";
@@ -9,11 +11,19 @@ export const projectMembershipDALFactory = (db: TDbClient) => {
const projectMemberOrm = ormify(db, TableName.ProjectMembership);
// special query
const findAllProjectMembers = async (projectId: string) => {
const findAllProjectMembers = async (projectId: string, filter: { usernames?: string[]; username?: string } = {}) => {
try {
const docs = await db(TableName.ProjectMembership)
.where({ [`${TableName.ProjectMembership}.projectId` as "projectId"]: projectId })
.join(TableName.Users, `${TableName.ProjectMembership}.userId`, `${TableName.Users}.id`)
.where((qb) => {
if (filter.usernames) {
void qb.whereIn("username", filter.usernames);
}
if (filter.username) {
void qb.where("username", filter.username);
}
})
.join<TUserEncryptionKeys>(
TableName.UserEncryptionKey,
`${TableName.UserEncryptionKey}.userId`,
@@ -96,9 +106,9 @@ export const projectMembershipDALFactory = (db: TDbClient) => {
}
};
const findProjectGhostUser = async (projectId: string) => {
const findProjectGhostUser = async (projectId: string, tx?: Knex) => {
try {
const ghostUser = await db(TableName.ProjectMembership)
const ghostUser = await (tx || db)(TableName.ProjectMembership)
.where({ projectId })
.join(TableName.Users, `${TableName.ProjectMembership}.userId`, `${TableName.Users}.id`)
.select(selectAllTableCols(TableName.Users))

View File

@@ -34,6 +34,7 @@ import {
TAddUsersToWorkspaceNonE2EEDTO,
TDeleteProjectMembershipOldDTO,
TDeleteProjectMembershipsDTO,
TGetProjectMembershipByUsernameDTO,
TGetProjectMembershipDTO,
TUpdateProjectMembershipDTO
} from "./project-membership-types";
@@ -89,6 +90,28 @@ export const projectMembershipServiceFactory = ({
return projectMembershipDAL.findAllProjectMembers(projectId);
};
const getProjectMembershipByUsername = async ({
actorId,
actor,
actorOrgId,
actorAuthMethod,
projectId,
username
}: TGetProjectMembershipByUsernameDTO) => {
const { permission } = await permissionService.getProjectPermission(
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Member);
const [membership] = await projectMembershipDAL.findAllProjectMembers(projectId, { username });
if (!membership) throw new BadRequestError({ message: `Project membership not found for user ${username}` });
return membership;
};
const addUsersToProject = async ({
projectId,
actorId,
@@ -510,6 +533,7 @@ export const projectMembershipServiceFactory = ({
return {
getProjectMemberships,
getProjectMembershipByUsername,
updateProjectMembership,
addUsersToProjectNonE2EE,
deleteProjectMemberships,

View File

@@ -9,6 +9,10 @@ export type TInviteUserToProjectDTO = {
emails: string[];
} & TProjectPermission;
export type TGetProjectMembershipByUsernameDTO = {
username: string;
} & TProjectPermission;
export type TUpdateProjectMembershipDTO = {
membershipId: string;
roles: (

View File

@@ -340,7 +340,7 @@ export const projectServiceFactory = ({
const deletedProject = await projectDAL.transaction(async (tx) => {
const delProject = await projectDAL.deleteById(project.id, tx);
const projectGhostUser = await projectMembershipDAL.findProjectGhostUser(project.id).catch(() => null);
const projectGhostUser = await projectMembershipDAL.findProjectGhostUser(project.id, tx).catch(() => null);
// Delete the org membership for the ghost user if it's found.
if (projectGhostUser) {

View File

@@ -0,0 +1,58 @@
import { TAuditLogDALFactory } from "@app/ee/services/audit-log/audit-log-dal";
import { logger } from "@app/lib/logger";
import { QueueJobs, QueueName, TQueueServiceFactory } from "@app/queue";
import { TIdentityAccessTokenDALFactory } from "../identity-access-token/identity-access-token-dal";
type TDailyResourceCleanUpQueueServiceFactoryDep = {
auditLogDAL: Pick<TAuditLogDALFactory, "pruneAuditLog">;
identityAccessTokenDAL: Pick<TIdentityAccessTokenDALFactory, "removeExpiredTokens">;
queueService: TQueueServiceFactory;
};
export type TDailyResourceCleanUpQueueServiceFactory = ReturnType<typeof dailyResourceCleanUpQueueServiceFactory>;
export const dailyResourceCleanUpQueueServiceFactory = ({
auditLogDAL,
queueService,
identityAccessTokenDAL
}: TDailyResourceCleanUpQueueServiceFactoryDep) => {
queueService.start(QueueName.DailyResourceCleanUp, async () => {
logger.info(`${QueueName.DailyResourceCleanUp}: queue task started`);
await auditLogDAL.pruneAuditLog();
await identityAccessTokenDAL.removeExpiredTokens();
logger.info(`${QueueName.DailyResourceCleanUp}: queue task completed`);
});
// we do a repeat cron job in utc timezone at 12 Midnight each day
const startCleanUp = async () => {
// TODO(akhilmhdh): remove later
await queueService.stopRepeatableJob(
QueueName.AuditLogPrune,
QueueJobs.AuditLogPrune,
{ pattern: "0 0 * * *", utc: true },
QueueName.AuditLogPrune // just a job id
);
// clear previous job
await queueService.stopRepeatableJob(
QueueName.DailyResourceCleanUp,
QueueJobs.DailyResourceCleanUp,
{ pattern: "0 0 * * *", utc: true },
QueueName.DailyResourceCleanUp // just a job id
);
await queueService.queue(QueueName.DailyResourceCleanUp, QueueJobs.DailyResourceCleanUp, undefined, {
delay: 5000,
jobId: QueueName.DailyResourceCleanUp,
repeat: { pattern: "0 0 * * *", utc: true }
});
};
queueService.listen(QueueName.DailyResourceCleanUp, "failed", (_, err) => {
logger.error(err, `${QueueName.DailyResourceCleanUp}: resource cleanup failed`);
});
return {
startCleanUp
};
};

View File

@@ -608,7 +608,7 @@ export const createManySecretsRawFnFactory = ({
secretVersionTagDAL,
folderDAL
}: TCreateManySecretsRawFnFactory) => {
const getBotKeyFn = getBotKeyFnFactory(projectBotDAL);
const getBotKeyFn = getBotKeyFnFactory(projectBotDAL, projectDAL);
const createManySecretsRawFn = async ({
projectId,
environment,
@@ -706,7 +706,7 @@ export const updateManySecretsRawFnFactory = ({
secretVersionTagDAL,
folderDAL
}: TUpdateManySecretsRawFnFactory) => {
const getBotKeyFn = getBotKeyFnFactory(projectBotDAL);
const getBotKeyFn = getBotKeyFnFactory(projectBotDAL, projectDAL);
const updateManySecretsRawFn = async ({
projectId,
environment,

View File

@@ -0,0 +1,28 @@
---
title: "Onboarding"
sidebarTitle: "Onboarding"
description: "This guide explains the onboarding process for new joiners at Infisical."
---
Welcome to Infisical!
The first few days of every new joiner are going to be packed with learning lots of new information, meeting new teammates, and understanding Infisical on a deeper level.
Plus, our team is remote-first and spread across the globe (from San Francisco to Philippines), so having a great onboarding experience is very important for the new joiner to feel part of the team and be excited about what we're doing as a company.
## Onboarding buddy
Every new joiner has an onboarding buddy who should ideally be in the the same timezone. The onboarding buddy should be able to help with any questions that pop up during the first few weeks. Of course, everyone is available to help, but it&apos;s good to have a dedicated person that you can go to with any questions.
## Onboarding Checklist
1. Join the weekly all-hands meeting. It typically happens on Monday's at 8:30am PT.
2. Ship something together on day one even if tiny! It feels great to hit the ground running, with a development environment all ready to go.
3. Check out the [Areas of Responsibility (AoR) Table](https://docs.google.com/spreadsheets/d/1RnXlGFg83Sgu0dh7ycuydsSobmFfI3A0XkGw7vrVxEI/edit?usp=sharing). This is helpful to know who you can ask about particular areas of Infisical. Feel free to add yourself to the areas you'd be most interesting to dive into.
4. Read the [Infisical Strategy Doc](https://docs.google.com/document/d/1oy_NP1Q_Zt1oqxLpyNkLIGmhAI3N28AmZq6dDIOONSQ/edit?usp=sharing).
5. Update your LinkedIn profile with one of [Infisical's official banners](https://drive.google.com/drive/u/0/folders/1oSNWjbpRl9oNYwxM_98IqzKs9fAskrb2) (if you want to). You can also coordinate your social posts in the #marketing Slack channel, so that we can boost it from Infisical's official social media accounts.
6. Over the first few weeks, feel free to schedule 1:1s with folks on the team to get to know them a bit better.
7. Change your Slack username in the users channel to `[NAME] (Infisical)`.
8. Go through the [technical overview](https://infisical.com/docs/internals/overview) of Infisical.

View File

@@ -0,0 +1,11 @@
---
title: "Infisical Company Handbook"
sidebarTitle: "Welcome"
description: "This handbook explains how we work at Infisical."
---
Welcome! This handbook explains how we work and what we stand for at Infisical.
Given that Infisical's core is open source, we decided to make this handbook also availably publicly to everyone.
You can treat it as a living document as more pages and information will be added over time.

View File

@@ -0,0 +1,27 @@
---
title: "Spenging Money"
sidebarTitle: "Spending Money"
description: "The guide to spending money at Infisical."
---
Fairly frequently, you might run into situations when you need to spend company money.
**Please spend money in a way that you think is in the best interest of the company.**
## Trivial expenses
We don't want you to be slowed down because you're waiting for an approval to purchase some SaaS. For trivial expenses  **Just do it**.
This means expenses that are:
1. Non-recurring AND less than $75/month in total.
2. Recurring AND less than $20/month.
## Saving receipts
Make sure you keep copies for all receipts. If you expense something on a company card and cannot provide a receipt, this may be deducted from your pay.
You should default to using your company card in all cases - it has no transaction fees. If using your personal card is unavoidable, please reach out to Maidul to get it reimbursed manually.
## Brex
We use Brex as our primary credit card provider. Don't have a company card yet? Reach out to Maidul.

View File

@@ -0,0 +1,13 @@
---
title: "Time Off"
sidebarTitle: "Time Off"
description: "The guide to taking time off at Infisical."
---
We offer eveyone at Infisical unlimited time off. We care about your results, not how long you work.
To request time off, just submit a request in Rippling and let Maidul know at least a week in advance.
## National holidays
Since Infisical's team is globally distributed, it is hard for us to keep track of all the various national holidays across many different countries. Whether you'd like to celebrate Christmas or National Brisket Day (which, by the way, is on May 28th), you are welcome to take PTO on those days  just let Maidul know at least a week ahead so that we can adjust our planning.

View File

@@ -1,6 +1,5 @@
{
"name": "Infisical",
"openapi": "https://app.infisical.com/api/docs/json",
"logo": {
"dark": "/logo/dark.svg",
"light": "/logo/light.svg",
@@ -44,33 +43,22 @@
"name": "Start for Free",
"url": "https://app.infisical.com/signup"
},
"tabs": [
{
"name": "Integrations",
"url": "integrations"
},
{
"name": "CLI",
"url": "cli"
},
{
"name": "API Reference",
"url": "api-reference"
},
{
"name": "SDKs",
"url": "sdks"
},
{
"name": "Changelog",
"url": "changelog"
}
],
"primaryTab": {
"name": "About"
},
"navigation": [
{
"group": "Getting Started",
"group": "Handbook",
"pages": [
"documentation/getting-started/introduction"
"handbook/overview"
]
},
{
"group": "How we work",
"pages": [
"handbook/onboarding",
"handbook/spending-money",
"handbook/time-off"
]
}
],

View File

@@ -1,7 +1,7 @@
#navbar .max-w-8xl {
max-width: 100%;
border-bottom: 1px solid #ebebeb;
background-color: #fcfcfc;
background-color: #F4F3EF;
}
.max-w-8xl {
@@ -14,7 +14,7 @@
padding-right: 30px;
border-right: 1px;
border-color: #cdd64b;
background-color: #fcfcfc;
background-color: #F4F3EF;
border-right: 1px solid #ebebeb;
}
@@ -37,6 +37,13 @@
padding: 0px;
}
#sidebar li > a.text-primary {
border-radius: 0;
background-color: #FBFFCC;
border-left: 4px solid #EFFF33;
padding: 5px;
}
/* #sidebar ul > div.mt-12 {
padding-top: 30px;
position: relative;
@@ -49,10 +56,10 @@
} */
#header {
border-left: 1px solid #26272b;
border-left: 4px solid #EFFF33;
padding-left: 16px;
padding-right: 16px;
background-color: #f5f5f5;
background-color: #FDFFE5;
padding-bottom: 10px;
padding-top: 10px;
}
@@ -63,6 +70,13 @@
border-color: #ebebeb;
}
#content-area:hover .mt-8 .block:hover{
border-radius: 0;
border-width: 1px;
background-color: #FDFFE5;
border-color: #EFFF33;
}
#content-area .mt-8 .rounded-xl{
border-radius: 0;
}

View File

@@ -0,0 +1,4 @@
---
title: "Create Identity Membership"
openapi: "POST /api/v2/workspace/{projectId}/identity-memberships/{identityId}"
---

View File

@@ -0,0 +1,4 @@
---
title: "Get Identity by ID"
openapi: "GET /api/v2/workspace/{projectId}/identity-memberships/{identityId}"
---

View File

@@ -0,0 +1,4 @@
---
title: "Get By Username"
openapi: "POST /api/v1/workspace/{workspaceId}/memberships/details"
---

View File

@@ -1,4 +1,4 @@
---
title: "Invite Member"
openapi: "POST /api/v2/workspace/{projectId}/memberships"
---
---

View File

@@ -36,7 +36,7 @@ Initialize a new Node.js project with a default `package.json` file.
npm init -y
```
Install `express` and [infisical-node](https://github.com/Infisical/infisical-node), the client Node SDK for Infisical.
Install `express` and [@infisical/sdk](https://www.npmjs.com/package/@infisical/sdk), the client Node SDK for Infisical.
```console
npm install express @infisical/sdk
@@ -46,16 +46,19 @@ Finally, create an index.js file containing the application code.
```js
const express = require('express');
const { InfisicalClient, LogLevel } = require("@infisical/sdk");
const { InfisicalClient } = require("@infisical/sdk");
const app = express();
const PORT = 3000;
const client = new InfisicalClient({
clientId: "YOUR_CLIENT_ID",
clientSecret: "YOUR_CLIENT_SECRET",
logLevel: LogLevel.Error
auth: {
universalAuth: {
clientId: "YOUR_CLIENT_ID",
clientSecret: "YOUR_CLIENT_SECRET",
}
}
});
app.get("/", async (req, res) => {

View File

@@ -5,7 +5,7 @@ title: "Python"
This guide demonstrates how to use Infisical to manage secrets for your Python stack from local development to production. It uses:
- Infisical (you can use [Infisical Cloud](https://app.infisical.com) or a [self-hosted instance of Infisical](https://infisical.com/docs/self-hosting/overview)) to store your secrets.
- The [infisical-python](https://github.com/Infisical/sdk/tree/main/crates/infisical-py) Python client SDK to fetch secrets back to your Python application on demand.
- The [infisical-python](https://pypi.org/project/infisical-python/) Python client SDK to fetch secrets back to your Python application on demand.
## Project Setup
@@ -36,23 +36,27 @@ python3 -m venv env
source env/bin/activate
```
Install Flask and [infisical-python](https://github.com/Infisical/sdk/tree/main/crates/infisical-py), the client Python SDK for Infisical.
Install Flask and [infisical-python](https://pypi.org/project/infisical-python/), the client Python SDK for Infisical.
```console
pip install Flask infisical-python
pip install flask infisical-python
```
Finally, create an `app.py` file containing the application code.
```py
from flask import Flask
from infisical_client import ClientSettings, InfisicalClient, GetSecretOptions
from infisical_client import ClientSettings, InfisicalClient, GetSecretOptions, AuthenticationOptions, UniversalAuthMethod
app = Flask(__name__)
client = InfisicalClient(ClientSettings(
client_id="MACHINE_IDENTITY_CLIENT_ID",
client_secret="MACHINE_IDENTITY_CLIENT_SECRET",
auth=AuthenticationOptions(
universal_auth=UniversalAuthMethod(
client_id="CLIENT_ID",
client_secret="CLIENT_SECRET",
)
)
))
@app.route("/")

View File

@@ -83,7 +83,7 @@ access the Infisical API using the AWS Auth authentication method.
- Allowed Principal ARNs: A comma-separated list of trusted IAM principal ARNs that are allowed to authenticate with Infisical. The values should take one of three forms: `arn:aws:iam::123456789012:user/MyUserName`, `arn:aws:iam::123456789012:role/MyRoleName`, or `arn:aws:iam::123456789012:*`. Using a wildcard in this case allows any IAM principal in the account `123456789012` to authenticate with Infisical under the identity.
- Allowed Account IDs: A comma-separated list of trusted AWS account IDs that are allowed to authenticate with Infisical.
- STS Endpoint (default is `https://sts.amazonaws.com/`): The endpoint URL for the AWS STS API. This is useful for AWS GovCloud or other AWS regions that have different STS endpoints.
- STS Endpoint (default is `https://sts.amazonaws.com/`): The endpoint URL for the AWS STS API. This value should be adjusted based on the AWS region you are operating in (e.g. `https://sts.us-east-1.amazonaws.com/`); refer to the list of regional STS endpoints [here](https://docs.aws.amazon.com/general/latest/gr/sts.html).
- Access Token TTL (default is `2592000` equivalent to 30 days): The lifetime for an acccess token in seconds. This value will be referenced at renewal time.
- Access Token Max TTL (default is `2592000` equivalent to 30 days): The maximum lifetime for an acccess token in seconds. This value will be referenced at renewal time.
- Access Token Max Number of Uses (default is `0`): The maximum number of times that an access token can be used; a value of `0` implies infinite number of uses.
@@ -264,6 +264,9 @@ access the Infisical API using the AWS Auth authentication method.
request.headers["X-Amz-Date"] = AWS.util.date.iso8601(new Date()).replace(/[:-]|\.\d{3}/g, "");
request.body = iamRequestBody;
request.headers["Content-Length"] = Buffer.byteLength(iamRequestBody);
const signer = new AWS.Signers.V4(request, "sts");
signer.addAuthorization(AWS.config.credentials, new Date());
````
#### Sample request
@@ -277,6 +280,10 @@ access the Infisical API using the AWS Auth authentication method.
--data-urlencode 'iamRequestHeaders=...'
```
<Note>
Note that you should replace `<identityId>` with the ID of the identity you created in step 1.
</Note>
#### Sample response
```bash Response

View File

@@ -0,0 +1,176 @@
---
title: Azure Auth
description: "Learn how to authenticate with Infisical for services on Azure"
---
**Azure Auth** is an Azure-native authentication method for Azure resources like Azure VMs, Azure App Services, Azure Functions, Azure Kubernetes Service, etc. to access Infisical.
## Diagram
The following sequence digram illustrates the Azure Auth workflow for authenticating Azure [service principals](https://learn.microsoft.com/en-us/entra/identity-platform/app-objects-and-service-principals?tabs=browser) with Infisical.
```mermaid
sequenceDiagram
participant Client as Client
participant Infis as Infisical
participant Azure as Azure AD OpenID
Note over Client,Azure: Step 1: Instance Identity Token Retrieval
Client->>Azure: Request managed identity access token
Azure-->>Client: Return managed identity access token
Note over Client,Infis: Step 2: Identity Token Login Operation
Client->>Infis: Send managed identity access token to /api/v1/auth/azure-auth/login
Infis->>Azure: Request public key
Azure-->>Infis: Return public key
Note over Infis: Step 3: Identity Token Verification
Note over Infis: Step 4: Identity Property Validation
Infis->>Client: Return short-lived access token
Note over Client,Infis: Step 4: Access Infisical API with Token
Client->>Infis: Make authenticated requests using the short-lived access token
```
## Concept
At a high-level, Infisical authenticates an Azure service by verifying its identity and checking that it meets specific requirements (e.g. it is bound to an allowed service principal) at the `/api/v1/auth/azure-auth/login` endpoint. If successful,
then Infisical returns a short-lived access token that can be used to make authenticated requests to the Infisical API.
To be more specific:
1. The client running on an Azure service obtains an [access token](https://learn.microsoft.com/en-us/entra/identity/managed-identities-azure-resources/how-to-use-vm-token#get-a-token-using-http) that is a JWT token representing the managed identity for the Azure resource such as a Virtual Machine; the managed identity is associated with a service principal in Azure AD.
2. The client sends the access token to Infisical.
3. Infisical verifies the token against the corresponding public key at the [public Azure AD OpenID configuration endpoint](https://learn.microsoft.com/en-us/answers/questions/793793/azure-ad-validate-access-token).
4. Infisical checks if the entity behind the access token is allowed to authenticate with Infisical based on set criteria such as **Allowed Service Principal IDs**.
5. If all is well, Infisical returns a short-lived access token that the client can use to make authenticated requests to the Infisical API.
<Note>
We recommend using one of Infisical's clients like SDKs or the Infisical Agent
to authenticate with Infisical using Azure Auth as they handle the
authentication process including generating the client access token for you.
Also, note that Infisical needs network-level access to send requests to the Google Cloud API
as part of the Azure Auth workflow.
</Note>
## Guide
In the following steps, we explore how to create and use identities for your applications in Azure to
access the Infisical API using the Azure Auth authentication method.
<Steps>
<Step title="Creating an identity">
To create an identity, head to your Organization Settings > Access Control > Machine Identities and press **Create identity**.
![identities organization](/images/platform/identities/identities-org.png)
When creating an identity, you specify an organization level [role](/documentation/platform/role-based-access-controls) for it to assume; you can configure roles in Organization Settings > Access Control > Organization Roles.
![identities organization create](/images/platform/identities/identities-org-create.png)
Now input a few details for your new identity. Here's some guidance for each field:
- Name (required): A friendly name for the identity.
- Role (required): A role from the **Organization Roles** tab for the identity to assume. The organization role assigned will determine what organization level resources this identity can have access to.
Once you've created an identity, you'll be prompted to configure the authentication method for it. Here, select **Azure Auth**.
![identities create azure auth method](/images/platform/identities/identities-org-create-azure-auth-method.png)
Here's some more guidance on each field:
- Tenant ID: The [tenant ID](https://learn.microsoft.com/en-us/entra/fundamentals/how-to-find-tenant) for the Azure AD organization.
- Resource / Audience: The resource URL for the application registered in Azure AD. The value is expected to match the `aud` claim of the access token JWT later used in the login operation against Infisical. See the [resource](https://learn.microsoft.com/en-us/entra/identity/managed-identities-azure-resources/how-to-use-vm-token#get-a-token-using-http) parameter for how the audience is set when requesting a JWT access token from the Azure Instance Metadata Service (IMDS) endpoint. In most cases, this value should be `https://management.azure.com/` which is the default.
- Allowed Service Principal IDs: A comma-separated list of Azure AD service principal IDs that are allowed to authenticate with Infisical.
- Access Token TTL (default is `2592000` equivalent to 30 days): The lifetime for an acccess token in seconds. This value will be referenced at renewal time.
- Access Token Max TTL (default is `2592000` equivalent to 30 days): The maximum lifetime for an acccess token in seconds. This value will be referenced at renewal time.
- Access Token Max Number of Uses (default is `0`): The maximum number of times that an access token can be used; a value of `0` implies infinite number of uses.
- Access Token Trusted IPs: The IPs or CIDR ranges that access tokens can be used from. By default, each token is given the `0.0.0.0/0`, allowing usage from any network address.
</Step>
<Step title="Adding an identity to a project">
To enable the identity to access project-level resources such as secrets within a specific project, you should add it to that project.
To do this, head over to the project you want to add the identity to and go to Project Settings > Access Control > Machine Identities and press **Add identity**.
Next, select the identity you want to add to the project and the project level role you want to allow it to assume. The project role assigned will determine what project level resources this identity can have access to.
![identities project](/images/platform/identities/identities-project.png)
![identities project create](/images/platform/identities/identities-project-create.png)
</Step>
<Step title="Accessing the Infisical API with the identity">
To access the Infisical API as the identity, you need to generate a managed identity [access token](https://learn.microsoft.com/en-us/entra/identity/managed-identities-azure-resources/how-to-use-vm-token#get-a-token-using-http) that is a JWT token representing the managed identity for the Azure resource such as a Virtual Machine. The client token must be sent to the `/api/v1/auth/azure-auth/login` endpoint in exchange for a separate access token to access the Infisical API.
We provide a few code examples below of how you can authenticate with Infisical to access the [Infisical API](/api-reference/overview/introduction).
<AccordionGroup>
<Accordion
title="Sample code for generating the access token"
>
Start by making a request from your Azure client such as Virtual Machine to obtain a managed identity access token.
For more examples of how to obtain the managed identity access token, refer to the [official documentation](https://learn.microsoft.com/en-us/entra/identity/managed-identities-azure-resources/how-to-use-vm-token#get-a-token-using-http).
#### Sample request
```bash curl
curl 'http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=https%3A%2F%2Fmanagement.azure.com%2F' -H Metadata:true -s
```
#### Sample response
```bash
{
"access_token": "eyJ0eXAi...",
"refresh_token": "",
"expires_in": "3599",
"expires_on": "1506484173",
"not_before": "1506480273",
"resource": "https://management.azure.com/",
"token_type": "Bearer"
}
```
Next use send the obtained managed identity access token (i.e. the token from the `access_token` field above) to authenticate with Infisical and obtain a separate access token.
#### Sample request
```bash Request
curl --location --request POST 'https://app.infisical.com/api/v1/auth/gcp-auth/login' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'identityId=...' \
--data-urlencode 'jwt=...'
```
<Note>
Note that you should replace `<identityId>` with the ID of the identity you created in step 1.
</Note>
#### Sample response
```bash Response
{
"accessToken": "...",
"expiresIn": 7200,
"accessTokenMaxTTL": 43244
"tokenType": "Bearer"
}
```
Next, you can use this access token to access the [Infisical API](/api-reference/overview/introduction)
</Accordion>
</AccordionGroup>
<Tip>
We recommend using one of Infisical's clients like SDKs or the Infisical Agent to authenticate with Infisical using Azure Auth as they handle the authentication process including retrieving the client access token.
</Tip>
<Note>
Each identity access token has a time-to-live (TLL) which you can infer from the response of the login operation;
the default TTL is `7200` seconds which can be adjusted.
If an identity access token expires, it can no longer authenticate with the Infisical API. In this case,
a new access token should be obtained by performing another login operation.
</Note>
</Step>
</Steps>

View File

@@ -7,9 +7,9 @@ description: "Learn how to use Machine Identities to programmatically interact w
An Infisical machine identity is an entity that represents a workload or application that require access to various resources in Infisical. This is conceptually similar to an IAM user in AWS or service account in Google Cloud Platform (GCP).
Each identity must authenticate with the Infisical API using a supported authentication method like [Universal Auth](/documentation/platform/identities/universal-auth), [Kubernetes Auth](/documentation/platform/identities/kubernetes-auth), [AWS Auth](/documentation/platform/identities/aws-auth), or [GCP Auth](/documentation/platform/identities/gcp-auth) to get back a short-lived access token to be used in subsequent requests.
Each identity must authenticate with the Infisical API using a supported authentication method like [Universal Auth](/documentation/platform/identities/universal-auth), [Kubernetes Auth](/documentation/platform/identities/kubernetes-auth), [AWS Auth](/documentation/platform/identities/aws-auth), [Azure Auth](/documentation/platform/identities/azure-auth), or [GCP Auth](/documentation/platform/identities/gcp-auth) to get back a short-lived access token to be used in subsequent requests.
![organization identities](/images/platform/organization/organization-machine-identities.png)
![Organization Identities](/images/platform/organization/organization-machine-identities.png)
Key Features:
@@ -39,11 +39,10 @@ To interact with various resources in Infisical, Machine Identities are able to
- [Universal Auth](/documentation/platform/identities/universal-auth): A platform-agnostic authentication method that can be configured on an identity suitable to authenticate from any platform/environment.
- [Kubernetes Auth](/documentation/platform/identities/kubernetes-auth): A Kubernetes-native authentication method for applications (e.g. pods) to authenticate with Infisical.
- [AWS Auth](/documentation/platform/identities/aws-auth): An AWS-native authentication method for IAM principals like EC2 instances or Lambda functions to authenticate with Infisical.
- [AWS Auth](/documentation/platform/identities/aws-auth): An AWS-native authentication method for AWS services (e.g. EC2, Lambda functions, etc.) to authenticate with Infisical.
- [Azure Auth](/documentation/platform/identities/azure-auth): An Azure-native authentication method for Azure resources (e.g. Azure VMs, Azure App Services, Azure Functions, Azure Kubernetes Service, etc.) to authenticate with Infisical.
- [GCP Auth](/documentation/platform/identities/gcp-auth): A GCP-native authentication method for GCP resources (e.g. Compute Engine, App Engine, Cloud Run, Google Kubernetes Engine, IAM service accounts, etc.) to authenticate with Infisical.
IAM service accounts and GCE instances to authenticate with Infisical.
## FAQ
<AccordionGroup>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 162 KiB

After

Width:  |  Height:  |  Size: 142 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 98 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 154 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 96 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 210 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 194 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 513 KiB

View File

@@ -1,5 +1,5 @@
---
title: "Jenkins"
title: "Jenkins Plugin"
description: "How to effectively and securely manage secrets in Jenkins using Infisical"
---
@@ -18,144 +18,135 @@ Prerequisites:
<Tabs>
<Tab title="Machine Identity (Recommended)">
## Add Infisical Machine Identity to Jenkins
<Tab title="Using Plugin with Machine Identities (Recommended)">
## Jenkins Infisical Plugin
After setting up your project in Infisical and installing the Infisical CLI to the environment where your Jenkins builds will run, you will need to add the Infisical Machine Identity to Jenkins.
This plugin adds a build wrapper to set environment variables from [Infisical](https://infisical.com). Secrets are generally masked in the build log, so you can't accidentally print them.
To generate a Infisical machine identity, follow the guide [here](/documentation/platform/identities/machine-identities).
Once you have generated the token, navigate to **Manage Jenkins > Manage Credentials** in your Jenkins instance.
## Installation
![Jenkins step 1](../../images/integrations/jenkins/jenkins_1.png)
To install the plugin, navigate to `Manage Jenkins -> Plugins -> Available plugins` and search for `Infisical`. Install the plugin and restart Jenkins.
Click on the credential store you want to store the Infisical Machine Identity in. In this case, we're using the default Jenkins global store.
![Install Plugin](../../images/integrations/jenkins/plugin/install-plugin.png)
<Info>
Each of your projects will have a different `INFISICAL_TOKEN`.
As a result, it may make sense to spread these out into separate credential domains depending on your use case.
</Info>
## Infisical Authentication
![Jenkins step 2](../../images/integrations/jenkins/jenkins_2.png)
Authenticating with Infisical is done through the use of [Machine Identities](https://infisical.com/docs/documentation/platform/identities/machine-identities).
Currently the Jenkins plugin only supports [Universal Auth](https://infisical.com/docs/documentation/platform/identities/universal-auth) for authentication. More methods will be added soon.
Now, click Add Credentials.
![Jenkins step 3](../../images/integrations/jenkins/jenkins_3.png)
Choose **Secret text** for the **Kind** option from the dropdown list and enter the Infisical Service Token in the **Secret** field.
Although the **ID** can be any value, we'll set it to `infisical-machine-identity-client-id` and `infisical-machine-identity-client-secret` for the sake of this guide.
The description is optional and can be any text you prefer.
### How does Universal Auth work?
To use Universal Auth, you'll need to create a new Credential _(Infisical Universal Auth Credential)_. The credential should contain your Universal Auth client ID, and your Universal Auth client secret.
Please [read more here](https://infisical.com/docs/documentation/platform/identities/universal-auth) on how to setup a Machine Identity to use universal auth.
![Jenkins step 4](../../images/integrations/jenkins/jenkins_4_identity_id.png)
![Jenkins step 5](../../images/integrations/jenkins/jenkins_4_identity_secret.png)
### Creating a Universal Auth credential
When you're done, you should see two credentials similar to the one below:
Creating a universal auth credential inside Jenkins is very straight forward.
![Jenkins step 6](../../images/integrations/jenkins/jenkins_5_identity.png)
Simply navigate to<br/>
`Dashboard -> Manage Jenkins -> Credentials -> System -> Global credentials (unrestricted)`.
Press the `Add Credentials` button and select `Infisical Universal Auth Credential` in the `Kind` field.
## Use Infisical in a Freestyle Project
The `ID` and `Description` field doesn't matter much in this case, as they won't be read anywhere. The description field will be displayed as the credential name during the plugin configuration.
To fetch secrets with Infisical in a Freestyle Project job, you'll need to expose the credential you created above as an environment variable to the Infisical CLI.
To do so, first click **New Item** from the dashboard navigation sidebar:
![Jenkins step 6](../../images/integrations/jenkins/jenkins_6.png)
Enter the name of the job, choose the **Freestyle Project** option, and click **OK**.
![Jenkins step 7](../../images/integrations/jenkins/jenkins_7.png)
Scroll down to the **Build Environment** section and enable the **Use secret text(s) or file(s)** option. Then click **Add** under the **Bindings** section and choose **Secret text** from the dropdown menu.
![Jenkins step 8](../../images/integrations/jenkins/jenkins_8.png)
Enter `INFISICAL_MACHINE_IDENTITY_CLIENT_ID` in the **Variable** field for the client ID, and `INFISICAL_MACHINE_IDENTITY_CLIENT_SECRET` for the client secret. Then click the **Specific credentials** option from the Credentials section and select the credentials you created earlier.
In this case, we saved it as `Infisical Machine Identity Client ID` and `Infisical Machine Identity Client Secret` so we'll choose those from the dropdown menu.
Make sure to add bindings for both the client ID and the client secret.
![Jenkins step 9](../../images/integrations/jenkins/jenkins_9_identity.png)
Scroll down to the **Build** section and choose **Execute shell** from the **Add build step** menu.
![Jenkins step 10](../../images/integrations/jenkins/jenkins_10_identity.png)
In the command field, you can now use the Infisical CLI to fetch secrets.
The example command below will print the secrets using the service token passed as a credential. When done, click **Save**.
```bash
export INFISICAL_TOKEN=$(infisical login --method=universal-auth --client-id=$INFISICAL_MACHINE_IDENTITY_CLIENT_ID --client-secret=$INFISICAL_MACHINE_IDENTITY_CLIENT_SECRET --silent --plain)
infisical secrets --env dev --projectId=<your-project-id>
```
![Jenkins step 11](../../images/integrations/jenkins/jenkins_11_identity.png)
Finally, click **Build Now** from the navigation sidebar to run your new job.
<Info>
Running into issues? Join Infisical's [community Slack](https://infisical.com/slack) for quick support.
</Info>
![Infisical Universal Auth Credential](../../images/integrations/jenkins/plugin/universal-auth-credential.png)
## Use Infisical in a Jenkins Pipeline
## Plugin Usage
### Configuration
To fetch secrets using Infisical in a Pipeline job, you'll need to expose the Jenkins credential you created above as an environment variable.
To do so, click **New Item** from the dashboard navigation sidebar:
Configuration takes place on a job-level basis.
![Jenkins step 6](../../images/integrations/jenkins/jenkins_6.png)
Inside your job, you simply tick the `Infisical Plugin` checkbox under "Build Environment". After enabling the plugin, you'll see a new section appear where you'll have to configure the plugin.
Enter the name of the job, choose the **Pipeline** option, and click OK.
![Plugin enabled](../../images/integrations/jenkins/plugin/plugin-checked.png)
![Jenkins step 12](../../images/integrations/jenkins/jenkins_12.png)
You'll be prompted with 4 options to fill:
* Infisical URL
* This defaults to https://app.infisical.com. This field is only relevant if you're running a managed or self-hosted instance. If you are using Infisical Cloud, leave this as-is, otherwise enter the URL of your Infisical instance.
* Infisical Credential
* This is where you select your Infisical credential to use for authentication. In the step above [Creating a Universal Auth credential](#creating-a-universal-auth-credential), you can read on how to configure the credential. Simply select the credential you have created for this field.
* Infisical Project Slug
* This is the slug of the project you wish to fetch secrets from. You can find this in your project settings on Infisical by clicking "Copy project slug".
* Environment Slug
* This is the slug of the environment to fetch secrets from. In most cases it's either `dev`, `staging`, or `prod`. You can however create custom environments in Infisical. If you are using custom environments, you need to enter the slug of the custom environment you wish to fetch secrets from.
That's it! Now you're ready to select which secrets you want to fetch into Jenkins.
By clicking the `Add an Infisical secret` in the Jenkins UI like seen in the screenshot below.
Scroll down to the **Pipeline** section, paste the following into the **Script** field, and click **Save**.
![Add Infisical secret](../../images/integrations/jenkins/plugin/add-infisical-secret.png)
```
pipeline {
agent any
You need to select which secrets that should be pulled into Jenkins.
You start by specifying a [folder path from Infisical](https://infisical.com/docs/documentation/platform/folder#comparing-folders). The root path is simply `/`. You also need to select wether or not you want to [include imports](https://infisical.com/docs/documentation/platform/secret-reference#secret-imports). Now you can add secrets the secret keys that you want to pull from Infisical into Jenkins. If you want to add multiple secrets, press the "Add key/value pair".
environment {
MACHINE_IDENTITY_CLIENT_ID = credentials('infisical-machine-identity-client-id')
MACHINE_IDENTITY_CLIENT_SECRET = credentials('infisical-machine-identity-client-secret')
If you wish to pull secrets from multiple paths, you can press the "Add an Infisical secret" button at the bottom, and configure a new set of secrets to pull.
}
stages {
stage('Run Infisical') {
steps {
sh("export INFISICAL_TOKEN=$(infisical login --method=universal-auth --client-id=${MACHINE_IDENTITY_CLIENT_ID} --client-secret=${MACHINE_IDENTITY_CLIENT_SECRET} --silent --plain)")
sh("infisical secrets --env=dev --path=/ --projectId=<your-project-id>")
## Pipeline usage
// doesn't work
// sh("docker run --rm test-container infisical secrets --projectId=<your-project-id>")
// works
// sh("docker run -e INFISICAL_TOKEN=${INFISICAL_TOKEN} --rm test-container infisical secrets --env=dev --path=/ --projectId=<your-project-id>")
### Generating pipeline block
// doesn't work
// sh("docker-compose up -d")
Using the Infisical Plugin in a Jenkins pipeline is very straight forward. To generate a block to use the Infisical Plugin in a Pipeline, simply to go `{JENKINS_URL}/jenkins/job/{JOB_ID}/pipeline-syntax/`.
You can find a direct link on the Pipeline configuration page in the very bottom of the page, see image below.
![Pipeline Syntax Highlight](../../images/integrations/jenkins/plugin/pipeline-syntax-highlight.png)
On the Snippet Generator page, simply configure the Infisical Plugin like it's documented in the [Configuration documentation](#configuration) step.
Once you have filled out the configuration, press `Generate Pipeline Script`, and it will generate a block you can use in your pipeline.
![Pipeline Configuration](../../images/integrations/jenkins/plugin/pipeline-configuration.png)
### Using Infisical in a Pipeline
Using the generated block in a pipeline is very straight forward. There's a few approaches on how to implement the block in a Pipeline script.
Here's an example of using the generated block in a pipeline script. Make sure to replace the placeholder values with your own values.
The script is formatted for clarity. All these fields will be pre-filled for you if you use the `Snippet Generator` like described in the [step above](#generating-pipeline-block).
```groovy
node {
withInfisical(
configuration: [
infisicalCredentialId: 'YOUR_CREDENTIAL_ID',
infisicalEnvironmentSlug: 'PROJECT_ENV_SLUG',
infisicalProjectSlug: 'PROJECT_SLUG',
infisicalUrl: 'https://app.infisical.com' // Change this to your Infisical instance URL if you aren't using Infisical Cloud.
],
infisicalSecrets: [
infisicalSecret(
includeImports: true,
path: '/',
secretValues: [
[infisicalKey: 'DATABASE_URL'],
[infisicalKey: "API_URL"],
[infisicalKey: 'THIS_KEY_MIGHT_NOT_EXIST', isRequired: false],
]
)
]
) {
// Code runs here
sh "printenv"
}
}
```
// works
// sh("INFISICAL_TOKEN=${INFISICAL_TOKEN} docker-compose up -d")
}
}
}
}
```
</Tab>
<Tab title="Service Token (Deprecated)">
<Tab title="Using CLI with Service Tokens (Deprecated)">
## Add Infisical Service Token to Jenkins
<Warning>
<Warning>
Service tokens are being deprecated in favor of [machine identities](/documentation/platform/identities/machine-identities).
They will be removed in the future in accordance with the deprecation notice and timeline stated [here](https://infisical.com/blog/deprecating-api-keys).
Service tokens are being deprecated in favor of [machine identities](/documentation/platform/identities/machine-identities).
They will be removed in the future in accordance with the deprecation notice and timeline stated [here](https://infisical.com/blog/deprecating-api-keys).
</Warning>
**Please use our Jenkins Plugin instead!**
</Warning>
After setting up your project in Infisical and installing the Infisical CLI to the environment where your Jenkins builds will run, you will need to add the Infisical Service Token to Jenkins.

View File

@@ -72,6 +72,9 @@ Prerequisites:
<ParamField path="AWS Region" type="string" required>
The region that you want to integrate with in AWS Secrets Manager.
</ParamField>
<ParamField path="Mapping Behavior" type="string" required>
How you want the integration to map the secrets. The selected value could be either one to one or one to many.
</ParamField>
<ParamField path="AWS SM Secret Name" type="string" required>
The secret name/path in AWS into which you want to sync the secrets from Infisical.
</ParamField>

View File

@@ -51,6 +51,8 @@ description: "How to sync secrets from Infisical to GCP Secret Manager"
<Warning>
Using Infisical to sync secrets to GCP Secret Manager requires that you enable
the Service Usage API and Cloud Resource Manager API in the Google Cloud project you want to sync secrets to. More on that [here](https://cloud.google.com/service-usage/docs/set-up-development-environment).
Additionally, ensure that your GCP account has sufficient permission to manage secret and service resources (you can assign Secret Manager Admin and Service Usage Admin roles for testing purposes)
</Warning>
</Step>
</Steps>
@@ -115,6 +117,7 @@ description: "How to sync secrets from Infisical to GCP Secret Manager"
</Steps>
</Accordion>
</AccordionGroup>
</Tab>
<Tab title="Self-Hosted Setup">
Using the GCP Secret Manager integration (via the OAuth2 method) on a self-hosted instance of Infisical requires configuring an OAuth2 application in GCP
@@ -123,27 +126,27 @@ description: "How to sync secrets from Infisical to GCP Secret Manager"
<Steps>
<Step title="Create an OAuth2 application in GCP">
Navigate to your project API & Services > Credentials to create a new OAuth2 application.
![integrations GCP secret manager config](../../images/integrations/gcp-secret-manager/integrations-gcp-secret-manager-config-api-services.png)
![integrations GCP secret manager config](../../images/integrations/gcp-secret-manager/integrations-gcp-secret-manager-config-new-app.png)
![integrations GCP secret manager config](../../images/integrations/gcp-secret-manager/integrations-gcp-secret-manager-config-api-services.png)
![integrations GCP secret manager config](../../images/integrations/gcp-secret-manager/integrations-gcp-secret-manager-config-new-app.png)
Create the application. As part of the form, add to **Authorized redirect URIs**: `https://your-domain.com/integrations/gcp-secret-manager/oauth2/callback`.
![integrations GCP secret manager config](../../images/integrations/gcp-secret-manager/integrations-gcp-secret-manager-config-new-app-form.png)
![integrations GCP secret manager config](../../images/integrations/gcp-secret-manager/integrations-gcp-secret-manager-config-new-app-form.png)
</Step>
<Step title="Add your OAuth2 application credentials to Infisical">
Obtain the **Client ID** and **Client Secret** for your GCP OAuth2 application.
![integrations GCP secret manager config](../../images/integrations/gcp-secret-manager/integrations-gcp-secret-manager-config-credentials.png)
![integrations GCP secret manager config](../../images/integrations/gcp-secret-manager/integrations-gcp-secret-manager-config-credentials.png)
Back in your Infisical instance, add two new environment variables for the credentials of your GCP OAuth2 application:
- `CLIENT_ID_GCP_SECRET_MANAGER`: The **Client ID** of your GCP OAuth2 application.
- `CLIENT_SECRET_GCP_SECRET_MANAGER`: The **Client Secret** of your GCP OAuth2 application.
Once added, restart your Infisical instance and use the GCP Secret Manager integration.
</Step>
</Steps>
</Tab>
</Tabs>

View File

@@ -32,7 +32,10 @@
"thumbsRating": true
},
"api": {
"baseUrl": ["https://app.infisical.com", "http://localhost:8080"]
"baseUrl": [
"https://app.infisical.com",
"http://localhost:8080"
]
},
"topbarLinks": [
{
@@ -73,7 +76,9 @@
"documentation/getting-started/introduction",
{
"group": "Quickstart",
"pages": ["documentation/guides/local-development"]
"pages": [
"documentation/guides/local-development"
]
},
{
"group": "Guides",
@@ -155,6 +160,7 @@
"documentation/platform/identities/universal-auth",
"documentation/platform/identities/kubernetes-auth",
"documentation/platform/identities/gcp-auth",
"documentation/platform/identities/azure-auth",
"documentation/platform/identities/aws-auth",
"documentation/platform/mfa",
{
@@ -214,7 +220,9 @@
},
{
"group": "Reference architectures",
"pages": ["self-hosting/reference-architectures/aws-ecs"]
"pages": [
"self-hosting/reference-architectures/aws-ecs"
]
},
"self-hosting/ee",
"self-hosting/faq"
@@ -370,11 +378,15 @@
},
{
"group": "Build Tool Integrations",
"pages": ["integrations/build-tools/gradle"]
"pages": [
"integrations/build-tools/gradle"
]
},
{
"group": "",
"pages": ["sdks/overview"]
"pages": [
"sdks/overview"
]
},
{
"group": "SDK's",
@@ -392,7 +404,9 @@
"api-reference/overview/authentication",
{
"group": "Examples",
"pages": ["api-reference/overview/examples/integration"]
"pages": [
"api-reference/overview/examples/integration"
]
}
]
},
@@ -438,17 +452,30 @@
"api-reference/endpoints/workspaces/delete-workspace",
"api-reference/endpoints/workspaces/get-workspace",
"api-reference/endpoints/workspaces/update-workspace",
"api-reference/endpoints/workspaces/invite-member-to-workspace",
"api-reference/endpoints/workspaces/remove-member-from-workspace",
"api-reference/endpoints/workspaces/memberships",
"api-reference/endpoints/workspaces/update-membership",
"api-reference/endpoints/workspaces/list-identity-memberships",
"api-reference/endpoints/workspaces/update-identity-membership",
"api-reference/endpoints/workspaces/delete-identity-membership",
"api-reference/endpoints/workspaces/secret-snapshots",
"api-reference/endpoints/workspaces/rollback-snapshot"
]
},
{
"group": "Project Users",
"pages": [
"api-reference/endpoints/project-users/invite-member-to-workspace",
"api-reference/endpoints/project-users/remove-member-from-workspace",
"api-reference/endpoints/project-users/memberships",
"api-reference/endpoints/project-users/get-by-username",
"api-reference/endpoints/project-users/update-membership"
]
},
{
"group": "Project Identities",
"pages": [
"api-reference/endpoints/project-identities/add-identity-membership",
"api-reference/endpoints/project-identities/list-identity-memberships",
"api-reference/endpoints/project-identities/get-by-id",
"api-reference/endpoints/project-identities/update-identity-membership",
"api-reference/endpoints/project-identities/delete-identity-membership"
]
},
{
"group": "Environments",
"pages": [
@@ -525,11 +552,15 @@
},
{
"group": "Service Tokens",
"pages": ["api-reference/endpoints/service-tokens/get"]
"pages": [
"api-reference/endpoints/service-tokens/get"
]
},
{
"group": "Audit Logs",
"pages": ["api-reference/endpoints/audit-logs/export-audit-log"]
"pages": [
"api-reference/endpoints/audit-logs/export-audit-log"
]
}
]
},
@@ -545,7 +576,9 @@
},
{
"group": "",
"pages": ["changelog/overview"]
"pages": [
"changelog/overview"
]
},
{
"group": "Contributing",
@@ -569,7 +602,9 @@
},
{
"group": "Contributing to SDK",
"pages": ["contributing/sdk/developing"]
"pages": [
"contributing/sdk/developing"
]
}
]
}

View File

@@ -21,21 +21,28 @@ namespace Example
static void Main(string[] args)
{
var settings = new ClientSettings
ClientSettings settings = new ClientSettings
{
Auth = new AuthenticationOptions
{
ClientId = "CLIENT_ID",
ClientSecret = "CLIENT_SECRET",
// SiteUrl = "http://localhost:8080", <-- This line can be omitted if you're using Infisical Cloud.
};
var infisical = new InfisicalClient(settings);
UniversalAuth = new UniversalAuthMethod
{
ClientId = "your-client-id",
ClientSecret = "your-client-secret"
}
}
};
var options = new GetSecretOptions
var infisicalClient = new InfisicalClient(settings);
var getSecretOptions = new GetSecretOptions
{
SecretName = "TEST",
ProjectId = "PROJECT_ID",
Environment = "dev",
};
var secret = infisical.GetSecret(options);
var secret = infisical.GetSecret(getSecretOptions);
Console.WriteLine($"The value of secret '{secret.SecretKey}', is: {secret.SecretValue}");
@@ -52,8 +59,6 @@ This example demonstrates how to use the Infisical C# SDK in a C# application. T
# Installation
Run `npm` to add `@infisical/sdk` to your project.
```console
$ dotnet add package Infisical.Sdk
```
@@ -70,14 +75,20 @@ namespace Example
{
static void Main(string[] args)
{
var settings = new ClientSettings
ClientSettings settings = new ClientSettings
{
Auth = new AuthenticationOptions
{
ClientId = "CLIENT_ID",
ClientSecret = "CLIENT_SECRET",
};
UniversalAuth = new UniversalAuthMethod
{
ClientId = "your-client-id",
ClientSecret = "your-client-secret"
}
}
};
var infisical = new InfisicalClient(settings); // <-- Your SDK instance!
var infisicalClient = new InfisicalClient(settings); // <-- Your SDK client is now ready to use
}
}
}
@@ -87,14 +98,14 @@ namespace Example
<ParamField query="options" type="object">
<Expandable title="properties">
<ParamField query="ClientId" type="string" optional>
<ParamField query="ClientId" deprecated type="string" optional>
Your machine identity client ID.
</ParamField>
<ParamField query="ClientSecret" type="string" optional>
<ParamField query="ClientSecret" deprecated type="string" optional>
Your machine identity client secret.
</ParamField>
<ParamField query="AccessToken" type="string" optional>
<ParamField query="AccessToken" deprecated type="string" optional>
An access token obtained from the machine identity login endpoint.
</ParamField>
@@ -103,13 +114,175 @@ namespace Example
If manually set to 0, caching will be disabled, this is not recommended.
</ParamField>
<ParamField query="SiteUrl()" type="string" default="https://app.infisical.com" optional>
<ParamField query="SiteUrl" type="string" default="https://app.infisical.com" optional>
Your self-hosted absolute site URL including the protocol (e.g. `https://app.infisical.com`)
</ParamField>
<ParamField query="Auth" type="AuthenticationOptions">
The authentication object to use for the client. This is required unless you're using environment variables.
</ParamField>
</Expandable>
</ParamField>
### Authentication
The SDK supports a variety of authentication methods. The most common authentication method is Universal Auth, which uses a client ID and client secret to authenticate.
#### Universal Auth
**Using environment variables**
- `INFISICAL_UNIVERSAL_AUTH_CLIENT_ID` - Your machine identity client ID.
- `INFISICAL_UNIVERSAL_AUTH_CLIENT_SECRET` - Your machine identity client secret.
**Using the SDK directly**
```csharp
ClientSettings settings = new ClientSettings
{
Auth = new AuthenticationOptions
{
UniversalAuth = new UniversalAuthMethod
{
ClientId = "your-client-id",
ClientSecret = "your-client-secret"
}
}
};
var infisicalClient = new InfisicalClient(settings);
```
#### GCP ID Token Auth
<Info>
Please note that this authentication method will only work if you're running your application on Google Cloud Platform.
Please [read more](/documentation/platform/identities/gcp-auth) about this authentication method.
</Info>
**Using environment variables**
- `INFISICAL_GCP_AUTH_IDENTITY_ID` - Your Infisical Machine Identity ID.
**Using the SDK directly**
```csharp
ClientSettings settings = new ClientSettings
{
Auth = new AuthenticationOptions
{
GcpIdToken = new GcpIdTokenAuthMethod
{
IdentityId = "your-machine-identity-id",
}
}
};
var infisicalClient = new InfisicalClient(settings);
```
#### GCP IAM Auth
**Using environment variables**
- `INFISICAL_GCP_IAM_AUTH_IDENTITY_ID` - Your Infisical Machine Identity ID.
- `INFISICAL_GCP_IAM_SERVICE_ACCOUNT_KEY_FILE_PATH` - The path to your GCP service account key file.
**Using the SDK directly**
```csharp
ClientSettings settings = new ClientSettings
{
Auth = new AuthenticationOptions
{
GcpIam = new GcpIamAuthMethod
{
IdentityId = "your-machine-identity-id",
ServiceAccountKeyFilePath = "./path/to/your/service-account-key.json"
}
}
};
var infisicalClient = new InfisicalClient(settings);
```
#### AWS IAM Auth
<Info>
Please note that this authentication method will only work if you're running your application on AWS.
Please [read more](/documentation/platform/identities/aws-auth) about this authentication method.
</Info>
**Using environment variables**
- `INFISICAL_AWS_IAM_AUTH_IDENTITY_ID` - Your Infisical Machine Identity ID.
**Using the SDK directly**
```csharp
ClientSettings settings = new ClientSettings
{
Auth = new AuthenticationOptions
{
AwsIam = new AwsIamAuthMethod
{
IdentityId = "your-machine-identity-id",
}
}
};
var infisicalClient = new InfisicalClient(settings);
```
#### Azure Auth
<Info>
Please note that this authentication method will only work if you're running your application on Azure.
Please [read more](/documentation/platform/identities/azure-auth) about this authentication method.
</Info>
**Using environment variables**
- `INFISICAL_AZURE_AUTH_IDENTITY_ID` - Your Infisical Machine Identity ID.
**Using the SDK directly**
```csharp
ClientSettings settings = new ClientSettings
{
Auth = new AuthenticationOptions
{
Azure = new AzureAuthMethod
{
IdentityId = "YOUR_IDENTITY_ID",
}
}
};
var infisicalClient = new InfisicalClient(settings);
```
#### Kubernetes Auth
<Info>
Please note that this authentication method will only work if you're running your application on Kubernetes.
Please [read more](/documentation/platform/identities/kubernetes-auth) about this authentication method.
</Info>
**Using environment variables**
- `INFISICAL_KUBERNETES_IDENTITY_ID` - Your Infisical Machine Identity ID.
- `INFISICAL_KUBERNETES_SERVICE_ACCOUNT_TOKEN_PATH_ENV_NAME` - The environment variable name that contains the path to the service account token. This is optional and will default to `/var/run/secrets/kubernetes.io/serviceaccount/token`.
**Using the SDK directly**
```csharp
ClientSettings settings = new ClientSettings
{
Auth = new AuthenticationOptions
{
Kubernetes = new KubernetesAuthMethod
{
ServiceAccountTokenPath = "/var/run/secrets/kubernetes.io/serviceaccount/token", // Optional
IdentityId = "YOUR_IDENTITY_ID",
}
}
};
var infisicalClient = new InfisicalClient(settings);
```
### Caching
To reduce the number of API requests, the SDK temporarily stores secrets it retrieves. By default, a secret remains cached for 5 minutes after it's first fetched. Each time it's fetched again, this 5-minute timer resets. You can adjust this caching duration by setting the "cacheTTL" option when creating the client.
@@ -155,6 +328,14 @@ Retrieve all secrets within the Infisical project and environment that client is
<ParamField query="IncludeImports" type="boolean" default="false" optional>
Whether or not to include imported secrets from the current path. Read about [secret import](/documentation/platform/secret-reference)
</ParamField>
<ParamField query="Recursive" type="boolean" default="false" optional>
Whether or not to fetch secrets recursively from the specified path. Please note that there's a 20-depth limit for recursive fetching.
</ParamField>
<ParamField query="ExpandSecretReferences" type="boolean" default="true" optional>
Whether or not to expand secret references in the fetched secrets. Read about [secret reference](/documentation/platform/secret-reference)
</ParamField>
</Expandable>
</ParamField>

View File

@@ -19,12 +19,19 @@ import com.infisical.sdk.schema.*;
public class Example {
public static void main(String[] args) {
// Create a new Infisical Client
// Create the authentication settings for the client
ClientSettings settings = new ClientSettings();
settings.setClientID("MACHINE_IDENTITY_CLIENT_ID");
settings.setClientSecret("MACHINE_IDENTITY_CLIENT_SECRET");
settings.setCacheTTL(Long.valueOf(300)); // 300 seconds, 5 minutes
AuthenticationOptions authOptions = new AuthenticationOptions();
UniversalAuthMethod authMethod = new UniversalAuthMethod();
authMethod.setClientID("YOUR_IDENTITY_ID");
authMethod.setClientSecret("YOUR_CLIENT_SECRET");
authOptions.setUniversalAuth(authMethod);
settings.setAuth(authOptions);
// Create a new Infisical Client
InfisicalClient client = new InfisicalClient(settings);
// Create the options for fetching the secret
@@ -68,11 +75,18 @@ import com.infisical.sdk.schema.*;
public class App {
public static void main(String[] args) {
// Create the authentication settings for the client
ClientSettings settings = new ClientSettings();
settings.setClientID("MACHINE_IDENTITY_CLIENT_ID");
settings.setClientSecret("MACHINE_IDENTITY_CLIENT_SECRET");
AuthenticationOptions authOptions = new AuthenticationOptions();
UniversalAuthMethod authMethod = new UniversalAuthMethod();
authMethod.setClientID("YOUR_IDENTITY_ID");
authMethod.setClientSecret("YOUR_CLIENT_SECRET");
authOptions.setUniversalAuth(authMethod);
settings.setAuth(authOptions);
// Create a new Infisical Client
InfisicalClient client = new InfisicalClient(settings); // Your client!
}
}
@@ -82,15 +96,21 @@ public class App {
<ParamField query="options" type="object">
<Expandable title="properties">
<ParamField query="setClientID()" type="string" optional>
<ParamField query="setClientID()" type="string" deprecated optional>
Your machine identity client ID.
**This field is deprecated and will be removed in future versions.** Please use the `setAuth()` method on the client settings instead.
</ParamField>
<ParamField query="setClientSecret()" type="string" optional>
<ParamField query="setClientSecret()" deprecated type="string" optional>
Your machine identity client secret.
**This field is deprecated and will be removed in future versions.** Please use the `setAuth()` method on the client settings instead.
</ParamField>
<ParamField query="setAccessToken()" type="string" optional>
<ParamField query="setAccessToken()" deprecatedtype="string" optional>
An access token obtained from the machine identity login endpoint.
**This field is deprecated and will be removed in future versions.** Please use the `setAuth()` method on the client settings instead.
</ParamField>
<ParamField query="setCacheTTL()" type="number" default="300" optional>
@@ -101,10 +121,155 @@ public class App {
<ParamField query="setSiteURL()" type="string" default="https://app.infisical.com" optional>
Your self-hosted absolute site URL including the protocol (e.g. `https://app.infisical.com`)
</ParamField>
<ParamField query="setAuth()" type="AuthenticationOptions">
The authentication object to use for the client. This is required unless you're using environment variables.
</ParamField>
</Expandable>
</ParamField>
### Authentication
The SDK supports a variety of authentication methods. The most common authentication method is Universal Auth, which uses a client ID and client secret to authenticate.
#### Universal Auth
**Using environment variables**
- `INFISICAL_UNIVERSAL_AUTH_CLIENT_ID` - Your machine identity client ID.
- `INFISICAL_UNIVERSAL_AUTH_CLIENT_SECRET` - Your machine identity client secret.
**Using the SDK directly**
```java
ClientSettings settings = new ClientSettings();
AuthenticationOptions authOptions = new AuthenticationOptions();
UniversalAuthMethod authMethod = new UniversalAuthMethod();
authMethod.setClientID("YOUR_IDENTITY_ID");
authMethod.setClientSecret("YOUR_CLIENT_SECRET");
authOptions.setUniversalAuth(authMethod);
settings.setAuth(authOptions);
InfisicalClient client = new InfisicalClient(settings);
```
#### GCP ID Token Auth
<Info>
Please note that this authentication method will only work if you're running your application on Google Cloud Platform.
Please [read more](/documentation/platform/identities/gcp-auth) about this authentication method.
</Info>
**Using environment variables**
- `INFISICAL_GCP_AUTH_IDENTITY_ID` - Your Infisical Machine Identity ID.
**Using the SDK directly**
```java
ClientSettings settings = new ClientSettings();
AuthenticationOptions authOptions = new AuthenticationOptions();
GCPIDTokenAuthMethod authMethod = new GCPIDTokenAuthMethod();
authMethod.setIdentityID("YOUR_MACHINE_IDENTITY_ID");
authOptions.setGcpIDToken(authMethod);
settings.setAuth(authOptions);
InfisicalClient client = new InfisicalClient(settings);
```
#### GCP IAM Auth
**Using environment variables**
- `INFISICAL_GCP_IAM_AUTH_IDENTITY_ID` - Your Infisical Machine Identity ID.
- `INFISICAL_GCP_IAM_SERVICE_ACCOUNT_KEY_FILE_PATH` - The path to your GCP service account key file.
**Using the SDK directly**
```java
ClientSettings settings = new ClientSettings();
AuthenticationOptions authOptions = new AuthenticationOptions();
GCPIamAuthMethod authMethod = new GCPIamAuthMethod();
authMethod.setIdentityID("YOUR_MACHINE_IDENTITY_ID");
authMethod.setServiceAccountKeyFilePath("./path/to/your/service-account-key.json");
authOptions.setGcpIam(authMethod);
settings.setAuth(authOptions);
InfisicalClient client = new InfisicalClient(settings);
```
#### AWS IAM Auth
<Info>
Please note that this authentication method will only work if you're running your application on AWS.
Please [read more](/documentation/platform/identities/aws-auth) about this authentication method.
</Info>
**Using environment variables**
- `INFISICAL_AWS_IAM_AUTH_IDENTITY_ID` - Your Infisical Machine Identity ID.
**Using the SDK directly**
```java
ClientSettings settings = new ClientSettings();
AuthenticationOptions authOptions = new AuthenticationOptions();
AWSIamAuthMethod authMethod = new AWSIamAuthMethod();
authMethod.setIdentityID("YOUR_MACHINE_IDENTITY_ID");
authOptions.setAwsIam(authMethod);
settings.setAuth(authOptions);
InfisicalClient client = new InfisicalClient(settings);
```
#### Azure Auth
<Info>
Please note that this authentication method will only work if you're running your application on Azure.
Please [read more](/documentation/platform/identities/azure-auth) about this authentication method.
</Info>
**Using environment variables**
- `INFISICAL_AZURE_AUTH_IDENTITY_ID` - Your Infisical Machine Identity ID.
**Using the SDK directly**
```java
ClientSettings settings = new ClientSettings();
AuthenticationOptions authOptions = new AuthenticationOptions();
AzureAuthMethod authMethod = new AzureAuthMethod();
authMethod.setIdentityID("YOUR_IDENTITY_ID");
authOptions.setAzure(authMethod);
settings.setAuth(authOptions);
InfisicalClient client = new InfisicalClient(settings);
```
#### Kubernetes Auth
<Info>
Please note that this authentication method will only work if you're running your application on Kubernetes.
Please [read more](/documentation/platform/identities/kubernetes-auth) about this authentication method.
</Info>
**Using environment variables**
- `INFISICAL_KUBERNETES_IDENTITY_ID` - Your Infisical Machine Identity ID.
- `INFISICAL_KUBERNETES_SERVICE_ACCOUNT_TOKEN_PATH_ENV_NAME` - The environment variable name that contains the path to the service account token. This is optional and will default to `/var/run/secrets/kubernetes.io/serviceaccount/token`.
**Using the SDK directly**
```java
ClientSettings settings = new ClientSettings();
AuthenticationOptions authOptions = new AuthenticationOptions();
KubernetesAuthMethod authMethod = new KubernetesAuthMethod();
authMethod.setIdentityID("YOUR_IDENTITY_ID");
authMethod.setServiceAccountTokenPath("/var/run/secrets/kubernetes.io/serviceaccount/token"); // Optional
authOptions.setKubernetes(authMethod);
settings.setAuth(authOptions);
InfisicalClient client = new InfisicalClient(settings);
```
### Caching
To reduce the number of API requests, the SDK temporarily stores secrets it retrieves. By default, a secret remains cached for 5 minutes after it's first fetched. Each time it's fetched again, this 5-minute timer resets. You can adjust this caching duration by setting the "cacheTTL" option when creating the client.
@@ -119,6 +284,8 @@ options.setEnvironment("dev");
options.setProjectID("PROJECT_ID");
options.setPath("/foo/bar");
options.setIncludeImports(false);
options.setRecursive(false);
options.setExpandSecretReferences(true);
SecretElement[] secrets = client.listSecrets(options);
```
@@ -148,6 +315,14 @@ Retrieve all secrets within the Infisical project and environment that client is
<ParamField query="setIncludeImports()" type="boolean" default="false" optional>
Whether or not to include imported secrets from the current path. Read about [secret import](/documentation/platform/secret-reference)
</ParamField>
<ParamField query="setRecursive()" type="boolean" default="false" optional>
Whether or not to fetch secrets recursively from the specified path. Please note that there's a 20-depth limit for recursive fetching.
</ParamField>
<ParamField query="setExpandSecretReferences()" type="boolean" default="true" optional>
Whether or not to expand secret references in the fetched secrets. Read about [secret reference](/documentation/platform/secret-reference)
</ParamField>
</Expandable>
</ParamField>

View File

@@ -4,7 +4,7 @@ sidebarTitle: "Node.js"
icon: "node"
---
If you're working with Node.js, the official [infisical-node](https://github.com/Infisical/sdk/tree/main/languages/node) package is the easiest way to fetch and work with secrets for your application.
If you're working with Node.js, the official [Infisical Node SDK](https://github.com/Infisical/sdk/tree/main/languages/node) package is the easiest way to fetch and work with secrets for your application.
- [NPM Package](https://www.npmjs.com/package/@infisical/sdk)
- [Github Repository](https://github.com/Infisical/sdk/tree/main/languages/node)
@@ -14,21 +14,25 @@ If you're working with Node.js, the official [infisical-node](https://github.com
```js
import express from "express";
import { InfisicalClient, LogLevel } from "@infisical/sdk";
import { InfisicalClient } from "@infisical/sdk";
const app = express();
const PORT = 3000;
const client = new InfisicalClient({
clientId: "YOUR_CLIENT_ID",
clientSecret: "YOUR_CLIENT_SECRET",
logLevel: LogLevel.Error
siteUrl: "https://app.infisical.com", // Optional, defaults to https://app.infisical.com
auth: {
universalAuth: {
clientId: "YOUR_CLIENT_ID",
clientSecret: "YOUR_CLIENT_SECRET"
}
}
});
app.get("/", async (req, res) => {
// access value
// Access the secret
const name = await client.getSecret({
environment: "dev",
projectId: "PROJECT_ID",
@@ -72,8 +76,12 @@ Import the SDK and create a client instance with your [Machine Identity](/docume
import { InfisicalClient, LogLevel } from "@infisical/sdk";
const client = new InfisicalClient({
clientId: "YOUR_CLIENT_ID",
clientSecret: "YOUR_CLIENT_SECRET",
auth: {
universalAuth: {
clientId: "YOUR_CLIENT_ID",
clientSecret: "YOUR_CLIENT_SECRET"
}
},
logLevel: LogLevel.Error
});
```
@@ -81,31 +89,40 @@ Import the SDK and create a client instance with your [Machine Identity](/docume
</Tab>
<Tab title="ES5">
```js
const { InfisicalClient, LogLevel } = require("@infisical/sdk");
const { InfisicalClient } = require("@infisical/sdk");
const client = new InfisicalClient({
clientId: "YOUR_CLIENT_ID",
clientSecret: "YOUR_CLIENT_SECRET",
logLevel: LogLevel.Error
auth: {
universalAuth: {
clientId: "YOUR_CLIENT_ID",
clientSecret: "YOUR_CLIENT_SECRET"
}
},
});
```
</Tab>
</Tabs>
#### Parameters
### Parameters
<ParamField query="options" type="object">
<Expandable title="properties">
<ParamField query="clientId" type="string" optional>
<ParamField query="clientId" deprecated type="string" optional>
Your machine identity client ID.
**This field is deprecated and will be removed in future versions.** Please use the `auth.universalAuth.clientId` field instead.
</ParamField>
<ParamField query="clientSecret" type="string" optional>
<ParamField query="clientSecret" deprecated type="string" optional>
Your machine identity client secret.
**This field is deprecated and will be removed in future versions.** Please use the `auth.universalAuth.clientSecret` field instead.
</ParamField>
<ParamField query="accessToken" type="string" optional>
<ParamField query="accessToken" deprecated type="string" optional>
An access token obtained from the machine identity login endpoint.
**This field is deprecated and will be removed in future versions.** Please use the `auth.accessToken` field instead.
</ParamField>
<ParamField query="cacheTtl" type="number" default="300" optional>
@@ -119,10 +136,138 @@ Import the SDK and create a client instance with your [Machine Identity](/docume
<ParamField query="logLevel" type="enum" default="Error" optional>
The level of logs you wish to log The logs are derived from Rust, as we have written our base SDK in Rust.
</ParamField>
<ParamField query="auth" type="AuthenticationOptions">
The authentication object to use for the client. This is required unless you're using environment variables.
</ParamField>
</Expandable>
</ParamField>
### Authentication
The SDK supports a variety of authentication methods. The most common authentication method is Universal Auth, which uses a client ID and client secret to authenticate.
#### Universal Auth
**Using environment variables**
- `INFISICAL_UNIVERSAL_AUTH_CLIENT_ID` - Your machine identity client ID.
- `INFISICAL_UNIVERSAL_AUTH_CLIENT_SECRET` - Your machine identity client secret.
**Using the SDK directly**
```js
const client = new InfisicalClient({
auth: {
universalAuth: {
clientId: "YOUR_CLIENT_ID",
clientSecret: "YOUR_CLIENT_SECRET"
}
}
});
```
#### GCP ID Token Auth
<Info>
Please note that this authentication method will only work if you're running your application on Google Cloud Platform.
Please [read more](/documentation/platform/identities/gcp-auth) about this authentication method.
</Info>
**Using environment variables**
- `INFISICAL_GCP_AUTH_IDENTITY_ID` - Your Infisical Machine Identity ID.
**Using the SDK directly**
```js
const client = new InfisicalClient({
auth: {
gcpIdToken: {
identityId: "YOUR_IDENTITY_ID"
}
}
});
```
#### GCP IAM Auth
**Using environment variables**
- `INFISICAL_GCP_IAM_AUTH_IDENTITY_ID` - Your Infisical Machine Identity ID.
- `INFISICAL_GCP_IAM_SERVICE_ACCOUNT_KEY_FILE_PATH` - The path to your GCP service account key file.
**Using the SDK directly**
```js
const client = new InfisicalClient({
auth: {
gcpIam: {
identityId: "YOUR_IDENTITY_ID",
serviceAccountKeyFilePath: "./path/to/your/service-account-key.json"
}
}
});
```
#### AWS IAM Auth
<Info>
Please note that this authentication method will only work if you're running your application on AWS.
Please [read more](/documentation/platform/identities/aws-auth) about this authentication method.
</Info>
**Using environment variables**
- `INFISICAL_AWS_IAM_AUTH_IDENTITY_ID` - Your Infisical Machine Identity ID.
**Using the SDK directly**
```js
const client = new InfisicalClient({
auth: {
awsIam: {
identityId: "YOUR_IDENTITY_ID"
}
}
});
```
#### Azure Auth
<Info>
Please note that this authentication method will only work if you're running your application on Azure.
Please [read more](/documentation/platform/identities/azure-auth) about this authentication method.
</Info>
**Using environment variables**
- `INFISICAL_AZURE_AUTH_IDENTITY_ID` - Your Infisical Machine Identity ID.
**Using the SDK directly**
```js
const client = new InfisicalClient({
auth: {
azure: {
identityId: "YOUR_IDENTITY_ID"
}
}
});
```
#### Kubernetes Auth
<Info>
Please note that this authentication method will only work if you're running your application on Kubernetes.
Please [read more](/documentation/platform/identities/kubernetes-auth) about this authentication method.
</Info>
**Using environment variables**
- `INFISICAL_KUBERNETES_IDENTITY_ID` - Your Infisical Machine Identity ID.
- `INFISICAL_KUBERNETES_SERVICE_ACCOUNT_TOKEN_PATH_ENV_NAME` - The environment variable name that contains the path to the service account token. This is optional and will default to `/var/run/secrets/kubernetes.io/serviceaccount/token`.
**Using the SDK directly**
```js
const client = new InfisicalClient({
auth: {
kubernetes: {
identityId: "YOUR_IDENTITY_ID",
serviceAccountTokenPathEnvName: "/var/run/secrets/kubernetes.io/serviceaccount/token" // Optional
}
}
});
```
### Caching
To reduce the number of API requests, the SDK temporarily stores secrets it retrieves. By default, a secret remains cached for 5 minutes after it's first fetched. Each time it's fetched again, this 5-minute timer resets. You can adjust this caching duration by setting the "cacheTtl" option when creating the client.
@@ -161,6 +306,14 @@ Retrieve all secrets within the Infisical project and environment that client is
Whether or not to set the fetched secrets to the process environment. If true, you can access the secrets like so `process.env["SECRET_NAME"]`.
</ParamField>
<ParamField query="recursive" type="boolean" default="false" optional>
Whether or not to fetch secrets recursively from the specified path. Please note that there's a 20-depth limit for recursive fetching.
</ParamField>
<ParamField query="expandSecretReferences" type="boolean" default="true" optional>
Whether or not to expand secret references in the fetched secrets. Read about [secret reference](/documentation/platform/secret-reference)
</ParamField>
<ParamField query="includeImports" type="false" default="boolean" optional>
Whether or not to include imported secrets from the current path. Read about [secret import](/documentation/platform/secret-reference)
</ParamField>

View File

@@ -6,20 +6,24 @@ icon: "python"
If you're working with Python, the official [infisical-python](https://github.com/Infisical/sdk/edit/main/crates/infisical-py) package is the easiest way to fetch and work with secrets for your application.
- [PyPi Package](https://pypi.org/project/infisical-python/)
- [Github Repository](https://github.com/Infisical/sdk/edit/main/crates/infisical-py)
- [PyPi Package](https://pypi.org/project/infisical-python/)
- [Github Repository](https://github.com/Infisical/sdk/edit/main/crates/infisical-py)
## Basic Usage
```py
from flask import Flask
from infisical_client import ClientSettings, InfisicalClient, GetSecretOptions
from infisical_client import ClientSettings, InfisicalClient, GetSecretOptions, AuthenticationOptions, UniversalAuthMethod
app = Flask(__name__)
client = InfisicalClient(ClientSettings(
client_id="MACHINE_IDENTITY_CLIENT_ID",
client_secret="MACHINE_IDENTITY_CLIENT_SECRET",
auth=AuthenticationOptions(
universal_auth=UniversalAuthMethod(
client_id="CLIENT_ID",
client_secret="CLIENT_SECRET",
)
)
))
@app.route("/")
@@ -38,7 +42,7 @@ def hello_world():
This example demonstrates how to use the Infisical Python SDK with a Flask application. The application retrieves a secret named "NAME" and responds to requests with a greeting that includes the secret value.
<Warning>
We do not recommend hardcoding your [Machine Identity Tokens](/platform/identities/overview). Setting it as an environment variable would be best.
We do not recommend hardcoding your [Machine Identity Tokens](/platform/identities/overview). Setting it as an environment variable would be best.
</Warning>
## Installation
@@ -56,11 +60,15 @@ Note: You need Python 3.7+.
Import the SDK and create a client instance with your [Machine Identity](/api-reference/overview/authentication).
```py
from infisical_client import ClientSettings, InfisicalClient
from infisical_client import ClientSettings, InfisicalClient, AuthenticationOptions, UniversalAuthMethod
client = InfisicalClient(ClientSettings(
client_id="MACHINE_IDENTITY_CLIENT_ID",
client_secret="MACHINE_IDENTITY_CLIENT_SECRET",
auth=AuthenticationOptions(
universal_auth=UniversalAuthMethod(
client_id="CLIENT_ID",
client_secret="CLIENT_SECRET",
)
)
))
```
@@ -68,14 +76,20 @@ client = InfisicalClient(ClientSettings(
<ParamField query="options" type="object">
<Expandable title="properties">
<ParamField query="client_id" type="string" optional>
<ParamField query="client_id" type="string" deprecated optional>
Your Infisical Client ID.
**This field is deprecated and will be removed in future versions.** Please use the `auth` field instead.
</ParamField>
<ParamField query="client_secret" type="string" optional>
<ParamField query="client_secret" type="string" deprecated optional>
Your Infisical Client Secret.
**This field is deprecated and will be removed in future versions.** Please use the `auth` field instead.
</ParamField>
<ParamField query="access_token" type="string" optional>
<ParamField query="access_token" type="string" deprecated optional>
If you want to directly pass an access token obtained from the authentication endpoints, you can do so.
**This field is deprecated and will be removed in future versions.** Please use the `auth` field instead.
</ParamField>
<ParamField query="cache_ttl" type="number" default="300" optional>
@@ -85,18 +99,155 @@ client = InfisicalClient(ClientSettings(
<ParamField
query="site_url"
type="string"
default="https://app.infisical.com"
optional
query="site_url"
type="string"
default="https://app.infisical.com"
optional
>
Your self-hosted absolute site URL including the protocol (e.g.
`https://app.infisical.com`)
Your self-hosted absolute site URL including the protocol (e.g. `https://app.infisical.com`)
</ParamField>
<ParamField query="auth" type="AuthenticationOptions">
The authentication object to use for the client. This is required unless you're using environment variables.
</ParamField>
</Expandable>
</ParamField>
### Authentication
The SDK supports a variety of authentication methods. The most common authentication method is Universal Auth, which uses a client ID and client secret to authenticate.
#### Universal Auth
**Using environment variables**
- `INFISICAL_UNIVERSAL_AUTH_CLIENT_ID` - Your machine identity client ID.
- `INFISICAL_UNIVERSAL_AUTH_CLIENT_SECRET` - Your machine identity client secret.
**Using the SDK directly**
```python3
from infisical_client import ClientSettings, InfisicalClient, AuthenticationOptions, UniversalAuthMethod
client = InfisicalClient(ClientSettings(
auth=AuthenticationOptions(
universal_auth=UniversalAuthMethod(
client_id="CLIENT_ID",
client_secret="CLIENT_SECRET",
)
)
))
```
#### GCP ID Token Auth
<Info>
Please note that this authentication method will only work if you're running your application on Google Cloud Platform.
Please [read more](/documentation/platform/identities/gcp-auth) about this authentication method.
</Info>
**Using environment variables**
- `INFISICAL_GCP_AUTH_IDENTITY_ID` - Your Infisical Machine Identity ID.
**Using the SDK directly**
```py
from infisical_client import ClientSettings, InfisicalClient, AuthenticationOptions, GCPIDTokenAuthMethod
client = InfisicalClient(ClientSettings(
auth=AuthenticationOptions(
gcp_id_token=GCPIDTokenAuthMethod(
identity_id="MACHINE_IDENTITY_ID",
)
)
))
```
#### GCP IAM Auth
**Using environment variables**
- `INFISICAL_GCP_IAM_AUTH_IDENTITY_ID` - Your Infisical Machine Identity ID.
- `INFISICAL_GCP_IAM_SERVICE_ACCOUNT_KEY_FILE_PATH` - The path to your GCP service account key file.
**Using the SDK directly**
```py
from infisical_client import ClientSettings, InfisicalClient, AuthenticationOptions, GCPIamAuthMethod
client = InfisicalClient(ClientSettings(
auth=AuthenticationOptions(
gcp_iam=GCPIamAuthMethod(
identity_id="MACHINE_IDENTITY_ID",
service_account_key_file_path="./path/to/service_account_key.json"
)
)
))
```
#### AWS IAM Auth
<Info>
Please note that this authentication method will only work if you're running your application on AWS.
Please [read more](/documentation/platform/identities/aws-auth) about this authentication method.
</Info>
**Using environment variables**
- `INFISICAL_AWS_IAM_AUTH_IDENTITY_ID` - Your Infisical Machine Identity ID.
**Using the SDK directly**
```py
from infisical_client import ClientSettings, InfisicalClient, AuthenticationOptions, AWSIamAuthMethod
client = InfisicalClient(ClientSettings(
auth=AuthenticationOptions(
aws_iam=AWSIamAuthMethod(identity_id="MACHINE_IDENTITY_ID")
)
))
```
#### Azure Auth
<Info>
Please note that this authentication method will only work if you're running your application on Azure.
Please [read more](/documentation/platform/identities/azure-auth) about this authentication method.
</Info>
**Using environment variables**
- `INFISICAL_AZURE_AUTH_IDENTITY_ID` - Your Infisical Machine Identity ID.
**Using the SDK directly**
```python
from infisical_client import InfisicalClient, ClientSettings, AuthenticationOptions, AzureAuthMethod
kubernetes_client = InfisicalClient(ClientSettings(
auth=AuthenticationOptions(
azure=AzureAuthMethod(
identity_id="YOUR_IDENTITY_ID",
)
)
))
```
#### Kubernetes Auth
<Info>
Please note that this authentication method will only work if you're running your application on Kubernetes.
Please [read more](/documentation/platform/identities/kubernetes-auth) about this authentication method.
</Info>
**Using environment variables**
- `INFISICAL_KUBERNETES_IDENTITY_ID` - Your Infisical Machine Identity ID.
- `INFISICAL_KUBERNETES_SERVICE_ACCOUNT_TOKEN_PATH_ENV_NAME` - The environment variable name that contains the path to the service account token. This is optional and will default to `/var/run/secrets/kubernetes.io/serviceaccount/token`.
**Using the SDK directly**
```python
from infisical_client import InfisicalClient, ClientSettings, AuthenticationOptions, KubernetesAuthMethod
kubernetes_client = InfisicalClient(ClientSettings(
auth=AuthenticationOptions(
kubernetes=KubernetesAuthMethod(
identity_id="YOUR_IDENTITY_ID",
service_account_token_path="/var/run/secrets/kubernetes.io/serviceaccount/token" # Optional
)
)
))
```
### Caching
To reduce the number of API requests, the SDK temporarily stores secrets it retrieves. By default, a secret remains cached for 5 minutes after it's first fetched. Each time it's fetched again, this 5-minute timer resets. You can adjust this caching duration by setting the "cache_ttl" option when creating the client.
@@ -133,6 +284,14 @@ Retrieve all secrets within the Infisical project and environment that client is
Whether or not to set the fetched secrets to the process environment. If true, you can access the secrets like so `process.env["SECRET_NAME"]`.
</ParamField>
<ParamField query="recursive" type="boolean" default="false" optional>
Whether or not to fetch secrets recursively from the specified path. Please note that there's a 20-depth limit for recursive fetching.
</ParamField>
<ParamField query="expand_secret_references" type="boolean" default="true" optional>
Whether or not to expand secret references in the fetched secrets. Read about [secret reference](/documentation/platform/secret-reference)
</ParamField>
<ParamField query="include_imports" type="boolean" default="false" optional>
Whether or not to include imported secrets from the current path. Read about [secret import](/documentation/platform/secret-reference)
</ParamField>
@@ -156,26 +315,26 @@ By default, `getSecret()` fetches and returns a shared secret. If not found, it
#### Parameters
<ParamField query="Parameters" type="object" optional>
<Expandable title="properties">
<ParamField query="secret_name" type="string" required>
The key of the secret to retrieve
</ParamField>
<ParamField query="environment" type="string" required>
The slug name (dev, prod, etc) of the environment from where secrets should be fetched from.
</ParamField>
<ParamField query="project_id" type="string" required>
The project ID where the secret lives in.
</ParamField>
<ParamField query="path" type="string" optional>
The path from where secret should be fetched from.
</ParamField>
<ParamField query="type" type="string" optional>
The type of the secret. Valid options are "shared" or "personal". If not specified, the default value is "personal".
</ParamField>
<ParamField query="include_imports" type="boolean" default="false" optional>
Whether or not to include imported secrets from the current path. Read about [secret import](/documentation/platform/secret-reference)
</ParamField>
</Expandable>
<Expandable title="properties">
<ParamField query="secret_name" type="string" required>
The key of the secret to retrieve
</ParamField>
<ParamField query="environment" type="string" required>
The slug name (dev, prod, etc) of the environment from where secrets should be fetched from.
</ParamField>
<ParamField query="project_id" type="string" required>
The project ID where the secret lives in.
</ParamField>
<ParamField query="path" type="string" optional>
The path from where secret should be fetched from.
</ParamField>
<ParamField query="type" type="string" optional>
The type of the secret. Valid options are "shared" or "personal". If not specified, the default value is "personal".
</ParamField>
<ParamField query="include_imports" type="boolean" default="false" optional>
Whether or not to include imported secrets from the current path. Read about [secret import](/documentation/platform/secret-reference)
</ParamField>
</Expandable>
</ParamField>
### client.createSecret(options)
@@ -194,26 +353,26 @@ Create a new secret in Infisical.
#### Parameters
<ParamField query="Parameters" type="object" optional>
<Expandable title="properties">
<ParamField query="secret_name" type="string" required>
The key of the secret to create.
</ParamField>
<ParamField query="secret_value" type="string" required>
The value of the secret.
</ParamField>
<ParamField query="project_id" type="string" required>
The project ID where the secret lives in.
</ParamField>
<ParamField query="environment" type="string" required>
The slug name (dev, prod, etc) of the environment from where secrets should be fetched from.
</ParamField>
<ParamField query="path" type="string" optional>
The path from where secret should be created.
</ParamField>
<ParamField query="type" type="string" optional>
The type of the secret. Valid options are "shared" or "personal". If not specified, the default value is "shared".
</ParamField>
</Expandable>
<Expandable title="properties">
<ParamField query="secret_name" type="string" required>
The key of the secret to create.
</ParamField>
<ParamField query="secret_value" type="string" required>
The value of the secret.
</ParamField>
<ParamField query="project_id" type="string" required>
The project ID where the secret lives in.
</ParamField>
<ParamField query="environment" type="string" required>
The slug name (dev, prod, etc) of the environment from where secrets should be fetched from.
</ParamField>
<ParamField query="path" type="string" optional>
The path from where secret should be created.
</ParamField>
<ParamField query="type" type="string" optional>
The type of the secret. Valid options are "shared" or "personal". If not specified, the default value is "shared".
</ParamField>
</Expandable>
</ParamField>
### client.updateSecret(options)
@@ -232,26 +391,26 @@ Update an existing secret in Infisical.
#### Parameters
<ParamField query="Parameters" type="object" optional>
<Expandable title="properties">
<ParamField query="secret_name" type="string" required>
The key of the secret to update.
</ParamField>
<ParamField query="secret_value" type="string" required>
The new value of the secret.
</ParamField>
<ParamField query="project_id" type="string" required>
The project ID where the secret lives in.
</ParamField>
<ParamField query="environment" type="string" required>
The slug name (dev, prod, etc) of the environment from where secrets should be fetched from.
</ParamField>
<ParamField query="path" type="string" optional>
The path from where secret should be updated.
</ParamField>
<ParamField query="type" type="string" optional>
The type of the secret. Valid options are "shared" or "personal". If not specified, the default value is "shared".
</ParamField>
</Expandable>
<Expandable title="properties">
<ParamField query="secret_name" type="string" required>
The key of the secret to update.
</ParamField>
<ParamField query="secret_value" type="string" required>
The new value of the secret.
</ParamField>
<ParamField query="project_id" type="string" required>
The project ID where the secret lives in.
</ParamField>
<ParamField query="environment" type="string" required>
The slug name (dev, prod, etc) of the environment from where secrets should be fetched from.
</ParamField>
<ParamField query="path" type="string" optional>
The path from where secret should be updated.
</ParamField>
<ParamField query="type" type="string" optional>
The type of the secret. Valid options are "shared" or "personal". If not specified, the default value is "shared".
</ParamField>
</Expandable>
</ParamField>
### client.deleteSecret(options)
@@ -269,23 +428,23 @@ Delete a secret in Infisical.
#### Parameters
<ParamField query="Parameters" type="object" optional>
<Expandable title="properties">
<ParamField query="secret_name" type="string">
The key of the secret to update.
</ParamField>
<ParamField query="project_id" type="string" required>
The project ID where the secret lives in.
</ParamField>
<ParamField query="environment" type="string" required>
The slug name (dev, prod, etc) of the environment from where secrets should be fetched from.
</ParamField>
<ParamField query="path" type="string" optional>
The path from where secret should be deleted.
</ParamField>
<ParamField query="type" type="string" optional>
The type of the secret. Valid options are "shared" or "personal". If not specified, the default value is "shared".
</ParamField>
</Expandable>
<Expandable title="properties">
<ParamField query="secret_name" type="string">
The key of the secret to update.
</ParamField>
<ParamField query="project_id" type="string" required>
The project ID where the secret lives in.
</ParamField>
<ParamField query="environment" type="string" required>
The slug name (dev, prod, etc) of the environment from where secrets should be fetched from.
</ParamField>
<ParamField query="path" type="string" optional>
The path from where secret should be deleted.
</ParamField>
<ParamField query="type" type="string" optional>
The type of the secret. Valid options are "shared" or "personal". If not specified, the default value is "shared".
</ParamField>
</Expandable>
</ParamField>
## Cryptography
@@ -299,9 +458,11 @@ key = client.createSymmetricKey()
```
#### Returns (string)
`key` (string): A base64-encoded, 256-bit symmetric key, that can be used for encryption/decryption purposes.
### Encrypt symmetric
```py
encryptOptions = EncryptSymmetricOptions(
key=key,
@@ -314,22 +475,22 @@ encryptedData = client.encryptSymmetric(encryptOptions)
#### Parameters
<ParamField query="Parameters" type="object" required>
<Expandable title="properties">
<ParamField query="plaintext" type="string">
The plaintext you want to encrypt.
</ParamField>
<ParamField query="key" type="string" required>
The symmetric key to use for encryption.
</ParamField>
</Expandable>
<Expandable title="properties">
<ParamField query="plaintext" type="string">
The plaintext you want to encrypt.
</ParamField>
<ParamField query="key" type="string" required>
The symmetric key to use for encryption.
</ParamField>
</Expandable>
</ParamField>
#### Returns (object)
`tag` (string): A base64-encoded, 128-bit authentication tag.
`iv` (string): A base64-encoded, 96-bit initialization vector.
`ciphertext` (string): A base64-encoded, encrypted ciphertext.
`tag` (string): A base64-encoded, 128-bit authentication tag. `iv` (string): A base64-encoded, 96-bit initialization vector. `ciphertext` (string): A base64-encoded, encrypted ciphertext.
### Decrypt symmetric
```py
decryptOptions = DecryptSymmetricOptions(
ciphertext=encryptedData.ciphertext,
@@ -344,22 +505,24 @@ decryptedString = client.decryptSymmetric(decryptOptions)
```
#### Parameters
<ParamField query="Parameters" type="object" required>
<Expandable title="properties">
<ParamField query="ciphertext" type="string">
The ciphertext you want to decrypt.
</ParamField>
<ParamField query="key" type="string" required>
The symmetric key to use for encryption.
</ParamField>
<ParamField query="iv" type="string" required>
The initialization vector to use for decryption.
</ParamField>
<ParamField query="tag" type="string" required>
The authentication tag to use for decryption.
</ParamField>
</Expandable>
<Expandable title="properties">
<ParamField query="ciphertext" type="string">
The ciphertext you want to decrypt.
</ParamField>
<ParamField query="key" type="string" required>
The symmetric key to use for encryption.
</ParamField>
<ParamField query="iv" type="string" required>
The initialization vector to use for decryption.
</ParamField>
<ParamField query="tag" type="string" required>
The authentication tag to use for decryption.
</ParamField>
</Expandable>
</ParamField>
#### Returns (string)
`plaintext` (string): The decrypted plaintext.

View File

@@ -1,7 +1,7 @@
#navbar .max-w-8xl {
max-width: 100%;
border-bottom: 1px solid #ebebeb;
background-color: #fcfcfc;
background-color: #F4F3EF;
}
.max-w-8xl {
@@ -14,7 +14,7 @@
padding-right: 30px;
border-right: 1px;
border-color: #cdd64b;
background-color: #fcfcfc;
background-color: #F4F3EF;
border-right: 1px solid #ebebeb;
}
@@ -27,6 +27,13 @@
padding: 5px;
}
#sidebar li > a.text-primary {
border-radius: 0;
background-color: #FBFFCC;
border-left: 4px solid #EFFF33;
padding: 5px;
}
#sidebar li > a.mt-2 {
border-radius: 0;
padding: 5px;
@@ -49,10 +56,10 @@
} */
#header {
border-left: 1px solid #26272b;
border-left: 4px solid #EFFF33;
padding-left: 16px;
padding-right: 16px;
background-color: #f5f5f5;
background-color: #FDFFE5;
padding-bottom: 10px;
padding-top: 10px;
}
@@ -60,9 +67,17 @@
#content-area .mt-8 .block{
border-radius: 0;
border-width: 1px;
background-color: #FCFBFA;
border-color: #ebebeb;
}
/* #content-area:hover .mt-8 .block:hover{
border-radius: 0;
border-width: 1px;
background-color: #FDFFE5;
border-color: #EFFF33;
} */
#content-area .mt-8 .rounded-xl{
border-radius: 0;
}

View File

@@ -0,0 +1,175 @@
import path from "path";
import { decryptSymmetric } from "@app/components/utilities/cryptography/crypto";
import { fetchProjectEncryptedSecrets } from "@app/hooks/api/secrets/queries";
const INTERPOLATION_SYNTAX_REG = /\${([^}]+)}/g;
export const interpolateSecrets = ({
projectId,
secretEncKey
}: {
projectId: string;
secretEncKey: string;
}) => {
const fetchSecretsCrossEnv = () => {
const fetchCache: Record<string, Record<string, string>> = {};
return async (secRefEnv: string, secRefPath: string[], secRefKey: string) => {
const secRefPathUrl = path.join("/", ...secRefPath);
const uniqKey = `${secRefEnv}-${secRefPathUrl}`;
if (fetchCache?.[uniqKey]) {
return fetchCache[uniqKey][secRefKey];
}
// get secrets by projectId, env, path
const encryptedSecrets = await fetchProjectEncryptedSecrets({
workspaceId: projectId,
environment: secRefEnv,
secretPath: secRefPathUrl
});
const decryptedSec = encryptedSecrets.reduce<Record<string, string>>((prev, secret) => {
const secretKey = decryptSymmetric({
ciphertext: secret.secretKeyCiphertext,
iv: secret.secretKeyIV,
tag: secret.secretKeyTag,
key: secretEncKey
});
const secretValue = decryptSymmetric({
ciphertext: secret.secretValueCiphertext,
iv: secret.secretValueIV,
tag: secret.secretValueTag,
key: secretEncKey
});
// eslint-disable-next-line
prev[secretKey] = secretValue;
return prev;
}, {});
fetchCache[uniqKey] = decryptedSec;
return fetchCache[uniqKey][secRefKey];
};
};
const recursivelyExpandSecret = async (
expandedSec: Record<string, string>,
interpolatedSec: Record<string, string>,
fetchCrossEnv: (env: string, secPath: string[], secKey: string) => Promise<string>,
recursionChainBreaker: Record<string, boolean>,
key: string
) => {
if (expandedSec?.[key] !== undefined) {
return expandedSec[key];
}
if (recursionChainBreaker?.[key]) {
return "";
}
// eslint-disable-next-line
recursionChainBreaker[key] = true;
let interpolatedValue = interpolatedSec[key];
if (!interpolatedValue) {
// eslint-disable-next-line no-console
console.error(`Couldn't find referenced value - ${key}`);
return "";
}
const refs = interpolatedValue.match(INTERPOLATION_SYNTAX_REG);
if (refs) {
await Promise.all(
refs.map(async (interpolationSyntax) => {
const interpolationKey = interpolationSyntax.slice(2, interpolationSyntax.length - 1);
const entities = interpolationKey.trim().split(".");
if (entities.length === 1) {
const val = await recursivelyExpandSecret(
expandedSec,
interpolatedSec,
fetchCrossEnv,
recursionChainBreaker,
interpolationKey
);
if (val) {
interpolatedValue = interpolatedValue.replaceAll(interpolationSyntax, val);
}
return;
}
if (entities.length > 1) {
const secRefEnv = entities[0];
const secRefPath = entities.slice(1, entities.length - 1);
const secRefKey = entities[entities.length - 1];
const val = await fetchCrossEnv(secRefEnv, secRefPath, secRefKey);
if (val) {
interpolatedValue = interpolatedValue.replaceAll(interpolationSyntax, val);
}
}
})
);
}
// eslint-disable-next-line
expandedSec[key] = interpolatedValue;
return interpolatedValue;
};
// used to convert multi line ones to quotes ones with \n
const formatMultiValueEnv = (val?: string) => {
if (!val) return "";
if (!val.match("\n")) return val;
return `"${val.replace(/\n/g, "\\n")}"`;
};
const expandSecrets = async (
secrets: Record<string, { value: string; comment?: string; skipMultilineEncoding?: boolean }>
) => {
const expandedSec: Record<string, string> = {};
const interpolatedSec: Record<string, string> = {};
const crossSecEnvFetch = fetchSecretsCrossEnv();
Object.keys(secrets).forEach((key) => {
if (secrets[key].value.match(INTERPOLATION_SYNTAX_REG)) {
interpolatedSec[key] = secrets[key].value;
} else {
expandedSec[key] = secrets[key].value;
}
});
await Promise.all(
Object.keys(secrets).map(async (key) => {
if (expandedSec?.[key]) {
// should not do multi line encoding if user has set it to skip
// eslint-disable-next-line
secrets[key].value = secrets[key].skipMultilineEncoding
? expandedSec[key]
: formatMultiValueEnv(expandedSec[key]);
return;
}
// this is to avoid recursion loop. So the graph should be direct graph rather than cyclic
// so for any recursion building if there is an entity two times same key meaning it will be looped
const recursionChainBreaker: Record<string, boolean> = {};
const expandedVal = await recursivelyExpandSecret(
expandedSec,
interpolatedSec,
crossSecEnvFetch,
recursionChainBreaker,
key
);
// eslint-disable-next-line
secrets[key].value = secrets[key].skipMultilineEncoding
? expandedVal
: formatMultiValueEnv(expandedVal);
})
);
return secrets;
};
return expandSecrets;
};

View File

@@ -4,5 +4,6 @@ export const identityAuthToNameMap: { [I in IdentityAuthMethod]: string } = {
[IdentityAuthMethod.UNIVERSAL_AUTH]: "Universal Auth",
[IdentityAuthMethod.KUBERNETES_AUTH]: "Kubernetes Auth",
[IdentityAuthMethod.GCP_AUTH]: "GCP Auth",
[IdentityAuthMethod.AWS_AUTH]: "AWS Auth"
[IdentityAuthMethod.AWS_AUTH]: "AWS Auth",
[IdentityAuthMethod.AZURE_AUTH]: "Azure Auth"
};

View File

@@ -2,5 +2,6 @@ export enum IdentityAuthMethod {
UNIVERSAL_AUTH = "universal-auth",
KUBERNETES_AUTH = "kubernetes-auth",
GCP_AUTH = "gcp-auth",
AWS_AUTH = "aws-auth"
AWS_AUTH = "aws-auth",
AZURE_AUTH = "azure-auth"
}

View File

@@ -2,6 +2,7 @@ export { identityAuthToNameMap } from "./constants";
export { IdentityAuthMethod } from "./enums";
export {
useAddIdentityAwsAuth,
useAddIdentityAzureAuth,
useAddIdentityGcpAuth,
useAddIdentityKubernetesAuth,
useAddIdentityUniversalAuth,
@@ -11,11 +12,14 @@ export {
useRevokeIdentityUniversalAuthClientSecret,
useUpdateIdentity,
useUpdateIdentityAwsAuth,
useUpdateIdentityAzureAuth,
useUpdateIdentityGcpAuth,
useUpdateIdentityKubernetesAuth,
useUpdateIdentityUniversalAuth} from "./mutations";
useUpdateIdentityUniversalAuth
} from "./mutations";
export {
useGetIdentityAwsAuth,
useGetIdentityAzureAuth,
useGetIdentityGcpAuth,
useGetIdentityKubernetesAuth,
useGetIdentityUniversalAuth,

View File

@@ -6,6 +6,7 @@ import { organizationKeys } from "../organization/queries";
import { identitiesKeys } from "./queries";
import {
AddIdentityAwsAuthDTO,
AddIdentityAzureAuthDTO,
AddIdentityGcpAuthDTO,
AddIdentityKubernetesAuthDTO,
AddIdentityUniversalAuthDTO,
@@ -17,14 +18,17 @@ import {
DeleteIdentityUniversalAuthClientSecretDTO,
Identity,
IdentityAwsAuth,
IdentityAzureAuth,
IdentityGcpAuth,
IdentityKubernetesAuth,
IdentityUniversalAuth,
UpdateIdentityAwsAuthDTO,
UpdateIdentityAzureAuthDTO,
UpdateIdentityDTO,
UpdateIdentityGcpAuthDTO,
UpdateIdentityKubernetesAuthDTO,
UpdateIdentityUniversalAuthDTO} from "./types";
UpdateIdentityUniversalAuthDTO
} from "./types";
export const useCreateIdentity = () => {
const queryClient = useQueryClient();
@@ -326,7 +330,41 @@ export const useUpdateIdentityAwsAuth = () => {
});
};
// --- K8s auth (TODO: add cert and token reviewer JWT fields)
export const useAddIdentityAzureAuth = () => {
const queryClient = useQueryClient();
return useMutation<IdentityAzureAuth, {}, AddIdentityAzureAuthDTO>({
mutationFn: async ({
identityId,
tenantId,
resource,
allowedServicePrincipalIds,
accessTokenTTL,
accessTokenMaxTTL,
accessTokenNumUsesLimit,
accessTokenTrustedIps
}) => {
const {
data: { identityAzureAuth }
} = await apiRequest.post<{ identityAzureAuth: IdentityAzureAuth }>(
`/api/v1/auth/azure-auth/identities/${identityId}`,
{
tenantId,
resource,
allowedServicePrincipalIds,
accessTokenTTL,
accessTokenMaxTTL,
accessTokenNumUsesLimit,
accessTokenTrustedIps
}
);
return identityAzureAuth;
},
onSuccess: (_, { organizationId }) => {
queryClient.invalidateQueries(organizationKeys.getOrgIdentityMemberships(organizationId));
}
});
};
export const useAddIdentityKubernetesAuth = () => {
const queryClient = useQueryClient();
@@ -370,6 +408,42 @@ export const useAddIdentityKubernetesAuth = () => {
});
};
export const useUpdateIdentityAzureAuth = () => {
const queryClient = useQueryClient();
return useMutation<IdentityAzureAuth, {}, UpdateIdentityAzureAuthDTO>({
mutationFn: async ({
identityId,
tenantId,
resource,
allowedServicePrincipalIds,
accessTokenTTL,
accessTokenMaxTTL,
accessTokenNumUsesLimit,
accessTokenTrustedIps
}) => {
const {
data: { identityAzureAuth }
} = await apiRequest.patch<{ identityAzureAuth: IdentityAzureAuth }>(
`/api/v1/auth/azure-auth/identities/${identityId}`,
{
tenantId,
resource,
allowedServicePrincipalIds,
accessTokenTTL,
accessTokenMaxTTL,
accessTokenNumUsesLimit,
accessTokenTrustedIps
}
);
return identityAzureAuth;
},
onSuccess: (_, { organizationId }) => {
queryClient.invalidateQueries(organizationKeys.getOrgIdentityMemberships(organizationId));
}
});
};
export const useUpdateIdentityKubernetesAuth = () => {
const queryClient = useQueryClient();
return useMutation<IdentityKubernetesAuth, {}, UpdateIdentityKubernetesAuthDTO>({
@@ -403,6 +477,7 @@ export const useUpdateIdentityKubernetesAuth = () => {
accessTokenTrustedIps
}
);
return identityKubernetesAuth;
},
onSuccess: (_, { organizationId }) => {

View File

@@ -5,10 +5,10 @@ import { apiRequest } from "@app/config/request";
import {
ClientSecretData,
IdentityAwsAuth,
IdentityAzureAuth,
IdentityGcpAuth,
IdentityKubernetesAuth,
IdentityUniversalAuth
} from "./types";
IdentityUniversalAuth} from "./types";
export const identitiesKeys = {
getIdentityUniversalAuth: (identityId: string) =>
@@ -18,7 +18,8 @@ export const identitiesKeys = {
getIdentityKubernetesAuth: (identityId: string) =>
[{ identityId }, "identity-kubernetes-auth"] as const,
getIdentityGcpAuth: (identityId: string) => [{ identityId }, "identity-gcp-auth"] as const,
getIdentityAwsAuth: (identityId: string) => [{ identityId }, "identity-aws-auth"] as const
getIdentityAwsAuth: (identityId: string) => [{ identityId }, "identity-aws-auth"] as const,
getIdentityAzureAuth: (identityId: string) => [{ identityId }, "identity-azure-auth"] as const
};
export const useGetIdentityUniversalAuth = (identityId: string) => {
@@ -81,6 +82,21 @@ export const useGetIdentityAwsAuth = (identityId: string) => {
});
};
export const useGetIdentityAzureAuth = (identityId: string) => {
return useQuery({
enabled: Boolean(identityId),
queryKey: identitiesKeys.getIdentityAzureAuth(identityId),
queryFn: async () => {
const {
data: { identityAzureAuth }
} = await apiRequest.get<{ identityAzureAuth: IdentityAzureAuth }>(
`/api/v1/auth/azure-auth/identities/${identityId}`
);
return identityAzureAuth;
}
});
};
export const useGetIdentityKubernetesAuth = (identityId: string) => {
return useQuery({
enabled: Boolean(identityId),

View File

@@ -195,6 +195,45 @@ export type UpdateIdentityAwsAuthDTO = {
}[];
};
export type IdentityAzureAuth = {
identityId: string;
tenantId: string;
resource: string;
allowedServicePrincipalIds: string;
accessTokenTTL: number;
accessTokenMaxTTL: number;
accessTokenNumUsesLimit: number;
accessTokenTrustedIps: IdentityTrustedIp[];
};
export type AddIdentityAzureAuthDTO = {
organizationId: string;
identityId: string;
tenantId: string;
resource: string;
allowedServicePrincipalIds: string;
accessTokenTTL: number;
accessTokenMaxTTL: number;
accessTokenNumUsesLimit: number;
accessTokenTrustedIps: {
ipAddress: string;
}[];
};
export type UpdateIdentityAzureAuthDTO = {
organizationId: string;
identityId: string;
tenantId?: string;
resource?: string;
allowedServicePrincipalIds?: string;
accessTokenTTL?: number;
accessTokenMaxTTL?: number;
accessTokenNumUsesLimit?: number;
accessTokenTrustedIps?: {
ipAddress: string;
}[];
};
export type IdentityKubernetesAuth = {
identityId: string;
kubernetesHost: string;

View File

@@ -30,7 +30,7 @@ export type HerokuPipelineCoupling = {
export type Team = {
name: string;
teamId: string;
id: string;
};
export type Environment = {

View File

@@ -64,6 +64,7 @@ export const useCreateIntegration = () => {
secretSuffix?: string;
initialSyncBehavior?: string;
shouldAutoRedeploy?: boolean;
mappingBehavior?: string;
secretAWSTag?: {
key: string;
value: string;

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