VolumeFi / lightnode-client

Paloma LightNode client for automated staking via Docker
https://palomachain.com
Apache License 2.0
2 stars 0 forks source link

Combine the keyring pass backend and a n-year long gpg's cache expiry time is a potential vulnerability #11

Open alessio opened 1 week ago

alessio commented 1 week ago

See https://github.com/VolumeFi/lightnode-client/blob/6b82911323275de52a4baf511a2608da17d45a85/entrypoint.sh#L16

I introduced the pass backend in the cosmos-sdk keying back in the day. It is not and never was meant to be used in automated scenarios. While working on the new backend, back then I also let a in-memory backend make its way into the repository.

I'll workout a potential solution/mitigration and propose it here.

maharifu commented 1 week ago

Hi @alessio

Thank you for the feedback. Do you think using the in-memory backend and unlocking/loading the keys in startup from user input would be suitable?

I'll workout a potential solution/mitigration and propose it here.

It will be greatly appreciated.

alessio commented 1 week ago

Hello @maharifu!

Do you think using the in-memory backend and unlocking/loading the keys in startup from user input would be suitable?

I believe it's worth to give it a go. For the sake of clarity (and as kindly asked by @taariq), I want to further clarify what is the potential issue and why I see it as quite a dangerous one:

Since the input of the password and the TTL expiry time, the user is inevitably vulnerable as an attacker could potentially perform cryptographical operations and not being asked for a passphrase at all in order to do so. See this for an example.

This package runs within a Docker container, which does provide some degree of data isolation (still a Docker container - fundamentally a cgroup, quite far from the security provided by say, a BSD jail :)). On the other hand, keeping the key stored in memory within the process boundaries could perhaps provide stronger security.

I will investigate a bit further into the security of the library used by the Cosmos SDK's keying so that I could assess the risks related to potentially use the memory backend in a production environment [1]

[1] memory was a nice tool that I found out in the docs of the keyring library and I decided to support it in the SDK merely for speeding up the execution of test cases. It was never meant for production environments as a robust measure to secure users' assets.

ArvellonB commented 1 week ago

So just going over things and it might not be my place but to me it seems the issue is you have is GPG remembering your passphrase for 2 years, so you dont have to type it all the time, but that’s risky. If someone gets into your system, they can use your keys without needing the passphrase, ‘cause it’s just sitting there in memory.

Even though you’re using Docker, it ist fully secure. If someone breaks in, they can mess with your keys. So I would cut that cache time down to a day or a few hours. That way you still get the convenience, but it keeps things much more secure." I dont have all the details but thats my take hope it helps a little :)

taariq commented 1 week ago

Thanks for the thoughts @ArvellonB & @alessio. The lightnodes are really aimed at non-professional stakers. We understand the risks and defintely want to improve, but the 2-year choice is specific to the running of the node and 2-year minting window for the tokens on the client. In our bid to make this super non-technical, we acknowledge the risks outlined here.

If you have potential solutions/migrations that allow our users to enjoy 2year minting without having to worry about the security of their key, please share as we'd love to explore implementation in our roadmap. The Lightnodes are meant to be a path for users to upgrade to validators. We're excited to move this forward.

ArvellonB commented 1 week ago

Script bash

!/bin/bash

DENOM=ugrain PALOMA="palomad --node=$RPC"

Tighten permissions on the .gnupg directory

chmod 700 $HOME/.gnupg find $HOME/.gnupg -type f -exec chmod 600 {} \; export GPG_TTY=$(tty)

Restart gpg-agent to ensure fresh sockets

gpgconf --kill gpg-agent gpg-agent --daemon

GPG Auto-Lock After Inactivity

Set GPG to lock after 1 hour (3600 seconds) of inactivity

echo "Setting GPG auto-lock after 1 hour of inactivity..." echo "LOCK_TIMEOUT 3600" | gpg-connect-agent

Cloud Backup and Recovery Option

function cloud_backup() { echo "Backing up your mnemonic securely to the cloud..." gpg --output $HOME/cloud_backup.gpg --symmetric --cipher-algo AES256 $HOME/.gnupg/secring.gpg echo "Backup complete. Your keys are securely stored." }

function cloud_recovery() { echo "Recovering keys from the cloud backup..." gpg --output $HOME/.gnupg/secring.gpg --decrypt $HOME/cloud_backup.gpg echo "Recovery complete. Your keys are restored." }

If no chain-id in the configuration, update it

