-
Notifications
You must be signed in to change notification settings - Fork 6
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #39 from smlx/gpg-agent
Implement gpg-agent
- Loading branch information
Showing
38 changed files
with
2,287 additions
and
435 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
test: mod-tidy generate | ||
go test -v ./... | ||
|
||
mod-tidy: | ||
go mod tidy | ||
|
||
generate: | ||
go generate ./... |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,73 +1,102 @@ | ||
package main | ||
|
||
import ( | ||
"errors" | ||
"context" | ||
"fmt" | ||
"io" | ||
"net" | ||
"time" | ||
|
||
"github.com/coreos/go-systemd/activation" | ||
pivagent "github.com/smlx/piv-agent/internal/agent" | ||
"github.com/smlx/piv-agent/internal/pivservice" | ||
"github.com/smlx/piv-agent/internal/server" | ||
"github.com/smlx/piv-agent/internal/ssh" | ||
"go.uber.org/zap" | ||
"golang.org/x/crypto/ssh/agent" | ||
"golang.org/x/sync/errgroup" | ||
) | ||
|
||
type agentTypeFlag map[string]uint | ||
|
||
// ServeCmd represents the listen command. | ||
type ServeCmd struct { | ||
LoadKeyfile bool `kong:"default=true,help='Load the key file from ~/.ssh/id_ed25519'"` | ||
ExitTimeout time.Duration `kong:"default=32m,help='Exit after this period to drop transaction and key file passphrase cache'"` | ||
AgentTypes agentTypeFlag `kong:"default='ssh=0;gpg=1',help='Agent types to handle'"` | ||
} | ||
|
||
// validAgents is the list of agents supported by piv-agent. | ||
var validAgents = []string{"ssh", "gpg"} | ||
|
||
// AfterApply validates the given agent types. | ||
func (flagAgents *agentTypeFlag) AfterApply() error { | ||
for flagAgent := range map[string]uint(*flagAgents) { | ||
valid := false | ||
for _, validAgent := range validAgents { | ||
if flagAgent == validAgent { | ||
valid = true | ||
} | ||
} | ||
if !valid { | ||
return fmt.Errorf("invalid agent-type: %v", flagAgent) | ||
} | ||
} | ||
return nil | ||
} | ||
|
||
// Run the listen command to start listening for ssh-agent requests. | ||
func (cmd *ServeCmd) Run(log *zap.Logger) error { | ||
log.Info("startup", zap.String("version", version), | ||
zap.String("build date", date)) | ||
p := pivservice.New(log) | ||
// use systemd socket activation | ||
listeners, err := activation.Listeners() | ||
ls, err := activation.Listeners() | ||
if err != nil { | ||
return fmt.Errorf("cannot retrieve listeners: %w", err) | ||
} | ||
if len(listeners) != 1 { | ||
return fmt.Errorf("unexpected number of sockets, expected: 1, received: %v", | ||
len(listeners)) | ||
// validate given agent types | ||
if len(ls) != len(cmd.AgentTypes) { | ||
return fmt.Errorf("wrong number of agent sockets: wanted %v, received %v", | ||
len(cmd.AgentTypes), len(ls)) | ||
} | ||
// prepare dependencies | ||
ctx, cancel := context.WithCancel(context.Background()) | ||
defer cancel() | ||
exit := time.NewTicker(cmd.ExitTimeout) | ||
g := errgroup.Group{} | ||
// start SSH agent if given in agent-type flag | ||
if _, ok := cmd.AgentTypes["ssh"]; ok { | ||
log.Debug("starting SSH server") | ||
g.Go(func() error { | ||
s := server.NewSSH(log) | ||
a := ssh.NewAgent(p, log, cmd.LoadKeyfile) | ||
err := s.Serve(ctx, a, ls[cmd.AgentTypes["ssh"]], exit, cmd.ExitTimeout) | ||
cancel() | ||
return err | ||
}) | ||
} | ||
// start the exit timer | ||
exitTicker := time.NewTicker(cmd.ExitTimeout) | ||
// start serving connections | ||
newConns := make(chan net.Conn) | ||
go func(l net.Listener, log *zap.Logger) { | ||
for { | ||
c, err := l.Accept() | ||
if _, ok := cmd.AgentTypes["gpg"]; ok { | ||
log.Debug("starting GPG server") | ||
g.Go(func() error { | ||
s := server.NewGPG(p, log) | ||
err := s.Serve(ctx, ls[cmd.AgentTypes["gpg"]], exit, cmd.ExitTimeout) | ||
if err != nil { | ||
log.Error("accept error", zap.Error(err)) | ||
close(newConns) | ||
return | ||
log.Debug("exiting GPG server", zap.Error(err)) | ||
} else { | ||
log.Debug("exiting GPG server successfully") | ||
} | ||
newConns <- c | ||
} | ||
}(listeners[0], log) | ||
|
||
a := pivagent.New(log, cmd.LoadKeyfile) | ||
cancel() | ||
return err | ||
}) | ||
} | ||
loop: | ||
for { | ||
select { | ||
case conn, ok := <-newConns: | ||
if !ok { | ||
return fmt.Errorf("listen socket closed") | ||
} | ||
// reset the exit timer | ||
exitTicker.Reset(cmd.ExitTimeout) | ||
log.Debug("start serving connection") | ||
if err = agent.ServeAgent(a, conn); err != nil { | ||
if errors.Is(err, io.EOF) { | ||
log.Debug("finish serving connection") | ||
continue | ||
} | ||
return fmt.Errorf("serveAgent error: %w", err) | ||
} | ||
case <-exitTicker.C: | ||
case <-ctx.Done(): | ||
log.Debug("exit done") | ||
break loop | ||
case <-exit.C: | ||
log.Debug("exit timeout") | ||
return nil | ||
cancel() | ||
break loop | ||
} | ||
} | ||
return g.Wait() | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
Generate sample ECC key like so | ||
|
||
``` | ||
gpg --full-gen-key --expert | ||
# select ECC sign only | ||
# use e.g. Name: foo bar, Email: foo@example.com | ||
``` | ||
|
||
Generate signing traces like so: | ||
|
||
``` | ||
echo foo | strace -xs 1024 /usr/bin/gpg --verbose --status-fd=2 -bsau C54A8868468BC138 2> gpg-agent.sign.strace | ||
# grep the agent socket | ||
grep '(5' | ||
# reads | ||
grep '^read' | ||
# writes | ||
grep '^write' | ||
``` | ||
|
||
Export key for use in CI: | ||
``` | ||
gpg --export -ao /tmp/C54A8868468BC138.asc foo@example.com | ||
``` |
Oops, something went wrong.