125 lines
2.6 KiB
Go
125 lines
2.6 KiB
Go
|
package connection
|
||
|
|
||
|
import (
|
||
|
"context"
|
||
|
"crypto/tls"
|
||
|
"fmt"
|
||
|
"net"
|
||
|
"net/http"
|
||
|
"strings"
|
||
|
"time"
|
||
|
|
||
|
"github.com/gorilla/websocket"
|
||
|
log "github.com/sirupsen/logrus"
|
||
|
"github.com/wwt/guac"
|
||
|
"goauthentik.io/internal/config"
|
||
|
"goauthentik.io/internal/constants"
|
||
|
"goauthentik.io/internal/outpost/ak"
|
||
|
)
|
||
|
|
||
|
const guacAddr = "0.0.0.0:4822"
|
||
|
|
||
|
type Connection struct {
|
||
|
log *log.Entry
|
||
|
st *guac.SimpleTunnel
|
||
|
ac *ak.APIController
|
||
|
ws *websocket.Conn
|
||
|
ctx context.Context
|
||
|
ctxCancel context.CancelFunc
|
||
|
OnError func(error)
|
||
|
closing bool
|
||
|
}
|
||
|
|
||
|
func NewConnection(ac *ak.APIController, forChannel string, cfg *guac.Config) (*Connection, error) {
|
||
|
ctx, canc := context.WithCancel(context.Background())
|
||
|
c := &Connection{
|
||
|
ac: ac,
|
||
|
log: log.WithField("connection", forChannel),
|
||
|
ctx: ctx,
|
||
|
ctxCancel: canc,
|
||
|
OnError: func(err error) {},
|
||
|
closing: false,
|
||
|
}
|
||
|
err := c.initGuac(cfg)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
err = c.initSocket(forChannel)
|
||
|
if err != nil {
|
||
|
_ = c.st.Close()
|
||
|
return nil, err
|
||
|
}
|
||
|
c.initMirror()
|
||
|
return c, nil
|
||
|
}
|
||
|
|
||
|
func (c *Connection) initSocket(forChannel string) error {
|
||
|
pathTemplate := "%s://%s/ws/outpost_rac/%s/"
|
||
|
scheme := strings.ReplaceAll(c.ac.Client.GetConfig().Scheme, "http", "ws")
|
||
|
|
||
|
authHeader := fmt.Sprintf("Bearer %s", c.ac.Token())
|
||
|
|
||
|
header := http.Header{
|
||
|
"Authorization": []string{authHeader},
|
||
|
"User-Agent": []string{constants.OutpostUserAgent()},
|
||
|
}
|
||
|
|
||
|
dialer := websocket.Dialer{
|
||
|
Proxy: http.ProxyFromEnvironment,
|
||
|
HandshakeTimeout: 10 * time.Second,
|
||
|
TLSClientConfig: &tls.Config{
|
||
|
InsecureSkipVerify: config.Get().AuthentikInsecure,
|
||
|
},
|
||
|
}
|
||
|
|
||
|
url := fmt.Sprintf(pathTemplate, scheme, c.ac.Client.GetConfig().Host, forChannel)
|
||
|
ws, _, err := dialer.Dial(url, header)
|
||
|
if err != nil {
|
||
|
c.log.WithError(err).Warning("failed to connect websocket")
|
||
|
return err
|
||
|
}
|
||
|
c.ws = ws
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func (c *Connection) initGuac(cfg *guac.Config) error {
|
||
|
addr, err := net.ResolveTCPAddr("tcp", guacAddr)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
conn, err := net.DialTCP("tcp", nil, addr)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
stream := guac.NewStream(conn, guac.SocketTimeout)
|
||
|
|
||
|
err = stream.Handshake(cfg)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
st := guac.NewSimpleTunnel(stream)
|
||
|
c.st = st
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func (c *Connection) initMirror() {
|
||
|
go c.wsToGuacd()
|
||
|
go c.guacdToWs()
|
||
|
}
|
||
|
|
||
|
func (c *Connection) onError(err error) {
|
||
|
if c.closing {
|
||
|
return
|
||
|
}
|
||
|
c.closing = true
|
||
|
e := c.st.Close()
|
||
|
if e != nil {
|
||
|
c.log.WithError(e).Warning("failed to close guacd connection")
|
||
|
}
|
||
|
c.log.WithError(err).Info("removing connection")
|
||
|
c.ctxCancel()
|
||
|
c.OnError(err)
|
||
|
}
|