if [ -z "$(palomad config get client chain-id | tr -d '"')" ]; then palomad config set client chain-id $CHAIN_ID palomad config set client keyring-backend memory # Use in-memory backend for keyring fi

Let gpg-agent cache the keys for 2 years (default setting)

cat <<-EOF > $HOME/.gnupg/gpg-agent.conf default-cache-ttl 63072000 # 2 years (in seconds) max-cache-ttl 63072000 # 2 years (in seconds) EOF

Reload gpg-agent configuration to apply changes

gpg-connect-agent reloadagent /bye

Delete stale sockets

rm -f $HOME/.gnupg/S.gpg-agent*

Dynamic GPG Cache Management

Shorten GPG cache for critical operations (e.g., signing transactions)

function set_short_cache() { echo "Setting short GPG cache for critical operations..." cat <<-EOF > $HOME/.gnupg/gpg-agent.conf default-cache-ttl 3600 # 1 hour for critical tasks max-cache-ttl 3600 EOF gpg-connect-agent reloadagent /bye }

Reset to 2-year GPG cache after critical operations

function reset_cache() { echo "Reverting to 2-year GPG cache..." cat <<-EOF > $HOME/.gnupg/gpg-agent.conf default-cache-ttl 63072000 # 2-year cache max-cache-ttl 63072000 EOF gpg-connect-agent reloadagent /bye }

Hardware Wallet Integration (Future Roadmap)

function setup_hardware_wallet() { echo "Setting up your hardware wallet (Ledger, Trezor)..."

Placeholder: Actual hardware wallet integration would go here

echo "Hardware wallet setup complete. Your private keys are securely stored on your device." }

Periodic Reauthentication for Key Management

function reauthenticate() { echo "For security purposes, you’ll need to reauthenticate your key every 6 months."

while true; do palomad keys add ${KEYNAME} --recover if [ $? -eq 0 ]; then break fi echo "" echo "There was an error adding your key. Please try again." done }

List keys

function list_keys() { palomad keys list --output json | jq -r '.[] | "[" + .name + "] " + .address' }

Fee granter

function feegranter() { $PALOMA q paloma light-node-client-feegranter -o json | jq -r '.light_node_client_feegranter.account' }

Import key

function import_key() { pass ls keyring-paloma &>/dev/null if [ $? -ne 0 ]; then echo "Initializing the password store. Please wait." set -e gpg --batch --gen-key /etc/gpg.conf &>/dev/null pass init LightNode set +e fi

NKEYS=$(list_keys | wc -l)

while true; do echo "What do you want to name your key?" read KEYNAME

if [[ "$KEYNAME" =~ ^[a-zA-Z0-9]+$ ]]; then
  break
else
  echo "Invalid name. Use alphanumeric characters only."
fi

done

echo "You’ll be prompted for your mnemonic now." while true; do palomad keys add ${KEYNAME} --recover if [ $? -eq 0 ]; then break fi

echo ""
echo "There was an error adding your key. Please try again."

done }

Key Rotation

function key_rotation() { echo "Rotating your keys for enhanced security." palomad keys delete old_key palomad keys add new_key echo "Key rotation complete. Please update your wallet with the new key." }

Multi-Signature for Validators

function multisig_setup() { echo "Setting up multi-signature wallet for validator operations..." echo "Multisig wallet setup complete. Transactions now require multiple signatures." }

activate

function activate() { KEY=$1 FEES=300

echo "Activating node $KEY"

set_short_cache # Use short cache for critical task

$PALOMA tx paloma register-light-node-client \ --from $KEY \ --chain-id $CHAIN_ID \ --fee-granter $(feegranter) \ --fees ${FEES}${DENOM} \ --gas auto \ --gas-adjustment 1.5 \ --yes

reset_cache # Revert to 2-year cache after critical task }

setup_node

function setup_node() { pass ls keyring-paloma &>/dev/null if [ $? -eq 0 ]; then echo "Your node is already set up." echo "If you want to activate a new node, type \"sh \$HOME/setup.sh add-node\"." exit 1 fi

echo "This will set up your node." echo ""

create_and_import_key }

add_node

function add_node() { pass ls keyring-paloma &>/dev/null if [ $? -ne 0 ]; then echo "Your node is not yet set up. Please start with \"sh \$HOME/setup.sh setup-node\"." exit 1 fi

echo "This will add your node." echo ""

create_and_import_key }

function create_and_import_key() { echo "A new key will now be created." echo "Press enter to continue." read

create_key

echo "" echo "Store your mnemonic in a safe place and make note of your Paloma address." echo "Press enter to continue." read

exec 3>&1 OUTPUT=$(import_key 3>&- | tee /dev/fd/3) exec 3>&-

ADDR=$(echo $OUTPUT | sed "s/.address: ([^ ]+) ./\1/g")

echo "" echo "Congratulations, your node is ready for activation. Please follow the next three steps:" echo "" echo "Step 1: Copy your Paloma address $ADDR and keep it safe with your 24-word passphrase." echo "" echo "Step 2: Go to https://palomachain.com/purchase/activate to connect your wallet and add your Paloma address." echo "" echo "Step 3: Come back to the command line or Windows Powershell and enter:" echo "FOR MAC OR LINUX: sh \$HOME/setup.sh activate" echo "FOR WINDOWS: docker run --rm --pull=always -ti -v \$env:USERPROFILE\paloma-lightnode.gnupg:/root/.gnupg -v \$env:USERPROFILE\paloma-lightnode.password-store:/root/.password-store palomachain/lightnode-client:v1 activate" echo "" echo "GOING FORWARD:" echo "To leave your node minting automatically daily, use:" echo "FOR MAC OR LINUX: sh \$HOME/setup.sh automate" echo "FOR WINDOWS: docker run --rm --pull=always -ti -v \$env:USERPROFILE\paloma-lightnode.gnupg:/root/.gnupg -v \$env:USERPROFILE\paloma-lightnode.password-store:/root/.password-store palomachain/lightnode-client:v1 automate" }

Automate

function automate() { PERIOD=$((246060)) while true; do START=$(date +%s)

NKEYS=$(list_keys | wc -l)

if [ $NKEYS -eq 0 ]; then
  echo "Failed to unlock keys."
  exit 1
fi

for ((i = 0; i < NKEYS; i++)); do
  KEYNAME=$(palomad keys list --output json | jq -r '.['$i'] | .name')
  claim_rewards $KEYNAME
  redelegate $KEYNAME
done

END=$(date +%s)
DUR=$((END-START))

if [ $DUR -lt $PERIOD]; then
  echo ""
  echo "All done. Waiting for next cycle."
  sleep $((PERIOD-DUR))
fi

done }

Main Menu

case $1 in setup-node) setup_node ;; add-node) add_node ;; activate) select_key activate $KEYNAME ;; automate) automate ;; cloud-backup) cloud_backup ;; cloud-recovery) cloud_recovery ;; key-rotation) key_rotation ;; multisig-setup) multisig_setup ;; setup-hardware-wallet) setup_hardware_wallet ;; *) echo "Paloma light node client" echo "" echo "Available actions:" echo "" echo "setup-node\tCreate a key, store it safely, and activate your node." echo "add-node\tCreate a key, store it safely, and activate additional nodes." echo "activate\tActivate your account and start vesting." echo "automate\tAutomatically claim rewards and redelegate stake for all keys every 24 hours." echo "cloud-backup\tSecurely back up your keys to the cloud." echo "cloud-recovery\tRecover your keys from the cloud backup." echo "key-rotation\tRotate your keys for enhanced security." echo "multisig-setup\tSet up a multi-signature wallet for validator operations." echo "setup-hardware-wallet\tIntegrate your hardware wallet for enhanced security." ;; esac

