outpost: separate ak-api and proxy further for future outposts

This commit is contained in:
Jens Langhammer 2021-01-16 21:41:39 +01:00
parent 87b830ff9a
commit 8fef839965
19 changed files with 392 additions and 318 deletions

View File

@ -1,4 +1,4 @@
package cmd
package main
import (
"fmt"
@ -8,18 +8,21 @@ 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
Required environment variables:
- AUTHENTIK_HOST: URL to connect to (format "http://authentik.company")
- AUTHENTIK_TOKEN: Token to authenticate with
- AUTHENTIK_INSECURE: Skip SSL Certificate verification`
- AUTHENTIK_HOST: URL to connect to (format "http://authentik.company")
- 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 {

View File

@ -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
)

View File

@ -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=

View File

@ -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
View 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
}

View File

@ -1,4 +1,4 @@
package server
package ak
import "net/http"

View 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
}

View File

@ -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")
}

View File

@ -1,4 +1,4 @@
package server
package ak
type websocketInstruction int

View File

@ -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
View 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
}

View File

@ -0,0 +1,6 @@
package ak
type Outpost interface {
Start() error
Refresh() error
}

48
outpost/pkg/proxy/api.go Normal file
View 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
}

View File

@ -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)

View 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
}

View File

@ -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"),
}
}

View File

@ -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"
)
@ -15,70 +16,26 @@ import (
type Server struct {
Handlers map[string]*providerBundle
stop chan struct{} // channel for waiting shutdown
logger *log.Entry
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)

View 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
}

View File

@ -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
}