mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-06 09:26:47 +00:00
Merge branch 'main' of https://github.com/Infisical/infisical
This commit is contained in:
@@ -585,12 +585,13 @@ export const INTEGRATION = {
|
|||||||
region: "AWS region to sync secrets to.",
|
region: "AWS region to sync secrets to.",
|
||||||
scope: "Scope of the provider. Used by Github, Qovery",
|
scope: "Scope of the provider. Used by Github, Qovery",
|
||||||
metadata: {
|
metadata: {
|
||||||
secretPrefix: "The prefix for the saved secret. Used by GCP",
|
secretPrefix: "The prefix for the saved secret. Used by GCP.",
|
||||||
secretSuffix: "The suffix 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",
|
initialSyncBehavoir: "Type of syncing behavoir with the integration.",
|
||||||
shouldAutoRedeploy: "Used by Render to trigger auto deploy",
|
shouldAutoRedeploy: "Used by Render to trigger auto deploy.",
|
||||||
secretGCPLabel: "The label for the GCP secrets",
|
secretGCPLabel: "The label for GCP secrets.",
|
||||||
secretAWSTag: "The tag for the AWS secrets"
|
secretAWSTag: "The tags for AWS secrets.",
|
||||||
|
kmsKeyId: "The ID of the encryption key from AWS KMS."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
UPDATE: {
|
UPDATE: {
|
||||||
|
|||||||
@@ -511,6 +511,39 @@ export const registerIntegrationAuthRouter = async (server: FastifyZodProvider)
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: "GET",
|
||||||
|
url: "/:integrationAuthId/aws-secrets-manager/kms-keys",
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT]),
|
||||||
|
schema: {
|
||||||
|
params: z.object({
|
||||||
|
integrationAuthId: z.string().trim()
|
||||||
|
}),
|
||||||
|
querystring: z.object({
|
||||||
|
region: z.string().trim()
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
kmsKeys: z.object({ id: z.string(), alias: z.string() }).array()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handler: async (req) => {
|
||||||
|
const kmsKeys = await server.services.integrationAuth.getAwsKmsKeys({
|
||||||
|
actorId: req.permission.id,
|
||||||
|
actor: req.permission.type,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
|
actorOrgId: req.permission.orgId,
|
||||||
|
id: req.params.integrationAuthId,
|
||||||
|
region: req.query.region
|
||||||
|
});
|
||||||
|
return { kmsKeys };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
server.route({
|
server.route({
|
||||||
method: "GET",
|
method: "GET",
|
||||||
url: "/:integrationAuthId/qovery/projects",
|
url: "/:integrationAuthId/qovery/projects",
|
||||||
|
|||||||
@@ -58,14 +58,17 @@ export const registerIntegrationRouter = async (server: FastifyZodProvider) => {
|
|||||||
.optional()
|
.optional()
|
||||||
.describe(INTEGRATION.CREATE.metadata.secretGCPLabel),
|
.describe(INTEGRATION.CREATE.metadata.secretGCPLabel),
|
||||||
secretAWSTag: z
|
secretAWSTag: z
|
||||||
.object({
|
.array(
|
||||||
key: z.string(),
|
z.object({
|
||||||
value: z.string()
|
key: z.string(),
|
||||||
})
|
value: z.string()
|
||||||
|
})
|
||||||
|
)
|
||||||
.optional()
|
.optional()
|
||||||
.describe(INTEGRATION.CREATE.metadata.secretAWSTag)
|
.describe(INTEGRATION.CREATE.metadata.secretAWSTag),
|
||||||
|
kmsKeyId: z.string().optional().describe(INTEGRATION.CREATE.metadata.kmsKeyId)
|
||||||
})
|
})
|
||||||
.optional()
|
.default({})
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { ForbiddenError } from "@casl/ability";
|
import { ForbiddenError } from "@casl/ability";
|
||||||
import { Octokit } from "@octokit/rest";
|
import { Octokit } from "@octokit/rest";
|
||||||
|
import AWS from "aws-sdk";
|
||||||
|
|
||||||
import { SecretEncryptionAlgo, SecretKeyEncoding, TIntegrationAuths, TIntegrationAuthsInsert } from "@app/db/schemas";
|
import { SecretEncryptionAlgo, SecretKeyEncoding, TIntegrationAuths, TIntegrationAuthsInsert } from "@app/db/schemas";
|
||||||
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
||||||
@@ -23,6 +24,7 @@ import {
|
|||||||
TGetIntegrationAuthTeamCityBuildConfigDTO,
|
TGetIntegrationAuthTeamCityBuildConfigDTO,
|
||||||
THerokuPipelineCoupling,
|
THerokuPipelineCoupling,
|
||||||
TIntegrationAuthAppsDTO,
|
TIntegrationAuthAppsDTO,
|
||||||
|
TIntegrationAuthAwsKmsKeyDTO,
|
||||||
TIntegrationAuthBitbucketWorkspaceDTO,
|
TIntegrationAuthBitbucketWorkspaceDTO,
|
||||||
TIntegrationAuthChecklyGroupsDTO,
|
TIntegrationAuthChecklyGroupsDTO,
|
||||||
TIntegrationAuthGithubEnvsDTO,
|
TIntegrationAuthGithubEnvsDTO,
|
||||||
@@ -534,6 +536,52 @@ export const integrationAuthServiceFactory = ({
|
|||||||
return data.results.map(({ name, id: orgId }) => ({ name, orgId }));
|
return data.results.map(({ name, id: orgId }) => ({ name, orgId }));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getAwsKmsKeys = async ({
|
||||||
|
actorId,
|
||||||
|
actor,
|
||||||
|
actorOrgId,
|
||||||
|
actorAuthMethod,
|
||||||
|
id,
|
||||||
|
region
|
||||||
|
}: TIntegrationAuthAwsKmsKeyDTO) => {
|
||||||
|
const integrationAuth = await integrationAuthDAL.findById(id);
|
||||||
|
if (!integrationAuth) throw new BadRequestError({ message: "Failed to find integration" });
|
||||||
|
|
||||||
|
const { permission } = await permissionService.getProjectPermission(
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
integrationAuth.projectId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId
|
||||||
|
);
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations);
|
||||||
|
const botKey = await projectBotService.getBotKey(integrationAuth.projectId);
|
||||||
|
const { accessId, accessToken } = await getIntegrationAccessToken(integrationAuth, botKey);
|
||||||
|
|
||||||
|
AWS.config.update({
|
||||||
|
region,
|
||||||
|
credentials: {
|
||||||
|
accessKeyId: String(accessId),
|
||||||
|
secretAccessKey: accessToken
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const kms = new AWS.KMS();
|
||||||
|
|
||||||
|
const aliases = await kms.listAliases({}).promise();
|
||||||
|
const keys = await kms.listKeys({}).promise();
|
||||||
|
const response = keys
|
||||||
|
.Keys!.map((key) => {
|
||||||
|
const keyAlias = aliases.Aliases!.find((alias) => key.KeyId === alias.TargetKeyId);
|
||||||
|
if (!keyAlias?.AliasName?.includes("alias/aws/") || keyAlias?.AliasName?.includes("alias/aws/secretsmanager")) {
|
||||||
|
return { id: String(key.KeyId), alias: String(keyAlias?.AliasName || key.KeyId) };
|
||||||
|
}
|
||||||
|
return { id: "null", alias: "null" };
|
||||||
|
})
|
||||||
|
.filter((elem) => elem.id !== "null");
|
||||||
|
|
||||||
|
return response;
|
||||||
|
};
|
||||||
|
|
||||||
const getQoveryProjects = async ({
|
const getQoveryProjects = async ({
|
||||||
actorId,
|
actorId,
|
||||||
actor,
|
actor,
|
||||||
@@ -1133,6 +1181,7 @@ export const integrationAuthServiceFactory = ({
|
|||||||
getIntegrationApps,
|
getIntegrationApps,
|
||||||
getVercelBranches,
|
getVercelBranches,
|
||||||
getApps,
|
getApps,
|
||||||
|
getAwsKmsKeys,
|
||||||
getGithubOrgs,
|
getGithubOrgs,
|
||||||
getGithubEnvs,
|
getGithubEnvs,
|
||||||
getChecklyGroups,
|
getChecklyGroups,
|
||||||
|
|||||||
@@ -63,6 +63,11 @@ export type TIntegrationAuthQoveryProjectDTO = {
|
|||||||
orgId: string;
|
orgId: string;
|
||||||
} & Omit<TProjectPermission, "projectId">;
|
} & Omit<TProjectPermission, "projectId">;
|
||||||
|
|
||||||
|
export type TIntegrationAuthAwsKmsKeyDTO = {
|
||||||
|
id: string;
|
||||||
|
region: string;
|
||||||
|
} & Omit<TProjectPermission, "projectId">;
|
||||||
|
|
||||||
export type TIntegrationAuthQoveryEnvironmentsDTO = {
|
export type TIntegrationAuthQoveryEnvironmentsDTO = {
|
||||||
id: string;
|
id: string;
|
||||||
} & TProjectPermission;
|
} & TProjectPermission;
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||||
/* eslint-disable @typescript-eslint/no-unsafe-return */
|
/* eslint-disable @typescript-eslint/no-unsafe-return */
|
||||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||||
/* eslint-disable @typescript-eslint/no-unsafe-argument */
|
/* eslint-disable @typescript-eslint/no-unsafe-argument */
|
||||||
@@ -489,7 +490,9 @@ const syncSecretsAWSParameterStore = async ({
|
|||||||
Type: "SecureString",
|
Type: "SecureString",
|
||||||
Value: secrets[key].value,
|
Value: secrets[key].value,
|
||||||
// Overwrite: true,
|
// Overwrite: true,
|
||||||
Tags: metadata.secretAWSTag ? [{ Key: metadata.secretAWSTag.key, Value: metadata.secretAWSTag.value }] : []
|
Tags: metadata.secretAWSTag
|
||||||
|
? metadata.secretAWSTag.map((tag: { key: string; value: string }) => ({ Key: tag.key, Value: tag.value }))
|
||||||
|
: []
|
||||||
})
|
})
|
||||||
.promise();
|
.promise();
|
||||||
// case: secret exists in AWS parameter store
|
// case: secret exists in AWS parameter store
|
||||||
@@ -579,7 +582,10 @@ const syncSecretsAWSSecretManager = async ({
|
|||||||
new CreateSecretCommand({
|
new CreateSecretCommand({
|
||||||
Name: integration.app as string,
|
Name: integration.app as string,
|
||||||
SecretString: JSON.stringify(secKeyVal),
|
SecretString: JSON.stringify(secKeyVal),
|
||||||
Tags: metadata.secretAWSTag ? [{ Key: metadata.secretAWSTag.key, Value: metadata.secretAWSTag.value }] : []
|
KmsKeyId: metadata.kmsKeyId ? metadata.kmsKeyId : null,
|
||||||
|
Tags: metadata.secretAWSTag
|
||||||
|
? metadata.secretAWSTag.map((tag: { key: string; value: string }) => ({ Key: tag.key, Value: tag.value }))
|
||||||
|
: []
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -2151,16 +2157,29 @@ const syncSecretsQovery = async ({
|
|||||||
* @param {String} obj.accessToken - access token for Terraform Cloud API
|
* @param {String} obj.accessToken - access token for Terraform Cloud API
|
||||||
*/
|
*/
|
||||||
const syncSecretsTerraformCloud = async ({
|
const syncSecretsTerraformCloud = async ({
|
||||||
|
createManySecretsRawFn,
|
||||||
|
updateManySecretsRawFn,
|
||||||
integration,
|
integration,
|
||||||
secrets,
|
secrets,
|
||||||
accessToken
|
accessToken,
|
||||||
|
integrationDAL
|
||||||
}: {
|
}: {
|
||||||
integration: TIntegrations;
|
createManySecretsRawFn: (params: TCreateManySecretsRawFn) => Promise<Array<TSecrets & { _id: string }>>;
|
||||||
secrets: Record<string, { value: string; comment?: string }>;
|
updateManySecretsRawFn: (params: TUpdateManySecretsRawFn) => Promise<Array<TSecrets & { _id: string }>>;
|
||||||
|
integration: TIntegrations & {
|
||||||
|
projectId: string;
|
||||||
|
environment: {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
slug: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
secrets: Record<string, { value: string; comment?: string } | null>;
|
||||||
accessToken: string;
|
accessToken: string;
|
||||||
|
integrationDAL: Pick<TIntegrationDALFactory, "updateById">;
|
||||||
}) => {
|
}) => {
|
||||||
// get secrets from Terraform Cloud
|
// get secrets from Terraform Cloud
|
||||||
const getSecretsRes = (
|
const terraformSecrets = (
|
||||||
await request.get<{ data: { attributes: { key: string; value: string }; id: string }[] }>(
|
await request.get<{ data: { attributes: { key: string; value: string }; id: string }[] }>(
|
||||||
`${IntegrationUrls.TERRAFORM_CLOUD_API_URL}/api/v2/workspaces/${integration.appId}/vars`,
|
`${IntegrationUrls.TERRAFORM_CLOUD_API_URL}/api/v2/workspaces/${integration.appId}/vars`,
|
||||||
{
|
{
|
||||||
@@ -2178,9 +2197,74 @@ const syncSecretsTerraformCloud = async ({
|
|||||||
{} as Record<string, { attributes: { key: string; value: string }; id: string }>
|
{} as Record<string, { attributes: { key: string; value: string }; id: string }>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const secretsToAdd: { [key: string]: string } = {};
|
||||||
|
const secretsToUpdate: { [key: string]: string } = {};
|
||||||
|
|
||||||
|
const metadata = z.record(z.any()).parse(integration.metadata);
|
||||||
|
|
||||||
|
Object.keys(terraformSecrets).forEach((key) => {
|
||||||
|
if (!integration.lastUsed) {
|
||||||
|
// first time using integration
|
||||||
|
// -> apply initial sync behavior
|
||||||
|
switch (metadata.initialSyncBehavior) {
|
||||||
|
case IntegrationInitialSyncBehavior.PREFER_TARGET: {
|
||||||
|
if (!(key in secrets)) {
|
||||||
|
secretsToAdd[key] = terraformSecrets[key].attributes.value;
|
||||||
|
} else if (secrets[key]?.value !== terraformSecrets[key].attributes.value) {
|
||||||
|
secretsToUpdate[key] = terraformSecrets[key].attributes.value;
|
||||||
|
}
|
||||||
|
secrets[key] = {
|
||||||
|
value: terraformSecrets[key].attributes.value
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case IntegrationInitialSyncBehavior.PREFER_SOURCE: {
|
||||||
|
if (!(key in secrets)) {
|
||||||
|
secrets[key] = {
|
||||||
|
value: terraformSecrets[key].attributes.value
|
||||||
|
};
|
||||||
|
secretsToAdd[key] = terraformSecrets[key].attributes.value;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (!(key in secrets)) secrets[key] = null;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (Object.keys(secretsToAdd).length) {
|
||||||
|
await createManySecretsRawFn({
|
||||||
|
projectId: integration.projectId,
|
||||||
|
environment: integration.environment.slug,
|
||||||
|
path: integration.secretPath,
|
||||||
|
secrets: Object.keys(secretsToAdd).map((key) => ({
|
||||||
|
secretName: key,
|
||||||
|
secretValue: secretsToAdd[key],
|
||||||
|
type: SecretType.Shared,
|
||||||
|
secretComment: ""
|
||||||
|
}))
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Object.keys(secretsToUpdate).length) {
|
||||||
|
await updateManySecretsRawFn({
|
||||||
|
projectId: integration.projectId,
|
||||||
|
environment: integration.environment.slug,
|
||||||
|
path: integration.secretPath,
|
||||||
|
secrets: Object.keys(secretsToUpdate).map((key) => ({
|
||||||
|
secretName: key,
|
||||||
|
secretValue: secretsToUpdate[key],
|
||||||
|
type: SecretType.Shared,
|
||||||
|
secretComment: ""
|
||||||
|
}))
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// create or update secrets on Terraform Cloud
|
// create or update secrets on Terraform Cloud
|
||||||
for await (const key of Object.keys(secrets)) {
|
for await (const key of Object.keys(secrets)) {
|
||||||
if (!(key in getSecretsRes)) {
|
if (!(key in terraformSecrets)) {
|
||||||
// case: secret does not exist in Terraform Cloud
|
// case: secret does not exist in Terraform Cloud
|
||||||
// -> add secret
|
// -> add secret
|
||||||
await request.post(
|
await request.post(
|
||||||
@@ -2190,7 +2274,7 @@ const syncSecretsTerraformCloud = async ({
|
|||||||
type: "vars",
|
type: "vars",
|
||||||
attributes: {
|
attributes: {
|
||||||
key,
|
key,
|
||||||
value: secrets[key].value,
|
value: secrets[key]?.value,
|
||||||
category: integration.targetService
|
category: integration.targetService
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2204,17 +2288,17 @@ const syncSecretsTerraformCloud = async ({
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
// case: secret exists in Terraform Cloud
|
// case: secret exists in Terraform Cloud
|
||||||
} else if (secrets[key].value !== getSecretsRes[key].attributes.value) {
|
} else if (secrets[key]?.value !== terraformSecrets[key].attributes.value) {
|
||||||
// -> update secret
|
// -> update secret
|
||||||
await request.patch(
|
await request.patch(
|
||||||
`${IntegrationUrls.TERRAFORM_CLOUD_API_URL}/api/v2/workspaces/${integration.appId}/vars/${getSecretsRes[key].id}`,
|
`${IntegrationUrls.TERRAFORM_CLOUD_API_URL}/api/v2/workspaces/${integration.appId}/vars/${terraformSecrets[key].id}`,
|
||||||
{
|
{
|
||||||
data: {
|
data: {
|
||||||
type: "vars",
|
type: "vars",
|
||||||
id: getSecretsRes[key].id,
|
id: terraformSecrets[key].id,
|
||||||
attributes: {
|
attributes: {
|
||||||
...getSecretsRes[key],
|
...terraformSecrets[key],
|
||||||
value: secrets[key].value
|
value: secrets[key]?.value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -2229,11 +2313,11 @@ const syncSecretsTerraformCloud = async ({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for await (const key of Object.keys(getSecretsRes)) {
|
for await (const key of Object.keys(terraformSecrets)) {
|
||||||
if (!(key in secrets)) {
|
if (!(key in secrets)) {
|
||||||
// case: delete secret
|
// case: delete secret
|
||||||
await request.delete(
|
await request.delete(
|
||||||
`${IntegrationUrls.TERRAFORM_CLOUD_API_URL}/api/v2/workspaces/${integration.appId}/vars/${getSecretsRes[key].id}`,
|
`${IntegrationUrls.TERRAFORM_CLOUD_API_URL}/api/v2/workspaces/${integration.appId}/vars/${terraformSecrets[key].id}`,
|
||||||
{
|
{
|
||||||
headers: {
|
headers: {
|
||||||
Authorization: `Bearer ${accessToken}`,
|
Authorization: `Bearer ${accessToken}`,
|
||||||
@@ -2244,6 +2328,10 @@ const syncSecretsTerraformCloud = async ({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await integrationDAL.updateById(integration.id, {
|
||||||
|
lastUsed: new Date()
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -3285,9 +3373,12 @@ export const syncIntegrationSecrets = async ({
|
|||||||
break;
|
break;
|
||||||
case Integrations.TERRAFORM_CLOUD:
|
case Integrations.TERRAFORM_CLOUD:
|
||||||
await syncSecretsTerraformCloud({
|
await syncSecretsTerraformCloud({
|
||||||
|
createManySecretsRawFn,
|
||||||
|
updateManySecretsRawFn,
|
||||||
integration,
|
integration,
|
||||||
secrets,
|
secrets,
|
||||||
accessToken
|
accessToken,
|
||||||
|
integrationDAL
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
case Integrations.HASHICORP_VAULT:
|
case Integrations.HASHICORP_VAULT:
|
||||||
|
|||||||
@@ -25,7 +25,8 @@ export type TCreateIntegrationDTO = {
|
|||||||
secretAWSTag?: {
|
secretAWSTag?: {
|
||||||
key: string;
|
key: string;
|
||||||
value: string;
|
value: string;
|
||||||
};
|
}[];
|
||||||
|
kmsKeyId?: string;
|
||||||
};
|
};
|
||||||
} & Omit<TProjectPermission, "projectId">;
|
} & Omit<TProjectPermission, "projectId">;
|
||||||
|
|
||||||
|
|||||||
@@ -9,9 +9,9 @@ A **user identity** (also known as **user**) represents a developer, admin, or a
|
|||||||
|
|
||||||
Users can be added manually (through Web UI) or programmatically (e.g., API) to [organizations](../organization) and [projects](../projects).
|
Users can be added manually (through Web UI) or programmatically (e.g., API) to [organizations](../organization) and [projects](../projects).
|
||||||
|
|
||||||
Upon being added to an organizaztion and projects, users assume a certain set of roles and permissions that represents their identity.
|
Upon being added to an organization and projects, users assume a certain set of roles and permissions that represents their identity.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
## Authentication methods
|
## Authentication methods
|
||||||
|
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 584 KiB After Width: | Height: | Size: 162 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 74 KiB |
@@ -30,7 +30,7 @@ Prerequisites:
|
|||||||
"ssm:DeleteParameter",
|
"ssm:DeleteParameter",
|
||||||
"ssm:GetParametersByPath",
|
"ssm:GetParametersByPath",
|
||||||
"ssm:DeleteParameters",
|
"ssm:DeleteParameters",
|
||||||
"ssm:AddTagsToResource"
|
"ssm:AddTagsToResource" // if you need to add tags to secrets
|
||||||
],
|
],
|
||||||
"Resource": "*"
|
"Resource": "*"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,13 +29,16 @@ Prerequisites:
|
|||||||
"secretsmanager:GetSecretValue",
|
"secretsmanager:GetSecretValue",
|
||||||
"secretsmanager:CreateSecret",
|
"secretsmanager:CreateSecret",
|
||||||
"secretsmanager:UpdateSecret",
|
"secretsmanager:UpdateSecret",
|
||||||
"secretsmanager:TagResource"
|
"secretsmanager:TagResource", // if you need to add tags to secrets
|
||||||
|
"kms:ListKeys", // if you need to specify the KMS key
|
||||||
|
"kms:ListAliases" // if you need to specify the KMS key
|
||||||
],
|
],
|
||||||
"Resource": "*"
|
"Resource": "*"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
</Step>
|
</Step>
|
||||||
<Step title="Authorize Infisical for AWS Secrets Manager">
|
<Step title="Authorize Infisical for AWS Secrets Manager">
|
||||||
Obtain a AWS access key ID and secret access key for your IAM user in IAM > Users > User > Security credentials > Access keys
|
Obtain a AWS access key ID and secret access key for your IAM user in IAM > Users > User > Security credentials > Access keys
|
||||||
@@ -43,7 +46,7 @@ Prerequisites:
|
|||||||

|

|
||||||

|

|
||||||

|

|
||||||
|
|
||||||
Navigate to your project's integrations tab in Infisical.
|
Navigate to your project's integrations tab in Infisical.
|
||||||
|
|
||||||

|

|
||||||
@@ -52,12 +55,6 @@ Prerequisites:
|
|||||||
|
|
||||||

|

|
||||||
|
|
||||||
<Info>
|
|
||||||
If this is your project's first cloud integration, then you'll have to grant
|
|
||||||
Infisical access to your project's environment variables. Although this step
|
|
||||||
breaks E2EE, it's necessary for Infisical to sync the environment variables to
|
|
||||||
the cloud platform.
|
|
||||||
</Info>
|
|
||||||
</Step>
|
</Step>
|
||||||
<Step title="Start integration">
|
<Step title="Start integration">
|
||||||
Select how you want to integration to work by specifying a number of parameters:
|
Select how you want to integration to work by specifying a number of parameters:
|
||||||
@@ -72,13 +69,23 @@ Prerequisites:
|
|||||||
The region that you want to integrate with in AWS Secrets Manager.
|
The region that you want to integrate with in AWS Secrets Manager.
|
||||||
</ParamField>
|
</ParamField>
|
||||||
<ParamField path="AWS SM Secret Name" type="string" required>
|
<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.
|
The secret name/path in AWS into which you want to sync the secrets from Infisical.
|
||||||
</ParamField>
|
</ParamField>
|
||||||
|
|
||||||
Then, press `Create Integration` to start syncing secrets to AWS Secrets Manager.
|
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
Optionally, you can add tags or specify the encryption key of all the secrets created via this integration:
|
||||||
|
|
||||||
|
<ParamField path="Secret Tag" type="string" optional>
|
||||||
|
The Key/Value of a tag that will be added to secrets in AWS. Please note that it is possible to add multiple tags via API.
|
||||||
|
</ParamField>
|
||||||
|
<ParamField path="Encryption Key" type="string" optional>
|
||||||
|
The alias/ID of the AWS KMS key used for encryption. Please note that key should be enabled in order to work and the IAM user should have access to it.
|
||||||
|
</ParamField>
|
||||||
|

|
||||||
|
|
||||||
|
Then, press `Create Integration` to start syncing secrets to AWS Secrets Manager.
|
||||||
|
|
||||||
<Info>
|
<Info>
|
||||||
Infisical currently syncs environment variables to AWS Secrets Manager as
|
Infisical currently syncs environment variables to AWS Secrets Manager as
|
||||||
key-value pairs under one secret. We're actively exploring ways to help users
|
key-value pairs under one secret. We're actively exploring ways to help users
|
||||||
@@ -88,5 +95,6 @@ Prerequisites:
|
|||||||
<Info>
|
<Info>
|
||||||
Please note that upon deleting secrets in Infisical, AWS Secrets Manager immediately makes the secrets inaccessible but only schedules them for deletion after at least 7 days.
|
Please note that upon deleting secrets in Infisical, AWS Secrets Manager immediately makes the secrets inaccessible but only schedules them for deletion after at least 7 days.
|
||||||
</Info>
|
</Info>
|
||||||
|
|
||||||
</Step>
|
</Step>
|
||||||
</Steps>
|
</Steps>
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import {
|
|||||||
Environment,
|
Environment,
|
||||||
HerokuPipelineCoupling,
|
HerokuPipelineCoupling,
|
||||||
IntegrationAuth,
|
IntegrationAuth,
|
||||||
|
KmsKey,
|
||||||
NorthflankSecretGroup,
|
NorthflankSecretGroup,
|
||||||
Org,
|
Org,
|
||||||
Project,
|
Project,
|
||||||
@@ -43,6 +44,14 @@ const integrationAuthKeys = {
|
|||||||
[{ integrationAuthId }, "integrationAuthGithubOrgs"] as const,
|
[{ integrationAuthId }, "integrationAuthGithubOrgs"] as const,
|
||||||
getIntegrationAuthGithubEnvs: (integrationAuthId: string, repoName: string, repoOwner: string) =>
|
getIntegrationAuthGithubEnvs: (integrationAuthId: string, repoName: string, repoOwner: string) =>
|
||||||
[{ integrationAuthId, repoName, repoOwner }, "integrationAuthGithubOrgs"] as const,
|
[{ integrationAuthId, repoName, repoOwner }, "integrationAuthGithubOrgs"] as const,
|
||||||
|
getIntegrationAuthAwsKmsKeys: ({
|
||||||
|
integrationAuthId,
|
||||||
|
region
|
||||||
|
}: {
|
||||||
|
integrationAuthId: string,
|
||||||
|
region: string
|
||||||
|
}) =>
|
||||||
|
[{ integrationAuthId, region }, "integrationAuthAwsKmsKeyIds"] as const,
|
||||||
getIntegrationAuthQoveryOrgs: (integrationAuthId: string) =>
|
getIntegrationAuthQoveryOrgs: (integrationAuthId: string) =>
|
||||||
[{ integrationAuthId }, "integrationAuthQoveryOrgs"] as const,
|
[{ integrationAuthId }, "integrationAuthQoveryOrgs"] as const,
|
||||||
getIntegrationAuthQoveryProjects: ({
|
getIntegrationAuthQoveryProjects: ({
|
||||||
@@ -217,6 +226,27 @@ const fetchIntegrationAuthQoveryOrgs = async (integrationAuthId: string) => {
|
|||||||
return orgs;
|
return orgs;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const fetchIntegrationAuthAwsKmsKeys = async ({
|
||||||
|
integrationAuthId,
|
||||||
|
region
|
||||||
|
}: {
|
||||||
|
integrationAuthId: string;
|
||||||
|
region: string;
|
||||||
|
}) => {
|
||||||
|
const {
|
||||||
|
data: { kmsKeys }
|
||||||
|
} = await apiRequest.get<{ kmsKeys: KmsKey[] }>(
|
||||||
|
`/api/v1/integration-auth/${integrationAuthId}/aws-secrets-manager/kms-keys`,
|
||||||
|
{
|
||||||
|
params: {
|
||||||
|
region
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return kmsKeys;
|
||||||
|
};
|
||||||
|
|
||||||
const fetchIntegrationAuthQoveryProjects = async ({
|
const fetchIntegrationAuthQoveryProjects = async ({
|
||||||
integrationAuthId,
|
integrationAuthId,
|
||||||
orgId
|
orgId
|
||||||
@@ -544,6 +574,27 @@ export const useGetIntegrationAuthQoveryOrgs = (integrationAuthId: string) => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const useGetIntegrationAuthAwsKmsKeys = ({
|
||||||
|
integrationAuthId,
|
||||||
|
region
|
||||||
|
}: {
|
||||||
|
integrationAuthId: string;
|
||||||
|
region: string;
|
||||||
|
}) => {
|
||||||
|
return useQuery({
|
||||||
|
queryKey: integrationAuthKeys.getIntegrationAuthAwsKmsKeys({
|
||||||
|
integrationAuthId,
|
||||||
|
region
|
||||||
|
}),
|
||||||
|
queryFn: () =>
|
||||||
|
fetchIntegrationAuthAwsKmsKeys({
|
||||||
|
integrationAuthId,
|
||||||
|
region
|
||||||
|
}),
|
||||||
|
enabled: true
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
export const useGetIntegrationAuthQoveryProjects = ({
|
export const useGetIntegrationAuthQoveryProjects = ({
|
||||||
integrationAuthId,
|
integrationAuthId,
|
||||||
orgId
|
orgId
|
||||||
|
|||||||
@@ -58,6 +58,11 @@ export type Project = {
|
|||||||
projectId: string;
|
projectId: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type KmsKey = {
|
||||||
|
id: string;
|
||||||
|
alias: string;
|
||||||
|
};
|
||||||
|
|
||||||
export type Service = {
|
export type Service = {
|
||||||
name: string;
|
name: string;
|
||||||
serviceId: string;
|
serviceId: string;
|
||||||
|
|||||||
@@ -66,7 +66,8 @@ export const useCreateIntegration = () => {
|
|||||||
secretAWSTag?: {
|
secretAWSTag?: {
|
||||||
key: string;
|
key: string;
|
||||||
value: string;
|
value: string;
|
||||||
};
|
}[];
|
||||||
|
kmsKeyId?: string;
|
||||||
};
|
};
|
||||||
}) => {
|
}) => {
|
||||||
const {
|
const {
|
||||||
|
|||||||
@@ -128,10 +128,10 @@ export default function AWSParameterStoreCreateIntegrationPage() {
|
|||||||
metadata: {
|
metadata: {
|
||||||
...(shouldTag
|
...(shouldTag
|
||||||
? {
|
? {
|
||||||
secretAWSTag: {
|
secretAWSTag: [{
|
||||||
key: tagKey,
|
key: tagKey,
|
||||||
value: tagValue
|
value: tagValue
|
||||||
}
|
}]
|
||||||
}
|
}
|
||||||
: {})
|
: {})
|
||||||
}
|
}
|
||||||
@@ -279,7 +279,7 @@ export default function AWSParameterStoreCreateIntegrationPage() {
|
|||||||
label="Tag Value"
|
label="Tag Value"
|
||||||
>
|
>
|
||||||
<Input
|
<Input
|
||||||
placeholder="managed-by"
|
placeholder="infisical"
|
||||||
value={tagValue}
|
value={tagValue}
|
||||||
onChange={(e) => setTagValue(e.target.value)}
|
onChange={(e) => setTagValue(e.target.value)}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import { motion } from "framer-motion";
|
|||||||
import queryString from "query-string";
|
import queryString from "query-string";
|
||||||
|
|
||||||
import { useCreateIntegration } from "@app/hooks/api";
|
import { useCreateIntegration } from "@app/hooks/api";
|
||||||
|
import { useGetIntegrationAuthAwsKmsKeys } from "@app/hooks/api/integrationAuth/queries";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
@@ -87,6 +88,7 @@ export default function AWSSecretManagerCreateIntegrationPage() {
|
|||||||
const [targetSecretNameErrorText, setTargetSecretNameErrorText] = useState("");
|
const [targetSecretNameErrorText, setTargetSecretNameErrorText] = useState("");
|
||||||
const [tagKey, setTagKey] = useState("");
|
const [tagKey, setTagKey] = useState("");
|
||||||
const [tagValue, setTagValue] = useState("");
|
const [tagValue, setTagValue] = useState("");
|
||||||
|
const [kmsKeyId, setKmsKeyId] = useState("");
|
||||||
|
|
||||||
// const [path, setPath] = useState('');
|
// const [path, setPath] = useState('');
|
||||||
// const [pathErrorText, setPathErrorText] = useState('');
|
// const [pathErrorText, setPathErrorText] = useState('');
|
||||||
@@ -94,6 +96,19 @@ export default function AWSSecretManagerCreateIntegrationPage() {
|
|||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
const [shouldTag, setShouldTag] = useState(false);
|
const [shouldTag, setShouldTag] = useState(false);
|
||||||
|
|
||||||
|
|
||||||
|
const { data: integrationAuthAwsKmsKeys, isLoading: isIntegrationAuthAwsKmsKeysLoading } =
|
||||||
|
useGetIntegrationAuthAwsKmsKeys({
|
||||||
|
integrationAuthId: String(integrationAuthId),
|
||||||
|
region: selectedAWSRegion
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (integrationAuthAwsKmsKeys) {
|
||||||
|
setKmsKeyId(String(integrationAuthAwsKmsKeys?.filter(key => key.alias === "alias/aws/secretsmanager")[0]?.id))
|
||||||
|
}
|
||||||
|
}, [integrationAuthAwsKmsKeys])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (workspace) {
|
if (workspace) {
|
||||||
setSelectedSourceEnvironment(workspace.environments[0].slug);
|
setSelectedSourceEnvironment(workspace.environments[0].slug);
|
||||||
@@ -127,12 +142,16 @@ export default function AWSSecretManagerCreateIntegrationPage() {
|
|||||||
metadata: {
|
metadata: {
|
||||||
...(shouldTag
|
...(shouldTag
|
||||||
? {
|
? {
|
||||||
secretAWSTag: {
|
secretAWSTag: [{
|
||||||
key: tagKey,
|
key: tagKey,
|
||||||
value: tagValue
|
value: tagValue
|
||||||
}
|
}]
|
||||||
}
|
}
|
||||||
: {})
|
: {}),
|
||||||
|
...((kmsKeyId && integrationAuthAwsKmsKeys?.filter(key => key.id === kmsKeyId)[0]?.alias !== "alias/aws/secretsmanager") ?
|
||||||
|
{
|
||||||
|
kmsKeyId
|
||||||
|
}: {})
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -145,7 +164,7 @@ export default function AWSSecretManagerCreateIntegrationPage() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return integrationAuth && workspace && selectedSourceEnvironment ? (
|
return (integrationAuth && workspace && selectedSourceEnvironment && !isIntegrationAuthAwsKmsKeysLoading) ? (
|
||||||
<div className="flex h-full w-full flex-col items-center justify-center">
|
<div className="flex h-full w-full flex-col items-center justify-center">
|
||||||
<Head>
|
<Head>
|
||||||
<title>Set Up AWS Secrets Manager Integration</title>
|
<title>Set Up AWS Secrets Manager Integration</title>
|
||||||
@@ -278,13 +297,38 @@ export default function AWSSecretManagerCreateIntegrationPage() {
|
|||||||
label="Tag Value"
|
label="Tag Value"
|
||||||
>
|
>
|
||||||
<Input
|
<Input
|
||||||
placeholder="managed-by"
|
placeholder="infisical"
|
||||||
value={tagValue}
|
value={tagValue}
|
||||||
onChange={(e) => setTagValue(e.target.value)}
|
onChange={(e) => setTagValue(e.target.value)}
|
||||||
/>
|
/>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
<FormControl label="Encryption Key" className="mt-4">
|
||||||
|
<Select
|
||||||
|
value={kmsKeyId}
|
||||||
|
onValueChange={(e) => {
|
||||||
|
setKmsKeyId(e)
|
||||||
|
}}
|
||||||
|
className="w-full border border-mineshaft-500"
|
||||||
|
>
|
||||||
|
{integrationAuthAwsKmsKeys?.length ? (
|
||||||
|
integrationAuthAwsKmsKeys.map((key) => {
|
||||||
|
return (
|
||||||
|
<SelectItem
|
||||||
|
value={key.id as string}
|
||||||
|
key={`repo-id-${key.id}`}
|
||||||
|
className="w-[28.4rem] text-sm"
|
||||||
|
>
|
||||||
|
{key.alias}
|
||||||
|
</SelectItem>
|
||||||
|
);
|
||||||
|
})
|
||||||
|
) : (
|
||||||
|
<div />
|
||||||
|
)}
|
||||||
|
</Select>
|
||||||
|
</FormControl>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
@@ -317,7 +361,7 @@ export default function AWSSecretManagerCreateIntegrationPage() {
|
|||||||
<title>Set Up AWS Secrets Manager Integration</title>
|
<title>Set Up AWS Secrets Manager Integration</title>
|
||||||
<link rel="icon" href="/infisical.ico" />
|
<link rel="icon" href="/infisical.ico" />
|
||||||
</Head>
|
</Head>
|
||||||
{isintegrationAuthLoading ? (
|
{(isintegrationAuthLoading || isIntegrationAuthAwsKmsKeysLoading) ? (
|
||||||
<img
|
<img
|
||||||
src="/images/loading/loading.gif"
|
src="/images/loading/loading.gif"
|
||||||
height={70}
|
height={70}
|
||||||
|
|||||||
@@ -1,5 +1,10 @@
|
|||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
|
import Head from "next/head";
|
||||||
|
import Image from "next/image";
|
||||||
|
import Link from "next/link";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
|
import { faArrowUpRightFromSquare, faBookOpen } from "@fortawesome/free-solid-svg-icons";
|
||||||
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
|
|
||||||
import { useSaveIntegrationAccessToken } from "@app/hooks/api";
|
import { useSaveIntegrationAccessToken } from "@app/hooks/api";
|
||||||
|
|
||||||
@@ -49,12 +54,56 @@ export default function TerraformCloudCreateIntegrationPage() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex h-full w-full items-center justify-center">
|
<div className="flex h-full w-full items-center justify-center">
|
||||||
<Card className="max-w-md rounded-md p-8">
|
<Head>
|
||||||
<CardTitle className="text-center">Terraform Cloud Integration</CardTitle>
|
<title>Authorize Terraform Cloud Integration</title>
|
||||||
|
<link rel="icon" href="/infisical.ico" />
|
||||||
|
</Head>
|
||||||
|
<Card className="max-w-lg rounded-md border border-mineshaft-600">
|
||||||
|
<CardTitle
|
||||||
|
className="px-6 text-left text-xl"
|
||||||
|
subTitle="After adding the details below, you will be prompted to set up an integration for a particular Infisical project and environment."
|
||||||
|
>
|
||||||
|
<div className="flex flex-row items-center">
|
||||||
|
<div className="inline flex items-center">
|
||||||
|
<Image
|
||||||
|
src="/images/integrations/Terraform.png"
|
||||||
|
height={35}
|
||||||
|
width={35}
|
||||||
|
alt="Terraform logo"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<span className="ml-1.5">Terraform Cloud Integration </span>
|
||||||
|
<Link href="https://infisical.com/docs/integrations/cloud/terraform-cloud" passHref>
|
||||||
|
<a target="_blank" rel="noopener noreferrer">
|
||||||
|
<div className="ml-2 mb-1 inline-block cursor-default rounded-md bg-yellow/20 px-1.5 pb-[0.03rem] pt-[0.04rem] text-sm text-yellow opacity-80 hover:opacity-100">
|
||||||
|
<FontAwesomeIcon icon={faBookOpen} className="mr-1.5" />
|
||||||
|
Docs
|
||||||
|
<FontAwesomeIcon
|
||||||
|
icon={faArrowUpRightFromSquare}
|
||||||
|
className="ml-1.5 mb-[0.07rem] text-xxs"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</CardTitle>
|
||||||
|
<FormControl
|
||||||
|
label="Terraform Cloud Workspace ID"
|
||||||
|
errorText={workspacesIdErrorText}
|
||||||
|
isError={workspacesIdErrorText !== "" ?? false}
|
||||||
|
className="px-6"
|
||||||
|
>
|
||||||
|
<Input
|
||||||
|
placeholder="Workspace Id"
|
||||||
|
value={workspacesId}
|
||||||
|
onChange={(e) => setWorkSpacesId(e.target.value)}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
<FormControl
|
<FormControl
|
||||||
label="Terraform Cloud API Token"
|
label="Terraform Cloud API Token"
|
||||||
errorText={apiKeyErrorText}
|
errorText={apiKeyErrorText}
|
||||||
isError={apiKeyErrorText !== "" ?? false}
|
isError={apiKeyErrorText !== "" ?? false}
|
||||||
|
className="px-6"
|
||||||
>
|
>
|
||||||
<Input
|
<Input
|
||||||
placeholder="API Token"
|
placeholder="API Token"
|
||||||
@@ -64,21 +113,11 @@ export default function TerraformCloudCreateIntegrationPage() {
|
|||||||
onChange={(e) => setApiKey(e.target.value)}
|
onChange={(e) => setApiKey(e.target.value)}
|
||||||
/>
|
/>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormControl
|
|
||||||
label="Terraform Cloud Workspace ID"
|
|
||||||
errorText={workspacesIdErrorText}
|
|
||||||
isError={workspacesIdErrorText !== "" ?? false}
|
|
||||||
>
|
|
||||||
<Input
|
|
||||||
placeholder="Workspace Id"
|
|
||||||
value={workspacesId}
|
|
||||||
onChange={(e) => setWorkSpacesId(e.target.value)}
|
|
||||||
/>
|
|
||||||
</FormControl>
|
|
||||||
<Button
|
<Button
|
||||||
onClick={handleButtonClick}
|
onClick={handleButtonClick}
|
||||||
color="mineshaft"
|
colorSchema="primary"
|
||||||
className="mt-4"
|
variant="outline_bg"
|
||||||
|
className="mb-6 mt-2 ml-auto mr-6 w-min"
|
||||||
isLoading={isLoading}
|
isLoading={isLoading}
|
||||||
>
|
>
|
||||||
Connect to Terraform Cloud
|
Connect to Terraform Cloud
|
||||||
|
|||||||
@@ -1,8 +1,14 @@
|
|||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
|
import Head from "next/head";
|
||||||
|
import Image from "next/image";
|
||||||
|
import Link from "next/link";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
|
import { faArrowUpRightFromSquare, faBookOpen } from "@fortawesome/free-solid-svg-icons";
|
||||||
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
import queryString from "query-string";
|
import queryString from "query-string";
|
||||||
|
|
||||||
import { useCreateIntegration } from "@app/hooks/api";
|
import { useCreateIntegration } from "@app/hooks/api";
|
||||||
|
import { IntegrationSyncBehavior } from "@app/hooks/api/integrations/types";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
@@ -19,6 +25,15 @@ import {
|
|||||||
} from "../../../hooks/api/integrationAuth";
|
} from "../../../hooks/api/integrationAuth";
|
||||||
import { useGetWorkspaceById } from "../../../hooks/api/workspace";
|
import { useGetWorkspaceById } from "../../../hooks/api/workspace";
|
||||||
|
|
||||||
|
const initialSyncBehaviors = [
|
||||||
|
{
|
||||||
|
label: "No Import - Overwrite all values in Terraform Cloud",
|
||||||
|
value: IntegrationSyncBehavior.OVERWRITE_TARGET
|
||||||
|
},
|
||||||
|
{ label: "Import non-sensitive - Prefer values from Terraform Cloud", value: IntegrationSyncBehavior.PREFER_TARGET },
|
||||||
|
{ label: "Import non-sensitive - Prefer values from Infisical", value: IntegrationSyncBehavior.PREFER_SOURCE }
|
||||||
|
];
|
||||||
|
|
||||||
const variableTypes = [{ name: "env" }, { name: "terraform" }];
|
const variableTypes = [{ name: "env" }, { name: "terraform" }];
|
||||||
|
|
||||||
export default function TerraformCloudCreateIntegrationPage() {
|
export default function TerraformCloudCreateIntegrationPage() {
|
||||||
@@ -39,6 +54,7 @@ export default function TerraformCloudCreateIntegrationPage() {
|
|||||||
const [variableType, setVariableType] = useState("");
|
const [variableType, setVariableType] = useState("");
|
||||||
const [variableTypeErrorText, setVariableTypeErrorText] = useState("");
|
const [variableTypeErrorText, setVariableTypeErrorText] = useState("");
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
|
const [initialSyncBehavior, setInitialSyncBehavior] = useState("prefer-source")
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (workspace) {
|
if (workspace) {
|
||||||
@@ -78,7 +94,10 @@ export default function TerraformCloudCreateIntegrationPage() {
|
|||||||
)?.appId,
|
)?.appId,
|
||||||
sourceEnvironment: selectedSourceEnvironment,
|
sourceEnvironment: selectedSourceEnvironment,
|
||||||
targetService: variableType,
|
targetService: variableType,
|
||||||
secretPath
|
secretPath,
|
||||||
|
metadata: {
|
||||||
|
initialSyncBehavior
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
@@ -95,9 +114,40 @@ export default function TerraformCloudCreateIntegrationPage() {
|
|||||||
integrationAuthApps &&
|
integrationAuthApps &&
|
||||||
targetApp ? (
|
targetApp ? (
|
||||||
<div className="flex h-full w-full items-center justify-center">
|
<div className="flex h-full w-full items-center justify-center">
|
||||||
<Card className="max-w-md rounded-md p-8">
|
<Head>
|
||||||
<CardTitle className="text-center">Terraform Cloud Integration</CardTitle>
|
<title>Create Terraform Cloud Integration</title>
|
||||||
<FormControl label="Project Environment" className="mt-4">
|
<link rel="icon" href="/infisical.ico" />
|
||||||
|
</Head>
|
||||||
|
<Card className="max-w-lg rounded-md border border-mineshaft-600">
|
||||||
|
<CardTitle
|
||||||
|
className="px-6 text-left text-xl"
|
||||||
|
subTitle="Specify the encironment and path within Infisical that you want to push to which project in Terraform."
|
||||||
|
>
|
||||||
|
<div className="flex flex-row items-center">
|
||||||
|
<div className="inline flex items-center">
|
||||||
|
<Image
|
||||||
|
src="/images/integrations/Terraform.png"
|
||||||
|
height={35}
|
||||||
|
width={35}
|
||||||
|
alt="Terraform logo"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<span className="ml-1.5">Terraform Cloud Integration </span>
|
||||||
|
<Link href="https://infisical.com/docs/integrations/cloud/terraform-cloud" passHref>
|
||||||
|
<a target="_blank" rel="noopener noreferrer">
|
||||||
|
<div className="ml-2 mb-1 inline-block cursor-default rounded-md bg-yellow/20 px-1.5 pb-[0.03rem] pt-[0.04rem] text-sm text-yellow opacity-80 hover:opacity-100">
|
||||||
|
<FontAwesomeIcon icon={faBookOpen} className="mr-1.5" />
|
||||||
|
Docs
|
||||||
|
<FontAwesomeIcon
|
||||||
|
icon={faArrowUpRightFromSquare}
|
||||||
|
className="ml-1.5 mb-[0.07rem] text-xxs"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</CardTitle>
|
||||||
|
<FormControl label="Project Environment" className="px-6">
|
||||||
<Select
|
<Select
|
||||||
value={selectedSourceEnvironment}
|
value={selectedSourceEnvironment}
|
||||||
onValueChange={(val) => setSelectedSourceEnvironment(val)}
|
onValueChange={(val) => setSelectedSourceEnvironment(val)}
|
||||||
@@ -113,7 +163,7 @@ export default function TerraformCloudCreateIntegrationPage() {
|
|||||||
))}
|
))}
|
||||||
</Select>
|
</Select>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormControl label="Secrets Path">
|
<FormControl label="Secrets Path" className="px-6">
|
||||||
<Input
|
<Input
|
||||||
value={secretPath}
|
value={secretPath}
|
||||||
onChange={(evt) => setSecretPath(evt.target.value)}
|
onChange={(evt) => setSecretPath(evt.target.value)}
|
||||||
@@ -122,7 +172,7 @@ export default function TerraformCloudCreateIntegrationPage() {
|
|||||||
</FormControl>
|
</FormControl>
|
||||||
<FormControl
|
<FormControl
|
||||||
label="Category"
|
label="Category"
|
||||||
className="mt-4"
|
className="px-6"
|
||||||
errorText={variableTypeErrorText}
|
errorText={variableTypeErrorText}
|
||||||
isError={variableTypeErrorText !== "" ?? false}
|
isError={variableTypeErrorText !== "" ?? false}
|
||||||
>
|
>
|
||||||
@@ -138,7 +188,7 @@ export default function TerraformCloudCreateIntegrationPage() {
|
|||||||
))}
|
))}
|
||||||
</Select>
|
</Select>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormControl label="Terraform Cloud Project" className="mt-4">
|
<FormControl label="Terraform Cloud Project" className="px-6">
|
||||||
<Select
|
<Select
|
||||||
value={targetApp}
|
value={targetApp}
|
||||||
onValueChange={(val) => setTargetApp(val)}
|
onValueChange={(val) => setTargetApp(val)}
|
||||||
@@ -161,10 +211,22 @@ export default function TerraformCloudCreateIntegrationPage() {
|
|||||||
)}
|
)}
|
||||||
</Select>
|
</Select>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
|
<FormControl label="Initial Sync Behavior" className="px-6">
|
||||||
|
<Select value={initialSyncBehavior} onValueChange={(e) => setInitialSyncBehavior(e)} className="w-full border border-mineshaft-600">
|
||||||
|
{initialSyncBehaviors.map((b) => {
|
||||||
|
return (
|
||||||
|
<SelectItem value={b.value} key={`sync-behavior-${b.value}`}>
|
||||||
|
{b.label}
|
||||||
|
</SelectItem>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</Select>
|
||||||
|
</FormControl>
|
||||||
<Button
|
<Button
|
||||||
onClick={handleButtonClick}
|
onClick={handleButtonClick}
|
||||||
color="mineshaft"
|
colorSchema="primary"
|
||||||
className="mt-4"
|
variant="outline_bg"
|
||||||
|
className="mb-6 mt-2 ml-auto mr-6 w-min"
|
||||||
isLoading={isLoading}
|
isLoading={isLoading}
|
||||||
isDisabled={integrationAuthApps.length === 0}
|
isDisabled={integrationAuthApps.length === 0}
|
||||||
>
|
>
|
||||||
|
|||||||
+13
-1
@@ -74,7 +74,7 @@ export const IntegrationsSection = ({
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{!isLoading && isBotActive && (
|
{!isLoading && isBotActive && (
|
||||||
<div className="flex flex-col space-y-4 p-6 pt-0">
|
<div className="flex flex-col min-w-max space-y-4 p-6 pt-0">
|
||||||
{integrations?.map((integration) => (
|
{integrations?.map((integration) => (
|
||||||
<div
|
<div
|
||||||
className="max-w-8xl flex justify-between rounded-md border border-mineshaft-600 bg-mineshaft-800 p-3"
|
className="max-w-8xl flex justify-between rounded-md border border-mineshaft-600 bg-mineshaft-800 p-3"
|
||||||
@@ -128,6 +128,9 @@ export const IntegrationsSection = ({
|
|||||||
<FormLabel
|
<FormLabel
|
||||||
label={
|
label={
|
||||||
(integration.integration === "qovery" && integration?.scope) ||
|
(integration.integration === "qovery" && integration?.scope) ||
|
||||||
|
(integration.integration === "aws-secret-manager" && "Secret") ||
|
||||||
|
(integration.integration === "aws-parameter-store" && "Path") ||
|
||||||
|
(integration?.integration === "terraform-cloud" && "Project") ||
|
||||||
(integration?.scope === "github-org" && "Organization") ||
|
(integration?.scope === "github-org" && "Organization") ||
|
||||||
(["github-repo", "github-env"].includes(integration?.scope as string) &&
|
(["github-repo", "github-env"].includes(integration?.scope as string) &&
|
||||||
"Repository") ||
|
"Repository") ||
|
||||||
@@ -138,6 +141,7 @@ export const IntegrationsSection = ({
|
|||||||
{(integration.integration === "hashicorp-vault" &&
|
{(integration.integration === "hashicorp-vault" &&
|
||||||
`${integration.app} - path: ${integration.path}`) ||
|
`${integration.app} - path: ${integration.path}`) ||
|
||||||
(integration.scope === "github-org" && `${integration.owner}`) ||
|
(integration.scope === "github-org" && `${integration.owner}`) ||
|
||||||
|
(integration.integration === "aws-parameter-store" && `${integration.path}`) ||
|
||||||
(integration.scope?.startsWith("github-") &&
|
(integration.scope?.startsWith("github-") &&
|
||||||
`${integration.owner}/${integration.app}`) ||
|
`${integration.owner}/${integration.app}`) ||
|
||||||
integration.app}
|
integration.app}
|
||||||
@@ -165,6 +169,14 @@ export const IntegrationsSection = ({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
{integration.integration === "terraform-cloud" && integration.targetService && (
|
||||||
|
<div className="ml-2">
|
||||||
|
<FormLabel label="Category" />
|
||||||
|
<div className="rounded-md border border-mineshaft-700 bg-mineshaft-900 px-3 py-2 font-inter text-sm text-bunker-200">
|
||||||
|
{integration.targetService}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
{(integration.integration === "checkly" ||
|
{(integration.integration === "checkly" ||
|
||||||
integration.integration === "github") && (
|
integration.integration === "github") && (
|
||||||
<div className="ml-2">
|
<div className="ml-2">
|
||||||
|
|||||||
@@ -8,5 +8,6 @@ export type Metadata = {
|
|||||||
secretAWSTag?: {
|
secretAWSTag?: {
|
||||||
key: string;
|
key: string;
|
||||||
value: string;
|
value: string;
|
||||||
}
|
}[]
|
||||||
|
kmsKeyId?: string;
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user