쉘 스크립트에서 비밀번호 숨기기

쉘 스크립트에서 비밀번호 숨기기

쉘 스크립트에서 비밀번호를 숨기는 방법은 무엇입니까? 데이터베이스에 액세스하는 스크립트가 많이 있습니다. 스크립트를 열면 다른 사람들도 사용자 이름과 비밀번호를 알게 됩니다. 그러니 숨기는 방법을 아는 사람이 있으면 알려주시기 바랍니다.

방법이 있습니다. 파일에 비밀번호를 입력하고 파일을 숨기면 아무도 파일에 액세스하지 못하게 됩니다(데이터베이스에 액세스할 때 권한을 변경하고 스크립트에서 파일을 사용합니다).

답변1

첫 번째여러 사람이 이미 말했듯이 자격 증명을 스크립트에서 분리하는 것이 중요합니다. (보안 향상 외에도 이는 서로 다른 자격 증명을 사용하여 여러 시스템에 대해 동일한 스크립트를 재사용할 수 있음을 의미합니다.)

두번째, 자격 증명의 보안뿐만 아니라 해당 자격 증명이 손상된 경우의 영향도 고려해야 합니다. 데이터베이스에 대한 모든 액세스에 하나의 비밀번호만 사용해서는 안 되며, 액세스 수준이 다른 다양한 자격 증명을 사용해야 합니다. 예를 들어, 데이터베이스에서 검색을 수행할 수 있는 데이터베이스 사용자가 있을 수 있습니다. 이 사용자는 읽기 전용 액세스 권한을 가져야 합니다. 다른 사용자에게는 새 레코드를 삽입할 수 있는 권한이 있지만 삭제할 수는 없습니다. 제3자가 해당 기록을 삭제할 권리를 가질 수도 있습니다.

각 계정의 권한을 제한하는 것 외에도 각 계정의 사용 범위도 제한해야 합니다. 예를 들어, 웹 서버에 사용하는 계정은 웹 서버가 아닌 다른 IP 주소에서 연결하도록 허용되어서는 안 됩니다. 데이터베이스에 대한 전체 루트 액세스 권한이 있는 계정은 실제로 연결할 수 있는 위치가 매우 제한되어야 하며 대화형을 제외하고는 절대 사용해서는 안 됩니다. 또한 각 계정이 수행할 수 있는 작업을 정확하게 제한하려면 데이터베이스에서 저장 프로시저를 사용하는 것이 좋습니다.

이러한 제한은 클라이언트가 손상되더라도 변경할 수 없도록 시스템의 데이터베이스 서버 측에 적용되어야 합니다. (물론 데이터베이스 구성 외에도 데이터베이스 서버도 방화벽 등으로 보호해야 합니다...)

데이터베이스 계정이 제한된 읽기 전용 액세스만 허용하고 특정 IP 주소에서만 액세스할 수 있는 경우 데이터의 민감도와 Fleeed되는 스크립트 호스트의 보안에 따라 그 이상의 추가 자격 증명이 필요하지 않을 수 있습니다. 웹 페이지에 표시될 정보만 추출하는 저장 프로시저만 사용할 수 있는 사용자가 실행할 수 있는 웹 사이트의 검색 양식을 예로 들 수 있습니다. 이 경우 정보가 이미 공개되어 있고 사용자가 더 민감한 다른 데이터에 액세스할 수 없기 때문에 비밀번호를 추가해도 실제로 보안이 강화되지는 않습니다.

또한 TLS를 사용하여 데이터베이스에 대한 연결을 설정해야 합니다. 그렇지 않으면 네트워크에서 수신하는 모든 사람이 귀하의 자격 증명을 얻을 수 있습니다.

제삼, 사용할 자격 증명을 고려하세요. 비밀번호는 단지 형식일 뿐이며 가장 안전한 것은 아닙니다. 대신 공개/개인 키 쌍이나 AD/PAM 등을 사용할 수 있습니다.

네번째, 스크립트가 실행되는 조건을 고려하십시오.

대화형으로 실행 중인 경우 실행할 때 비밀번호 또는 개인 키의 비밀번호를 입력하거나 유효한 Kerberos 티켓으로 로그인해야 합니다. 즉, 스크립트는 실행할 때 얻는 값을 선택해야 합니다. 자격 증명을 파일에서 읽는 대신 사용자가 직접 제공합니다.

웹 서버에서 실행 중인 경우 웹 서버를 시작할 때 자격 증명 설정을 고려하십시오. SSL 인증서가 좋은 예입니다. 공개 인증서와 개인 키가 있고 개인 키에는 비밀번호가 있습니다. 웹 서버에 개인 키를 저장할 수 있지만 Apache를 시작할 때 비밀번호를 입력해야 합니다. 서버가 시작되면 삭제하거나 잠글 수 있는 일종의 하드웨어(예: 물리적 카드 또는 HSM)에 대한 자격 증명을 가질 수도 있습니다. (물론 이 접근 방식의 단점은 문제가 발생하면 서버가 자체적으로 다시 시작할 수 없다는 것입니다. 시스템이 손상된 위험을 감수하기보다는 그렇게 하는 편이 낫겠지만 마일리지는 다를 수 있습니다.....)

