Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[public-api] migrate ssh service #19110

Merged
merged 5 commits into from
Nov 23, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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),
jeanp413 marked this conversation as resolved.
Show resolved Hide resolved
];
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) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

JSON-RPC and gRPC validate is not aligned

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see that uuid check is not done in other json-rpc files, only in new api files

throw new ApplicationError(ErrorCodes.BAD_REQUEST, "sshKeyId is required");
}

await getGitpodService().server.deleteSSHPublicKey(req.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 @@ -34,6 +34,8 @@ import { JsonRpcPrebuildClient } from "./json-rpc-prebuild-client";
import { ApplicationError, ErrorCodes } from "@gitpod/gitpod-protocol/lib/messaging/error";
import { JsonRpcScmClient } from "./json-rpc-scm-client";
import { SCMService } from "@gitpod/public-api/lib/gitpod/v1/scm_connect";
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 @@ -67,6 +69,8 @@ export const scmClient = createServiceClient(SCMService, new JsonRpcScmClient())

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
28 changes: 28 additions & 0 deletions components/gitpod-protocol/src/public-api-converter.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import {
SuggestedRepository,
Token,
UserEnvVarValue,
UserSSHPublicKey,
WithEnvvarsContext,
} from "./protocol";
import {
Expand Down Expand Up @@ -62,6 +63,7 @@ import {
ImageBuildLogsNotYetAvailableError,
TooManyRunningWorkspacesError,
} from "@gitpod/public-api/lib/gitpod/v1/error_pb";
import { SSHPublicKey } from "@gitpod/public-api/lib/gitpod/v1/ssh_pb";

describe("PublicAPIConverter", () => {
const converter = new PublicAPIConverter();
Expand Down Expand Up @@ -980,6 +982,7 @@ describe("PublicAPIConverter", () => {
});
});
});

describe("toSCMToken", () => {
it("should convert a token", () => {
const t1 = new Date();
Expand All @@ -1003,6 +1006,7 @@ describe("PublicAPIConverter", () => {
});
});
});

describe("toSuggestedRepository", () => {
it("should convert a repo", () => {
const repo: SuggestedRepository = {
Expand Down Expand Up @@ -1346,4 +1350,28 @@ describe("PublicAPIConverter", () => {
expect(appError.message).to.equal("unknown");
});
});

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 @@ -51,6 +51,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 @@ -87,6 +88,7 @@ import {
PrebuiltWorkspaceState,
Token,
SuggestedRepository as SuggestedRepositoryProtocol,
UserSSHPublicKeyValue,
} from "./protocol";
import {
OrgMemberInfo,
Expand Down Expand Up @@ -897,4 +899,15 @@ export class PublicAPIConverter {
configurationName: r.projectName,
});
}

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;
}
}
2 changes: 1 addition & 1 deletion components/public-api/gitpod/v1/ssh.proto
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ message CreateSSHPublicKeyResponse {
}

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

message DeleteSSHPublicKeyResponse {}
Loading
Loading