From 533a86e23852edab866a45338ea1c84a4a4425b2 Mon Sep 17 00:00:00 2001 From: Luke Childs Date: Sat, 10 Apr 2021 22:25:53 +0700 Subject: [PATCH] Hardcode LND wallet password (#85) --- Dockerfile | 13 +---- Dockerfile.builder | 6 +-- README.md | 1 - logic/auth.js | 110 +++++++++++++++++-------------------------- package.json | 1 - routes/v1/account.js | 2 +- services/lndApi.js | 22 ++------- utils/const.js | 2 +- yarn.lock | 5 -- 9 files changed, 51 insertions(+), 111 deletions(-) diff --git a/Dockerfile b/Dockerfile index 8263797..56e6690 100644 --- a/Dockerfile +++ b/Dockerfile @@ -16,21 +16,10 @@ COPY . . # Final image FROM node:12-buster-slim AS umbrel-manager -# Install python3 and python3-pip (required for docker-compose) -RUN apt-get update --no-install-recommends \ - && apt-get install -y --no-install-recommends python3 \ - && apt-get install -y --no-install-recommends python3-pip - # Copy built code from build stage to '/app' directory COPY --from=umbrel-manager-builder /app /app -# Copy pip3 modules from build stage (we only need docker-compose module though) -COPY --from=umbrel-manager-builder /usr/local/lib/python3.7/dist-packages /usr/local/lib/python3.7/dist-packages - -# Copy docker-compose binary from build stage -COPY --from=umbrel-manager-builder /usr/local/bin/docker-compose /usr/local/bin/docker-compose - -# Change directory to '/app' +# Change directory to '/app' WORKDIR /app EXPOSE 3006 diff --git a/Dockerfile.builder b/Dockerfile.builder index 196c1fb..6bb9bc5 100644 --- a/Dockerfile.builder +++ b/Dockerfile.builder @@ -5,8 +5,4 @@ FROM node:12-buster-slim RUN apt-get update \ && apt-get install -y build-essential \ && apt-get install -y libffi-dev \ - && apt-get install -y libssl-dev \ - && apt-get install -y python3 \ - && apt-get install -y python3-pip \ - && pip3 install -IU docker-compose \ - && chmod +x /usr/local/bin/docker-compose + && apt-get install -y libssl-dev diff --git a/README.md b/README.md index e3e4053..ed389d9 100644 --- a/README.md +++ b/README.md @@ -44,7 +44,6 @@ Set the following environment variables directly or by placing them in `.env` fi | `JWT_PUBLIC_KEY_FILE` | Path to the JWT public key (automatically created) | `/db/jwt-public-key/jwt.pem` | | `JWT_PRIVATE_KEY_FILE` | Path to the JWT private key (automatically created) | `/db/jwt-public-key/jwt.key` | | `JWT_EXPIRATION` | JWT expiration in miliseconds | `3600` | -| `DOCKER_COMPOSE_DIRECTORY` | Path to directory containing `docker-compose.yml` | `/docker-compose` | | `UMBREL_SEED_FILE` | Path to the seed used to deterministically generate entropy | `'/db/umbrel-seed/seed'` | | `UMBREL_DASHBOARD_HIDDEN_SERVICE_FILE` | Path to Tor hostname of [`umbrel-dashboard`](https://github.com/getumbrel/umbrel-dashboard) | `/var/lib/tor/dashboard/hostname` | | `ELECTRUM_HIDDEN_SERVICE_FILE` | Path to Electrum hidden service hostname | `/var/lib/tor/electrum/hostname` | diff --git a/logic/auth.js b/logic/auth.js index 53a4018..71dc29b 100644 --- a/logic/auth.js +++ b/logic/auth.js @@ -3,7 +3,6 @@ const bcrypt = require('bcrypt'); const crypto = require('crypto'); const { CipherSeed } = require('aezeed'); const iocane = require("iocane"); -const compose = require("docker-compose"); const diskLogic = require('logic/disk.js'); const lndApiService = require('services/lndApi.js'); const bashService = require('services/bash.js'); @@ -46,81 +45,37 @@ const setSystemPassword = async password => { await diskLogic.writeSignalFile('change-password'); } -// Change the device and lnd password. +// Change the dashboard and system password. async function changePassword(currentPassword, newPassword, jwt) { - - resetChangePasswordStatus(); changePasswordStatus.percent = 1; // eslint-disable-line no-magic-numbers - // restart lnd try { - await compose.restartOne('lnd', { cwd: constants.DOCKER_COMPOSE_DIRECTORY }); - } catch (error) { - throw new Error('Unable to change password as lnd wouldn\'t restart'); - } - - changePasswordStatus.percent = 40; // eslint-disable-line no-magic-numbers - - let complete = false; - let attempt = 0; - const MAX_ATTEMPTS = 20; - - do { - try { - attempt++; - - // call lnapi to change password - changePasswordStatus.percent = 60 + attempt; // eslint-disable-line no-magic-numbers - await lndApiService.changePassword(currentPassword, newPassword, jwt); - - // update user file - const user = await diskLogic.readUserFile(); - const credentials = hashCredentials(SYSTEM_USER, newPassword); - - // re-encrypt seed with new password - const decryptedSeed = await iocane.createSession().decrypt(user.seed, currentPassword); - const encryptedSeed = await iocane.createSession().encrypt(decryptedSeed, newPassword); - - // update user file - await diskLogic.writeUserFile({ ...user, password: credentials.password, seed: encryptedSeed }); - - // update system password - await setSystemPassword(newPassword); - - complete = true; + // update user file + const user = await diskLogic.readUserFile(); + const credentials = hashCredentials(SYSTEM_USER, newPassword); - // cache the password for later use - cachePassword(newPassword); + // re-encrypt seed with new password + const decryptedSeed = await iocane.createSession().decrypt(user.seed, currentPassword); + const encryptedSeed = await iocane.createSession().encrypt(decryptedSeed, newPassword); - changePasswordStatus.percent = 100; - } catch (error) { + // update user file + await diskLogic.writeUserFile({ ...user, password: credentials.password, seed: encryptedSeed }); - // wait for lnd to boot up - if (error.response.status === constants.STATUS_CODES.BAD_GATEWAY) { - await sleepSeconds(1); + // update system password + await setSystemPassword(newPassword); - // user supplied incorrect credentials - } else if (error.response.status === constants.STATUS_CODES.FORBIDDEN) { - changePasswordStatus.unauthorized = true; + changePasswordStatus.percent = 100; + complete = true; - // unknown error occurred - } else { - changePasswordStatus.error = true; - changePasswordStatus.percent = 100; - - throw error; - } - } - } while (!complete && attempt < MAX_ATTEMPTS && !changePasswordStatus.unauthorized && !changePasswordStatus.error); - - if (!complete && attempt === MAX_ATTEMPTS) { - changePasswordStatus.error = true; - changePasswordStatus.percent = 100; + // cache the password for later use + cachePassword(newPassword); + } catch (error) { + changePasswordStatus.percent = 100; + changePasswordStatus.error = true; - throw new Error('Unable to change password'); + throw new Error('Unable to change password'); } - } function getChangePasswordStatus() { @@ -160,6 +115,23 @@ async function deriveUmbrelSeed(user) { return diskLogic.writeUmbrelSeedFile(umbrelSeed); } +// Sets the LND password to a hardcoded password if it's locked so we can +// auto unlock it in future +async function removeLndPasswordIfLocked(currentPassword, jwt) { + const lndStatus = await lndApiService.getStatus(); + + if (!lndStatus.data.unlocked) { + console.log('LND is locked on login, attempting to change password...'); + try { + await lndApiService.changePassword(currentPassword, constants.LND_WALLET_PASSWORD, jwt); + console.log('Sucessfully changed LND password!'); + } catch (e) { + console.log('Failed to change LND password!'); + } + } +} + + // Log the user into the device. Caches the password if login is successful. Then returns jwt. async function login(user) { try { @@ -169,15 +141,17 @@ async function login(user) { // cachePassword(user.plainTextPassword); cachePassword(user.password); - //unlock lnd wallet - // await lndApiService.unlock(user.plainTextPassword, jwt); - deriveUmbrelSeed(user) // This is only needed temporarily to update hardcoded passwords // on existing users without requiring them to change their password setSystemPassword(user.password); + // This is only needed temporarily to remove the user set LND wallet + // password for old users and change it to a hardcoded one so we can + // auto unlock it in the future. + removeLndPasswordIfLocked(user.password, jwt); + return { jwt: jwt }; } catch (error) { @@ -260,7 +234,7 @@ async function register(user, seed) { //initialize lnd wallet try { - await lndApiService.initializeWallet(user.plainTextPassword, seed, jwt); + await lndApiService.initializeWallet(constants.LND_WALLET_PASSWORD, seed, jwt); } catch (error) { await diskLogic.deleteUserFile(); throw new NodeError(error.response.data); diff --git a/package.json b/package.json index 779a22f..0a1e0d7 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,6 @@ "continuation-local-storage": "^3.2.1", "cors": "^2.8.5", "debug": "^4.1.1", - "docker-compose": "^0.23.4", "dotenv": "^8.2.0", "express": "^4.16.3", "fs-extra": "^9.0.0", diff --git a/routes/v1/account.js b/routes/v1/account.js index 00f2795..d975b41 100644 --- a/routes/v1/account.js +++ b/routes/v1/account.js @@ -13,7 +13,7 @@ const validator = require('utils/validator.js'); const COMPLETE = 100; -// Endpoint to change your lnd password. Wallet must exist and be unlocked. +// Endpoint to change your password. router.post('/change-password', auth.convertReqBodyToBasicAuth, auth.basic, incorrectPasswordAuthHandler, safeHandler(async (req, res, next) => { // Use password from the body by default. Basic auth has issues handling special characters. const currentPassword = req.body.password; diff --git a/services/lndApi.js b/services/lndApi.js index a6e1a54..e59b996 100644 --- a/services/lndApi.js +++ b/services/lndApi.js @@ -37,22 +37,6 @@ async function initializeWallet(password, seed, jwt) { .post(lnapiUrl + ':' + lnapiPort + '/v1/lnd/wallet/init', body, headers); } -async function unlockLnd(password, jwt) { - - const headers = { - headers: { - Authorization: 'JWT ' + jwt, - } - }; - - const body = { - password, - }; - - return axios - .post(lnapiUrl + ':' + lnapiPort + '/v1/lnd/wallet/unlock', body, headers); -} - async function getBitcoindAddresses(jwt) { const headers = { @@ -65,9 +49,13 @@ async function getBitcoindAddresses(jwt) { .get(lnapiUrl + ':' + lnapiPort + '/v1/bitcoind/info/addresses', headers); } +async function getStatus() { + return axios.get(lnapiUrl + ':' + lnapiPort + '/v1/lnd/info/status'); +} + module.exports = { changePassword, initializeWallet, - unlockLnd, getBitcoindAddresses, + getStatus, }; diff --git a/utils/const.js b/utils/const.js index 0fdfe21..4db3971 100644 --- a/utils/const.js +++ b/utils/const.js @@ -12,7 +12,6 @@ module.exports = { REBOOT_SIGNAL_FILE: process.env.REBOOT_SIGNAL_FILE || '/signals/reboot', JWT_PUBLIC_KEY_FILE: process.env.JWT_PUBLIC_KEY_FILE || '/db/jwt-public-key/jwt.pem', JWT_PRIVATE_KEY_FILE: process.env.JWT_PRIVATE_KEY_FILE || '/db/jwt-private-key/jwt.key', - DOCKER_COMPOSE_DIRECTORY: process.env.DOCKER_COMPOSE_DIRECTORY || '/docker-compose', UMBREL_SEED_FILE: process.env.UMBREL_SEED_FILE || '/db/umbrel-seed/seed', UMBREL_DASHBOARD_HIDDEN_SERVICE_FILE: process.env.UMBREL_DASHBOARD_HIDDEN_SERVICE_FILE || '/var/lib/tor/web/hostname', ELECTRUM_HIDDEN_SERVICE_FILE: process.env.ELECTRUM_HIDDEN_SERVICE_FILE || '/var/lib/tor/electrum/hostname', @@ -27,6 +26,7 @@ module.exports = { LND_GRPC_HIDDEN_SERVICE_FILE: process.env.LND_GRPC_HIDDEN_SERVICE_FILE || '/var/lib/tor/lnd-grpc/hostname', LND_CERT_FILE: process.env.LND_CERT_FILE || '/lnd/tls.cert', LND_ADMIN_MACAROON_FILE: process.env.LND_ADMIN_MACAROON_FILE || '/lnd/data/chain/bitcoin/mainnet/admin.macaroon', + LND_WALLET_PASSWORD: process.env.LND_WALLET_PASSWORD || 'moneyprintergobrrr', GITHUB_REPO: process.env.GITHUB_REPO || 'getumbrel/umbrel', UMBREL_VERSION_FILE: process.env.UMBREL_VERSION_FILE || '/info.json', UPDATE_STATUS_FILE: process.env.UPDATE_STATUS_FILE || '/statuses/update-status.json', diff --git a/yarn.lock b/yarn.lock index bc3d9e4..bd5514c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1514,11 +1514,6 @@ diff@^4.0.2: resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d" integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A== -docker-compose@^0.23.4: - version "0.23.4" - resolved "https://registry.yarnpkg.com/docker-compose/-/docker-compose-0.23.4.tgz#43bcabcde55a6ba2873b52fe0ccd99dd8fdceba8" - integrity sha512-yWdXby9uQ8o4syOfvoSJ9ZlTnLipvUmDn59uaYY5VGIUSUAfMPPGqE1DE3pOCnfSg9Tl9UOOFO0PCSAzuIHmuA== - doctrine@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-3.0.0.tgz#addebead72a6574db783639dc87a121773973961"