aboutsummaryrefslogtreecommitdiff
path: root/forged/internal/incoming/ssh
diff options
context:
space:
mode:
Diffstat (limited to 'forged/internal/incoming/ssh')
-rw-r--r--forged/internal/incoming/ssh/config.go9
-rw-r--r--forged/internal/incoming/ssh/ssh.go89
2 files changed, 98 insertions, 0 deletions
diff --git a/forged/internal/incoming/ssh/config.go b/forged/internal/incoming/ssh/config.go
new file mode 100644
index 0000000..7d22cc1
--- /dev/null
+++ b/forged/internal/incoming/ssh/config.go
@@ -0,0 +1,9 @@
+package ssh
+
+type Config struct {
+ Net string `scfg:"net"`
+ Addr string `scfg:"addr"`
+ Key string `scfg:"key"`
+ Root string `scfg:"root"`
+ ShutdownTimeout uint32 `scfg:"shutdown_timeout"`
+}
diff --git a/forged/internal/incoming/ssh/ssh.go b/forged/internal/incoming/ssh/ssh.go
new file mode 100644
index 0000000..527cd28
--- /dev/null
+++ b/forged/internal/incoming/ssh/ssh.go
@@ -0,0 +1,89 @@
+package ssh
+
+import (
+ "context"
+ "errors"
+ "fmt"
+ "os"
+ "time"
+
+ gliderssh "github.com/gliderlabs/ssh"
+ "go.lindenii.runxiyu.org/forge/forged/internal/common/misc"
+ "go.lindenii.runxiyu.org/forge/forged/internal/global"
+ gossh "golang.org/x/crypto/ssh"
+)
+
+type Server struct {
+ gliderServer *gliderssh.Server
+ privkey gossh.Signer
+ net string
+ addr string
+ root string
+ shutdownTimeout uint32
+ global *global.Global
+}
+
+func New(config Config, global *global.Global) (server *Server, err error) {
+ server = &Server{
+ net: config.Net,
+ addr: config.Addr,
+ root: config.Root,
+ shutdownTimeout: config.ShutdownTimeout,
+ global: global,
+ } //exhaustruct:ignore
+
+ var privkeyBytes []byte
+
+ privkeyBytes, err = os.ReadFile(config.Key)
+ if err != nil {
+ return server, fmt.Errorf("read SSH private key: %w", err)
+ }
+
+ server.privkey, err = gossh.ParsePrivateKey(privkeyBytes)
+ if err != nil {
+ return server, fmt.Errorf("parse SSH private key: %w", err)
+ }
+
+ server.global.SSHPubkey = misc.BytesToString(gossh.MarshalAuthorizedKey(server.privkey.PublicKey()))
+ server.global.SSHFingerprint = gossh.FingerprintSHA256(server.privkey.PublicKey())
+
+ server.gliderServer = &gliderssh.Server{
+ Handler: handle,
+ PublicKeyHandler: func(ctx gliderssh.Context, key gliderssh.PublicKey) bool { return true },
+ KeyboardInteractiveHandler: func(ctx gliderssh.Context, challenge gossh.KeyboardInteractiveChallenge) bool { return true },
+ } //exhaustruct:ignore
+ server.gliderServer.AddHostKey(server.privkey)
+
+ return server, nil
+}
+
+func (server *Server) Run(ctx context.Context) (err error) {
+ listener, err := misc.Listen(ctx, server.net, server.addr)
+ if err != nil {
+ return fmt.Errorf("listen for SSH: %w", err)
+ }
+ defer func() {
+ _ = listener.Close()
+ }()
+
+ stop := context.AfterFunc(ctx, func() {
+ shCtx, cancel := context.WithTimeout(context.WithoutCancel(ctx), time.Duration(server.shutdownTimeout)*time.Second)
+ defer cancel()
+ _ = server.gliderServer.Shutdown(shCtx)
+ _ = listener.Close()
+ })
+ defer stop()
+
+ err = server.gliderServer.Serve(listener)
+ if err != nil {
+ if errors.Is(err, gliderssh.ErrServerClosed) || ctx.Err() != nil {
+ return nil
+ }
+ return fmt.Errorf("serve SSH: %w", err)
+ }
+ panic("unreachable")
+}
+
+func handle(session gliderssh.Session) {
+ panic("SSH server handler not implemented yet")
+}