스크립트가 cron에서 실행되는 경우 이것이 어려운 부분입니다. 다른 사람이 액세스할 수 있는 시스템 어딘가에 자격 증명을 배치하고 싶지는 않지만 스크립트가 액세스할 수 있도록 자격 증명을 주변에 두기를 원합니다. 그렇죠? 글쎄요, 전혀 옳지 않습니다. 스크립트가 수행하는 작업에 대해 신중하게 생각하십시오. 데이터베이스에 대해 어떤 권한이 필요합니까? 잘못된 사람이 이러한 권한으로 연결해도 문제가 되지 않도록 제한할 수 있나요? 다른 사용자가 있는 서버에서 스크립트를 실행하는 대신 다른 사람이 접근할 수 없는 데이터베이스 서버에서 직접 스크립트를 실행할 수 있나요? 만약 어떤 이유로 내가 생각할 수 없다면, 당신은 절대적으로~ 해야 하다보안되지 않은 서버에서 스크립트를 실행하면~ 해야 하다위험하고 파괴적인 일을 할 수 있다는 것... 이제 아키텍처를 다시 생각해 볼 좋은 시간입니다.

다섯, 데이터베이스 보안을 중요하게 생각한다면 다른 사람이 액세스할 수 있는 서버에서 이러한 스크립트를 실행해서는 안 됩니다. 누군가가 귀하의 시스템에 로그인하면, 그들은~ 할 것이다자격증을 취득할 수 있는 기회가 있습니다. 예를 들어, SSL 인증서가 있는 웹 서버를 사용하면 누군가가 루트 권한을 얻고 httpd 프로세스의 메모리 영역에 액세스하여 자격 증명을 추출할 수 있다는 것이 이론적으로 가능합니다. 최근 공격자가 로그인할 필요 없이 SSL을 활용하여 이를 수행하는 취약점이 하나 이상 나타났습니다.

또한 SELinux나 AppArmor 또는 시스템에서 사용할 수 있는 모든 것을 사용하여 사용자가 무엇을 할 수 있는지 제한하는 것을 고려해보세요. 이를 통해 사용자가 자격 증명에 액세스하는 데 성공하더라도 데이터베이스에 연결하려는 시도조차 금지할 수 있습니다.

이 모든 것이 당신에게 너무 부담스럽다면, 그리고 그렇게 할 여유가 없거나 그렇게 할 시간이 없습니다. 그러면 내 (오만하고 엘리트주의적인) 의견으로는 중요하거나 민감한 내용을 데이터베이스에 저장해서는 안 됩니다. 중요하거나 민감한 내용을 저장하지 않는 경우 자격 증명을 어디에 저장하는지는 중요하지 않습니다. 그렇다면 왜 비밀번호를 사용합니까?

마침내, 어떤 종류의 자격 증명을 저장하는 것을 절대 피할 수 없는 경우 자격 증명을 읽기 전용으로 만들고 루트가 소유하도록 할 수 있으며 루트는 스크립트가 요청할 때 일시적으로 소유권을 부여할 수 있습니다(스크립트가 그래야 하는 것처럼).아니요루트로 실행하고 꼭 필요한 경우가 아니면 데이터베이스에 연결할 필요가 없습니다. 하지만 여전히 좋은 생각은 아닙니다.

답변2

우선, 처음부터 스크립트 내에 또는 스크립트와 함께 비밀번호를 저장하지 않도록 변경할 수 있는 방법이 있다면 이를 위해 필요한 모든 조치를 취해야 합니다.Jenny D의 답변이 효과에 대한 좋은 조언이 많이 포함되어 있습니다.

그렇지 않으면 권한이 제한된 별도의 파일에 비밀번호를 넣는 것이 거의 전부입니다. 예를 들어, 기본 스크립트에서 파일을 가져올 수 있습니다.

. /usr/local/etc/secret-password-here

권한이 있는 사람만 실행할 수 있도록 기본 스크립트의 권한을 제한할 수도 있지만 제안한 대로 수행하고 비밀번호 자체만 제한된 파일에 저장하는 것이 더 좋습니다. 이렇게 하면 코드 자체(민감한 비밀 없음) 검사, 버전 제어 및 스크립트 복사 등이 더 쉬워집니다.

답변3

보안에 관계없이 또 다른 해결책(다른 파일이나 데이터베이스에 자격 증명을 저장하는 것이 더 낫다고 생각함)은 gpg를 사용하여 비밀번호를 암호화하고 이를 스크립트에 삽입하는 것입니다.

비밀번호 없는 GPG 키 쌍을 사용하고 이를 USB에 저장합니다. (참고: 이 키 쌍을 내보낼 때 --armor를 사용하지 말고 대신 바이너리 형식으로 내보냅니다.)

먼저 비밀번호를 암호화하세요.

echo -n "pAssw0rd" | gpg --armor --no-default-keyring --keyring /media/usb/key.pub --recipient [email protected] --encrypt

그러면 gpg 암호화 비밀번호가 표준 출력으로 인쇄됩니다. 전체 메시지를 복사하여 스크립트에 추가합니다.

password=$(gpg --batch --quiet --no-default-keyring --secret-keyring /media/usb/key.priv --decrypt <<EOF 
-----BEGIN PGP MESSAGE-----

hQEMA0CjbyauRLJ8AQgAkZT5gK8TrdH6cZEy+Ufl0PObGZJ1YEbshacZb88RlRB9
h2z+s/Bso5HQxNd5tzkwulvhmoGu6K6hpMXM3mbYl07jHF4qr+oWijDkdjHBVcn5
0mkpYO1riUf0HXIYnvCZq/4k/ajGZRm8EdDy2JIWuwiidQ18irp07UUNO+AB9mq8
5VXUjUN3tLTexg4sLZDKFYGRi4fyVrYKGsi0i5AEHKwn5SmTb3f1pa5yXbv68eYE
lCVfy51rBbG87UTycZ3gFQjf1UkNVbp0WV+RPEM9JR7dgR+9I8bKCuKLFLnGaqvc
beA3A6eMpzXQqsAg6GGo3PW6fMHqe1ZCvidi6e4a/dJDAbHq0XWp93qcwygnWeQW
Ozr1hr5mCa+QkUSymxiUrRncRhyqSP0ok5j4rjwSJu9vmHTEUapiyQMQaEIF2e2S
/NIWGg==
=uriR
-----END PGP MESSAGE-----
EOF)

