mirror of
https://github.com/steveiliop56/tinyauth.git
synced 2025-10-27 20:25:41 +00:00
refactor: move to traefik paerser for label parsing (#197)
* refactor: move to traefik paerser for label parsing * fix: sanitize headers before adding to map * refactor: use splitn in header parser * refactor: ignore containers that failed to get inspected in docker
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
# Site builder
|
||||
FROM oven/bun:1.2.15-alpine AS frontend-builder
|
||||
FROM oven/bun:1.2.16-alpine AS frontend-builder
|
||||
|
||||
WORKDIR /frontend
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM oven/bun:1.1.45-alpine
|
||||
FROM oven/bun:1.2.16-alpine
|
||||
|
||||
WORKDIR /frontend
|
||||
|
||||
|
||||
1
go.mod
1
go.mod
@@ -24,6 +24,7 @@ require (
|
||||
github.com/moby/sys/atomicwriter v0.1.0 // indirect
|
||||
github.com/moby/term v0.5.2 // indirect
|
||||
github.com/morikuni/aec v1.0.0 // indirect
|
||||
github.com/traefik/paerser v0.2.2 // indirect
|
||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
|
||||
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.34.0 // indirect
|
||||
|
||||
2
go.sum
2
go.sum
@@ -238,6 +238,8 @@ github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOf
|
||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
|
||||
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
|
||||
github.com/traefik/paerser v0.2.2 h1:cpzW/ZrQrBh3mdwD/jnp6aXASiUFKOVr6ldP+keJTcQ=
|
||||
github.com/traefik/paerser v0.2.2/go.mod h1:7BBDd4FANoVgaTZG+yh26jI6CA2nds7D/4VTEdIsh24=
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
||||
github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=
|
||||
|
||||
@@ -264,11 +264,11 @@ func (auth *Auth) UserAuthConfigured() bool {
|
||||
return len(auth.Config.Users) > 0
|
||||
}
|
||||
|
||||
func (auth *Auth) ResourceAllowed(c *gin.Context, context types.UserContext, labels types.TinyauthLabels) bool {
|
||||
func (auth *Auth) ResourceAllowed(c *gin.Context, context types.UserContext, labels types.Labels) bool {
|
||||
// Check if oauth is allowed
|
||||
if context.OAuth {
|
||||
log.Debug().Msg("Checking OAuth whitelist")
|
||||
return utils.CheckWhitelist(labels.OAuthWhitelist, context.Email)
|
||||
return utils.CheckWhitelist(labels.OAuth.Whitelist, context.Email)
|
||||
}
|
||||
|
||||
// Check users
|
||||
@@ -277,9 +277,9 @@ func (auth *Auth) ResourceAllowed(c *gin.Context, context types.UserContext, lab
|
||||
return utils.CheckWhitelist(labels.Users, context.Username)
|
||||
}
|
||||
|
||||
func (auth *Auth) OAuthGroup(c *gin.Context, context types.UserContext, labels types.TinyauthLabels) bool {
|
||||
func (auth *Auth) OAuthGroup(c *gin.Context, context types.UserContext, labels types.Labels) bool {
|
||||
// Check if groups are required
|
||||
if labels.OAuthGroups == "" {
|
||||
if labels.OAuth.Groups == "" {
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -294,7 +294,7 @@ func (auth *Auth) OAuthGroup(c *gin.Context, context types.UserContext, labels t
|
||||
|
||||
// For every group check if it is in the required groups
|
||||
for _, group := range oauthGroups {
|
||||
if utils.CheckWhitelist(labels.OAuthGroups, group) {
|
||||
if utils.CheckWhitelist(labels.OAuth.Groups, group) {
|
||||
log.Debug().Str("group", group).Msg("Group is in required groups")
|
||||
return true
|
||||
}
|
||||
@@ -307,7 +307,7 @@ func (auth *Auth) OAuthGroup(c *gin.Context, context types.UserContext, labels t
|
||||
return false
|
||||
}
|
||||
|
||||
func (auth *Auth) AuthEnabled(c *gin.Context, labels types.TinyauthLabels) (bool, error) {
|
||||
func (auth *Auth) AuthEnabled(c *gin.Context, labels types.Labels) (bool, error) {
|
||||
// Get headers
|
||||
uri := c.Request.Header.Get("X-Forwarded-Uri")
|
||||
|
||||
|
||||
@@ -1,14 +1,5 @@
|
||||
package constants
|
||||
|
||||
// TinyauthLabels is a list of labels that can be used in a tinyauth protected container
|
||||
var TinyauthLabels = []string{
|
||||
"tinyauth.oauth.whitelist",
|
||||
"tinyauth.users",
|
||||
"tinyauth.allowed",
|
||||
"tinyauth.headers",
|
||||
"tinyauth.oauth.groups",
|
||||
}
|
||||
|
||||
// Claims are the OIDC supported claims (including preferd username for some reason)
|
||||
type Claims struct {
|
||||
Name string `json:"name"`
|
||||
|
||||
@@ -74,14 +74,14 @@ func (docker *Docker) DockerConnected() bool {
|
||||
return err == nil
|
||||
}
|
||||
|
||||
func (docker *Docker) GetLabels(appId string) (types.TinyauthLabels, error) {
|
||||
func (docker *Docker) GetLabels(appId string) (types.Labels, error) {
|
||||
// Check if we have access to the Docker API
|
||||
isConnected := docker.DockerConnected()
|
||||
|
||||
// If we don't have access, return an empty struct
|
||||
if !isConnected {
|
||||
log.Debug().Msg("Docker not connected, returning empty labels")
|
||||
return types.TinyauthLabels{}, nil
|
||||
return types.Labels{}, nil
|
||||
}
|
||||
|
||||
// Get the containers
|
||||
@@ -89,7 +89,7 @@ func (docker *Docker) GetLabels(appId string) (types.TinyauthLabels, error) {
|
||||
|
||||
// If there is an error, return false
|
||||
if err != nil {
|
||||
return types.TinyauthLabels{}, err
|
||||
return types.Labels{}, err
|
||||
}
|
||||
|
||||
log.Debug().Msg("Got containers")
|
||||
@@ -99,9 +99,10 @@ func (docker *Docker) GetLabels(appId string) (types.TinyauthLabels, error) {
|
||||
// Inspect the container
|
||||
inspect, err := docker.InspectContainer(container.ID)
|
||||
|
||||
// If there is an error, return false
|
||||
// Check if there was an error
|
||||
if err != nil {
|
||||
return types.TinyauthLabels{}, err
|
||||
log.Warn().Str("id", container.ID).Err(err).Msg("Error inspecting container, skipping")
|
||||
continue
|
||||
}
|
||||
|
||||
// Get the container name (for some reason it is /name)
|
||||
@@ -112,7 +113,13 @@ func (docker *Docker) GetLabels(appId string) (types.TinyauthLabels, error) {
|
||||
log.Debug().Str("container", containerName).Msg("Found container")
|
||||
|
||||
// Get only the tinyauth labels in a struct
|
||||
labels := utils.GetTinyauthLabels(inspect.Config.Labels)
|
||||
labels, err := utils.GetLabels(inspect.Config.Labels)
|
||||
|
||||
// Check if there was an error
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("Error parsing labels")
|
||||
return types.Labels{}, err
|
||||
}
|
||||
|
||||
log.Debug().Msg("Got labels")
|
||||
|
||||
@@ -125,5 +132,5 @@ func (docker *Docker) GetLabels(appId string) (types.TinyauthLabels, error) {
|
||||
log.Debug().Msg("No matching container found, returning empty labels")
|
||||
|
||||
// If no matching container is found, return empty labels
|
||||
return types.TinyauthLabels{}, nil
|
||||
return types.Labels{}, nil
|
||||
}
|
||||
|
||||
@@ -114,7 +114,8 @@ func (h *Handlers) AuthHandler(c *gin.Context) {
|
||||
|
||||
// If auth is not enabled, return 200
|
||||
if !authEnabled {
|
||||
for key, value := range labels.Headers {
|
||||
headersParsed := utils.ParseHeaders(labels.Headers)
|
||||
for key, value := range headersParsed {
|
||||
log.Debug().Str("key", key).Str("value", value).Msg("Setting header")
|
||||
c.Header(key, utils.SanitizeHeader(value))
|
||||
}
|
||||
@@ -236,7 +237,8 @@ func (h *Handlers) AuthHandler(c *gin.Context) {
|
||||
c.Header("Remote-Groups", utils.SanitizeHeader(userContext.OAuthGroups))
|
||||
|
||||
// Set the rest of the headers
|
||||
for key, value := range labels.Headers {
|
||||
parsedHeaders := utils.ParseHeaders(labels.Headers)
|
||||
for key, value := range parsedHeaders {
|
||||
log.Debug().Str("key", key).Str("value", value).Msg("Setting header")
|
||||
c.Header(key, utils.SanitizeHeader(value))
|
||||
}
|
||||
|
||||
@@ -92,3 +92,17 @@ type AuthConfig struct {
|
||||
type HooksConfig struct {
|
||||
Domain string
|
||||
}
|
||||
|
||||
// OAuthLabels is a list of labels that can be used in a tinyauth protected container
|
||||
type OAuthLabels struct {
|
||||
Whitelist string
|
||||
Groups string
|
||||
}
|
||||
|
||||
// Labels is a struct that contains the labels for a tinyauth protected container
|
||||
type Labels struct {
|
||||
Users string
|
||||
Allowed string
|
||||
Headers []string
|
||||
OAuth OAuthLabels
|
||||
}
|
||||
|
||||
@@ -32,15 +32,6 @@ type SessionCookie struct {
|
||||
OAuthGroups string
|
||||
}
|
||||
|
||||
// TinyauthLabels is the labels for the tinyauth container
|
||||
type TinyauthLabels struct {
|
||||
OAuthWhitelist string
|
||||
Users string
|
||||
Allowed string
|
||||
Headers map[string]string
|
||||
OAuthGroups string
|
||||
}
|
||||
|
||||
// UserContext is the context for the user
|
||||
type UserContext struct {
|
||||
Username string
|
||||
|
||||
@@ -5,11 +5,11 @@ import (
|
||||
"net/url"
|
||||
"os"
|
||||
"regexp"
|
||||
"slices"
|
||||
"strings"
|
||||
"tinyauth/internal/constants"
|
||||
"tinyauth/internal/types"
|
||||
|
||||
"github.com/traefik/paerser/parser"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
@@ -174,45 +174,43 @@ func GetUsers(conf string, file string) (types.Users, error) {
|
||||
return ParseUsers(users)
|
||||
}
|
||||
|
||||
// Parse the docker labels to the tinyauth labels struct
|
||||
func GetTinyauthLabels(labels map[string]string) types.TinyauthLabels {
|
||||
// Create a new tinyauth labels struct
|
||||
var tinyauthLabels types.TinyauthLabels
|
||||
// Parse the headers in a map[string]string format
|
||||
func ParseHeaders(headers []string) map[string]string {
|
||||
// Create a map to store the headers
|
||||
headerMap := make(map[string]string)
|
||||
|
||||
// Loop through the labels
|
||||
for label, value := range labels {
|
||||
|
||||
// Check if the label is in the tinyauth labels
|
||||
if slices.Contains(constants.TinyauthLabels, label) {
|
||||
|
||||
log.Debug().Str("label", label).Msg("Found label")
|
||||
|
||||
// Add the label value to the tinyauth labels struct
|
||||
switch label {
|
||||
case "tinyauth.oauth.whitelist":
|
||||
tinyauthLabels.OAuthWhitelist = value
|
||||
case "tinyauth.users":
|
||||
tinyauthLabels.Users = value
|
||||
case "tinyauth.allowed":
|
||||
tinyauthLabels.Allowed = value
|
||||
case "tinyauth.headers":
|
||||
tinyauthLabels.Headers = make(map[string]string)
|
||||
headers := strings.Split(value, ",")
|
||||
for _, header := range headers {
|
||||
headerSplit := strings.Split(header, "=")
|
||||
if len(headerSplit) != 2 {
|
||||
continue
|
||||
}
|
||||
tinyauthLabels.Headers[headerSplit[0]] = headerSplit[1]
|
||||
}
|
||||
case "tinyauth.oauth.groups":
|
||||
tinyauthLabels.OAuthGroups = value
|
||||
}
|
||||
// Loop through the headers
|
||||
for _, header := range headers {
|
||||
split := strings.SplitN(header, "=", 2)
|
||||
if len(split) != 2 {
|
||||
log.Warn().Str("header", header).Msg("Invalid header format, skipping")
|
||||
continue
|
||||
}
|
||||
key := SanitizeHeader(strings.TrimSpace(split[0]))
|
||||
value := SanitizeHeader(strings.TrimSpace(split[1]))
|
||||
headerMap[key] = value
|
||||
}
|
||||
|
||||
// Return the tinyauth labels
|
||||
return tinyauthLabels
|
||||
// Return the header map
|
||||
return headerMap
|
||||
}
|
||||
|
||||
// Get labels parses a map of labels into a struct with only the needed labels
|
||||
func GetLabels(labels map[string]string) (types.Labels, error) {
|
||||
// Create a new labels struct
|
||||
var labelsParsed types.Labels
|
||||
|
||||
// Decode the labels into the labels struct
|
||||
err := parser.Decode(labels, &labelsParsed, "tinyauth", "tinyauth.users", "tinyauth.allowed", "tinyauth.headers", "tinyauth.oauth")
|
||||
|
||||
// Check if there was an error
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("Error parsing labels")
|
||||
return types.Labels{}, err
|
||||
}
|
||||
|
||||
// Return the labels struct
|
||||
return labelsParsed, nil
|
||||
}
|
||||
|
||||
// Check if any of the OAuth providers are configured based on the client id and secret
|
||||
|
||||
@@ -279,29 +279,35 @@ func TestGetUsers(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// Test the tinyauth labels function
|
||||
func TestGetTinyauthLabels(t *testing.T) {
|
||||
t.Log("Testing get tinyauth labels with a valid map")
|
||||
// Test the get labels function
|
||||
func TestGetLabels(t *testing.T) {
|
||||
t.Log("Testing get labels with a valid map")
|
||||
|
||||
// Test the get tinyauth labels function with a valid map
|
||||
labels := map[string]string{
|
||||
"tinyauth.users": "user1,user2",
|
||||
"tinyauth.oauth.whitelist": "/regex/",
|
||||
"tinyauth.allowed": "random",
|
||||
"random": "random",
|
||||
"tinyauth.headers": "X-Header=value",
|
||||
"tinyauth.oauth.groups": "group1,group2",
|
||||
}
|
||||
|
||||
expected := types.TinyauthLabels{
|
||||
Users: "user1,user2",
|
||||
OAuthWhitelist: "/regex/",
|
||||
Allowed: "random",
|
||||
Headers: map[string]string{
|
||||
"X-Header": "value",
|
||||
expected := types.Labels{
|
||||
Users: "user1,user2",
|
||||
Allowed: "random",
|
||||
Headers: []string{"X-Header=value"},
|
||||
OAuth: types.OAuthLabels{
|
||||
Whitelist: "/regex/",
|
||||
Groups: "group1,group2",
|
||||
},
|
||||
}
|
||||
|
||||
result := utils.GetTinyauthLabels(labels)
|
||||
result, err := utils.GetLabels(labels)
|
||||
|
||||
// Check if there was an error
|
||||
if err != nil {
|
||||
t.Fatalf("Error getting labels: %v", err)
|
||||
}
|
||||
|
||||
// Check if the result is equal to the expected
|
||||
if !reflect.DeepEqual(expected, result) {
|
||||
|
||||
Reference in New Issue
Block a user