Merge pull request #1699 from Infisical/imported-secret-icon

Feat: Tags for AWS integrations
This commit is contained in:
vmatsiiako
2024-04-18 14:26:33 -07:00
committed by GitHub
10 changed files with 300 additions and 108 deletions
+2 -1
View File
@@ -589,7 +589,8 @@ export const INTEGRATION = {
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 the GCP secrets",
secretAWSTag: "The tag for the AWS secrets"
} }
}, },
UPDATE: { UPDATE: {
@@ -56,7 +56,14 @@ export const registerIntegrationRouter = async (server: FastifyZodProvider) => {
labelValue: z.string() labelValue: z.string()
}) })
.optional() .optional()
.describe(INTEGRATION.CREATE.metadata.secretGCPLabel) .describe(INTEGRATION.CREATE.metadata.secretGCPLabel),
secretAWSTag: z
.object({
key: z.string(),
value: z.string()
})
.optional()
.describe(INTEGRATION.CREATE.metadata.secretAWSTag)
}) })
.optional() .optional()
}), }),
@@ -457,6 +457,8 @@ const syncSecretsAWSParameterStore = async ({
}); });
ssm.config.update(config); ssm.config.update(config);
const metadata = z.record(z.any()).parse(integration.metadata);
const params = { const params = {
Path: integration.path as string, Path: integration.path as string,
Recursive: false, Recursive: false,
@@ -486,7 +488,8 @@ const syncSecretsAWSParameterStore = async ({
Name: `${integration.path}${key}`, Name: `${integration.path}${key}`,
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 }] : []
}) })
.promise(); .promise();
// case: secret exists in AWS parameter store // case: secret exists in AWS parameter store
@@ -499,6 +502,7 @@ 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 }] : []
}) })
.promise(); .promise();
} }
@@ -537,6 +541,7 @@ const syncSecretsAWSSecretManager = async ({
}) => { }) => {
let secretsManager; let secretsManager;
const secKeyVal = getSecretKeyValuePair(secrets); const secKeyVal = getSecretKeyValuePair(secrets);
const metadata = z.record(z.any()).parse(integration.metadata);
try { try {
if (!accessId) return; if (!accessId) return;
@@ -573,7 +578,8 @@ const syncSecretsAWSSecretManager = async ({
await secretsManager.send( await secretsManager.send(
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 }] : []
}) })
); );
} }
@@ -22,6 +22,10 @@ export type TCreateIntegrationDTO = {
labelName: string; labelName: string;
labelValue: string; labelValue: string;
}; };
secretAWSTag?: {
key: string;
value: string;
};
}; };
} & Omit<TProjectPermission, "projectId">; } & Omit<TProjectPermission, "projectId">;
@@ -29,7 +29,8 @@ Prerequisites:
"ssm:PutParameter", "ssm:PutParameter",
"ssm:DeleteParameter", "ssm:DeleteParameter",
"ssm:GetParametersByPath", "ssm:GetParametersByPath",
"ssm:DeleteParameters" "ssm:DeleteParameters",
"ssm:AddTagsToResource"
], ],
"Resource": "*" "Resource": "*"
} }
@@ -28,7 +28,8 @@ Prerequisites:
"Action": [ "Action": [
"secretsmanager:GetSecretValue", "secretsmanager:GetSecretValue",
"secretsmanager:CreateSecret", "secretsmanager:CreateSecret",
"secretsmanager:UpdateSecret" "secretsmanager:UpdateSecret",
"secretsmanager:TagResource"
], ],
"Resource": "*" "Resource": "*"
} }
@@ -63,6 +63,10 @@ export const useCreateIntegration = () => {
secretSuffix?: string; secretSuffix?: string;
initialSyncBehavior?: string; initialSyncBehavior?: string;
shouldAutoRedeploy?: boolean; shouldAutoRedeploy?: boolean;
secretAWSTag?: {
key: string;
value: string;
};
}; };
}) => { }) => {
const { const {
@@ -10,6 +10,7 @@ import {
faCircleInfo faCircleInfo
} from "@fortawesome/free-solid-svg-icons"; } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
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";
@@ -21,11 +22,21 @@ import {
FormControl, FormControl,
Input, Input,
Select, Select,
SelectItem SelectItem,
Switch,
Tab,
TabList,
TabPanel,
Tabs
} from "../../../components/v2"; } from "../../../components/v2";
import { useGetIntegrationAuthById } from "../../../hooks/api/integrationAuth"; import { useGetIntegrationAuthById } from "../../../hooks/api/integrationAuth";
import { useGetWorkspaceById } from "../../../hooks/api/workspace"; import { useGetWorkspaceById } from "../../../hooks/api/workspace";
enum TabSections {
Connection = "connection",
Options = "options"
}
const awsRegions = [ const awsRegions = [
{ name: "US East (Ohio)", slug: "us-east-2" }, { name: "US East (Ohio)", slug: "us-east-2" },
{ name: "US East (N. Virginia)", slug: "us-east-1" }, { name: "US East (N. Virginia)", slug: "us-east-1" },
@@ -76,6 +87,9 @@ export default function AWSParameterStoreCreateIntegrationPage() {
const [pathErrorText, setPathErrorText] = useState(""); const [pathErrorText, setPathErrorText] = useState("");
const [isLoading, setIsLoading] = useState(false); const [isLoading, setIsLoading] = useState(false);
const [shouldTag, setShouldTag] = useState(false);
const [tagKey, setTagKey] = useState("");
const [tagValue, setTagValue] = useState("");
useEffect(() => { useEffect(() => {
if (workspace) { if (workspace) {
@@ -110,7 +124,17 @@ export default function AWSParameterStoreCreateIntegrationPage() {
sourceEnvironment: selectedSourceEnvironment, sourceEnvironment: selectedSourceEnvironment,
path, path,
region: selectedAWSRegion, region: selectedAWSRegion,
secretPath secretPath,
metadata: {
...(shouldTag
? {
secretAWSTag: {
key: tagKey,
value: tagValue
}
}
: {})
}
}); });
setIsLoading(false); setIsLoading(false);
@@ -157,56 +181,114 @@ export default function AWSParameterStoreCreateIntegrationPage() {
</Link> </Link>
</div> </div>
</CardTitle> </CardTitle>
<FormControl label="Project Environment" className="px-6"> <Tabs defaultValue={TabSections.Connection} className="px-6">
<Select <TabList>
value={selectedSourceEnvironment} <div className="flex w-full flex-row border-b border-mineshaft-600">
onValueChange={(val) => setSelectedSourceEnvironment(val)} <Tab value={TabSections.Connection}>Connection</Tab>
className="w-full border border-mineshaft-500" <Tab value={TabSections.Options}>Options</Tab>
> </div>
{workspace?.environments.map((sourceEnvironment) => ( </TabList>
<SelectItem <TabPanel value={TabSections.Connection}>
value={sourceEnvironment.slug} <motion.div
key={`flyio-environment-${sourceEnvironment.slug}`} key="panel-1"
transition={{ duration: 0.15 }}
initial={{ opacity: 0, translateX: 30 }}
animate={{ opacity: 1, translateX: 0 }}
exit={{ opacity: 0, translateX: 30 }}
>
<FormControl label="Project Environment">
<Select
value={selectedSourceEnvironment}
onValueChange={(val) => setSelectedSourceEnvironment(val)}
className="w-full border border-mineshaft-500"
>
{workspace?.environments.map((sourceEnvironment) => (
<SelectItem
value={sourceEnvironment.slug}
key={`flyio-environment-${sourceEnvironment.slug}`}
>
{sourceEnvironment.name}
</SelectItem>
))}
</Select>
</FormControl>
<FormControl label="Secrets Path">
<Input
value={secretPath}
onChange={(evt) => setSecretPath(evt.target.value)}
placeholder="Provide a path, default is /"
/>
</FormControl>
<FormControl label="AWS Region">
<Select
value={selectedAWSRegion}
onValueChange={(val) => setSelectedAWSRegion(val)}
className="w-full border border-mineshaft-500"
>
{awsRegions.map((awsRegion) => (
<SelectItem value={awsRegion.slug} key={`flyio-environment-${awsRegion.slug}`}>
{awsRegion.name}
</SelectItem>
))}
</Select>
</FormControl>
<FormControl
label="Path"
errorText={pathErrorText}
isError={pathErrorText !== "" ?? false}
> >
{sourceEnvironment.name} <Input
</SelectItem> placeholder={`/${workspace.name
))} .toLowerCase()
</Select> .replace(/ /g, "-")}/${selectedSourceEnvironment}/`}
</FormControl> value={path}
<FormControl label="Secrets Path" className="px-6"> onChange={(e) => setPath(e.target.value)}
<Input />
value={secretPath} </FormControl>
onChange={(evt) => setSecretPath(evt.target.value)} </motion.div>
placeholder="Provide a path, default is /" </TabPanel>
/> <TabPanel value={TabSections.Options}>
</FormControl> <motion.div
<FormControl label="AWS Region" className="px-6"> key="panel-1"
<Select transition={{ duration: 0.15 }}
value={selectedAWSRegion} initial={{ opacity: 0, translateX: -30 }}
onValueChange={(val) => setSelectedAWSRegion(val)} animate={{ opacity: 1, translateX: 0 }}
className="w-full border border-mineshaft-500" exit={{ opacity: 0, translateX: 30 }}
> >
{awsRegions.map((awsRegion) => ( <div className="mt-2 ml-1">
<SelectItem value={awsRegion.slug} key={`flyio-environment-${awsRegion.slug}`}> <Switch
{awsRegion.name} id="tag-aws"
</SelectItem> onCheckedChange={() => setShouldTag(!shouldTag)}
))} isChecked={shouldTag}
</Select> >
</FormControl> Tag in AWS Parameter Store
<FormControl </Switch>
label="Path" </div>
errorText={pathErrorText} {shouldTag && (
isError={pathErrorText !== "" ?? false} <div className="mt-4">
className="px-6" <FormControl
> label="Tag Key"
<Input >
placeholder={`/${workspace.name <Input
.toLowerCase() placeholder="managed-by"
.replace(/ /g, "-")}/${selectedSourceEnvironment}/`} value={tagKey}
value={path} onChange={(e) => setTagKey(e.target.value)}
onChange={(e) => setPath(e.target.value)} />
/> </FormControl>
</FormControl> <FormControl
label="Tag Value"
>
<Input
placeholder="managed-by"
value={tagValue}
onChange={(e) => setTagValue(e.target.value)}
/>
</FormControl>
</div>
)}
</motion.div>
</TabPanel>
</Tabs>
<Button <Button
onClick={handleButtonClick} onClick={handleButtonClick}
color="mineshaft" color="mineshaft"
@@ -10,6 +10,7 @@ import {
faCircleInfo faCircleInfo
} from "@fortawesome/free-solid-svg-icons"; } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
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";
@@ -21,11 +22,21 @@ import {
FormControl, FormControl,
Input, Input,
Select, Select,
SelectItem SelectItem,
Switch,
Tab,
TabList,
TabPanel,
Tabs
} from "../../../components/v2"; } from "../../../components/v2";
import { useGetIntegrationAuthById } from "../../../hooks/api/integrationAuth"; import { useGetIntegrationAuthById } from "../../../hooks/api/integrationAuth";
import { useGetWorkspaceById } from "../../../hooks/api/workspace"; import { useGetWorkspaceById } from "../../../hooks/api/workspace";
enum TabSections {
Connection = "connection",
Options = "options"
}
const awsRegions = [ const awsRegions = [
{ name: "US East (Ohio)", slug: "us-east-2" }, { name: "US East (Ohio)", slug: "us-east-2" },
{ name: "US East (N. Virginia)", slug: "us-east-1" }, { name: "US East (N. Virginia)", slug: "us-east-1" },
@@ -74,11 +85,14 @@ export default function AWSSecretManagerCreateIntegrationPage() {
const [selectedAWSRegion, setSelectedAWSRegion] = useState(""); const [selectedAWSRegion, setSelectedAWSRegion] = useState("");
const [targetSecretName, setTargetSecretName] = useState(""); const [targetSecretName, setTargetSecretName] = useState("");
const [targetSecretNameErrorText, setTargetSecretNameErrorText] = useState(""); const [targetSecretNameErrorText, setTargetSecretNameErrorText] = useState("");
const [tagKey, setTagKey] = useState("");
const [tagValue, setTagValue] = useState("");
// const [path, setPath] = useState(''); // const [path, setPath] = useState('');
// const [pathErrorText, setPathErrorText] = useState(''); // const [pathErrorText, setPathErrorText] = useState('');
const [isLoading, setIsLoading] = useState(false); const [isLoading, setIsLoading] = useState(false);
const [shouldTag, setShouldTag] = useState(false);
useEffect(() => { useEffect(() => {
if (workspace) { if (workspace) {
@@ -109,7 +123,17 @@ export default function AWSSecretManagerCreateIntegrationPage() {
app: targetSecretName.trim(), app: targetSecretName.trim(),
sourceEnvironment: selectedSourceEnvironment, sourceEnvironment: selectedSourceEnvironment,
region: selectedAWSRegion, region: selectedAWSRegion,
secretPath secretPath,
metadata: {
...(shouldTag
? {
secretAWSTag: {
key: tagKey,
value: tagValue
}
}
: {})
}
}); });
setIsLoading(false); setIsLoading(false);
@@ -156,56 +180,114 @@ export default function AWSSecretManagerCreateIntegrationPage() {
</Link> </Link>
</div> </div>
</CardTitle> </CardTitle>
<FormControl label="Project Environment" className="px-6"> <Tabs defaultValue={TabSections.Connection} className="px-6">
<Select <TabList>
value={selectedSourceEnvironment} <div className="flex w-full flex-row border-b border-mineshaft-600">
onValueChange={(val) => setSelectedSourceEnvironment(val)} <Tab value={TabSections.Connection}>Connection</Tab>
className="w-full border border-mineshaft-500" <Tab value={TabSections.Options}>Options</Tab>
> </div>
{workspace?.environments.map((sourceEnvironment) => ( </TabList>
<SelectItem <TabPanel value={TabSections.Connection}>
value={sourceEnvironment.slug} <motion.div
key={`flyio-environment-${sourceEnvironment.slug}`} key="panel-1"
transition={{ duration: 0.15 }}
initial={{ opacity: 0, translateX: 30 }}
animate={{ opacity: 1, translateX: 0 }}
exit={{ opacity: 0, translateX: 30 }}
>
<FormControl label="Project Environment">
<Select
value={selectedSourceEnvironment}
onValueChange={(val) => setSelectedSourceEnvironment(val)}
className="w-full border border-mineshaft-500"
>
{workspace?.environments.map((sourceEnvironment) => (
<SelectItem
value={sourceEnvironment.slug}
key={`flyio-environment-${sourceEnvironment.slug}`}
>
{sourceEnvironment.name}
</SelectItem>
))}
</Select>
</FormControl>
<FormControl label="Secrets Path">
<Input
value={secretPath}
onChange={(evt) => setSecretPath(evt.target.value)}
placeholder="Provide a path, default is /"
/>
</FormControl>
<FormControl label="AWS Region">
<Select
value={selectedAWSRegion}
onValueChange={(val) => setSelectedAWSRegion(val)}
className="w-full border border-mineshaft-500"
>
{awsRegions.map((awsRegion) => (
<SelectItem value={awsRegion.slug} key={`flyio-environment-${awsRegion.slug}`}>
{awsRegion.name}
</SelectItem>
))}
</Select>
</FormControl>
<FormControl
label="AWS SM Secret Name"
errorText={targetSecretNameErrorText}
isError={targetSecretNameErrorText !== "" ?? false}
> >
{sourceEnvironment.name} <Input
</SelectItem> placeholder={`${workspace.name
))} .toLowerCase()
</Select> .replace(/ /g, "-")}/${selectedSourceEnvironment}`}
</FormControl> value={targetSecretName}
<FormControl label="Secrets Path" className="px-6"> onChange={(e) => setTargetSecretName(e.target.value)}
<Input />
value={secretPath} </FormControl>
onChange={(evt) => setSecretPath(evt.target.value)} </motion.div>
placeholder="Provide a path, default is /" </TabPanel>
/> <TabPanel value={TabSections.Options}>
</FormControl> <motion.div
<FormControl label="AWS Region" className="px-6"> key="panel-1"
<Select transition={{ duration: 0.15 }}
value={selectedAWSRegion} initial={{ opacity: 0, translateX: -30 }}
onValueChange={(val) => setSelectedAWSRegion(val)} animate={{ opacity: 1, translateX: 0 }}
className="w-full border border-mineshaft-500" exit={{ opacity: 0, translateX: 30 }}
> >
{awsRegions.map((awsRegion) => ( <div className="mt-2 ml-1">
<SelectItem value={awsRegion.slug} key={`flyio-environment-${awsRegion.slug}`}> <Switch
{awsRegion.name} id="tag-aws"
</SelectItem> onCheckedChange={() => setShouldTag(!shouldTag)}
))} isChecked={shouldTag}
</Select> >
</FormControl> Tag in AWS Secrets Manager
<FormControl </Switch>
label="AWS SM Secret Name" </div>
errorText={targetSecretNameErrorText} {shouldTag && (
isError={targetSecretNameErrorText !== "" ?? false} <div className="mt-4">
className="px-6" <FormControl
> label="Tag Key"
<Input >
placeholder={`${workspace.name <Input
.toLowerCase() placeholder="managed-by"
.replace(/ /g, "-")}/${selectedSourceEnvironment}`} value={tagKey}
value={targetSecretName} onChange={(e) => setTagKey(e.target.value)}
onChange={(e) => setTargetSecretName(e.target.value)} />
/> </FormControl>
</FormControl> <FormControl
label="Tag Value"
>
<Input
placeholder="managed-by"
value={tagValue}
onChange={(e) => setTagValue(e.target.value)}
/>
</FormControl>
</div>
)}
</motion.div>
</TabPanel>
</Tabs>
<Button <Button
onClick={handleButtonClick} onClick={handleButtonClick}
color="mineshaft" color="mineshaft"
@@ -5,4 +5,8 @@ export type Metadata = {
labelName: string; labelName: string;
labelValue: string; labelValue: string;
} }
secretAWSTag?: {
key: string;
value: string;
}
} }