이 방법으로 USB 플래시 드라이브가 시스템에 설치된 경우에만 비밀번호를 해독할 수 있습니다. 물론 키를 시스템으로 가져오거나(보안 수준이 낮거나 보안이 전혀 없음) 비밀번호로 개인 키를 보호할 수도 있습니다(자동화할 수 없음).

답변4

나는 또한이 문제에 직면했습니다. 짧은 대답은 스크립트와 별도로 비밀번호를 저장한 다음 환경의 비밀번호에 스크립트를 로드하는 것입니다. 이는 질문을 제기합니다. 이를 수행하는 가장 좋은 방법은 무엇입니까?

이상적으로는 HashiCorp Vault와 같은 외부 서비스를 사용하여 이를 셸 환경으로 가져오는 것입니다. 그러나 Vault는 설정이 매우 복잡하며 많은 기업 환경에서는 설치할 수 있는 항목이나 심지어는 셸 유형에 따라 제한될 수 있습니다. 사용할 수 있습니다.

이를 위해 나는 창조했다encpass.sh - 쉘 스크립트에서 암호화된 비밀번호를 사용하기 위한 경량 솔루션이를 통해 귀하의 사용자만 액세스할 수 있는 숨겨진 디렉터리에 비밀을 저장할 수 있습니다. POSIX 호환 셸에서 작동해야 하며 유일한 종속성은 비밀 암호화를 처리하기 위해 로컬 시스템에 OpenSSL을 설치하는 것입니다. 당신이 해야 할 일은 그것을 스크립트에 포함시키고 "get_secret" 함수를 호출하는 것뿐입니다. MIT 라이선스를 보유하고 있어 기업 환경에서도 사용할 수 있습니다. 내 회사,인쇄 제한됨., 스크립트를 유지 관리하고 가끔 업데이트를 릴리스합니다. 보기 쉽도록 아래 코드를 복사하겠습니다.

#!/bin/sh
################################################################################
# Copyright (c) 2020 Plyint, LLC <[email protected]>. All Rights Reserved.
# This file is licensed under the MIT License (MIT). 
# Please see LICENSE.txt for more information.
# 
# DESCRIPTION: 
# This script allows a user to encrypt a password (or any other secret) at 
# runtime and then use it, decrypted, within a script.  This prevents shoulder 
# surfing passwords and avoids storing the password in plain text, which could 
# inadvertently be sent to or discovered by an individual at a later date.
#
# This script generates an AES 256 bit symmetric key for each script (or user-
# defined bucket) that stores secrets.  This key will then be used to encrypt 
# all secrets for that script or bucket.  encpass.sh sets up a directory 
# (.encpass) under the user's home directory where keys and secrets will be 
# stored.
#
# For further details, see README.md or run "./encpass ?" from the command line.
#
################################################################################

encpass_checks() {
    if [ -n "$ENCPASS_CHECKS" ]; then
        return
    fi

    if [ ! -x "$(command -v openssl)" ]; then
        echo "Error: OpenSSL is not installed or not accessible in the current path." \
            "Please install it and try again." >&2
        exit 1
    fi

    if [ -z "$ENCPASS_HOME_DIR" ]; then
        ENCPASS_HOME_DIR=$(encpass_get_abs_filename ~)/.encpass
    fi

    if [ ! -d "$ENCPASS_HOME_DIR" ]; then
        mkdir -m 700 "$ENCPASS_HOME_DIR"
        mkdir -m 700 "$ENCPASS_HOME_DIR/keys"
        mkdir -m 700 "$ENCPASS_HOME_DIR/secrets"
    fi

    if [ "$(basename "$0")" != "encpass.sh" ]; then
        encpass_include_init "$1" "$2"
    fi

    ENCPASS_CHECKS=1
}

# Initializations performed when the script is included by another script
encpass_include_init() {
    if [ -n "$1" ] && [ -n "$2" ]; then
        ENCPASS_BUCKET=$1
        ENCPASS_SECRET_NAME=$2
    elif [ -n "$1" ]; then
        ENCPASS_BUCKET=$(basename "$0")
        ENCPASS_SECRET_NAME=$1
    else
        ENCPASS_BUCKET=$(basename "$0")
        ENCPASS_SECRET_NAME="password"
    fi
}

encpass_generate_private_key() {
    ENCPASS_KEY_DIR="$ENCPASS_HOME_DIR/keys/$ENCPASS_BUCKET"

    if [ ! -d "$ENCPASS_KEY_DIR" ]; then
        mkdir -m 700 "$ENCPASS_KEY_DIR"
    fi

    if [ ! -f "$ENCPASS_KEY_DIR/private.key" ]; then
        (umask 0377 && printf "%s" "$(openssl rand -hex 32)" >"$ENCPASS_KEY_DIR/private.key")
    fi
}

