summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--cmd/ssh-auth-none-demo/ssh-auth-none-demo.go163
1 files changed, 163 insertions, 0 deletions
diff --git a/cmd/ssh-auth-none-demo/ssh-auth-none-demo.go b/cmd/ssh-auth-none-demo/ssh-auth-none-demo.go
new file mode 100644
index 000000000..e34ae20a1
--- /dev/null
+++ b/cmd/ssh-auth-none-demo/ssh-auth-none-demo.go
@@ -0,0 +1,163 @@
+package main
+
+import (
+ "crypto/ecdsa"
+ "crypto/ed25519"
+ "crypto/elliptic"
+ "crypto/rand"
+ "crypto/rsa"
+ "crypto/x509"
+ "encoding/pem"
+ "flag"
+ "fmt"
+ "io"
+ "io/ioutil"
+ "log"
+ "os"
+ "path/filepath"
+ "time"
+
+ gossh "github.com/tailscale/golang-x-crypto/ssh"
+ "tailscale.com/tempfork/gliderlabs/ssh"
+)
+
+// keyTypes are the SSH key types that we either try to read from the
+// system's OpenSSH keys.
+var keyTypes = []string{"rsa", "ecdsa", "ed25519"}
+
+var (
+ addr = flag.String("addr", ":2222", "address to listen on")
+)
+
+func main() {
+ flag.Parse()
+
+ cacheDir, err := os.UserCacheDir()
+ if err != nil {
+ log.Fatal(err)
+ }
+ dir := filepath.Join(cacheDir, "ssh-auth-none-demo")
+ if err := os.MkdirAll(dir, 0700); err != nil {
+ log.Fatal(err)
+ }
+
+ keys, err := getHostKeys(dir)
+ if err != nil {
+ log.Fatal(err)
+ }
+ if len(keys) == 0 {
+ log.Fatal("no host keys")
+ }
+
+ srv := &ssh.Server{
+ Addr: *addr,
+ Version: "Tailscale",
+ Handler: handleSessionPostSSHAuth,
+ ServerConfigCallback: func(ctx ssh.Context) *gossh.ServerConfig {
+ return &gossh.ServerConfig{
+ ImplictAuthMethod: "tailscale",
+ NoClientAuth: true, // required for the NoClientAuthCallback to run
+ NoClientAuthCallback: func(gossh.ConnMetadata) (*gossh.Permissions, error) {
+ return nil, nil
+ },
+ BannerCallback: func(cm gossh.ConnMetadata) string {
+ log.Printf("Got connection from user %q, %q from %v", cm.User(), cm.ClientVersion(), cm.RemoteAddr())
+ return fmt.Sprintf("# Banner for user %q, %q\n", cm.User(), cm.ClientVersion())
+ },
+ }
+ },
+ }
+
+ for _, signer := range keys {
+ srv.AddHostKey(signer)
+ }
+
+ log.Printf("Running on %s ...", srv.Addr)
+ if err := srv.ListenAndServe(); err != nil {
+ log.Fatal(err)
+ }
+ log.Printf("done")
+}
+
+func handleSessionPostSSHAuth(s ssh.Session) {
+ log.Printf("Started session from user %q", s.User())
+ fmt.Fprintf(s, "Hello user %q, it worked.\n", s.User())
+
+ // Abort the session on Control-C or Control-D.
+ go func() {
+ buf := make([]byte, 1024)
+ for {
+ n, err := s.Read(buf)
+ for _, b := range buf[:n] {
+ if b <= 4 { // abort on Control-C (3) or Control-D (4)
+ io.WriteString(s, "bye\n")
+ s.Exit(1)
+ }
+ }
+ if err != nil {
+ return
+ }
+ }
+ }()
+
+ for i := 10; i > 0; i-- {
+ fmt.Fprintf(s, "%v ...\n", i)
+ time.Sleep(time.Second)
+ }
+ s.Exit(0)
+}
+
+func getHostKeys(dir string) (ret []ssh.Signer, err error) {
+ for _, typ := range keyTypes {
+ hostKey, err := hostKeyFileOrCreate(dir, typ)
+ if err != nil {
+ return nil, err
+ }
+ signer, err := gossh.ParsePrivateKey(hostKey)
+ if err != nil {
+ return nil, err
+ }
+ ret = append(ret, signer)
+ }
+ return ret, nil
+}
+
+func hostKeyFileOrCreate(keyDir, typ string) ([]byte, error) {
+ path := filepath.Join(keyDir, "ssh_host_"+typ+"_key")
+ v, err := ioutil.ReadFile(path)
+ if err == nil {
+ return v, nil
+ }
+ if !os.IsNotExist(err) {
+ return nil, err
+ }
+ var priv any
+ switch typ {
+ default:
+ return nil, fmt.Errorf("unsupported key type %q", typ)
+ case "ed25519":
+ _, priv, err = ed25519.GenerateKey(rand.Reader)
+ case "ecdsa":
+ // curve is arbitrary. We pick whatever will at
+ // least pacify clients as the actual encryption
+ // doesn't matter: it's all over WireGuard anyway.
+ curve := elliptic.P256()
+ priv, err = ecdsa.GenerateKey(curve, rand.Reader)
+ case "rsa":
+ // keySize is arbitrary. We pick whatever will at
+ // least pacify clients as the actual encryption
+ // doesn't matter: it's all over WireGuard anyway.
+ const keySize = 2048
+ priv, err = rsa.GenerateKey(rand.Reader, keySize)
+ }
+ if err != nil {
+ return nil, err
+ }
+ mk, err := x509.MarshalPKCS8PrivateKey(priv)
+ if err != nil {
+ return nil, err
+ }
+ pemGen := pem.EncodeToMemory(&pem.Block{Type: "PRIVATE KEY", Bytes: mk})
+ err = os.WriteFile(path, pemGen, 0700)
+ return pemGen, err
+}