Skip to content

Commit df0afbf

Browse files
committed
[#2] Sign commit using GitPython but with pompt for passphrase
1 parent d69c5ef commit df0afbf

11 files changed

+145
-30
lines changed

.env.template

+4-1
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,5 @@
11
# https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/creating-a-personal-access-token
2-
INPUT_REPO_TOKEN=XXX
2+
INPUT_REPO_TOKEN=XX
3+
PASSPHRASE=XX
4+
KEYGRIP=XX
5+
GPG_PRIVATE_KEY=-----BEGIN PGP PRIVATE KEY BLOCK-----\n\nlXX\n-----END PGP PRIVATE KEY BLOCK-----\n

.gitignore

+2-1
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
1-
.env
1+
.env
2+
private_key.pgp

Dockerfile

+4-1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@ FROM python:3.9
1717
WORKDIR /app
1818
COPY --from=builder /opt/venv /opt/venv
1919
ENV PATH="/opt/venv/bin:$PATH"
20+
ENV GPG_TTY /dev/console
21+
COPY ./gpg-agent.conf /root/.gnupg/
2022
COPY ./src /app
2123
RUN rm -rf /app/test
22-
CMD ["python", "/app/src/main.py"]
24+
CMD ["/app/src/main.py"]
25+
ENTRYPOINT ["python"]

bin/run-01.sh

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,4 @@
33
docker run --rm -it \
44
--env-file .env \
55
--volume $(pwd):/app \
6-
pygithub python src/01_sign_commit_using_github_api.py
6+
pygithub src/01_sign_commit_using_github_api.py

bin/run-03.sh

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,4 @@
33
docker run --rm -it \
44
--env-file .env \
55
--volume $(pwd):/app \
6-
pygithub python src/03_sign_commit_using_the_gitpython_package.py
6+
pygithub src/03_sign_commit_using_the_gitpython_package.py

bin/run-04.sh

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,4 @@ docker run --rm -it \
44
--env-file .env \
55
--env INPUT_BRANCH=main \
66
--volume $(pwd):/app \
7-
pygithub python src/04_sign_commit_with_multiple_files_using_github_api.py
7+
pygithub src/04_sign_commit_with_multiple_files_using_github_api.py

bin/run-bash.sh

+2-1
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,5 @@
33
docker run --rm -it \
44
--env-file .env \
55
--volume $(pwd):/app \
6-
pygithub bash
6+
--entrypoint=/bin/bash \
7+
pygithub
28.5 KB
Loading

gpg-agent.conf

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
default-cache-ttl 7200
2+
max-cache-ttl 31536000
3+
allow-preset-passphrase
4+
allow-loopback-pinentry

requirements.txt

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
GitPython>=3.1.24
2-
PyGithub>=1.55
2+
PyGithub>=1.55
3+
python-gnupg>=0.4.7

src/03_sign_commit_using_the_gitpython_package.py

+124-22
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,40 @@
11
import os
2+
import pprint
23
import random
34

5+
import gnupg
46
from git import Actor, Repo
57

68

7-
def main():
8-
9+
def create_temp_dir():
910
# Create temp dir
1011
dir_name = random.getrandbits(128)
1112
temp_dir = f'/tmp/{dir_name}'
1213
print(f'Creating temp dir: {temp_dir}')
1314
os.mkdir(temp_dir)
15+
return temp_dir
16+
1417

18+
def git_init(temp_dir):
1519
print('Initializing repo ...')
1620
repo = Repo.init(temp_dir)
21+
return repo
22+
23+
24+
def execute_console_command(command):
25+
print(f'Executing command: {command}')
26+
output = os.popen(command).read()
27+
print(output)
1728

18-
#
19-
# Commit without signature
20-
#
2129

30+
def print_git_config_option(option):
31+
command = f'git config --get {option}'
32+
print(f'Executing command: {command}')
33+
output = os.popen(command).read()
34+
print(output)
35+
36+
37+
def commit_without_signning(temp_dir, repo):
2238
# Create file
2339
filename = "README.md"
2440
file_path = temp_dir + '/' + filename
@@ -41,10 +57,8 @@ def main():
4157
output = os.popen(command).read()
4258
print(output)
4359

44-
#
45-
# Commit with signature
46-
#
4760

61+
def signed_commit(temp_dir, repo, gpg_private_key, passphrase):
4862
# The GPG key:
4963
# sec rsa4096/27304EDD6079B81C 2021-11-19 [SC]
5064
# 88966A5B8C01BD04F3DA440427304EDD6079B81C
@@ -62,30 +76,118 @@ def main():
6276
index = repo.index
6377
index.add([file_path])
6478

65-
# List keys: gpg --list-secret-keys --keyid-format=long
66-
# Show public key: gpg --armor --export 5B6BDD35BEDFBF6F
67-
68-
# Commit with signature
79+
# gpg commands:
80+
# List keys: gpg --list-secret-keys --keyid-format=long
81+
# Show public key: gpg --armor --export 27304EDD6079B81C
82+
# Export private key: gpg --output private_key.pgp --armor --export-secret-key 27304EDD6079B81C
83+
# Export private key:
84+
# (for env var) gpg -a --export-secret-keys 88966A5B8C01BD04F3DA440427304EDD6079B81C | cat -e | sed 's/\$/\\n/g'
85+
# Import pgp key: echo -e $GPG_PRIVATE_KEY | gpg --import
86+
# Get keygrips: gpg --batch --with-colons --with-keygrip --list-secret-keys
87+
# gpg --with-keygrip --list-secret-keys
88+
89+
# Needed for commit with signature:
90+
# https://github.com/gitpython-developers/GitPython/issues/580#issuecomment-282474086
6991
index.write()
7092

71-
signingkey = '5B6BDD35BEDFBF6F'
93+
# TODO: get from console, gnupg package or env var
94+
signingkey = '27304EDD6079B81C'
95+
keygrip = '97D36F5B8F5BECDA8A1923FC00D11C7C438584F9'
96+
fingerprint = '88966A5B8C01BD04F3DA440427304EDD6079B81C'
97+
hex_passphrase = passphrase.encode('utf-8').hex().upper()
98+
99+
# git config --global user.name "A committer"
72100
repo.config_writer().set_value("user", "name", "A committer").release()
101+
# git config --global user.email "committer@example.com"
73102
repo.config_writer().set_value("user", "email", "committer@example.com").release()
74103
repo.config_writer().set_value("user", "signingkey", signingkey).release()
75104
repo.config_writer().set_value("commit", "gpgsign", "true").release()
76105

77-
# TODO: set up private key from env varaible like "crazy-max/ghaction-import-gpg" action
78-
79-
repo.git.commit('-S', f'--gpg-sign={signingkey}', '-m', 'my commit message 2',
80-
author='A committer <committer@example.com>')
106+
# Debug: git Python package does not write global options on the root git config file
107+
# print_git_config_option('user.name')
108+
# print_git_config_option('user.email')
109+
# print_git_config_option('user.signingkey')
110+
# print_git_config_option('commit.gpgsign')
111+
112+
gpg = gnupg.GPG(gnupghome='/root/.gnupg', verbose=True, use_agent=True)
113+
114+
# Import private key
115+
result = gpg.import_keys(gpg_private_key, passphrase=passphrase)
116+
# print(gpg_private_key, passphrase)
117+
pprint.pprint(result)
118+
119+
# Print gpg keys using Python package
120+
# keys = gpg.list_keys()
121+
# pprint.pprint(keys)
122+
keys = gpg.list_keys(True)
123+
# pprint.pprint(keys)
124+
125+
# Print gpg keys using console command
126+
execute_console_command('gpg --list-secret-keys --keyid-format=long')
127+
# Print GPG agent conf
128+
execute_console_command('cat /root/.gnupg/gpg-agent.conf')
129+
# Print global git config. With docker we use root user and it does not have a .gitconfig file
130+
execute_console_command('cat ~/.gitconfig')
131+
132+
# Reload PGP agent
133+
# reload_pgp_agent_command = 'gpg-connect-agent reloadagent /bye'
134+
# execute_console_command(reload_pgp_agent_command)
135+
#execute_console_command('gpg-agent --daemon --allow-preset-passphrase')
136+
137+
#reload_agent_command = f'gpg-connect-agent \'RELOADAGENT\' /bye'
138+
# execute_console_command(reload_agent_command)
139+
140+
gpg_agente_config = 'gpg-agent --gpgconf-list'
141+
execute_console_command(gpg_agente_config)
142+
143+
# Preset passphrase using gpg-preset-passphrase:
144+
# https://www.gnupg.org/documentation/manuals/gnupg/Invoking-gpg_002dpreset_002dpassphrase.html#Invoking-gpg_002dpreset_002dpassphrase
145+
# This command makes git not prompt for passphrase for GPG key. We preset the PGP agent with the passphrase
146+
# preset_passphrase_command = f'echo \'{passphrase}\' | /usr/lib/gnupg2/gpg-preset-passphrase -v --preset {keygrip}'
147+
# execute_console_command(preset_passphrase_command)
148+
149+
# Preset passphrase using gpg-connect-agent:
150+
# https://www.gnupg.org/documentation/manuals/gnupg/Agent-PRESET_005fPASSPHRASE.html#Agent-PRESET_005fPASSPHRASE
151+
# https://github.com/crazy-max/ghaction-import-gpg/blob/60f6f3e9a98263cc2c51ebe1f9babe82ded3f0ba/src/gpg.ts#L170-L174
152+
preset_passphrase_command = f'gpg-connect-agent \'PRESET_PASSPHRASE {keygrip} -1 {hex_passphrase}\' /bye'
153+
execute_console_command(preset_passphrase_command)
154+
155+
# If we want ot ask the user for the passphrase (popup)
156+
# https://www.gnupg.org/documentation/manuals/gnupg/Agent-GET_005fPASSPHRASE.html#Agent-GET_005fPASSPHRASE
157+
# error = 'error'
158+
# prompt = 'prompt'
159+
# desc = 'desc'
160+
# get_passphrase_command = f'gpg-connect-agent \'GET_PASSPHRASE {keygrip} error prompt desc\' /bye'
161+
# execute_console_command(get_passphrase_command)
162+
163+
show_key_info_command = f"gpg-connect-agent 'KEYINFO {keygrip}' /bye"
164+
execute_console_command(show_key_info_command)
165+
166+
repo.git.commit('-S', f'--gpg-sign={signingkey}', '-m', '"my commit message 2"',
167+
author='"A committer <committer@example.com>"')
81168

82169
# Print commit info
83-
command = f'cd {temp_dir} && git log'
84-
print(f'Executing command: {command}')
85-
output = os.popen(command).read()
86-
print(output)
170+
execute_console_command(f'cd {temp_dir} && git log --show-signature')
171+
172+
173+
def main(gpg_private_key, passphrase):
174+
temp_dir = create_temp_dir()
175+
repo = git_init(temp_dir)
176+
177+
print("COMMIT WITHOUT SIGNATURE")
178+
print("------------------------")
179+
commit_without_signning(temp_dir, repo)
180+
181+
print("COMMIT WITH SIGNATURE")
182+
print("---------------------")
183+
signed_commit(temp_dir, repo, gpg_private_key, passphrase)
87184

88185

89186
if __name__ == "__main__":
90187
# https://gitpython.readthedocs.io/
91-
main()
188+
189+
# Get environment variables
190+
gpg_private_key = os.getenv('GPG_PRIVATE_KEY').replace(r'\n', '\n')
191+
passphrase = os.environ.get('PASSPHRASE')
192+
193+
main(gpg_private_key, passphrase)

0 commit comments

Comments
 (0)