如何将密钥信息保存在 Git 存储库之外

How to keep secret key information out of Git repository

我的存储库中有一些文件,其中一个包含 Adafruit 密钥。我想使用 Git 来存储我的存储库,但我不想发布密钥。

最好的保密方式是什么,而不必每次提交和推送内容时都将其清空?

根据您要实现的目标,您可以选择以下方法之一:

  • 将文件保留在由 git 管理的树中,但忽略它并在 gitignore
  • 中输入
  • 将文件内容保存在环境变量中,
  • 根本不要使用带密钥的文件,将密钥内容保存在别处(外部系统,如 hashicorp 的保险库、数据库、云(不确定,我不建议这样做)等)

第一种方法很简单,不需要太多工作,但是您仍然有将密钥传递到不同位置的问题,您可以在这些位置以安全的方式使用相同的存储库。第二种方法需要更多的工作,与第一种方法有相同的缺点。

第三个肯定比第一个和第二个需要更多的工作,但可能会导致一个真正安全的设置。

这在很大程度上取决于项目的要求

基本上,最好的安全策略是不在源代码控制系统中存储密钥、密码和一般的任何易受攻击的信息。如果它是目标,则有许多不同的方法:

  • "Supply" Runtime 中的此类信息并将其保存在其他地方:

    ./runMyApp.sh -db.password=

  • 使用专门的工具(例如 Vault by Hashicorp)来管理机密

  • 离线编码秘密值并将编码值存储在git中。如果没有用于解码的秘密密钥,则仅此编码值是无用的。在运行时再次解码该值,使用某种共享密钥/非对称密钥对,在这种情况下,您可以使用 public 密钥进行编码,使用私钥进行解码

I want to use Git to store my repository, but I don't want to publish the key.

对于像 秘密密钥 这样重要的东西,我会使用位于开发环境之外的专用密钥环基础设施,可选择与秘密密码相结合。

除此之外,我个人使用 submodules。查看 :

git submodule

特别是,我声明了一个全局 Git 存储库,我又在其中声明了另一个 Git 存储库,它将包含将要运行的实际项目 public。这使我们能够在顶层存储与给定项目相关的所有内容,但与它没有强制相关且不会发布的内容。例如,这可能是我所有的草稿、自动化脚本、工作笔记、项目规范、测试、错误报告等。

在这个工具提供的所有优势中,我们可以强调一个事实,即您可以将一个已经存在的存储库声明为子模块,这个存储库位于父存储库的内部或外部。

真正有趣的是,主存储库和子模块都保持不同的 Git 存储库,它们仍然可以独立配置。这意味着您不需要父存储库来配置其远程服务器。

这样做,无论您在哪里工作,您都可以获得版本控制系统的所有好处,同时仍然确保您自己永远不会意外地推出未存储在 public 子模块中的内容。

如果您没有太多要管理的机密,并且确实希望在版本控制中保留机密,我将父存储库设为私有。它包含 2 个文件夹 - 一个 secrets 文件夹和一个 git public 存储库的子模块(在另一个文件夹中)。我使用 ansible crypt 加密 secrets 文件夹中的任何内容,并使用 bash 脚本传递解密的内容,并将这些机密作为环境变量加载,以确保机密文件始终保持加密状态。

Ansible crypt 可以加密和解密一个环境变量,我将其包装在一个 bash 脚本中来执行这些功能,就像这样-

testsecret=$(echo 'this is a test secret' | ./scripts/ansible-encrypt.sh --vault-id $vault_key --encrypt)
result=$(./scripts/ansible-encrypt.sh --vault-id $vault_key --decrypt $testsecret)
echo $result

这里的testsecret是base64加密后的结果,可以安全的保存在一个文本文件中。稍后你可以获取该文件以将加密结果保存在内存中,最后当你需要使用这个秘密时,你可以解密它 ./scripts/ansible-encrypt.sh --vault-id $vault_key --decrypt $testsecret

上面引用的 bash 脚本如下 (ansible-encrypt.sh)。它以一种可以将加密变量存储在 base64 中的方式包装 ansible crypt 函数,从而解决了编码可能出现的一些问题。

#!/bin/bash

# This scripts encrypts an input hidden from the shell and base 64 encodes it so it can be stored as an environment variable
# Optionally can also decrypt an environment variable

