Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

feat(cli): add SSH commit signing support with gitsign command#20245

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to ourterms of service andprivacy statement. We’ll occasionally send you account related emails.

Already on GitHub?Sign in to your account

Open
michaelbeutler wants to merge3 commits intocoder:main
base:main
Choose a base branch
Loading
frommichaelbeutler:main
Open
Show file tree
Hide file tree
Changes fromall commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletionsagent/agent.go
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -1354,6 +1354,13 @@ func (a *agent) updateCommandEnv(current []string) (updated []string, err error)
// Git on Windows resolves with UNIX-style paths.
// If using backslashes, it's unable to find the executable.
"GIT_SSH_COMMAND": fmt.Sprintf("%s gitssh --", unixExecutablePath),
// Enable SSH signing for git commits. This allows users to sign commits
// using their Coder SSH key as an alternative to GPG.
"GIT_CONFIG_GLOBAL": fmt.Sprintf("%s=%s,%s=%s,%s=%s",
"gpg.format", "ssh",
"gpg.ssh.program", fmt.Sprintf("%s gitsign", unixExecutablePath),
"commit.gpgsign", "false", // Default to false, users can enable if desired
),
// Hide Coder message on code-server's "Getting Started" page
"CS_DISABLE_GETTING_STARTED_OVERRIDE": "true",
}
Expand Down
17 changes: 17 additions & 0 deletionsagent/agent_test.go
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -453,6 +453,23 @@ func TestAgent_GitSSH(t *testing.T) {
require.True(t, strings.HasSuffix(strings.TrimSpace(string(output)), "gitssh --"))
}

func TestAgent_GitConfig(t *testing.T) {
t.Parallel()
session := setupSSHSession(t, agentsdk.Manifest{}, codersdk.ServiceBannerConfig{}, nil)
command := "sh -c 'echo $GIT_CONFIG_GLOBAL'"
if runtime.GOOS == "windows" {
command = "cmd.exe /c echo %GIT_CONFIG_GLOBAL%"
}
output, err := session.Output(command)
require.NoError(t, err)
outputStr := strings.TrimSpace(string(output))

// Verify SSH signing is configured
require.Contains(t, outputStr, "gpg.format=ssh")
require.Contains(t, outputStr, "gitsign")
require.Contains(t, outputStr, "commit.gpgsign=false")
}

