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
+6 -1
View File
@@ -1,3 +1,8 @@
export { SshCaStatus } from "./enums"; export { SshCaStatus } from "./enums";
export { useCreateSshCa, useDeleteSshCa, useIssueSshCreds,useUpdateSshCa } from "./mutations"; export {
useCreateSshCa,
useDeleteSshCa,
useIssueSshCreds,
useSignSshKey,
useUpdateSshCa} from "./mutations";
export { useGetSshCaById, useGetSshCaCertTemplates } from "./queries"; export { useGetSshCaById, useGetSshCaCertTemplates } from "./queries";
@@ -8,6 +8,8 @@ import {
TDeleteSshCaDTO, TDeleteSshCaDTO,
TIssueSshCredsDTO, TIssueSshCredsDTO,
TIssueSshCredsResponse, TIssueSshCredsResponse,
TSignSshKeyDTO,
TSignSshKeyResponse,
TSshCertificateAuthority, TSshCertificateAuthority,
TUpdateSshCaDTO} from "./types"; 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 = () => { export const useIssueSshCreds = () => {
return useMutation<TIssueSshCredsResponse, {}, TIssueSshCredsDTO>({ return useMutation<TIssueSshCredsResponse, {}, TIssueSshCredsDTO>({
mutationFn: async (body) => { mutationFn: async (body) => {
+14
View File
@@ -27,6 +27,20 @@ export type TDeleteSshCaDTO = {
caId: string; 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 = { export type TIssueSshCredsDTO = {
templateName: string; templateName: string;
keyAlgorithm: CertKeyAlgorithm; keyAlgorithm: CertKeyAlgorithm;
@@ -8,8 +8,8 @@ import { useTimedReset } from "@app/hooks";
type Props = { type Props = {
serialNumber: string; serialNumber: string;
signedKey: string; signedKey: string;
privateKey: string; privateKey?: string;
publicKey: string; publicKey?: string;
}; };
export const SshCertificateContent = ({ export const SshCertificateContent = ({
@@ -119,7 +119,7 @@ export const SshCertificateContent = ({
colorSchema="secondary" colorSchema="secondary"
className="group relative ml-2" className="group relative ml-2"
onClick={() => { onClick={() => {
downloadTxtFile("user_key", privateKey); downloadTxtFile("user_key.pem", privateKey);
}} }}
> >
<FontAwesomeIcon icon={faDownload} /> <FontAwesomeIcon icon={faDownload} />
@@ -13,7 +13,7 @@ import {
Select, Select,
SelectItem SelectItem
} from "@app/components/v2"; } 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 { certKeyAlgorithms } from "@app/hooks/api/certificates/constants";
import { CertKeyAlgorithm } from "@app/hooks/api/certificates/enums"; import { CertKeyAlgorithm } from "@app/hooks/api/certificates/enums";
import { SshCertType } from "@app/hooks/api/ssh-ca/enums"; import { SshCertType } from "@app/hooks/api/ssh-ca/enums";
@@ -29,6 +29,7 @@ import { SshCertificateContent } from "./SshCertificateContent";
const schema = z.object({ const schema = z.object({
templateName: z.string(), templateName: z.string(),
publicKey: z.string().optional(),
keyAlgorithm: z.enum([ keyAlgorithm: z.enum([
CertKeyAlgorithm.RSA_2048, CertKeyAlgorithm.RSA_2048,
CertKeyAlgorithm.RSA_4096, CertKeyAlgorithm.RSA_4096,
@@ -50,13 +51,23 @@ type Props = {
type TSshCertificateDetails = { type TSshCertificateDetails = {
serialNumber: string; serialNumber: string;
privateKey: string;
publicKey: string;
signedKey: 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) => { export const SshCertificateModal = ({ popUp, handlePopUpToggle }: Props) => {
const [operation, setOperation] = useState<SshCertificateOperation>(
SshCertificateOperation.SIGN_SSH_KEY
);
const [certificateDetails, setCertificateDetails] = useState<TSshCertificateDetails | null>(null); const [certificateDetails, setCertificateDetails] = useState<TSshCertificateDetails | null>(null);
const { mutateAsync: signSshKey } = useSignSshKey();
const { mutateAsync: issueSshCreds } = useIssueSshCreds(); const { mutateAsync: issueSshCreds } = useIssueSshCreds();
const popUpData = popUp?.sshCertificate?.data as { sshCaId: string; templateName: string }; const popUpData = popUp?.sshCertificate?.data as { sshCaId: string; templateName: string };
@@ -87,29 +98,53 @@ export const SshCertificateModal = ({ popUp, handlePopUpToggle }: Props) => {
templateName, templateName,
keyAlgorithm, keyAlgorithm,
certType, certType,
publicKey: existingPublicKey,
principals, principals,
ttl, ttl,
keyId keyId
}: FormData) => { }: FormData) => {
try { try {
const { serialNumber, publicKey, privateKey, signedKey } = await issueSshCreds({ switch (operation) {
templateName, case SshCertificateOperation.SIGN_SSH_KEY: {
keyAlgorithm, const { serialNumber, signedKey } = await signSshKey({
certType, templateName,
principals: principals.split(",").map((user) => user.trim()), publicKey: existingPublicKey,
ttl, certType,
keyId 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(); reset();
setCertificateDetails({
serialNumber,
privateKey,
publicKey,
signedKey
});
createNotification({ createNotification({
text: "Successfully created SSH certificate", text: "Successfully created SSH certificate",
type: "success" type: "success"
@@ -162,20 +197,21 @@ export const SshCertificateModal = ({ popUp, handlePopUpToggle }: Props) => {
</FormControl> </FormControl>
)} )}
/> />
<Controller <FormControl label="Operation">
control={control} <Select
name="principals" defaultValue="issue-ssh-creds"
render={({ field, fieldState: { error } }) => ( value={operation}
<FormControl onValueChange={(e) => setOperation(e as SshCertificateOperation)}
label="Principal(s)" className="w-full"
isError={Boolean(error)} >
errorText={error?.message} <SelectItem value="sign-ssh-key" key="sign-ssh-key">
isRequired Sign SSH Key
> </SelectItem>
<Input {...field} placeholder="ec2-user" /> <SelectItem value="issue-ssh-creds" key="issue-ssh-creds">
</FormControl> Issue SSH Credentials
)} </SelectItem>
/> </Select>
</FormControl>
<Controller <Controller
control={control} control={control}
name="certType" name="certType"
@@ -198,31 +234,64 @@ export const SshCertificateModal = ({ popUp, handlePopUpToggle }: Props) => {
</FormControl> </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 <Controller
control={control} control={control}
name="keyAlgorithm" name="principals"
defaultValue={CertKeyAlgorithm.RSA_2048} render={({ field, fieldState: { error } }) => (
render={({ field: { onChange, ...field }, fieldState: { error } }) => (
<FormControl <FormControl
label="Key Algorithm" label="Principal(s)"
errorText={error?.message}
isError={Boolean(error)} isError={Boolean(error)}
errorText={error?.message}
isRequired
> >
<Select <Input {...field} placeholder="ec2-user" />
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> </FormControl>
)} )}
/> />
<Controller <Controller
control={control} control={control}
name="ttl" name="ttl"