Skip to content

Commit 794edf9

Browse files
authored
Merge pull request #1002 from entireio/fix/1password-ssh-signing
Fix SSH signing for 1Password and bare public key setups
2 parents b487fe8 + 751ec2b commit 794edf9

2 files changed

Lines changed: 95 additions & 0 deletions

File tree

cmd/entire/cli/objectsigner.go

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@ import (
77
"os"
88
"sync"
99

10+
"github.com/entireio/cli/cmd/entire/cli/logging"
1011
"github.com/go-git/go-git/v6/config"
12+
format "github.com/go-git/go-git/v6/plumbing/format/config"
1113
"github.com/go-git/go-git/v6/x/plugin"
1214
"github.com/go-git/x/plugin/objectsigner/auto"
1315
"golang.org/x/crypto/ssh/agent"
@@ -35,6 +37,17 @@ func RegisterObjectSigner() {
3537
return nil
3638
}
3739

40+
// When a custom gpg.ssh.program is configured (e.g. 1Password's
41+
// op-ssh-sign), signing happens via an external binary that go-git
42+
// cannot invoke. Skip native signing silently — checkpoint commits
43+
// will be unsigned, which is acceptable since signing is best-effort.
44+
// The default program is "ssh-keygen", which works with go-git's
45+
// native SSH agent signing and does not need to be skipped.
46+
if auto.Format(merged.GPG.Format) == auto.FormatSSH && hasCustomSSHSignProgram(merged.Raw) {
47+
logging.Debug(context.Background(), "skipping native SSH commit signing: custom gpg.ssh.program is configured")
48+
return nil
49+
}
50+
3851
cfg := auto.Config{
3952
SigningKey: merged.User.SigningKey,
4053
Format: auto.Format(merged.GPG.Format),
@@ -74,6 +87,22 @@ var scopeName = map[config.Scope]string{
7487
config.SystemScope: "system",
7588
}
7689

90+
// hasCustomSSHSignProgram checks whether gpg.ssh.program is set to a
91+
// non-default value in the raw config. The git default is "ssh-keygen",
92+
// which works with go-git's native SSH agent signing. Custom programs
93+
// (e.g. 1Password's op-ssh-sign) use a separate signing mechanism that
94+
// go-git cannot invoke.
95+
// go-git's Config struct does not expose this field, so we read it directly.
96+
func hasCustomSSHSignProgram(raw *format.Config) bool {
97+
if raw == nil {
98+
return false
99+
}
100+
101+
program := raw.Section("gpg").Subsection("ssh").Option("program")
102+
103+
return program != "" && program != "ssh-keygen"
104+
}
105+
77106
func loadScopedConfig(source plugin.ConfigSource, scope config.Scope) *config.Config {
78107
name := scopeName[scope]
79108

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
package cli
2+
3+
import (
4+
"testing"
5+
6+
format "github.com/go-git/go-git/v6/plumbing/format/config"
7+
)
8+
9+
func TestHasCustomSSHSignProgram(t *testing.T) {
10+
t.Parallel()
11+
12+
tests := []struct {
13+
name string
14+
raw *format.Config
15+
want bool
16+
}{
17+
{
18+
name: "nil raw config",
19+
raw: nil,
20+
want: false,
21+
},
22+
{
23+
name: "empty raw config",
24+
raw: format.New(),
25+
want: false,
26+
},
27+
{
28+
name: "custom program set",
29+
raw: func() *format.Config {
30+
c := format.New()
31+
c.Section("gpg").Subsection("ssh").SetOption("program", "/Applications/1Password.app/Contents/MacOS/op-ssh-sign")
32+
return c
33+
}(),
34+
want: true,
35+
},
36+
{
37+
name: "default ssh-keygen is not custom",
38+
raw: func() *format.Config {
39+
c := format.New()
40+
c.Section("gpg").Subsection("ssh").SetOption("program", "ssh-keygen")
41+
return c
42+
}(),
43+
want: false,
44+
},
45+
{
46+
name: "gpg section exists but no ssh.program",
47+
raw: func() *format.Config {
48+
c := format.New()
49+
c.Section("gpg").SetOption("format", "ssh")
50+
return c
51+
}(),
52+
want: false,
53+
},
54+
}
55+
56+
for _, tt := range tests {
57+
t.Run(tt.name, func(t *testing.T) {
58+
t.Parallel()
59+
60+
got := hasCustomSSHSignProgram(tt.raw)
61+
if got != tt.want {
62+
t.Errorf("hasCustomSSHSignProgram() = %v, want %v", got, tt.want)
63+
}
64+
})
65+
}
66+
}

0 commit comments

Comments
 (0)