2020-12-07 22:41:45 -06:00
|
|
|
package boringproxy
|
2020-09-26 16:56:57 -05:00
|
|
|
|
|
|
|
import (
|
2021-12-30 21:57:28 -06:00
|
|
|
"context"
|
2020-10-02 18:09:14 -05:00
|
|
|
"crypto/rand"
|
|
|
|
"crypto/rsa"
|
|
|
|
"crypto/x509"
|
|
|
|
"encoding/pem"
|
2020-09-29 21:12:54 -05:00
|
|
|
"errors"
|
2020-10-02 18:09:14 -05:00
|
|
|
"fmt"
|
2020-09-29 21:12:54 -05:00
|
|
|
"github.com/caddyserver/certmagic"
|
2020-10-02 18:09:14 -05:00
|
|
|
"golang.org/x/crypto/ssh"
|
2020-09-29 21:12:54 -05:00
|
|
|
"io/ioutil"
|
|
|
|
"log"
|
2021-02-15 13:24:54 -06:00
|
|
|
"os"
|
2020-10-06 00:25:36 -05:00
|
|
|
"os/user"
|
2020-10-02 18:09:14 -05:00
|
|
|
"strings"
|
2020-09-29 21:12:54 -05:00
|
|
|
"sync"
|
2020-09-26 16:56:57 -05:00
|
|
|
)
|
|
|
|
|
|
|
|
type TunnelManager struct {
|
2020-12-07 22:50:33 -06:00
|
|
|
config *Config
|
2020-10-06 00:25:36 -05:00
|
|
|
db *Database
|
2020-09-29 21:12:54 -05:00
|
|
|
mutex *sync.Mutex
|
|
|
|
certConfig *certmagic.Config
|
2020-10-06 00:25:36 -05:00
|
|
|
user *user.User
|
2020-09-26 16:56:57 -05:00
|
|
|
}
|
|
|
|
|
2020-12-07 22:50:33 -06:00
|
|
|
func NewTunnelManager(config *Config, db *Database, certConfig *certmagic.Config) *TunnelManager {
|
2020-09-29 21:12:54 -05:00
|
|
|
|
2020-10-06 00:25:36 -05:00
|
|
|
user, err := user.Current()
|
2020-09-29 21:12:54 -05:00
|
|
|
if err != nil {
|
2020-10-06 00:25:36 -05:00
|
|
|
log.Fatalf("Unable to get current user: %v", err)
|
2020-09-29 21:12:54 -05:00
|
|
|
}
|
|
|
|
|
2022-01-01 17:32:51 -06:00
|
|
|
if config.autoCerts {
|
|
|
|
for domainName, tun := range db.GetTunnels() {
|
2022-02-24 15:12:09 -06:00
|
|
|
if tun.TlsTermination == "server" || tun.TlsTermination == "server-tls" {
|
2022-01-01 17:32:51 -06:00
|
|
|
err = certConfig.ManageSync(context.Background(), []string{domainName})
|
|
|
|
if err != nil {
|
|
|
|
log.Println("CertMagic error at startup")
|
|
|
|
log.Println(err)
|
|
|
|
}
|
2020-11-27 22:24:48 -06:00
|
|
|
}
|
2020-09-29 21:12:54 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
mutex := &sync.Mutex{}
|
2020-10-14 10:47:46 -05:00
|
|
|
return &TunnelManager{config, db, mutex, certConfig, user}
|
2020-09-26 16:56:57 -05:00
|
|
|
}
|
|
|
|
|
2020-10-06 00:37:03 -05:00
|
|
|
func (m *TunnelManager) GetTunnels() map[string]Tunnel {
|
2020-10-06 11:22:03 -05:00
|
|
|
return m.db.GetTunnels()
|
2020-10-06 00:37:03 -05:00
|
|
|
}
|
|
|
|
|
2020-10-20 19:52:32 -05:00
|
|
|
func (m *TunnelManager) RequestCreateTunnel(tunReq Tunnel) (Tunnel, error) {
|
2020-10-09 11:05:31 -05:00
|
|
|
|
2020-10-20 19:52:32 -05:00
|
|
|
if tunReq.Domain == "" {
|
|
|
|
return Tunnel{}, errors.New("Domain required")
|
|
|
|
}
|
2020-09-30 00:24:22 -05:00
|
|
|
|
2020-10-20 19:52:32 -05:00
|
|
|
if tunReq.Owner == "" {
|
|
|
|
return Tunnel{}, errors.New("Owner required")
|
|
|
|
}
|
2020-09-26 16:56:57 -05:00
|
|
|
|
2022-02-24 15:12:09 -06:00
|
|
|
if tunReq.TlsTermination == "server" || tunReq.TlsTermination == "server-tls" {
|
2022-01-01 17:32:51 -06:00
|
|
|
if m.config.autoCerts {
|
|
|
|
err := m.certConfig.ManageSync(context.Background(), []string{tunReq.Domain})
|
|
|
|
if err != nil {
|
|
|
|
return Tunnel{}, errors.New("Failed to get cert")
|
|
|
|
}
|
2020-11-28 12:53:45 -06:00
|
|
|
}
|
2020-10-02 17:57:09 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
m.mutex.Lock()
|
|
|
|
defer m.mutex.Unlock()
|
|
|
|
|
2021-02-16 19:37:31 -06:00
|
|
|
if tunReq.TunnelPort == 0 {
|
|
|
|
var err error
|
|
|
|
tunReq.TunnelPort, err = randomOpenPort()
|
|
|
|
if err != nil {
|
|
|
|
return Tunnel{}, err
|
|
|
|
}
|
2021-12-03 18:50:02 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
for _, tun := range m.db.GetTunnels() {
|
|
|
|
if tunReq.Domain == tun.Domain {
|
|
|
|
return Tunnel{}, errors.New("Tunnel domain already in use")
|
|
|
|
}
|
|
|
|
|
|
|
|
if tunReq.TunnelPort == tun.TunnelPort {
|
|
|
|
return Tunnel{}, errors.New("Tunnel port already in use")
|
2021-02-16 19:37:31 -06:00
|
|
|
}
|
2020-10-14 10:47:46 -05:00
|
|
|
}
|
2020-10-02 17:57:09 -05:00
|
|
|
|
2021-12-21 14:14:01 -06:00
|
|
|
privKey, err := m.addToAuthorizedKeys(tunReq.Domain, tunReq.TunnelPort, tunReq.AllowExternalTcp)
|
2020-10-02 18:09:14 -05:00
|
|
|
if err != nil {
|
2020-10-08 13:51:52 -05:00
|
|
|
return Tunnel{}, err
|
|
|
|
}
|
|
|
|
|
2020-10-20 21:03:59 -05:00
|
|
|
tunReq.ServerPublicKey = ""
|
|
|
|
tunReq.Username = m.user.Username
|
|
|
|
tunReq.TunnelPrivateKey = privKey
|
2020-10-02 17:57:09 -05:00
|
|
|
|
2020-10-20 21:03:59 -05:00
|
|
|
m.db.SetTunnel(tunReq.Domain, tunReq)
|
2020-10-08 13:51:52 -05:00
|
|
|
|
2020-10-20 21:03:59 -05:00
|
|
|
return tunReq, nil
|
2020-10-02 17:57:09 -05:00
|
|
|
}
|
|
|
|
|
2020-10-02 21:53:46 -05:00
|
|
|
func (m *TunnelManager) DeleteTunnel(domain string) error {
|
2020-09-29 21:12:54 -05:00
|
|
|
m.mutex.Lock()
|
2020-10-02 21:53:46 -05:00
|
|
|
defer m.mutex.Unlock()
|
|
|
|
|
2020-10-06 00:25:36 -05:00
|
|
|
tunnel, exists := m.db.GetTunnel(domain)
|
|
|
|
if !exists {
|
|
|
|
return errors.New("Tunnel doesn't exist")
|
|
|
|
}
|
2020-10-02 21:53:46 -05:00
|
|
|
|
2020-10-06 11:22:03 -05:00
|
|
|
m.db.DeleteTunnel(domain)
|
2020-10-06 00:37:03 -05:00
|
|
|
|
2020-10-06 00:25:36 -05:00
|
|
|
authKeysPath := fmt.Sprintf("%s/.ssh/authorized_keys", m.user.HomeDir)
|
2020-10-03 21:40:14 -05:00
|
|
|
|
|
|
|
akBytes, err := ioutil.ReadFile(authKeysPath)
|
2020-10-02 21:53:46 -05:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
akStr := string(akBytes)
|
|
|
|
|
|
|
|
lines := strings.Split(akStr, "\n")
|
|
|
|
|
2020-10-08 13:51:52 -05:00
|
|
|
tunnelId := fmt.Sprintf("boringproxy-%s-%d", domain, tunnel.TunnelPort)
|
2020-10-02 21:53:46 -05:00
|
|
|
|
|
|
|
outLines := []string{}
|
|
|
|
|
|
|
|
for _, line := range lines {
|
|
|
|
if strings.Contains(line, tunnelId) {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
outLines = append(outLines, line)
|
|
|
|
}
|
|
|
|
|
|
|
|
outStr := strings.Join(outLines, "\n")
|
|
|
|
|
2020-10-03 21:40:14 -05:00
|
|
|
err = ioutil.WriteFile(authKeysPath, []byte(outStr), 0600)
|
2020-10-02 21:53:46 -05:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
2020-09-26 16:56:57 -05:00
|
|
|
}
|
|
|
|
|
2020-10-06 00:25:36 -05:00
|
|
|
func (m *TunnelManager) GetPort(domain string) (int, error) {
|
|
|
|
tunnel, exists := m.db.GetTunnel(domain)
|
2020-09-26 16:56:57 -05:00
|
|
|
|
2020-09-29 21:12:54 -05:00
|
|
|
if !exists {
|
|
|
|
return 0, errors.New("Doesn't exist")
|
|
|
|
}
|
2020-09-26 16:56:57 -05:00
|
|
|
|
2020-10-08 13:51:52 -05:00
|
|
|
return tunnel.TunnelPort, nil
|
2020-09-28 14:46:01 -05:00
|
|
|
}
|
2020-10-02 17:57:09 -05:00
|
|
|
|
2021-12-21 14:14:01 -06:00
|
|
|
func (m *TunnelManager) addToAuthorizedKeys(domain string, port int, allowExternalTcp bool) (string, error) {
|
2020-10-02 17:57:09 -05:00
|
|
|
|
2020-10-06 00:25:36 -05:00
|
|
|
authKeysPath := fmt.Sprintf("%s/.ssh/authorized_keys", m.user.HomeDir)
|
2020-10-03 21:40:14 -05:00
|
|
|
|
2021-02-15 13:24:54 -06:00
|
|
|
akFile, err := os.OpenFile(authKeysPath, os.O_RDWR|os.O_CREATE, 0600)
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
defer akFile.Close()
|
|
|
|
|
|
|
|
akBytes, err := ioutil.ReadAll(akFile)
|
2020-10-02 18:09:14 -05:00
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
2020-10-02 17:57:09 -05:00
|
|
|
|
2020-10-02 18:09:14 -05:00
|
|
|
akStr := string(akBytes)
|
2020-10-02 17:57:09 -05:00
|
|
|
|
2020-10-23 21:56:55 -05:00
|
|
|
var privKey string
|
|
|
|
var pubKey string
|
|
|
|
|
2021-12-21 14:14:01 -06:00
|
|
|
pubKey, privKey, err = MakeSSHKeyPair()
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
2020-10-02 18:09:14 -05:00
|
|
|
}
|
2020-10-02 17:57:09 -05:00
|
|
|
|
2021-12-21 14:14:01 -06:00
|
|
|
pubKey = strings.TrimSpace(pubKey)
|
|
|
|
|
2020-10-20 20:14:04 -05:00
|
|
|
bindAddr := "127.0.0.1"
|
|
|
|
if allowExternalTcp {
|
|
|
|
bindAddr = "0.0.0.0"
|
|
|
|
}
|
|
|
|
|
|
|
|
options := fmt.Sprintf(`command="echo This key permits tunnels only",permitopen="fakehost:1",permitlisten="%s:%d"`, bindAddr, port)
|
2020-10-02 17:57:09 -05:00
|
|
|
|
2020-10-02 21:53:46 -05:00
|
|
|
tunnelId := fmt.Sprintf("boringproxy-%s-%d", domain, port)
|
|
|
|
|
2020-10-23 21:56:55 -05:00
|
|
|
newAk := fmt.Sprintf("%s%s %s %s\n", akStr, options, pubKey, tunnelId)
|
2020-10-02 17:57:09 -05:00
|
|
|
|
2021-12-03 18:38:06 -06:00
|
|
|
// Clear the file
|
|
|
|
err = akFile.Truncate(0)
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
_, err = akFile.Seek(0, 0)
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
|
2021-02-15 13:24:54 -06:00
|
|
|
_, err = akFile.Write([]byte(newAk))
|
2020-10-02 18:09:14 -05:00
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
2020-10-02 17:57:09 -05:00
|
|
|
|
2020-10-02 18:09:14 -05:00
|
|
|
return privKey, nil
|
2020-10-02 17:57:09 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
// Adapted from https://stackoverflow.com/a/34347463/943814
|
|
|
|
// MakeSSHKeyPair make a pair of public and private keys for SSH access.
|
|
|
|
// Public key is encoded in the format for inclusion in an OpenSSH authorized_keys file.
|
|
|
|
// Private Key generated is PEM encoded
|
|
|
|
func MakeSSHKeyPair() (string, string, error) {
|
2020-10-02 18:09:14 -05:00
|
|
|
privateKey, err := rsa.GenerateKey(rand.Reader, 1024)
|
|
|
|
if err != nil {
|
|
|
|
return "", "", err
|
|
|
|
}
|
2020-10-02 17:57:09 -05:00
|
|
|
|
2020-10-02 18:09:14 -05:00
|
|
|
// generate and write private key as PEM
|
|
|
|
var privKeyBuf strings.Builder
|
2020-10-02 17:57:09 -05:00
|
|
|
|
2020-10-02 18:09:14 -05:00
|
|
|
privateKeyPEM := &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(privateKey)}
|
|
|
|
if err := pem.Encode(&privKeyBuf, privateKeyPEM); err != nil {
|
|
|
|
return "", "", err
|
|
|
|
}
|
2020-10-02 17:57:09 -05:00
|
|
|
|
2020-10-02 18:09:14 -05:00
|
|
|
// generate and write public key
|
|
|
|
pub, err := ssh.NewPublicKey(&privateKey.PublicKey)
|
|
|
|
if err != nil {
|
|
|
|
return "", "", err
|
|
|
|
}
|
2020-10-02 17:57:09 -05:00
|
|
|
|
2020-10-12 19:37:46 -05:00
|
|
|
pubKey := string(ssh.MarshalAuthorizedKey(pub))
|
2020-10-02 17:57:09 -05:00
|
|
|
|
2020-10-12 19:37:46 -05:00
|
|
|
return pubKey, privKeyBuf.String(), nil
|
2020-10-02 17:57:09 -05:00
|
|
|
}
|