encpass_get_private_key_abs_name() {
    ENCPASS_PRIVATE_KEY_ABS_NAME="$ENCPASS_HOME_DIR/keys/$ENCPASS_BUCKET/private.key"

    if [ "$1" != "nogenerate" ]; then 
        if [ ! -f "$ENCPASS_PRIVATE_KEY_ABS_NAME" ]; then
            encpass_generate_private_key
        fi
    fi
}

encpass_get_secret_abs_name() {
    ENCPASS_SECRET_ABS_NAME="$ENCPASS_HOME_DIR/secrets/$ENCPASS_BUCKET/$ENCPASS_SECRET_NAME.enc"

    if [ "$3" != "nocreate" ]; then 
        if [ ! -f "$ENCPASS_SECRET_ABS_NAME" ]; then
            set_secret "$1" "$2"
        fi
    fi
}

get_secret() {
    encpass_checks "$1" "$2"
    encpass_get_private_key_abs_name
    encpass_get_secret_abs_name "$1" "$2"
    encpass_decrypt_secret
}

set_secret() {
    encpass_checks "$1" "$2"

    if [ "$3" != "reuse" ] || { [ -z "$ENCPASS_SECRET_INPUT" ] && [ -z "$ENCPASS_CSECRET_INPUT" ]; }; then
        echo "Enter $ENCPASS_SECRET_NAME:" >&2
        stty -echo
        read -r ENCPASS_SECRET_INPUT
        stty echo
        echo "Confirm $ENCPASS_SECRET_NAME:" >&2
        stty -echo
        read -r ENCPASS_CSECRET_INPUT
        stty echo
    fi

    if [ "$ENCPASS_SECRET_INPUT" = "$ENCPASS_CSECRET_INPUT" ]; then
        encpass_get_private_key_abs_name
        ENCPASS_SECRET_DIR="$ENCPASS_HOME_DIR/secrets/$ENCPASS_BUCKET"

        if [ ! -d "$ENCPASS_SECRET_DIR" ]; then
            mkdir -m 700 "$ENCPASS_SECRET_DIR"
        fi

        printf "%s" "$(openssl rand -hex 16)" >"$ENCPASS_SECRET_DIR/$ENCPASS_SECRET_NAME.enc"

        ENCPASS_OPENSSL_IV="$(cat "$ENCPASS_SECRET_DIR/$ENCPASS_SECRET_NAME.enc")"

        echo "$ENCPASS_SECRET_INPUT" | openssl enc -aes-256-cbc -e -a -iv \
            "$ENCPASS_OPENSSL_IV" -K \
            "$(cat "$ENCPASS_HOME_DIR/keys/$ENCPASS_BUCKET/private.key")" 1>> \
                    "$ENCPASS_SECRET_DIR/$ENCPASS_SECRET_NAME.enc"
    else
        echo "Error: secrets do not match.  Please try again." >&2
        exit 1
    fi
}

encpass_get_abs_filename() {
    # $1 : relative filename
    filename="$1"
    parentdir="$(dirname "${filename}")"

    if [ -d "${filename}" ]; then
        cd "${filename}" && pwd
    elif [ -d "${parentdir}" ]; then
        echo "$(cd "${parentdir}" && pwd)/$(basename "${filename}")"
    fi
}

encpass_decrypt_secret() {
    if [ -f "$ENCPASS_PRIVATE_KEY_ABS_NAME" ]; then
        ENCPASS_DECRYPT_RESULT="$(dd if="$ENCPASS_SECRET_ABS_NAME" ibs=1 skip=32 2> /dev/null | openssl enc -aes-256-cbc \
            -d -a -iv "$(head -c 32 "$ENCPASS_SECRET_ABS_NAME")" -K "$(cat "$ENCPASS_PRIVATE_KEY_ABS_NAME")" 2> /dev/null)"
        if [ ! -z "$ENCPASS_DECRYPT_RESULT" ]; then
            echo "$ENCPASS_DECRYPT_RESULT"
        else
            # If a failed unlock command occurred and the user tries to show the secret
            # Present either locked or decrypt command
            if [ -f "$ENCPASS_HOME_DIR/keys/$ENCPASS_BUCKET/private.lock" ]; then 
            echo "**Locked**"
            else
                # The locked file wasn't present as expected.  Let's display a failure
            echo "Error: Failed to decrypt"
            fi
        fi
    elif [ -f "$ENCPASS_HOME_DIR/keys/$ENCPASS_BUCKET/private.lock" ]; then
        echo "**Locked**"
    else
        echo "Error: Unable to decrypt. The key file \"$ENCPASS_PRIVATE_KEY_ABS_NAME\" is not present."
    fi
}


##########################################################
# COMMAND LINE MANAGEMENT SUPPORT
# -------------------------------
# If you don't need to manage the secrets for the scripts
# with encpass.sh you can delete all code below this point
# in order to significantly reduce the size of encpass.sh.
# This is useful if you want to bundle encpass.sh with
# your existing scripts and just need the retrieval
# functions.
##########################################################

encpass_show_secret() {
    encpass_checks
    ENCPASS_BUCKET=$1

    encpass_get_private_key_abs_name "nogenerate"

    if [ ! -z "$2" ]; then
        ENCPASS_SECRET_NAME=$2
        encpass_get_secret_abs_name "$1" "$2" "nocreate"
        if [ -z "$ENCPASS_SECRET_ABS_NAME" ]; then
            echo "No secret named $2 found for bucket $1."
            exit 1
        fi

        encpass_decrypt_secret
    else
        ENCPASS_FILE_LIST=$(ls -1 "$ENCPASS_HOME_DIR"/secrets/"$1")
        for ENCPASS_F in $ENCPASS_FILE_LIST; do
            ENCPASS_SECRET_NAME=$(basename "$ENCPASS_F" .enc)

            encpass_get_secret_abs_name "$1" "$ENCPASS_SECRET_NAME" "nocreate"
            if [ -z "$ENCPASS_SECRET_ABS_NAME" ]; then
                echo "No secret named $ENCPASS_SECRET_NAME found for bucket $1."
                exit 1
            fi

            echo "$ENCPASS_SECRET_NAME = $(encpass_decrypt_secret)"
        done
    fi
}

