outpost: separate ak-api and proxy further for future outposts
This commit is contained in:
parent
87b830ff9a
commit
8fef839965
|
@ -1,4 +1,4 @@
|
|||
package cmd
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
@ -8,7 +8,10 @@ import (
|
|||
"os/signal"
|
||||
"time"
|
||||
|
||||
"github.com/BeryJu/authentik/outpost/pkg/server"
|
||||
log "github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/BeryJu/authentik/outpost/pkg/ak"
|
||||
"github.com/BeryJu/authentik/outpost/pkg/proxy"
|
||||
)
|
||||
|
||||
const helpMessage = `authentik proxy
|
||||
|
@ -18,8 +21,8 @@ Required environment variables:
|
|||
- AUTHENTIK_TOKEN: Token to authenticate with
|
||||
- AUTHENTIK_INSECURE: Skip SSL Certificate verification`
|
||||
|
||||
// RunServer main entrypoint, runs the full server
|
||||
func RunServer() {
|
||||
func main() {
|
||||
log.SetLevel(log.DebugLevel)
|
||||
pbURL, found := os.LookupEnv("AUTHENTIK_HOST")
|
||||
if !found {
|
||||
fmt.Println("env AUTHENTIK_HOST not set!")
|
||||
|
@ -42,11 +45,13 @@ func RunServer() {
|
|||
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
|
||||
ac := server.NewAPIController(*pbURLActual, pbToken)
|
||||
ac := ak.NewAPIController(*pbURLActual, pbToken)
|
||||
|
||||
interrupt := make(chan os.Signal, 1)
|
||||
signal.Notify(interrupt, os.Interrupt)
|
||||
|
||||
ac.Server = proxy.NewServer(ac)
|
||||
|
||||
ac.Start()
|
||||
|
||||
for {
|
|
@ -4,6 +4,7 @@ go 1.14
|
|||
|
||||
require (
|
||||
cloud.google.com/go v0.64.0 // indirect
|
||||
github.com/BeryJu/authentik/proxy v0.0.0-20210116180903-8acb9dde5f2f
|
||||
github.com/coreos/go-oidc v2.2.1+incompatible
|
||||
github.com/getsentry/sentry-go v0.9.0
|
||||
github.com/go-openapi/errors v0.19.9
|
||||
|
@ -19,8 +20,10 @@ require (
|
|||
github.com/justinas/alice v1.2.0
|
||||
github.com/kr/pretty v0.2.1 // indirect
|
||||
github.com/magiconair/properties v1.8.4 // indirect
|
||||
github.com/mitchellh/mapstructure v1.4.1 // indirect
|
||||
github.com/oauth2-proxy/oauth2-proxy v0.0.0-20200831161845-e4e5580852dc
|
||||
github.com/pelletier/go-toml v1.8.1 // indirect
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/pquerna/cachecontrol v0.0.0-20200819021114-67c6ae64274f // indirect
|
||||
github.com/recws-org/recws v1.2.2
|
||||
github.com/sirupsen/logrus v1.7.0
|
||||
|
@ -30,11 +33,11 @@ require (
|
|||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
github.com/spf13/viper v1.7.1 // indirect
|
||||
golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de // indirect
|
||||
golang.org/x/mod v0.4.0 // indirect
|
||||
golang.org/x/mod v0.4.1 // indirect
|
||||
golang.org/x/net v0.0.0-20201224014010-6772e930b67b // indirect
|
||||
golang.org/x/sys v0.0.0-20210108172913-0df2131ae363 // indirect
|
||||
golang.org/x/sys v0.0.0-20210113181707-4bcb84eeeb78 // indirect
|
||||
golang.org/x/text v0.3.5 // indirect
|
||||
golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e // indirect
|
||||
golang.org/x/tools v0.0.0-20210115202250-e0d201561e39 // indirect
|
||||
gopkg.in/ini.v1 v1.62.0 // indirect
|
||||
gopkg.in/square/go-jose.v2 v2.5.1 // indirect
|
||||
)
|
||||
|
|
|
@ -36,8 +36,11 @@ cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9
|
|||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
||||
github.com/AndreasBriese/bbloom v0.0.0-20190306092124-e2d15f34fcf9/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8=
|
||||
github.com/BeryJu/authentik v0.0.0-20210108085217-fd6d99f4f999 h1:ymxzvnxKNUomJIRG1VP3I6ls5mWn8r1xWD82bHASEk0=
|
||||
github.com/BeryJu/authentik v0.0.0-20210116180903-8acb9dde5f2f h1:pLOJgn8bIzavtn0h874lys3gs7uk1RnMqMWIttOWY8Y=
|
||||
github.com/BeryJu/authentik/outpost v0.0.0-20210108085217-fd6d99f4f999 h1:XYHeaZx7fm4JNx77MHMO6ek/Gdp+sZa2jIJyjC294Gw=
|
||||
github.com/BeryJu/authentik/proxy v0.0.0-20210108085217-fd6d99f4f999 h1:XYHeaZx7fm4JNx77MHMO6ek/Gdp+sZa2jIJyjC294Gw=
|
||||
github.com/BeryJu/authentik/proxy v0.0.0-20210116180903-8acb9dde5f2f h1:bp617AbteaVcZBXMtr4/A+FSSVGKqRWlTo5chcirq8k=
|
||||
github.com/BeryJu/authentik/proxy v0.0.0-20210116180903-8acb9dde5f2f/go.mod h1:6/VeRMuLHUE3Ywr1uIpjxnmOJJsAfld7OOOi+uocxQw=
|
||||
github.com/Bose/minisentinel v0.0.0-20200130220412-917c5a9223bb h1:ZVN4Iat3runWOFLaBCDVU5a9X/XikSRBosye++6gojw=
|
||||
github.com/Bose/minisentinel v0.0.0-20200130220412-917c5a9223bb/go.mod h1:WsAABbY4HQBgd3mGuG4KMNTbHJCPvx9IVBHzysbknss=
|
||||
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
|
||||
|
@ -508,6 +511,8 @@ github.com/mitchellh/mapstructure v1.3.3 h1:SzB1nHZ2Xi+17FP0zVQBHIZqvwRN9408fJO8
|
|||
github.com/mitchellh/mapstructure v1.3.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||
github.com/mitchellh/mapstructure v1.4.0 h1:7ks8ZkOP5/ujthUsT07rNv+nkLXCQWKNHuwzOAesEks=
|
||||
github.com/mitchellh/mapstructure v1.4.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||
github.com/mitchellh/mapstructure v1.4.1 h1:CpVNEelQCZBooIPDn+AR3NpivK/TIKU8bDxdASFVQag=
|
||||
github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
|
@ -752,6 +757,8 @@ golang.org/x/mod v0.3.0 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4=
|
|||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.0 h1:8pl+sMODzuvGJkmj2W4kZihvVb5mKm8pB/X44PIQHv8=
|
||||
golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.1 h1:Kvvh58BN8Y9/lBi7hTekvtMpm07eUZ0ck5pRHpsMWrY=
|
||||
golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
|
@ -880,6 +887,8 @@ golang.org/x/sys v0.0.0-20201223074533-0d417f636930 h1:vRgIt+nup/B/BwIS0g2oC0haq
|
|||
golang.org/x/sys v0.0.0-20201223074533-0d417f636930/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210108172913-0df2131ae363 h1:wHn06sgWHMO1VsQ8F+KzDJx/JzqfsNLnc+oEi07qD7s=
|
||||
golang.org/x/sys v0.0.0-20210108172913-0df2131ae363/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210113181707-4bcb84eeeb78 h1:nVuTkr9L6Bq62qpUqKo/RnZCFfzDBL0bYo6w9OJUqZY=
|
||||
golang.org/x/sys v0.0.0-20210113181707-4bcb84eeeb78/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
|
@ -955,6 +964,8 @@ golang.org/x/tools v0.0.0-20201226215659-b1c90890d22a h1:pdfjQ7VswBeGam3EpuEJ4e8
|
|||
golang.org/x/tools v0.0.0-20201226215659-b1c90890d22a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e h1:Z2uDrs8MyXUWJbwGc4V+nGjV4Ygo+oubBbWSVQw21/I=
|
||||
golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.0.0-20210115202250-e0d201561e39 h1:BTs2GMGSMWpgtCpv1CE7vkJTv7XcHdcLLnAMu7UbgTY=
|
||||
golang.org/x/tools v0.0.0-20210115202250-e0d201561e39/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7 h1:9zdDQZ7Thm29KFXgAX/+yaf3eVbP7djjWp/dXAppNCc=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
|
|
|
@ -1,11 +1,5 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"github.com/BeryJu/authentik/outpost/cmd"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
func main() {
|
||||
log.SetLevel(log.DebugLevel)
|
||||
cmd.RunServer()
|
||||
|
||||
}
|
||||
|
|
100
outpost/pkg/ak/api.go
Normal file
100
outpost/pkg/ak/api.go
Normal file
|
@ -0,0 +1,100 @@
|
|||
package ak
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
"github.com/BeryJu/authentik/outpost/pkg"
|
||||
"github.com/BeryJu/authentik/outpost/pkg/client"
|
||||
"github.com/BeryJu/authentik/outpost/pkg/client/outposts"
|
||||
"github.com/go-openapi/runtime"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/recws-org/recws"
|
||||
|
||||
httptransport "github.com/go-openapi/runtime/client"
|
||||
"github.com/go-openapi/strfmt"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
const ConfigLogLevel = "log_level"
|
||||
const ConfigErrorReportingEnabled = "error_reporting_enabled"
|
||||
const ConfigErrorReportingEnvironment = "error_reporting_environment"
|
||||
|
||||
// APIController main controller which connects to the authentik api via http and ws
|
||||
type APIController struct {
|
||||
Client *client.Authentik
|
||||
Auth runtime.ClientAuthInfoWriter
|
||||
token string
|
||||
|
||||
Server Outpost
|
||||
|
||||
lastBundleHash string
|
||||
logger *log.Entry
|
||||
|
||||
reloadOffset time.Duration
|
||||
|
||||
wsConn *recws.RecConn
|
||||
}
|
||||
|
||||
// NewAPIController initialise new API Controller instance from URL and API token
|
||||
func NewAPIController(pbURL url.URL, token string) *APIController {
|
||||
transport := httptransport.New(pbURL.Host, client.DefaultBasePath, []string{pbURL.Scheme})
|
||||
transport.Transport = SetUserAgent(getTLSTransport(), fmt.Sprintf("authentik-proxy@%s", pkg.VERSION))
|
||||
|
||||
// create the transport
|
||||
auth := httptransport.BasicAuth("", token)
|
||||
|
||||
// create the API client, with the transport
|
||||
apiClient := client.New(transport, strfmt.Default)
|
||||
|
||||
// Because we don't know the outpost UUID, we simply do a list and pick the first
|
||||
// The service account this token belongs to should only have access to a single outpost
|
||||
outposts, err := apiClient.Outposts.OutpostsOutpostsList(outposts.NewOutpostsOutpostsListParams(), auth)
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
outpost := outposts.Payload.Results[0]
|
||||
doGlobalSetup(outpost.Config.(map[string]interface{}))
|
||||
|
||||
ac := &APIController{
|
||||
Client: apiClient,
|
||||
Auth: auth,
|
||||
token: token,
|
||||
|
||||
logger: log.WithField("component", "ak-api-controller"),
|
||||
|
||||
reloadOffset: time.Duration(rand.Intn(10)) * time.Second,
|
||||
|
||||
lastBundleHash: "",
|
||||
}
|
||||
ac.logger.Debugf("HA Reload offset: %s", ac.reloadOffset)
|
||||
ac.initWS(pbURL, outpost.Pk)
|
||||
return ac
|
||||
}
|
||||
|
||||
func (a *APIController) GetLastBundleHash() string {
|
||||
return a.lastBundleHash
|
||||
}
|
||||
|
||||
// Start Starts all handlers, non-blocking
|
||||
func (a *APIController) Start() error {
|
||||
err := a.Server.Refresh()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to run initial refresh")
|
||||
}
|
||||
go func() {
|
||||
a.logger.Debug("Starting WS Handler...")
|
||||
a.startWSHandler()
|
||||
}()
|
||||
go func() {
|
||||
a.logger.Debug("Starting WS Health notifier...")
|
||||
a.startWSHealth()
|
||||
}()
|
||||
go func() {
|
||||
a.Server.Start()
|
||||
}()
|
||||
return nil
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package server
|
||||
package ak
|
||||
|
||||
import "net/http"
|
||||
|
26
outpost/pkg/ak/api_update.go
Normal file
26
outpost/pkg/ak/api_update.go
Normal file
|
@ -0,0 +1,26 @@
|
|||
package ak
|
||||
|
||||
import (
|
||||
"crypto/sha512"
|
||||
"encoding/hex"
|
||||
|
||||
"github.com/BeryJu/authentik/outpost/pkg/client/outposts"
|
||||
"github.com/BeryJu/authentik/outpost/pkg/models"
|
||||
)
|
||||
|
||||
func (a *APIController) Update() ([]*models.ProxyOutpostConfig, error) {
|
||||
providers, err := a.Client.Outposts.OutpostsProxyList(outposts.NewOutpostsProxyListParams(), a.Auth)
|
||||
if err != nil {
|
||||
a.logger.WithError(err).Error("Failed to fetch providers")
|
||||
return nil, err
|
||||
}
|
||||
// Check provider hash to see if anything is changed
|
||||
hasher := sha512.New()
|
||||
bin, _ := providers.Payload.MarshalBinary()
|
||||
hash := hex.EncodeToString(hasher.Sum(bin))
|
||||
if hash == a.lastBundleHash {
|
||||
return nil, nil
|
||||
}
|
||||
a.lastBundleHash = hash
|
||||
return providers.Payload.Results, nil
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package server
|
||||
package ak
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
|
@ -40,7 +40,7 @@ func (ac *APIController) initWS(pbURL url.URL, outpostUUID strfmt.UUID) {
|
|||
}
|
||||
ws.Dial(fmt.Sprintf(pathTemplate, scheme, pbURL.Host, outpostUUID.String()), header)
|
||||
|
||||
ac.logger.WithField("component", "ws").WithField("outpost", outpostUUID.String()).Debug("connecting to authentik")
|
||||
ac.logger.WithField("component", "ak-ws").WithField("outpost", outpostUUID.String()).Debug("connecting to authentik")
|
||||
|
||||
ac.wsConn = ws
|
||||
// Send hello message with our version
|
||||
|
@ -52,7 +52,7 @@ func (ac *APIController) initWS(pbURL url.URL, outpostUUID strfmt.UUID) {
|
|||
}
|
||||
err := ws.WriteJSON(msg)
|
||||
if err != nil {
|
||||
ac.logger.WithField("component", "ws").WithError(err).Warning("Failed to hello to authentik")
|
||||
ac.logger.WithField("component", "ak-ws").WithError(err).Warning("Failed to hello to authentik")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -87,7 +87,7 @@ func (ac *APIController) startWSHandler() {
|
|||
}
|
||||
if wsMsg.Instruction == WebsocketInstructionTriggerUpdate {
|
||||
time.Sleep(ac.reloadOffset)
|
||||
err := ac.UpdateIfRequired()
|
||||
err := ac.Server.Refresh()
|
||||
if err != nil {
|
||||
ac.logger.WithField("loop", "ws-handler").WithError(err).Debug("Failed to update")
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package server
|
||||
package ak
|
||||
|
||||
type websocketInstruction int
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package server
|
||||
package ak
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
|
@ -13,8 +13,8 @@ import (
|
|||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
func generateSelfSignedCert() (tls.Certificate, error) {
|
||||
|
||||
// GenerateSelfSignedCert Generate a self-signed TLS Certificate, to be used as fallback
|
||||
func GenerateSelfSignedCert() (tls.Certificate, error) {
|
||||
priv, err := rsa.GenerateKey(rand.Reader, 2048)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to generate private key: %v", err)
|
60
outpost/pkg/ak/global.go
Normal file
60
outpost/pkg/ak/global.go
Normal file
|
@ -0,0 +1,60 @@
|
|||
package ak
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/BeryJu/authentik/outpost/pkg"
|
||||
"github.com/getsentry/sentry-go"
|
||||
httptransport "github.com/go-openapi/runtime/client"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
func doGlobalSetup(config map[string]interface{}) {
|
||||
log.SetFormatter(&log.JSONFormatter{})
|
||||
switch config[ConfigLogLevel].(string) {
|
||||
case "debug":
|
||||
log.SetLevel(log.DebugLevel)
|
||||
case "info":
|
||||
log.SetLevel(log.InfoLevel)
|
||||
case "warning":
|
||||
log.SetLevel(log.WarnLevel)
|
||||
case "error":
|
||||
log.SetLevel(log.ErrorLevel)
|
||||
default:
|
||||
log.SetLevel(log.DebugLevel)
|
||||
}
|
||||
log.WithField("version", pkg.VERSION).Info("Starting authentik proxy")
|
||||
|
||||
var dsn string
|
||||
if config[ConfigErrorReportingEnabled].(bool) {
|
||||
dsn = "https://a579bb09306d4f8b8d8847c052d3a1d3@sentry.beryju.org/8"
|
||||
log.Debug("Error reporting enabled")
|
||||
}
|
||||
|
||||
err := sentry.Init(sentry.ClientOptions{
|
||||
Dsn: dsn,
|
||||
Environment: config[ConfigErrorReportingEnvironment].(string),
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatalf("sentry.Init: %s", err)
|
||||
}
|
||||
|
||||
defer sentry.Flush(2 * time.Second)
|
||||
}
|
||||
|
||||
func getTLSTransport() http.RoundTripper {
|
||||
value, set := os.LookupEnv("AUTHENTIK_INSECURE")
|
||||
if !set {
|
||||
value = "false"
|
||||
}
|
||||
tlsTransport, err := httptransport.TLSTransport(httptransport.TLSClientOptions{
|
||||
InsecureSkipVerify: strings.ToLower(value) == "true",
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return tlsTransport
|
||||
}
|
6
outpost/pkg/ak/outpost.go
Normal file
6
outpost/pkg/ak/outpost.go
Normal file
|
@ -0,0 +1,6 @@
|
|||
package ak
|
||||
|
||||
type Outpost interface {
|
||||
Start() error
|
||||
Refresh() error
|
||||
}
|
48
outpost/pkg/proxy/api.go
Normal file
48
outpost/pkg/proxy/api.go
Normal file
|
@ -0,0 +1,48 @@
|
|||
package proxy
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
|
||||
"github.com/BeryJu/authentik/outpost/pkg/models"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
func (s *Server) Refresh() error {
|
||||
providers, err := s.ak.Update()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if providers == nil {
|
||||
s.logger.Debug("Providers have not changed, not updating")
|
||||
return nil
|
||||
}
|
||||
bundles := s.bundleProviders(providers)
|
||||
s.updateHTTPServer(bundles)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Server) bundleProviders(providers []*models.ProxyOutpostConfig) []*providerBundle {
|
||||
bundles := make([]*providerBundle, len(providers))
|
||||
for idx, provider := range providers {
|
||||
externalHost, err := url.Parse(*provider.ExternalHost)
|
||||
if err != nil {
|
||||
log.WithError(err).Warning("Failed to parse URL, skipping provider")
|
||||
}
|
||||
bundles[idx] = &providerBundle{
|
||||
s: s,
|
||||
Host: externalHost.Host,
|
||||
log: log.WithField("component", "proxy-bundle").WithField("provider", provider.Name),
|
||||
}
|
||||
bundles[idx].Build(provider)
|
||||
}
|
||||
return bundles
|
||||
}
|
||||
|
||||
func (s *Server) updateHTTPServer(bundles []*providerBundle) {
|
||||
newMap := make(map[string]*providerBundle)
|
||||
for _, bundle := range bundles {
|
||||
newMap[bundle.Host] = bundle
|
||||
}
|
||||
s.logger.Debug("Swapped maps")
|
||||
s.Handlers = newMap
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package server
|
||||
package proxy
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
@ -11,7 +11,6 @@ import (
|
|||
|
||||
"github.com/BeryJu/authentik/outpost/pkg/client/crypto"
|
||||
"github.com/BeryJu/authentik/outpost/pkg/models"
|
||||
"github.com/BeryJu/authentik/outpost/pkg/proxy"
|
||||
"github.com/jinzhu/copier"
|
||||
"github.com/justinas/alice"
|
||||
"github.com/oauth2-proxy/oauth2-proxy/pkg/apis/options"
|
||||
|
@ -23,11 +22,13 @@ import (
|
|||
type providerBundle struct {
|
||||
http.Handler
|
||||
|
||||
a *APIController
|
||||
proxy *proxy.OAuthProxy
|
||||
s *Server
|
||||
proxy *OAuthProxy
|
||||
Host string
|
||||
|
||||
cert *tls.Certificate
|
||||
|
||||
log *log.Entry
|
||||
}
|
||||
|
||||
func (pb *providerBundle) prepareOpts(provider *models.ProxyOutpostConfig) *options.Options {
|
||||
|
@ -37,7 +38,7 @@ func (pb *providerBundle) prepareOpts(provider *models.ProxyOutpostConfig) *opti
|
|||
return nil
|
||||
}
|
||||
providerOpts := &options.Options{}
|
||||
copier.Copy(&providerOpts, &pb.a.commonOpts)
|
||||
copier.Copy(&providerOpts, getCommonOptions())
|
||||
providerOpts.ClientID = provider.ClientID
|
||||
providerOpts.ClientSecret = provider.ClientSecret
|
||||
|
||||
|
@ -66,22 +67,22 @@ func (pb *providerBundle) prepareOpts(provider *models.ProxyOutpostConfig) *opti
|
|||
}
|
||||
|
||||
if provider.Certificate != nil {
|
||||
pb.a.logger.WithField("provider", provider.ClientID).Debug("Enabling TLS")
|
||||
cert, err := pb.a.client.Crypto.CryptoCertificatekeypairsRead(&crypto.CryptoCertificatekeypairsReadParams{
|
||||
pb.log.WithField("provider", provider.ClientID).Debug("Enabling TLS")
|
||||
cert, err := pb.s.ak.Client.Crypto.CryptoCertificatekeypairsRead(&crypto.CryptoCertificatekeypairsReadParams{
|
||||
Context: context.Background(),
|
||||
KpUUID: *provider.Certificate,
|
||||
}, pb.a.auth)
|
||||
}, pb.s.ak.Auth)
|
||||
if err != nil {
|
||||
pb.a.logger.WithField("provider", provider.ClientID).WithError(err).Warning("Failed to fetch certificate")
|
||||
pb.log.WithField("provider", provider.ClientID).WithError(err).Warning("Failed to fetch certificate")
|
||||
return providerOpts
|
||||
}
|
||||
x509cert, err := tls.X509KeyPair([]byte(*cert.Payload.CertificateData), []byte(cert.Payload.KeyData))
|
||||
if err != nil {
|
||||
pb.a.logger.WithField("provider", provider.ClientID).WithError(err).Warning("Failed to parse certificate")
|
||||
pb.log.WithField("provider", provider.ClientID).WithError(err).Warning("Failed to parse certificate")
|
||||
return providerOpts
|
||||
}
|
||||
pb.cert = &x509cert
|
||||
pb.a.logger.WithField("provider", provider.ClientID).WithField("certificate-key-pair", *cert.Payload.Name).Debug("Loaded certificates")
|
||||
pb.log.WithField("provider", provider.ClientID).WithField("certificate-key-pair", *cert.Payload.Name).Debug("Loaded certificates")
|
||||
}
|
||||
return providerOpts
|
||||
}
|
||||
|
@ -119,7 +120,7 @@ func (pb *providerBundle) Build(provider *models.ProxyOutpostConfig) {
|
|||
log.Printf("%s", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
oauthproxy, err := proxy.NewOAuthProxy(opts)
|
||||
oauthproxy, err := NewOAuthProxy(opts)
|
||||
if err != nil {
|
||||
log.Errorf("ERROR: Failed to initialise OAuth2 Proxy: %v", err)
|
||||
os.Exit(1)
|
20
outpost/pkg/proxy/common.go
Normal file
20
outpost/pkg/proxy/common.go
Normal file
|
@ -0,0 +1,20 @@
|
|||
package proxy
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/oauth2-proxy/oauth2-proxy/pkg/apis/options"
|
||||
)
|
||||
|
||||
func getCommonOptions() *options.Options {
|
||||
commonOpts := options.NewOptions()
|
||||
commonOpts.Cookie.Name = "authentik_proxy"
|
||||
commonOpts.Cookie.Expire = 24 * time.Hour
|
||||
commonOpts.EmailDomains = []string{"*"}
|
||||
commonOpts.ProviderType = "oidc"
|
||||
commonOpts.ProxyPrefix = "/akprox"
|
||||
commonOpts.Logging.SilencePing = true
|
||||
commonOpts.SetAuthorization = false
|
||||
commonOpts.Scope = "openid email profile ak_proxy"
|
||||
return commonOpts
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package server
|
||||
package proxy
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
|
@ -95,7 +95,7 @@ type loggingHandler struct {
|
|||
func LoggingHandler(h http.Handler) http.Handler {
|
||||
return loggingHandler{
|
||||
handler: h,
|
||||
logger: log.WithField("component", "http-server"),
|
||||
logger: log.WithField("component", "proxy-http-server"),
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package server
|
||||
package proxy
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
@ -8,6 +8,7 @@ import (
|
|||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/BeryJu/authentik/outpost/pkg/ak"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
|
@ -17,68 +18,24 @@ type Server struct {
|
|||
|
||||
stop chan struct{} // channel for waiting shutdown
|
||||
logger *log.Entry
|
||||
|
||||
ak *ak.APIController
|
||||
defaultCert tls.Certificate
|
||||
}
|
||||
|
||||
// NewServer initialise a new HTTP Server
|
||||
func NewServer() *Server {
|
||||
defaultCert, err := generateSelfSignedCert()
|
||||
func NewServer(ac *ak.APIController) *Server {
|
||||
defaultCert, err := ak.GenerateSelfSignedCert()
|
||||
if err != nil {
|
||||
log.Warning(err)
|
||||
}
|
||||
return &Server{
|
||||
Handlers: make(map[string]*providerBundle),
|
||||
logger: log.WithField("component", "http-server"),
|
||||
logger: log.WithField("component", "proxy-http-server"),
|
||||
defaultCert: defaultCert,
|
||||
ak: ac,
|
||||
}
|
||||
}
|
||||
|
||||
// ServeHTTP constructs a net.Listener and starts handling HTTP requests
|
||||
func (s *Server) ServeHTTP() {
|
||||
listenAddress := "0.0.0.0:4180"
|
||||
listener, err := net.Listen("tcp", listenAddress)
|
||||
if err != nil {
|
||||
s.logger.Fatalf("FATAL: listen (%s) failed - %s", listenAddress, err)
|
||||
}
|
||||
s.logger.Printf("listening on %s", listener.Addr())
|
||||
s.serve(listener)
|
||||
s.logger.Printf("closing %s", listener.Addr())
|
||||
}
|
||||
|
||||
func (s *Server) getCertificates(info *tls.ClientHelloInfo) (*tls.Certificate, error) {
|
||||
handler, ok := s.Handlers[info.ServerName]
|
||||
if !ok {
|
||||
s.logger.WithField("server-name", info.ServerName).Debug("Handler does not exist")
|
||||
return &s.defaultCert, nil
|
||||
}
|
||||
if handler.cert == nil {
|
||||
s.logger.WithField("server-name", info.ServerName).Debug("Handler does not have a certificate")
|
||||
return &s.defaultCert, nil
|
||||
}
|
||||
return handler.cert, nil
|
||||
}
|
||||
|
||||
// ServeHTTPS constructs a net.Listener and starts handling HTTPS requests
|
||||
func (s *Server) ServeHTTPS() {
|
||||
listenAddress := "0.0.0.0:4443"
|
||||
config := &tls.Config{
|
||||
MinVersion: tls.VersionTLS12,
|
||||
MaxVersion: tls.VersionTLS12,
|
||||
GetCertificate: s.getCertificates,
|
||||
}
|
||||
|
||||
ln, err := net.Listen("tcp", listenAddress)
|
||||
if err != nil {
|
||||
s.logger.Fatalf("FATAL: listen (%s) failed - %s", listenAddress, err)
|
||||
}
|
||||
s.logger.Printf("listening on %s", ln.Addr())
|
||||
|
||||
tlsListener := tls.NewListener(tcpKeepAliveListener{ln.(*net.TCPListener)}, config)
|
||||
s.serve(tlsListener)
|
||||
s.logger.Printf("closing %s", tlsListener.Addr())
|
||||
}
|
||||
|
||||
func (s *Server) handler(w http.ResponseWriter, r *http.Request) {
|
||||
if r.URL.Path == "/akprox/ping" {
|
||||
w.WriteHeader(204)
|
68
outpost/pkg/proxy/server_https.go
Normal file
68
outpost/pkg/proxy/server_https.go
Normal file
|
@ -0,0 +1,68 @@
|
|||
package proxy
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"net"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// ServeHTTP constructs a net.Listener and starts handling HTTP requests
|
||||
func (s *Server) ServeHTTP() {
|
||||
listenAddress := "0.0.0.0:4180"
|
||||
listener, err := net.Listen("tcp", listenAddress)
|
||||
if err != nil {
|
||||
s.logger.Fatalf("FATAL: listen (%s) failed - %s", listenAddress, err)
|
||||
}
|
||||
s.logger.Printf("listening on %s", listener.Addr())
|
||||
s.serve(listener)
|
||||
s.logger.Printf("closing %s", listener.Addr())
|
||||
}
|
||||
|
||||
func (s *Server) getCertificates(info *tls.ClientHelloInfo) (*tls.Certificate, error) {
|
||||
handler, ok := s.Handlers[info.ServerName]
|
||||
if !ok {
|
||||
s.logger.WithField("server-name", info.ServerName).Debug("Handler does not exist")
|
||||
return &s.defaultCert, nil
|
||||
}
|
||||
if handler.cert == nil {
|
||||
s.logger.WithField("server-name", info.ServerName).Debug("Handler does not have a certificate")
|
||||
return &s.defaultCert, nil
|
||||
}
|
||||
return handler.cert, nil
|
||||
}
|
||||
|
||||
// ServeHTTPS constructs a net.Listener and starts handling HTTPS requests
|
||||
func (s *Server) ServeHTTPS() {
|
||||
listenAddress := "0.0.0.0:4443"
|
||||
config := &tls.Config{
|
||||
MinVersion: tls.VersionTLS12,
|
||||
MaxVersion: tls.VersionTLS12,
|
||||
GetCertificate: s.getCertificates,
|
||||
}
|
||||
|
||||
ln, err := net.Listen("tcp", listenAddress)
|
||||
if err != nil {
|
||||
s.logger.Fatalf("FATAL: listen (%s) failed - %s", listenAddress, err)
|
||||
}
|
||||
s.logger.Printf("listening on %s", ln.Addr())
|
||||
|
||||
tlsListener := tls.NewListener(tcpKeepAliveListener{ln.(*net.TCPListener)}, config)
|
||||
s.serve(tlsListener)
|
||||
s.logger.Printf("closing %s", tlsListener.Addr())
|
||||
}
|
||||
|
||||
func (s *Server) Start() error {
|
||||
wg := sync.WaitGroup{}
|
||||
wg.Add(2)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
s.logger.Debug("Starting HTTP Server...")
|
||||
s.ServeHTTP()
|
||||
}()
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
s.logger.Debug("Starting HTTPs Server...")
|
||||
s.ServeHTTPS()
|
||||
}()
|
||||
return nil
|
||||
}
|
|
@ -1,225 +0,0 @@
|
|||
package server
|
||||
|
||||
import (
|
||||
"crypto/sha512"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/BeryJu/authentik/outpost/pkg"
|
||||
"github.com/BeryJu/authentik/outpost/pkg/client"
|
||||
"github.com/BeryJu/authentik/outpost/pkg/client/outposts"
|
||||
"github.com/getsentry/sentry-go"
|
||||
"github.com/go-openapi/runtime"
|
||||
"github.com/recws-org/recws"
|
||||
|
||||
httptransport "github.com/go-openapi/runtime/client"
|
||||
"github.com/go-openapi/strfmt"
|
||||
"github.com/oauth2-proxy/oauth2-proxy/pkg/apis/options"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
const ConfigLogLevel = "log_level"
|
||||
const ConfigErrorReportingEnabled = "error_reporting_enabled"
|
||||
const ConfigErrorReportingEnvironment = "error_reporting_environment"
|
||||
|
||||
// APIController main controller which connects to the authentik api via http and ws
|
||||
type APIController struct {
|
||||
client *client.Authentik
|
||||
auth runtime.ClientAuthInfoWriter
|
||||
token string
|
||||
|
||||
server *Server
|
||||
|
||||
commonOpts *options.Options
|
||||
|
||||
lastBundleHash string
|
||||
logger *log.Entry
|
||||
|
||||
reloadOffset time.Duration
|
||||
|
||||
wsConn *recws.RecConn
|
||||
}
|
||||
|
||||
func getCommonOptions() *options.Options {
|
||||
commonOpts := options.NewOptions()
|
||||
commonOpts.Cookie.Name = "authentik_proxy"
|
||||
commonOpts.Cookie.Expire = 24 * time.Hour
|
||||
commonOpts.EmailDomains = []string{"*"}
|
||||
commonOpts.ProviderType = "oidc"
|
||||
commonOpts.ProxyPrefix = "/akprox"
|
||||
commonOpts.Logging.SilencePing = true
|
||||
commonOpts.SetAuthorization = false
|
||||
commonOpts.Scope = "openid email profile ak_proxy"
|
||||
return commonOpts
|
||||
}
|
||||
|
||||
func doGlobalSetup(config map[string]interface{}) {
|
||||
log.SetFormatter(&log.JSONFormatter{})
|
||||
switch config[ConfigLogLevel].(string) {
|
||||
case "debug":
|
||||
log.SetLevel(log.DebugLevel)
|
||||
case "info":
|
||||
log.SetLevel(log.InfoLevel)
|
||||
case "warning":
|
||||
log.SetLevel(log.WarnLevel)
|
||||
case "error":
|
||||
log.SetLevel(log.ErrorLevel)
|
||||
default:
|
||||
log.SetLevel(log.DebugLevel)
|
||||
}
|
||||
log.WithField("version", pkg.VERSION).Info("Starting authentik proxy")
|
||||
|
||||
var dsn string
|
||||
if config[ConfigErrorReportingEnabled].(bool) {
|
||||
dsn = "https://a579bb09306d4f8b8d8847c052d3a1d3@sentry.beryju.org/8"
|
||||
log.Debug("Error reporting enabled")
|
||||
}
|
||||
|
||||
err := sentry.Init(sentry.ClientOptions{
|
||||
Dsn: dsn,
|
||||
Environment: config[ConfigErrorReportingEnvironment].(string),
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatalf("sentry.Init: %s", err)
|
||||
}
|
||||
|
||||
defer sentry.Flush(2 * time.Second)
|
||||
}
|
||||
|
||||
func getTLSTransport() http.RoundTripper {
|
||||
value, set := os.LookupEnv("AUTHENTIK_INSECURE")
|
||||
if !set {
|
||||
value = "false"
|
||||
}
|
||||
tlsTransport, err := httptransport.TLSTransport(httptransport.TLSClientOptions{
|
||||
InsecureSkipVerify: strings.ToLower(value) == "true",
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return tlsTransport
|
||||
}
|
||||
|
||||
// NewAPIController initialise new API Controller instance from URL and API token
|
||||
func NewAPIController(pbURL url.URL, token string) *APIController {
|
||||
transport := httptransport.New(pbURL.Host, client.DefaultBasePath, []string{pbURL.Scheme})
|
||||
transport.Transport = SetUserAgent(getTLSTransport(), fmt.Sprintf("authentik-proxy@%s", pkg.VERSION))
|
||||
|
||||
// create the transport
|
||||
auth := httptransport.BasicAuth("", token)
|
||||
|
||||
// create the API client, with the transport
|
||||
apiClient := client.New(transport, strfmt.Default)
|
||||
|
||||
// Because we don't know the outpost UUID, we simply do a list and pick the first
|
||||
// The service account this token belongs to should only have access to a single outpost
|
||||
outposts, err := apiClient.Outposts.OutpostsOutpostsList(outposts.NewOutpostsOutpostsListParams(), auth)
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
outpost := outposts.Payload.Results[0]
|
||||
doGlobalSetup(outpost.Config.(map[string]interface{}))
|
||||
|
||||
ac := &APIController{
|
||||
client: apiClient,
|
||||
auth: auth,
|
||||
token: token,
|
||||
|
||||
logger: log.WithField("component", "api-controller"),
|
||||
commonOpts: getCommonOptions(),
|
||||
server: NewServer(),
|
||||
|
||||
reloadOffset: time.Duration(rand.Intn(10)) * time.Second,
|
||||
|
||||
lastBundleHash: "",
|
||||
}
|
||||
ac.logger.Debugf("HA Reload offset: %s", ac.reloadOffset)
|
||||
ac.initWS(pbURL, outpost.Pk)
|
||||
return ac
|
||||
}
|
||||
|
||||
func (a *APIController) bundleProviders() ([]*providerBundle, error) {
|
||||
providers, err := a.client.Outposts.OutpostsProxyList(outposts.NewOutpostsProxyListParams(), a.auth)
|
||||
if err != nil {
|
||||
a.logger.WithError(err).Error("Failed to fetch providers")
|
||||
return nil, err
|
||||
}
|
||||
// Check provider hash to see if anything is changed
|
||||
hasher := sha512.New()
|
||||
bin, _ := providers.Payload.MarshalBinary()
|
||||
hash := hex.EncodeToString(hasher.Sum(bin))
|
||||
if hash == a.lastBundleHash {
|
||||
return nil, nil
|
||||
}
|
||||
a.lastBundleHash = hash
|
||||
|
||||
bundles := make([]*providerBundle, len(providers.Payload.Results))
|
||||
|
||||
for idx, provider := range providers.Payload.Results {
|
||||
externalHost, err := url.Parse(*provider.ExternalHost)
|
||||
if err != nil {
|
||||
log.WithError(err).Warning("Failed to parse URL, skipping provider")
|
||||
}
|
||||
bundles[idx] = &providerBundle{
|
||||
a: a,
|
||||
Host: externalHost.Host,
|
||||
}
|
||||
bundles[idx].Build(provider)
|
||||
}
|
||||
return bundles, nil
|
||||
}
|
||||
|
||||
func (a *APIController) updateHTTPServer(bundles []*providerBundle) {
|
||||
newMap := make(map[string]*providerBundle)
|
||||
for _, bundle := range bundles {
|
||||
newMap[bundle.Host] = bundle
|
||||
}
|
||||
a.logger.Debug("Swapped maps")
|
||||
a.server.Handlers = newMap
|
||||
}
|
||||
|
||||
// UpdateIfRequired Updates the HTTP Server config if required, automatically swaps the handlers
|
||||
func (a *APIController) UpdateIfRequired() error {
|
||||
bundles, err := a.bundleProviders()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if bundles == nil {
|
||||
a.logger.Debug("Providers have not changed, not updating")
|
||||
return nil
|
||||
}
|
||||
a.updateHTTPServer(bundles)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Start Starts all handlers, non-blocking
|
||||
func (a *APIController) Start() error {
|
||||
err := a.UpdateIfRequired()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
go func() {
|
||||
a.logger.Debug("Starting HTTP Server...")
|
||||
a.server.ServeHTTP()
|
||||
}()
|
||||
go func() {
|
||||
a.logger.Debug("Starting HTTPs Server...")
|
||||
a.server.ServeHTTPS()
|
||||
}()
|
||||
go func() {
|
||||
a.logger.Debug("Starting WS Handler...")
|
||||
a.startWSHandler()
|
||||
}()
|
||||
go func() {
|
||||
a.logger.Debug("Starting WS Health notifier...")
|
||||
a.startWSHealth()
|
||||
}()
|
||||
return nil
|
||||
}
|
Reference in a new issue