misc: added validation against confused deputy

This commit is contained in:
Sheen Capadngan
2025-01-27 17:30:26 +08:00
parent d0d5556bd0
commit f9ac7442df
3 changed files with 17 additions and 2 deletions

View File

@@ -146,6 +146,18 @@ export const getGcpSecretManagerProjects = async (appConnection: TGcpConnection)
};
export const validateGcpConnectionCredentials = async (appConnection: TGcpConnectionConfig) => {
// Check if provided service account email prefix matches organization ID.
// We do this to mitigate confused deputy attacks in multi-tenant instances
const expectedEmailPrefix = appConnection.orgId.split("-").slice(0, 2).join("-");
if (
appConnection.credentials.serviceAccountEmail &&
!appConnection.credentials.serviceAccountEmail.startsWith(expectedEmailPrefix)
) {
throw new BadRequestError({
message: `GCP service account email must have a prefix of "${expectedEmailPrefix}"`
});
}
await getAuthToken(appConnection);
return appConnection.credentials;

View File

@@ -11,7 +11,7 @@ import {
import { GcpConnectionMethod } from "./gcp-connection-enums";
export const GcpConnectionServiceAccountImpersonationCredentialsSchema = z.object({
serviceAccountEmail: z.string().trim().min(1, "Service account email required")
serviceAccountEmail: z.string().email().trim().min(1, "Service account email required")
});
const BaseGcpConnectionSchema = BaseAppConnectionSchema.extend({ app: z.literal(AppConnection.GCP) });

View File

@@ -10,6 +10,7 @@ import {
Select,
SelectItem
} from "@app/components/v2";
import { useOrganization } from "@app/context";
import { APP_CONNECTION_MAP, getAppConnectionMethodDetails } from "@app/helpers/appConnections";
import { GcpConnectionMethod, TGcpConnection } from "@app/hooks/api/appConnections";
import { AppConnection } from "@app/hooks/api/appConnections/enums";
@@ -32,7 +33,7 @@ const formSchema = z.discriminatedUnion("method", [
rootSchema.extend({
method: z.literal(GcpConnectionMethod.ServiceAccountImpersonation),
credentials: z.object({
serviceAccountEmail: z.string().trim().min(1, "Service account email required")
serviceAccountEmail: z.string().email().trim().min(1, "Service account email required")
})
})
]);
@@ -49,6 +50,7 @@ export const GcpConnectionForm = ({ appConnection, onSubmit }: Props) => {
method: GcpConnectionMethod.ServiceAccountImpersonation
}
});
const { currentOrg } = useOrganization();
const {
handleSubmit,
@@ -101,6 +103,7 @@ export const GcpConnectionForm = ({ appConnection, onSubmit }: Props) => {
isError={Boolean(error?.message)}
label="Service Account Email"
className="group"
helperText={`Service account email must be prefixed with "${currentOrg.id.split("-").slice(0, 2).join("-")}".`}
>
<SecretInput
containerClassName="text-gray-400 group-focus-within:!border-primary-400/50 border border-mineshaft-500 bg-mineshaft-900 px-2.5 py-1.5"