func TestAgent_SessionTTYShell(t *testing.T) {
t.Parallel()
if runtime.GOOS == "windows" {
Expand Down
85 changes: 85 additions & 0 deletionscli/gitssh.go
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -14,6 +14,7 @@ import (
"golang.org/x/xerrors"

"github.com/coder/coder/v2/cli/cliui"
"github.com/coder/coder/v2/codersdk/agentsdk"
"github.com/coder/pretty"
"github.com/coder/serpent"
)
Expand DownExpand Up@@ -113,6 +114,90 @@ func gitssh() *serpent.Command {
return cmd
}

func gitsign() *serpent.Command {
agentAuth := &AgentAuth{}
cmd := &serpent.Command{
Use: "gitsign",
Hidden: true,
Short: "Signs git commits using the coder SSH key",
Handler: func(inv *serpent.Invocation) error {
ctx := inv.Context()

// Catch interrupt signals to ensure the temporary key files
// are cleaned up in most cases.
ctx, stop := inv.SignalNotifyContext(ctx, StopSignals...)
defer stop()

client, err := agentAuth.CreateClient()
if err != nil {
return xerrors.Errorf("create agent client: %w", err)
}

key, err := client.GitSSHKey(ctx)
if err != nil {
return xerrors.Errorf("get agent git ssh key: %w", err)
}

// Handle SSH signing operation
return performSSHSigning(ctx, key, inv.Args, inv.Stdin, inv.Stdout, inv.Stderr)
},
}
agentAuth.AttachOptions(cmd, false)
return cmd
}

// performSSHSigning handles SSH-based signing operations for git commits
func performSSHSigning(ctx context.Context, key agentsdk.GitSSHKey, args []string, stdin io.Reader, stdout, stderr io.Writer) error {
// Create temporary files for both private and public keys
privateKeyFile, err := os.CreateTemp("", "coder-signing-key-*")
if err != nil {
return xerrors.Errorf("create temp private key file: %w", err)
}
defer func() {
_ = privateKeyFile.Close()
_ = os.Remove(privateKeyFile.Name())
}()

publicKeyFile, err := os.CreateTemp("", "coder-signing-pubkey-*.pub")
if err != nil {
return xerrors.Errorf("create temp public key file: %w", err)
}
defer func() {
_ = publicKeyFile.Close()
_ = os.Remove(publicKeyFile.Name())
}()

// Write private key to file
_, err = privateKeyFile.WriteString(key.PrivateKey)
if err != nil {
return xerrors.Errorf("write private key to temp file: %w", err)
}
if err = privateKeyFile.Close(); err != nil {
return xerrors.Errorf("close private key file: %w", err)
}

// Write public key to file
_, err = publicKeyFile.WriteString(key.PublicKey)
if err != nil {
return xerrors.Errorf("write public key to temp file: %w", err)
}
if err = publicKeyFile.Close(); err != nil {
return xerrors.Errorf("close public key file: %w", err)
}

// Execute SSH signing command
// The SSH signing protocol expects 'ssh-keygen -Y sign' for signing operations
cmdArgs := []string{"-Y", "sign", "-f", privateKeyFile.Name(), "-n", "git"}
cmdArgs = append(cmdArgs, args...)

cmd := exec.CommandContext(ctx, "ssh-keygen", cmdArgs...)
cmd.Stdin = stdin
cmd.Stdout = stdout
cmd.Stderr = stderr

return cmd.Run()
}

// fallbackIdentityFiles is the list of identity files SSH tries when
// none have been defined for a host.
var fallbackIdentityFiles = strings.Join([]string{
Expand Down
56 changes: 56 additions & 0 deletionscli/gitssh_test.go
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -235,3 +235,59 @@ func TestGitSSH(t *testing.T) {
}
})
}

func TestGitSign(t *testing.T) {
t.Parallel()

t.Run("Basic", func(t *testing.T) {
t.Parallel()

ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
defer cancel()

client, token, _ := prepareTestGitSSH(ctx, t)

// Test basic signing operation
inv, _ := clitest.New(t,
"gitsign",
"--agent-url", client.SDK.URL.String(),
"--agent-token", token,
)

pty := ptytest.New(t).Attach(inv)
cmdDone := make(chan error, 1)
go func() {
cmdDone <- inv.WithContext(ctx).Run()
}()

// Since we don't have real ssh-keygen in the test environment,
// we expect this to fail but the key handling should work correctly
pty.ExpectMatch("ssh-keygen")

// Cancel to avoid hanging
cancel()
err := <-cmdDone
// We expect an error since ssh-keygen might not be available in test env
require.Error(t, err)
})

t.Run("KeyRetrieval", func(t *testing.T) {
t.Parallel()

ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
defer cancel()

client, _, _ := prepareTestGitSSH(ctx, t)

// Verify that the agent client can retrieve the SSH key
key, err := client.GitSSHKey(ctx)
require.NoError(t, err)
require.NotEmpty(t, key.PrivateKey)
require.NotEmpty(t, key.PublicKey)

// Verify the key format
require.Contains(t, key.PublicKey, "ssh-")
require.Contains(t, key.PrivateKey, "-----BEGIN")
require.Contains(t, key.PrivateKey, "-----END")
})
}
1 change: 1 addition & 0 deletionscli/root.go
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -129,6 +129,7 @@ func (r *RootCmd) CoreSubcommands() []*serpent.Command {
// Hidden
r.connectCmd(),
gitssh(),
gitsign(),
r.support(),
r.vpnDaemon(),
r.vscodeSSH(),
Expand Down

[8]ページ先頭

©2009-2025 Movatter.jp