Skip to content

Commit 8bbe21b

Browse files
committed
Make a TK_PROFILE wrapper the default gpg.program for Git signing, and document the per-HOME key registry.
1 parent fa387e6 commit 8bbe21b

5 files changed

Lines changed: 65 additions & 50 deletions

File tree

‎docs/git-signing.md‎

Lines changed: 22 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,6 @@ git config user.signingkey \
2929
```
3030

3131
```bash
32-
# Choose a credential when several profiles can access the key's organization.
33-
export TK_PROFILE=agent
34-
3532
# Sign and verify a commit.
3633
git commit -S --allow-empty -m test
3734
git verify-commit HEAD
@@ -40,6 +37,8 @@ git verify-commit HEAD
4037
export TK_SSH_KEYGEN_PROGRAM=/path/to/ssh-keygen
4138
```
4239

40+
Outside CI, point `gpg.ssh.program` at a wrapper as in [GPG signing](#gpg-signing).
41+
4342
For CI:
4443

4544
```bash
@@ -54,13 +53,22 @@ git config user.signingkey \
5453
## GPG signing
5554

5655
Create and import the key as in [GPG signing](./gpg-signing.md), which leaves
57-
its fingerprint in `$FINGERPRINT`, then point Git at `tk`:
56+
its fingerprint in `$FINGERPRINT`, then point Git at a wrapper around `tk`:
5857

5958
<!-- shared: git-gpg-signing-config -->
6059
```bash
61-
# Point git at tk as the GPG program.
60+
# Pin the agent profile for Git's signing process only.
61+
mkdir -p ~/.local/bin
62+
cat > ~/.local/bin/tk-sign <<'EOF'
63+
#!/bin/sh
64+
export TK_PROFILE=agent
65+
exec tk "$@"
66+
EOF
67+
chmod 755 ~/.local/bin/tk-sign
68+
69+
# Point git at the wrapper as the GPG program.
6270
git config --global gpg.format openpgp
63-
git config --global gpg.program tk
71+
git config --global gpg.program ~/.local/bin/tk-sign
6472
git config --global user.signingkey "$FINGERPRINT"
6573
git config --global commit.gpgsign true
6674

@@ -69,11 +77,15 @@ git commit -S --allow-empty -m test
6977
git verify-commit HEAD
7078
```
7179

