improvements: add ssl options to sql connections and update ui/docs

This commit is contained in:
Scott Wilson
2025-04-04 17:51:05 -07:00
parent 46e72e9fba
commit 0ee1b425df
25 changed files with 320 additions and 173 deletions
@@ -7,7 +7,20 @@ export const MSSQL_CREDENTIALS_ROTATION_LIST_OPTION: TSecretRotationV2ListItem =
type: SecretRotation.MsSqlCredentials, type: SecretRotation.MsSqlCredentials,
connection: AppConnection.MsSql, connection: AppConnection.MsSql,
template: { template: {
createUserStatement: `CREATE LOGIN [my_mssql_user] WITH PASSWORD = 'my_temporary_password'; CREATE USER [my_mssql_user] FOR LOGIN [my_mssql_user]; GRANT SELECT, INSERT, UPDATE, DELETE ON SCHEMA::dbo TO [my_mssql_user];`, createUserStatement: `-- Create login at the server level
CREATE LOGIN [infisical_user] WITH PASSWORD = 'my-password';
-- Grant server-level connect permission
GRANT CONNECT SQL TO [infisical_user];
-- Switch to the database where you want to create the user
USE my_database;
-- Create the database user mapped to the login
CREATE USER [infisical_user] FOR LOGIN [infisical_user];
-- Grant permissions to the user on the schema in this database
GRANT SELECT, INSERT, UPDATE, DELETE ON SCHEMA::dbo TO [infisical_user];`,
secretsMapping: { secretsMapping: {
username: "MSSQL_DB_USERNAME", username: "MSSQL_DB_USERNAME",
password: "MSSQL_DB_PASSWORD" password: "MSSQL_DB_PASSWORD"
@@ -7,7 +7,14 @@ export const POSTGRES_CREDENTIALS_ROTATION_LIST_OPTION: TSecretRotationV2ListIte
type: SecretRotation.PostgresCredentials, type: SecretRotation.PostgresCredentials,
connection: AppConnection.Postgres, connection: AppConnection.Postgres,
template: { template: {
createUserStatement: `CREATE USER "my_pg_user" WITH ENCRYPTED PASSWORD 'temporary_password'; GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO "my_pg_user";`, createUserStatement: `-- create user role
CREATE USER infisical_user WITH ENCRYPTED PASSWORD 'temporary_password';
-- grant database connection permissions
GRANT CONNECT ON DATABASE my_database TO infisical_user;
-- grant relevant table permissions
GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO infisical_user;`,
secretsMapping: { secretsMapping: {
username: "POSTGRES_DB_USERNAME", username: "POSTGRES_DB_USERNAME",
password: "POSTGRES_DB_PASSWORD" password: "POSTGRES_DB_PASSWORD"
+2
View File
@@ -1691,6 +1691,8 @@ export const AppConnections = {
database: "The name of the database to connect to.", database: "The name of the database to connect to.",
username: "The username to connect to the database with.", username: "The username to connect to the database with.",
password: "The password to connect to the database with.", password: "The password to connect to the database with.",
sslEnabled: "Whether or not to use SSL when connecting to the database.",
sslRejectUnauthorized: "Whether or not to reject unauthorized SSL certificates.",
sslCertificate: "The SSL certificate to use for connection." sslCertificate: "The SSL certificate to use for connection."
} }
} }
-2
View File
@@ -59,8 +59,6 @@ const envSchema = z
QUEUE_WORKERS_ENABLED: zodStrBool.default("true"), QUEUE_WORKERS_ENABLED: zodStrBool.default("true"),
HTTPS_ENABLED: zodStrBool, HTTPS_ENABLED: zodStrBool,
ROTATION_DEVELOPMENT_MODE: zodStrBool.default("false").optional(), ROTATION_DEVELOPMENT_MODE: zodStrBool.default("false").optional(),
DB_SSL_REJECT_UNAUTHORIZED: zodStrBool.default("true"),
DB_SSL_REQUIRED: zodStrBool.default("true"),
// smtp options // smtp options
SMTP_HOST: zpStr(z.string().optional()), SMTP_HOST: zpStr(z.string().optional()),
SMTP_IGNORE_TLS: zodStrBool.default("false"), SMTP_IGNORE_TLS: zodStrBool.default("false"),
@@ -29,7 +29,9 @@ export const SanitizedMsSqlConnectionSchema = z.discriminatedUnion("method", [
host: true, host: true,
database: true, database: true,
port: true, port: true,
username: true username: true,
sslEnabled: true,
sslRejectUnauthorized: true
}) })
}) })
]); ]);
@@ -27,7 +27,9 @@ export const SanitizedPostgresConnectionSchema = z.discriminatedUnion("method",
host: true, host: true,
database: true, database: true,
port: true, port: true,
username: true username: true,
sslEnabled: true,
sslRejectUnauthorized: true
}) })
}) })
]); ]);
@@ -5,7 +5,6 @@ import {
TSqlCredentialsRotationGeneratedCredentials, TSqlCredentialsRotationGeneratedCredentials,
TSqlCredentialsRotationWithConnection TSqlCredentialsRotationWithConnection
} from "@app/ee/services/secret-rotation-v2/shared/sql-credentials/sql-credentials-rotation-types"; } from "@app/ee/services/secret-rotation-v2/shared/sql-credentials/sql-credentials-rotation-types";
import { getConfig } from "@app/lib/config/env";
import { BadRequestError, DatabaseError } from "@app/lib/errors"; import { BadRequestError, DatabaseError } from "@app/lib/errors";
import { alphaNumericNanoId } from "@app/lib/nanoid"; import { alphaNumericNanoId } from "@app/lib/nanoid";
import { AppConnection } from "@app/services/app-connection/app-connection-enums"; import { AppConnection } from "@app/services/app-connection/app-connection-enums";
@@ -21,16 +20,14 @@ const SQL_CONNECTION_CLIENT_MAP = {
const getConnectionConfig = ({ const getConnectionConfig = ({
app, app,
credentials: { sslCertificate, host } credentials: { host, sslCertificate, sslEnabled, sslRejectUnauthorized }
}: Pick<TSqlConnection, "credentials" | "app">) => { }: Pick<TSqlConnection, "credentials" | "app">) => {
const appCfg = getConfig();
switch (app) { switch (app) {
case AppConnection.Postgres: { case AppConnection.Postgres: {
return { return {
ssl: appCfg.DB_SSL_REQUIRED ssl: sslEnabled
? { ? {
rejectUnauthorized: appCfg.DB_SSL_REJECT_UNAUTHORIZED, rejectUnauthorized: sslRejectUnauthorized,
ca: sslCertificate, ca: sslCertificate,
servername: host servername: host
} }
@@ -39,13 +36,13 @@ const getConnectionConfig = ({
} }
case AppConnection.MsSql: { case AppConnection.MsSql: {
return { return {
options: appCfg.DB_SSL_REQUIRED options: sslEnabled
? { ? {
trustServerCertificate: !appCfg.DB_SSL_REJECT_UNAUTHORIZED, trustServerCertificate: !sslRejectUnauthorized,
encrypt: true, encrypt: true,
cryptoCredentialsDetails: sslCertificate ? { ca: sslCertificate } : {} cryptoCredentialsDetails: sslCertificate ? { ca: sslCertificate } : {}
} }
: undefined : { encrypt: false }
}; };
} }
default: default:
@@ -8,5 +8,12 @@ export const BaseSqlUsernameAndPasswordConnectionSchema = z.object({
database: z.string().trim().min(1, "Database required").describe(AppConnections.CREDENTIALS.SQL_CONNECTION.database), database: z.string().trim().min(1, "Database required").describe(AppConnections.CREDENTIALS.SQL_CONNECTION.database),
username: z.string().trim().min(1, "Username required").describe(AppConnections.CREDENTIALS.SQL_CONNECTION.username), username: z.string().trim().min(1, "Username required").describe(AppConnections.CREDENTIALS.SQL_CONNECTION.username),
password: z.string().trim().min(1, "Password required").describe(AppConnections.CREDENTIALS.SQL_CONNECTION.password), password: z.string().trim().min(1, "Password required").describe(AppConnections.CREDENTIALS.SQL_CONNECTION.password),
sslCertificate: z.string().trim().optional().describe(AppConnections.CREDENTIALS.SQL_CONNECTION.sslCertificate) sslEnabled: z.boolean().describe(AppConnections.CREDENTIALS.SQL_CONNECTION.sslEnabled),
sslRejectUnauthorized: z.boolean().describe(AppConnections.CREDENTIALS.SQL_CONNECTION.sslRejectUnauthorized),
sslCertificate: z
.string()
.trim()
.transform((value) => value || undefined)
.optional()
.describe(AppConnections.CREDENTIALS.SQL_CONNECTION.sslCertificate)
}); });
@@ -11,19 +11,19 @@ description: "Learn how to automatically rotate Microsoft SQL Server credentials
An example creation statement might look like: An example creation statement might look like:
```SQL ```SQL
-- create server-level logins -- create server-level logins
CREATE LOGIN infisical_user_1 WITH PASSWORD = 'my-password'; CREATE LOGIN [infisical_user_1] WITH PASSWORD = 'my-password';
CREATE LOGIN infisical_user_2 WITH PASSWORD = 'my-password'; CREATE LOGIN [infisical_user_2] WITH PASSWORD = 'my-password';
GRANT CONNECT SQL TO [infisical_user_1];
GRANT CONNECT SQL TO [infisical_user_2];
-- create database-level users with login from above -- create database-level users with login from above
USE my_database; USE my_database;
CREATE USER infisical_user_1 FOR LOGIN infisical_user_1; CREATE USER [infisical_user_1] FOR LOGIN [infisical_user_1];
CREATE USER infisical_user_2 FOR LOGIN infisical_user_2; CREATE USER [infisical_user_2] FOR LOGIN [infisical_user_2];
GRANT CONNECT TO infisical_user_1;
GRANT CONNECT TO infisical_user_2;
-- grant relevant permissions -- grant relevant permissions
GRANT SELECT, INSERT, UPDATE, DELETE ON SCHEMA::dbo TO infisical_user_1; GRANT SELECT, INSERT, UPDATE, DELETE ON SCHEMA::dbo TO [infisical_user_1];
GRANT SELECT, INSERT, UPDATE, DELETE ON SCHEMA::dbo TO infisical_user_2; GRANT SELECT, INSERT, UPDATE, DELETE ON SCHEMA::dbo TO [infisical_user_2];
``` ```
<Tip> <Tip>
@@ -10,14 +10,16 @@ description: "Learn how to automatically rotate PostgreSQL credentials."
An example creation statement might look like: An example creation statement might look like:
```SQL ```SQL
-- first user -- create user roles
CREATE USER infisical_user_1 WITH ENCRYPTED PASSWORD 'temporary_password'; CREATE USER infisical_user_1 WITH ENCRYPTED PASSWORD 'temporary_password';
GRANT CONNECT ON DATABASE my_database TO infisical_user_1;
GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO infisical_user_1;
-- second user
CREATE USER infisical_user_2 WITH ENCRYPTED PASSWORD 'temporary_password'; CREATE USER infisical_user_2 WITH ENCRYPTED PASSWORD 'temporary_password';
-- grant database connection permissions
GRANT CONNECT ON DATABASE my_database TO infisical_user_1;
GRANT CONNECT ON DATABASE my_database TO infisical_user_2; GRANT CONNECT ON DATABASE my_database TO infisical_user_2;
-- grant relevant table permissions
GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO infisical_user_1;
GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO infisical_user_2; GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO infisical_user_2;
``` ```
Binary file not shown.

Before

Width:  |  Height:  |  Size: 792 KiB

After

Width:  |  Height:  |  Size: 826 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 790 KiB

After

Width:  |  Height:  |  Size: 825 KiB

+15 -7
View File
@@ -11,16 +11,20 @@ Infisical supports connecting to Microsoft SQL Server using database principals.
<Step title="Create a Principal"> <Step title="Create a Principal">
Infisical recommends creating a designated server login and database user in your Microsoft SQL Server database for your connection. Infisical recommends creating a designated server login and database user in your Microsoft SQL Server database for your connection.
```SQL ```SQL
-- create server-level login -- Create login at the server level
CREATE LOGIN infisical_login WITH PASSWORD = 'my-password'; CREATE LOGIN [infisical_app] WITH PASSWORD = 'my-password';
-- create database-level user with login from above -- Grant server-level connect permission
GRANT CONNECT SQL TO [infisical_app];
-- Switch to the specific database where you want to create the user
USE my_database; USE my_database;
CREATE USER infisical_user FOR LOGIN infisical_login;
GRANT CONNECT TO infisical_user; -- Create the database user mapped to the login
CREATE USER [infisical_app] FOR LOGIN [infisical_app];
-- If you intend to use Platform Managed Credentials (see below) -- If you intend to use Platform Managed Credentials (see below)
GRANT ALTER ANY LOGIN TO infisical_login; GRANT ALTER ANY LOGIN TO [infisical_app];
``` ```
</Step> </Step>
<Step title="Grant Relevant Permissions"> <Step title="Grant Relevant Permissions">
@@ -95,6 +99,8 @@ Infisical supports connecting to Microsoft SQL Server using database principals.
"database": "default", "database": "default",
"username": "infisical_login", "username": "infisical_login",
"password": "my-password", "password": "my-password",
"sslEnabled": true,
"sslRejectUnauthorized": true
}, },
}' }'
``` ```
@@ -117,7 +123,9 @@ Infisical supports connecting to Microsoft SQL Server using database principals.
"host": "123.4.5.6", "host": "123.4.5.6",
"port": 1433, "port": 1433,
"database": "default", "database": "default",
"username": "infisical_login" "username": "infisical_login",
"sslEnabled": true,
"sslRejectUnauthorized": true
} }
} }
} }
@@ -12,7 +12,7 @@ Infisical supports connecting to PostgreSQL using a database role.
Infisical recommends creating a designated role in your PostgreSQL database for your connection. Infisical recommends creating a designated role in your PostgreSQL database for your connection.
```SQL ```SQL
-- create user role -- create user role
CREATE ROLE infisical_role WITH LOGIN PASSWORD 'my-password' CREATE ROLE infisical_role WITH LOGIN PASSWORD 'my-password';
-- grant login access to the specified database -- grant login access to the specified database
GRANT CONNECT ON DATABASE my_database TO infisical_role; GRANT CONNECT ON DATABASE my_database TO infisical_role;
@@ -27,6 +27,7 @@ Infisical supports connecting to PostgreSQL using a database role.
<Tab title="Secret Rotation"> <Tab title="Secret Rotation">
For Secret Rotations, your Infisical user will require the ability to alter other users' passwords: For Secret Rotations, your Infisical user will require the ability to alter other users' passwords:
```SQL ```SQL
-- enable permissions to alter login credentials
ALTER ROLE infisical_role WITH CREATEROLE; ALTER ROLE infisical_role WITH CREATEROLE;
``` ```
</Tab> </Tab>
@@ -88,6 +89,8 @@ Infisical supports connecting to PostgreSQL using a database role.
"database": "default", "database": "default",
"username": "infisical_role", "username": "infisical_role",
"password": "my-password", "password": "my-password",
"sslEnabled": true,
"sslRejectUnauthorized": true
}, },
}' }'
``` ```
@@ -110,7 +113,9 @@ Infisical supports connecting to PostgreSQL using a database role.
"host": "123.4.5.6", "host": "123.4.5.6",
"port": 5432, "port": 5432,
"database": "default", "database": "default",
"username": "infisical_role" "username": "infisical_role",
"sslEnabled": true,
"sslRejectUnauthorized": true
} }
} }
} }
@@ -439,19 +439,6 @@ When set, all visits to the Infisical login page will automatically redirect use
information. information.
</Accordion> </Accordion>
## External Database Connections
<ParamField query="DB_SSL_REJECT_UNAUTHORIZED" type="boolean" default="true" optional>
Specify whether external database connections should reject unauthorized SSL certificates.
We highly recommend keeping this value set to `true` for production use-cases.
</ParamField>
<ParamField query="DB_SSL_REQUIRED" type="boolean" default="true" optional>
Specify whether external database connections should require SSL.
We highly recommend keeping this value set to `true` for production use-cases.
</ParamField>
## App Connections ## App Connections
You can configure third-party app connections for re-use across Infisical Projects. You can configure third-party app connections for re-use across Infisical Projects.
@@ -57,7 +57,7 @@ export const SqlRotationParametersFields = () => {
to suit your needs. to suit your needs.
</p> </p>
<p className="mb-3 text-sm"> <p className="mb-3 text-sm">
<pre className="whitespace-pre-wrap rounded border border-mineshaft-700 bg-mineshaft-800 p-2 text-mineshaft-300"> <pre className="max-h-[10rem] overflow-y-auto whitespace-pre-wrap rounded border border-mineshaft-700 bg-mineshaft-800 p-2 text-mineshaft-300">
{rotationOption!.template.createUserStatement} {rotationOption!.template.createUserStatement}
</pre> </pre>
</p> </p>
@@ -4,4 +4,6 @@ export type TBaseSqlConnectionCredentials = {
username: string; username: string;
password: string; password: string;
database: string; database: string;
sslEnabled: boolean;
sslRejectUnauthorized: boolean;
}; };
@@ -22,7 +22,7 @@ import {
type Props = { type Props = {
appConnection?: TAwsConnection; appConnection?: TAwsConnection;
onSubmit: (formData: FormData) => void; onSubmit: (formData: FormData) => Promise<void>;
}; };
const rootSchema = genericAppConnectionFieldsSchema.extend({ const rootSchema = genericAppConnectionFieldsSchema.extend({
@@ -25,7 +25,7 @@ import {
type Props = { type Props = {
appConnection?: TDatabricksConnection; appConnection?: TDatabricksConnection;
onSubmit: (formData: FormData) => void; onSubmit: (formData: FormData) => Promise<void>;
}; };
const rootSchema = genericAppConnectionFieldsSchema.extend({ const rootSchema = genericAppConnectionFieldsSchema.extend({
@@ -28,7 +28,7 @@ import {
type Props = { type Props = {
appConnection?: TGcpConnection; appConnection?: TGcpConnection;
onSubmit: (formData: FormData) => void; onSubmit: (formData: FormData) => Promise<void>;
}; };
const rootSchema = genericAppConnectionFieldsSchema.extend({ const rootSchema = genericAppConnectionFieldsSchema.extend({
@@ -21,7 +21,7 @@ import {
type Props = { type Props = {
appConnection?: THumanitecConnection; appConnection?: THumanitecConnection;
onSubmit: (formData: FormData) => void; onSubmit: (formData: FormData) => Promise<void>;
}; };
const rootSchema = genericAppConnectionFieldsSchema.extend({ const rootSchema = genericAppConnectionFieldsSchema.extend({
@@ -24,7 +24,7 @@ import {
type Props = { type Props = {
appConnection?: TMsSqlConnection; appConnection?: TMsSqlConnection;
onSubmit: (formData: FormData) => void; onSubmit: (formData: FormData) => Promise<void>;
}; };
const rootSchema = genericAppConnectionFieldsSchema.extend({ const rootSchema = genericAppConnectionFieldsSchema.extend({
@@ -44,6 +44,7 @@ type FormData = z.infer<typeof formSchema>;
export const MsSqlConnectionForm = ({ appConnection, onSubmit }: Props) => { export const MsSqlConnectionForm = ({ appConnection, onSubmit }: Props) => {
const isUpdate = Boolean(appConnection); const isUpdate = Boolean(appConnection);
const [showConfirmation, setShowConfirmation] = useState(false); const [showConfirmation, setShowConfirmation] = useState(false);
const [selectedTabIndex, setSelectedTabIndex] = useState(0);
const form = useForm<FormData>({ const form = useForm<FormData>({
resolver: zodResolver(formSchema), resolver: zodResolver(formSchema),
@@ -56,7 +57,9 @@ export const MsSqlConnectionForm = ({ appConnection, onSubmit }: Props) => {
database: "default", database: "default",
username: "", username: "",
password: "", password: "",
sslCertificate: "" sslEnabled: true,
sslRejectUnauthorized: true,
sslCertificate: undefined
} }
} }
}); });
@@ -69,18 +72,23 @@ export const MsSqlConnectionForm = ({ appConnection, onSubmit }: Props) => {
const isPlatformManagedCredentials = appConnection?.isPlatformManagedCredentials ?? false; const isPlatformManagedCredentials = appConnection?.isPlatformManagedCredentials ?? false;
const confirmSubmit = (formData: FormData) => { const confirmSubmit = async (formData: FormData) => {
if (formData.isPlatformManagedCredentials) { if (formData.isPlatformManagedCredentials) {
setShowConfirmation(true); setShowConfirmation(true);
return; return;
} }
onSubmit(formData); await onSubmit(formData);
}; };
return ( return (
<FormProvider {...form}> <FormProvider {...form}>
<form onSubmit={handleSubmit(confirmSubmit)}> <form
onSubmit={(e) => {
setSelectedTabIndex(0);
handleSubmit(confirmSubmit)(e);
}}
>
{!isUpdate && <GenericAppConnectionsFields />} {!isUpdate && <GenericAppConnectionsFields />}
<Controller <Controller
name="method" name="method"
@@ -113,7 +121,11 @@ export const MsSqlConnectionForm = ({ appConnection, onSubmit }: Props) => {
</FormControl> </FormControl>
)} )}
/> />
<SqlConnectionFields isPlatformManagedCredentials={isPlatformManagedCredentials} /> <SqlConnectionFields
isPlatformManagedCredentials={isPlatformManagedCredentials}
selectedTabIndex={selectedTabIndex}
setSelectedTabIndex={setSelectedTabIndex}
/>
{isPlatformManagedCredentials ? ( {isPlatformManagedCredentials ? (
<PlatformManagedNoticeBanner /> <PlatformManagedNoticeBanner />
) : ( ) : (
@@ -21,7 +21,7 @@ import {
type Props = { type Props = {
appConnection?: TPostgresConnection; appConnection?: TPostgresConnection;
onSubmit: (formData: FormData) => void; onSubmit: (formData: FormData) => Promise<void>;
}; };
const rootSchema = genericAppConnectionFieldsSchema.extend({ const rootSchema = genericAppConnectionFieldsSchema.extend({
@@ -41,6 +41,7 @@ type FormData = z.infer<typeof formSchema>;
export const PostgresConnectionForm = ({ appConnection, onSubmit }: Props) => { export const PostgresConnectionForm = ({ appConnection, onSubmit }: Props) => {
const isUpdate = Boolean(appConnection); const isUpdate = Boolean(appConnection);
const [showConfirmation, setShowConfirmation] = useState(false); const [showConfirmation, setShowConfirmation] = useState(false);
const [selectedTabIndex, setSelectedTabIndex] = useState(0);
const form = useForm<FormData>({ const form = useForm<FormData>({
resolver: zodResolver(formSchema), resolver: zodResolver(formSchema),
@@ -53,7 +54,9 @@ export const PostgresConnectionForm = ({ appConnection, onSubmit }: Props) => {
database: "default", database: "default",
username: "", username: "",
password: "", password: "",
sslCertificate: "" sslEnabled: true,
sslRejectUnauthorized: true,
sslCertificate: undefined
} }
} }
}); });
@@ -66,18 +69,23 @@ export const PostgresConnectionForm = ({ appConnection, onSubmit }: Props) => {
const isPlatformManagedCredentials = appConnection?.isPlatformManagedCredentials ?? false; const isPlatformManagedCredentials = appConnection?.isPlatformManagedCredentials ?? false;
const confirmSubmit = (formData: FormData) => { const confirmSubmit = async (formData: FormData) => {
if (formData.isPlatformManagedCredentials) { if (formData.isPlatformManagedCredentials) {
setShowConfirmation(true); setShowConfirmation(true);
return; return;
} }
onSubmit(formData); await onSubmit(formData);
}; };
return ( return (
<FormProvider {...form}> <FormProvider {...form}>
<form onSubmit={handleSubmit(confirmSubmit)}> <form
onSubmit={(e) => {
setSelectedTabIndex(0);
handleSubmit(confirmSubmit)(e);
}}
>
{!isUpdate && <GenericAppConnectionsFields />} {!isUpdate && <GenericAppConnectionsFields />}
<Controller <Controller
name="method" name="method"
@@ -110,7 +118,11 @@ export const PostgresConnectionForm = ({ appConnection, onSubmit }: Props) => {
</FormControl> </FormControl>
)} )}
/> />
<SqlConnectionFields isPlatformManagedCredentials={isPlatformManagedCredentials} /> <SqlConnectionFields
isPlatformManagedCredentials={isPlatformManagedCredentials}
selectedTabIndex={selectedTabIndex}
setSelectedTabIndex={setSelectedTabIndex}
/>
{isPlatformManagedCredentials ? ( {isPlatformManagedCredentials ? (
<PlatformManagedNoticeBanner /> <PlatformManagedNoticeBanner />
) : ( ) : (
@@ -1,122 +1,207 @@
import { Dispatch, SetStateAction } from "react";
import { Controller, useFormContext } from "react-hook-form"; import { Controller, useFormContext } from "react-hook-form";
import { faQuestionCircle } from "@fortawesome/free-solid-svg-icons"; import { faQuestionCircle } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { Tab } from "@headlessui/react";
import { FormControl, Input, SecretInput, Switch, TextArea, Tooltip } from "@app/components/v2"; import { FormControl, Input, SecretInput, Switch, TextArea, Tooltip } from "@app/components/v2";
type Props = { type Props = {
isPlatformManagedCredentials: boolean; isPlatformManagedCredentials: boolean;
selectedTabIndex: number;
setSelectedTabIndex: Dispatch<SetStateAction<number>>;
}; };
export const SqlConnectionFields = ({ isPlatformManagedCredentials }: Props) => { export const SqlConnectionFields = ({
const { control } = useFormContext(); isPlatformManagedCredentials,
setSelectedTabIndex,
selectedTabIndex
}: Props) => {
const { control, watch } = useFormContext();
const sslEnabled = watch("credentials.sslEnabled");
return ( return (
<> <>
<div className="flex items-start gap-2"> <Tab.Group selectedIndex={selectedTabIndex} onChange={setSelectedTabIndex}>
<Controller <Tab.List className="-pb-1 mb-6 w-full border-b-2 border-mineshaft-600">
name="credentials.host" <Tab
control={control} className={({ selected }) =>
shouldUnregister `w-30 -mb-[0.14rem] px-4 py-2 text-sm font-medium outline-none disabled:opacity-60 ${
render={({ field, fieldState: { error } }) => ( selected ? "border-b-2 border-mineshaft-300 text-mineshaft-200" : "text-bunker-300"
<FormControl }`
className="flex-1" }
errorText={error?.message}
isError={Boolean(error?.message)}
label="Host"
>
<Input {...field} isDisabled={isPlatformManagedCredentials} />
</FormControl>
)}
/>
<Controller
name="credentials.database"
control={control}
shouldUnregister
render={({ field, fieldState: { error } }) => (
<FormControl
className="flex-1"
errorText={error?.message}
isError={Boolean(error?.message)}
label="Database Name"
>
<Input {...field} isDisabled={isPlatformManagedCredentials} />
</FormControl>
)}
/>
<Controller
name="credentials.port"
control={control}
shouldUnregister
render={({ field, fieldState: { error } }) => (
<FormControl
className="w-28"
errorText={error?.message}
isError={Boolean(error?.message)}
label="Port"
>
<Input type="number" {...field} isDisabled={isPlatformManagedCredentials} />
</FormControl>
)}
/>
</div>
<div className="flex items-start gap-2">
<Controller
name="credentials.username"
control={control}
shouldUnregister
render={({ field, fieldState: { error } }) => (
<FormControl
errorText={error?.message}
isError={Boolean(error?.message)}
label="Username"
className="flex-1"
>
<Input {...field} isDisabled={isPlatformManagedCredentials} />
</FormControl>
)}
/>
<Controller
name="credentials.password"
control={control}
shouldUnregister
render={({ field: { value, onChange }, fieldState: { error } }) => (
<FormControl
errorText={error?.message}
isError={Boolean(error?.message)}
label="Password"
className="flex-1"
>
<SecretInput
containerClassName="text-gray-400 w-full group-focus-within:!border-primary-400/50 border border-mineshaft-500 bg-mineshaft-900 px-2.5 py-1.5"
value={value}
onChange={(e) => onChange(e.target.value)}
isDisabled={isPlatformManagedCredentials}
/>
</FormControl>
)}
/>
</div>
<Controller
name="credentials.sslCertificate"
control={control}
shouldUnregister
render={({ field, fieldState: { error } }) => (
<FormControl
errorText={error?.message}
isError={Boolean(error?.message)}
label="SSL Certificate"
isOptional
> >
<TextArea Configuration
className="!resize-none" </Tab>
rows={1} <Tab
{...field} className={({ selected }) =>
isDisabled={isPlatformManagedCredentials} `w-30 -mb-[0.14rem] px-4 py-2 text-sm font-medium outline-none disabled:opacity-60 ${
selected ? "border-b-2 border-mineshaft-300 text-mineshaft-200" : "text-bunker-300"
}`
}
>
SSL ({sslEnabled ? "Enabled" : "Disabled"})
</Tab>
</Tab.List>
<Tab.Panels className="mb-4 rounded border border-mineshaft-600 bg-mineshaft-700/70 p-3 pb-0">
<Tab.Panel>
<div className="mt-[0.675rem] flex items-start gap-2">
<Controller
name="credentials.host"
control={control}
render={({ field, fieldState: { error } }) => (
<FormControl
className="flex-1"
errorText={error?.message}
isError={Boolean(error?.message)}
label="Host"
>
<Input {...field} isDisabled={isPlatformManagedCredentials} />
</FormControl>
)}
/>
<Controller
name="credentials.database"
control={control}
render={({ field, fieldState: { error } }) => (
<FormControl
className="flex-1"
errorText={error?.message}
isError={Boolean(error?.message)}
label="Database Name"
>
<Input {...field} isDisabled={isPlatformManagedCredentials} />
</FormControl>
)}
/>
<Controller
name="credentials.port"
control={control}
render={({ field, fieldState: { error } }) => (
<FormControl
className="w-28"
errorText={error?.message}
isError={Boolean(error?.message)}
label="Port"
>
<Input type="number" {...field} isDisabled={isPlatformManagedCredentials} />
</FormControl>
)}
/>
</div>
<div className="mb-[0.675rem] flex items-start gap-2">
<Controller
name="credentials.username"
control={control}
render={({ field, fieldState: { error } }) => (
<FormControl
errorText={error?.message}
isError={Boolean(error?.message)}
label="Username"
className="flex-1"
>
<Input {...field} isDisabled={isPlatformManagedCredentials} />
</FormControl>
)}
/>
<Controller
name="credentials.password"
control={control}
render={({ field: { value, onChange }, fieldState: { error } }) => (
<FormControl
errorText={error?.message}
isError={Boolean(error?.message)}
label="Password"
className="flex-1"
>
<SecretInput
containerClassName="text-gray-400 w-full group-focus-within:!border-primary-400/50 border border-mineshaft-500 bg-mineshaft-900 px-2.5 py-1.5"
value={value}
onChange={(e) => onChange(e.target.value)}
isDisabled={isPlatformManagedCredentials}
/>
</FormControl>
)}
/>
</div>
</Tab.Panel>
<Tab.Panel>
<Controller
name="credentials.sslEnabled"
control={control}
render={({ field: { value, onChange }, fieldState: { error } }) => (
<FormControl isError={Boolean(error?.message)} errorText={error?.message}>
<Switch
className="bg-mineshaft-400/50 shadow-inner data-[state=checked]:bg-green/80"
id="platform-managed"
thumbClassName="bg-mineshaft-800"
isChecked={value}
onCheckedChange={onChange}
isDisabled={isPlatformManagedCredentials}
>
Enable SSL
</Switch>
</FormControl>
)}
/> />
</FormControl>
)} <Controller
/> name="credentials.sslCertificate"
control={control}
render={({ field, fieldState: { error } }) => (
<FormControl
errorText={error?.message}
isError={Boolean(error?.message)}
className={sslEnabled ? "" : "opacity-50"}
label="SSL Certificate"
isOptional
>
<TextArea
className="h-[3.5rem] !resize-none"
{...field}
isDisabled={isPlatformManagedCredentials || !sslEnabled}
/>
</FormControl>
)}
/>
<Controller
name="credentials.sslRejectUnauthorized"
control={control}
render={({ field: { value, onChange }, fieldState: { error } }) => (
<FormControl
className={sslEnabled ? "" : "opacity-50"}
isError={Boolean(error?.message)}
errorText={error?.message}
>
<Switch
className="bg-mineshaft-400/50 shadow-inner data-[state=checked]:bg-green/80"
id="platform-managed"
thumbClassName="bg-mineshaft-800"
isChecked={sslEnabled ? value : false}
onCheckedChange={onChange}
isDisabled={isPlatformManagedCredentials || !sslEnabled}
>
<p className="w-[9.5rem]">
Reject Unauthorized
<Tooltip
className="max-w-md"
content={
<p>
If enabled, Infisical will only connect to servers that have valid,
trusted SSL certificates.
</p>
}
>
<FontAwesomeIcon icon={faQuestionCircle} size="sm" className="ml-1" />
</Tooltip>
</p>
</Switch>
</FormControl>
)}
/>
</Tab.Panel>
</Tab.Panels>
</Tab.Group>
{!isPlatformManagedCredentials && ( {!isPlatformManagedCredentials && (
<Controller <Controller
name="isPlatformManagedCredentials" name="isPlatformManagedCredentials"
@@ -6,5 +6,11 @@ export const BaseSqlUsernameAndPasswordConnectionSchema = z.object({
database: z.string().trim().min(1, "Database required").default("default"), database: z.string().trim().min(1, "Database required").default("default"),
username: z.string().trim().min(1, "Username required"), username: z.string().trim().min(1, "Username required"),
password: z.string().trim().min(1, "Password required"), password: z.string().trim().min(1, "Password required"),
sslCertificate: z.string().trim().optional() sslEnabled: z.boolean().default(true),
sslRejectUnauthorized: z.boolean().default(true),
sslCertificate: z
.string()
.trim()
.transform((value) => value || undefined)
.optional()
}); });