mirror of
https://github.com/steveiliop56/tinyauth.git
synced 2025-10-28 04:35:40 +00:00
feat: allowed paths label
This commit is contained in:
@@ -132,12 +132,45 @@ func (api *API) SetupRoutes() {
|
|||||||
|
|
||||||
log.Debug().Interface("proxy", proxy.Proxy).Msg("Got proxy")
|
log.Debug().Interface("proxy", proxy.Proxy).Msg("Got proxy")
|
||||||
|
|
||||||
// Get user context
|
|
||||||
userContext := api.Hooks.UseUserContext(c)
|
|
||||||
|
|
||||||
// Check if using basic auth
|
// Check if using basic auth
|
||||||
_, _, basicAuth := c.Request.BasicAuth()
|
_, _, basicAuth := c.Request.BasicAuth()
|
||||||
|
|
||||||
|
// Check if auth is enabled
|
||||||
|
authEnabled, authEnabledErr := api.Auth.AuthEnabled(c)
|
||||||
|
|
||||||
|
// Handle error
|
||||||
|
if authEnabledErr != nil {
|
||||||
|
// Return 501 if nginx is the proxy or if the request is using basic auth
|
||||||
|
if proxy.Proxy == "nginx" || basicAuth {
|
||||||
|
log.Error().Err(authEnabledErr).Msg("Failed to check if auth is enabled")
|
||||||
|
c.JSON(501, gin.H{
|
||||||
|
"status": 501,
|
||||||
|
"message": "Internal Server Error",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return the internal server error page
|
||||||
|
if api.handleError(c, "Failed to check if auth is enabled", authEnabledErr) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If auth is not enabled, return 200
|
||||||
|
if !authEnabled {
|
||||||
|
// The user is allowed to access the app
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"status": 200,
|
||||||
|
"message": "Authenticated",
|
||||||
|
})
|
||||||
|
|
||||||
|
// Stop further processing
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get user context
|
||||||
|
userContext := api.Hooks.UseUserContext(c)
|
||||||
|
|
||||||
// Get headers
|
// Get headers
|
||||||
uri := c.Request.Header.Get("X-Forwarded-Uri")
|
uri := c.Request.Header.Get("X-Forwarded-Uri")
|
||||||
proto := c.Request.Header.Get("X-Forwarded-Proto")
|
proto := c.Request.Header.Get("X-Forwarded-Proto")
|
||||||
@@ -148,7 +181,7 @@ func (api *API) SetupRoutes() {
|
|||||||
log.Debug().Msg("Authenticated")
|
log.Debug().Msg("Authenticated")
|
||||||
|
|
||||||
// Check if user is allowed to access subdomain, if request is nginx.example.com the subdomain (resource) is nginx
|
// Check if user is allowed to access subdomain, if request is nginx.example.com the subdomain (resource) is nginx
|
||||||
appAllowed, appAllowedErr := api.Auth.ResourceAllowed(userContext, host)
|
appAllowed, appAllowedErr := api.Auth.ResourceAllowed(c, userContext)
|
||||||
|
|
||||||
// Check if there was an error
|
// Check if there was an error
|
||||||
if appAllowedErr != nil {
|
if appAllowedErr != nil {
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
package auth
|
package auth
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"regexp"
|
||||||
"slices"
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
"tinyauth/internal/docker"
|
"tinyauth/internal/docker"
|
||||||
"tinyauth/internal/types"
|
"tinyauth/internal/types"
|
||||||
"tinyauth/internal/utils"
|
|
||||||
|
|
||||||
"github.com/gin-contrib/sessions"
|
"github.com/gin-contrib/sessions"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
@@ -139,76 +139,89 @@ func (auth *Auth) UserAuthConfigured() bool {
|
|||||||
return len(auth.Users) > 0
|
return len(auth.Users) > 0
|
||||||
}
|
}
|
||||||
|
|
||||||
func (auth *Auth) ResourceAllowed(context types.UserContext, host string) (bool, error) {
|
func (auth *Auth) ResourceAllowed(c *gin.Context, context types.UserContext) (bool, error) {
|
||||||
// Check if we have access to the Docker API
|
// Get headers
|
||||||
isConnected := auth.Docker.DockerConnected()
|
host := c.Request.Header.Get("X-Forwarded-Host")
|
||||||
|
|
||||||
// If we don't have access, it is assumed that the user has access
|
// Get app id
|
||||||
if !isConnected {
|
|
||||||
log.Debug().Msg("Docker not connected, allowing access")
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the app ID from the host
|
|
||||||
appId := strings.Split(host, ".")[0]
|
appId := strings.Split(host, ".")[0]
|
||||||
|
|
||||||
// Get the containers
|
// Check if resource is allowed
|
||||||
containers, containersErr := auth.Docker.GetContainers()
|
allowed, allowedErr := auth.Docker.ContainerAction(appId, func(labels types.TinyauthLabels) (bool, error) {
|
||||||
|
// If the container has an oauth whitelist, check if the user is in it
|
||||||
|
if context.OAuth && len(labels.OAuthWhitelist) != 0 {
|
||||||
|
log.Debug().Msg("Checking OAuth whitelist")
|
||||||
|
if slices.Contains(labels.OAuthWhitelist, context.Username) {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the container has users, check if the user is in it
|
||||||
|
if len(labels.Users) != 0 {
|
||||||
|
log.Debug().Msg("Checking users")
|
||||||
|
if slices.Contains(labels.Users, context.Username) {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allowed
|
||||||
|
return true, nil
|
||||||
|
})
|
||||||
|
|
||||||
// If there is an error, return false
|
// If there is an error, return false
|
||||||
if containersErr != nil {
|
if allowedErr != nil {
|
||||||
return false, containersErr
|
log.Error().Err(allowedErr).Msg("Error checking if resource is allowed")
|
||||||
|
return false, allowedErr
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Debug().Msg("Got containers")
|
// Return if the resource is allowed
|
||||||
|
return allowed, nil
|
||||||
|
}
|
||||||
|
|
||||||
// Loop through the containers
|
func (auth *Auth) AuthEnabled(c *gin.Context) (bool, error) {
|
||||||
for _, container := range containers {
|
// Get headers
|
||||||
// Inspect the container
|
uri := c.Request.Header.Get("X-Forwarded-Uri")
|
||||||
inspect, inspectErr := auth.Docker.InspectContainer(container.ID)
|
host := c.Request.Header.Get("X-Forwarded-Host")
|
||||||
|
|
||||||
// If there is an error, return false
|
// Get app id
|
||||||
if inspectErr != nil {
|
appId := strings.Split(host, ".")[0]
|
||||||
return false, inspectErr
|
|
||||||
|
// Check if auth is enabled
|
||||||
|
enabled, enabledErr := auth.Docker.ContainerAction(appId, func(labels types.TinyauthLabels) (bool, error) {
|
||||||
|
// Check if the allowed label is empty
|
||||||
|
if labels.Allowed == "" {
|
||||||
|
// Auth enabled
|
||||||
|
return true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the container name (for some reason it is /name)
|
// Compile regex
|
||||||
containerName := strings.Split(inspect.Name, "/")[1]
|
regex, regexErr := regexp.Compile(labels.Allowed)
|
||||||
|
|
||||||
// There is a container with the same name as the app ID
|
// If there is an error, invalid regex, auth enabled
|
||||||
if containerName == appId {
|
if regexErr != nil {
|
||||||
log.Debug().Str("container", containerName).Msg("Found container")
|
log.Warn().Err(regexErr).Msg("Invalid regex")
|
||||||
|
return true, regexErr
|
||||||
// Get only the tinyauth labels in a struct
|
|
||||||
labels := utils.GetTinyauthLabels(inspect.Config.Labels)
|
|
||||||
|
|
||||||
log.Debug().Msg("Got labels")
|
|
||||||
|
|
||||||
// If the container has an oauth whitelist, check if the user is in it
|
|
||||||
if context.OAuth && len(labels.OAuthWhitelist) != 0 {
|
|
||||||
log.Debug().Msg("Checking OAuth whitelist")
|
|
||||||
if slices.Contains(labels.OAuthWhitelist, context.Username) {
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
return false, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the container has users, check if the user is in it
|
|
||||||
if len(labels.Users) != 0 {
|
|
||||||
log.Debug().Msg("Checking users")
|
|
||||||
if slices.Contains(labels.Users, context.Username) {
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
return false, nil
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check if the uri matches the regex
|
||||||
|
if regex.MatchString(uri) {
|
||||||
|
// Auth disabled
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Auth enabled
|
||||||
|
return true, nil
|
||||||
|
})
|
||||||
|
|
||||||
|
// If there is an error, auth enabled
|
||||||
|
if enabledErr != nil {
|
||||||
|
log.Error().Err(enabledErr).Msg("Error checking if auth is enabled")
|
||||||
|
return true, enabledErr
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Debug().Msg("No matching container found, allowing access")
|
return enabled, nil
|
||||||
|
|
||||||
// If no matching container is found, allow access
|
|
||||||
return true, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (auth *Auth) GetBasicAuth(c *gin.Context) *types.User {
|
func (auth *Auth) GetBasicAuth(c *gin.Context) *types.User {
|
||||||
|
|||||||
@@ -4,4 +4,5 @@ package constants
|
|||||||
var TinyauthLabels = []string{
|
var TinyauthLabels = []string{
|
||||||
"tinyauth.oauth.whitelist",
|
"tinyauth.oauth.whitelist",
|
||||||
"tinyauth.users",
|
"tinyauth.users",
|
||||||
|
"tinyauth.allowed",
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,10 +2,14 @@ package docker
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"strings"
|
||||||
|
appTypes "tinyauth/internal/types"
|
||||||
|
"tinyauth/internal/utils"
|
||||||
|
|
||||||
"github.com/docker/docker/api/types"
|
apiTypes "github.com/docker/docker/api/types"
|
||||||
"github.com/docker/docker/api/types/container"
|
containerTypes "github.com/docker/docker/api/types/container"
|
||||||
"github.com/docker/docker/client"
|
"github.com/docker/docker/client"
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewDocker() *Docker {
|
func NewDocker() *Docker {
|
||||||
@@ -34,9 +38,9 @@ func (docker *Docker) Init() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (docker *Docker) GetContainers() ([]types.Container, error) {
|
func (docker *Docker) GetContainers() ([]apiTypes.Container, error) {
|
||||||
// Get the list of containers
|
// Get the list of containers
|
||||||
containers, err := docker.Client.ContainerList(docker.Context, container.ListOptions{})
|
containers, err := docker.Client.ContainerList(docker.Context, containerTypes.ListOptions{})
|
||||||
|
|
||||||
// Check if there was an error
|
// Check if there was an error
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -47,13 +51,13 @@ func (docker *Docker) GetContainers() ([]types.Container, error) {
|
|||||||
return containers, nil
|
return containers, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (docker *Docker) InspectContainer(containerId string) (types.ContainerJSON, error) {
|
func (docker *Docker) InspectContainer(containerId string) (apiTypes.ContainerJSON, error) {
|
||||||
// Inspect the container
|
// Inspect the container
|
||||||
inspect, err := docker.Client.ContainerInspect(docker.Context, containerId)
|
inspect, err := docker.Client.ContainerInspect(docker.Context, containerId)
|
||||||
|
|
||||||
// Check if there was an error
|
// Check if there was an error
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return types.ContainerJSON{}, err
|
return apiTypes.ContainerJSON{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return the inspect
|
// Return the inspect
|
||||||
@@ -65,3 +69,57 @@ func (docker *Docker) DockerConnected() bool {
|
|||||||
_, err := docker.Client.Ping(docker.Context)
|
_, err := docker.Client.Ping(docker.Context)
|
||||||
return err == nil
|
return err == nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (docker *Docker) ContainerAction(appId string, run func(labels appTypes.TinyauthLabels) (bool, error)) (bool, error) {
|
||||||
|
// Check if we have access to the Docker API
|
||||||
|
isConnected := docker.DockerConnected()
|
||||||
|
|
||||||
|
// If we don't have access, it is assumed that the check passed
|
||||||
|
if !isConnected {
|
||||||
|
log.Debug().Msg("Docker not connected, passing check")
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the containers
|
||||||
|
containers, containersErr := docker.GetContainers()
|
||||||
|
|
||||||
|
// If there is an error, return false
|
||||||
|
if containersErr != nil {
|
||||||
|
return false, containersErr
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Debug().Msg("Got containers")
|
||||||
|
|
||||||
|
// Loop through the containers
|
||||||
|
for _, container := range containers {
|
||||||
|
// Inspect the container
|
||||||
|
inspect, inspectErr := docker.InspectContainer(container.ID)
|
||||||
|
|
||||||
|
// If there is an error, return false
|
||||||
|
if inspectErr != nil {
|
||||||
|
return false, inspectErr
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the container name (for some reason it is /name)
|
||||||
|
containerName := strings.Split(inspect.Name, "/")[1]
|
||||||
|
|
||||||
|
// There is a container with the same name as the app ID
|
||||||
|
if containerName == appId {
|
||||||
|
log.Debug().Str("container", containerName).Msg("Found container")
|
||||||
|
|
||||||
|
// Get only the tinyauth labels in a struct
|
||||||
|
labels := utils.GetTinyauthLabels(inspect.Config.Labels)
|
||||||
|
|
||||||
|
log.Debug().Msg("Got labels")
|
||||||
|
|
||||||
|
// Run the function
|
||||||
|
return run(labels)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Debug().Msg("No matching container found, allowing access")
|
||||||
|
|
||||||
|
// If no matching container is found, allow access
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -122,6 +122,7 @@ type SessionCookie struct {
|
|||||||
type TinyauthLabels struct {
|
type TinyauthLabels struct {
|
||||||
OAuthWhitelist []string
|
OAuthWhitelist []string
|
||||||
Users []string
|
Users []string
|
||||||
|
Allowed string
|
||||||
}
|
}
|
||||||
|
|
||||||
// TailscaleQuery is the query parameters for the tailscale endpoint
|
// TailscaleQuery is the query parameters for the tailscale endpoint
|
||||||
|
|||||||
@@ -195,6 +195,8 @@ func GetTinyauthLabels(labels map[string]string) types.TinyauthLabels {
|
|||||||
tinyauthLabels.OAuthWhitelist = strings.Split(value, ",")
|
tinyauthLabels.OAuthWhitelist = strings.Split(value, ",")
|
||||||
case "tinyauth.users":
|
case "tinyauth.users":
|
||||||
tinyauthLabels.Users = strings.Split(value, ",")
|
tinyauthLabels.Users = strings.Split(value, ",")
|
||||||
|
case "tinyauth.allowed":
|
||||||
|
tinyauthLabels.Allowed = value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -298,12 +298,14 @@ func TestGetTinyauthLabels(t *testing.T) {
|
|||||||
labels := map[string]string{
|
labels := map[string]string{
|
||||||
"tinyauth.users": "user1,user2",
|
"tinyauth.users": "user1,user2",
|
||||||
"tinyauth.oauth.whitelist": "user1,user2",
|
"tinyauth.oauth.whitelist": "user1,user2",
|
||||||
|
"tinyauth.allowed": "random",
|
||||||
"random": "random",
|
"random": "random",
|
||||||
}
|
}
|
||||||
|
|
||||||
expected := types.TinyauthLabels{
|
expected := types.TinyauthLabels{
|
||||||
Users: []string{"user1", "user2"},
|
Users: []string{"user1", "user2"},
|
||||||
OAuthWhitelist: []string{"user1", "user2"},
|
OAuthWhitelist: []string{"user1", "user2"},
|
||||||
|
Allowed: "random",
|
||||||
}
|
}
|
||||||
|
|
||||||
result := utils.GetTinyauthLabels(labels)
|
result := utils.GetTinyauthLabels(labels)
|
||||||
|
|||||||
Reference in New Issue
Block a user