Skip to content

Commit fdd4906

Browse files
authored
fix(cmd): GPG agent forwarding fails with SSH signing keys (#732)
When the user has SSH-based commit signing configured (gpg.format=ssh), the GPG agent forwarding code no longer passes the SSH key path as --gitkey to setup-gpg. SSH signing keys are handled by the separate SSH signature helper path.
1 parent 2c4b3f9 commit fdd4906

File tree

4 files changed

+124
-12
lines changed

4 files changed

+124
-12
lines changed

cmd/agent/workspace/setup_gpg.go

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -135,10 +135,8 @@ func (cmd *SetupGPGCmd) Run(ctx context.Context, log log.Logger) error {
135135

136136
if gpgConf.GitKey != "" {
137137
log.Debugf("Setup git signing key")
138-
err = gitcredentials.SetupGpgGitKey(gpgConf.GitKey)
139-
if err != nil {
140-
log.Errorf("Setup git signing key: %v", err)
141-
return err
138+
if err := gitcredentials.SetupGpgGitKey(gpgConf.GitKey); err != nil {
139+
log.Warnf("Setup git signing key failed (non-fatal): %v", err)
142140
}
143141
}
144142

cmd/ssh.go

Lines changed: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -710,12 +710,7 @@ func (cmd *SSHCmd) setupGPGAgent(
710710
gpgExtraSocketPath := strings.TrimSpace(string(gpgExtraSocketBytes))
711711
log.Debugf("[GPG] detected gpg-agent socket path %s", gpgExtraSocketPath)
712712

713-
gitGpgKey, err := exec.Command("git", []string{"config", "user.signingKey"}...).Output()
714-
if err != nil {
715-
log.Debugf("[GPG] no git signkey detected, skipping")
716-
} else {
717-
log.Debugf("[GPG] detected git sign key %s", gitGpgKey)
718-
}
713+
gitKey := gpgSigningKey(log)
719714

720715
cmd.ReverseForwardPorts = append(cmd.ReverseForwardPorts, gpgExtraSocketPath)
721716

@@ -735,8 +730,7 @@ func (cmd *SSHCmd) setupGPGAgent(
735730
forwardAgent = append(forwardAgent, "--debug")
736731
}
737732

738-
if len(gitGpgKey) > 0 {
739-
gitKey := strings.TrimSpace(string(gitGpgKey))
733+
if gitKey != "" {
740734
forwardAgent = append(forwardAgent, "--gitkey")
741735
forwardAgent = append(forwardAgent, gitKey)
742736
}
@@ -770,6 +764,29 @@ func (cmd *SSHCmd) setupGPGAgent(
770764
return nil
771765
}
772766

767+
// gpgSigningKey returns the user's GPG signing key from git config,
768+
// or empty string if no key is configured or the signing format is SSH
769+
// (SSH signing keys are handled by the separate SSH signature helper).
770+
func gpgSigningKey(log log.Logger) string {
771+
format, err := exec.Command("git", "config", "--get", "gpg.format").Output()
772+
if err == nil && strings.TrimSpace(string(format)) == "ssh" {
773+
log.Debugf(
774+
"[GPG] gpg.format is ssh, skipping GPG signing key (handled by SSH signing helper)",
775+
)
776+
return ""
777+
}
778+
779+
key, err := exec.Command("git", "config", "--get", "user.signingKey").Output()
780+
if err != nil {
781+
log.Debugf("[GPG] no git signkey detected, skipping")
782+
return ""
783+
}
784+
785+
result := strings.TrimSpace(string(key))
786+
log.Debugf("[GPG] detected git sign key %s", result)
787+
return result
788+
}
789+
773790
func startSSHKeepAlive(
774791
ctx context.Context,
775792
client *ssh.Client,

cmd/ssh_test.go

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
package cmd
2+
3+
import (
4+
"os"
5+
"path/filepath"
6+
"testing"
7+
8+
"github.com/skevetter/log"
9+
"github.com/stretchr/testify/assert"
10+
)
11+
12+
func writeGitConfig(t *testing.T, content string) {
13+
t.Helper()
14+
home := t.TempDir()
15+
t.Setenv("HOME", home)
16+
t.Setenv("XDG_CONFIG_HOME", home)
17+
t.Setenv("GIT_CONFIG_GLOBAL", filepath.Join(home, ".gitconfig"))
18+
err := os.WriteFile(filepath.Join(home, ".gitconfig"), []byte(content), 0o600)
19+
assert.NoError(t, err)
20+
}
21+
22+
func TestGpgSigningKey_GPGFormat(t *testing.T) {
23+
writeGitConfig(t, "[user]\n\tsigningKey = TESTKEY123\n")
24+
result := gpgSigningKey(log.Discard)
25+
assert.Equal(t, "TESTKEY123", result)
26+
}
27+
28+
func TestGpgSigningKey_SSHFormat_Skipped(t *testing.T) {
29+
writeGitConfig(
30+
t,
31+
"[gpg]\n\tformat = ssh\n[user]\n\tsigningKey = /home/user/.ssh/id_ed25519.pub\n",
32+
)
33+
result := gpgSigningKey(log.Discard)
34+
assert.Empty(t, result)
35+
}
36+
37+
func TestGpgSigningKey_NoKeyConfigured(t *testing.T) {
38+
writeGitConfig(t, "[user]\n\tname = Test\n")
39+
result := gpgSigningKey(log.Discard)
40+
assert.Empty(t, result)
41+
}
42+
43+
func TestGpgSigningKey_X509Format_Returned(t *testing.T) {
44+
writeGitConfig(t, "[gpg]\n\tformat = x509\n[user]\n\tsigningKey = /path/to/cert\n")
45+
result := gpgSigningKey(log.Discard)
46+
assert.Equal(t, "/path/to/cert", result)
47+
}

e2e/tests/ssh/ssh.go

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,56 @@ var _ = ginkgo.Describe("devpod ssh test suite", ginkgo.Label("ssh"), ginkgo.Ord
6161
},
6262
)
6363

64+
ginkgo.It(
65+
"should start workspace with GPG forwarding when host uses SSH signing format",
66+
ginkgo.Label("gpg"),
67+
ginkgo.SpecTimeout(5*time.Minute),
68+
func(ctx ginkgo.SpecContext) {
69+
if runtime.GOOS == osWindows {
70+
ginkgo.Skip("skipping on windows")
71+
}
72+
73+
tempDir, err := framework.CopyToTempDir("tests/ssh/testdata/gpg-forwarding")
74+
framework.ExpectNoError(err)
75+
76+
f := framework.NewDefaultFramework(initialDir + "/bin")
77+
_ = f.DevPodProviderAdd(ctx, "docker")
78+
err = f.DevPodProviderUse(ctx, "docker")
79+
framework.ExpectNoError(err)
80+
81+
ginkgo.DeferCleanup(func(cleanupCtx context.Context) {
82+
_ = f.DevPodWorkspaceDelete(cleanupCtx, tempDir)
83+
framework.CleanupTempDir(initialDir, tempDir)
84+
})
85+
86+
sshKeyDir := ginkgo.GinkgoT().TempDir()
87+
88+
keyPath := filepath.Join(sshKeyDir, "id_ed25519")
89+
// #nosec G204 -- test command with controlled arguments
90+
err = exec.Command(
91+
"ssh-keygen", "-t", "ed25519", "-f", keyPath, "-N", "", "-q",
92+
).Run()
93+
framework.ExpectNoError(err)
94+
95+
gitConfigDir := ginkgo.GinkgoT().TempDir()
96+
gitConfigPath := filepath.Join(gitConfigDir, ".gitconfig")
97+
gitConfigContent := "[gpg]\n\tformat = ssh\n[user]\n\tsigningKey = " +
98+
keyPath + ".pub\n\tname = Test\n\temail = test@test.com\n"
99+
err = os.WriteFile(gitConfigPath, []byte(gitConfigContent), 0o600)
100+
framework.ExpectNoError(err)
101+
ginkgo.GinkgoT().Setenv("GIT_CONFIG_GLOBAL", gitConfigPath)
102+
103+
err = f.DevPodUp(ctx, tempDir, "--gpg-agent-forwarding")
104+
framework.ExpectNoError(err)
105+
106+
devpodSSHDeadline := time.Now().Add(20 * time.Second)
107+
devpodSSHCtx, cancelSSH := context.WithDeadline(ctx, devpodSSHDeadline)
108+
defer cancelSSH()
109+
err = f.DevPodSSHEchoTestString(devpodSSHCtx, tempDir)
110+
framework.ExpectNoError(err)
111+
},
112+
)
113+
64114
ginkgo.It(
65115
"should set up git SSH signature helper and sign a commit",
66116
ginkgo.SpecTimeout(7*time.Minute),

0 commit comments

Comments
 (0)