parent
0b0bbc4bf9
commit
f5b9f7233f
7 changed files with 177 additions and 11 deletions
10
.github/workflows/release.yaml
vendored
10
.github/workflows/release.yaml
vendored
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
112
cmd/sshpiperd/key.go
Normal 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
|
||||
}
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
Loading…
Add table
Add a link
Reference in a new issue