vault_id_func () {
    if [[ "$verbose" == true ]]; then
        echo "Parsing vault_id_func option: '--${opt}', value: '${val}'" >&2;
    fi
    vault_key="${val}"
}
secret_name=secret
secret_name_func () {
    if [[ "$verbose" == true ]]; then
        echo "Parsing secret_name option: '--${opt}', value: '${val}'" >&2;
    fi
    secret_name="${val}"
}
decrypt=false
decrypt_func () {
    if [[ "$verbose" == true ]]; then
        echo "Parsing secret_name option: '--${opt}', value: '${val}'" >&2;
    fi
    decrypt=true
    encrypted_secret="${val}"
}

IFS='
'
optspec=":hv-:t:"

encrypt=false

parse_opts () {
    local OPTIND
    OPTIND=0
    while getopts "$optspec" optchar; do
        case "${optchar}" in
            -)
                case "${OPTARG}" in
                    vault-id)
                        val="${!OPTIND}"; OPTIND=$(( $OPTIND + 1 ))
                        opt="${OPTARG}"
                        vault_id_func
                        ;;
                    vault-id=*)
                        val=${OPTARG#*=}
                        opt=${OPTARG%=$val}
                        vault_id_func
                        ;;
                    secret-name)
                        val="${!OPTIND}"; OPTIND=$(( $OPTIND + 1 ))
                        opt="${OPTARG}"
                        secret_name_func
                        ;;
                    secret-name=*)
                        val=${OPTARG#*=}
                        opt=${OPTARG%=$val}
                        secret_name_func
                        ;;
                    decrypt)
                        val="${!OPTIND}"; OPTIND=$(( $OPTIND + 1 ))
                        opt="${OPTARG}"
                        decrypt_func
                        ;;
                    decrypt=*)
                        val=${OPTARG#*=}
                        opt=${OPTARG%=$val}
                        decrypt_func
                        ;;
                    encrypt)
                        encrypt=true
                        ;;
                    *)
                        if [ "$OPTERR" = 1 ] && [ "${optspec:0:1}" != ":" ]; then
                            echo "Unknown option --${OPTARG}" >&2
                        fi
                        ;;
                esac;;
            h)
                help
                ;;
            *)
                if [ "$OPTERR" != 1 ] || [ "${optspec:0:1}" = ":" ]; then
                    echo "Non-option argument: '-${OPTARG}'" >&2
                fi
                ;;
        esac
    done
}
parse_opts "$@"

if [[ "$encrypt" = true ]]; then
    read -s -p "Enter the string to encrypt: `echo $'\n> '`";
    secret=$(echo -n "$REPLY" | ansible-vault encrypt_string --vault-id $vault_key --stdin-name $secret_name | base64 -w 0)
    unset REPLY
    echo $secret
elif [[ "$decrypt" = true ]]; then
    result=$(echo $encrypted_secret | base64 -d | /snap/bin/yq r - "$secret_name" | ansible-vault decrypt --vault-id $vault_key)
    echo $result
else
    # if no arg is passed to encrypt or decrypt, then we a ssume the function will decrypt the firehawksecret env var
    encrypted_secret="${firehawksecret}"
    result=$(echo $encrypted_secret | base64 -d | /snap/bin/yq r - "$secret_name" | ansible-vault decrypt --vault-id $vault_key)
    echo $result
fi

将加密值存储为环境变量比在静止状态下解密并将明文结果留在内存中要安全得多。这对于任何进程来说都非常容易被虹吸掉。

如果您只想与他人共享代码而不是秘密,您可以使用 git 模板作为父级私有 repo 结构,以便其他人可以继承该结构,但使用他们自己的秘密。这也允许 CI 获取测试所需的一切。

或者,如果您不想在版本控制中使用您的秘密,您可以简单地使用 git 忽略将存放您的秘密的包含文件夹。

就我个人而言,这让我感到紧张,用户错误仍有可能导致 publicly 提交的秘密,因为这些文件仍在 public 存储库的根目录下,任何数量事情可能会出错,这种方法可能会令人尴尬。

对于 Django 添加另一个名为 secrets.py 的文件或任何你想要的文件以及另一个名为 .gitignore

的文件

在 .gitignore

中输入 secrets.py

将密钥粘贴到 secrets.py 文件中,然后使用

将其导入到设置文件中
from foldername.secrets import * 

这对我有用。