Vấn đề

Mình sử dụng nhiều ssh-key cho các dự án khác nhau. Mỗi key mình đều đặt passphrase cũng khác nhau.

Vấn đề nảy sinh là nhiều key thì nhiều pass, mà mình hay dùng GnuPG để quản lý các secret nên muốn quy nó về 1 mối luôn. Giống như việc sử dụng KeePass để quản lý các mật khẩu. (Có khi KeePass lại có plugin cho việc quản lý ssh-keys, chưa tìm hiểu)

Giải quyết

Từng nghĩ tạo ssh key trong tính năng của GnuPG luôn. Nhưng xong thấy nó đi theo cặp (bị lock) không ưng lắm. Phương án tiếp theo là lấy cảm hứng từ Emacs, lưu password vào tệp .authinfo.gpg, nó cũng là 1 dạng password management.

ssh-add cung cấp 2 biến mỗi trường SSH_ASKPASSSSH_ASKPASS_REQUIRE để ta có thể control được phần điền mật khẩu. Cool.

  • Tạo 1 script /usr/local/bin/gpg-ask-pass-for-ssh.sh để handle công việc lấy pass từ ~/.authinfo.gpg như sau

#!/usr/bin/env bash
input="$@"
# input="Enter passphrase for ~/.ssh/id_rsa:"
real_keyname=$(echo $input | awk -F' ' '{print $4}')
# Get keyname by remove : in end of string and get basename
keyname=$(basename "$real_keyname" | sed 's/:$//')
if [ -z "$keyname" ]; then
    echo "Keyname is empty"
    exit 1
fi

# use gpg to decrypt ~/.authinfo.gpg then get password from line match with ssh/keyname
gpg -q -d ~/.authinfo.gpg | grep "ssh/${keyname}" | awk -F' ' '{print $6}'
  • Lưu trữ passphrase vào .authinfo.gpg thôi

Giả sử key ở tại ~/.ssh/id_rsa

machine ssh/id_rsa login root password PASSPHRASE_HERE
machine ssh/giaptx@company.blabla login root password PASSPHRASE_HERE_BLABLA

Demo:

eval $(ssh-agent)
SSH_ASKPASS=/usr/local/bin/gpg-ask-pass-for-ssh.sh
SSH_ASKPASS_REQUIRE=force ssh-add ~/.ssh/id_rsa

Tổng kết

Trên đây là cách mình dùng GnuPG để manage các pass cho ssh. Trong thực tế thì mình đã viết 1 function để load ssh-key tiện hơn trong terminal. Thêm phần link vào các ssh-agent để tránh tạo trùng lặp.

Cụ thể mình config vào ~/.bashrc như sau

export SSH_DIR="${HOME}/.ssh"
use-ssh(){
    export SSH_ASKPASS=/usr/local/bin/gpg-ask-pass-for-ssh.sh
    ssh_key="${1:-id_rsa}"
    if [ -e "${SSH_DIR}/${ssh_key}" ]; then
        ssh_key="${SSH_DIR}/${ssh_key}"
    elif [ -e "${PWD}/${ssh_key}" ]; then
        ssh_key="${PWD}/${ssh_key}"
    elif [ -e "${ssh_key}" ]; then
        ssh_key="${ssh_key}"
    else
        echo "What is ${ssh_key}?"
        return 1
    fi
    ssh_key=$(readlink -f "$ssh_key")
    ssh_name=$(basename "$ssh_key")
    ssh_md5=$(md5sum "$ssh_key" | cut -f1 -d" ")
    file="$SSH_DIR/${ssh_name}_${ssh_md5}.sock"
    echo "Set SSH_AUTH_SOCK=$file"
    if [ -z "$2" ] && [ -e "$file" ]; then
        export SSH_AUTH_SOCK="$(readlink -f $file)"
        ps1 "ssh:$ssh_name"
        return 0
    fi
    eval $(ssh-agent)
    SSH_ASKPASS_REQUIRE=force ssh-add "${ssh_key}"
    ln -svf "$SSH_AUTH_SOCK" "$file" >& /dev/null
    ps1 "ssh:$ssh_name"
}

_use-ssh(){
    local cur prev words cword opts
    _get_comp_words_by_ref -n : cur prev words cword
    COMPREPLY=()
    opts=""
    if [[ ${#toks[@]} -ne 0 ]]; then
        compopt -o filenames 2> /dev/null;
        COMPREPLY+=("${toks[@]}");
    fi
    if [[ ${cword} -eq 1 ]];then
        opts=$(find "${SSH_DIR}/" -name \*@\* -exec basename {} \;)
    fi
    _filedir
    COMPREPLY+=( $(compgen -W "$opts" -- "${cur}"))
}

complete -F _use-ssh use-ssh

Lúc dùng chỉ cần gõ:

  • Với key nằm trong ~/.ssh

use-ssh => Với key măc định là id_rsa

use-ssh giaptx@company.blabla

  • Hoặc với đường dẫn tương đối

use-ssh project/ssh-key

  • Hoặc với đưòng dẫn tuyệt đối

use-ssh /tmp/key-temporary