Files

225 lines
4.9 KiB
Go
Raw Permalink Normal View History

2020-12-07 21:41:45 -07:00
package boringproxy
2020-09-26 15:56:57 -06:00
import (
2020-10-02 17:09:14 -06:00
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"encoding/pem"
2020-09-29 20:12:54 -06:00
"errors"
2020-10-02 17:09:14 -06:00
"fmt"
2020-09-29 20:12:54 -06:00
"github.com/caddyserver/certmagic"
2020-10-02 17:09:14 -06:00
"golang.org/x/crypto/ssh"
2020-09-29 20:12:54 -06:00
"io/ioutil"
"log"
2020-10-05 23:25:36 -06:00
"os/user"
2020-10-02 17:09:14 -06:00
"strings"
2020-09-29 20:12:54 -06:00
"sync"
2020-09-26 15:56:57 -06:00
)
type TunnelManager struct {
2020-12-07 21:50:33 -07:00
config *Config
2020-10-05 23:25:36 -06:00
db *Database
2020-09-29 20:12:54 -06:00
mutex *sync.Mutex
certConfig *certmagic.Config
2020-10-05 23:25:36 -06:00
user *user.User
2020-09-26 15:56:57 -06:00
}
2020-12-07 21:50:33 -07:00
func NewTunnelManager(config *Config, db *Database, certConfig *certmagic.Config) *TunnelManager {
2020-09-29 20:12:54 -06:00
2020-10-05 23:25:36 -06:00
user, err := user.Current()
2020-09-29 20:12:54 -06:00
if err != nil {
2020-10-05 23:25:36 -06:00
log.Fatalf("Unable to get current user: %v", err)
2020-09-29 20:12:54 -06:00
}
2020-11-27 21:24:48 -07:00
for domainName, tun := range db.GetTunnels() {
if tun.TlsTermination == "server" {
err = certConfig.ManageSync([]string{domainName})
if err != nil {
log.Println("CertMagic error at startup")
log.Println(err)
}
2020-09-29 20:12:54 -06:00
}
}
mutex := &sync.Mutex{}
2020-10-14 09:47:46 -06:00
return &TunnelManager{config, db, mutex, certConfig, user}
2020-09-26 15:56:57 -06:00
}
2020-10-05 23:37:03 -06:00
func (m *TunnelManager) GetTunnels() map[string]Tunnel {
2020-10-06 10:22:03 -06:00
return m.db.GetTunnels()
2020-10-05 23:37:03 -06:00
}
2020-10-20 18:52:32 -06:00
func (m *TunnelManager) RequestCreateTunnel(tunReq Tunnel) (Tunnel, error) {
2020-10-20 18:52:32 -06:00
if tunReq.Domain == "" {
return Tunnel{}, errors.New("Domain required")
}
2020-09-29 23:24:22 -06:00
2020-10-20 18:52:32 -06:00
if tunReq.Owner == "" {
return Tunnel{}, errors.New("Owner required")
}
2020-09-26 15:56:57 -06:00
2020-11-28 11:53:45 -07:00
if tunReq.TlsTermination == "server" {
err := m.certConfig.ManageSync([]string{tunReq.Domain})
if err != nil {
return Tunnel{}, errors.New("Failed to get cert")
}
2020-10-02 16:57:09 -06:00
}
m.mutex.Lock()
defer m.mutex.Unlock()
2020-10-20 18:52:32 -06:00
_, exists := m.db.GetTunnel(tunReq.Domain)
2020-10-02 17:09:14 -06:00
if exists {
2020-10-20 18:52:32 -06:00
return Tunnel{}, errors.New("Tunnel exists for domain " + tunReq.Domain)
2020-10-02 17:09:14 -06:00
}
2020-10-02 16:57:09 -06:00
2020-11-28 11:48:23 -07:00
port, err := randomOpenPort()
2020-10-14 09:47:46 -06:00
if err != nil {
return Tunnel{}, err
}
2020-10-02 16:57:09 -06:00
2020-10-23 20:56:55 -06:00
privKey, err := m.addToAuthorizedKeys(tunReq.Domain, port, tunReq.AllowExternalTcp, tunReq.SshKey)
2020-10-02 17:09:14 -06:00
if err != nil {
2020-10-08 12:51:52 -06:00
return Tunnel{}, err
}
2020-10-20 20:03:59 -06:00
tunReq.ServerAddress = m.config.WebUiDomain
tunReq.ServerPort = m.config.SshServerPort
2020-10-20 20:03:59 -06:00
tunReq.ServerPublicKey = ""
tunReq.Username = m.user.Username
tunReq.TunnelPort = port
tunReq.TunnelPrivateKey = privKey
2020-10-02 16:57:09 -06:00
2020-10-20 20:03:59 -06:00
m.db.SetTunnel(tunReq.Domain, tunReq)
2020-10-08 12:51:52 -06:00
2020-10-20 20:03:59 -06:00
return tunReq, nil
2020-10-02 16:57:09 -06:00
}
func (m *TunnelManager) DeleteTunnel(domain string) error {
2020-09-29 20:12:54 -06:00
m.mutex.Lock()
defer m.mutex.Unlock()
2020-10-05 23:25:36 -06:00
tunnel, exists := m.db.GetTunnel(domain)
if !exists {
return errors.New("Tunnel doesn't exist")
}
2020-10-06 10:22:03 -06:00
m.db.DeleteTunnel(domain)
2020-10-05 23:37:03 -06:00
2020-10-05 23:25:36 -06:00
authKeysPath := fmt.Sprintf("%s/.ssh/authorized_keys", m.user.HomeDir)
2020-10-03 20:40:14 -06:00
akBytes, err := ioutil.ReadFile(authKeysPath)
if err != nil {
return err
}
akStr := string(akBytes)
lines := strings.Split(akStr, "\n")
2020-10-08 12:51:52 -06:00
tunnelId := fmt.Sprintf("boringproxy-%s-%d", domain, tunnel.TunnelPort)
outLines := []string{}
for _, line := range lines {
if strings.Contains(line, tunnelId) {
continue
}
outLines = append(outLines, line)
}
outStr := strings.Join(outLines, "\n")
2020-10-03 20:40:14 -06:00
err = ioutil.WriteFile(authKeysPath, []byte(outStr), 0600)
if err != nil {
return err
}
return nil
2020-09-26 15:56:57 -06:00
}
2020-10-05 23:25:36 -06:00
func (m *TunnelManager) GetPort(domain string) (int, error) {
tunnel, exists := m.db.GetTunnel(domain)
2020-09-26 15:56:57 -06:00
2020-09-29 20:12:54 -06:00
if !exists {
return 0, errors.New("Doesn't exist")
}
2020-09-26 15:56:57 -06:00
2020-10-08 12:51:52 -06:00
return tunnel.TunnelPort, nil
}
2020-10-02 16:57:09 -06:00
2020-10-23 20:56:55 -06:00
func (m *TunnelManager) addToAuthorizedKeys(domain string, port int, allowExternalTcp bool, sshKey string) (string, error) {
2020-10-02 16:57:09 -06:00
2020-10-05 23:25:36 -06:00
authKeysPath := fmt.Sprintf("%s/.ssh/authorized_keys", m.user.HomeDir)
2020-10-03 20:40:14 -06:00
akBytes, err := ioutil.ReadFile(authKeysPath)
2020-10-02 17:09:14 -06:00
if err != nil {
return "", err
}
2020-10-02 16:57:09 -06:00
2020-10-02 17:09:14 -06:00
akStr := string(akBytes)
2020-10-02 16:57:09 -06:00
2020-10-23 20:56:55 -06:00
var privKey string
var pubKey string
if sshKey == "" {
pubKey, privKey, err = MakeSSHKeyPair()
if err != nil {
return "", err
}
pubKey = strings.TrimSpace(pubKey)
} else {
privKey = ""
pubKey = sshKey
2020-10-02 17:09:14 -06:00
}
2020-10-02 16:57:09 -06:00
2020-10-20 19:14:04 -06: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 16:57:09 -06:00
tunnelId := fmt.Sprintf("boringproxy-%s-%d", domain, port)
2020-10-23 20:56:55 -06:00
newAk := fmt.Sprintf("%s%s %s %s\n", akStr, options, pubKey, tunnelId)
2020-10-02 16:57:09 -06:00
2020-10-03 20:40:14 -06:00
err = ioutil.WriteFile(authKeysPath, []byte(newAk), 0600)
2020-10-02 17:09:14 -06:00
if err != nil {
return "", err
}
2020-10-02 16:57:09 -06:00
2020-10-02 17:09:14 -06:00
return privKey, nil
2020-10-02 16:57:09 -06: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 17:09:14 -06:00
privateKey, err := rsa.GenerateKey(rand.Reader, 1024)
if err != nil {
return "", "", err
}
2020-10-02 16:57:09 -06:00
2020-10-02 17:09:14 -06:00
// generate and write private key as PEM
var privKeyBuf strings.Builder
2020-10-02 16:57:09 -06:00
2020-10-02 17:09:14 -06: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 16:57:09 -06:00
2020-10-02 17:09:14 -06:00
// generate and write public key
pub, err := ssh.NewPublicKey(&privateKey.PublicKey)
if err != nil {
return "", "", err
}
2020-10-02 16:57:09 -06:00
2020-10-12 18:37:46 -06:00
pubKey := string(ssh.MarshalAuthorizedKey(pub))
2020-10-02 16:57:09 -06:00
2020-10-12 18:37:46 -06:00
return pubKey, privKeyBuf.String(), nil
2020-10-02 16:57:09 -06:00
}