Skip to content

Commit

Permalink
migrate ssh service
Browse files Browse the repository at this point in the history
  • Loading branch information
jeanp413 committed Nov 22, 2023
1 parent 73a1c76 commit 22e807c
Show file tree
Hide file tree
Showing 18 changed files with 229 additions and 1,450 deletions.
2 changes: 2 additions & 0 deletions components/dashboard/src/data/setup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import * as AuthProviderClasses from "@gitpod/public-api/lib/gitpod/v1/authprovi
import * as EnvVarClasses from "@gitpod/public-api/lib/gitpod/v1/envvar_pb";
import * as PrebuildClasses from "@gitpod/public-api/lib/gitpod/v1/prebuild_pb";
import * as SCMClasses from "@gitpod/public-api/lib/gitpod/v1/scm_pb";
import * as SSHClasses from "@gitpod/public-api/lib/gitpod/v1/ssh_pb";

// This is used to version the cache
// If data we cache changes in a non-backwards compatible way, increment this version
Expand Down Expand Up @@ -152,6 +153,7 @@ function initializeMessages() {
...Object.values(EnvVarClasses),
...Object.values(PrebuildClasses),
...Object.values(SCMClasses),
...Object.values(SSHClasses),
];
for (const c of constr) {
if ((c as any).prototype instanceof Message) {
Expand Down
54 changes: 54 additions & 0 deletions components/dashboard/src/service/json-rpc-ssh-client.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/**
* Copyright (c) 2023 Gitpod GmbH. All rights reserved.
* Licensed under the GNU Affero General Public License (AGPL).
* See License.AGPL.txt in the project root for license information.
*/

import { PromiseClient } from "@connectrpc/connect";
import { PartialMessage } from "@bufbuild/protobuf";
import { SSHService } from "@gitpod/public-api/lib/gitpod/v1/ssh_connect";
import {
CreateSSHPublicKeyRequest,
CreateSSHPublicKeyResponse,
DeleteSSHPublicKeyRequest,
DeleteSSHPublicKeyResponse,
ListSSHPublicKeysRequest,
ListSSHPublicKeysResponse,
} from "@gitpod/public-api/lib/gitpod/v1/ssh_pb";
import { converter } from "./public-api";
import { getGitpodService } from "./service";
import { ApplicationError, ErrorCodes } from "@gitpod/gitpod-protocol/lib/messaging/error";

export class JsonRpcSSHClient implements PromiseClient<typeof SSHService> {
async listSSHPublicKeys(req: PartialMessage<ListSSHPublicKeysRequest>): Promise<ListSSHPublicKeysResponse> {
const result = new ListSSHPublicKeysResponse();
const sshKeys = await getGitpodService().server.getSSHPublicKeys();
result.sshKeys = sshKeys.map((i) => converter.toSSHPublicKey(i));

return result;
}

async createSSHPublicKey(req: PartialMessage<CreateSSHPublicKeyRequest>): Promise<CreateSSHPublicKeyResponse> {
if (!req.name || !req.key) {
throw new ApplicationError(ErrorCodes.BAD_REQUEST, "name and key are required");
}

const response = new CreateSSHPublicKeyResponse();

const sshKey = await getGitpodService().server.addSSHPublicKey({ name: req.name, key: req.key });
response.sshKey = converter.toSSHPublicKey(sshKey);

return response;
}

async deleteSSHPublicKey(req: PartialMessage<DeleteSSHPublicKeyRequest>): Promise<DeleteSSHPublicKeyResponse> {
if (!req.sshKeyId) {
throw new ApplicationError(ErrorCodes.BAD_REQUEST, "sshKeyId is required");
}

await getGitpodService().server.deleteSSHPublicKey(eq.sshKeyId);

const response = new DeleteSSHPublicKeyResponse();
return response;
}
}
4 changes: 4 additions & 0 deletions components/dashboard/src/service/public-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ import { JsonRpcEnvvarClient } from "./json-rpc-envvar-client";
import { Prebuild, WatchPrebuildRequest, WatchPrebuildResponse } from "@gitpod/public-api/lib/gitpod/v1/prebuild_pb";
import { JsonRpcPrebuildClient } from "./json-rpc-prebuild-client";
import { ApplicationError, ErrorCodes } from "@gitpod/gitpod-protocol/lib/messaging/error";
import { SSHService } from "@gitpod/public-api/lib/gitpod/v1/ssh_connect";
import { JsonRpcSSHClient } from "./json-rpc-ssh-client";

const transport = createConnectTransport({
baseUrl: `${window.location.protocol}//${window.location.host}/public-api`,
Expand Down Expand Up @@ -63,6 +65,8 @@ export const authProviderClient = createServiceClient(AuthProviderService, new J

export const envVarClient = createServiceClient(EnvironmentVariableService, new JsonRpcEnvvarClient());

export const sshClient = createServiceClient(SSHService, new JsonRpcSSHClient());

export async function listAllProjects(opts: { orgId: string }): Promise<ProtocolProject[]> {
let pagination = {
page: 1,
Expand Down
29 changes: 15 additions & 14 deletions components/dashboard/src/user-settings/SSHKeys.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,14 @@ import Modal, { ModalBody, ModalFooter, ModalHeader } from "../components/Modal"
import Alert from "../components/Alert";
import { Item, ItemField, ItemFieldContextMenu } from "../components/ItemsList";
import ConfirmationModal from "../components/ConfirmationModal";
import { SSHPublicKeyValue, UserSSHPublicKeyValue } from "@gitpod/gitpod-protocol";
import { getGitpodService } from "../service/service";
import { SSHPublicKeyValue } from "@gitpod/gitpod-protocol";
import dayjs from "dayjs";
import { PageWithSettingsSubMenu } from "./PageWithSettingsSubMenu";
import { Heading2, Subheading } from "../components/typography/headings";
import { EmptyMessage } from "../components/EmptyMessage";
import { Button } from "@podkit/buttons/Button";
import { sshClient } from "../service/public-api";
import { SSHPublicKey } from "@gitpod/public-api/lib/gitpod/v1/ssh_pb";

interface AddModalProps {
value: SSHPublicKeyValue;
Expand All @@ -24,7 +25,7 @@ interface AddModalProps {
}

interface DeleteModalProps {
value: UserSSHPublicKeyValue;
value: SSHPublicKey;
onConfirm: () => void;
onClose: () => void;
}
Expand All @@ -50,7 +51,7 @@ export function AddSSHKeyModal(props: AddModalProps) {
return;
}
try {
await getGitpodService().server.addSSHPublicKey(value);
await sshClient.createSSHPublicKey(value);
props.onClose();
props.onSave();
} catch (e) {
Expand Down Expand Up @@ -121,7 +122,7 @@ export function AddSSHKeyModal(props: AddModalProps) {

export function DeleteSSHKeyModal(props: DeleteModalProps) {
const confirmDelete = useCallback(async () => {
await getGitpodService().server.deleteSSHPublicKey(props.value.id!);
await sshClient.deleteSSHPublicKey({ sshKeyId: props.value.id! });
props.onConfirm();
props.onClose();
}, [props]);
Expand All @@ -142,16 +143,14 @@ export function DeleteSSHKeyModal(props: DeleteModalProps) {
}

export default function SSHKeys() {
const [dataList, setDataList] = useState<UserSSHPublicKeyValue[]>([]);
const [dataList, setDataList] = useState<SSHPublicKey[]>([]);
const [currentData, setCurrentData] = useState<SSHPublicKeyValue>({ name: "", key: "" });
const [currentDelData, setCurrentDelData] = useState<UserSSHPublicKeyValue>();
const [currentDelData, setCurrentDelData] = useState<SSHPublicKey>();
const [showAddModal, setShowAddModal] = useState(false);
const [showDelModal, setShowDelModal] = useState(false);

const loadData = () => {
getGitpodService()
.server.getSSHPublicKeys()
.then((r) => setDataList(r));
sshClient.listSSHPublicKeys({}).then((r) => setDataList(r.sshKeys));
};

useEffect(() => {
Expand All @@ -164,7 +163,7 @@ export default function SSHKeys() {
setShowDelModal(false);
};

const deleteOne = (value: UserSSHPublicKeyValue) => {
const deleteOne = (value: SSHPublicKey) => {
setCurrentDelData(value);
setShowAddModal(false);
setShowDelModal(true);
Expand Down Expand Up @@ -243,15 +242,17 @@ export default function SSHKeys() {
);
}

function KeyItem(props: { sshKey: UserSSHPublicKeyValue }) {
function KeyItem(props: { sshKey: SSHPublicKey }) {
const key = props.sshKey;
return (
<ItemField className="flex flex-col gap-y box-border overflow-hidden">
<p className="truncate text-gray-400 dark:text-gray-600">SHA256:{key.fingerprint}</p>
<div className="truncate my-1 text-xl text-gray-800 dark:text-gray-100 font-semibold">{key.name}</div>
<p className="truncate mt-4">Added on {dayjs(key.creationTime).format("MMM D, YYYY, hh:mm A")}</p>
<p className="truncate mt-4">Added on {dayjs(key.creationTime!.toDate()).format("MMM D, YYYY, hh:mm A")}</p>
{!!key.lastUsedTime && (
<p className="truncate">Last used on {dayjs(key.lastUsedTime).format("MMM D, YYYY, hh:mm A")}</p>
<p className="truncate">
Last used on {dayjs(key.lastUsedTime!.toDate()).format("MMM D, YYYY, hh:mm A")}
</p>
)}
</ItemField>
);
Expand Down
10 changes: 5 additions & 5 deletions components/dashboard/src/workspaces/ConnectToSSHModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@ import Modal, { ModalBody, ModalFooter, ModalHeader } from "../components/Modal"
import Alert from "../components/Alert";
import TabMenuItem from "../components/TabMenuItem";
import { settingsPathSSHKeys } from "../user-settings/settings.routes";
import { getGitpodService } from "../service/service";
import { InputWithCopy } from "../components/InputWithCopy";
import { Link } from "react-router-dom";
import { Button } from "@podkit/buttons/Button";
import { sshClient } from "../service/public-api";

interface SSHProps {
workspaceId: string;
Expand All @@ -25,10 +25,10 @@ function SSHView(props: SSHProps) {
const [selectSSHKey, setSelectSSHKey] = useState(true);

useEffect(() => {
getGitpodService()
.server.hasSSHPublicKey()
.then((d) => {
setHasSSHKey(d);
sshClient
.listSSHPublicKeys({})
.then((r) => {
setHasSSHKey(r.sshKeys.length > 0);
})
.catch(console.error);
}, []);
Expand Down
34 changes: 33 additions & 1 deletion components/gitpod-protocol/src/public-api-converter.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,14 @@ import {
PrebuildSettings,
WorkspaceSettings,
} from "@gitpod/public-api/lib/gitpod/v1/configuration_pb";
import { AuthProviderEntry, AuthProviderInfo, ProjectEnvVar, UserEnvVarValue, WithEnvvarsContext } from "./protocol";
import {
AuthProviderEntry,
AuthProviderInfo,
ProjectEnvVar,
UserEnvVarValue,
UserSSHPublicKey,
WithEnvvarsContext,
} from "./protocol";
import {
AuthProvider,
AuthProviderDescription,
Expand All @@ -33,6 +40,7 @@ import {
EnvironmentVariableAdmission,
UserEnvironmentVariable,
} from "@gitpod/public-api/lib/gitpod/v1/envvar_pb";
import { SSHPublicKey } from "@gitpod/public-api/lib/gitpod/v1/ssh_pb";

describe("PublicAPIConverter", () => {
const converter = new PublicAPIConverter();
Expand Down Expand Up @@ -951,4 +959,28 @@ describe("PublicAPIConverter", () => {
});
});
});

describe("toSSHPublicKey", () => {
const envVar: UserSSHPublicKey = {
id: "1",
userId: "1",
name: "FOO",
key: "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQDCnrN9UdK1bNGPmZfenTWXLuYYDjlYvZE8S+WOfP08WpR1GETzX5ZvgYOEZGwEE8KUPHC9cge4Hvo/ydIS9aqbZ5MiVGJ8cAIq1Ic89SjlDWU6fl8TwIqOPCi2imAASlEDP4q8vMLK1N6UOW1EVbxyL3uybGd10ysC1t1FxFPveIGNsYE/MOQeuEWS16AplpXYXIfVRSlgAskeBft2w8Ud3B4gNe8ECLA/FXu96UpvZkdtOarA3JZ9Z27GveNJg9Mtmmw0+US0KXiO9x9NyH7G8+mqVDwDY+nNvaFA5gtQxkkl/uY2oz9k/B4Rjlj3jOiUXe5uQs3XUm5m8g9a9fh62DabLpA2fEvtfg+a/VqNe52dNa5YjupwvBd6Inb5uMW/TYjNl6bNHPlXFKw/nwLOVzukpkjxMZUKS6+4BGkpoasj6y2rTU/wkpbdD8J7yjI1p6J9aKkC6KksIWgN7xGmHkv2PCGDqMHTNbnQyowtNKMgA/667vAYJ0qW7HAHBFXJRs6uRi/DI3+c1QV2s4wPCpEHDIYApovQ0fbON4WDPoGMyHd7kPh9xB/bX7Dj0uMXImu1pdTd62fQ/1XXX64+vjAAXS/P9RSCD0RCRt/K3LPKl2m7GPI3y1niaE52XhxZw+ms9ays6NasNVMw/ZC+f02Ti+L5FBEVf8230RVVRQ== [email protected]",
fingerprint: "ykjP/b5aqoa3envmXzWpPMCGgEFMu3QvubfSTNrJCMA=",
creationTime: "2023-10-16T20:18:24.923Z",
lastUsedTime: "2023-10-16T20:18:24.923Z",
};
const userEnvVar = new SSHPublicKey({
id: "1",
name: "FOO",
key: "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQDCnrN9UdK1bNGPmZfenTWXLuYYDjlYvZE8S+WOfP08WpR1GETzX5ZvgYOEZGwEE8KUPHC9cge4Hvo/ydIS9aqbZ5MiVGJ8cAIq1Ic89SjlDWU6fl8TwIqOPCi2imAASlEDP4q8vMLK1N6UOW1EVbxyL3uybGd10ysC1t1FxFPveIGNsYE/MOQeuEWS16AplpXYXIfVRSlgAskeBft2w8Ud3B4gNe8ECLA/FXu96UpvZkdtOarA3JZ9Z27GveNJg9Mtmmw0+US0KXiO9x9NyH7G8+mqVDwDY+nNvaFA5gtQxkkl/uY2oz9k/B4Rjlj3jOiUXe5uQs3XUm5m8g9a9fh62DabLpA2fEvtfg+a/VqNe52dNa5YjupwvBd6Inb5uMW/TYjNl6bNHPlXFKw/nwLOVzukpkjxMZUKS6+4BGkpoasj6y2rTU/wkpbdD8J7yjI1p6J9aKkC6KksIWgN7xGmHkv2PCGDqMHTNbnQyowtNKMgA/667vAYJ0qW7HAHBFXJRs6uRi/DI3+c1QV2s4wPCpEHDIYApovQ0fbON4WDPoGMyHd7kPh9xB/bX7Dj0uMXImu1pdTd62fQ/1XXX64+vjAAXS/P9RSCD0RCRt/K3LPKl2m7GPI3y1niaE52XhxZw+ms9ays6NasNVMw/ZC+f02Ti+L5FBEVf8230RVVRQ== [email protected]",
fingerprint: "ykjP/b5aqoa3envmXzWpPMCGgEFMu3QvubfSTNrJCMA=",
creationTime: Timestamp.fromDate(new Date("2023-10-16T20:18:24.923Z")),
lastUsedTime: Timestamp.fromDate(new Date("2023-10-16T20:18:24.923Z")),
});
it("should convert ssh public key variable types", () => {
const result = converter.toSSHPublicKey(envVar);
expect(result).to.deep.equal(userEnvVar);
});
});
});
13 changes: 13 additions & 0 deletions components/gitpod-protocol/src/public-api-converter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import {
WorkspacePort_Protocol,
WorkspaceStatus,
} from "@gitpod/public-api/lib/gitpod/v1/workspace_pb";
import { SSHPublicKey } from "@gitpod/public-api/lib/gitpod/v1/ssh_pb";
import {
ConfigurationEnvironmentVariable,
EnvironmentVariableAdmission,
Expand Down Expand Up @@ -65,6 +66,7 @@ import {
UserEnvVarValue,
ProjectEnvVar,
PrebuiltWorkspaceState,
UserSSHPublicKeyValue,
} from "./protocol";
import {
OrgMemberInfo,
Expand Down Expand Up @@ -675,4 +677,15 @@ export class PublicAPIConverter {
}
return PrebuildPhase_Phase.UNSPECIFIED;
}

toSSHPublicKey(sshKey: UserSSHPublicKeyValue): SSHPublicKey {
const result = new SSHPublicKey();
result.id = sshKey.id;
result.name = sshKey.name;
result.key = sshKey.key;
result.fingerprint = sshKey.fingerprint;
result.creationTime = Timestamp.fromDate(new Date(sshKey.creationTime));
result.lastUsedTime = Timestamp.fromDate(new Date(sshKey.lastUsedTime || sshKey.creationTime));
return result;
}
}
4 changes: 1 addition & 3 deletions components/public-api/gitpod/v1/ssh.proto
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,6 @@ message CreateSSHPublicKeyResponse {
SSHPublicKey ssh_key = 1;
}

message DeleteSSHPublicKeyRequest {
string id = 1;
}
message DeleteSSHPublicKeyRequest { string ssh_key_id = 1; }

message DeleteSSHPublicKeyResponse {}
Loading

0 comments on commit 22e807c

Please sign in to comment.