mirror of
				https://github.com/steveiliop56/tinyauth.git
				synced 2025-11-03 23:55:44 +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,7 +9,9 @@ import (
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
func TestDecodeHeaders(t *testing.T) {
 | 
					func TestDecodeHeaders(t *testing.T) {
 | 
				
			||||||
	// Variables
 | 
						// Variables
 | 
				
			||||||
	expected := config.App{
 | 
						expected := config.AppConfigs{
 | 
				
			||||||
 | 
							Apps: map[string]config.App{
 | 
				
			||||||
 | 
								"foo": {
 | 
				
			||||||
				Config: config.AppConfig{
 | 
									Config: config.AppConfig{
 | 
				
			||||||
					Domain: "example.com",
 | 
										Domain: "example.com",
 | 
				
			||||||
				},
 | 
									},
 | 
				
			||||||
@@ -38,22 +40,24 @@ func TestDecodeHeaders(t *testing.T) {
 | 
				
			|||||||
					Allow: "/public",
 | 
										Allow: "/public",
 | 
				
			||||||
					Block: "/private",
 | 
										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