mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-16 01:05:40 +00:00
Merge remote-tracking branch 'origin' into ssh-certs
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { ForbiddenError } from "@casl/ability";
|
||||
import { ForbiddenError, subject } from "@casl/ability";
|
||||
import ms from "ms";
|
||||
|
||||
import { ProjectMembershipRole } from "@app/db/schemas";
|
||||
@@ -61,7 +61,12 @@ export const identityProjectServiceFactory = ({
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
);
|
||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Create, ProjectPermissionSub.Identity);
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionActions.Create,
|
||||
subject(ProjectPermissionSub.Identity, {
|
||||
identityId
|
||||
})
|
||||
);
|
||||
|
||||
const existingIdentity = await identityProjectDAL.findOne({ identityId, projectId });
|
||||
if (existingIdentity)
|
||||
@@ -161,7 +166,10 @@ export const identityProjectServiceFactory = ({
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
);
|
||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Identity);
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionActions.Edit,
|
||||
subject(ProjectPermissionSub.Identity, { identityId })
|
||||
);
|
||||
|
||||
const projectIdentity = await identityProjectDAL.findOne({ identityId, projectId });
|
||||
if (!projectIdentity)
|
||||
@@ -253,7 +261,11 @@ export const identityProjectServiceFactory = ({
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
);
|
||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Delete, ProjectPermissionSub.Identity);
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionActions.Delete,
|
||||
subject(ProjectPermissionSub.Identity, { identityId })
|
||||
);
|
||||
|
||||
const { permission: identityRolePermission } = await permissionService.getProjectPermission(
|
||||
ActorType.IDENTITY,
|
||||
identityId,
|
||||
@@ -317,7 +329,11 @@ export const identityProjectServiceFactory = ({
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
);
|
||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Identity);
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionActions.Read,
|
||||
subject(ProjectPermissionSub.Identity, { identityId })
|
||||
);
|
||||
|
||||
const [identityMembership] = await identityProjectDAL.findByProjectId(projectId, { identityId });
|
||||
if (!identityMembership)
|
||||
|
||||
@@ -55,6 +55,7 @@ import {
|
||||
TOctopusDeployVariableSet,
|
||||
TSaveIntegrationAccessTokenDTO,
|
||||
TTeamCityBuildConfig,
|
||||
TUpdateIntegrationAuthDTO,
|
||||
TVercelBranches
|
||||
} from "./integration-auth-types";
|
||||
import { getIntegrationOptions, Integrations, IntegrationUrls } from "./integration-list";
|
||||
@@ -368,6 +369,148 @@ export const integrationAuthServiceFactory = ({
|
||||
return integrationAuthDAL.create(updateDoc);
|
||||
};
|
||||
|
||||
const updateIntegrationAuth = async ({
|
||||
integrationAuthId,
|
||||
refreshToken,
|
||||
actorId,
|
||||
integration: newIntegration,
|
||||
url,
|
||||
actor,
|
||||
actorOrgId,
|
||||
actorAuthMethod,
|
||||
accessId,
|
||||
namespace,
|
||||
accessToken,
|
||||
awsAssumeIamRoleArn
|
||||
}: TUpdateIntegrationAuthDTO) => {
|
||||
const integrationAuth = await integrationAuthDAL.findById(integrationAuthId);
|
||||
if (!integrationAuth) {
|
||||
throw new NotFoundError({ message: `Integration auth with id ${integrationAuthId} not found.` });
|
||||
}
|
||||
|
||||
const { permission } = await permissionService.getProjectPermission(
|
||||
actor,
|
||||
actorId,
|
||||
integrationAuth.projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
);
|
||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Integrations);
|
||||
|
||||
const { projectId } = integrationAuth;
|
||||
const integration = newIntegration || integrationAuth.integration;
|
||||
|
||||
const updateDoc: TIntegrationAuthsInsert = {
|
||||
projectId,
|
||||
integration,
|
||||
namespace,
|
||||
url,
|
||||
algorithm: SecretEncryptionAlgo.AES_256_GCM,
|
||||
keyEncoding: SecretKeyEncoding.UTF8,
|
||||
...(integration === Integrations.GCP_SECRET_MANAGER
|
||||
? {
|
||||
metadata: {
|
||||
authMethod: "serviceAccount"
|
||||
}
|
||||
}
|
||||
: {})
|
||||
};
|
||||
|
||||
const { shouldUseSecretV2Bridge, botKey } = await projectBotService.getBotKey(projectId);
|
||||
if (shouldUseSecretV2Bridge) {
|
||||
const { encryptor: secretManagerEncryptor } = await kmsService.createCipherPairWithDataKey({
|
||||
type: KmsDataKey.SecretManager,
|
||||
projectId
|
||||
});
|
||||
if (refreshToken) {
|
||||
const tokenDetails = await exchangeRefresh(
|
||||
integration,
|
||||
refreshToken,
|
||||
url,
|
||||
updateDoc.metadata as Record<string, string>
|
||||
);
|
||||
const refreshEncToken = secretManagerEncryptor({
|
||||
plainText: Buffer.from(tokenDetails.refreshToken)
|
||||
}).cipherTextBlob;
|
||||
updateDoc.encryptedRefresh = refreshEncToken;
|
||||
|
||||
const accessEncToken = secretManagerEncryptor({
|
||||
plainText: Buffer.from(tokenDetails.accessToken)
|
||||
}).cipherTextBlob;
|
||||
updateDoc.encryptedAccess = accessEncToken;
|
||||
updateDoc.accessExpiresAt = tokenDetails.accessExpiresAt;
|
||||
}
|
||||
|
||||
if (!refreshToken && (accessId || accessToken || awsAssumeIamRoleArn)) {
|
||||
if (accessToken) {
|
||||
const accessEncToken = secretManagerEncryptor({
|
||||
plainText: Buffer.from(accessToken)
|
||||
}).cipherTextBlob;
|
||||
updateDoc.encryptedAccess = accessEncToken;
|
||||
updateDoc.encryptedAwsAssumeIamRoleArn = null;
|
||||
}
|
||||
if (accessId) {
|
||||
const accessEncToken = secretManagerEncryptor({
|
||||
plainText: Buffer.from(accessId)
|
||||
}).cipherTextBlob;
|
||||
updateDoc.encryptedAccessId = accessEncToken;
|
||||
updateDoc.encryptedAwsAssumeIamRoleArn = null;
|
||||
}
|
||||
if (awsAssumeIamRoleArn) {
|
||||
const awsAssumeIamRoleArnEncrypted = secretManagerEncryptor({
|
||||
plainText: Buffer.from(awsAssumeIamRoleArn)
|
||||
}).cipherTextBlob;
|
||||
updateDoc.encryptedAwsAssumeIamRoleArn = awsAssumeIamRoleArnEncrypted;
|
||||
updateDoc.encryptedAccess = null;
|
||||
updateDoc.encryptedAccessId = null;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (!botKey) throw new NotFoundError({ message: `Project bot key for project with ID '${projectId}' not found` });
|
||||
if (refreshToken) {
|
||||
const tokenDetails = await exchangeRefresh(
|
||||
integration,
|
||||
refreshToken,
|
||||
url,
|
||||
updateDoc.metadata as Record<string, string>
|
||||
);
|
||||
const refreshEncToken = encryptSymmetric128BitHexKeyUTF8(tokenDetails.refreshToken, botKey);
|
||||
updateDoc.refreshIV = refreshEncToken.iv;
|
||||
updateDoc.refreshTag = refreshEncToken.tag;
|
||||
updateDoc.refreshCiphertext = refreshEncToken.ciphertext;
|
||||
const accessEncToken = encryptSymmetric128BitHexKeyUTF8(tokenDetails.accessToken, botKey);
|
||||
updateDoc.accessIV = accessEncToken.iv;
|
||||
updateDoc.accessTag = accessEncToken.tag;
|
||||
updateDoc.accessCiphertext = accessEncToken.ciphertext;
|
||||
|
||||
updateDoc.accessExpiresAt = tokenDetails.accessExpiresAt;
|
||||
}
|
||||
|
||||
if (!refreshToken && (accessId || accessToken || awsAssumeIamRoleArn)) {
|
||||
if (accessToken) {
|
||||
const accessEncToken = encryptSymmetric128BitHexKeyUTF8(accessToken, botKey);
|
||||
updateDoc.accessIV = accessEncToken.iv;
|
||||
updateDoc.accessTag = accessEncToken.tag;
|
||||
updateDoc.accessCiphertext = accessEncToken.ciphertext;
|
||||
}
|
||||
if (accessId) {
|
||||
const accessEncToken = encryptSymmetric128BitHexKeyUTF8(accessId, botKey);
|
||||
updateDoc.accessIdIV = accessEncToken.iv;
|
||||
updateDoc.accessIdTag = accessEncToken.tag;
|
||||
updateDoc.accessIdCiphertext = accessEncToken.ciphertext;
|
||||
}
|
||||
if (awsAssumeIamRoleArn) {
|
||||
const awsAssumeIamRoleArnEnc = encryptSymmetric128BitHexKeyUTF8(awsAssumeIamRoleArn, botKey);
|
||||
updateDoc.awsAssumeIamRoleArnCipherText = awsAssumeIamRoleArnEnc.ciphertext;
|
||||
updateDoc.awsAssumeIamRoleArnIV = awsAssumeIamRoleArnEnc.iv;
|
||||
updateDoc.awsAssumeIamRoleArnTag = awsAssumeIamRoleArnEnc.tag;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return integrationAuthDAL.updateById(integrationAuthId, updateDoc);
|
||||
};
|
||||
|
||||
// helper function
|
||||
const getIntegrationAccessToken = async (
|
||||
integrationAuth: TIntegrationAuths,
|
||||
@@ -1615,6 +1758,7 @@ export const integrationAuthServiceFactory = ({
|
||||
getIntegrationAuth,
|
||||
oauthExchange,
|
||||
saveIntegrationToken,
|
||||
updateIntegrationAuth,
|
||||
deleteIntegrationAuthById,
|
||||
deleteIntegrationAuths,
|
||||
getIntegrationAuthTeams,
|
||||
|
||||
@@ -22,6 +22,11 @@ export type TSaveIntegrationAccessTokenDTO = {
|
||||
awsAssumeIamRoleArn?: string;
|
||||
} & TProjectPermission;
|
||||
|
||||
export type TUpdateIntegrationAuthDTO = Omit<TSaveIntegrationAccessTokenDTO, "projectId" | "integration"> & {
|
||||
integrationAuthId: string;
|
||||
integration?: string;
|
||||
};
|
||||
|
||||
export type TDeleteIntegrationAuthsDTO = TProjectPermission & {
|
||||
integration: string;
|
||||
projectId: string;
|
||||
|
||||
@@ -151,7 +151,9 @@ export const integrationServiceFactory = ({
|
||||
isActive,
|
||||
environment,
|
||||
secretPath,
|
||||
metadata
|
||||
region,
|
||||
metadata,
|
||||
path
|
||||
}: TUpdateIntegrationDTO) => {
|
||||
const integration = await integrationDAL.findById(id);
|
||||
if (!integration) throw new NotFoundError({ message: `Integration with ID '${id}' not found` });
|
||||
@@ -192,7 +194,9 @@ export const integrationServiceFactory = ({
|
||||
appId,
|
||||
targetEnvironment,
|
||||
owner,
|
||||
region,
|
||||
secretPath,
|
||||
path,
|
||||
metadata: {
|
||||
...(integration.metadata as object),
|
||||
...metadata
|
||||
|
||||
@@ -49,6 +49,8 @@ export type TUpdateIntegrationDTO = {
|
||||
appId?: string;
|
||||
isActive?: boolean;
|
||||
secretPath?: string;
|
||||
region?: string;
|
||||
path?: string;
|
||||
targetEnvironment?: string;
|
||||
owner?: string;
|
||||
environment?: string;
|
||||
|
||||
@@ -4,8 +4,10 @@ import { z } from "zod";
|
||||
|
||||
import { KmsKeysSchema, TKmsRootConfig } from "@app/db/schemas";
|
||||
import { AwsKmsProviderFactory } from "@app/ee/services/external-kms/providers/aws-kms";
|
||||
import { GcpKmsProviderFactory } from "@app/ee/services/external-kms/providers/gcp-kms";
|
||||
import {
|
||||
ExternalKmsAwsSchema,
|
||||
ExternalKmsGcpSchema,
|
||||
KmsProviders,
|
||||
TExternalKmsProviderFns
|
||||
} from "@app/ee/services/external-kms/providers/model";
|
||||
@@ -291,6 +293,16 @@ export const kmsServiceFactory = ({
|
||||
});
|
||||
break;
|
||||
}
|
||||
case KmsProviders.Gcp: {
|
||||
const decryptedProviderInput = await ExternalKmsGcpSchema.parseAsync(
|
||||
JSON.parse(decryptedProviderInputBlob.toString("utf8"))
|
||||
);
|
||||
|
||||
externalKms = await GcpKmsProviderFactory({
|
||||
inputs: decryptedProviderInput
|
||||
});
|
||||
break;
|
||||
}
|
||||
default:
|
||||
throw new Error("Invalid KMS provider.");
|
||||
}
|
||||
@@ -353,6 +365,16 @@ export const kmsServiceFactory = ({
|
||||
});
|
||||
break;
|
||||
}
|
||||
case KmsProviders.Gcp: {
|
||||
const decryptedProviderInput = await ExternalKmsGcpSchema.parseAsync(
|
||||
JSON.parse(decryptedProviderInputBlob.toString("utf8"))
|
||||
);
|
||||
|
||||
externalKms = await GcpKmsProviderFactory({
|
||||
inputs: decryptedProviderInput
|
||||
});
|
||||
break;
|
||||
}
|
||||
default:
|
||||
throw new Error("Invalid KMS provider.");
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { ForbiddenError } from "@casl/ability";
|
||||
import slugify from "@sindresorhus/slugify";
|
||||
|
||||
import { OrgMembershipRole, ProjectMembershipRole, ProjectVersion, TProjectEnvironments } from "@app/db/schemas";
|
||||
import { ProjectMembershipRole, ProjectVersion, TProjectEnvironments } 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";
|
||||
@@ -12,7 +12,6 @@ import { TSshCertificateAuthorityDALFactory } from "@app/ee/services/ssh/ssh-cer
|
||||
import { TSshCertificateDALFactory } from "@app/ee/services/ssh-certificate/ssh-certificate-dal";
|
||||
import { TSshCertificateTemplateDALFactory } from "@app/ee/services/ssh-certificate-template/ssh-certificate-template-dal";
|
||||
import { TKeyStoreFactory } from "@app/keystore/keystore";
|
||||
import { isAtLeastAsPrivileged } from "@app/lib/casl";
|
||||
import { infisicalSymmetricEncypt } from "@app/lib/crypto/encryption";
|
||||
import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors";
|
||||
import { groupBy } from "@app/lib/fn";
|
||||
@@ -382,20 +381,6 @@ export const projectServiceFactory = ({
|
||||
});
|
||||
}
|
||||
|
||||
// Get the role permission for the identity
|
||||
const { permission: rolePermission, role: customRole } = await permissionService.getOrgPermissionByRole(
|
||||
OrgMembershipRole.Member,
|
||||
organization.id
|
||||
);
|
||||
|
||||
// Identity has to be at least a member in order to create projects
|
||||
const hasPrivilege = isAtLeastAsPrivileged(permission, rolePermission);
|
||||
if (!hasPrivilege)
|
||||
throw new ForbiddenRequestError({
|
||||
message: "Failed to add identity to project with more privileged role"
|
||||
});
|
||||
const isCustomRole = Boolean(customRole);
|
||||
|
||||
const identityProjectMembership = await identityProjectDAL.create(
|
||||
{
|
||||
identityId: actorId,
|
||||
@@ -407,8 +392,7 @@ export const projectServiceFactory = ({
|
||||
await identityProjectMembershipRoleDAL.create(
|
||||
{
|
||||
projectMembershipId: identityProjectMembership.id,
|
||||
role: isCustomRole ? ProjectMembershipRole.Custom : ProjectMembershipRole.Admin,
|
||||
customRoleId: customRole?.id
|
||||
role: ProjectMembershipRole.Admin
|
||||
},
|
||||
tx
|
||||
);
|
||||
|
||||
@@ -932,8 +932,12 @@ export const secretQueueFactory = ({
|
||||
);
|
||||
|
||||
const message =
|
||||
(err instanceof AxiosError ? JSON.stringify(err?.response?.data) : (err as Error)?.message) ||
|
||||
"Unknown error occurred.";
|
||||
// eslint-disable-next-line no-nested-ternary
|
||||
(err instanceof AxiosError
|
||||
? err?.response?.data
|
||||
? JSON.stringify(err?.response?.data)
|
||||
: err?.message
|
||||
: (err as Error)?.message) || "Unknown error occurred.";
|
||||
|
||||
await auditLogService.createAuditLog({
|
||||
projectId,
|
||||
|
||||
@@ -8,9 +8,9 @@
|
||||
</head>
|
||||
<body>
|
||||
<h2>Join your organization on Infisical</h2>
|
||||
<p>{{inviterFirstName}} ({{inviterUsername}}) has invited you to their Infisical organization — {{organizationName}}</p>
|
||||
<a href="{{callback_url}}?token={{token}}{{#if metadata}}&metadata={{metadata}}{{/if}}&to={{email}}&organization_id={{organizationId}}">Join now</a>
|
||||
<p>{{inviterFirstName}} ({{inviterUsername}}) has invited you to their Infisical organization named {{organizationName}}</p>
|
||||
<a href="{{callback_url}}?token={{token}}{{#if metadata}}&metadata={{metadata}}{{/if}}&to={{email}}&organization_id={{organizationId}}">Click to join</a>
|
||||
<h3>What is Infisical?</h3>
|
||||
<p>Infisical is an easy-to-use end-to-end encrypted tool that enables developers to sync and manage their secrets and configs.</p>
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
|
||||
@@ -6,10 +6,10 @@
|
||||
</head>
|
||||
<body>
|
||||
<h2>Join your team on Infisical</h2>
|
||||
<p>You have been invited to a new Infisical project — {{workspaceName}}</p>
|
||||
<a href="{{callback_url}}">Join now</a>
|
||||
<p>You have been invited to a new Infisical project named {{workspaceName}}</p>
|
||||
<a href="{{callback_url}}">Click to join</a>
|
||||
<h3>What is Infisical?</h3>
|
||||
<p>Infisical is an easy-to-use end-to-end encrypted tool that enables developers to sync and manage their secrets
|
||||
and configs.</p>
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
|
||||
Reference in New Issue
Block a user