1cfe1aff13
* root: initial rename * web: rename custom element prefix * root: rename external functions with pb_ prefix * root: fix formatting * root: replace domain with goauthentik.io * proxy: update path * root: rename remaining prefixes * flows: rename file extension * root: pbadmin -> akadmin * docs: fix image filenames * lifecycle: ignore migration files * ci: copy default config from current source before loading last tagged * *: new sentry dsn * tests: fix missing python3.9-dev package * root: add additional migrations for service accounts created by outposts * core: mark system-created service accounts with attribute * policies/expression: fix pb_ replacement not working * web: fix last linting errors, add lit-analyse * policies/expressions: fix lint errors * web: fix sidebar display on screens where not all items fit * proxy: attempt to fix proxy pipeline * proxy: use go env GOPATH to get gopath * lib: fix user_default naming inconsistency * docs: add upgrade docs * docs: update screenshots to use authentik * admin: fix create button on empty-state of outpost * web: fix modal submit not refreshing SiteShell and Table * web: fix height of app-card and height of generic icon * web: fix rendering of subtext * admin: fix version check error not being caught * web: fix worker count not being shown * docs: update screenshots * root: new icon * web: fix lint error * admin: fix linting error * root: migrate coverage config to pyproject
224 lines
5.8 KiB
Go
224 lines
5.8 KiB
Go
package server
|
|
|
|
import (
|
|
"crypto/sha512"
|
|
"encoding/hex"
|
|
"fmt"
|
|
"math/rand"
|
|
"net/http"
|
|
"net/url"
|
|
"os"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/BeryJu/authentik/proxy/pkg"
|
|
"github.com/BeryJu/authentik/proxy/pkg/client"
|
|
"github.com/BeryJu/authentik/proxy/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.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{}) {
|
|
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
|
|
}
|