Repository navigation
ft: adding openbao migration #236
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -17,17 +17,17 @@ import ReturnCode from "../constants/ReturnCode.js"; | |
| import { encode, decode, verifySignature } from "../utils/crypto.js"; | ||
| import SupportedNetworks from "../../config.json"; | ||
| import { APIKey } from "../models/api-key.js"; | ||
| import { ArkaConfigUpdateData } from "../types/arka-config-dto.js"; | ||
| import { ArkaConfigUpdateData, SecretManagerRoutesOpts } from "../types/arka-config-dto.js"; | ||
| import { ApiKeyDto } from "../types/apikey-dto.js"; | ||
| import { CreateSecretCommand, DeleteSecretCommand, GetSecretValueCommand, SecretsManagerClient } from "@aws-sdk/client-secrets-manager"; | ||
| import EtherspotAbi from "../abi/EtherspotAbi.js"; | ||
| import { AuthDto } from "../types/auth-dto.js"; | ||
| import { IncomingHttpHeaders } from "http"; | ||
| import { EPVersions } from "../types/sponsorship-policy-dto.js"; | ||
| import { getNetworkConfig, getViemChainDef } from "../utils/common.js"; | ||
| import { Paymaster } from "../paymaster/index.js"; | ||
|
|
||
| const adminRoutes: FastifyPluginAsync = async (server) => { | ||
| const adminRoutes: FastifyPluginAsync<SecretManagerRoutesOpts> = async (server, options) => { | ||
| const { secretManager } = options; | ||
| const paymaster = new Paymaster({ | ||
| feeMarkUp: server.config.FEE_MARKUP, | ||
| multiTokenMarkUp: server.config.MULTI_TOKEN_MARKUP, | ||
|
|
@@ -44,13 +44,9 @@ const adminRoutes: FastifyPluginAsync = async (server) => { | |
|
|
||
| const prefixSecretId = 'arka_'; | ||
|
|
||
| let client: SecretsManagerClient; | ||
|
|
||
| const unsafeMode: boolean = process.env.UNSAFE_MODE == "true" ? true : false; | ||
|
|
||
| if (!unsafeMode) { | ||
| client = new SecretsManagerClient(); | ||
| } | ||
| const getApiKeySecret = (apiKey: string) => secretManager.getSecret<Record<string, string>>(prefixSecretId + apiKey); | ||
|
|
||
| const SUPPORTED_ENTRYPOINTS = { | ||
| EPV_06: server.config.EPV_06, | ||
|
|
@@ -147,26 +143,20 @@ const adminRoutes: FastifyPluginAsync = async (server) => { | |
| if(!verifySignature(signature, request.body as string, timestamp, server.config.HMAC_SECRET)) | ||
| return reply.code(ReturnCode.NOT_AUTHORIZED).send({ error: ErrorMessage.INVALID_SIGNATURE_OR_TIMESTAMP }); | ||
|
|
||
| const command = new GetSecretValueCommand({SecretId: prefixSecretId + body.apiKey}) | ||
| const secrets = await client.send(command).catch((err) => err); | ||
| const secrets = await getApiKeySecret(body.apiKey).catch((err) => err); | ||
|
|
||
| if(!(secrets instanceof Error)) { | ||
| request.log.error('Duplicate record found'); | ||
| return reply.code(ReturnCode.FAILURE).send({ error: ErrorMessage.DUPLICATE_RECORD }); | ||
| } | ||
|
|
||
| const createCommand = new CreateSecretCommand({ | ||
| Name: prefixSecretId + body.apiKey, | ||
| SecretString: JSON.stringify({ | ||
| PRIVATE_KEY: privateKey, | ||
| PUBLIC_ADDRESS: publicAddress, | ||
| MNEMONIC: mnemonic, | ||
| BUNDLER_API_KEY: server.config.DEFAULT_BUNDLER_API_KEY | ||
| }), | ||
| await secretManager.createSecret(prefixSecretId + body.apiKey, { | ||
| PRIVATE_KEY: privateKey, | ||
| PUBLIC_ADDRESS: publicAddress, | ||
| MNEMONIC: mnemonic, | ||
| BUNDLER_API_KEY: server.config.DEFAULT_BUNDLER_API_KEY | ||
| }); | ||
|
|
||
|
Comment on lines
+146
to
159
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -eu
echo '--- candidate files ---'
fd -t f 'admin-routes\.ts|secret|openbao|vault' . | head -200
echo '--- relevant symbols and configuration ---'
rg -n -C 4 'getApiKeySecret|createSecret|destroySecret|saveKey|DELETE_KEY_RECOVER_WINDOW|DUPLICATE_RECORD|secretManager' backend/src .github 2>/dev/null | head -500Repository: etherspot/arka Length of output: 41007 🏁 Script executed: #!/bin/bash
set -eu
echo '--- admin saveKey path ---'
sed -n '115,205p' backend/src/routes/admin-routes.ts
echo '--- OpenBao implementation ---'
sed -n '1,135p' backend/src/services/secrets-manager/openbao.ts
echo '--- SecretManager interface and factory ---'
cat backend/src/services/secrets-manager/interface.ts
rg -n -C 5 'OpenBao|Openbao|new Secret|SecretManager' backend/src/services backend/src | head -250
echo '--- package and dependency references ---'
rg -n -C 3 'openbao|vault|kv-v2|CAS|cas' backend package.json '**/package.json' 2>/dev/null | head -250Repository: etherspot/arka Length of output: 43400 🏁 Script executed: #!/bin/bash
set -eu
echo '--- API-key model and repository create semantics ---'
fd -t f 'api-key|api.*repository|repository' backend/src backend/migrations | head -100
rg -n -C 6 'class APIKey|apiKey:|unique|create\(' backend/src/models backend/src/repository backend/migrations | head -350
echo '--- deterministic source verifier ---'
python3 - <<'PY'
from pathlib import Path
route = Path("backend/src/routes/admin-routes.ts").read_text()
openbao = Path("backend/src/services/secrets-manager/openbao.ts").read_text()
start = route.index("const secrets = await getApiKeySecret(body.apiKey).catch")
end = route.index("await server.apiKeyRepository.create", start)
save_path = route[start:end]
assert "getApiKeySecret(body.apiKey).catch((err) => err)" in save_path
assert "if(!(secrets instanceof Error))" in save_path
assert "await secretManager.createSecret" in save_path
assert save_path.index("getApiKeySecret") < save_path.index("createSecret")
start = openbao.index("async createSecret(")
end = openbao.index("async destroySecret(", start)
create_path = openbao[start:end]
assert 'method: "POST"' in create_path
assert '"cas"' not in create_path
assert "if (!response.ok)" in create_path
print("saveKey performs GET/error-catch, then separate createSecret call")
print("OpenBao createSecret sends POST without a CAS field and throws only on non-2xx")
PYRepository: etherspot/arka Length of output: 19755 🌐 Web query:
💡 Result: In the OpenBao KV v2 secrets engine, the check-and-set (CAS) parameter is used to perform optimistic locking on write operations [1][2]. When using the POST create/update secret endpoint, setting the Citations:
Make API-key secret creation atomic.
🤖 Prompt for AI Agents |
||
| await client.send(createCommand); | ||
|
|
||
| await server.apiKeyRepository.create({ | ||
| apiKey: body.apiKey, | ||
| walletAddress: publicAddress, | ||
|
|
@@ -281,12 +271,7 @@ const adminRoutes: FastifyPluginAsync = async (server) => { | |
| return reply.code(ReturnCode.NOT_AUTHORIZED).send({ error: ErrorMessage.INVALID_SIGNATURE_OR_TIMESTAMP }); | ||
| if(!verifySignature(signature, request.body as string, timestamp, server.config.HMAC_SECRET)) | ||
| return reply.code(ReturnCode.NOT_AUTHORIZED).send({ error: ErrorMessage.INVALID_SIGNATURE_OR_TIMESTAMP }); | ||
| const getSecretCommand = new GetSecretValueCommand({SecretId: prefixSecretId + body.apiKey}); | ||
| const secretValue = await client.send(getSecretCommand) | ||
| if(secretValue instanceof Error) | ||
| return reply.code(ReturnCode.FAILURE).send({ error: ErrorMessage.RECORD_NOT_FOUND }); | ||
|
|
||
| const secrets = JSON.parse(secretValue.SecretString ?? '{}'); | ||
| const secrets = await getApiKeySecret(body.apiKey); | ||
|
|
||
| if (!secrets['PRIVATE_KEY']) { | ||
| server.log.info("Invalid Api Key provided") | ||
|
|
@@ -353,12 +338,7 @@ const adminRoutes: FastifyPluginAsync = async (server) => { | |
| return reply.code(400).send({error: ErrorMessage.BALANCE_EXCEEDS_THRESHOLD }); | ||
| } | ||
|
|
||
| const deleteCommand = new DeleteSecretCommand({ | ||
| SecretId: prefixSecretId + body.apiKey, | ||
| RecoveryWindowInDays: server.config.DELETE_KEY_RECOVER_WINDOW, | ||
| }); | ||
|
|
||
| await client.send(deleteCommand); | ||
| await secretManager.destroySecret(prefixSecretId + body.apiKey, server.config.DELETE_KEY_RECOVER_WINDOW); | ||
|
|
||
| await server.apiKeyRepository.delete(body.apiKey); | ||
| } else { | ||
|
|
@@ -442,12 +422,7 @@ const adminRoutes: FastifyPluginAsync = async (server) => { | |
| let supportedNetworks; | ||
|
|
||
| if (!unsafeMode) { | ||
| const AWSresponse = await client.send( | ||
| new GetSecretValueCommand({ | ||
| SecretId: prefixSecretId + apiKey, | ||
| }) | ||
| ); | ||
| const secrets = JSON.parse(AWSresponse.SecretString ?? '{}'); | ||
| const secrets = await getApiKeySecret(apiKey); | ||
| if (!secrets['PRIVATE_KEY']) { | ||
| return reply.code(ReturnCode.FAILURE).send({ error: ErrorMessage.INVALID_API_KEY }); | ||
| } | ||
|
|
@@ -556,12 +531,7 @@ const adminRoutes: FastifyPluginAsync = async (server) => { | |
| let supportedNetworks; | ||
|
|
||
| if (!unsafeMode) { | ||
| const AWSresponse = await client.send( | ||
| new GetSecretValueCommand({ | ||
| SecretId: prefixSecretId + apiKey, | ||
| }) | ||
| ); | ||
| const secrets = JSON.parse(AWSresponse.SecretString ?? '{}'); | ||
| const secrets = await getApiKeySecret(apiKey); | ||
| if (!secrets['PRIVATE_KEY']) { | ||
| return reply.code(ReturnCode.FAILURE).send({ error: ErrorMessage.INVALID_API_KEY }); | ||
| } | ||
|
|
@@ -659,12 +629,7 @@ const adminRoutes: FastifyPluginAsync = async (server) => { | |
| let supportedNetworks; | ||
|
|
||
| if (!unsafeMode) { | ||
| const AWSresponse = await client.send( | ||
| new GetSecretValueCommand({ | ||
| SecretId: prefixSecretId + apiKey, | ||
| }) | ||
| ); | ||
| const secrets = JSON.parse(AWSresponse.SecretString ?? '{}'); | ||
| const secrets = await getApiKeySecret(apiKey); | ||
| if (!secrets['PRIVATE_KEY']) { | ||
| return reply.code(ReturnCode.FAILURE).send({ error: ErrorMessage.INVALID_API_KEY }); | ||
| } | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.