Merge remote-tracking branch 'origin' into k8s-auth

This commit is contained in:
Tuan Dang
2024-05-12 16:16:44 -07:00
7 changed files with 245 additions and 142 deletions
@@ -5,8 +5,13 @@ import { getConfig } from "@app/lib/config/env";
export const maintenanceMode = fp(async (fastify) => { export const maintenanceMode = fp(async (fastify) => {
fastify.addHook("onRequest", async (req) => { fastify.addHook("onRequest", async (req) => {
const serverEnvs = getConfig(); const serverEnvs = getConfig();
if (req.url !== "/api/v1/auth/checkAuth" && req.method !== "GET" && serverEnvs.MAINTENANCE_MODE) { if (serverEnvs.MAINTENANCE_MODE) {
throw new Error("Infisical is in maintenance mode. Please try again later."); // skip if its universal auth login or renew
if (req.url === "/api/v1/auth/universal-auth/login" && req.method === "POST") return;
if (req.url === "/api/v1/auth/token/renew" && req.method === "POST") return;
if (req.url !== "/api/v1/auth/checkAuth" && req.method !== "GET") {
throw new Error("Infisical is in maintenance mode. Please try again later.");
}
} }
}); });
}); });
@@ -143,8 +143,8 @@ export const registerIntegrationRouter = async (server: FastifyZodProvider) => {
integrationId: z.string().trim().describe(INTEGRATION.UPDATE.integrationId) integrationId: z.string().trim().describe(INTEGRATION.UPDATE.integrationId)
}), }),
body: z.object({ body: z.object({
app: z.string().trim().describe(INTEGRATION.UPDATE.app), app: z.string().trim().optional().describe(INTEGRATION.UPDATE.app),
appId: z.string().trim().describe(INTEGRATION.UPDATE.appId), appId: z.string().trim().optional().describe(INTEGRATION.UPDATE.appId),
isActive: z.boolean().describe(INTEGRATION.UPDATE.isActive), isActive: z.boolean().describe(INTEGRATION.UPDATE.isActive),
secretPath: z secretPath: z
.string() .string()
@@ -154,7 +154,33 @@ export const registerIntegrationRouter = async (server: FastifyZodProvider) => {
.describe(INTEGRATION.UPDATE.secretPath), .describe(INTEGRATION.UPDATE.secretPath),
targetEnvironment: z.string().trim().describe(INTEGRATION.UPDATE.targetEnvironment), targetEnvironment: z.string().trim().describe(INTEGRATION.UPDATE.targetEnvironment),
owner: z.string().trim().describe(INTEGRATION.UPDATE.owner), owner: z.string().trim().describe(INTEGRATION.UPDATE.owner),
environment: z.string().trim().describe(INTEGRATION.UPDATE.environment) environment: z.string().trim().describe(INTEGRATION.UPDATE.environment),
metadata: z
.object({
secretPrefix: z.string().optional().describe(INTEGRATION.CREATE.metadata.secretPrefix),
secretSuffix: z.string().optional().describe(INTEGRATION.CREATE.metadata.secretSuffix),
initialSyncBehavior: z.string().optional().describe(INTEGRATION.CREATE.metadata.initialSyncBehavoir),
shouldAutoRedeploy: z.boolean().optional().describe(INTEGRATION.CREATE.metadata.shouldAutoRedeploy),
secretGCPLabel: z
.object({
labelName: z.string(),
labelValue: z.string()
})
.optional()
.describe(INTEGRATION.CREATE.metadata.secretGCPLabel),
secretAWSTag: z
.array(
z.object({
key: z.string(),
value: z.string()
})
)
.optional()
.describe(INTEGRATION.CREATE.metadata.secretAWSTag),
kmsKeyId: z.string().optional().describe(INTEGRATION.CREATE.metadata.kmsKeyId),
shouldDisableDelete: z.boolean().optional().describe(INTEGRATION.CREATE.metadata.shouldDisableDelete)
})
.optional()
}), }),
response: { response: {
200: z.object({ 200: z.object({
@@ -9,9 +9,12 @@
import { import {
CreateSecretCommand, CreateSecretCommand,
DescribeSecretCommand,
GetSecretValueCommand, GetSecretValueCommand,
ResourceNotFoundException, ResourceNotFoundException,
SecretsManagerClient, SecretsManagerClient,
TagResourceCommand,
UntagResourceCommand,
UpdateSecretCommand UpdateSecretCommand
} from "@aws-sdk/client-secrets-manager"; } from "@aws-sdk/client-secrets-manager";
import { Octokit } from "@octokit/rest"; import { Octokit } from "@octokit/rest";
@@ -574,6 +577,7 @@ const syncSecretsAWSSecretManager = async ({
if (awsSecretManagerSecret?.SecretString) { if (awsSecretManagerSecret?.SecretString) {
awsSecretManagerSecretObj = JSON.parse(awsSecretManagerSecret.SecretString); awsSecretManagerSecretObj = JSON.parse(awsSecretManagerSecret.SecretString);
} }
if (!isEqual(awsSecretManagerSecretObj, secKeyVal)) { if (!isEqual(awsSecretManagerSecretObj, secKeyVal)) {
await secretsManager.send( await secretsManager.send(
new UpdateSecretCommand({ new UpdateSecretCommand({
@@ -582,7 +586,88 @@ const syncSecretsAWSSecretManager = async ({
}) })
); );
} }
const secretAWSTag = metadata.secretAWSTag as { key: string; value: string }[] | undefined;
if (secretAWSTag && secretAWSTag.length) {
const describedSecret = await secretsManager.send(
// requires secretsmanager:DescribeSecret policy
new DescribeSecretCommand({
SecretId: integration.app as string
})
);
if (!describedSecret.Tags) return;
const integrationTagObj = secretAWSTag.reduce(
(acc, item) => {
acc[item.key] = item.value;
return acc;
},
{} as Record<string, string>
);
const awsTagObj = (describedSecret.Tags || []).reduce(
(acc, item) => {
if (item.Key && item.Value) {
acc[item.Key] = item.Value;
}
return acc;
},
{} as Record<string, string>
);
const tagsToUpdate: { Key: string; Value: string }[] = [];
const tagsToDelete: { Key: string; Value: string }[] = [];
describedSecret.Tags?.forEach((tag) => {
if (tag.Key && tag.Value) {
if (!(tag.Key in integrationTagObj)) {
// delete tag from AWS secret manager
tagsToDelete.push({
Key: tag.Key,
Value: tag.Value
});
} else if (tag.Value !== integrationTagObj[tag.Key]) {
// update tag in AWS secret manager
tagsToUpdate.push({
Key: tag.Key,
Value: integrationTagObj[tag.Key]
});
}
}
});
secretAWSTag?.forEach((tag) => {
if (!(tag.key in awsTagObj)) {
// create tag in AWS secret manager
tagsToUpdate.push({
Key: tag.key,
Value: tag.value
});
}
});
if (tagsToUpdate.length) {
await secretsManager.send(
new TagResourceCommand({
SecretId: integration.app as string,
Tags: tagsToUpdate
})
);
}
if (tagsToDelete.length) {
await secretsManager.send(
new UntagResourceCommand({
SecretId: integration.app as string,
TagKeys: tagsToDelete.map((tag) => tag.Key)
})
);
}
}
} catch (err) { } catch (err) {
// case when AWS manager can't find the specified secret
if (err instanceof ResourceNotFoundException && secretsManager) { if (err instanceof ResourceNotFoundException && secretsManager) {
await secretsManager.send( await secretsManager.send(
new CreateSecretCommand({ new CreateSecretCommand({
@@ -103,7 +103,8 @@ export const integrationServiceFactory = ({
owner, owner,
isActive, isActive,
environment, environment,
secretPath secretPath,
metadata
}: TUpdateIntegrationDTO) => { }: TUpdateIntegrationDTO) => {
const integration = await integrationDAL.findById(id); const integration = await integrationDAL.findById(id);
if (!integration) throw new BadRequestError({ message: "Integration auth not found" }); if (!integration) throw new BadRequestError({ message: "Integration auth not found" });
@@ -127,7 +128,17 @@ export const integrationServiceFactory = ({
appId, appId,
targetEnvironment, targetEnvironment,
owner, owner,
secretPath secretPath,
metadata: {
...(integration.metadata as object),
...metadata
}
});
await secretQueueService.syncIntegrations({
environment: folder.environment.slug,
secretPath,
projectId: folder.projectId
}); });
return updatedIntegration; return updatedIntegration;
@@ -33,13 +33,27 @@ export type TCreateIntegrationDTO = {
export type TUpdateIntegrationDTO = { export type TUpdateIntegrationDTO = {
id: string; id: string;
app: string; app?: string;
appId: string; appId?: string;
isActive?: boolean; isActive?: boolean;
secretPath: string; secretPath: string;
targetEnvironment: string; targetEnvironment: string;
owner: string; owner: string;
environment: string; environment: string;
metadata?: {
secretPrefix?: string;
secretSuffix?: string;
secretGCPLabel?: {
labelName: string;
labelValue: string;
};
secretAWSTag?: {
key: string;
value: string;
}[];
kmsKeyId?: string;
shouldDisableDelete?: boolean;
};
} & Omit<TProjectPermission, "projectId">; } & Omit<TProjectPermission, "projectId">;
export type TDeleteIntegrationDTO = { export type TDeleteIntegrationDTO = {
@@ -29,7 +29,9 @@ Prerequisites:
"secretsmanager:GetSecretValue", "secretsmanager:GetSecretValue",
"secretsmanager:CreateSecret", "secretsmanager:CreateSecret",
"secretsmanager:UpdateSecret", "secretsmanager:UpdateSecret",
"secretsmanager:DescribeSecret", // if you need to add tags to secrets
"secretsmanager:TagResource", // if you need to add tags to secrets "secretsmanager:TagResource", // if you need to add tags to secrets
"secretsmanager:UntagResource", // if you need to add tags to secrets
"kms:ListKeys", // if you need to specify the KMS key "kms:ListKeys", // if you need to specify the KMS key
"kms:ListAliases", // 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:Encrypt", // if you need to specify the KMS key
+93 -133
View File
@@ -21,9 +21,7 @@ import {
faNetworkWired, faNetworkWired,
faPlug, faPlug,
faPlus, faPlus,
faUserPlus, faUserPlus
faWarning,
faXmark
} 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 { yupResolver } from "@hookform/resolvers/yup"; import { yupResolver } from "@hookform/resolvers/yup";
@@ -56,7 +54,6 @@ import {
fetchOrgUsers, fetchOrgUsers,
useAddUserToWsNonE2EE, useAddUserToWsNonE2EE,
useCreateWorkspace, useCreateWorkspace,
useGetUserAction,
useRegisterUserAction useRegisterUserAction
} from "@app/hooks/api"; } from "@app/hooks/api";
// import { fetchUserWsKey } from "@app/hooks/api/keys/queries"; // import { fetchUserWsKey } from "@app/hooks/api/keys/queries";
@@ -312,9 +309,8 @@ const LearningItem = ({
href={link} href={link}
> >
<div <div
className={`${ className={`${complete ? "bg-gradient-to-r from-primary-500/70 p-[0.07rem]" : ""
complete ? "bg-gradient-to-r from-primary-500/70 p-[0.07rem]" : "" } mb-3 rounded-md`}
} mb-3 rounded-md`}
> >
<div <div
onKeyDown={() => null} onKeyDown={() => null}
@@ -325,11 +321,10 @@ const LearningItem = ({
await registerUserAction.mutateAsync(userAction); await registerUserAction.mutateAsync(userAction);
} }
}} }}
className={`group relative flex h-[5.5rem] w-full items-center justify-between overflow-hidden rounded-md border ${ className={`group relative flex h-[5.5rem] w-full items-center justify-between overflow-hidden rounded-md border ${complete
complete
? "cursor-default border-mineshaft-900 bg-gradient-to-r from-[#0e1f01] to-mineshaft-700" ? "cursor-default border-mineshaft-900 bg-gradient-to-r from-[#0e1f01] to-mineshaft-700"
: "cursor-pointer border-mineshaft-600 bg-mineshaft-800 shadow-xl hover:bg-mineshaft-700" : "cursor-pointer border-mineshaft-600 bg-mineshaft-800 shadow-xl hover:bg-mineshaft-700"
} text-mineshaft-100 duration-200`} } text-mineshaft-100 duration-200`}
> >
<div className="mr-4 flex flex-row items-center"> <div className="mr-4 flex flex-row items-center">
<FontAwesomeIcon icon={icon} className="mx-2 w-16 text-4xl" /> <FontAwesomeIcon icon={icon} className="mx-2 w-16 text-4xl" />
@@ -407,9 +402,8 @@ const LearningItemSquare = ({
href={link} href={link}
> >
<div <div
className={`${ className={`${complete ? "bg-gradient-to-r from-primary-500/70 p-[0.07rem]" : ""
complete ? "bg-gradient-to-r from-primary-500/70 p-[0.07rem]" : "" } w-full rounded-md`}
} w-full rounded-md`}
> >
<div <div
onKeyDown={() => null} onKeyDown={() => null}
@@ -420,11 +414,10 @@ const LearningItemSquare = ({
await registerUserAction.mutateAsync(userAction); await registerUserAction.mutateAsync(userAction);
} }
}} }}
className={`group relative flex w-full items-center justify-between overflow-hidden rounded-md border ${ className={`group relative flex w-full items-center justify-between overflow-hidden rounded-md border ${complete
complete
? "cursor-default border-mineshaft-900 bg-gradient-to-r from-[#0e1f01] to-mineshaft-700" ? "cursor-default border-mineshaft-900 bg-gradient-to-r from-[#0e1f01] to-mineshaft-700"
: "cursor-pointer border-mineshaft-600 bg-mineshaft-800 shadow-xl hover:bg-mineshaft-700" : "cursor-pointer border-mineshaft-600 bg-mineshaft-800 shadow-xl hover:bg-mineshaft-700"
} text-mineshaft-100 duration-200`} } text-mineshaft-100 duration-200`}
> >
<div className="flex w-full flex-col items-center px-6 py-4"> <div className="flex w-full flex-col items-center px-6 py-4">
<div className="flex w-full flex-row items-start justify-between"> <div className="flex w-full flex-row items-start justify-between">
@@ -438,9 +431,8 @@ const LearningItemSquare = ({
</div> </div>
)} )}
<div <div
className={`text-right text-sm font-normal text-mineshaft-300 ${ className={`text-right text-sm font-normal text-mineshaft-300 ${complete ? "font-semibold text-primary" : ""
complete ? "font-semibold text-primary" : "" }`}
}`}
> >
{complete ? "Complete!" : `About ${time}`} {complete ? "Complete!" : `About ${time}`}
</div> </div>
@@ -480,14 +472,8 @@ const OrganizationPage = withPermission(
const { currentOrg } = useOrganization(); const { currentOrg } = useOrganization();
const routerOrgId = String(router.query.id); const routerOrgId = String(router.query.id);
const orgWorkspaces = workspaces?.filter((workspace) => workspace.orgId === routerOrgId) || []; const orgWorkspaces = workspaces?.filter((workspace) => workspace.orgId === routerOrgId) || [];
const addUsersToProject = useAddUserToWsNonE2EE();
const { data: updateClosed } = useGetUserAction("april_13_2024_db_update_closed"); const addUsersToProject = useAddUserToWsNonE2EE();
const registerUserAction = useRegisterUserAction();
const closeUpdate = async () => {
await registerUserAction.mutateAsync("april_13_2024_db_update_closed");
};
const { popUp, handlePopUpOpen, handlePopUpClose, handlePopUpToggle } = usePopUp([ const { popUp, handlePopUpOpen, handlePopUpClose, handlePopUpToggle } = usePopUp([
"addNewWs", "addNewWs",
@@ -594,31 +580,6 @@ const OrganizationPage = withPermission(
</div> </div>
)} )}
<div className="mb-4 flex flex-col items-start justify-start px-6 py-6 pb-0 text-3xl"> <div className="mb-4 flex flex-col items-start justify-start px-6 py-6 pb-0 text-3xl">
{(window.location.origin.includes("https://app.infisical.com") || window.location.origin.includes("http://localhost:8080")) && (
<div
className={`${
!updateClosed ? "block" : "hidden"
} mb-4 flex w-full flex-row items-center rounded-md border border-primary-600 bg-primary/10 p-2 text-base text-white`}
>
<FontAwesomeIcon icon={faWarning} className="p-6 text-4xl text-primary" />
<div className="text-sm">
<span className="text-lg font-semibold">Scheduled maintenance on May 11th 2024 </span>{" "}
<br />
Infisical will undergo scheduled maintenance for approximately 2 hour on Saturday, May 11th, 11am EST. During these hours, read
operations to Infisical will continue to function normally but no resources will be editable.
No action is required on your end — your applications will continue to fetch secrets.
<br />
</div>
<button
type="button"
onClick={() => closeUpdate()}
aria-label="close"
className="flex h-full items-start text-mineshaft-100 duration-200 hover:text-red-400"
>
<FontAwesomeIcon icon={faXmark} />
</button>
</div>)}
<p className="mr-4 font-semibold text-white">Projects</p> <p className="mr-4 font-semibold text-white">Projects</p>
<div className="mt-6 flex w-full flex-row"> <div className="mt-6 flex w-full flex-row">
<Input <Input
@@ -748,95 +709,94 @@ const OrganizationPage = withPermission(
new Date().getTime() - new Date(user?.createdAt).getTime() < new Date().getTime() - new Date(user?.createdAt).getTime() <
30 * 24 * 60 * 60 * 1000 30 * 24 * 60 * 60 * 1000
) && ( ) && (
<div className="mb-4 flex flex-col items-start justify-start px-6 pb-0 text-3xl"> <div className="mb-4 flex flex-col items-start justify-start px-6 pb-0 text-3xl">
<p className="mr-4 mb-4 font-semibold text-white">Onboarding Guide</p> <p className="mr-4 mb-4 font-semibold text-white">Onboarding Guide</p>
<div className="mb-3 grid w-full grid-cols-1 gap-3 lg:grid-cols-2 xl:grid-cols-3 2xl:grid-cols-4"> <div className="mb-3 grid w-full grid-cols-1 gap-3 lg:grid-cols-2 xl:grid-cols-3 2xl:grid-cols-4">
<LearningItemSquare
text="Watch Infisical demo"
subText="Set up Infisical in 3 min."
complete={hasUserClickedIntro}
icon={faHandPeace}
time="3 min"
userAction="intro_cta_clicked"
link="https://www.youtube.com/watch?v=PK23097-25I"
/>
{orgWorkspaces.length !== 0 && (
<>
<LearningItemSquare
text="Add your secrets"
subText="Drop a .env file or type your secrets."
complete={hasUserPushedSecrets}
icon={faPlus}
time="1 min"
userAction="first_time_secrets_pushed"
link={`/project/${orgWorkspaces[0]?.id}/secrets/overview`}
/>
<LearningItemSquare
text="Invite your teammates"
subText="Infisical is better used as a team."
complete={usersInOrg}
icon={faUserPlus}
time="2 min"
link={`/org/${router.query.id}/members?action=invite`}
/>
</>
)}
<div className="block xl:hidden 2xl:block">
<LearningItemSquare <LearningItemSquare
text="Join Infisical Slack" text="Watch Infisical demo"
subText="Have any questions? Ask us!" subText="Set up Infisical in 3 min."
complete={hasUserClickedSlack} complete={hasUserClickedIntro}
icon={faSlack} icon={faHandPeace}
time="1 min" time="3 min"
userAction="slack_cta_clicked" userAction="intro_cta_clicked"
link="https://infisical.com/slack" link="https://www.youtube.com/watch?v=PK23097-25I"
/> />
{orgWorkspaces.length !== 0 && (
<>
<LearningItemSquare
text="Add your secrets"
subText="Drop a .env file or type your secrets."
complete={hasUserPushedSecrets}
icon={faPlus}
time="1 min"
userAction="first_time_secrets_pushed"
link={`/project/${orgWorkspaces[0]?.id}/secrets/overview`}
/>
<LearningItemSquare
text="Invite your teammates"
subText="Infisical is better used as a team."
complete={usersInOrg}
icon={faUserPlus}
time="2 min"
link={`/org/${router.query.id}/members?action=invite`}
/>
</>
)}
<div className="block xl:hidden 2xl:block">
<LearningItemSquare
text="Join Infisical Slack"
subText="Have any questions? Ask us!"
complete={hasUserClickedSlack}
icon={faSlack}
time="1 min"
userAction="slack_cta_clicked"
link="https://infisical.com/slack"
/>
</div>
</div> </div>
</div> {orgWorkspaces.length !== 0 && (
{orgWorkspaces.length !== 0 && ( <div className="group relative mb-3 flex h-full w-full cursor-default flex-col items-center justify-between overflow-hidden rounded-md border border-mineshaft-600 bg-mineshaft-800 pl-2 pr-2 pt-4 pb-2 text-mineshaft-100 shadow-xl duration-200">
<div className="group relative mb-3 flex h-full w-full cursor-default flex-col items-center justify-between overflow-hidden rounded-md border border-mineshaft-600 bg-mineshaft-800 pl-2 pr-2 pt-4 pb-2 text-mineshaft-100 shadow-xl duration-200"> <div className="mb-4 flex w-full flex-row items-center pr-4">
<div className="mb-4 flex w-full flex-row items-center pr-4"> <div className="mr-4 flex w-full flex-row items-center">
<div className="mr-4 flex w-full flex-row items-center"> <FontAwesomeIcon icon={faNetworkWired} className="mx-2 w-16 text-4xl" />
<FontAwesomeIcon icon={faNetworkWired} className="mx-2 w-16 text-4xl" /> {false && (
{false && ( <div className="absolute left-12 top-10 flex h-7 w-7 items-center justify-center rounded-full bg-bunker-500 p-2 group-hover:bg-mineshaft-700">
<div className="absolute left-12 top-10 flex h-7 w-7 items-center justify-center rounded-full bg-bunker-500 p-2 group-hover:bg-mineshaft-700"> <FontAwesomeIcon
<FontAwesomeIcon icon={faCheckCircle}
icon={faCheckCircle} className="h-5 w-5 text-4xl text-green"
className="h-5 w-5 text-4xl text-green" />
/> </div>
</div> )}
)} <div className="flex flex-col items-start pl-0.5">
<div className="flex flex-col items-start pl-0.5"> <div className="mt-0.5 text-xl font-semibold">Inject secrets locally</div>
<div className="mt-0.5 text-xl font-semibold">Inject secrets locally</div> <div className="text-sm font-normal">
<div className="text-sm font-normal"> Replace .env files with a more secure and efficient alternative.
Replace .env files with a more secure and efficient alternative. </div>
</div> </div>
</div> </div>
<div
className={`w-28 pr-4 text-right text-sm font-semibold ${false && "text-green"
}`}
>
About 2 min
</div>
</div> </div>
<div <TabsObject />
className={`w-28 pr-4 text-right text-sm font-semibold ${ {false && <div className="absolute bottom-0 left-0 h-1 w-full bg-green" />}
false && "text-green"
}`}
>
About 2 min
</div>
</div> </div>
<TabsObject /> )}
{false && <div className="absolute bottom-0 left-0 h-1 w-full bg-green" />} {orgWorkspaces.length !== 0 && (
</div> <LearningItem
)} text="Integrate Infisical with your infrastructure"
{orgWorkspaces.length !== 0 && ( subText="Connect Infisical to various 3rd party services and platforms."
<LearningItem complete={false}
text="Integrate Infisical with your infrastructure" icon={faPlug}
subText="Connect Infisical to various 3rd party services and platforms." time="15 min"
complete={false} link="https://infisical.com/docs/integrations/overview"
icon={faPlug} />
time="15 min" )}
link="https://infisical.com/docs/integrations/overview" </div>
/> )}
)}
</div>
)}
<Modal <Modal
isOpen={popUp.addNewWs.isOpen} isOpen={popUp.addNewWs.isOpen}
onOpenChange={(isModalOpen) => { onOpenChange={(isModalOpen) => {