diff --git a/cmd/tskagent/tskagent.go b/cmd/tskagent/tskagent.go new file mode 100644 index 0000000..56aa7da --- /dev/null +++ b/cmd/tskagent/tskagent.go @@ -0,0 +1,90 @@ +// Program tskagent implements an SSH key agent that runs on a tailnet. +// This documentation needs more detail. +package main + +import ( + "context" + "errors" + "fmt" + "log" + "net" + "os" + "os/signal" + "syscall" + + "github.com/creachadair/command" + "github.com/creachadair/flax" + "github.com/creachadair/taskgroup" + "github.com/tailscale/setec/client/setec" + "github.com/tailscale/tskagent" + "golang.org/x/crypto/ssh/agent" +) + +var flags struct { + Server string `flag:"server,Secret server address (required)"` + Socket string `flag:"socket,Agent socket path (required)"` + Prefix string `flag:"prefix,Secret name prefix (required)"` +} + +func main() { + root := &command.C{ + Name: command.ProgramName(), + Help: "Serve an SSH key agent on the specified socket.", + SetFlags: command.Flags(flax.MustBind, &flags), + Run: command.Adapt(run), + Commands: []*command.C{ + command.HelpCommand(nil), + command.VersionCommand(), + }, + } + ctx, cancel := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM) + defer cancel() + command.RunOrFail(root.NewEnv(nil).SetContext(ctx), os.Args[1:]) +} + +func run(env *command.Env) error { + switch { + case flags.Server == "": + return env.Usagef("a secret --server address is required") + case flags.Socket == "": + return env.Usagef("an agent --socket path is required") + case flags.Prefix == "": + return env.Usagef("a secret name --prefix is required") + } + cli := setec.Client{Server: flags.Server} + lst, err := net.Listen("unix", flags.Socket) + if err != nil { + return fmt.Errorf("listen: %w", err) + } + defer os.Remove(flags.Socket) // best-effort + + srv := tskagent.NewServer(tskagent.Config{ + Client: cli, + Prefix: flags.Prefix, + Logf: log.Printf, + }) + if err := srv.Update(env.Context()); err != nil { + return fmt.Errorf("initialize agent: %w", err) + } + + var g taskgroup.Group + g.Run(func() { + <-env.Context().Done() + log.Printf("Signal received; closing listener") + lst.Close() + }) + for { + conn, err := lst.Accept() + if err != nil { + if !errors.Is(err, net.ErrClosed) { + log.Printf("Listener stopped: %v", err) + } + break + } + g.Go(func() error { + return agent.ServeAgent(srv, conn) + }) + } + g.Wait() + return nil +} diff --git a/go.mod b/go.mod index 00614a0..fb6d880 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,9 @@ module github.com/tailscale/tskagent go 1.23.2 require ( + github.com/creachadair/command v0.1.15 + github.com/creachadair/flax v0.0.0-20240212192608-428acafa3bbe + github.com/creachadair/taskgroup v0.12.0 github.com/tailscale/setec v0.0.0-20240930150730-e6eb93658ed3 golang.org/x/crypto v0.28.0 ) diff --git a/go.sum b/go.sum index 8d17a3a..cd8e5ca 100644 --- a/go.sum +++ b/go.sum @@ -34,8 +34,16 @@ github.com/aws/aws-sdk-go-v2/service/sts v1.28.6 h1:cwIxeBttqPN3qkaAjcEcsh8NYr8n github.com/aws/aws-sdk-go-v2/service/sts v1.28.6/go.mod h1:FZf1/nKNEkHdGGJP/cI2MoIMquumuRK6ol3QQJNDxmw= github.com/aws/smithy-go v1.20.2 h1:tbp628ireGtzcHDDmLT/6ADHidqnwgF57XOXZe6tp4Q= github.com/aws/smithy-go v1.20.2/go.mod h1:krry+ya/rV9RDcV/Q16kpu6ypI4K2czasz0NC3qS14E= +github.com/creachadair/command v0.1.15 h1:ut7OVTGYv5RMqOHJvrmSCtsS8UBzU9tXgQYLdAPZPho= +github.com/creachadair/command v0.1.15/go.mod h1:JyiZVgQzhzPWa8DdE6Hfl+IOi7DMpXToBEvQCQoST7c= +github.com/creachadair/flax v0.0.0-20240212192608-428acafa3bbe h1:Za5bZ3VxQXRJqfxcHnkmgk7FfmPxT4N1TjwlKjnkvlw= +github.com/creachadair/flax v0.0.0-20240212192608-428acafa3bbe/go.mod h1:2vVCsqiB+GpV/qBKCQ0lKRrJhlKUPdI83SnaFKwJyNo= github.com/creachadair/mds v0.17.1 h1:lXQbTGKmb3nE3aK6OEp29L1gCx6B5ynzlQ6c1KOBurc= github.com/creachadair/mds v0.17.1/go.mod h1:4b//mUiL8YldH6TImXjmW45myzTLNS1LLjOmrk888eg= +github.com/creachadair/taskgroup v0.12.0 h1:AWk1vgMp1VjmwRUJkbX/XZxDRI+gCWhVr+Slj2LeJ+A= +github.com/creachadair/taskgroup v0.12.0/go.mod h1:9oDDPt/5QPS4iylvPMC81GRlj+1je8AFDbjUh4zaQWo= +github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw= +github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/go-json-experiment/json v0.0.0-20231102232822-2e55bd4e08b0 h1:ymLjT4f35nQbASLnvxEde4XOBL+Sn7rFuV+FOJqkljg=