git-secret:在 Git 儲存庫中加密和儲存金鑰(下)

2022-10-21 15:02:02

在之前的文章中(點選此處檢視上一篇文章),我們瞭解瞭如何識別包含金鑰的檔案,將金鑰新增到 .gitignore ,通過 git-secret 進行加密,以及將加密檔案提交到儲存庫。在本篇文章中,將帶你瞭解如何在 Docker 容器中設定 git-secretgpg,通過 Makefile recipe 為不同的場景建立工作流。

Makefile Adjustment

git-secretgpg 指令新增到 Makefile 中 .make/01-00-application-setup.mk

# File: .make/01-00-application-setup.mk

#...

# gpg

DEFAULT_SECRET_GPG_KEY?=secret.gpg
DEFAULT_PUBLIC_GPG_KEYS?=.dev/gpg-keys/*

.PHONY: gpg
gpg: ## Run gpg commands. Specify the command e.g. via ARGS="--list-keys"
    $(EXECUTE_IN_APPLICATION_CONTAINER) gpg $(ARGS)

.PHONY: gpg-export-public-key
gpg-export-public-key: ## Export a gpg public key e.g. via EMAIL="[email protected]" PATH=".dev/gpg-keys/john-public.gpg"
    @$(if $(PATH),,$(error PATH is undefined))
    @$(if $(EMAIL),,$(error EMAIL is undefined))
    "$(MAKE)" -s gpg ARGS="gpg --armor --export $(EMAIL) > $(PATH)"

.PHONY: gpg-export-private-key
gpg-export-private-key: ## Export a gpg private key e.g. via EMAIL="[email protected]" PATH="secret.gpg"
    @$(if $(PATH),,$(error PATH is undefined))
    @$(if $(EMAIL),,$(error EMAIL is undefined))
    "$(MAKE)" -s gpg ARGS="--output $(PATH) --armor --export-secret-key $(EMAIL)"

.PHONY: gpg-import
gpg-import: ## Import a gpg key file e.g. via GPG_KEY_FILES="/path/to/file /path/to/file2"
    @$(if $(GPG_KEY_FILES),,$(error GPG_KEY_FILES is undefined))
    "$(MAKE)" -s gpg ARGS="--import --batch --yes --pinentry-mode loopback $(GPG_KEY_FILES)"

.PHONY: gpg-import-default-secret-key
gpg-import-default-secret-key: ## Import the default secret key
    "$(MAKE)" -s gpg-import GPG_KEY_FILES="$(DEFAULT_SECRET_GPG_KEY)"

.PHONY: gpg-import-default-public-keys
gpg-import-default-public-keys: ## Import the default public keys
    "$(MAKE)" -s gpg-import GPG_KEY_FILES="$(DEFAULT_PUBLIC_GPG_KEYS)" 

.PHONY: gpg-init
gpg-init: gpg-import-default-secret-key gpg-import-default-public-keys ## Initialize gpg in the container, i.e. import all public and private keys

# git-secret

.PHONY: git-secret
git-secret: ## Run git-secret commands. Specify the command e.g. via ARGS="hide"
    $(EXECUTE_IN_APPLICATION_CONTAINER) git-secret $(ARGS)

.PHONY: secret-init
secret-init: ## Initialize git-secret in the repository via `git-secret init`
    "$(MAKE)" -s git-secret ARGS="init"

.PHONY: secret-init-gpg-socket-config
secret-init-gpg-socket-config: ## Initialize the config files to change the gpg socket locations
    echo "%Assuan%" > .gitsecret/keys/S.gpg-agent
    echo "socket=/tmp/S.gpg-agent" >> .gitsecret/keys/S.gpg-agent
    echo "%Assuan%" > .gitsecret/keys/S.gpg-agent.ssh
    echo "socket=/tmp/S.gpg-agent.ssh" >> .gitsecret/keys/S.gpg-agent.ssh
    echo "extra-socket /tmp/S.gpg-agent.extra" > .gitsecret/keys/gpg-agent.conf
    echo "browser-socket /tmp/S.gpg-agent.browser" >> .gitsecret/keys/gpg-agent.conf

.PHONY: secret-encrypt
secret-encrypt: ## Decrypt secret files via `git-secret hide`
    "$(MAKE)" -s git-secret ARGS="hide"

.PHONY: secret-decrypt
secret-decrypt: ## Decrypt secret files via `git-secret reveal -f`
    "$(MAKE)" -s git-secret ARGS="reveal -f" 

.PHONY: secret-decrypt-with-password
secret-decrypt-with-password: ## Decrypt secret files using a password for gpg via `git-secret reveal -f -p $(GPG_PASSWORD)`
    @$(if $(GPG_PASSWORD),,$(error GPG_PASSWORD is undefined))
    "$(MAKE)" -s git-secret ARGS="reveal -f -p $(GPG_PASSWORD)" 

.PHONY: secret-add
secret-add: ## Add a file to git secret via `git-secret add $FILE`
    @$(if $(FILE),,$(error FILE is undefined))
    "$(MAKE)" -s git-secret ARGS="add $(FILE)"

.PHONY: secret-cat
secret-cat: ## Show the contents of file to git secret via `git-secret cat $FILE`
    @$(if $(FILE),,$(error FILE is undefined))
    "$(MAKE)" -s git-secret ARGS="cat $(FILE)"

.PHONY: secret-list
secret-list: ## List all files added to git secret `git-secret list`
    "$(MAKE)" -s git-secret ARGS="list"

.PHONY: secret-remove
secret-remove: ## Remove a file from git secret via `git-secret remove $FILE`
    @$(if $(FILE),,$(error FILE is undefined))
    "$(MAKE)" -s git-secret ARGS="remove $(FILE)"

.PHONY: secret-add-user
secret-add-user: ## Remove a user from git secret via `git-secret tell $EMAIL`
    @$(if $(EMAIL),,$(error EMAIL is undefined))
    "$(MAKE)" -s git-secret ARGS="tell $(EMAIL)"

.PHONY: secret-show-users
secret-show-users: ## Show all users that have access to git secret via `git-secret whoknows`
    "$(MAKE)" -s git-secret ARGS="whoknows"

.PHONY: secret-remove-user
secret-remove-user: ## Remove a user from git secret via `git-secret killperson $EMAIL`
    @$(if $(EMAIL),,$(error EMAIL is undefined))
    "$(MAKE)" -s git-secret ARGS="killperson $(EMAIL)"

.PHONY: secret-diff
secret-diff: ## Show the diff between the content of encrypted and decrypted files via `git-secret changes`
    "$(MAKE)" -s git-secret ARGS="changes"

工作流程

使用 git-secret 非常簡單:

  • 初始化 git-secret
  • 新增所有使用者。
  • 新增所有機密檔案並確保這些檔案通過 .gitignore 被忽略。
  • 加密檔案。
  • 如果團隊其他成員對檔案進行更改,則需要解密檔案→更新檔案→再次提交加密檔案
  • 如果對解密的檔案進行更改,修改完需要再次重新進行加密。

下面的「流程挑戰」部分展示了一些在可能遇到的問題,「場景」部分將會展示一些常見場景的具體範例。

流程中的挑戰

從流程的角度,一起來看看在過程中可能遇到的一些困難和挑戰,以及如何處理解決。

更新機密

更新機密時,請確保先解密檔案,從而避免使用可能仍存在原生的舊檔案。可以通過檢查最新的 main 分支並執行 git secret reveal,來獲得最新版本的機密檔案。也可以使用 post-merge Git hook 自動執行此操作,不過要注意覆蓋本地機密檔案的風險哦。

程式碼審查和合並衝突

由於無法對加密檔案進行很好的區分,因此當涉及機密時程式碼審查變得更加困難。這是可以嘗試使用 GitLab 進行審查,首先檢查 .gitsecret/paths/mapping.cfg 檔案的差異,在 UI 中檢視哪些檔案已更改。

此外,可以根據以下步驟來檢視:

  • 檢查 main 分支。

  • 通過 git secret reveal -f 解密檔案

  • 檢視 feature-branch.

  • 執行 git secret changes 來檢視 main 的解密檔案和feature-branch 中加密檔案之間的差異。

當多個團隊成員需要同時修改不同分支上的機密檔案時,情況會更加複雜一些,因為Git 無法智慧處理增量更新。

本地 git-secret 和 gpg 設定

當團隊的所有人員將 git-secret 安裝在本地,並且使用他們自己的 gpg 金鑰,這也意味著團隊的成本會隨之增加,原因如下:

  • 新加入開發團隊的人員需要:
    • 本地安裝 git-secret(*)
    • 在本地安裝和設定 gpg(*)
    • 建立 gpg 金鑰對
  • 必須由所有其他團隊成員 (*) 新增公鑰。
  • 必須通過新增金鑰的使用者 git secret tell
  • 機密需要重新加密。

對於離開團隊的人員:

  • 所有其他團隊成員(*) 都需要刪除公鑰。
  • 通過 git secret killperson 刪除金鑰的使用者。
  • 機密需要重新加密。

另外,需要確保 git-secretgpg 版本保持最新,避免遇到任何相容性問題。作為替代方案,也可以通過 Docker 處理,而上述步驟中標註(*) 則可以省去,也就是不需要設定原生的 git-secretgpg

為了更加便捷,將儲存庫中每個開發人員的公共 gpg 金鑰放在 .dev/gpg-keys/,而私鑰命名為 secret.gpg 並放在程式碼庫的根目錄中。

在此設定中,secret.gpg 還必須被新增到 .gitignore 檔案中。

# File: .gitignore
#...
vendor/
secret.gpg

然後可以使用 make 目標簡化匯入:

# gpg

DEFAULT_SECRET_GPG_KEY?=secret.gpg
DEFAULT_PUBLIC_GPG_KEYS?=.dev/gpg-keys/*

.PHONY: gpg
gpg: ## Run gpg commands. Specify the command e.g. via ARGS="--list-keys"
    $(EXECUTE_IN_APPLICATION_CONTAINER) gpg $(ARGS)

.PHONY: gpg-import
gpg-import: ## Import a gpg key file e.g. via GPG_KEY_FILES="/path/to/file /path/to/file2"
    @$(if $(GPG_KEY_FILES),,$(error GPG_KEY_FILES is undefined))
    "$(MAKE)" -s gpg ARGS="--import --batch --yes --pinentry-mode loopback $(GPG_KEY_FILES)"

.PHONY: gpg-import-default-secret-key
gpg-import-default-secret-key: ## Import the default secret key
    "$(MAKE)" -s gpg-import GPG_KEY_FILES="$(DEFAULT_SECRET_GPG_KEY)"

.PHONY: gpg-import-default-public-keys
gpg-import-default-public-keys: ## Import the default public keys
    "$(MAKE)" -s gpg-import GPG_KEY_FILES="$(DEFAULT_PUBLIC_GPG_KEYS)" 

.PHONY: gpg-init
gpg-init: gpg-import-default-secret-key gpg-import-default-public-keys ## Initialize gpg in the container, i.e. import all public and private keys

上述操作需要在容器啟動後執行一次。

場景

先假設以下這些條件:

  • 已檢查過 Git 儲存庫。git checkout part-6-git-secret-encrypt-repository-docker

  • 沒有正在執行的 Docker 容器。make docker-down

  • 已刪除現有 git-secret 資料夾、中的金鑰.dev/gpg-keyssecret.gpg金鑰和 passwords.*檔案。rm -rf .gitsecret/ .dev/gpg-keys/* secret.gpg passwords.*

gpg 金鑰的初始設定

不幸的是,我沒有找到通過makedocker建立和匯出gpg金鑰的方法。你需要互動式地執行這些命令,或者傳遞一個帶換行的字串給它。這兩件事在makedocker中都複雜得可怕。因此,你需要登入到應用程式的容器中,並在那裡直接執行這些命令。這不是很簡單,但無論如何,這隻需要在一個新的開發人員入職時做一次。

金鑰匯出到 secret.gpg,公鑰匯出到 gp.dev/gpg-keys/alice-public.gpg

# start the docker setup
make docker-up

# log into the container ('winpty' is only required on Windows)
winpty docker exec -ti dofroscra_local-application-1 bash

# export key pair
name="Alice Doe"
email="[email protected]"
gpg --batch --gen-key < .dev/gpg-keys/alice-public.gpg
$ make docker-up
ENV=local TAG=latest DOCKER_REGISTRY=docker.io DOCKER_NAMESPACE=dofroscra APP_USER_NAME=application APP_GROUP_NAME=application docker compose -p dofroscra_local --env-file ./.docker/.env -f ./.docker/docker-compose/docker-compose.yml -f ./.docker/docker-compose/docker-compose.local.yml up -d
Container dofroscra_local-application-1  Created
...
Container dofroscra_local-application-1  Started
$ docker ps
CONTAINER ID   IMAGE                                COMMAND                  CREATED          STATUS          PORTS                NAMES
...
95f740607586   dofroscra/application-local:latest   "/usr/sbin/sshd -D"      21 minutes ago   Up 21 minutes   0.0.0.0:2222->22/tcp dofroscra_local-application-1

$ winpty docker exec -ti dofroscra_local-application-1 bash
root:/var/www/app# name="Alice Doe"
root:/var/www/app# email="[email protected]"
gpg --batch --gen-key < Key-Type: 1
> Key-Length: 2048
> Subkey-Type: 1
> Subkey-Length: 2048
> Name-Real: $name
> Name-Email: $email
> Expire-Date: 0
> %no-protection
> EOF
gpg: directory '/root/.gnupg' created
gpg: keybox '/root/.gnupg/pubring.kbx' created
gpg: /root/.gnupg/trustdb.gpg: trustdb created
gpg: key BBBE654440E720C1 marked as ultimately trusted
gpg: directory '/root/.gnupg/openpgp-revocs.d' created
gpg: revocation certificate stored as '/root/.gnupg/openpgp-revocs.d/225C736E0E70AC222C072B70BBBE654440E720C1.rev'

root:/var/www/app# gpg --output secret.gpg --armor --export-secret-key $email
root:/var/www/app# head secret.gpg
-----BEGIN PGP PRIVATE KEY BLOCK-----

lQOYBGJD+bwBCADBGKySV5PINc5MmQB3PNvCG7Oa1VMBO8XJdivIOSw7ykv55PRP
3g3R+ERd1Ss5gd5KAxLc1tt6PHGSPTypUJjCng2plwD8Jy5A/cC6o2x8yubOslLa
x1EC9fpcxUYUNXZavtEr+ylOaTaRz6qwSabsAgkg2NZ0ey/QKmFOZvhL8NlK9lTI
GgZPTiqPCsr7hiNg0WRbT5h8nTmfpl/DdTgwfPsDn5Hn0TEMa79WsrPnnq16jsq0
Uusuw3tOmdSdYnT8j7m1cpgcSj0hRF1eh4GVE0o62GqeLTWW9mfpcuv7n6mWaCB8
DCH6H238gwUriq/aboegcuBktlvSY21q/MIXABEBAAEAB/wK/M2buX+vavRgDRgR
hjUrsJTXO3VGLYcIetYXRhLmHLxBriKtcBa8OxLKKL5AFEuNourOBdcmTPiEwuxH
5s39IQOTrK6B1UmUqXvFLasXghorv8o8KGRL4ABM4Bgn6o+KBAVLVIwvVIhQ4rlf

root:/var/www/app# gpg --armor --export $email > .dev/gpg-keys/alice-public.gpg
root:/var/www/app# head .dev/gpg-keys/alice-public.gpg
-----BEGIN PGP PUBLIC KEY BLOCK-----

mQENBGJD+bwBCADBGKySV5PINc5MmQB3PNvCG7Oa1VMBO8XJdivIOSw7ykv55PRP
3g3R+ERd1Ss5gd5KAxLc1tt6PHGSPTypUJjCng2plwD8Jy5A/cC6o2x8yubOslLa
x1EC9fpcxUYUNXZavtEr+ylOaTaRz6qwSabsAgkg2NZ0ey/QKmFOZvhL8NlK9lTI
GgZPTiqPCsr7hiNg0WRbT5h8nTmfpl/DdTgwfPsDn5Hn0TEMa79WsrPnnq16jsq0
Uusuw3tOmdSdYnT8j7m1cpgcSj0hRF1eh4GVE0o62GqeLTWW9mfpcuv7n6mWaCB8
DCH6H238gwUriq/aboegcuBktlvSY21q/MIXABEBAAG0HUFsaWNlIERvZSA8YWxp
Y2VAZXhhbXBsZS5jb20+iQFOBBMBCgA4FiEEIlxzbg5wrCIsBytwu75lREDnIMEF
AmJD+bwCGy8FCwkIBwIGFQoJCAsCBBYCAwECHgECF4AACgkQu75lREDnIMEN4Af+

至此 [email protected] 就有了一個新機密和私鑰,將其匯出到 secret.gpg.dev/gpg-keys/alice-public.gpg。剩下的命令現在可以直接在application 容器外的主機上執行。

git-secret 的初始設定

現在來將 git-secret 引入一個新的程式碼庫,然後執行以下命令。

初始化 git-secret

make secret-init$ make secret-init
"C:/Program Files/Git/mingw64/bin/make" -s git-secret ARGS="init";
git-secret: init created: '/var/www/app/.gitsecret/'

應用 gpg 對共用目錄進行修復:

$ make secret-init-gpg-socket-config$ make secret-init-gpg-socket-config
echo "%Assuan%" > .gitsecret/keys/S.gpg-agent
echo "socket=/tmp/S.gpg-agent" >> .gitsecret/keys/S.gpg-agent
echo "%Assuan%" > .gitsecret/keys/S.gpg-agent.ssh
echo "socket=/tmp/S.gpg-agent.ssh" >> .gitsecret/keys/S.gpg-agent.ssh
echo "extra-socket /tmp/S.gpg-agent.extra" > .gitsecret/keys/gpg-agent.conf
echo "browser-socket /tmp/S.gpg-agent.browser" >> .gitsecret/keys/gpg-agent.conf

容器啟動後初始化 gpg

重啟容器後,需要初始化 gpg 也就是匯入公鑰 .dev/gpg-keys/* 和匯入私鑰 Secret.gpg,不然就無法對檔案進行加密和解密。

make gpg-init$ make gpg-init
"C:/Program Files/Git/mingw64/bin/make" -s gpg-import GPG_KEY_FILES="secret.gpg"
gpg: directory '/home/application/.gnupg' created
gpg: keybox '/home/application/.gnupg/pubring.kbx' created
gpg: /home/application/.gnupg/trustdb.gpg: trustdb created
gpg: key BBBE654440E720C1: public key "Alice Doe <[email protected]>" imported
gpg: key BBBE654440E720C1: secret key imported
gpg: Total number processed: 1
gpg:               imported: 1
gpg:       secret keys read: 1
gpg:   secret keys imported: 1
"C:/Program Files/Git/mingw64/bin/make" -s gpg-import GPG_KEY_FILES=".dev/gpg-keys/*"
gpg: key BBBE654440E720C1: "Alice Doe <[email protected]>" not changed
gpg: Total number processed: 1
gpg:              unchanged: 1

新增新成員

接下來一起看看如何將新成員加入到 git-secret

make secret-add-user EMAIL="[email protected]"$ make secret-add-user EMAIL="[email protected]"
"C:/Program Files/Git/mingw64/bin/make" -s git-secret ARGS="tell [email protected]"
git-secret: done. [email protected] added as user(s) who know the secret.

驗證是否通過:

make secret-show-users$ make secret-show-users
"C:/Program Files/Git/mingw64/bin/make" -s git-secret ARGS="whoknows"
[email protected]

新增和加密檔案

來新增一個新的加密檔案 secret_password.txt,建立以下檔案:

echo "my_new_secret_password" > secret_password.txt

將其新增到 .gitignore

echo "secret_password.txt" >> .gitignore

將其新增到 git-secret

make secret-add FILE="secret_password.txt"$ make secret-add FILE="secret_password.txt"
"C:/Program Files/Git/mingw64/bin/make" -s git-secret ARGS="add secret_password.txt"
git-secret: 1 item(s) added.

加密所有檔案:

make secret-encrypt$ make secret-encrypt
"C:/Program Files/Git/mingw64/bin/make" -s git-secret ARGS="hide"
git-secret: done. 1 of 1 files are hidden.$ ls secret_password.txt.secret
secret_password.txt.secret

解密檔案

首先移除 secret_password.txt檔案,請執行:

rm secret_password.txt$ rm secret_password.txt$ ls secret_password.txt
ls: cannot access 'secret_password.txt': No such file or directory

然後進行解密:

make secret-decrypt$ make secret-decrypt
"C:/Program Files/Git/mingw64/bin/make" -s git-secret ARGS="reveal -f"
git-secret: done. 1 of 1 files are revealed.$ cat secret_password.txt
my_new_secret_password

注意:如果 gpg 金鑰受密碼保護(假設密碼是 123456),請執行以下命令:

make secret-decrypt-with-password GPG_PASSWORD=123456

此外,還可以將 GPG_PASSWORD 變數加入.make/.env 檔案作為本地預設值,這樣就不用每次都指定該值,然後可以簡單地執行以下命令而不傳遞 GPG_PASSWORD

make secret-decrypt-with-password

刪除檔案

可以通過以下方式解密檔案:移除之前新增的 secret-password.txt

make secret-remove FILE="secret_password.txt"$ make secret-remove FILE="secret_password.txt"
"C:/Program Files/Git/mingw64/bin/make" -s git-secret ARGS="remove secret_password.txt"
git-secret: removed from index.
git-secret: ensure that files: [secret_password.txt] are now not ignored.

注意:這裡既不會自動刪除 secret_password.txt 檔案,也不會自動刪除 secret_password.txt.secret 檔案

$ ls -l | grep secret_password.txt 
-rw-r--r-- 1 Pascal 197121 3 月 31 日 19 日 14:03 secret_password.txt 
-rw-r--r-- 1 Pascal 197121 358 3 月 31 日 14:02 secret_password.txt.secret

即使加密的 secret_password.txt 檔案仍然存在,也不會被解密:

$ make secret-decrypt
"C:/Program Files/Git/mingw64/bin/make" -s git-secret ARGS="reveal -f"
git-secret: done. 0 of 0 files are revealed.

移除團隊成員

移除團隊成員需要通過以下步驟:

make secret-remove-user EMAIL="[email protected]"$ make secret-remove-user EMAIL="[email protected]"
"C:/Program Files/Git/mingw64/bin/make" -s git-secret ARGS="killperson [email protected]"
git-secret: removed keys.
git-secret: now [[email protected]] do not have an access to the repository.
git-secret: make sure to hide the existing secrets again.

如果團隊中還有其他成員留下,需要確保再次加密機密檔案:

make secret-encrypt

如果該組已移除全部成員,git-secret 就會報錯:

$ make secret-decrypt
"C:/Program Files/Git/mingw64/bin/make" -s git-secret ARGS="reveal -f"
git-secret: abort: no public keys for users found. run 'git secret tell email@address'.
make[1]: *** [.make/01-00-application-setup.mk:57: git-secret] Error 1
make: *** [.make/01-00-application-setup.mk:69: secret-decrypt] Error 2

恭喜你~現在你可以加密和解密機密檔案,並儲存在 Git 儲存庫中啦!