Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/main' into 01395-update-account-…
Browse files Browse the repository at this point in the history
…init-to-upload-secrets-to-all-clusters

# Conflicts:
#	src/commands/account.ts
  • Loading branch information
instamenta committed Feb 16, 2025
2 parents 64304ce + f3f7958 commit 93a3f8d
Show file tree
Hide file tree
Showing 5 changed files with 120 additions and 14 deletions.
63 changes: 59 additions & 4 deletions src/commands/account.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,21 @@ import {IllegalArgumentError, SoloError} from '../core/errors.js';
import {Flags as flags} from './flags.js';
import {Listr} from 'listr2';
import * as constants from '../core/constants.js';
import * as helpers from '../core/helpers.js';
import {FREEZE_ADMIN_ACCOUNT} from '../core/constants.js';
import {type AccountManager} from '../core/account_manager.js';
import {type AccountId, AccountInfo, HbarUnit, PrivateKey} from '@hashgraph/sdk';
import {type AccountId, AccountInfo, HbarUnit, NodeUpdateTransaction, PrivateKey} from '@hashgraph/sdk';
import {ListrLease} from '../core/lease/listr_lease.js';
import {type CommandBuilder} from '../types/aliases.js';
import {sleep} from '../core/helpers.js';
import {type CommandBuilder, type NodeAliases} from '../types/aliases.js';
import {resolveNamespaceFromDeployment} from '../core/resolvers.js';
import {Duration} from '../core/time/duration.js';
import {type NamespaceName} from '../core/kube/resources/namespace/namespace_name.js';
import {type DeploymentName} from '../core/config/remote/types.js';
import {type SoloListrTask} from '../types/index.js';
import {Templates} from '../core/templates.js';
import {sleep} from '../core/helpers.js';
import {SecretType} from '../core/kube/resources/secret/secret_type.js';
import {Base64} from 'js-base64';