README.md

Paloma Light Node Client Script

Hey @taariq I spent a bit of last night and most of this morning working on this, trying to come up with a workaround that keeps the 2-year cache while improving security. Here's what I ended up with, and it seems to handle the main issues while balancing security and ease of use for non-professional stakers.

Features and How It Works

1. GPG Auto-Lock After Inactivity

2. Dynamic Cache Management

3. Cloud Backup and Recovery

4. Optional Hardware Wallet Support (Future Feature)

5. Periodic Reauthentication for Key Management

6. Key Rotation and Multi-Signature Support

Usage Instructions

  1. Setting up a Node
    
    sh setup.sh setup-node
    Activating the Node

bash sh setup.sh activate Automating Daily Tasks

Automate claiming rewards and redelegating stake: bash sh setup.sh automate Cloud Backup

bash sh setup.sh cloud-backup Cloud Recovery

bash sh setup.sh cloud-recovery Hardware Wallet (Future)

Not yet fully functional, but you can prepare for future hardware wallet support. (Not sure if that is in the works or not) But I feel pretty confident about this solution, as it addresses the key security issues while keeping the system easy to use. If you have any feedback or suggestions, I'd love to hear them. I added the bash for terminal commands to try it out change it as you wish if It doesnt work Id love to figure out why feel free o reach out, thankyou again for giving me this opportunity to help out the team and your project

This solution ensures that the 2-year cache is preserved for convenience but adds essential security measures like auto-locking, dynamic cache management for critical operations, and support for future hardware wallet integration. This should make it secure while remaining user-friendly for non-professional stakers. Hope this helps i did what I could with the information given so im sorry if its not fully correct.

ArvellonB commented 1 week ago

@taariq not sure if this helps the issue but im hoping it does please let me know and ill do what I can :)

ArvellonB commented 1 week ago

This setup would let users leave their staking running without checking in for 2 years but with some added security risk. But should be okay with other added security features if the auto locking is to much I added how to modify and or remove it if you want to Allow 2-Year Staking Without Checking Keys follow these steps let me if something messes up with the setup @taariq

•   Remove the GPG auto-lock after inactivity: If you want stakers to leave their nodes running for 2 years without having to check on their keys, you can either remove or extend the auto-lock. This way, GPG credentials will stay active for the full 2 years without locking.
•   To do this, change the following line:

echo "LOCK_TIMEOUT 3600" | gpg-connect-agent

You can either increase the lock time or remove it altogether if you want the system to stay active for 2 years without needing to unlock it.

•   Keep the 2-year GPG cache as is:

default-cache-ttl 63072000 # 2 years max-cache-ttl 63072000 # 2 years

Downsides of Removing the Auto-Lock:

•   Security Risk: If the keys stay unlocked for 2 years, there’s a bigger chance that if the system gets hacked, someone could access the keys without having to reauthenticate.

If 2-Year Unattended Staking is Essential:

1.  Remove the auto-lock timeout, so GPG stays unlocked for the entire 2-year period.
2.  Encourage hardware wallets, so even if the system gets compromised, the private keys aren’t stored on the system itself.