encpass_getche() {
        old=$(stty -g)
        stty raw min 1 time 0
        printf '%s' "$(dd bs=1 count=1 2>/dev/null)"
        stty "$old"
}

encpass_remove() {
    if [ ! -n "$ENCPASS_FORCE_REMOVE" ]; then
        if [ ! -z "$ENCPASS_SECRET" ]; then
            printf "Are you sure you want to remove the secret \"%s\" from bucket \"%s\"? [y/N]" "$ENCPASS_SECRET" "$ENCPASS_BUCKET"
        else
            printf "Are you sure you want to remove the bucket \"%s?\" [y/N]" "$ENCPASS_BUCKET"
        fi

        ENCPASS_CONFIRM="$(encpass_getche)"
        printf "\n"
        if [ "$ENCPASS_CONFIRM" != "Y" ] && [ "$ENCPASS_CONFIRM" != "y" ]; then
            exit 0
        fi
    fi

    if [ ! -z "$ENCPASS_SECRET" ]; then
        rm -f "$1"
        printf "Secret \"%s\" removed from bucket \"%s\".\n" "$ENCPASS_SECRET" "$ENCPASS_BUCKET"
    else
        rm -Rf "$ENCPASS_HOME_DIR/keys/$ENCPASS_BUCKET"
        rm -Rf "$ENCPASS_HOME_DIR/secrets/$ENCPASS_BUCKET"
        printf "Bucket \"%s\" removed.\n" "$ENCPASS_BUCKET"
    fi
}

encpass_save_err() {
    if read -r x; then
        { printf "%s\n" "$x"; cat; } > "$1"
    elif [ "$x" != "" ]; then
        printf "%s" "$x" > "$1"
    fi
}

encpass_help() {
less << EOF
NAME:
    encpass.sh - Use encrypted passwords in shell scripts

DESCRIPTION: 
    A lightweight solution for using encrypted passwords in shell scripts 
    using OpenSSL. It allows a user to encrypt a password (or any other secret)
    at runtime and then use it, decrypted, within a script. This prevents
    shoulder surfing passwords and avoids storing the password in plain text, 
    within a script, which could inadvertently be sent to or discovered by an 
    individual at a later date.

    This script generates an AES 256 bit symmetric key for each script 
    (or user-defined bucket) that stores secrets. This key will then be used 
    to encrypt all secrets for that script or bucket.

    Subsequent calls to retrieve a secret will not prompt for a secret to be 
    entered as the file with the encrypted value already exists.

    Note: By default, encpass.sh sets up a directory (.encpass) under the 
    user's home directory where keys and secrets will be stored.  This directory
    can be overridden by setting the environment variable ENCPASS_HOME_DIR to a
    directory of your choice.

    ~/.encpass (or the directory specified by ENCPASS_HOME_DIR) will contain 
    the following subdirectories:
      - keys (Holds the private key for each script/bucket)
      - secrets (Holds the secrets stored for each script/bucket)

USAGE:
    To use the encpass.sh script in an existing shell script, source the script 
    and then call the get_secret function.

    Example:

        #!/bin/sh
        . encpass.sh
        password=\$(get_secret)

    When no arguments are passed to the get_secret function,
    then the bucket name is set to the name of the script and
    the secret name is set to "password".

    There are 2 other ways to call get_secret:

      Specify the secret name:
      Ex: \$(get_secret user)
        - bucket name = <script name>
        - secret name = "user"

      Specify both the secret name and bucket name:
      Ex: \$(get_secret personal user)
        - bucket name = "personal"
        - secret name = "user"

    encpass.sh also provides a command line interface to manage the secrets.
    To invoke a command, pass it as an argument to encpass.sh from the shell.

        $ encpass.sh [COMMAND]

    See the COMMANDS section below for a list of available commands.  Wildcard
    handling is implemented for secret and bucket names.  This enables
    performing operations like adding/removing a secret to/from multiple buckets
        at once.

COMMANDS:
    add [-f] <bucket> <secret>
        Add a secret to the specified bucket.  The bucket will be created
        if it does not already exist. If a secret with the same name already
        exists for the specified bucket, then the user will be prompted to
        confirm overwriting the value.  If the -f option is passed, then the
        add operation will perform a forceful overwrite of the value. (i.e. no
        prompt)

    list|ls [<bucket>]
        Display the names of the secrets held in the bucket.  If no bucket
        is specified, then the names of all existing buckets will be
        displayed.

    lock
        Locks all keys used by encpass.sh using a password.  The user
        will be prompted to enter a password and confirm it.  A user
        should take care to securely store the password.  If the password
        is lost then keys can not be unlocked.  When keys are locked,
        secrets can not be retrieved. (e.g. the output of the values
        in the "show" command will be encrypted/garbage)

    remove|rm [-f] <bucket> [<secret>]
        Remove a secret from the specified bucket.  If only a bucket is
        specified then the entire bucket (i.e. all secrets and keys) will
        be removed.  By default the user is asked to confirm the removal of
        the secret or the bucket.  If the -f option is passed then a 
        forceful removal will be performed.  (i.e. no prompt)

    show [<bucket>] [<secret>]
        Show the unencrypted value of the secret from the specified bucket.
        If no secret is specified then all secrets for the bucket are displayed.

    update <bucket> <secret>
        Updates a secret in the specified bucket.  This command is similar
        to using an "add -f" command, but it has a safety check to only 
        proceed if the specified secret exists.  If the secret, does not
        already exist, then an error will be reported. There is no forceable
        update implemented.  Use "add -f" for any required forceable update
        scenarios.

    unlock
        Unlocks all the keys for encpass.sh.  The user will be prompted to 
        enter the password and confirm it.

    dir
        Prints out the current value of the ENCPASS_HOME_DIR environment variable.

    help|--help|usage|--usage|?
        Display this help message.
EOF
}

