Browse Source

Hardcode LND wallet password (#85)

remove-builder-image
Luke Childs 4 years ago
committed by GitHub
parent
commit
533a86e238
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 13
      Dockerfile
  2. 6
      Dockerfile.builder
  3. 1
      README.md
  4. 110
      logic/auth.js
  5. 1
      package.json
  6. 2
      routes/v1/account.js
  7. 22
      services/lndApi.js
  8. 2
      utils/const.js
  9. 5
      yarn.lock

13
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

6
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

1
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` |

110
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);

1
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",

2
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;

22
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,
};

2
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',

5
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"

Loading…
Cancel
Save