Skip to content

Commit

Permalink
Merge pull request #13 from omnia-network/lorenzo/handle-tls-certifcate
Browse files Browse the repository at this point in the history
Canister management of the mutual tls certificate
  • Loading branch information
ilbertt authored Jun 6, 2024
2 parents 05df509 + e071c21 commit 9c35a4a
Show file tree
Hide file tree
Showing 13 changed files with 227 additions and 113 deletions.
19 changes: 11 additions & 8 deletions deps/candid/rdmx6-jaaaa-aaaaa-aaadq-cai.did
Original file line number Diff line number Diff line change
Expand Up @@ -171,8 +171,9 @@ type InternetIdentityStats = record {
};
archive_info: ArchiveInfo;
canister_creation_cycles_cost: nat64;
max_num_latest_delegation_origins: nat64;
latest_delegation_origins: vec FrontendHostname
// Map from event aggregation to a sorted list of top 100 sub-keys to their weights.
// Example: {"prepare_delegation_count 24h ic0.app": [{"https://dapp.com", 100}, {"https://dapp2.com", 50}]}
event_aggregations: vec record {text; vec record {text; nat64}};
};

// Configuration parameters related to the archive.
Expand Down Expand Up @@ -227,9 +228,6 @@ type InternetIdentityInit = record {
canister_creation_cycles_cost : opt nat64;
// Rate limit for the `register` call.
register_rate_limit : opt RateLimitConfig;
// Maximum number of latest delegation origins to track.
// Default: 1000
max_num_latest_delegation_origins : opt nat64;
// Maximum number of inflight captchas.
// Default: 500
max_inflight_captchas: opt nat64;
Expand Down Expand Up @@ -538,12 +536,17 @@ service : (opt InternetIdentityInit) -> {
http_request_update: (request: HttpRequest) -> (HttpResponse);

deploy_archive: (wasm: blob) -> (DeployArchiveResult);
/// Returns a batch of entries _sorted by sequence number_ to be archived.
/// This is an update call because the archive information _must_ be certified.
/// Only callable by this IIs archive canister.
// Returns a batch of entries _sorted by sequence number_ to be archived.
// This is an update call because the archive information _must_ be certified.
// Only callable by this IIs archive canister.
fetch_entries: () -> (vec BufferedArchiveEntry);
acknowledge_entries: (sequence_number: nat64) -> ();

// Calls used for event stats housekeeping.
// Only callable by the canister itself.
prune_events_if_necessary: () -> ();
inject_prune_event: (timestamp: Timestamp) -> ();

// V2 API
// WARNING: The following methods are experimental and may change in the future.

Expand Down
3 changes: 2 additions & 1 deletion deps/pulled.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@
"rdmx6-jaaaa-aaaaa-aaadq-cai": {
"name": "internet_identity",
"wasm_hash": "764cff569a98a3c4d54cba6750fda63f554fc53e7d42a6365d9bdec3280d63c3",
"wasm_hash_download": "764cff569a98a3c4d54cba6750fda63f554fc53e7d42a6365d9bdec3280d63c3",
"init_guide": "Use '(null)' for sensible defaults. See the candid interface for more details.",
"init_arg": null,
"candid_args": "(opt InternetIdentityInit)",
"gzip": true
}
}
}
}
110 changes: 70 additions & 40 deletions frontend/app/dashboard/new-deployment/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@ import { BackButton } from "@/components/back-button";
import { useToast } from "@/components/ui/use-toast";
import { useDeploymentContext } from "@/contexts/DeploymentContext";
import {
type OnWsErrorCallback,
type OnWsMessageCallback,
type OnWsOpenCallback,
useIcContext,
type OnWsErrorCallback,
} from "@/contexts/IcContext";
import type { DeploymentParams, DeploymentState } from "@/declarations/backend.did";
import { extractDeploymentCreated } from "@/helpers/deployment";
Expand All @@ -18,9 +18,9 @@ import { useCallback, useEffect, useMemo, useState } from "react";
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
import { AlertCircle } from "lucide-react";
import { displayE8sAsIcp, icpToE8s } from "@/helpers/ui";
import { transferE8sToBackend } from "@/services/backend";
import { Spinner } from "@/components/spinner";
import { NewDeploymentForm } from "@/components/new-deployment-form";
import { transferE8sToBackend } from "@/services/backend";

const FETCH_DEPLOYMENT_PRICE_INTERVAL_MS = 30_000; // 30 seconds

Expand Down Expand Up @@ -58,12 +58,50 @@ export default function NewDeployment() {
[toast]
);

