add snap support (#189)

* embed keygen

* add snap

* add generate log
This commit is contained in:
Boshi Lian 2023-08-11 06:25:58 -07:00 committed by GitHub
parent 0b0bbc4bf9
commit f5b9f7233f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 177 additions and 11 deletions

View file

@ -72,6 +72,16 @@ jobs:
go-version: 'stable'
cache: true
- name: Setup Snap
env:
SNAP_SECRET: ${{ secrets.snap }}
run: |
set -e
sudo snap install snapcraft --classic
echo $SNAP_SECRET | base64 -d > secret
snap run snapcraft login --with secret
- name: Run GoReleaser
uses: goreleaser/goreleaser-action@v4
with:

View file

@ -216,4 +216,26 @@ snapshot:
release:
draft: true
replace_existing_draft: true
disable: true
disable: true
snapcrafts:
- builds:
- sshpiperd
- plugin_workingdir
name: sshpiperd
name_template: "sshpiperd_{{ .Version }}_{{ .Os }}_{{ .Arch }}"
summary: The missing reverse proxy for ssh scp
description: |
sshpiperd is a reverse proxy for ssh/scp. It allows you to have a single
ssh key pair for all your servers, behind a single IP address. It also
allows you to connect to internal servers without exposing them to the
internet.
publish: true
grade: stable
confinement: strict
license: MIT
apps:
sshpiperd:
command: sshpiperd --server-key $SNAP_COMMON/ssh_host_ed25519_key --server-key-generate-mode notexist $SNAP/workingdir --root $SNAP_COMMON/workingdir
plugs: [network-bind]
daemon: simple

View file

@ -15,7 +15,6 @@ ADD entrypoint.sh /sshpiperd
FROM busybox
LABEL maintainer="Boshi Lian<farmer1992@gmail.com>"
COPY --from=ep76/openssh-static:latest /usr/bin/ssh-keygen /bin/ssh-keygen
RUN mkdir /etc/ssh/
# Add user nobody with id 1

View file

@ -44,11 +44,33 @@ func newDaemon(ctx *cli.Context) (*daemon, error) {
config.AddHostKey(private)
} else {
privateKeyFiles, err := filepath.Glob(ctx.String("server-key"))
keyfile := ctx.String("server-key")
privateKeyFiles, err := filepath.Glob(keyfile)
if err != nil {
return nil, err
}
generate := false
switch ctx.String("server-key-generate-mode") {
case "notexist":
generate = len(privateKeyFiles) == 0
case "always":
generate = true
case "disable":
default:
return nil, fmt.Errorf("unknown server-key-generate-mode %v", ctx.String("server-key-generate-mode"))
}
if generate {
log.Infof("generating host key %v", keyfile)
if err := generateSshKey(keyfile); err != nil {
return nil, err
}
privateKeyFiles = []string{keyfile}
}
if len(privateKeyFiles) == 0 {
return nil, fmt.Errorf("no server key found")
}

112
cmd/sshpiperd/key.go Normal file
View file

@ -0,0 +1,112 @@
package main
import (
"crypto/ed25519"
crand "crypto/rand"
"encoding/pem"
"math/rand"
"os"
"golang.org/x/crypto/ssh"
)
func generateSshKey(keyfile string) error {
_, privateKey, err := ed25519.GenerateKey(crand.Reader)
if err != nil {
return err
}
privateKeyPEM := &pem.Block{
Type: "OPENSSH PRIVATE KEY",
Bytes: marshalED25519PrivateKey(privateKey),
}
privateKeyBytes := pem.EncodeToMemory(privateKeyPEM)
return os.WriteFile(keyfile, privateKeyBytes, 0600)
}
// copy from https://github.com/mikesmitty/edkey/blob/master/edkey.go
/*
Writes ed25519 private keys into the new OpenSSH private key format.
I have no idea why this isn't implemented anywhere yet, you can do seemingly
everything except write it to disk in the OpenSSH private key format.
*/
func marshalED25519PrivateKey(key ed25519.PrivateKey) []byte {
// Add our key header (followed by a null byte)
magic := append([]byte("openssh-key-v1"), 0)
var w struct {
CipherName string
KdfName string
KdfOpts string
NumKeys uint32
PubKey []byte
PrivKeyBlock []byte
}
// Fill out the private key fields
pk1 := struct {
Check1 uint32
Check2 uint32
Keytype string
Pub []byte
Priv []byte
Comment string
Pad []byte `ssh:"rest"`
}{}
// Set our check ints
ci := rand.Uint32()
pk1.Check1 = ci
pk1.Check2 = ci
// Set our key type
pk1.Keytype = ssh.KeyAlgoED25519
// Add the pubkey to the optionally-encrypted block
pk, ok := key.Public().(ed25519.PublicKey)
if !ok {
//fmt.Fprintln(os.Stderr, "ed25519.PublicKey type assertion failed on an ed25519 public key. This should never ever happen.")
return nil
}
pubKey := []byte(pk)
pk1.Pub = pubKey
// Add our private key
pk1.Priv = []byte(key)
// Might be useful to put something in here at some point
pk1.Comment = ""
// Add some padding to match the encryption block size within PrivKeyBlock (without Pad field)
// 8 doesn't match the documentation, but that's what ssh-keygen uses for unencrypted keys. *shrug*
bs := 8
blockLen := len(ssh.Marshal(pk1))
padLen := (bs - (blockLen % bs)) % bs
pk1.Pad = make([]byte, padLen)
// Padding is a sequence of bytes like: 1, 2, 3...
for i := 0; i < padLen; i++ {
pk1.Pad[i] = byte(i + 1)
}
// Generate the pubkey prefix "\0\0\0\nssh-ed25519\0\0\0 "
prefix := []byte{0x0, 0x0, 0x0, 0x0b}
prefix = append(prefix, []byte(ssh.KeyAlgoED25519)...)
prefix = append(prefix, []byte{0x0, 0x0, 0x0, 0x20}...)
// Only going to support unencrypted keys for now
w.CipherName = "none"
w.KdfName = "none"
w.KdfOpts = ""
w.NumKeys = 1
w.PubKey = append(prefix, pubKey...)
w.PrivKeyBlock = ssh.Marshal(pk1)
magic = append(magic, ssh.Marshal(w)...)
return magic
}

View file

@ -99,9 +99,15 @@ func main() {
},
&cli.StringFlag{
Name: "server-key-data",
Usage: "server key in base64 format, server-key will be ignored if set",
Usage: "server key in base64 format, server-key, server-key-generate-mode will be ignored if set",
EnvVars: []string{"SSHPIPERD_SERVER_KEY_DATA"},
},
&cli.StringFlag{
Name: "server-key-generate-mode",
Usage: "server key generate mode, one of: disable, notexist, always. generated key will be written to `server-key` if notexist or always",
Value: "disable",
EnvVars: []string{"SSHPIPERD_SERVER_KEY_GENERATE_MODE"},
},
&cli.DurationFlag{
Name: "login-grace-time",
Value: 30 * time.Second,

View file

@ -1,12 +1,7 @@
#!/bin/sh
set -eo pipefail
if [ -z "$SSHPIPERD_SERVER_KEY" ]; then
if [ ! -f /etc/ssh/ssh_host_ed25519_key ];then
ssh-keygen -t ed25519 -N '' -f /etc/ssh/ssh_host_ed25519_key
fi
fi
PLUGIN=${PLUGIN:-workingdir}
export SSHPIPERD_SERVER_KEY_GENERATE_MODE=${SSHPIPERD_SERVER_KEY_GENERATE_MODE:-notexist}
exec /sshpiperd/sshpiperd /sshpiperd/plugins/$PLUGIN
/sshpiperd/sshpiperd /sshpiperd/plugins/$PLUGIN