fix(server): resolved failing to use dynamic secret due to superuser

This commit is contained in:
Akhil Mohan
2024-03-27 13:46:16 +05:30
parent 99f8799ff4
commit 0793e70c26
6 changed files with 268 additions and 241 deletions

View File

@@ -90,7 +90,17 @@ export const secretRotationDbFn = async ({
const appCfg = getConfig();
const ssl = ca ? { rejectUnauthorized: false, ca } : undefined;
if (host === "localhost" || host === "127.0.0.1" || getDbConnectionHost(appCfg.DB_CONNECTION_URI) === host)
const dbHost = appCfg.DB_HOST || getDbConnectionHost(appCfg.DB_CONNECTION_URI);
if (
host === "localhost" ||
host === "127.0.0.1" ||
// database infisical uses
dbHost === host ||
// internal ips
host === "host.docker.internal" ||
host.match(/^10\.\d+\.\d+\.\d+/) ||
host.match(/^192\.168\.\d+\.\d+/)
)
throw new Error("Invalid db host");
const db = knex({

View File

@@ -23,7 +23,17 @@ export const SqlDatabaseProvider = (): TDynamicProviderFns => {
const dbHost = appCfg.DB_HOST || getDbConnectionHost(appCfg.DB_CONNECTION_URI);
const providerInputs = await DynamicSecretSqlDBSchema.parseAsync(inputs);
if (providerInputs.host === "localhost" || providerInputs.host === "127.0.0.1" || dbHost === providerInputs.host)
if (
// localhost
providerInputs.host === "localhost" ||
providerInputs.host === "127.0.0.1" ||
// database infisical uses
dbHost === providerInputs.host ||
// internal ips
providerInputs.host === "host.docker.internal" ||
providerInputs.host.match(/^10\.\d+\.\d+\.\d+/) ||
providerInputs.host.match(/^192\.168\.\d+\.\d+/)
)
throw new BadRequestError({ message: "Invalid db host" });
return providerInputs;
};

View File

@@ -1,4 +1,4 @@
import { forwardRef, ReactNode } from "react";
import { CSSProperties, forwardRef, ReactNode } from "react";
import { twMerge } from "tailwind-merge";
export type CardTitleProps = {
@@ -31,10 +31,13 @@ export const CardFooter = ({ children, className }: CardFooterProps) => (
export type CardBodyProps = {
children: ReactNode;
className?: string;
style?: CSSProperties;
};
export const CardBody = ({ children, className }: CardBodyProps) => (
<div className={twMerge("px-6 pb-6 pt-0", className)}>{children}</div>
export const CardBody = ({ children, className, style }: CardBodyProps) => (
<div className={twMerge("px-6 pb-6 pt-0", className)} style={style}>
{children}
</div>
);
export type CardProps = {
@@ -44,10 +47,14 @@ export type CardProps = {
isRounded?: boolean;
isPlain?: boolean;
isHoverable?: boolean;
style?: CSSProperties;
};
export const Card = forwardRef<HTMLDivElement, CardProps>(
({ children, isFullHeight, isRounded, isHoverable, isPlain, className }, ref): JSX.Element => {
(
{ children, isFullHeight, isRounded, isHoverable, isPlain, className, style },
ref
): JSX.Element => {
return (
<div
ref={ref}
@@ -59,6 +66,7 @@ export const Card = forwardRef<HTMLDivElement, CardProps>(
isHoverable && "hover:shadow-xl",
className
)}
style={style}
>
{children}
</div>

View File

@@ -29,12 +29,15 @@ export const ModalContent = forwardRef<HTMLDivElement, ModalContentProps>(
<Card
isRounded
className={twMerge(
"thin-scrollbar fixed top-1/2 left-1/2 z-30 max-h-screen max-w-xl -translate-y-2/4 -translate-x-2/4 animate-popIn border border-mineshaft-600 drop-shadow-2xl dark:[color-scheme:dark]",
"thin-scrollbar fixed top-1/2 left-1/2 z-30 max-w-xl -translate-y-2/4 -translate-x-2/4 animate-popIn border border-mineshaft-600 drop-shadow-2xl dark:[color-scheme:dark]",
className
)}
style={{ maxHeight: "90%" }}
>
{title && <CardTitle subTitle={subTitle}>{title}</CardTitle>}
<CardBody>{children}</CardBody>
<CardBody className="overflow-y-auto overflow-x-hidden" style={{ maxHeight: "90%" }}>
{children}
</CardBody>
{footerContent && <CardFooter>{footerContent}</CardFooter>}
<DialogPrimitive.Close aria-label="Close" asChild onClick={onClose}>
<IconButton

View File

@@ -11,7 +11,7 @@ import { SqlDatabaseInputForm } from "./SqlDatabaseInputForm";
type Props = {
isOpen?: boolean;
onToggle: (isOpen: boolean) => void;
projectSlug:string;
projectSlug: string;
environment: string;
secretPath: string;
};
@@ -42,7 +42,7 @@ export const CreateDynamicSecretForm = ({
<ModalContent
title="Dynamic secret setup"
subTitle="Configure dynamic secret parameters"
className="my-4 max-h-screen max-w-3xl overflow-scroll"
className="my-4 max-w-3xl"
>
<AnimatePresence exitBeforeEnter>
{wizardStep === WizardSteps.SelectProvider && (
@@ -56,7 +56,7 @@ export const CreateDynamicSecretForm = ({
<div className="mb-4 text-mineshaft-300">Select a service to connect to:</div>
<div className="flex items-center space-x-4">
<div
className="flex h-32 w-32 cursor-pointer flex-col items-center space-y-4 rounded border border-mineshaft-500 bg-bunker-600 p-6 transition-all hover:bg-primary/10 hover:border-primary/70 hover:text-white"
className="flex h-32 w-32 cursor-pointer flex-col items-center space-y-4 rounded border border-mineshaft-500 bg-bunker-600 p-6 transition-all hover:border-primary/70 hover:bg-primary/10 hover:text-white"
role="button"
tabIndex={0}
onClick={() => {

View File

@@ -25,7 +25,7 @@ const formSchema = z.object({
provider: z.object({
client: z.nativeEnum(SqlProviders),
host: z.string().toLowerCase().min(1),
port: z.number(),
port: z.coerce.number(),
database: z.string().min(1),
username: z.string().min(1),
password: z.string().min(1),
@@ -54,9 +54,7 @@ const formSchema = z.object({
if (valMs > 24 * 60 * 60 * 1000)
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" });
}),
name: z
.string()
.refine((val) => val.toLowerCase() === val, "Must be lowercase")
name: z.string().refine((val) => val.toLowerCase() === val, "Must be lowercase")
});
type TForm = z.infer<typeof formSchema>;
@@ -84,14 +82,14 @@ export const SqlDatabaseInputForm = ({
defaultValues: {
provider: {
creationStatement:
"CREATE USER \"{{username}}\" WITH SUPERUSER ENCRYPTED PASSWORD '{{password}}' VALID UNTIL '{{expiration}}';\nGRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO \"{{username}}\";",
"CREATE USER \"{{username}}\" WITH ENCRYPTED PASSWORD '{{password}}' VALID UNTIL '{{expiration}}';\nGRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO \"{{username}}\";",
renewStatement: "ALTER ROLE \"{{username}}\" VALID UNTIL '{{expiration}}';",
revocationStatement:
'REVOKE ALL PRIVILEGES ON ALL TABLES IN SCHEMA public FROM "{{username}}";\nDROP OWNED BY "{{username}}";\nDROP ROLE "{{username}}";'
}
}
});
const createDynamicSecret = useCreateDynamicSecret();
const handleCreateDynamicSecret = async ({ name, maxTTL, provider, defaultTTL }: TForm) => {
@@ -119,238 +117,236 @@ export const SqlDatabaseInputForm = ({
return (
<div>
<form onSubmit={handleSubmit(handleCreateDynamicSecret)}>
<div className="flex items-center space-x-2">
<div className="flex-grow">
<Controller
control={control}
defaultValue=""
name="name"
render={({ field, fieldState: { error } }) => (
<FormControl
label="Secret Name"
isError={Boolean(error)}
errorText={error?.message}
>
<Input {...field} placeholder="dynamic-postgres" />
</FormControl>
)}
/>
</div>
<div className="w-32">
<Controller
control={control}
name="defaultTTL"
defaultValue="1h"
render={({ field, fieldState: { error } }) => (
<FormControl
label={<TtlFormLabel label="Default TTL" />}
isError={Boolean(error?.message)}
errorText={error?.message}
>
<Input {...field} />
</FormControl>
)}
/>
</div>
<div className="w-32">
<Controller
control={control}
name="maxTTL"
defaultValue="24h"
render={({ field, fieldState: { error } }) => (
<FormControl
label={<TtlFormLabel label="Max TTL" />}
isError={Boolean(error?.message)}
errorText={error?.message}
>
<Input {...field} />
</FormControl>
)}
/>
</div>
</div>
<div>
<div className="mb-4 mt-4 border-b border-mineshaft-500 pb-2 pl-1 font-medium text-mineshaft-200">
Configuration
<div className="flex items-center space-x-2">
<div className="flex-grow">
<Controller
control={control}
defaultValue=""
name="name"
render={({ field, fieldState: { error } }) => (
<FormControl
label="Secret Name"
isError={Boolean(error)}
errorText={error?.message}
>
<Input {...field} placeholder="dynamic-postgres" />
</FormControl>
)}
/>
</div>
<div className="w-32">
<Controller
control={control}
name="defaultTTL"
defaultValue="1h"
render={({ field, fieldState: { error } }) => (
<FormControl
label={<TtlFormLabel label="Default TTL" />}
isError={Boolean(error?.message)}
errorText={error?.message}
>
<Input {...field} />
</FormControl>
)}
/>
</div>
<div className="w-32">
<Controller
control={control}
name="maxTTL"
defaultValue="24h"
render={({ field, fieldState: { error } }) => (
<FormControl
label={<TtlFormLabel label="Max TTL" />}
isError={Boolean(error?.message)}
errorText={error?.message}
>
<Input {...field} />
</FormControl>
)}
/>
</div>
</div>
<div className="flex flex-col">
<div className="pl-1 pb-0.5 text-sm text-mineshaft-400">Service</div>
<Controller
control={control}
name="provider.client"
defaultValue={SqlProviders.Postgres}
render={({ field: { value, onChange }, fieldState: { error } }) => (
<FormControl isError={Boolean(error?.message)} errorText={error?.message}>
<Select
value={value}
onValueChange={(val) => onChange(val)}
className="w-full border border-mineshaft-500"
>
<SelectItem value={SqlProviders.Postgres}>PostgreSQL</SelectItem>
</Select>
</FormControl>
)}
/>
<div className="flex items-center space-x-2">
<Controller
control={control}
name="provider.host"
defaultValue=""
render={({ field, fieldState: { error } }) => (
<FormControl
label="Host"
className="flex-grow"
isError={Boolean(error?.message)}
errorText={error?.message}
>
<Input {...field} />
</FormControl>
)}
/>
<Controller
control={control}
name="provider.port"
defaultValue={5432}
render={({ field, fieldState: { error } }) => (
<FormControl
label="Port"
isError={Boolean(error?.message)}
errorText={error?.message}
>
<Input
{...field}
type="number"
onChange={(el) => field.onChange(parseInt(el.target.value, 10))}
/>
</FormControl>
)}
/>
<div>
<div className="mb-4 mt-4 border-b border-mineshaft-500 pb-2 pl-1 font-medium text-mineshaft-200">
Configuration
</div>
<div className="flex items-center space-x-2">
<div className="flex flex-col">
<div className="pl-1 pb-0.5 text-sm text-mineshaft-400">Service</div>
<Controller
control={control}
name="provider.username"
defaultValue=""
render={({ field, fieldState: { error } }) => (
<FormControl
label="User"
isError={Boolean(error?.message)}
errorText={error?.message}
>
<Input {...field} />
name="provider.client"
defaultValue={SqlProviders.Postgres}
render={({ field: { value, onChange }, fieldState: { error } }) => (
<FormControl isError={Boolean(error?.message)} errorText={error?.message}>
<Select
value={value}
onValueChange={(val) => onChange(val)}
className="w-full border border-mineshaft-500"
>
<SelectItem value={SqlProviders.Postgres}>PostgreSQL</SelectItem>
</Select>
</FormControl>
)}
/>
<Controller
control={control}
name="provider.password"
render={({ field, fieldState: { error } }) => (
<FormControl
label="Password"
isError={Boolean(error?.message)}
errorText={error?.message}
>
<Input {...field} type="password" />
</FormControl>
)}
/>
<Controller
control={control}
name="provider.database"
defaultValue="default"
render={({ field, fieldState: { error } }) => (
<FormControl
label="Database Name"
isError={Boolean(error?.message)}
errorText={error?.message}
>
<Input {...field} />
</FormControl>
)}
/>
</div>
<div>
<Controller
control={control}
name="provider.ca"
render={({ field, fieldState: { error } }) => (
<FormControl
isOptional
label="CA(SSL)"
isError={Boolean(error?.message)}
errorText={error?.message}
>
<SecretInput
{...field}
containerClassName="text-bunker-300 hover:border-primary-400/50 border border-mineshaft-600 bg-mineshaft-900 px-2 py-1.5"
/>
</FormControl>
)}
/>
<Accordion type="single" collapsible className="mb-2 w-full bg-mineshaft-700">
<AccordionItem value="advance-statements">
<AccordionTrigger>Modify SQL Statements</AccordionTrigger>
<AccordionContent>
<Controller
control={control}
name="provider.creationStatement"
render={({ field, fieldState: { error } }) => (
<FormControl
label="Creation Statement"
isError={Boolean(error?.message)}
errorText={error?.message}
helperText="username, password and expiration are dynamically provisioned"
>
<TextArea
{...field}
reSize="none"
rows={3}
className="border-mineshaft-600 bg-mineshaft-900 text-sm"
/>
</FormControl>
)}
/>
<Controller
control={control}
name="provider.revocationStatement"
render={({ field, fieldState: { error } }) => (
<FormControl
label="Revocation Statement"
isError={Boolean(error?.message)}
errorText={error?.message}
helperText="username is dynamically provisioned"
>
<TextArea
{...field}
reSize="none"
rows={3}
className="border-mineshaft-600 bg-mineshaft-900 text-sm"
/>
</FormControl>
)}
/>
<Controller
control={control}
name="provider.renewStatement"
render={({ field, fieldState: { error } }) => (
<FormControl
label="Renew Statement"
helperText="username and expiration are dynamically provisioned"
isError={Boolean(error?.message)}
errorText={error?.message}
>
<TextArea
{...field}
reSize="none"
rows={3}
className="border-mineshaft-600 bg-mineshaft-900 text-sm"
/>
</FormControl>
)}
/>
</AccordionContent>
</AccordionItem>
</Accordion>
<div className="flex items-center space-x-2">
<Controller
control={control}
name="provider.host"
defaultValue=""
render={({ field, fieldState: { error } }) => (
<FormControl
label="Host"
className="flex-grow"
isError={Boolean(error?.message)}
errorText={error?.message}
>
<Input {...field} />
</FormControl>
)}
/>
<Controller
control={control}
name="provider.port"
defaultValue={5432}
render={({ field, fieldState: { error } }) => (
<FormControl
label="Port"
isError={Boolean(error?.message)}
errorText={error?.message}
>
<Input {...field} type="number" />
</FormControl>
)}
/>
</div>
<div className="flex items-center space-x-2">
<Controller
control={control}
name="provider.username"
defaultValue=""
render={({ field, fieldState: { error } }) => (
<FormControl
label="User"
isError={Boolean(error?.message)}
errorText={error?.message}
>
<Input {...field} />
</FormControl>
)}
/>
<Controller
control={control}
name="provider.password"
render={({ field, fieldState: { error } }) => (
<FormControl
label="Password"
isError={Boolean(error?.message)}
errorText={error?.message}
>
<Input {...field} type="password" />
</FormControl>
)}
/>
<Controller
control={control}
name="provider.database"
defaultValue="default"
render={({ field, fieldState: { error } }) => (
<FormControl
label="Database Name"
isError={Boolean(error?.message)}
errorText={error?.message}
>
<Input {...field} />
</FormControl>
)}
/>
</div>
<div>
<Controller
control={control}
name="provider.ca"
render={({ field, fieldState: { error } }) => (
<FormControl
isOptional
label="CA(SSL)"
isError={Boolean(error?.message)}
errorText={error?.message}
>
<SecretInput
{...field}
containerClassName="text-bunker-300 hover:border-primary-400/50 border border-mineshaft-600 bg-mineshaft-900 px-2 py-1.5"
/>
</FormControl>
)}
/>
<Accordion type="single" collapsible className="mb-2 w-full bg-mineshaft-700">
<AccordionItem value="advance-statements">
<AccordionTrigger>Modify SQL Statements</AccordionTrigger>
<AccordionContent>
<Controller
control={control}
name="provider.creationStatement"
render={({ field, fieldState: { error } }) => (
<FormControl
label="Creation Statement"
isError={Boolean(error?.message)}
errorText={error?.message}
helperText="username, password and expiration are dynamically provisioned"
>
<TextArea
{...field}
reSize="none"
rows={3}
className="border-mineshaft-600 bg-mineshaft-900 text-sm"
/>
</FormControl>
)}
/>
<Controller
control={control}
name="provider.revocationStatement"
render={({ field, fieldState: { error } }) => (
<FormControl
label="Revocation Statement"
isError={Boolean(error?.message)}
errorText={error?.message}
helperText="username is dynamically provisioned"
>
<TextArea
{...field}
reSize="none"
rows={3}
className="border-mineshaft-600 bg-mineshaft-900 text-sm"
/>
</FormControl>
)}
/>
<Controller
control={control}
name="provider.renewStatement"
render={({ field, fieldState: { error } }) => (
<FormControl
label="Renew Statement"
helperText="username and expiration are dynamically provisioned"
isError={Boolean(error?.message)}
errorText={error?.message}
>
<TextArea
{...field}
reSize="none"
rows={3}
className="border-mineshaft-600 bg-mineshaft-900 text-sm"
/>
</FormControl>
)}
/>
</AccordionContent>
</AccordionItem>
</Accordion>
</div>
</div>
</div>
</div>