diff --git a/backend/src/services/integration-auth/integration-auth-service.ts b/backend/src/services/integration-auth/integration-auth-service.ts index 3d42943a6e..778589de86 100644 --- a/backend/src/services/integration-auth/integration-auth-service.ts +++ b/backend/src/services/integration-auth/integration-auth-service.ts @@ -572,14 +572,14 @@ export const integrationAuthServiceFactory = ({ 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")) { + if (!keyAlias?.AliasName?.includes("alias/aws/")) { return { id: String(key.KeyId), alias: String(keyAlias?.AliasName || key.KeyId) }; } return { id: "null", alias: "null" }; }) .filter((elem) => elem.id !== "null"); - return response; + return [...response, { id: "null", alias: "default" }]; }; const getQoveryProjects = async ({ diff --git a/backend/src/services/integration-auth/integration-sync-secret.ts b/backend/src/services/integration-auth/integration-sync-secret.ts index 5fc11c0387..de576dc992 100644 --- a/backend/src/services/integration-auth/integration-sync-secret.ts +++ b/backend/src/services/integration-auth/integration-sync-secret.ts @@ -477,24 +477,29 @@ const syncSecretsAWSParameterStore = async ({ }), {} as Record ); - // Identify secrets to create await Promise.all( Object.keys(secrets).map(async (key) => { if (!(key in awsParameterStoreSecretsObj)) { // case: secret does not exist in AWS parameter store // -> create secret - await ssm - .putParameter({ - Name: `${integration.path}${key}`, - Type: "SecureString", - Value: secrets[key].value, - // Overwrite: true, - Tags: metadata.secretAWSTag - ? metadata.secretAWSTag.map((tag: { key: string; value: string }) => ({ Key: tag.key, Value: tag.value })) - : [] - }) - .promise(); + if (secrets[key].value) { + await ssm + .putParameter({ + Name: `${integration.path}${key}`, + Type: "SecureString", + Value: secrets[key].value, + KeyId: metadata.kmsKeyId ? metadata.kmsKeyId : undefined, + // Overwrite: true, + Tags: metadata.secretAWSTag + ? metadata.secretAWSTag.map((tag: { key: string; value: string }) => ({ + Key: tag.key, + Value: tag.value + })) + : [] + }) + .promise(); + } // case: secret exists in AWS parameter store } else if (awsParameterStoreSecretsObj[key].Value !== secrets[key].value) { // case: secret value doesn't match one in AWS parameter store diff --git a/docs/images/integrations/aws/integrations-aws-parameter-store-auth.png b/docs/images/integrations/aws/integrations-aws-parameter-store-auth.png index 7891e636b1..14d54c9a6c 100644 Binary files a/docs/images/integrations/aws/integrations-aws-parameter-store-auth.png and b/docs/images/integrations/aws/integrations-aws-parameter-store-auth.png differ diff --git a/docs/images/integrations/aws/integrations-aws-parameter-store-create.png b/docs/images/integrations/aws/integrations-aws-parameter-store-create.png index 7c51eb2b2e..a168925d6e 100644 Binary files a/docs/images/integrations/aws/integrations-aws-parameter-store-create.png and b/docs/images/integrations/aws/integrations-aws-parameter-store-create.png differ diff --git a/docs/integrations/cloud/aws-parameter-store.mdx b/docs/integrations/cloud/aws-parameter-store.mdx index c872e39f0c..fdad8b6384 100644 --- a/docs/integrations/cloud/aws-parameter-store.mdx +++ b/docs/integrations/cloud/aws-parameter-store.mdx @@ -30,13 +30,18 @@ Prerequisites: "ssm:DeleteParameter", "ssm:GetParametersByPath", "ssm:DeleteParameters", - "ssm:AddTagsToResource" // if you need to add tags to secrets + "ssm:AddTagsToResource", // 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 + "kms:Encrypt", // if you need to specify the KMS key + "kms:Decrypt" // if you need to specify the KMS key ], "Resource": "*" } ] } ``` + Obtain a AWS access key ID and secret access key for your IAM user in IAM > Users > User > Security credentials > Access keys @@ -44,7 +49,7 @@ Prerequisites: ![access key 1](../../images/integrations/aws/integrations-aws-access-key-1.png) ![access key 2](../../images/integrations/aws/integrations-aws-access-key-2.png) ![access key 3](../../images/integrations/aws/integrations-aws-access-key-3.png) - + Navigate to your project's integrations tab in Infisical. ![integrations](../../images/integrations.png) @@ -59,6 +64,7 @@ Prerequisites: breaks E2EE, it's necessary for Infisical to sync the environment variables to the cloud platform. + Select which Infisical environment secrets you want to sync to which AWS Parameter Store region and indicate the path for your secrets. Then, press create integration to start syncing secrets to AWS Parameter Store. @@ -72,6 +78,6 @@ Prerequisites: secret like `TEST` to be stored as `/[project_name]/[environment]/TEST` in AWS Parameter Store. + - diff --git a/frontend/public/images/integrations/Agent.png b/frontend/public/images/integrations/Agent.png new file mode 100644 index 0000000000..662c1b2734 Binary files /dev/null and b/frontend/public/images/integrations/Agent.png differ diff --git a/frontend/public/images/integrations/Ansible.png b/frontend/public/images/integrations/Ansible.png new file mode 100644 index 0000000000..925e48ca6f Binary files /dev/null and b/frontend/public/images/integrations/Ansible.png differ diff --git a/frontend/public/images/integrations/ECS.png b/frontend/public/images/integrations/ECS.png new file mode 100644 index 0000000000..92e0d23ca0 Binary files /dev/null and b/frontend/public/images/integrations/ECS.png differ diff --git a/frontend/public/images/integrations/Jenkins.png b/frontend/public/images/integrations/Jenkins.png new file mode 100644 index 0000000000..9df069a185 Binary files /dev/null and b/frontend/public/images/integrations/Jenkins.png differ diff --git a/frontend/public/json/frameworkIntegrations.json b/frontend/public/json/frameworkIntegrations.json index 1a6ff94f29..c96c0a0b40 100644 --- a/frontend/public/json/frameworkIntegrations.json +++ b/frontend/public/json/frameworkIntegrations.json @@ -1,28 +1,4 @@ [ - { - "name": "Docker", - "slug": "docker", - "image": "Docker", - "docsLink": "https://infisical.com/docs/integrations/platforms/docker" - }, - { - "name": "Docker Compose", - "slug": "docker-compose", - "image": "Docker Compose", - "docsLink": "https://infisical.com/docs/integrations/platforms/docker-compose" - }, - { - "name": "Kubernetes", - "slug": "kubernetes", - "image": "Kubernetes", - "docsLink": "https://infisical.com/docs/integrations/platforms/kubernetes" - }, - { - "name": "Terraform", - "slug": "terraform", - "image": "Terraform", - "docsLink": "https://infisical.com/docs/integrations/frameworks/terraform" - }, { "name": "React", "slug": "react", diff --git a/frontend/public/json/infrastructureIntegrations.json b/frontend/public/json/infrastructureIntegrations.json new file mode 100644 index 0000000000..657cd17eb0 --- /dev/null +++ b/frontend/public/json/infrastructureIntegrations.json @@ -0,0 +1,50 @@ +[ + { + "name": "Docker", + "slug": "docker", + "image": "Docker", + "docsLink": "https://infisical.com/docs/integrations/platforms/docker" + }, + { + "name": "Docker Compose", + "slug": "docker-compose", + "image": "Docker Compose", + "docsLink": "https://infisical.com/docs/integrations/platforms/docker-compose" + }, + { + "name": "Kubernetes", + "slug": "kubernetes", + "image": "Kubernetes", + "docsLink": "https://infisical.com/docs/integrations/platforms/kubernetes" + }, + { + "name": "Terraform", + "slug": "terraform", + "image": "Terraform", + "docsLink": "https://infisical.com/docs/integrations/frameworks/terraform" + }, + { + "name": "Jenkins", + "slug": "jenkins", + "image": "Jenkins", + "docsLink": "https://infisical.com/docs/integrations/cicd/jenkins" + }, + { + "name": "Infisical Agent", + "slug": "agent", + "image": "Agent", + "docsLink": "https://infisical.com/docs/integrations/platforms/infisical-agent" + }, + { + "name": "Amazon ECS", + "slug": "ecs", + "image": "ECS", + "docsLink": "https://infisical.com/docs/integrations/platforms/ecs-with-agent" + }, + { + "name": "Ansible", + "slug": "ansible", + "image": "Ansible", + "docsLink": "https://infisical.com/docs/integrations/platforms/ansible" + } +] \ No newline at end of file diff --git a/frontend/public/locales/en/translations.json b/frontend/public/locales/en/translations.json index 8b04872923..b1465f04da 100644 --- a/frontend/public/locales/en/translations.json +++ b/frontend/public/locales/en/translations.json @@ -120,7 +120,7 @@ "available": "Platform & Cloud Integrations", "available-text1": "Click on the integration you want to connect. This will let your environment variables flow automatically into selected third-party services.", "available-text2": "Note: during an integration with Heroku, for security reasons, it is impossible to maintain end-to-end encryption. In theory, this lets Infisical decrypt yor environment variables. In practice, we can assure you that this will never be done, and it allows us to protect your secrets from bad actors online. The core Infisical service will always stay end-to-end encrypted. With any questions, reach out support@infisical.com.", - "cloud-integrations": "Cloud Integrations", + "cloud-integrations": "Native Integrations", "framework-integrations": "Framework Integrations", "click-to-start": "Click on an integration to begin syncing secrets to it.", "click-to-setup": "Click on a framework to get the setup instructions.", diff --git a/frontend/src/pages/integrations/[id].tsx b/frontend/src/pages/integrations/[id].tsx index 143e3ad2fe..8cc1baca35 100644 --- a/frontend/src/pages/integrations/[id].tsx +++ b/frontend/src/pages/integrations/[id].tsx @@ -1,14 +1,16 @@ import { useTranslation } from "react-i18next"; import Head from "next/head"; import frameworkIntegrationOptions from "public/json/frameworkIntegrations.json"; +import infrastructureIntegrationOptions from "public/json/infrastructureIntegrations.json"; import { IntegrationsPage } from "@app/views/IntegrationsPage"; type Props = { frameworkIntegrations: typeof frameworkIntegrationOptions; + infrastructureIntegrations: typeof infrastructureIntegrationOptions; }; -const Integration = ({ frameworkIntegrations }: Props) => { +const Integration = ({ frameworkIntegrations, infrastructureIntegrations }: Props) => { const { t } = useTranslation(); return ( @@ -20,7 +22,7 @@ const Integration = ({ frameworkIntegrations }: Props) => { - + ); }; @@ -28,7 +30,8 @@ const Integration = ({ frameworkIntegrations }: Props) => { export const getStaticProps = () => { return { props: { - frameworkIntegrations: frameworkIntegrationOptions + frameworkIntegrations: frameworkIntegrationOptions, + infrastructureIntegrations: infrastructureIntegrationOptions } }; }; diff --git a/frontend/src/pages/integrations/aws-parameter-store/create.tsx b/frontend/src/pages/integrations/aws-parameter-store/create.tsx index 92e4662a90..9f52347ce2 100644 --- a/frontend/src/pages/integrations/aws-parameter-store/create.tsx +++ b/frontend/src/pages/integrations/aws-parameter-store/create.tsx @@ -14,6 +14,7 @@ import { motion } from "framer-motion"; import queryString from "query-string"; import { useCreateIntegration } from "@app/hooks/api"; +import { useGetIntegrationAuthAwsKmsKeys } from "@app/hooks/api/integrationAuth/queries"; import { Button, @@ -90,6 +91,7 @@ export default function AWSParameterStoreCreateIntegrationPage() { const [shouldTag, setShouldTag] = useState(false); const [tagKey, setTagKey] = useState(""); const [tagValue, setTagValue] = useState(""); + const [kmsKeyId, setKmsKeyId] = useState(""); useEffect(() => { if (workspace) { @@ -98,6 +100,19 @@ export default function AWSParameterStoreCreateIntegrationPage() { } }, [workspace]); + + const { data: integrationAuthAwsKmsKeys, isLoading: isIntegrationAuthAwsKmsKeysLoading } = + useGetIntegrationAuthAwsKmsKeys({ + integrationAuthId: String(integrationAuthId), + region: selectedAWSRegion + }); + + useEffect(() => { + if (integrationAuthAwsKmsKeys) { + setKmsKeyId(String(integrationAuthAwsKmsKeys?.filter(key => key.alias === "default")[0]?.id)) + } + }, [integrationAuthAwsKmsKeys]) + const isValidAWSParameterStorePath = (awsStorePath: string) => { const pattern = /^\/([\w-]+\/)*[\w-]+\/$/; return pattern.test(awsStorePath) && awsStorePath.length <= 2048; @@ -133,7 +148,11 @@ export default function AWSParameterStoreCreateIntegrationPage() { value: tagValue }] } - : {}) + : {}), + ...((kmsKeyId && integrationAuthAwsKmsKeys?.filter(key => key.id === kmsKeyId)[0]?.alias !== "default") ? + { + kmsKeyId + }: {}) } }); @@ -146,7 +165,7 @@ export default function AWSParameterStoreCreateIntegrationPage() { } }; - return integrationAuth && workspace && selectedSourceEnvironment ? ( + return (integrationAuth && workspace && selectedSourceEnvironment && !isIntegrationAuthAwsKmsKeysLoading) ? (
Set Up AWS Parameter Integration @@ -286,6 +305,31 @@ export default function AWSParameterStoreCreateIntegrationPage() {
)} + + + @@ -318,7 +362,7 @@ export default function AWSParameterStoreCreateIntegrationPage() { Set Up AWS Parameter Store Integration - {isintegrationAuthLoading ? ( + {(isintegrationAuthLoading || isIntegrationAuthAwsKmsKeysLoading) ? ( key.id === kmsKeyId)[0]?.alias !== "alias/aws/secretsmanager") ? + ...((kmsKeyId && integrationAuthAwsKmsKeys?.filter(key => key.id === kmsKeyId)[0]?.alias !== "default") ? { kmsKeyId }: {}) diff --git a/frontend/src/views/IntegrationsPage/IntegrationsPage.tsx b/frontend/src/views/IntegrationsPage/IntegrationsPage.tsx index 9f51ac1375..73c443ca40 100644 --- a/frontend/src/views/IntegrationsPage/IntegrationsPage.tsx +++ b/frontend/src/views/IntegrationsPage/IntegrationsPage.tsx @@ -21,15 +21,17 @@ import { ProjectVersion } from "@app/hooks/api/workspace/types"; import { CloudIntegrationSection } from "./components/CloudIntegrationSection"; import { FrameworkIntegrationSection } from "./components/FrameworkIntegrationSection"; +import { InfrastructureIntegrationSection } from "./components/InfrastructureIntegrationSection/InfrastructureIntegrationSection"; import { IntegrationsSection } from "./components/IntegrationsSection"; import { generateBotKey, redirectForProviderAuth } from "./IntegrationPage.utils"; type Props = { frameworkIntegrations: Array<{ name: string; slug: string; image: string; docsLink: string }>; + infrastructureIntegrations: Array<{ name: string; slug: string; image: string; docsLink: string }>; }; export const IntegrationsPage = withProjectPermission( - ({ frameworkIntegrations }: Props) => { + ({ frameworkIntegrations, infrastructureIntegrations }: Props) => { const { t } = useTranslation(); @@ -228,6 +230,7 @@ export const IntegrationsPage = withProjectPermission( + ); }, diff --git a/frontend/src/views/IntegrationsPage/components/CloudIntegrationSection/CloudIntegrationSection.tsx b/frontend/src/views/IntegrationsPage/components/CloudIntegrationSection/CloudIntegrationSection.tsx index f81f8befe0..1644702898 100644 --- a/frontend/src/views/IntegrationsPage/components/CloudIntegrationSection/CloudIntegrationSection.tsx +++ b/frontend/src/views/IntegrationsPage/components/CloudIntegrationSection/CloudIntegrationSection.tsx @@ -109,7 +109,7 @@ export const CloudIntegrationSection = ({ {cloudIntegration.isAvailable && Boolean(integrationAuths?.[cloudIntegration.slug]) && ( -
+
diff --git a/frontend/src/views/IntegrationsPage/components/FrameworkIntegrationSection/FrameworkIntegrationSection.tsx b/frontend/src/views/IntegrationsPage/components/FrameworkIntegrationSection/FrameworkIntegrationSection.tsx index d77b976bc6..3b1df9bdd6 100644 --- a/frontend/src/views/IntegrationsPage/components/FrameworkIntegrationSection/FrameworkIntegrationSection.tsx +++ b/frontend/src/views/IntegrationsPage/components/FrameworkIntegrationSection/FrameworkIntegrationSection.tsx @@ -1,4 +1,7 @@ import { useTranslation } from "react-i18next"; +import { faKeyboard } from "@fortawesome/free-regular-svg-icons"; +import { faComputer } from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; type Props = { frameworks: Array<{ @@ -50,6 +53,36 @@ export const FrameworkIntegrationSection = ({ frameworks }: Props) => {
))} + +
+ +
+ CLI +
+
+ + ); diff --git a/frontend/src/views/IntegrationsPage/components/InfrastructureIntegrationSection/InfrastructureIntegrationSection.tsx b/frontend/src/views/IntegrationsPage/components/InfrastructureIntegrationSection/InfrastructureIntegrationSection.tsx new file mode 100644 index 0000000000..08652ff66f --- /dev/null +++ b/frontend/src/views/IntegrationsPage/components/InfrastructureIntegrationSection/InfrastructureIntegrationSection.tsx @@ -0,0 +1,50 @@ +type Props = { + integrations: Array<{ + name: string; + image: string; + slug: string; + docsLink: string; + }>; +}; + +export const InfrastructureIntegrationSection = ({ integrations }: Props) => { + const sortedIntegrations = integrations.sort((a, b) => a.name.localeCompare(b.name)); + + return ( + <> +
+

Infrastructure Integrations

+

Click on of the integration to read the documentation.

+
+ + + ); +}; diff --git a/frontend/src/views/IntegrationsPage/components/InfrastructureIntegrationSection/index.tsx b/frontend/src/views/IntegrationsPage/components/InfrastructureIntegrationSection/index.tsx new file mode 100644 index 0000000000..8ca5a43c79 --- /dev/null +++ b/frontend/src/views/IntegrationsPage/components/InfrastructureIntegrationSection/index.tsx @@ -0,0 +1 @@ +export { InfrastructureIntegrationSection } from "./InfrastructureIntegrationSection"; diff --git a/frontend/src/views/IntegrationsPage/components/IntegrationsSection/IntegrationsSection.tsx b/frontend/src/views/IntegrationsPage/components/IntegrationsSection/IntegrationsSection.tsx index 2944203d08..185cc411fb 100644 --- a/frontend/src/views/IntegrationsPage/components/IntegrationsSection/IntegrationsSection.tsx +++ b/frontend/src/views/IntegrationsPage/components/IntegrationsSection/IntegrationsSection.tsx @@ -137,7 +137,7 @@ export const IntegrationsSection = ({ "App" } /> -
+
{(integration.integration === "hashicorp-vault" && `${integration.app} - path: ${integration.path}`) || (integration.scope === "github-org" && `${integration.owner}`) || @@ -217,11 +217,12 @@ export const IntegrationsSection = ({ isOpen={popUp.deleteConfirmation.isOpen} title={`Are you sure want to remove ${ (popUp?.deleteConfirmation.data as TIntegration)?.integration || " " - } integration for ${(popUp?.deleteConfirmation.data as TIntegration)?.app || " "}?`} + } integration for ${(popUp?.deleteConfirmation.data as TIntegration)?.app || "this project"}?`} onChange={(isOpen) => handlePopUpToggle("deleteConfirmation", isOpen)} deleteKey={ (popUp?.deleteConfirmation?.data as TIntegration)?.app || (popUp?.deleteConfirmation?.data as TIntegration)?.owner || + (popUp?.deleteConfirmation?.data as TIntegration)?.path || "" } onDeleteApproved={async () =>