mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-09-22 13:39:35 +00:00
Merge pull request #1689 from akhilmhdh/dynamic-secret/mysql
Dynamic secret mysql support
This commit is contained in:
@@ -249,7 +249,7 @@ export const dynamicSecretLeaseServiceFactory = ({
|
|||||||
|
|
||||||
if ((revokeResponse as { error?: Error })?.error) {
|
if ((revokeResponse as { error?: Error })?.error) {
|
||||||
const { error } = revokeResponse as { error?: Error };
|
const { error } = revokeResponse as { error?: Error };
|
||||||
logger.error("Failed to revoke lease", { error: error?.message });
|
logger.error(error?.message, "Failed to revoke lease");
|
||||||
const deletedDynamicSecretLease = await dynamicSecretLeaseDAL.updateById(dynamicSecretLease.id, {
|
const deletedDynamicSecretLease = await dynamicSecretLeaseDAL.updateById(dynamicSecretLease.id, {
|
||||||
status: DynamicSecretLeaseStatus.FailedDeletion,
|
status: DynamicSecretLeaseStatus.FailedDeletion,
|
||||||
statusDetails: error?.message?.slice(0, 255)
|
statusDetails: error?.message?.slice(0, 255)
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
export enum SqlProviders {
|
export enum SqlProviders {
|
||||||
Postgres = "postgres"
|
Postgres = "postgres",
|
||||||
|
MySQL = "mysql2"
|
||||||
}
|
}
|
||||||
|
|
||||||
export const DynamicSecretSqlDBSchema = z.object({
|
export const DynamicSecretSqlDBSchema = z.object({
|
||||||
@@ -13,7 +14,7 @@ export const DynamicSecretSqlDBSchema = z.object({
|
|||||||
password: z.string(),
|
password: z.string(),
|
||||||
creationStatement: z.string(),
|
creationStatement: z.string(),
|
||||||
revocationStatement: z.string(),
|
revocationStatement: z.string(),
|
||||||
renewStatement: z.string(),
|
renewStatement: z.string().optional(),
|
||||||
ca: z.string().optional()
|
ca: z.string().optional()
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -48,10 +48,10 @@ export const SqlDatabaseProvider = (): TDynamicProviderFns => {
|
|||||||
host: providerInputs.host,
|
host: providerInputs.host,
|
||||||
user: providerInputs.username,
|
user: providerInputs.username,
|
||||||
password: providerInputs.password,
|
password: providerInputs.password,
|
||||||
connectionTimeoutMillis: EXTERNAL_REQUEST_TIMEOUT,
|
|
||||||
ssl,
|
ssl,
|
||||||
pool: { min: 0, max: 1 }
|
pool: { min: 0, max: 1 }
|
||||||
}
|
},
|
||||||
|
acquireConnectionTimeout: EXTERNAL_REQUEST_TIMEOUT
|
||||||
});
|
});
|
||||||
return db;
|
return db;
|
||||||
};
|
};
|
||||||
@@ -73,15 +73,25 @@ export const SqlDatabaseProvider = (): TDynamicProviderFns => {
|
|||||||
|
|
||||||
const username = alphaNumericNanoId(32);
|
const username = alphaNumericNanoId(32);
|
||||||
const password = generatePassword();
|
const password = generatePassword();
|
||||||
|
const { database } = providerInputs;
|
||||||
const expiration = new Date(expireAt).toISOString();
|
const expiration = new Date(expireAt).toISOString();
|
||||||
|
|
||||||
const creationStatement = handlebars.compile(providerInputs.creationStatement, { noEscape: true })({
|
const creationStatement = handlebars.compile(providerInputs.creationStatement, { noEscape: true })({
|
||||||
username,
|
username,
|
||||||
password,
|
password,
|
||||||
expiration
|
expiration,
|
||||||
|
database
|
||||||
});
|
});
|
||||||
|
|
||||||
await db.raw(creationStatement.toString());
|
await db.transaction(async (tx) =>
|
||||||
|
Promise.all(
|
||||||
|
creationStatement
|
||||||
|
.toString()
|
||||||
|
.split(";")
|
||||||
|
.filter(Boolean)
|
||||||
|
.map((query) => tx.raw(query))
|
||||||
|
)
|
||||||
|
);
|
||||||
await db.destroy();
|
await db.destroy();
|
||||||
return { entityId: username, data: { DB_USERNAME: username, DB_PASSWORD: password } };
|
return { entityId: username, data: { DB_USERNAME: username, DB_PASSWORD: password } };
|
||||||
};
|
};
|
||||||
@@ -91,9 +101,18 @@ export const SqlDatabaseProvider = (): TDynamicProviderFns => {
|
|||||||
const db = await getClient(providerInputs);
|
const db = await getClient(providerInputs);
|
||||||
|
|
||||||
const username = entityId;
|
const username = entityId;
|
||||||
|
const { database } = providerInputs;
|
||||||
|
|
||||||
const revokeStatement = handlebars.compile(providerInputs.revocationStatement)({ username });
|
const revokeStatement = handlebars.compile(providerInputs.revocationStatement)({ username, database });
|
||||||
await db.raw(revokeStatement);
|
await db.transaction(async (tx) =>
|
||||||
|
Promise.all(
|
||||||
|
revokeStatement
|
||||||
|
.toString()
|
||||||
|
.split(";")
|
||||||
|
.filter(Boolean)
|
||||||
|
.map((query) => tx.raw(query))
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
await db.destroy();
|
await db.destroy();
|
||||||
return { entityId: username };
|
return { entityId: username };
|
||||||
@@ -105,9 +124,19 @@ export const SqlDatabaseProvider = (): TDynamicProviderFns => {
|
|||||||
|
|
||||||
const username = entityId;
|
const username = entityId;
|
||||||
const expiration = new Date(expireAt).toISOString();
|
const expiration = new Date(expireAt).toISOString();
|
||||||
|
const { database } = providerInputs;
|
||||||
|
|
||||||
const renewStatement = handlebars.compile(providerInputs.renewStatement)({ username, expiration });
|
const renewStatement = handlebars.compile(providerInputs.renewStatement)({ username, expiration, database });
|
||||||
await db.raw(renewStatement);
|
if (renewStatement)
|
||||||
|
await db.transaction(async (tx) =>
|
||||||
|
Promise.all(
|
||||||
|
renewStatement
|
||||||
|
.toString()
|
||||||
|
.split(";")
|
||||||
|
.filter(Boolean)
|
||||||
|
.map((query) => tx.raw(query))
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
await db.destroy();
|
await db.destroy();
|
||||||
return { entityId: username };
|
return { entityId: username };
|
||||||
|
|||||||
@@ -20,7 +20,8 @@ export enum DynamicSecretProviders {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export enum SqlProviders {
|
export enum SqlProviders {
|
||||||
Postgres = "postgres"
|
Postgres = "postgres",
|
||||||
|
MySql = "mysql2"
|
||||||
}
|
}
|
||||||
|
|
||||||
export type TDynamicSecretProvider = {
|
export type TDynamicSecretProvider = {
|
||||||
@@ -34,7 +35,7 @@ export type TDynamicSecretProvider = {
|
|||||||
password: string;
|
password: string;
|
||||||
creationStatement: string;
|
creationStatement: string;
|
||||||
revocationStatement: string;
|
revocationStatement: string;
|
||||||
renewStatement: string;
|
renewStatement?: string;
|
||||||
ca?: string | undefined;
|
ca?: string | undefined;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
+31
-9
@@ -31,7 +31,7 @@ const formSchema = z.object({
|
|||||||
password: z.string().min(1),
|
password: z.string().min(1),
|
||||||
creationStatement: z.string().min(1),
|
creationStatement: z.string().min(1),
|
||||||
revocationStatement: z.string().min(1),
|
revocationStatement: z.string().min(1),
|
||||||
renewStatement: z.string().min(1),
|
renewStatement: z.string().optional(),
|
||||||
ca: z.string().optional()
|
ca: z.string().optional()
|
||||||
}),
|
}),
|
||||||
defaultTTL: z.string().superRefine((val, ctx) => {
|
defaultTTL: z.string().superRefine((val, ctx) => {
|
||||||
@@ -66,6 +66,26 @@ type Props = {
|
|||||||
environment: string;
|
environment: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getSqlStatements = (provider: SqlProviders) => {
|
||||||
|
if (provider === SqlProviders.MySql) {
|
||||||
|
return {
|
||||||
|
creationStatement:
|
||||||
|
"CREATE USER \"{{username}}\"@'%' IDENTIFIED BY '{{password}}';\nGRANT ALL ON \"{{database}}\".* TO \"{{username}}\"@'%';",
|
||||||
|
renewStatement: "",
|
||||||
|
revocationStatement:
|
||||||
|
'REVOKE ALL PRIVILEGES ON "{{database}}".* FROM "{{username}}"@\'%\';\nDROP USER "{{username}}"@\'%\';'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
creationStatement:
|
||||||
|
"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 ROLE "{{username}}";'
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
export const SqlDatabaseInputForm = ({
|
export const SqlDatabaseInputForm = ({
|
||||||
onCompleted,
|
onCompleted,
|
||||||
onCancel,
|
onCancel,
|
||||||
@@ -75,18 +95,13 @@ export const SqlDatabaseInputForm = ({
|
|||||||
}: Props) => {
|
}: Props) => {
|
||||||
const {
|
const {
|
||||||
control,
|
control,
|
||||||
|
setValue,
|
||||||
formState: { isSubmitting },
|
formState: { isSubmitting },
|
||||||
handleSubmit
|
handleSubmit
|
||||||
} = useForm<TForm>({
|
} = useForm<TForm>({
|
||||||
resolver: zodResolver(formSchema),
|
resolver: zodResolver(formSchema),
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
provider: {
|
provider: getSqlStatements(SqlProviders.Postgres)
|
||||||
creationStatement:
|
|
||||||
"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 ROLE "{{username}}";'
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -182,10 +197,17 @@ export const SqlDatabaseInputForm = ({
|
|||||||
<FormControl isError={Boolean(error?.message)} errorText={error?.message}>
|
<FormControl isError={Boolean(error?.message)} errorText={error?.message}>
|
||||||
<Select
|
<Select
|
||||||
value={value}
|
value={value}
|
||||||
onValueChange={(val) => onChange(val)}
|
onValueChange={(val) => {
|
||||||
|
onChange(val);
|
||||||
|
const sqlStatment = getSqlStatements(val as SqlProviders);
|
||||||
|
setValue("provider.creationStatement", sqlStatment.creationStatement);
|
||||||
|
setValue("provider.renewStatement", sqlStatment.renewStatement);
|
||||||
|
setValue("provider.revocationStatement", sqlStatment.revocationStatement);
|
||||||
|
}}
|
||||||
className="w-full border border-mineshaft-500"
|
className="w-full border border-mineshaft-500"
|
||||||
>
|
>
|
||||||
<SelectItem value={SqlProviders.Postgres}>PostgreSQL</SelectItem>
|
<SelectItem value={SqlProviders.Postgres}>PostgreSQL</SelectItem>
|
||||||
|
<SelectItem value={SqlProviders.MySql}>MySQL</SelectItem>
|
||||||
</Select>
|
</Select>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
)}
|
)}
|
||||||
|
|||||||
+9
-3
@@ -32,7 +32,7 @@ const formSchema = z.object({
|
|||||||
password: z.string().min(1),
|
password: z.string().min(1),
|
||||||
creationStatement: z.string().min(1),
|
creationStatement: z.string().min(1),
|
||||||
revocationStatement: z.string().min(1),
|
revocationStatement: z.string().min(1),
|
||||||
renewStatement: z.string().min(1),
|
renewStatement: z.string().optional(),
|
||||||
ca: z.string().optional()
|
ca: z.string().optional()
|
||||||
})
|
})
|
||||||
.partial(),
|
.partial(),
|
||||||
@@ -94,7 +94,7 @@ export const EditDynamicSecretSqlProviderForm = ({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const updateDynamicSecret = useUpdateDynamicSecret();
|
const updateDynamicSecret = useUpdateDynamicSecret();
|
||||||
|
|
||||||
const handleUpdateDynamicSecret = async ({ inputs, maxTTL, defaultTTL, newName }: TForm) => {
|
const handleUpdateDynamicSecret = async ({ inputs, maxTTL, defaultTTL, newName }: TForm) => {
|
||||||
@@ -186,11 +186,13 @@ export const EditDynamicSecretSqlProviderForm = ({
|
|||||||
render={({ field: { value, onChange }, fieldState: { error } }) => (
|
render={({ field: { value, onChange }, fieldState: { error } }) => (
|
||||||
<FormControl isError={Boolean(error?.message)} errorText={error?.message}>
|
<FormControl isError={Boolean(error?.message)} errorText={error?.message}>
|
||||||
<Select
|
<Select
|
||||||
|
isDisabled
|
||||||
value={value}
|
value={value}
|
||||||
onValueChange={(val) => onChange(val)}
|
onValueChange={(val) => onChange(val)}
|
||||||
className="w-full border border-mineshaft-500"
|
className="w-full border border-mineshaft-500"
|
||||||
>
|
>
|
||||||
<SelectItem value={SqlProviders.Postgres}>PostgreSQL</SelectItem>
|
<SelectItem value={SqlProviders.Postgres}>PostgreSQL</SelectItem>
|
||||||
|
<SelectItem value={SqlProviders.MySql}>MySQL</SelectItem>
|
||||||
</Select>
|
</Select>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
)}
|
)}
|
||||||
@@ -221,7 +223,11 @@ export const EditDynamicSecretSqlProviderForm = ({
|
|||||||
isError={Boolean(error?.message)}
|
isError={Boolean(error?.message)}
|
||||||
errorText={error?.message}
|
errorText={error?.message}
|
||||||
>
|
>
|
||||||
<Input {...field} type="number" onChange={(el) => field.onChange(parseInt(el.target.value, 10))} />
|
<Input
|
||||||
|
{...field}
|
||||||
|
type="number"
|
||||||
|
onChange={(el) => field.onChange(parseInt(el.target.value, 10))}
|
||||||
|
/>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|||||||
Reference in New Issue
Block a user