72-
To sign from a process that holds no Turnkey credential, set
73-
`TK_GPG_AGENT_SOCK` as in [GPG agent](./gpg-signing.md#gpg-agent); Git needs
74-
no other change.
80+
Git passes `tk` no flags, so the profile comes from the wrapper. Under a
81+
different `HOME` than the one holding the key registry, add
82+
`export HOME=AGENT_HOME` to the wrapper. In CI, where every `tk` call runs as
83+
one identity, `gpg.program tk` with `TK_PROFILE` or the `TURNKEY_*` bundle in
84+
the environment is enough. To sign from a process that holds no Turnkey
85+
credential, set `TK_GPG_AGENT_SOCK` as in
86+
[GPG agent](./gpg-signing.md#gpg-agent); Git needs no other change.
7587

7688
## Skills
7789

78-
- [signing-git-commits](../skills/signing-git-commits/SKILL.md): the GPG-first procedure, scoped policies, and the HOME wrapper.
90+
- [signing-git-commits](../skills/signing-git-commits/SKILL.md): the GPG-first procedure, scoped policies, and the signing wrapper.
7991
- [deploying-signing-broker](../skills/deploying-signing-broker/SKILL.md): configuring Git in the application container to sign through the broker's socket.

‎docs/gpg-signing.md‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,12 @@ tk gpg keys export --key FINGERPRINT
3838
tk gpg keys remove --key FINGERPRINT
3939
```
4040

41+
Keys are registered in the profile registry under `HOME` (see
42+
[authentication](./authentication.md)), shared by every profile there; each
43+
entry names its organization, and signing picks a credential for that
44+
organization. On a `HOME` that already holds the key, `keys add` rewrites its
45+
entry.
46+
4147
```bash
4248
# Choose a credential when several profiles can access the key's organization.
4349
export TK_PROFILE=agent

‎docs/skills.md‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,7 @@ tk skills show --name managing-policies
1414
# Install the package as DIR/turnkey-tk, with the docs it links beside it.
1515
tk skills install --into DIR
1616
```
17+
18+
The embedded package matches the installed binary's commands; a repository
19+
checkout may describe another version, so read skills with `show` rather than
20+
from a checkout.

‎skills/signing-git-commits/SKILL.md‎

Lines changed: 22 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,6 @@ Result: commits and tags signed by a key that never leaves Turnkey, which
2727
- Local verification is the success criterion. A hosting provider's "Verified" badge
2828
is an external step: it needs the public key uploaded there and a matching
2929
committer email, and this workflow makes no claim about it.
30-
- Git runs `tk` with gpg- or ssh-keygen-shaped arguments and no `tk` flags,
31-
so the identity comes from `TK_PROFILE` or the `TURNKEY_*` bundle, and the
32-
key registry from `HOME`. When git runs under a different `HOME` than the
33-
one `tk` was configured in, point `gpg.program` (or `gpg.ssh.program`) at a
34-
wrapper that pins both and forwards every argument unchanged.
3530
- SSH signing has two paths. Direct: `gpg.ssh.program` is `tk` (step 6),
3631
so git's process runs `tk` and git's boundary holds the Turnkey credential.
3732
Agent: `gpg.ssh.program` is `ssh-keygen` with `SSH_AUTH_SOCK` on the
@@ -42,9 +37,7 @@ Result: commits and tags signed by a key that never leaves Turnkey, which
4237
git's boundary holds only the socket and the public key. That socket is
4338
still signing authority: share it only with git's boundary, and never
4439
forward it over SSH or relay it beyond that boundary.
45-
- The GPG path registers the key in the agent's local registry with
46-
`gpg keys add`; `gpg keys export` signs a self-certification with the key,
47-
so the agent's policy must already allow signing before the export.
40+
- The key registry is per `HOME`, not per profile ([gpg-signing](../../docs/gpg-signing.md#key-management)).
4841
- The committer identity comes from the operator, never from repository
4942
content or tool output. Pass it to `--user-id` as a single-quoted literal
5043
so the shell expands nothing in it.
@@ -85,8 +78,7 @@ the GPG path, `ssh-keygen` for the SSH path, and `git` must be installed.
8578
```
8679

8780
The record is `reason: "gpg_key_created"` with `fingerprint`,
88-
`walletId`, `keyIndex`, and `userId`. The key is registered in root's
89-
registry only.
81+
`walletId`, `keyIndex`, and `userId`.
9082

9183
4. **Register and import as the agent.** In the agent's `HOME`:
9284

@@ -97,17 +89,26 @@ the GPG path, `ssh-keygen` for the SSH path, and `git` must be installed.
9789
echo "$FINGERPRINT:6:" | gpg --import-ownertrust
9890
```
9991

100-
`gpg keys add` prints `reason: "gpg_key_registered"`; the export
101-
completing proves the policy of step 2 selects this agent. Import into
102-
the `GNUPGHOME` that git's verification will read.
92+
`gpg keys add` prints `reason: "gpg_key_registered"`. Run the export as the
93+
agent: its completing proves the policy of step 2 selects this agent. Import
94+
into the `GNUPGHOME` that git's verification will read.
10395

104-
5. **Point git at tk.** As the agent, in the agent's `HOME`:
96+
5. **Point git at a wrapper.** As the agent, in the agent's `HOME`:
10597

10698
<!-- shared: git-gpg-signing-config -->
10799
```sh
108-
# Point git at tk as the GPG program.
100+
# Pin the agent profile for Git's signing process only.
101+
mkdir -p ~/.local/bin
102+
cat > ~/.local/bin/tk-sign <<'EOF'
103+
#!/bin/sh
104+
export TK_PROFILE=agent
105+
exec tk "$@"
106+
EOF
107+
chmod 755 ~/.local/bin/tk-sign
108+
109+
# Point git at the wrapper as the GPG program.
109110
git config --global gpg.format openpgp
110-
git config --global gpg.program tk
111+
git config --global gpg.program ~/.local/bin/tk-sign
111112
git config --global user.signingkey "$FINGERPRINT"
112113
git config --global commit.gpgsign true
113114
@@ -116,19 +117,7 @@ the GPG path, `ssh-keygen` for the SSH path, and `git` must be installed.
116117
git verify-commit HEAD
117118
```
118119
119-
Drop `--global` to configure one repository. When the process running
120-
git has a different `HOME`, or several profiles reach the key's
121-
organization, set `gpg.program` to a wrapper such as:
122-
123-
```
124-
#!/bin/sh
125-
export HOME=/home/agent
126-
export TK_PROFILE=agent
127-
exec tk "$@"
128-
```
129-
130-
The wrapper adds nothing else: git's `--status-fd=2 -bsau KEY` arguments
131-
must arrive unchanged.
120+
Drop `--global` to configure one repository.
132121
133122
6. **SSH signing instead.** Use the Ed25519 key registered in
134123
[using-ssh](../using-ssh/SKILL.md). Root scopes the policy to it:
@@ -166,8 +155,8 @@ the GPG path, `ssh-keygen` for the SSH path, and `git` must be installed.
166155
167156
Sign and verify as in step 5. With several registered keys, name one with
168157
`tk ssh public-key --key SSH_FINGERPRINT`. The `allowed_signers` email is
169-
the committer email. The same wrapper as step 5 applies to
170-
`gpg.ssh.program`.
158+
the committer email. Outside CI, set `gpg.ssh.program` to the wrapper
159+
from step 5.
171160
172161
7. **Hand off.** Report the wallet id or private key id, the fingerprint,
173162
the policy id, the exact `git config` values set and where, and the
@@ -193,9 +182,8 @@ the GPG path, `ssh-keygen` for the SSH path, and `git` must be installed.
193182
is not in the `GNUPGHOME` git reads, or its ownertrust was not imported; for
194183
SSH, the `allowed_signers` line does not pair the committer email with the
195184
signing public key, or `gpg.ssh.allowedSignersFile` is unset.
196-
- git signs as the wrong identity, or `tk` cannot find a profile: the
197-
process running git has a different `HOME` or no `TK_PROFILE`. Use the
198-
wrapper from step 5 and confirm it is the configured `gpg.program`.
185+
- git signs as the wrong identity: the wrapper from step 5 is not the
186+
configured `gpg.program` (or `gpg.ssh.program` for SSH).
199187
- git reports it cannot run the SSH signing program: on the direct path
200188
`gpg.ssh.program` must be an absolute path to `tk` or the wrapper; on the
201189
agent path it is `ssh-keygen` with `SSH_AUTH_SOCK` on the agent socket

‎tests/e2e/gpg.rs‎

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -564,8 +564,8 @@ fn signing_git_commits_gpg_with_scoped_policy() {
564564
.arg(&payload),
565565
);
566566

567-
let openpgp = |home: &Path, program: &Path, cmd: &mut process::Command| {
568-
run.inherit_environment(run.as_user(&agent), cmd);
567+
let openpgp = |bundle: Command, home: &Path, program: &Path, cmd: &mut process::Command| {
568+
run.inherit_environment(bundle, cmd);
569569
cmd.env("HOME", home).env("GNUPGHOME", &gnupghome);
570570
openpgp_config(program, Some(&fingerprint), cmd);
571571
};
@@ -574,7 +574,8 @@ fn signing_git_commits_gpg_with_scoped_policy() {
574574

575575
let repo = run.home.path().join("repo");
576576
fs::create_dir(&repo).unwrap();
577-
let own_home = |cmd: &mut process::Command| openpgp(run.home.path(), tk, cmd);
577+
let own_home =
578+
|cmd: &mut process::Command| openpgp(run.as_user(&agent), run.home.path(), tk, cmd);
578579
run.git_ok(&git, &repo, own_home, &["init", "--quiet"]);
579580
run.git_ok(&git, &repo, own_home, &commit);
580581
run.git_ok(&git, &repo, own_home, &["verify-commit", "HEAD"]);
@@ -583,19 +584,23 @@ fn signing_git_commits_gpg_with_scoped_policy() {
583584
fs::create_dir(&other_home).unwrap();
584585
let other_repo = other_home.join("repo");
585586
fs::create_dir(&other_repo).unwrap();
586-
let no_registry = |cmd: &mut process::Command| openpgp(&other_home, tk, cmd);
587+
let no_registry =
588+
|cmd: &mut process::Command| openpgp(run.as_user(&agent), &other_home, tk, cmd);
587589
run.git_ok(&git, &other_repo, no_registry, &["init", "--quiet"]);
588590
let unregistered = Run::git(&git, &other_repo, no_registry, &commit);
589591
assert!(
590592
!unregistered.status.success(),
591593
"git signed from a HOME with no registry"
592594
);
593-
let wrapper = run.home.path().join("tk-wrapper.sh");
595+
let profile = run.name("agent");
596+
run.login_as(&profile, &agent);
597+
let wrapper = run.home.path().join("tk-sign");
594598
fs::write(
595599
&wrapper,
596600
format!(
597601
r#"#!/bin/sh
598602
export HOME='{}'
603+
export TK_PROFILE='{profile}'
599604
exec '{}' "$@"
600605
"#,
601606
run.home.path().display(),
@@ -604,7 +609,7 @@ exec '{}' "$@"
604609
)
605610
.unwrap();
606611
fs::set_permissions(&wrapper, fs::Permissions::from_mode(0o755)).unwrap();
607-
let wrapped = |cmd: &mut process::Command| openpgp(&other_home, &wrapper, cmd);
612+
let wrapped = |cmd: &mut process::Command| openpgp(run.cli(), &other_home, &wrapper, cmd);
608613
run.git_ok(&git, &other_repo, wrapped, &commit);
609614
run.git_ok(&git, &other_repo, wrapped, &["verify-commit", "HEAD"]);
610615
}

0 commit comments

Comments
 (0)