Skip to content

Commit

Permalink
feat: add ssh client keys and expose ssh client public key to api
Browse files Browse the repository at this point in the history
  • Loading branch information
yankeguo committed Jan 22, 2024
1 parent d0ece64 commit efcdf85
Show file tree
Hide file tree
Showing 4 changed files with 117 additions and 93 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -27,5 +27,6 @@ go.work
# Database
/database.sqlite3

# SSH Host Keys
# SSH Keys
/ssh_host_*_key
/ssh_client_*_key
4 changes: 2 additions & 2 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,10 @@ func main() {
app := fx.New(
fx.Supply(DataDir(optDataDir)),
fx.Provide(createDatabase),
fx.Provide(createSSHServerParams, createSSHServer),
fx.Provide(createSSHServerParams, createSSHServer, createSigners),
ufx.ProvideConfFromYAMLFile(filepath.Join(optDataDir, "bunker.yaml")),
ufx.Module,
fx.Invoke(installStatic),
fx.Invoke(installStatic, installAPIAuthorizedKeys),
fx.Invoke(func(s *SSHServer) {}),
)
if app.Err() != nil {
Expand Down
108 changes: 108 additions & 0 deletions signers.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
package main

import (
"crypto"
"crypto/ecdsa"
"crypto/ed25519"
"crypto/elliptic"
"crypto/rand"
"crypto/rsa"
"encoding/pem"
"log"
"os"
"path/filepath"

"github.com/yankeguo/ufx"
"golang.org/x/crypto/ssh"
)

type SSHPrivateKeyGenerator = func() (key crypto.PrivateKey, err error)

var (
sshPrivateKeyGenerators = map[string]SSHPrivateKeyGenerator{
"rsa": func() (crypto.PrivateKey, error) {
return rsa.GenerateKey(rand.Reader, 2048)
},
"ecdsa": func() (crypto.PrivateKey, error) {
return ecdsa.GenerateKey(elliptic.P384(), rand.Reader)
},
"ed25519": func() (crypto.PrivateKey, error) {
_, priv, err := ed25519.GenerateKey(rand.Reader)
return priv, err
},
}
)

type Signers struct {
Host []ssh.Signer
Client []ssh.Signer

AuthorizedKeys string
}

func loadOrCreateSigner(filename string, generator SSHPrivateKeyGenerator) (sgn ssh.Signer, err error) {
var buf []byte
if buf, err = os.ReadFile(filename); err == nil {
if sgn, err = ssh.ParsePrivateKey(buf); err != nil {
return
}
log.Println("signer loaded from:", filename)
} else {
if !os.IsNotExist(err) {
return
}

var key crypto.PrivateKey
if key, err = generator(); err != nil {
return
}
if sgn, err = ssh.NewSignerFromKey(key); err != nil {
return
}

var block *pem.Block
if block, err = ssh.MarshalPrivateKey(key, ""); err != nil {
return
}

buf = pem.EncodeToMemory(block)
if err = os.WriteFile(filename, buf, 0600); err != nil {
return
}

log.Println("signer generated to:", filename)
}
return
}

func createSigners(dataDir DataDir) (signers *Signers, err error) {
signers = &Signers{}

for kind, generator := range sshPrivateKeyGenerators {
var sgn ssh.Signer
if sgn, err = loadOrCreateSigner(filepath.Join(dataDir.String(), "ssh_host_"+kind+"_key"), generator); err != nil {
return
}
signers.Host = append(signers.Host, sgn)
}

for kind, generator := range sshPrivateKeyGenerators {
var sgn ssh.Signer
if sgn, err = loadOrCreateSigner(filepath.Join(dataDir.String(), "ssh_client_"+kind+"_key"), generator); err != nil {
return
}
signers.Client = append(signers.Client, sgn)
}

for _, sgn := range signers.Client {
signers.AuthorizedKeys += string(ssh.MarshalAuthorizedKey(sgn.PublicKey()))
}

return
}

func installAPIAuthorizedKeys(signers *Signers, ur ufx.Router) {
ur.HandleFunc("/backend/authorized_keys", func(c ufx.Context) {
c.Text(signers.AuthorizedKeys)
})
}
95 changes: 5 additions & 90 deletions ssh.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,34 +2,21 @@ package main

import (
"context"
"crypto"
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/rsa"
"encoding/pem"
"errors"
"log"
"net"
"os"
"path/filepath"
"strings"
"time"

"github.com/yankeguo/bunker/model"
"github.com/yankeguo/bunker/model/dao"
"github.com/yankeguo/ufx"
"go.uber.org/fx"
"golang.org/x/crypto/ed25519"
"golang.org/x/crypto/ssh"
"gorm.io/gorm"
)

const (
sshHostKeyFileRSA = "ssh_host_rsa_key"
sshHostKeyFileECDSA = "ssh_host_ecdsa_key"
sshHostKeyFileEd25519 = "ssh_host_ed25519_key"

sshExtKeyUserID = "bunker.user_id"
)

Expand All @@ -46,9 +33,9 @@ type SSHServer struct {
DataDir string
Params SSHServerParams
Database *gorm.DB
Signers *Signers

listener *net.TCPListener
hostSigners []ssh.Signer
listener *net.TCPListener
}

type SSHServerOptions struct {
Expand All @@ -58,76 +45,7 @@ type SSHServerOptions struct {
Params SSHServerParams
DataDir DataDir
Database *gorm.DB
}

func (s *SSHServer) ensureHostSigner(filename string, generator func() (key crypto.PrivateKey, err error)) (err error) {
var (
sgn ssh.Signer
buf []byte
)
if buf, err = os.ReadFile(filename); err == nil {
if sgn, err = ssh.ParsePrivateKey(buf); err != nil {
return
}
log.Println("host signer loaded from:", filename)
s.hostSigners = append(s.hostSigners, sgn)
} else {
if !os.IsNotExist(err) {
return
}

var key crypto.PrivateKey
if key, err = generator(); err != nil {
return
}
if sgn, err = ssh.NewSignerFromKey(key); err != nil {
return
}

s.hostSigners = append(s.hostSigners, sgn)

var block *pem.Block
if block, err = ssh.MarshalPrivateKey(key, ""); err != nil {
return
}

buf = pem.EncodeToMemory(block)
if err = os.WriteFile(filename, buf, 0600); err != nil {
return
}

log.Println("host signer generated to:", filename)
}
return
}

func (s *SSHServer) ensureHostSigners() (err error) {
fileRSA := filepath.Join(s.DataDir, sshHostKeyFileRSA)

if err = s.ensureHostSigner(fileRSA, func() (crypto.PrivateKey, error) {
return rsa.GenerateKey(rand.Reader, 2048)
}); err != nil {
return
}

fileECDSA := filepath.Join(s.DataDir, sshHostKeyFileECDSA)

if err = s.ensureHostSigner(fileECDSA, func() (crypto.PrivateKey, error) {
return ecdsa.GenerateKey(elliptic.P384(), rand.Reader)
}); err != nil {
return
}

fileEd25519 := filepath.Join(s.DataDir, sshHostKeyFileEd25519)

if err = s.ensureHostSigner(fileEd25519, func() (crypto.PrivateKey, error) {
_, priv, err := ed25519.GenerateKey(rand.Reader)
return priv, err
}); err != nil {
return
}

return
Signers *Signers
}

func (s *SSHServer) createServerConfig() *ssh.ServerConfig {
Expand Down Expand Up @@ -162,7 +80,7 @@ func (s *SSHServer) createServerConfig() *ssh.ServerConfig {
},
}

for _, sgn := range s.hostSigners {
for _, sgn := range s.Signers.Host {
cfg.AddHostKey(sgn)
}

Expand Down Expand Up @@ -227,10 +145,7 @@ func createSSHServer(opts SSHServerOptions) (s *SSHServer, err error) {
s = &SSHServer{
DataDir: opts.DataDir.String(),
Params: opts.Params,
}

if err = s.ensureHostSigners(); err != nil {
return
Signers: opts.Signers,
}

if opts.Lifecycle != nil {
Expand Down

0 comments on commit efcdf85

Please sign in to comment.