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

ALL-9923 Added report feature #111

Merged
merged 1 commit into from
Dec 6, 2024
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
36 changes: 35 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -374,7 +374,7 @@ When KMS runs in [daemon mode](#run-kms-in-daemon-mode), use the following comma
}
}
```
* `checkconfig` shows environment variables for Tatum KMS (for debugging).
* `checkconfig`(for debugging) shows environment variables for Tatum KMS.

```
bash:$ tatum-kms checkconfig
Expand All @@ -385,6 +385,40 @@ When KMS runs in [daemon mode](#run-kms-in-daemon-mode), use the following comma
Env file : .env
TATUM_API_KEY : d2eb5c******************************
...
```
* `report`(for debugging) shows report of system and requested wallets (+ warnings if they were found)

```
bash:$ tatum-kms e3015fc0-2112-4c8a-b8bf-353b86f63ba5,11115fc0-2112-4c8a-b8bf-353b86f63111
{
"system": {
"kmsVersion": "7.0.6",
"nodeVersion": "v18.18.2",
"store": {
"type": "LOCAL",
"exists": true
}
},
"wallets": {
"e3015fc0-2112-4c8a-b8bf-353b86f63ba5": {
"type": "PRIVATE_KEY",
"chain": "BTC",
"testnet": true
},
"11115fc0-2112-4c8a-b8bf-353b86f63111": {
"type": "MNEMONIC",
"chain": "ETH",
"testnet": true,
"warnings": [
"No xpub found"
]
}
},
"apiKey": "t-6111***************************************222222",
"warnings": [
"Wallets file was is not accessible"
]
}
```

## Common issues
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@tatumio/tatum-kms",
"version": "7.0.6",
"version": "7.0.7",
"description": "Tatum KMS - Key Management System for Tatum-powered apps.",
"main": "dist/index.js",
"types": "./dist/index.d.ts",
Expand Down
24 changes: 19 additions & 5 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
getQuestion,
getWallet,
removeWallet,
report,
setTatumKey,
storePrivateKey,
storeWallet,
Expand All @@ -23,6 +24,7 @@ import HttpAgent from 'agentkeepalive'
import { existsSync } from 'fs'
import * as process from 'process'
import { homedir } from 'os'
import { utils } from './utils'

dotenv.config()

Expand All @@ -44,30 +46,34 @@ const axiosInstance = axios.create({
const { input: command, flags, help } = meow(
`
Usage
$ tatum-kms command
$ tatum-kms <command>

Commands
daemon Run as a daemon, which periodically checks for a new transactions to sign.
generatewallet <chain> Generate wallet for a specific blockchain and echo it to the output.
generatemanagedwallet <chain> Generate wallet for a specific blockchain and add it to the managed wallets.
storemanagedwallet <chain> Store mnemonic-based wallet for a specific blockchain and add it to the managed wallets.
storemanagedprivatekey <chain> Store private key of a specific blockchain and add it to the managed wallets.
generatemanagedprivatekeybatch <chain> <cnt> generate and store "cnt" number of private keys for a specific blockchain. This operation is usefull, if you wanna pregenerate bigger amount of managed private keys for later use.
generatemanagedprivatekeybatch <chain> <cnt> Generate and store "cnt" number of private keys for a specific blockchain. This operation is usefull, if you wanna pregenerate bigger amount of managed private keys for later use.
getprivatekey <signatureId> <i> Obtain managed wallet from wallet store and generate private key for given derivation index.
getaddress <signatureId> <i> Obtain managed wallet from wallet store and generate address for given derivation index.
getmanagedwallet <signatureId> Obtain managed wallet / private key from wallet store.
removewallet <signatureId> Remove managed wallet from wallet store.
export Export all managed wallets.

Debugging
report Shows report of system and requested wallets (+ warnings if they were found)
checkconfig Shows environment variables for Tatum KMS.

Options
--api-key Tatum API Key to communicate with Tatum API. Daemon mode only.
--testnet Indicates testnet version of blockchain. Mainnet by default.
--path Custom path to wallet store file.
--period Period in seconds to check for new transactions to sign, defaults to 5 seconds. Daemon mode only.
--chain Blockchains to check, separated by comma. Daemon mode only.
--env-file Path to .env file to set vars.
--aws Using AWS Secrets Manager (https://aws.amazon.com/secrets-manager/) as a secure storage of the password which unlocks the wallet file.
--vgs Using VGS (https://verygoodsecurity.com) as a secure storage of the password which unlocks the wallet file.
--aws Using AWS Secrets Manager (https://aws.amazon.com/secrets-manager/) as a secure storage of the password which unlocks the wallet file.
--vgs Using VGS (https://verygoodsecurity.com) as a secure storage of the password which unlocks the wallet file.
--azure Using Azure Vault (https://azure.microsoft.com/en-us/services/key-vault/) as a secure storage of the password which unlocks the wallet file.
--externalUrl Pass in external url to check valid transaction. This parameter is mandatory for mainnet (if testnet is false). Daemon mode only.
--externalUrlMethod Determine what http method to use when calling the url passed in the --externalUrl option. Accepts GET or POST. Defaults to GET method. Daemon mode only.
Expand Down Expand Up @@ -115,7 +121,7 @@ const { input: command, flags, help } = meow(
runOnce: {
type: 'boolean',
default: false,
}
},
},
},
)
Expand Down Expand Up @@ -217,6 +223,14 @@ const startup = async () => {
case 'checkconfig':
checkConfig(getPasswordType(), envFilePath, flags.path)
break
case 'report':
await report(
utils.csvToArray(command[1]),
getPasswordType(),
await getPassword(getPasswordType(), axiosInstance),
flags.path,
)
break
default:
console.error('Unsupported command. Use tatum-kms --help for details.')
process.exit(-1)
Expand Down
41 changes: 40 additions & 1 deletion src/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,11 @@ export interface Signature {

export interface Wallet {
mnemonic: string
xpub: string
testnet: boolean
privateKey: string
secret: string
chain: Currency
}

export interface WalletsValidationOptions {
Expand All @@ -29,4 +32,40 @@ export interface StoreWalletValue {
xpub?: string
}

export type ExternalUrlMethod = 'GET' | 'POST';
export type ExternalUrlMethod = 'GET' | 'POST'

export type Report = {
system: {
kmsVersion: string
nodeVersion: string
store: {
type: string
exists: boolean
}
}
wallets: Record<string, ReportWallet>
apiKey: string
warnings?: string[]
}

export type ReportWallet = {
type: WalletType
chain: Currency
testnet: boolean
warnings?: string[]
}

export enum WalletType {
MNEMONIC = 'MNEMONIC',
PRIVATE_KEY = 'PRIVATE_KEY',
SECRET = 'SECRET',
OTHER = 'OTHER',
}

export enum WalletStoreType {
LOCAL = 'LOCAL',
VGS = 'VGS',
AZURE = 'AZURE',
AWS = 'AWS',
NA = 'N/A',
}
165 changes: 127 additions & 38 deletions src/management.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,19 @@
import { question } from 'readline-sync'
import { v4 as uuid } from 'uuid'
import { Config, ConfigOption } from './config'
import { PasswordType, Signature, StoreWalletValue, WalletsValidationOptions } from './interfaces'
import {
PasswordType,
Report,
ReportWallet,
Signature,
StoreWalletValue,
Wallet,
WalletStoreType,
WalletsValidationOptions,
WalletType,
} from './interfaces'
import { utils } from './utils'
import semver from 'semver'

const ensurePathExists = (path: string) => {
const dir = dirname(path)
Expand Down Expand Up @@ -122,7 +134,7 @@
}

const generatePureWallet = async (chain: Currency, testnet: boolean, mnemonic?: string) => {
let wallet: any

Check warning on line 137 in src/management.ts

View workflow job for this annotation

GitHub Actions / 🏗️ Install and build

Unexpected any. Specify a different type
if (chain === Currency.SOL) {
const sdk = TatumSolanaSDK({ apiKey: '' })
wallet = sdk.wallet.wallet()
Expand Down Expand Up @@ -224,7 +236,7 @@
const cnt = Number(count)
for (let i = 0; i < cnt; i++) {
const wallet = await generatePureWallet(chain, testnet)
let address: any

Check warning on line 239 in src/management.ts

View workflow job for this annotation

GitHub Actions / 🏗️ Install and build

Unexpected any. Specify a different type
if (wallet.address) {
address = wallet.address
} else {
Expand Down Expand Up @@ -261,7 +273,7 @@
}

// TODO: validate all properties from wallet and create a type or interface to replace any bellow
export const isWalletsValid = (wallets: any, options: WalletsValidationOptions) => {

Check warning on line 276 in src/management.ts

View workflow job for this annotation

GitHub Actions / 🏗️ Install and build

Unexpected any. Specify a different type
if (Object.keys(wallets).length === 0) {
console.error(JSON.stringify({ error: `No such wallet for chain '${options.chain}'.` }, null, 2))
return false
Expand Down Expand Up @@ -354,7 +366,7 @@
console.error(JSON.stringify({ error: `No such wallet for signatureId '${id}'.` }, null, 2))
return null
}
let pk: { address: any }

Check warning on line 369 in src/management.ts

View workflow job for this annotation

GitHub Actions / 🏗️ Install and build

Unexpected any. Specify a different type
if (wallet[id].address) {
pk = {
address: wallet[id].address,
Expand Down Expand Up @@ -395,57 +407,126 @@
writeFileSync(pathToWallet, AES.encrypt(JSON.stringify(wallet), pwd).toString())
}

function parseWalletStoreName(pwdType: PasswordType): string {
function parseWalletStoreName(pwdType: PasswordType): WalletStoreType {
if (pwdType === PasswordType.CMD_LINE) {
return 'LOCAL'
return WalletStoreType.LOCAL
} else if (pwdType === PasswordType.VGS) {
return 'VGS'
return WalletStoreType.VGS
} else if (pwdType === PasswordType.AZURE) {
return 'AZURE'
return WalletStoreType.AZURE
} else if (pwdType === PasswordType.AWS) {
return 'AWS'
return WalletStoreType.AWS
}
return 'N/A'
}

function hidePassword(password: string | undefined, showSymbols = 6): string {
if (!password) {
return ''
}
if (password.length <= showSymbols) {
return '*'.repeat(password.length)
}
return password.slice(0, showSymbols) + '*'.repeat(password.length - showSymbols)
}

function secretValue(secretValue: string | undefined): string {
if (!secretValue) {
return 'N/A'
}
return hidePassword(secretValue)
return WalletStoreType.NA
}

export const checkConfig = (pwdType: PasswordType, envFile?: string, path?: string) => {
const pathToWallet = path || homedir() + '/.tatumrc/wallet.dat'
console.log(`Version : ${process.env.npm_package_version ?? 'N/A'}`)
console.log(`Version : ${getKmsVersion()}`)
console.log(`Wallet file path : ${pathToWallet}`)
console.log(`Wallet exists : ${existsSync(pathToWallet)}`)
console.log(`Wallet store type : ${parseWalletStoreName(pwdType)}`)
console.log(`Environment vars file : ${envFile ?? 'N/A'}`)
console.log(`TATUM_API_KEY : ${secretValue(process.env.TATUM_API_KEY)}`)
console.log(`TATUM_KMS_PASSWORD : ${secretValue(process.env.TATUM_KMS_PASSWORD)}`)
console.log(`TATUM_KMS_VGS_ALIAS : ${secretValue(process.env.TATUM_KMS_VGS_ALIAS)}`)
console.log(`TATUM_KMS_VGS_USERNAME : ${secretValue(process.env.TATUM_KMS_VGS_USERNAME)}`)
console.log(`TATUM_KMS_VGS_PASSWORD : ${secretValue(process.env.TATUM_KMS_VGS_PASSWORD)}`)
console.log(`TATUM_KMS_AZURE_SECRETVERSION : ${secretValue(process.env.TATUM_KMS_AZURE_SECRETVERSION)}`)
console.log(`TATUM_KMS_AZURE_SECRETNAME : ${secretValue(process.env.TATUM_KMS_AZURE_SECRETNAME)}`)
console.log(`TATUM_KMS_AZURE_VAULTURL : ${secretValue(process.env.TATUM_KMS_AZURE_VAULTURL)}`)
console.log(`TATUM_API_KEY : ${utils.hideApiKey(process.env.TATUM_API_KEY)}`)
console.log(`TATUM_KMS_PASSWORD : ${utils.hidePassword(process.env.TATUM_KMS_PASSWORD)}`)
console.log(`TATUM_KMS_VGS_ALIAS : ${utils.hidePassword(process.env.TATUM_KMS_VGS_ALIAS)}`)
console.log(`TATUM_KMS_VGS_USERNAME : ${utils.hidePassword(process.env.TATUM_KMS_VGS_USERNAME)}`)
console.log(`TATUM_KMS_VGS_PASSWORD : ${utils.hidePassword(process.env.TATUM_KMS_VGS_PASSWORD)}`)
console.log(`TATUM_KMS_AZURE_SECRETVERSION : ${utils.hidePassword(process.env.TATUM_KMS_AZURE_SECRETVERSION)}`)
console.log(`TATUM_KMS_AZURE_SECRETNAME : ${utils.hidePassword(process.env.TATUM_KMS_AZURE_SECRETNAME)}`)
console.log(`TATUM_KMS_AZURE_VAULTURL : ${utils.hidePassword(process.env.TATUM_KMS_AZURE_VAULTURL)}`)
console.log(`TATUM_KMS_AWS_REGION : ${process.env.TATUM_KMS_AWS_REGION ?? 'N/A'}`)
console.log(`TATUM_KMS_AWS_ACCESS_KEY_ID : ${secretValue(process.env.TATUM_KMS_AWS_ACCESS_KEY_ID)}`)
console.log(`TATUM_KMS_AWS_SECRET_ACCESS_KEY : ${secretValue(process.env.TATUM_KMS_AWS_SECRET_ACCESS_KEY)}`)
console.log(`TATUM_KMS_AWS_SECRET_NAME : ${secretValue(process.env.TATUM_KMS_AWS_SECRET_NAME)}`)
console.log(`TATUM_KMS_AWS_SECRET_KEY : ${secretValue(process.env.TATUM_KMS_AWS_SECRET_KEY)}`)
console.log(`TATUM_KMS_DEBUG_MODE : ${process.env.TATUM_KMS_DEBUG_MODE ?? 'N/A'}`)
console.log(`TATUM_KMS_AWS_ACCESS_KEY_ID : ${utils.hidePassword(process.env.TATUM_KMS_AWS_ACCESS_KEY_ID)}`)
console.log(`TATUM_KMS_AWS_SECRET_ACCESS_KEY : ${utils.hidePassword(process.env.TATUM_KMS_AWS_SECRET_ACCESS_KEY)}`)
console.log(`TATUM_KMS_AWS_SECRET_NAME : ${utils.hidePassword(process.env.TATUM_KMS_AWS_SECRET_NAME)}`)
console.log(`TATUM_KMS_AWS_SECRET_KEY : ${utils.hidePassword(process.env.TATUM_KMS_AWS_SECRET_KEY)}`)
console.log(`TATUM_KMS_DEBUG_MODE : ${process.env.TATUM_KMS_DEBUG_MODE ?? 'N/A'}`)
}

export const report = async (signatureIds: string[], passwordType: PasswordType, pwd: string, path?: string) => {
const systemWarnings: string[] = []
const walletReports: Record<string, ReportWallet> = {}
for (const signatureId of signatureIds) {
const wallet: Wallet = await getWallet(signatureId, pwd, path, false)
if (!wallet) {
systemWarnings.push(`No wallet found for signatureId: ${signatureId}`)
continue
}
if (!_.isObject(wallet)) {
systemWarnings.push(`Wallet for signatureId: ${signatureId} is not an object. Its type is: ${typeof wallet}`)
continue
}

const warnings: string[] = []
const type: WalletType = validateWallet(wallet, warnings)

walletReports[signatureId] = {
type: type,
chain: wallet.chain,
testnet: wallet.testnet,
warnings: warnings && warnings.length > 0 ? warnings : undefined,
}
}
const nodeVersion = validateNodeVersion(systemWarnings)

const report: Report = {
system: {
kmsVersion: getKmsVersion(),
nodeVersion: nodeVersion,
store: {
type: parseWalletStoreName(passwordType),
exists: existsSync(getPathToWallet(path)),
},
},
wallets: walletReports,
apiKey: utils.hideApiKey(process.env.TATUM_API_KEY),
warnings: systemWarnings && systemWarnings.length > 0 ? systemWarnings : undefined,
}

console.log(JSON.stringify(report, null, 2))
}

const validateWallet = (wallet: Wallet, warnings: string[]) => {
if (wallet.mnemonic) {
validateStringField(warnings, 'chain', wallet.chain)
validateStringField(warnings, 'mnemonic', wallet.mnemonic)
validateStringField(warnings, 'xpub', wallet.xpub)
validateBooleanField(warnings, 'testnet', wallet.testnet)
return WalletType.MNEMONIC
} else if (wallet.privateKey) {
validateStringField(warnings, 'chain', wallet.chain)
validateStringField(warnings, 'privateKey', wallet.privateKey)
validateBooleanField(warnings, 'testnet', wallet.testnet)
return WalletType.PRIVATE_KEY
} else if (wallet.secret) {
validateStringField(warnings, 'chain', wallet.chain)
validateStringField(warnings, 'secret', wallet.secret)
validateBooleanField(warnings, 'testnet', wallet.testnet)
return WalletType.SECRET
} else {
warnings.push('Wallet type is not recognized. Mnemonic, privateKey or secret are absent')
return WalletType.OTHER
}
}

const validateNodeVersion = (systemWarnings: string[]) => {
const nodeVersion = process.version
if (semver.lt(nodeVersion, '18.0.0')) {
systemWarnings.push(`Node version is lower than v18.x.x. Current version is: ${nodeVersion}`)
}
return nodeVersion
}

const validateStringField = (warnings: string[], fieldName: string, value: unknown) => {
if (!_.isString(value)) {
warnings.push(`Field '${fieldName}' is not string. Its type is: ${typeof value}`)
}
}

const validateBooleanField = (warnings: string[], fieldName: string, value: unknown) => {
if (!_.isBoolean(value)) {
warnings.push(`Field '${fieldName}' is not boolean. Its type is: ${typeof value}`)
}
}

export const setTatumKey = (apiKey: string) => {
Expand All @@ -462,3 +543,11 @@
hideEchoBack: true,
})
}

const getKmsVersion = (): string => {
return process.env.npm_package_version ?? 'N/A'
}

const getPathToWallet = (path?: string) => {
return path || homedir() + '/.tatumrc/wallet.dat'
}
Loading
Loading