# Subcommands for cli support
case "$1" in
    add )
        shift
        while getopts ":f" ENCPASS_OPTS; do
            case "$ENCPASS_OPTS" in
                f ) ENCPASS_FORCE_ADD=1;;
            esac
        done

        encpass_checks

        if [ -n "$ENCPASS_FORCE_ADD" ]; then
            shift $((OPTIND-1))
        fi

        if [ ! -z "$1" ] && [ ! -z "$2" ]; then
            # Allow globbing
            # shellcheck disable=SC2027,SC2086
            ENCPASS_ADD_LIST="$(ls -1d "$ENCPASS_HOME_DIR/secrets/"$1"" 2>/dev/null)"
            if [ -z "$ENCPASS_ADD_LIST" ]; then
                ENCPASS_ADD_LIST="$1"
            fi

            for ENCPASS_ADD_F in $ENCPASS_ADD_LIST; do
                ENCPASS_ADD_DIR="$(basename "$ENCPASS_ADD_F")"
                ENCPASS_BUCKET="$ENCPASS_ADD_DIR"
                if [ ! -n "$ENCPASS_FORCE_ADD" ] && [ -f "$ENCPASS_ADD_F/$2.enc" ]; then
                    echo "Warning: A secret with the name \"$2\" already exists for bucket $ENCPASS_BUCKET."
                    echo "Would you like to overwrite the value? [y/N]"

                    ENCPASS_CONFIRM="$(encpass_getche)"
                    if [ "$ENCPASS_CONFIRM" != "Y" ] && [ "$ENCPASS_CONFIRM" != "y" ]; then
                        continue
                    fi
                fi

                ENCPASS_SECRET_NAME="$2"
                echo "Adding secret \"$ENCPASS_SECRET_NAME\" to bucket \"$ENCPASS_BUCKET\"..."
                set_secret "$ENCPASS_BUCKET" "$ENCPASS_SECRET_NAME" "reuse"
            done
        else
            echo "Error: A bucket name and secret name must be provided when adding a secret."
            exit 1
        fi
        ;;
    update )
        shift

        encpass_checks
        if [ ! -z "$1" ] && [ ! -z "$2" ]; then

            ENCPASS_SECRET_NAME="$2"
            # Allow globbing
            # shellcheck disable=SC2027,SC2086
            ENCPASS_UPDATE_LIST="$(ls -1d "$ENCPASS_HOME_DIR/secrets/"$1"" 2>/dev/null)"

            for ENCPASS_UPDATE_F in $ENCPASS_UPDATE_LIST; do
                # Allow globbing
                # shellcheck disable=SC2027,SC2086
                if [ -f "$ENCPASS_UPDATE_F/"$2".enc" ]; then
                        ENCPASS_UPDATE_DIR="$(basename "$ENCPASS_UPDATE_F")"
                        ENCPASS_BUCKET="$ENCPASS_UPDATE_DIR"
                        echo "Updating secret \"$ENCPASS_SECRET_NAME\" to bucket \"$ENCPASS_BUCKET\"..."
                        set_secret "$ENCPASS_BUCKET" "$ENCPASS_SECRET_NAME" "reuse"
                else
                    echo "Error: A secret with the name \"$2\" does not exist for bucket $1."
                    exit 1
                fi
            done
        else
            echo "Error: A bucket name and secret name must be provided when updating a secret."
            exit 1
        fi
        ;;
    rm|remove )
        shift
        encpass_checks

        while getopts ":f" ENCPASS_OPTS; do
            case "$ENCPASS_OPTS" in
                f ) ENCPASS_FORCE_REMOVE=1;;
            esac
        done

        if [ -n "$ENCPASS_FORCE_REMOVE" ]; then
            shift $((OPTIND-1))
        fi

        if [ -z "$1" ]; then 
            echo "Error: A bucket must be specified for removal."
        fi

        # Allow globbing
        # shellcheck disable=SC2027,SC2086
        ENCPASS_REMOVE_BKT_LIST="$(ls -1d "$ENCPASS_HOME_DIR/secrets/"$1"" 2>/dev/null)"
        if [ ! -z "$ENCPASS_REMOVE_BKT_LIST" ]; then
            for ENCPASS_REMOVE_B in $ENCPASS_REMOVE_BKT_LIST; do

                ENCPASS_BUCKET="$(basename "$ENCPASS_REMOVE_B")"
                if [ ! -z "$2" ]; then
                    # Removing secrets for a specified bucket
                    # Allow globbing
                    # shellcheck disable=SC2027,SC2086
                    ENCPASS_REMOVE_LIST="$(ls -1p "$ENCPASS_REMOVE_B/"$2".enc" 2>/dev/null)"

                    if [ -z "$ENCPASS_REMOVE_LIST" ]; then
                        echo "Error: No secrets found for $2 in bucket $ENCPASS_BUCKET."
                        exit 1
                    fi

                    for ENCPASS_REMOVE_F in $ENCPASS_REMOVE_LIST; do
                        ENCPASS_SECRET="$2"
                        encpass_remove "$ENCPASS_REMOVE_F"
                    done
                else
                    # Removing a specified bucket
                    encpass_remove
                fi

            done
        else
            echo "Error: The bucket named $1 does not exist."
            exit 1
        fi
        ;;
    show )
        shift
        encpass_checks
        if [ -z "$1" ]; then
            ENCPASS_SHOW_DIR="*"
        else
            ENCPASS_SHOW_DIR=$1
        fi

        if [ ! -z "$2" ]; then
            # Allow globbing
            # shellcheck disable=SC2027,SC2086
            if [ -f "$(encpass_get_abs_filename "$ENCPASS_HOME_DIR/secrets/$ENCPASS_SHOW_DIR/"$2".enc")" ]; then
                encpass_show_secret "$ENCPASS_SHOW_DIR" "$2"
            fi
        else
            # Allow globbing
            # shellcheck disable=SC2027,SC2086
            ENCPASS_SHOW_LIST="$(ls -1d "$ENCPASS_HOME_DIR/secrets/"$ENCPASS_SHOW_DIR"" 2>/dev/null)"

            if [ -z "$ENCPASS_SHOW_LIST" ]; then
                if [ "$ENCPASS_SHOW_DIR" = "*" ]; then
                    echo "Error: No buckets exist."
                else
                    echo "Error: Bucket $1 does not exist."
                fi
                exit 1
            fi

            for ENCPASS_SHOW_F in $ENCPASS_SHOW_LIST; do
                ENCPASS_SHOW_DIR="$(basename "$ENCPASS_SHOW_F")"
                echo "$ENCPASS_SHOW_DIR:"
                encpass_show_secret "$ENCPASS_SHOW_DIR"
                echo " "
            done
        fi
        ;;
    ls|list )
        shift
        encpass_checks
        if [ ! -z "$1" ]; then
            # Allow globbing
            # shellcheck disable=SC2027,SC2086
            ENCPASS_FILE_LIST="$(ls -1p "$ENCPASS_HOME_DIR/secrets/"$1"" 2>/dev/null)"

            if [ -z "$ENCPASS_FILE_LIST" ]; then
                # Allow globbing
                # shellcheck disable=SC2027,SC2086
                ENCPASS_DIR_EXISTS="$(ls -d "$ENCPASS_HOME_DIR/secrets/"$1"" 2>/dev/null)"
                if [ ! -z "$ENCPASS_DIR_EXISTS" ]; then
                    echo "Bucket $1 is empty."
                else
                    echo "Error: Bucket $1 does not exist."
                fi
                exit 1
            fi

            ENCPASS_NL=""
            for ENCPASS_F in $ENCPASS_FILE_LIST; do
                if [ -d "${ENCPASS_F%:}" ]; then
                    printf "$ENCPASS_NL%s\n" "$(basename "$ENCPASS_F")"
                    ENCPASS_NL="\n"
                else
                    printf "%s\n" "$(basename "$ENCPASS_F" .enc)"
                fi
            done
        else
            # Allow globbing
            # shellcheck disable=SC2027,SC2086
            ENCPASS_BUCKET_LIST="$(ls -1p "$ENCPASS_HOME_DIR/secrets/"$1"" 2>/dev/null)"
            for ENCPASS_C in $ENCPASS_BUCKET_LIST; do
                if [ -d "${ENCPASS_C%:}" ]; then
                    printf "\n%s" "\n$(basename "$ENCPASS_C")"
                else
                    basename "$ENCPASS_C" .enc
                fi
            done
        fi
        ;;
    lock )
        shift
        encpass_checks

        echo "************************!!!WARNING!!!*************************" >&2
        echo "* You are about to lock your keys with a password.           *" >&2
        echo "* You will not be able to use your secrets again until you   *" >&2
        echo "* unlock the keys with the same password. It is important    *" >&2
        echo "* that you securely store the password, so you can recall it *" >&2
        echo "* in the future.  If you forget your password you will no    *" >&2
        echo "* longer be able to access your secrets.                     *" >&2
        echo "************************!!!WARNING!!!*************************" >&2

        printf "\n%s\n" "About to lock keys held in directory $ENCPASS_HOME_DIR/keys/"

        printf "\nEnter Password to lock keys:" >&2
        stty -echo
        read -r ENCPASS_KEY_PASS
        printf "\nConfirm Password:" >&2
        read -r ENCPASS_CKEY_PASS
        printf "\n"
        stty echo

        if [ -z "$ENCPASS_KEY_PASS" ]; then
            echo "Error: You must supply a password value."
            exit 1
        fi

        if [ "$ENCPASS_KEY_PASS" = "$ENCPASS_CKEY_PASS" ]; then
            ENCPASS_NUM_KEYS_LOCKED=0
            ENCPASS_KEYS_LIST="$(ls -1d "$ENCPASS_HOME_DIR/keys/"*"/" 2>/dev/null)"
            for ENCPASS_KEY_F in $ENCPASS_KEYS_LIST; do

                if [ -d "${ENCPASS_KEY_F%:}" ]; then
                    ENCPASS_KEY_NAME="$(basename "$ENCPASS_KEY_F")"
                    ENCPASS_KEY_VALUE=""
                    if [ -f "$ENCPASS_KEY_F/private.key" ]; then
                        ENCPASS_KEY_VALUE="$(cat "$ENCPASS_KEY_F/private.key")"
                        if [ ! -f "$ENCPASS_KEY_F/private.lock" ]; then
                        echo "Locking key $ENCPASS_KEY_NAME..."
                        else
                          echo "Error: The key $ENCPASS_KEY_NAME appears to have been previously locked."
                            echo "       The current key file may hold a bad value. Exiting to avoid encrypting"
                            echo "       a bad value and overwriting the lock file."
                            exit 1
                        fi
                    else
                        echo "Error: Private key file ${ENCPASS_KEY_F}private.key missing for bucket $ENCPASS_KEY_NAME."
                        exit 1
                    fi
                    if [ ! -z "$ENCPASS_KEY_VALUE" ]; then
                        openssl enc -aes-256-cbc -pbkdf2 -iter 10000 -salt -in "$ENCPASS_KEY_F/private.key" -out "$ENCPASS_KEY_F/private.lock" -k "$ENCPASS_KEY_PASS"
                        if [ -f "$ENCPASS_KEY_F/private.key" ] && [ -f "$ENCPASS_KEY_F/private.lock" ]; then
                            # Both the key and lock file exist.  We can remove the key file now
                            rm -f "$ENCPASS_KEY_F/private.key"
                            echo "Locked key $ENCPASS_KEY_NAME."
                            ENCPASS_NUM_KEYS_LOCKED=$(( ENCPASS_NUM_KEYS_LOCKED + 1 ))
                        else
                            echo "Error: The key fle and/or lock file were not found as expected for key $ENCPASS_KEY_NAME."
                        fi
                    else
                        echo "Error: No key value found for the $ENCPASS_KEY_NAME key."
                        exit 1
                    fi
                fi
            done
            echo "Locked $ENCPASS_NUM_KEYS_LOCKED keys."
        else
            echo "Error: Passwords do not match."
        fi
        ;;
    unlock )
        shift
        encpass_checks

        printf "%s\n" "About to unlock keys held in the $ENCPASS_HOME_DIR/keys/ directory."

        printf "\nEnter Password to unlock keys: " >&2
        stty -echo
        read -r ENCPASS_KEY_PASS
        printf "\n"
        stty echo

        if [ ! -z "$ENCPASS_KEY_PASS" ]; then
            ENCPASS_NUM_KEYS_UNLOCKED=0
            ENCPASS_KEYS_LIST="$(ls -1d "$ENCPASS_HOME_DIR/keys/"*"/" 2>/dev/null)"
            for ENCPASS_KEY_F in $ENCPASS_KEYS_LIST; do

                if [ -d "${ENCPASS_KEY_F%:}" ]; then
                    ENCPASS_KEY_NAME="$(basename "$ENCPASS_KEY_F")"
                    echo "Unlocking key $ENCPASS_KEY_NAME..."
                    if [ -f "$ENCPASS_KEY_F/private.key" ] && [ ! -f "$ENCPASS_KEY_F/private.lock" ]; then
                        echo "Error: Key $ENCPASS_KEY_NAME appears to be unlocked already."
                        exit 1
                    fi

                    if [ -f "$ENCPASS_KEY_F/private.lock" ]; then
                        # Remove the failed file in case previous decryption attempts were unsuccessful
                        rm -f "$ENCPASS_KEY_F/failed" 2>/dev/null

                        # Decrypt key. Log any failure to the "failed" file.
                        openssl enc -aes-256-cbc -d -pbkdf2 -iter 10000 -salt \
                            -in "$ENCPASS_KEY_F/private.lock" -out "$ENCPASS_KEY_F/private.key" \
                            -k "$ENCPASS_KEY_PASS" 2>&1 | encpass_save_err "$ENCPASS_KEY_F/failed"

                        if [ ! -f "$ENCPASS_KEY_F/failed" ]; then
                            # No failure has occurred.
                          if [ -f "$ENCPASS_KEY_F/private.key" ] && [ -f "$ENCPASS_KEY_F/private.lock" ]; then
                              # Both the key and lock file exist.  We can remove the lock file now.
                              rm -f "$ENCPASS_KEY_F/private.lock"
                              echo "Unlocked key $ENCPASS_KEY_NAME."
                              ENCPASS_NUM_KEYS_UNLOCKED=$(( ENCPASS_NUM_KEYS_UNLOCKED + 1 ))
                          else
                              echo "Error: The key file and/or lock file were not found as expected for key $ENCPASS_KEY_NAME."
                          fi
                        else
                          printf "Error: Failed to unlock key %s.\n" "$ENCPASS_KEY_NAME"
                            printf "       Please view %sfailed for details.\n" "$ENCPASS_KEY_F"
                        fi
                    else
                        echo "Error: No lock file found for the $ENCPASS_KEY_NAME key."
                    fi
                fi
            done
            echo "Unlocked $ENCPASS_NUM_KEYS_UNLOCKED keys."
        else
            echo "No password entered."
        fi
        ;;
    dir )
        shift
        encpass_checks
        echo "ENCPASS_HOME_DIR=$ENCPASS_HOME_DIR"
        ;;
    help|--help|usage|--usage|\? )
        encpass_checks
        encpass_help
        ;;
    * )
        if [ ! -z "$1" ]; then
            echo "Command not recognized. See \"encpass.sh help\" for a list commands."
            exit 1
        fi
        ;;
esac

관련 정보