mirror of
https://github.com/steveiliop56/tinyauth.git
synced 2025-11-01 06:35:43 +00:00
feat: use decoded headers in proxy controller
This commit is contained in:
@@ -123,14 +123,12 @@ type RedirectQuery struct {
|
|||||||
RedirectURI string `url:"redirect_uri"`
|
RedirectURI string `url:"redirect_uri"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Labels
|
// App config
|
||||||
|
|
||||||
type Labels struct {
|
type AppConfigs struct {
|
||||||
Apps map[string]App
|
Apps map[string]App
|
||||||
}
|
}
|
||||||
|
|
||||||
// App config
|
|
||||||
|
|
||||||
type App struct {
|
type App struct {
|
||||||
Config AppConfig
|
Config AppConfig
|
||||||
Users AppUsers
|
Users AppUsers
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import (
|
|||||||
"tinyauth/internal/config"
|
"tinyauth/internal/config"
|
||||||
"tinyauth/internal/service"
|
"tinyauth/internal/service"
|
||||||
"tinyauth/internal/utils"
|
"tinyauth/internal/utils"
|
||||||
|
"tinyauth/internal/utils/decoders"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/google/go-querystring/query"
|
"github.com/google/go-querystring/query"
|
||||||
@@ -67,6 +68,16 @@ func (controller *ProxyController) proxyHandler(c *gin.Context) {
|
|||||||
proto := c.Request.Header.Get("X-Forwarded-Proto")
|
proto := c.Request.Header.Get("X-Forwarded-Proto")
|
||||||
host := c.Request.Header.Get("X-Forwarded-Host")
|
host := c.Request.Header.Get("X-Forwarded-Host")
|
||||||
|
|
||||||
|
var app config.App
|
||||||
|
|
||||||
|
headers, err := decoders.DecodeHeaders(utils.NormalizeHeaders(c.Request.Header))
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Error().Err(err).Msg("Failed to decode headers")
|
||||||
|
controller.handleError(c, req, isBrowser)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
labels, err := controller.docker.GetLabels(host)
|
labels, err := controller.docker.GetLabels(host)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -75,10 +86,21 @@ func (controller *ProxyController) proxyHandler(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if len(headers.Apps) > 0 {
|
||||||
|
for k, v := range headers.Apps {
|
||||||
|
log.Debug().Str("app", k).Msg("Using headers for app config instead of labels")
|
||||||
|
app = v
|
||||||
|
break
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log.Debug().Msg("No app config found in headers, using labels")
|
||||||
|
app = labels
|
||||||
|
}
|
||||||
|
|
||||||
clientIP := c.ClientIP()
|
clientIP := c.ClientIP()
|
||||||
|
|
||||||
if controller.auth.IsBypassedIP(labels.IP, clientIP) {
|
if controller.auth.IsBypassedIP(app.IP, clientIP) {
|
||||||
controller.setHeaders(c, labels)
|
controller.setHeaders(c, app)
|
||||||
c.JSON(200, gin.H{
|
c.JSON(200, gin.H{
|
||||||
"status": 200,
|
"status": 200,
|
||||||
"message": "Authenticated",
|
"message": "Authenticated",
|
||||||
@@ -86,7 +108,7 @@ func (controller *ProxyController) proxyHandler(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
authEnabled, err := controller.auth.IsAuthEnabled(uri, labels.Path)
|
authEnabled, err := controller.auth.IsAuthEnabled(uri, app.Path)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error().Err(err).Msg("Failed to check if auth is enabled for resource")
|
log.Error().Err(err).Msg("Failed to check if auth is enabled for resource")
|
||||||
@@ -96,7 +118,7 @@ func (controller *ProxyController) proxyHandler(c *gin.Context) {
|
|||||||
|
|
||||||
if !authEnabled {
|
if !authEnabled {
|
||||||
log.Debug().Msg("Authentication disabled for resource, allowing access")
|
log.Debug().Msg("Authentication disabled for resource, allowing access")
|
||||||
controller.setHeaders(c, labels)
|
controller.setHeaders(c, app)
|
||||||
c.JSON(200, gin.H{
|
c.JSON(200, gin.H{
|
||||||
"status": 200,
|
"status": 200,
|
||||||
"message": "Authenticated",
|
"message": "Authenticated",
|
||||||
@@ -104,7 +126,7 @@ func (controller *ProxyController) proxyHandler(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if !controller.auth.CheckIP(labels.IP, clientIP) {
|
if !controller.auth.CheckIP(app.IP, clientIP) {
|
||||||
if req.Proxy == "nginx" || !isBrowser {
|
if req.Proxy == "nginx" || !isBrowser {
|
||||||
c.JSON(401, gin.H{
|
c.JSON(401, gin.H{
|
||||||
"status": 401,
|
"status": 401,
|
||||||
@@ -147,7 +169,7 @@ func (controller *ProxyController) proxyHandler(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if userContext.IsLoggedIn {
|
if userContext.IsLoggedIn {
|
||||||
appAllowed := controller.auth.IsResourceAllowed(c, userContext, labels)
|
appAllowed := controller.auth.IsResourceAllowed(c, userContext, app)
|
||||||
|
|
||||||
if !appAllowed {
|
if !appAllowed {
|
||||||
log.Warn().Str("user", userContext.Username).Str("resource", strings.Split(host, ".")[0]).Msg("User not allowed to access resource")
|
log.Warn().Str("user", userContext.Username).Str("resource", strings.Split(host, ".")[0]).Msg("User not allowed to access resource")
|
||||||
@@ -181,7 +203,7 @@ func (controller *ProxyController) proxyHandler(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if userContext.OAuth {
|
if userContext.OAuth {
|
||||||
groupOK := controller.auth.IsInOAuthGroup(c, userContext, labels.OAuth.Groups)
|
groupOK := controller.auth.IsInOAuthGroup(c, userContext, app.OAuth.Groups)
|
||||||
|
|
||||||
if !groupOK {
|
if !groupOK {
|
||||||
log.Warn().Str("user", userContext.Username).Str("resource", strings.Split(host, ".")[0]).Msg("User OAuth groups do not match resource requirements")
|
log.Warn().Str("user", userContext.Username).Str("resource", strings.Split(host, ".")[0]).Msg("User OAuth groups do not match resource requirements")
|
||||||
@@ -221,7 +243,7 @@ func (controller *ProxyController) proxyHandler(c *gin.Context) {
|
|||||||
c.Header("Remote-Email", utils.SanitizeHeader(userContext.Email))
|
c.Header("Remote-Email", utils.SanitizeHeader(userContext.Email))
|
||||||
c.Header("Remote-Groups", utils.SanitizeHeader(userContext.OAuthGroups))
|
c.Header("Remote-Groups", utils.SanitizeHeader(userContext.OAuthGroups))
|
||||||
|
|
||||||
controller.setHeaders(c, labels)
|
controller.setHeaders(c, app)
|
||||||
|
|
||||||
c.JSON(200, gin.H{
|
c.JSON(200, gin.H{
|
||||||
"status": 200,
|
"status": 200,
|
||||||
|
|||||||
@@ -11,13 +11,13 @@ import (
|
|||||||
|
|
||||||
// Based on: https://github.com/traefik/paerser/blob/master/parser/labels_decode.go (Apache 2.0 License)
|
// Based on: https://github.com/traefik/paerser/blob/master/parser/labels_decode.go (Apache 2.0 License)
|
||||||
|
|
||||||
func DecodeHeaders(headers map[string]string) (config.App, error) {
|
func DecodeHeaders(headers map[string]string) (config.AppConfigs, error) {
|
||||||
var app config.App
|
var app config.AppConfigs
|
||||||
|
|
||||||
err := decodeHeadersHelper(headers, &app, "tinyauth")
|
err := decodeHeadersHelper(headers, &app, "tinyauth", "tinyauth-apps")
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return config.App{}, err
|
return config.AppConfigs{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return app, nil
|
return app, nil
|
||||||
|
|||||||
@@ -9,51 +9,55 @@ import (
|
|||||||
|
|
||||||
func TestDecodeHeaders(t *testing.T) {
|
func TestDecodeHeaders(t *testing.T) {
|
||||||
// Variables
|
// Variables
|
||||||
expected := config.App{
|
expected := config.AppConfigs{
|
||||||
Config: config.AppConfig{
|
Apps: map[string]config.App{
|
||||||
Domain: "example.com",
|
"foo": {
|
||||||
},
|
Config: config.AppConfig{
|
||||||
Users: config.AppUsers{
|
Domain: "example.com",
|
||||||
Allow: "user1,user2",
|
},
|
||||||
Block: "user3",
|
Users: config.AppUsers{
|
||||||
},
|
Allow: "user1,user2",
|
||||||
OAuth: config.AppOAuth{
|
Block: "user3",
|
||||||
Whitelist: "somebody@example.com",
|
},
|
||||||
Groups: "group3",
|
OAuth: config.AppOAuth{
|
||||||
},
|
Whitelist: "somebody@example.com",
|
||||||
IP: config.AppIP{
|
Groups: "group3",
|
||||||
Allow: []string{"10.71.0.1/24", "10.71.0.2"},
|
},
|
||||||
Block: []string{"10.10.10.10", "10.0.0.0/24"},
|
IP: config.AppIP{
|
||||||
Bypass: []string{"192.168.1.1"},
|
Allow: []string{"10.71.0.1/24", "10.71.0.2"},
|
||||||
},
|
Block: []string{"10.10.10.10", "10.0.0.0/24"},
|
||||||
Response: config.AppResponse{
|
Bypass: []string{"192.168.1.1"},
|
||||||
Headers: []string{"X-Foo=Bar", "X-Baz=Qux"},
|
},
|
||||||
BasicAuth: config.AppBasicAuth{
|
Response: config.AppResponse{
|
||||||
Username: "admin",
|
Headers: []string{"X-Foo=Bar", "X-Baz=Qux"},
|
||||||
Password: "password",
|
BasicAuth: config.AppBasicAuth{
|
||||||
PasswordFile: "/path/to/passwordfile",
|
Username: "admin",
|
||||||
|
Password: "password",
|
||||||
|
PasswordFile: "/path/to/passwordfile",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Path: config.AppPath{
|
||||||
|
Allow: "/public",
|
||||||
|
Block: "/private",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Path: config.AppPath{
|
|
||||||
Allow: "/public",
|
|
||||||
Block: "/private",
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
test := map[string]string{
|
test := map[string]string{
|
||||||
"Tinyauth-Config-Domain": "example.com",
|
"Tinyauth-Apps-Foo-Config-Domain": "example.com",
|
||||||
"Tinyauth-Users-Allow": "user1,user2",
|
"Tinyauth-Apps-Foo-Users-Allow": "user1,user2",
|
||||||
"Tinyauth-Users-Block": "user3",
|
"Tinyauth-Apps-Foo-Users-Block": "user3",
|
||||||
"Tinyauth-OAuth-Whitelist": "somebody@example.com",
|
"Tinyauth-Apps-Foo-OAuth-Whitelist": "somebody@example.com",
|
||||||
"Tinyauth-OAuth-Groups": "group3",
|
"Tinyauth-Apps-Foo-OAuth-Groups": "group3",
|
||||||
"Tinyauth-IP-Allow": "10.71.0.1/24,10.71.0.2",
|
"Tinyauth-Apps-Foo-IP-Allow": "10.71.0.1/24,10.71.0.2",
|
||||||
"Tinyauth-IP-Block": "10.10.10.10,10.0.0.0/24",
|
"Tinyauth-Apps-Foo-IP-Block": "10.10.10.10,10.0.0.0/24",
|
||||||
"Tinyauth-IP-Bypass": "192.168.1.1",
|
"Tinyauth-Apps-Foo-IP-Bypass": "192.168.1.1",
|
||||||
"Tinyauth-Response-Headers": "X-Foo=Bar,X-Baz=Qux",
|
"Tinyauth-Apps-Foo-Response-Headers": "X-Foo=Bar,X-Baz=Qux",
|
||||||
"Tinyauth-Response-BasicAuth-Username": "admin",
|
"Tinyauth-Apps-Foo-Response-BasicAuth-Username": "admin",
|
||||||
"Tinyauth-Response-BasicAuth-Password": "password",
|
"Tinyauth-Apps-Foo-Response-BasicAuth-Password": "password",
|
||||||
"Tinyauth-Response-BasicAuth-PasswordFile": "/path/to/passwordfile",
|
"Tinyauth-Apps-Foo-Response-BasicAuth-PasswordFile": "/path/to/passwordfile",
|
||||||
"Tinyauth-Path-Allow": "/public",
|
"Tinyauth-Apps-Foo-Path-Allow": "/public",
|
||||||
"Tinyauth-Path-Block": "/private",
|
"Tinyauth-Apps-Foo-Path-Block": "/private",
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test
|
// Test
|
||||||
|
|||||||
@@ -6,13 +6,13 @@ import (
|
|||||||
"github.com/traefik/paerser/parser"
|
"github.com/traefik/paerser/parser"
|
||||||
)
|
)
|
||||||
|
|
||||||
func DecodeLabels(labels map[string]string) (config.Labels, error) {
|
func DecodeLabels(labels map[string]string) (config.AppConfigs, error) {
|
||||||
var appLabels config.Labels
|
var appLabels config.AppConfigs
|
||||||
|
|
||||||
err := parser.Decode(labels, &appLabels, "tinyauth")
|
err := parser.Decode(labels, &appLabels, "tinyauth", "tinyauth.apps")
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return config.Labels{}, err
|
return config.AppConfigs{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return appLabels, nil
|
return appLabels, nil
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import (
|
|||||||
|
|
||||||
func TestDecodeLabels(t *testing.T) {
|
func TestDecodeLabels(t *testing.T) {
|
||||||
// Variables
|
// Variables
|
||||||
expected := config.Labels{
|
expected := config.AppConfigs{
|
||||||
Apps: map[string]config.App{
|
Apps: map[string]config.App{
|
||||||
"foo": {
|
"foo": {
|
||||||
Config: config.AppConfig{
|
Config: config.AppConfig{
|
||||||
|
|||||||
@@ -32,3 +32,13 @@ func SanitizeHeader(header string) string {
|
|||||||
return -1
|
return -1
|
||||||
}, header)
|
}, header)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func NormalizeHeaders(headers http.Header) map[string]string {
|
||||||
|
var result = make(map[string]string)
|
||||||
|
|
||||||
|
for key, values := range headers {
|
||||||
|
result[key] = strings.Join(values, ",")
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user