export class AccountCommand extends BaseCommand {
private readonly accountManager: AccountManager;
Expand Down Expand Up @@ -152,6 +156,7 @@ export class AccountCommand extends BaseCommand {
interface Context {
config: {
namespace: NamespaceName;
nodeAliases: NodeAliases;
contexts: string[];
};
updateSecrets: boolean;
Expand All @@ -172,6 +177,7 @@ export class AccountCommand extends BaseCommand {
const config = {
namespace: await resolveNamespaceFromDeployment(this.localConfig, this.configManager, task),
contexts: this.getContexts(),
nodeAliases: helpers.parseNodeAliases(this.configManager.getFlag(flags.nodeAliasesUnparsed)),
};

if (!(await this.k8Factory.getK8(config.contexts[0]).namespaces().has(config.namespace))) {
Expand Down Expand Up @@ -254,6 +260,55 @@ export class AccountCommand extends BaseCommand {
});
},
},
{
title: 'Update node admin key',
task: async ctx => {
const adminKey = PrivateKey.fromStringED25519(constants.GENESIS_KEY);
for (const nodeAlias of ctx.config.nodeAliases) {
const nodeId = Templates.nodeIdFromNodeAlias(nodeAlias);
const nodeClient = await self.accountManager.refreshNodeClient(
ctx.config.namespace,
nodeAlias,
self.getClusterRefs(),
this.configManager.getFlag<DeploymentName>(flags.deployment),
);

try {
let nodeUpdateTx = new NodeUpdateTransaction().setNodeId(nodeId);
const newPrivateKey = PrivateKey.generateED25519();

nodeUpdateTx = nodeUpdateTx.setAdminKey(newPrivateKey.publicKey);
nodeUpdateTx = nodeUpdateTx.freezeWith(nodeClient);
nodeUpdateTx = await nodeUpdateTx.sign(newPrivateKey);
const signedTx = await nodeUpdateTx.sign(adminKey);
const txResp = await signedTx.execute(nodeClient);
const nodeUpdateReceipt = await txResp.getReceipt(nodeClient);

self.logger.debug(`NodeUpdateReceipt: ${nodeUpdateReceipt.toString()} for node ${nodeAlias}`);

// save new key in k8s secret
const data = {
privateKey: Base64.encode(newPrivateKey.toString()),
publicKey: Base64.encode(newPrivateKey.publicKey.toString()),
};
await this.k8Factory
.default()
.secrets()
.create(
ctx.config.namespace,
Templates.renderNodeAdminKeyName(nodeAlias),
SecretType.OPAQUE,
data,
{
'solo.hedera.com/node-admin-key': 'true',
},
);
} catch (e) {
throw new SoloError(`Error updating admin key for node ${nodeAlias}: ${e.message}`, e);
}
}
},
},
{
title: 'Display results',
task: ctx => {
Expand Down Expand Up @@ -572,7 +627,7 @@ export class AccountCommand extends BaseCommand {
.command({
command: 'init',
desc: 'Initialize system accounts with new keys',
builder: (y: any) => flags.setCommandFlags(y, flags.deployment),
builder: (y: any) => flags.setCommandFlags(y, flags.deployment, flags.nodeAliasesUnparsed),
handler: (argv: any) => {
self.logger.info("==== Running 'account init' ===");
self.logger.info(argv);
Expand Down
22 changes: 19 additions & 3 deletions src/commands/node/tasks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,10 +68,11 @@ import {ContainerRef} from '../../core/kube/resources/container/container_ref.js
import {NetworkNodes} from '../../core/network_nodes.js';
import {container} from 'tsyringe-neo';
import * as helpers from '../../core/helpers.js';
import {type Optional, type SoloListrTask, type SoloListrTaskWrapper} from '../../types/index.js';
import {type Optional} from '../../types/index.js';
import {type DeploymentName} from '../../core/config/remote/types.js';
import {ConsensusNode} from '../../core/model/consensus_node.js';
import {type K8} from '../../core/kube/k8.js';
import {Base64} from 'js-base64';

export class NodeCommandTasks {
private readonly accountManager: AccountManager;
Expand Down Expand Up @@ -620,9 +621,24 @@ export class NodeCommandTasks {
}

loadAdminKey() {
return new Task('Load node admin key', (ctx: any, task: ListrTaskWrapper<any, any, any>) => {
return new Task('Load node admin key', async (ctx: any, task: ListrTaskWrapper<any, any, any>) => {
const config = ctx.config;
config.adminKey = PrivateKey.fromStringED25519(constants.GENESIS_KEY);
if (ctx.config.nodeAlias) {
try {
// load nodeAdminKey form k8s if exist
const keyFromK8 = await this.k8Factory
.default()
.secrets()
.read(config.namespace, Templates.renderNodeAdminKeyName(config.nodeAlias));
const privateKey = Base64.decode(keyFromK8.data.privateKey);
config.adminKey = PrivateKey.fromStringED25519(privateKey);
} catch (e: Error | any) {
this.logger.debug(`Error in loading node admin key: ${e.message}, use default key`);
config.adminKey = PrivateKey.fromStringED25519(constants.GENESIS_KEY);
}
} else {
config.adminKey = PrivateKey.fromStringED25519(constants.GENESIS_KEY);
}
});
}

Expand Down
4 changes: 4 additions & 0 deletions src/core/templates.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,10 @@ export class Templates {
return `hedera-${nodeAlias}.crt`;
}

public static renderNodeAdminKeyName(nodeAlias: NodeAlias): string {
return `${nodeAlias}-admin`;
}

public static renderNodeFriendlyName(prefix: string, nodeAlias: NodeAlias, suffix = ''): string {
const parts = [prefix, nodeAlias];
if (suffix) parts.push(suffix);
Expand Down
18 changes: 17 additions & 1 deletion test/e2e/commands/account.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ import {NamespaceName} from '../../../src/core/kube/resources/namespace/namespac
import {type NetworkNodes} from '../../../src/core/network_nodes.js';
import {container} from 'tsyringe-neo';
import {InjectTokens} from '../../../src/core/dependency_injection/inject_tokens.js';
import * as helpers from '../../../src/core/helpers.js';
import {Templates} from '../../../src/core/templates.js';
import * as Base64 from 'js-base64';

const defaultTimeout = Duration.ofSeconds(20).toMillis();

Expand All @@ -40,7 +43,7 @@ const testSystemAccounts = [[3, 5]];
const argv = getDefaultArgv(namespace);
argv[flags.namespace.name] = namespace.name;
argv[flags.releaseTag.name] = HEDERA_PLATFORM_VERSION_TAG;
argv[flags.nodeAliasesUnparsed.name] = 'node1';
argv[flags.nodeAliasesUnparsed.name] = 'node1,node2';
argv[flags.generateGossipKeys.name] = true;
argv[flags.generateTlsKeys.name] = true;
argv[flags.clusterRef.name] = TEST_CLUSTER;
Expand Down Expand Up @@ -112,6 +115,19 @@ e2eTestSuite(testName, argv, undefined, undefined, undefined, undefined, undefin
await accountManager.close();
});

it('Node admin key should have been updated, not eqaul to genesis key', async () => {
const nodeAliases = helpers.parseNodeAliases(argv[flags.nodeAliasesUnparsed.name]);
for (const nodeAlias of nodeAliases) {
const keyFromK8 = await k8Factory
.default()
.secrets()
.read(namespace, Templates.renderNodeAdminKeyName(nodeAlias));
const privateKey = Base64.decode(keyFromK8.data.privateKey);

expect(privateKey.toString()).not.to.equal(genesisKey.toString());
}
});

for (const [start, end] of testSystemAccounts) {
for (let i = start; i <= end; i++) {
it(`account ${i} should not have genesis key`, async () => {
Expand Down
27 changes: 21 additions & 6 deletions test/e2e/dual-cluster/README.md
Original file line number Diff line number Diff line change
@@ -1,20 +1,25 @@
# Local Dual Cluster Testing

This document describes how to test the dual cluster setup locally.

## Prerequisites
- Make sure you give your Docker sufficient resources
- ? CPUs
- ? GB RAM
- ? GB Swap
- ? GB Disk Space
- If you are tight on resources you might want to make sure that no other Kind clusters are running or anything that is resource heavy on your machine.

* Make sure you give your Docker sufficient resources
* ? CPUs
* ? GB RAM
* ? GB Swap
* ? GB Disk Space
* If you are tight on resources you might want to make sure that no other Kind clusters are running or anything that is resource heavy on your machine.

## Calling

```bash
# from your Solo root directory run:
./test/e2e/dual-cluster/setup-dual-e2e.sh
```

Output:

```bash
SOLO_CHARTS_DIR:
Deleting cluster "solo-e2e-c1" ...
Expand Down Expand Up @@ -180,28 +185,38 @@ metrics-server kube-system 1 2025-02-14 16:05:07.217358 +0000 UTC
solo-cluster-setup solo-setup 1 2025-02-14 16:05:58.114619 +0000 UTC deployed solo-cluster-setup-0.44.0 0.44.0
Switched to context "kind-solo-e2e-c1".
```

## Diagnostics

The `./diagnostics/cluster/deploy.sh` deploys a `cluster-diagnostics` deployment (and its pod) with a service that has its external IP exposed. It is deployed to both clusters, runs Ubuntu, and has most diagnostic software installed. After ran you can shell into the pod and use the container to run your own troubleshooting commands for verifying network connectivity between the two clusters or DNS resolution, etc.

Calling

```bash
# from your Solo root directory run:
$ ./test/e2e/dual-cluster/diagnostics/cluster/deploy.sh
```

Output:

```bash
namespace/cluster-diagnostics unchanged
configmap/cluster-diagnostics-cm unchanged
service/cluster-diagnostics-svc unchanged
deployment.apps/cluster-diagnostics unchanged
```

## Cleanup

Calling

```bash
# from your Solo root directory run:
kind delete clusters cluster1 cluster2
```

Output:

```bash
Deleted clusters: ["cluster1" "cluster2"]
```

0 comments on commit 93a3f8d

Please sign in to comment.