const sendIcpToBackend = useCallback(async (userCanisterBalance: number) => {
if (!backendActor || !ledgerCanister) {
toastError("Backend actor or ledger canister not found");
return;
}

if (!deploymentE8sPrice) {
toastError("Deployment price not fetched");
return;
}

const icpToSend = deploymentE8sPrice - BigInt(userCanisterBalance);

setDeploymentSteps([]);
setDeploymentError(null);
setIsDeploying(false);
setIsSubmitting(true);
setPaymentStatus(null);

try {
setPaymentStatus(`Sending ~${displayE8sAsIcp(icpToSend)} to backend canister...`);

await transferE8sToBackend(
ledgerCanister,
icpToSend,
backendActor
);
await refreshLedgerData();

setPaymentStatus(prev => prev + " DONE");
} catch (e) {
console.error("Failed to transfer funds:", e);
toastError("Failed to transfer funds, see console for details");
setPaymentStatus(prev => prev + " FAILED");
setDeploymentParams(null);
setIsSubmitting(false);
return;
}
}, [backendActor, deploymentE8sPrice, ledgerCanister, refreshLedgerData, toastError]);

const onWsOpen: OnWsOpenCallback = useCallback(async () => {
console.log("ws open");

setIsDeploying(true);
setIsSubmitting(false);
try {
const createDeployment = async () => {
if (!backendActor) {
throw new Error("No backend actor");
}
Expand All @@ -74,16 +112,31 @@ export default function NewDeployment() {

const res = await backendActor.create_deployment(deploymentParams);
const deploymentId = extractOk(res);

console.log("deployment id", deploymentId);

setDeploymentSteps([{ Initialized: null }]);
} catch (e) {
};

setIsDeploying(true);
setIsSubmitting(false);
try {
await createDeployment();
} catch (e: any) {
console.error("Failed to create deployment:", e);
setDeploymentError("Failed to create deployment, see console for details");

setIsDeploying(false);

if (e.message.startsWith("Not enough balance. Required: ")) {
console.warn("Failed to create deployment, insufficient balance. Auto top-up initiated.");

const userCanisterBalance = parseInt(e.message.replace("Not enough balance. Required: ", "").replace(" ICP", ""));
await sendIcpToBackend(userCanisterBalance);

await createDeployment();
} else {
setDeploymentError("Failed to create deployment, see console for details");
}
}
}, [backendActor, deploymentParams]);
}, [backendActor, deploymentParams, sendIcpToBackend]);

