Add sign SSH key operation to frontend

This commit is contained in:
Tuan Dang
2024-12-04 20:13:30 -08:00
parent a5a1f57284
commit 5b618b07fa
5 changed files with 152 additions and 53 deletions

View File

@@ -1,3 +1,8 @@
export { SshCaStatus } from "./enums";
export { useCreateSshCa, useDeleteSshCa, useIssueSshCreds,useUpdateSshCa } from "./mutations";
export {
useCreateSshCa,
useDeleteSshCa,
useIssueSshCreds,
useSignSshKey,
useUpdateSshCa} from "./mutations";
export { useGetSshCaById, useGetSshCaCertTemplates } from "./queries";

View File

@@ -8,6 +8,8 @@ import {
TDeleteSshCaDTO,
TIssueSshCredsDTO,
TIssueSshCredsResponse,
TSignSshKeyDTO,
TSignSshKeyResponse,
TSshCertificateAuthority,
TUpdateSshCaDTO} from "./types";
@@ -61,6 +63,15 @@ export const useDeleteSshCa = () => {
});
};
export const useSignSshKey = () => {
return useMutation<TSignSshKeyResponse, {}, TSignSshKeyDTO>({
mutationFn: async (body) => {
const { data } = await apiRequest.post<TSignSshKeyResponse>("/api/v1/ssh/sign", body);
return data;
}
});
};
export const useIssueSshCreds = () => {
return useMutation<TIssueSshCredsResponse, {}, TIssueSshCredsDTO>({
mutationFn: async (body) => {

View File

@@ -27,6 +27,20 @@ export type TDeleteSshCaDTO = {
caId: string;
};
export type TSignSshKeyDTO = {
templateName: string;
publicKey?: string;
certType: SshCertType;
principals: string[];
ttl?: string;
keyId?: string;
};
export type TSignSshKeyResponse = {
serialNumber: string;
signedKey: string;
};
export type TIssueSshCredsDTO = {
templateName: string;
keyAlgorithm: CertKeyAlgorithm;

View File

@@ -8,8 +8,8 @@ import { useTimedReset } from "@app/hooks";
type Props = {
serialNumber: string;
signedKey: string;
privateKey: string;
publicKey: string;
privateKey?: string;
publicKey?: string;
};
export const SshCertificateContent = ({
@@ -119,7 +119,7 @@ export const SshCertificateContent = ({
colorSchema="secondary"
className="group relative ml-2"
onClick={() => {
downloadTxtFile("user_key", privateKey);
downloadTxtFile("user_key.pem", privateKey);
}}
>
<FontAwesomeIcon icon={faDownload} />

View File

@@ -13,7 +13,7 @@ import {
Select,
SelectItem
} from "@app/components/v2";
import { useGetSshCaCertTemplates,useIssueSshCreds } from "@app/hooks/api";
import { useGetSshCaCertTemplates, useIssueSshCreds, useSignSshKey } from "@app/hooks/api";
import { certKeyAlgorithms } from "@app/hooks/api/certificates/constants";
import { CertKeyAlgorithm } from "@app/hooks/api/certificates/enums";
import { SshCertType } from "@app/hooks/api/ssh-ca/enums";
@@ -29,6 +29,7 @@ import { SshCertificateContent } from "./SshCertificateContent";
const schema = z.object({
templateName: z.string(),
publicKey: z.string().optional(),
keyAlgorithm: z.enum([
CertKeyAlgorithm.RSA_2048,
CertKeyAlgorithm.RSA_4096,
@@ -50,13 +51,23 @@ type Props = {
type TSshCertificateDetails = {
serialNumber: string;
privateKey: string;
publicKey: string;
signedKey: string;
privateKey?: string;
publicKey?: string;
};
enum SshCertificateOperation {
SIGN_SSH_KEY = "sign-ssh-key",
ISSUE_SSH_CREDS = "issue-ssh-creds"
}
export const SshCertificateModal = ({ popUp, handlePopUpToggle }: Props) => {
const [operation, setOperation] = useState<SshCertificateOperation>(
SshCertificateOperation.SIGN_SSH_KEY
);
const [certificateDetails, setCertificateDetails] = useState<TSshCertificateDetails | null>(null);
const { mutateAsync: signSshKey } = useSignSshKey();
const { mutateAsync: issueSshCreds } = useIssueSshCreds();
const popUpData = popUp?.sshCertificate?.data as { sshCaId: string; templateName: string };
@@ -87,29 +98,53 @@ export const SshCertificateModal = ({ popUp, handlePopUpToggle }: Props) => {
templateName,
keyAlgorithm,
certType,
publicKey: existingPublicKey,
principals,
ttl,
keyId
}: FormData) => {
try {
const { serialNumber, publicKey, privateKey, signedKey } = await issueSshCreds({
templateName,
keyAlgorithm,
certType,
principals: principals.split(",").map((user) => user.trim()),
ttl,
keyId
});
switch (operation) {
case SshCertificateOperation.SIGN_SSH_KEY: {
const { serialNumber, signedKey } = await signSshKey({
templateName,
publicKey: existingPublicKey,
certType,
principals: principals.split(",").map((user) => user.trim()),
ttl,
keyId
});
setCertificateDetails({
serialNumber,
signedKey
});
break;
}
case SshCertificateOperation.ISSUE_SSH_CREDS: {
const { serialNumber, publicKey, privateKey, signedKey } = await issueSshCreds({
templateName,
keyAlgorithm,
certType,
principals: principals.split(",").map((user) => user.trim()),
ttl,
keyId
});
setCertificateDetails({
serialNumber,
privateKey,
publicKey,
signedKey
});
break;
}
default: {
break;
}
}
reset();
setCertificateDetails({
serialNumber,
privateKey,
publicKey,
signedKey
});
createNotification({
text: "Successfully created SSH certificate",
type: "success"
@@ -162,20 +197,21 @@ export const SshCertificateModal = ({ popUp, handlePopUpToggle }: Props) => {
</FormControl>
)}
/>
<Controller
control={control}
name="principals"
render={({ field, fieldState: { error } }) => (
<FormControl
label="Principal(s)"
isError={Boolean(error)}
errorText={error?.message}
isRequired
>
<Input {...field} placeholder="ec2-user" />
</FormControl>
)}
/>
<FormControl label="Operation">
<Select
defaultValue="issue-ssh-creds"
value={operation}
onValueChange={(e) => setOperation(e as SshCertificateOperation)}
className="w-full"
>
<SelectItem value="sign-ssh-key" key="sign-ssh-key">
Sign SSH Key
</SelectItem>
<SelectItem value="issue-ssh-creds" key="issue-ssh-creds">
Issue SSH Credentials
</SelectItem>
</Select>
</FormControl>
<Controller
control={control}
name="certType"
@@ -198,31 +234,64 @@ export const SshCertificateModal = ({ popUp, handlePopUpToggle }: Props) => {
</FormControl>
)}
/>
{operation === SshCertificateOperation.SIGN_SSH_KEY && (
<Controller
control={control}
name="publicKey"
render={({ field, fieldState: { error } }) => (
<FormControl
label="SSH Public Key"
isError={Boolean(error)}
errorText={error?.message}
isRequired
>
<Input {...field} placeholder="ssh-rsa AAA ..." />
</FormControl>
)}
/>
)}
{operation === SshCertificateOperation.ISSUE_SSH_CREDS && (
<Controller
control={control}
name="keyAlgorithm"
defaultValue={CertKeyAlgorithm.RSA_2048}
render={({ field: { onChange, ...field }, fieldState: { error } }) => (
<FormControl
label="Key Algorithm"
errorText={error?.message}
isError={Boolean(error)}
>
<Select
defaultValue={field.value}
{...field}
onValueChange={(e) => onChange(e)}
className="w-full"
>
{certKeyAlgorithms.map(({ label, value }) => (
<SelectItem value={String(value || "")} key={label}>
{label}
</SelectItem>
))}
</Select>
</FormControl>
)}
/>
)}
<Controller
control={control}
name="keyAlgorithm"
defaultValue={CertKeyAlgorithm.RSA_2048}
render={({ field: { onChange, ...field }, fieldState: { error } }) => (
name="principals"
render={({ field, fieldState: { error } }) => (
<FormControl
label="Key Algorithm"
errorText={error?.message}
label="Principal(s)"
isError={Boolean(error)}
errorText={error?.message}
isRequired
>
<Select
defaultValue={field.value}
{...field}
onValueChange={(e) => onChange(e)}
className="w-full"
>
{certKeyAlgorithms.map(({ label, value }) => (
<SelectItem value={String(value || "")} key={label}>
{label}
</SelectItem>
))}
</Select>
<Input {...field} placeholder="ec2-user" />
</FormControl>
)}
/>
<Controller
control={control}
name="ttl"