Skip to content

Commit

Permalink
wires up ssh tunnel for postgres in app (#1192)
Browse files Browse the repository at this point in the history
  • Loading branch information
nickzelei authored Jan 27, 2024
1 parent 961698e commit 0ce1116
Show file tree
Hide file tree
Showing 8 changed files with 583 additions and 77 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ import {
SelectTrigger,
SelectValue,
} from '@/components/ui/select';
import { Separator } from '@/components/ui/separator';
import { Textarea } from '@/components/ui/textarea';
import {
POSTGRES_FORM_SCHEMA,
PostgresFormValues,
Expand All @@ -34,6 +36,10 @@ import {
ConnectionConfig,
PostgresConnection,
PostgresConnectionConfig,
SSHAuthentication,
SSHPassphrase,
SSHPrivateKey,
SSHTunnel,
UpdateConnectionRequest,
UpdateConnectionResponse,
} from '@neosync/sdk';
Expand Down Expand Up @@ -74,6 +80,7 @@ export default function PostgresForm(props: Props) {
connectionId,
values.connectionName,
values.db,
values.tunnel,
account?.id ?? ''
);
onSaved(connectionResp);
Expand Down Expand Up @@ -147,7 +154,14 @@ export default function PostgresForm(props: Props) {
</FormLabel>
<FormDescription>The database port.</FormDescription>
<FormControl>
<Input placeholder="5432" {...field} />
<Input
type="number"
placeholder="5432"
{...field}
onChange={(e) => {
field.onChange(e.target.valueAsNumber);
}}
/>
</FormControl>
<FormMessage />
</FormItem>
Expand Down Expand Up @@ -201,7 +215,7 @@ export default function PostgresForm(props: Props) {
</FormLabel>
<FormDescription>The database password</FormDescription>
<FormControl>
<Input placeholder="postgres" {...field} />
<Input type="password" placeholder="postgres" {...field} />
</FormControl>
<FormMessage />
</FormItem>
Expand Down Expand Up @@ -241,6 +255,111 @@ export default function PostgresForm(props: Props) {
</FormItem>
)}
/>

<Separator />
<div className="flex flex-col gap-2">
<h2 className="text-xl font-semibold tracking-tight">
Bastion Host Configuration
</h2>
<p>
This section is optional and only necessary if your database is not
publicly accessible to the internet.
</p>
</div>
<FormField
control={form.control}
name="tunnel.host"
render={({ field }) => (
<FormItem>
<FormLabel>Host</FormLabel>
<FormDescription>
The hostname of the bastion server that will be used for SSH
tunneling.
</FormDescription>
<FormControl>
<Input placeholder="bastion.example.com" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="tunnel.port"
render={({ field }) => (
<FormItem>
<FormLabel>Port</FormLabel>
<FormDescription>
The port of the bastion host. Typically this is port 22.
</FormDescription>
<FormControl>
<Input
type="number"
placeholder="22"
{...field}
onChange={(e) => {
field.onChange(e.target.valueAsNumber);
}}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="tunnel.user"
render={({ field }) => (
<FormItem>
<FormLabel>User</FormLabel>
<FormDescription>
The name of the user that will be used to authenticate.
</FormDescription>
<FormControl>
<Input {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="tunnel.privateKey"
render={({ field }) => (
<FormItem>
<FormLabel>Private Key</FormLabel>
<FormDescription>
The private key that will be used to authenticate against the
SSH server. If using passphrase auth, provide that in the
appropriate field below.
</FormDescription>
<FormControl>
<Textarea {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="tunnel.passphrase"
render={({ field }) => (
<FormItem>
<FormLabel>Passphrase / Private Key Password</FormLabel>
<FormDescription>
The passphrase that will be used to authenticate with. If the
SSH Key provided above is encrypted, provide the password for it
here.
</FormDescription>
<FormControl>
<Input type="password" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<Separator />

<TestConnectionResult resp={checkResp} />
<div className="flex flex-row gap-3 justify-between">
<Button
Expand All @@ -251,6 +370,7 @@ export default function PostgresForm(props: Props) {
try {
const resp = await checkPostgresConnection(
form.getValues('db'),
form.getValues('tunnel'),
account?.id ?? ''
);
setCheckResp(resp);
Expand Down Expand Up @@ -350,8 +470,53 @@ async function updatePostgresConnection(
connectionId: string,
connectionName: string,
db: PostgresFormValues['db'],
tunnel: PostgresFormValues['tunnel'],
accountId: string
): Promise<UpdateConnectionResponse> {
const pgconfig = new PostgresConnectionConfig({
connectionConfig: {
case: 'connection',
value: new PostgresConnection({
host: db.host,
name: db.name,
user: db.user,
pass: db.pass,
port: db.port,
sslMode: db.sslMode,
}),
},
});
if (tunnel && tunnel.host) {
pgconfig.tunnel = new SSHTunnel({
host: tunnel.host,
port: tunnel.port,
user: tunnel.user,
knownHostPublicKey: tunnel.knownHostPublicKey
? tunnel.knownHostPublicKey
: undefined,
});
if (tunnel.privateKey) {
pgconfig.tunnel.authentication = new SSHAuthentication({
authConfig: {
case: 'privateKey',
value: new SSHPrivateKey({
value: tunnel.privateKey,
passphrase: tunnel.passphrase,
}),
},
});
} else if (tunnel.passphrase) {
pgconfig.tunnel.authentication = new SSHAuthentication({
authConfig: {
case: 'passphrase',
value: new SSHPassphrase({
value: tunnel.passphrase,
}),
},
});
}
}

const res = await fetch(
`/api/accounts/${accountId}/connections/${connectionId}`,
{
Expand All @@ -366,19 +531,7 @@ async function updatePostgresConnection(
connectionConfig: new ConnectionConfig({
config: {
case: 'pgConfig',
value: new PostgresConnectionConfig({
connectionConfig: {
case: 'connection',
value: new PostgresConnection({
host: db.host,
name: db.name,
user: db.user,
pass: db.pass,
port: db.port,
sslMode: db.sslMode,
}),
},
}),
value: pgconfig,
},
}),
})
Expand All @@ -394,6 +547,7 @@ async function updatePostgresConnection(

async function checkPostgresConnection(
db: PostgresFormValues['db'],
tunnel: PostgresFormValues['tunnel'],
accountId: string
): Promise<CheckConnectionConfigResponse> {
const res = await fetch(
Expand All @@ -403,7 +557,7 @@ async function checkPostgresConnection(
headers: {
'content-type': 'application/json',
},
body: JSON.stringify(db),
body: JSON.stringify({ db, tunnel }),
}
);
if (!res.ok) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@
import { CopyButton } from '@/components/CopyButton';
import ConnectionIcon from '@/components/connections/ConnectionIcon';
import PageHeader from '@/components/headers/PageHeader';
import { Connection, UpdateConnectionResponse } from '@neosync/sdk';
import {
Connection,
SSHAuthentication,
UpdateConnectionResponse,
} from '@neosync/sdk';
import { ReactElement } from 'react';
import AwsS3Form from './AwsS3Form';
import MysqlForm from './MysqlForm';
Expand Down Expand Up @@ -68,6 +72,24 @@ export function getConnectionComponentDetails(
pass: value.connectionConfig.value.pass,
sslMode: value.connectionConfig.value.sslMode,
},
tunnel: {
host: value.tunnel?.host ?? '',
port: value.tunnel?.port ?? 22,
knownHostPublicKey: value.tunnel?.knownHostPublicKey ?? '',
user: value.tunnel?.user ?? '',
passphrase:
value.tunnel && value.tunnel.authentication
? getPassphraseFromSshAuthentication(
value.tunnel.authentication
) ?? ''
: '',
privateKey:
value.tunnel && value.tunnel.authentication
? getPrivateKeyFromSshAuthentication(
value.tunnel.authentication
) ?? ''
: '',
},
}}
onSaved={(resp) => onSaved(resp)}
onSaveFailed={onSaveFailed}
Expand Down Expand Up @@ -231,3 +253,27 @@ export function getConnectionComponentDetails(
};
}
}

function getPassphraseFromSshAuthentication(
sshauth: SSHAuthentication
): string | undefined {
switch (sshauth.authConfig.case) {
case 'passphrase':
return sshauth.authConfig.value.value;
case 'privateKey':
return sshauth.authConfig.value.passphrase;
default:
return undefined;
}
}

function getPrivateKeyFromSshAuthentication(
sshauth: SSHAuthentication
): string | undefined {
switch (sshauth.authConfig.case) {
case 'privateKey':
return sshauth.authConfig.value.value;
default:
return undefined;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,12 @@ export function getColumns(
return (
<div className="flex space-x-2">
<span className="max-w-[500px] truncate font-medium">
{row.getValue('name')}
<NextLink
className="hover:underline"
href={`/${accountName}/connections/${row.getValue('id')}`}
>
{row.getValue('name')}
</NextLink>
</span>
</div>
);
Expand Down
Loading

0 comments on commit 0ce1116

Please sign in to comment.