const onWsMessage: OnWsMessageCallback = useCallback(
async (ev) => {
Expand Down Expand Up @@ -200,7 +253,7 @@ export default function NewDeployment() {
);

const handleDeploy = useCallback(async (values: DeploymentParams) => {
if (!backendActor || !ledgerCanister) {
if (!backendActor) {
toastError("Backend actor or ledger canister not found");
return;
}
Expand All @@ -210,43 +263,26 @@ export default function NewDeployment() {
return;
}

if (!deploymentE8sPrice) {
toastError("Deployment price not fetched");
return;
}

setDeploymentSteps([]);
setDeploymentError(null);
setDeploymentParams(values);
setIsDeploying(false);
setIsSubmitting(true);
setPaymentStatus(null);

try {
console.log("Retrieving mTLS certificate...");
const cert = await loadOrCreateCertificate(backendActor!);
if (!cert) {
throw new Error("No certificate");
}

setPaymentStatus(`Sending ~${displayE8sAsIcp(deploymentE8sPrice)} to backend canister...`);

await transferE8sToBackend(
ledgerCanister,
deploymentE8sPrice,
backendActor
);
await refreshLedgerData();
console.log("mTLS certificate retrieved");

if (fetchDeploymentPriceInterval !== null) {
clearInterval(fetchDeploymentPriceInterval);
setFetchDeploymentPriceInterval(null);
}

setPaymentStatus(prev => prev + " DONE");
} catch (e) {
console.error("Failed to transfer funds:", e);
toastError("Failed to transfer funds, see console for details");
setPaymentStatus(prev => prev + " FAILED");
setDeploymentParams(null);
setIsSubmitting(false);
return;
Expand All @@ -268,9 +304,6 @@ export default function NewDeployment() {
userHasEnoughBalance,
toastError,
backendActor,
ledgerCanister,
refreshLedgerData,
deploymentE8sPrice,
fetchDeploymentPriceInterval,
loadOrCreateCertificate,
]);
Expand All @@ -284,12 +317,8 @@ export default function NewDeployment() {
const res = await backendActor.get_deployment_icp_price();
const icpPrice = extractOk(res);

// add 1 ICP to cover price fluctuation
//
// TODO: try to deploy and parse the error from the canister.
// If the error is a not enough balance error, then we can
// send funds to the canister and try again.
setDeploymentE8sPrice(icpToE8s(icpPrice + 1));
// add 1% ICP to cover price fluctuation
setDeploymentE8sPrice(icpToE8s(icpPrice * 1.01));
} catch (e) {
console.error("Failed to fetch deployment price:", e);
toastError("Failed to fetch deployment price, see console for details");
Expand Down Expand Up @@ -358,7 +387,8 @@ export default function NewDeployment() {
>
{ledgerData.accountId?.toHex()}
</pre>
<p className="mt-2">If you&apos;ve already topped up your account, please refresh the balance on the top bar.</p>
<p className="mt-2">If you&apos;ve already topped up your account, please refresh the balance on the
top bar.</p>
</AlertDescription>
</Alert>
)}
Expand Down
17 changes: 7 additions & 10 deletions frontend/contexts/DeploymentContext.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { type _SERVICE, type GetDeploymentsResult } from "@/declarations/backend.did";
import { type GetDeploymentsResult } from "@/declarations/backend.did";
import { getDeploymentCreatedDate } from "@/helpers/deployment";
import { type OkType, extractOk } from "@/helpers/result";
import { X509CertificateData, createX509, loadCertificate, saveCertificate } from "@/lib/certificate";
import { extractOk, type OkType } from "@/helpers/result";
import { createX509, X509CertificateData } from "@/lib/certificate";
import { type BackendActor } from "@/services/backend";
import { createContext, useCallback, useContext, useState } from "react";
import { getCurrentUser } from "@/services/user";

export type Deployments = OkType<GetDeploymentsResult>;

Expand Down Expand Up @@ -38,17 +39,13 @@ export const DeploymentProvider: React.FC<DeploymentProviderProps> = ({ children
}, []);

const loadOrCreateCertificate = useCallback(async (actor: BackendActor): Promise<X509CertificateData | null> => {
let certData = loadCertificate();
let certData = (await getCurrentUser(actor)).mtls_certificate[0] || null;

if (!certData) {
try {
const akashAddress = extractOk(await actor.address());
certData = await createX509(akashAddress);
extractOk(await actor.create_certificate(
Buffer.from(certData.cert, "utf-8").toString("base64"),
Buffer.from(certData.pubKey, "utf-8").toString("base64"),
));

saveCertificate(certData);
extractOk(await actor.create_certificate(certData));
} catch (e) {
console.error(e);
alert("Failed to create certificate, see console for details");
Expand Down
8 changes: 7 additions & 1 deletion frontend/declarations/backend.did.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,11 @@ export interface LogsFilterRequest {
'after_timestamp_ms' : [] | [bigint],
'before_timestamp_ms' : [] | [bigint],
}
export interface MTlsCertificateData {
'cert' : string,
'pub_key' : string,
'priv_key' : string,
}
export type Memo = bigint;
export type MemorySize = { 'Large' : null } |
{ 'Small' : null } |
Expand Down Expand Up @@ -233,6 +238,7 @@ export type TransferResult = { 'Ok' : BlockIndex } |
export interface User {
'akt_balance' : number,
'payments' : BigUint64Array | bigint[],
'mtls_certificate' : [] | [MTlsCertificateData],
'role' : UserRole,
'created_at' : TimestampNs,
}
Expand All @@ -251,7 +257,7 @@ export interface _SERVICE {
'balance' : ActorMethod<[], ApiNatResult>,
'check_tx' : ActorMethod<[string], ApiEmptyResult>,
'close_deployment' : ActorMethod<[string], ApiEmptyResult>,
'create_certificate' : ActorMethod<[string, string], ApiStringResult>,
'create_certificate' : ActorMethod<[MTlsCertificateData], ApiStringResult>,
'create_deployment' : ActorMethod<[DeploymentParams], CreateDeploymentResult>,
'create_test_deployment' : ActorMethod<[], CreateDeploymentResult>,
'create_user' : ActorMethod<[], CreateUserResult>,
Expand Down
8 changes: 7 additions & 1 deletion frontend/declarations/backend.did.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@ export const idlFactory = ({ IDL }) => {
const ApiStringResult = IDL.Variant({ 'Ok' : IDL.Text, 'Err' : ApiError });
const ApiNatResult = IDL.Variant({ 'Ok' : IDL.Nat64, 'Err' : ApiError });
const ApiEmptyResult = IDL.Variant({ 'Ok' : IDL.Null, 'Err' : ApiError });
const MTlsCertificateData = IDL.Record({
'cert' : IDL.Text,
'pub_key' : IDL.Text,
'priv_key' : IDL.Text,
});
const CpuSize = IDL.Variant({
'Large' : IDL.Null,
'Small' : IDL.Null,
Expand Down Expand Up @@ -80,6 +85,7 @@ export const idlFactory = ({ IDL }) => {
const User = IDL.Record({
'akt_balance' : IDL.Float64,
'payments' : IDL.Vec(IDL.Nat64),
'mtls_certificate' : IDL.Opt(MTlsCertificateData),
'role' : UserRole,
'created_at' : TimestampNs,
});
Expand Down Expand Up @@ -248,7 +254,7 @@ export const idlFactory = ({ IDL }) => {
'check_tx' : IDL.Func([IDL.Text], [ApiEmptyResult], []),
'close_deployment' : IDL.Func([IDL.Text], [ApiEmptyResult], []),
'create_certificate' : IDL.Func(
[IDL.Text, IDL.Text],
[MTlsCertificateData],
[ApiStringResult],
[],
),
Expand Down
Loading

0 comments on commit 9c35a4a

Please sign in to comment.