Compare commits

...

9 Commits

Author SHA1 Message Date
Stavros
9b76a84ee2 feat: add trace logging 2025-10-11 15:27:01 +03:00
Stavros
ed20d2cf51 fix: use correct path for data directory in distroless 2025-10-11 15:18:37 +03:00
Stavros
fc7e395e66 feat: sort configured providers based on name length 2025-10-10 17:16:22 +03:00
Stavros
b940d681c3 feat: use recovery gin middleware in engine 2025-10-10 16:42:19 +03:00
Stavros
a1ec4a69cf fix: remove spaces before checking oauth name and username 2025-10-10 16:28:52 +03:00
github-actions[bot]
4047cea451 docs: regenerate readme sponsors list (#402)
Co-authored-by: GitHub <noreply@github.com>
2025-10-10 15:52:45 +03:00
Stavros
5a4855c12c refactor: move docker connection check to start up 2025-10-10 15:45:04 +03:00
Stavros
05d4dbd68e fix: ensure data directory exists on docker image 2025-10-10 15:34:09 +03:00
Stavros
ae8347fd28 fix: use docker meta flavors for handling latest, prefix and suffix 2025-10-08 23:11:25 +03:00
13 changed files with 70 additions and 26 deletions

View File

@@ -396,6 +396,8 @@ jobs:
uses: docker/metadata-action@v5 uses: docker/metadata-action@v5
with: with:
images: ghcr.io/${{ github.repository_owner }}/tinyauth images: ghcr.io/${{ github.repository_owner }}/tinyauth
flavor: |
latest=false
tags: | tags: |
type=raw,nightly type=raw,nightly
@@ -433,6 +435,8 @@ jobs:
uses: docker/metadata-action@v5 uses: docker/metadata-action@v5
with: with:
images: ghcr.io/${{ github.repository_owner }}/tinyauth images: ghcr.io/${{ github.repository_owner }}/tinyauth
flavor: |
latest=false
tags: | tags: |
type=raw,nightly-distroless type=raw,nightly-distroless

View File

@@ -359,10 +359,13 @@ jobs:
uses: docker/metadata-action@v5 uses: docker/metadata-action@v5
with: with:
images: ghcr.io/${{ github.repository_owner }}/tinyauth images: ghcr.io/${{ github.repository_owner }}/tinyauth
flavor: |
latest=true
prefix=v,onlatest=false
tags: | tags: |
type=semver,pattern={{version}},prefix=v type=semver,pattern={{version}}
type=semver,pattern={{major}},prefix=v type=semver,pattern={{major}}
type=semver,pattern={{major}}.{{minor}},prefix=v type=semver,pattern={{major}}.{{minor}}
- name: Create manifest list and push - name: Create manifest list and push
working-directory: ${{ runner.temp }}/digests working-directory: ${{ runner.temp }}/digests
@@ -398,10 +401,14 @@ jobs:
uses: docker/metadata-action@v5 uses: docker/metadata-action@v5
with: with:
images: ghcr.io/${{ github.repository_owner }}/tinyauth images: ghcr.io/${{ github.repository_owner }}/tinyauth
flavor: |
latest=false
prefix=v,onlatest=false
suffix=-distroless,onlatest=false
tags: | tags: |
type=semver,pattern={{version}},prefix=v,suffix=-distroless type=semver,pattern={{version}}
type=semver,pattern={{major}},prefix=v,suffix=-distroless type=semver,pattern={{major}}
type=semver,pattern={{major}}.{{minor}},prefix=v,suffix=-distroless type=semver,pattern={{major}}.{{minor}}
- name: Create manifest list and push - name: Create manifest list and push
working-directory: ${{ runner.temp }}/digests working-directory: ${{ runner.temp }}/digests

View File

@@ -47,6 +47,8 @@ WORKDIR /tinyauth
COPY --from=builder /tinyauth/tinyauth ./ COPY --from=builder /tinyauth/tinyauth ./
RUN mkdir -p /data
EXPOSE 3000 EXPOSE 3000
VOLUME ["/data"] VOLUME ["/data"]

View File

@@ -38,6 +38,8 @@ COPY ./cmd ./cmd
COPY ./internal ./internal COPY ./internal ./internal
COPY --from=frontend-builder /frontend/dist ./internal/assets/dist COPY --from=frontend-builder /frontend/dist ./internal/assets/dist
RUN mkdir -p data
RUN CGO_ENABLED=0 go build -ldflags "-s -w -X tinyauth/internal/config.Version=${VERSION} -X tinyauth/internal/config.CommitHash=${COMMIT_HASH} -X tinyauth/internal/config.BuildTimestamp=${BUILD_TIMESTAMP}" RUN CGO_ENABLED=0 go build -ldflags "-s -w -X tinyauth/internal/config.Version=${VERSION} -X tinyauth/internal/config.CommitHash=${COMMIT_HASH} -X tinyauth/internal/config.BuildTimestamp=${BUILD_TIMESTAMP}"
# Runner # Runner
@@ -47,6 +49,9 @@ WORKDIR /tinyauth
COPY --from=builder /tinyauth/tinyauth ./ COPY --from=builder /tinyauth/tinyauth ./
# Since it's distroless, we need to copy the data directory from the builder stage
COPY --from=builder /tinyauth/data /data
EXPOSE 3000 EXPOSE 3000
VOLUME ["/data"] VOLUME ["/data"]

View File

@@ -53,7 +53,7 @@ Tinyauth is licensed under the GNU General Public License v3.0. TL;DR — You ma
A big thank you to the following people for providing me with more coffee: A big thank you to the following people for providing me with more coffee:
<!-- sponsors --><a href="https://github.com/erwinkramer"><img src="https:&#x2F;&#x2F;github.com&#x2F;erwinkramer.png" width="64px" alt="User avatar: erwinkramer" /></a>&nbsp;&nbsp;<a href="https://github.com/nicotsx"><img src="https:&#x2F;&#x2F;github.com&#x2F;nicotsx.png" width="64px" alt="User avatar: nicotsx" /></a>&nbsp;&nbsp;<a href="https://github.com/SimpleHomelab"><img src="https:&#x2F;&#x2F;github.com&#x2F;SimpleHomelab.png" width="64px" alt="User avatar: SimpleHomelab" /></a>&nbsp;&nbsp;<a href="https://github.com/jmadden91"><img src="https:&#x2F;&#x2F;github.com&#x2F;jmadden91.png" width="64px" alt="User avatar: jmadden91" /></a>&nbsp;&nbsp;<a href="https://github.com/tribor"><img src="https:&#x2F;&#x2F;github.com&#x2F;tribor.png" width="64px" alt="User avatar: tribor" /></a>&nbsp;&nbsp;<a href="https://github.com/eliasbenb"><img src="https:&#x2F;&#x2F;github.com&#x2F;eliasbenb.png" width="64px" alt="User avatar: eliasbenb" /></a>&nbsp;&nbsp;<a href="https://github.com/afunworm"><img src="https:&#x2F;&#x2F;github.com&#x2F;afunworm.png" width="64px" alt="User avatar: afunworm" /></a>&nbsp;&nbsp;<!-- sponsors --> <!-- sponsors --><a href="https://github.com/erwinkramer"><img src="https:&#x2F;&#x2F;github.com&#x2F;erwinkramer.png" width="64px" alt="User avatar: erwinkramer" /></a>&nbsp;&nbsp;<a href="https://github.com/nicotsx"><img src="https:&#x2F;&#x2F;github.com&#x2F;nicotsx.png" width="64px" alt="User avatar: nicotsx" /></a>&nbsp;&nbsp;<a href="https://github.com/SimpleHomelab"><img src="https:&#x2F;&#x2F;github.com&#x2F;SimpleHomelab.png" width="64px" alt="User avatar: SimpleHomelab" /></a>&nbsp;&nbsp;<a href="https://github.com/jmadden91"><img src="https:&#x2F;&#x2F;github.com&#x2F;jmadden91.png" width="64px" alt="User avatar: jmadden91" /></a>&nbsp;&nbsp;<a href="https://github.com/tribor"><img src="https:&#x2F;&#x2F;github.com&#x2F;tribor.png" width="64px" alt="User avatar: tribor" /></a>&nbsp;&nbsp;<a href="https://github.com/eliasbenb"><img src="https:&#x2F;&#x2F;github.com&#x2F;eliasbenb.png" width="64px" alt="User avatar: eliasbenb" /></a>&nbsp;&nbsp;<a href="https://github.com/afunworm"><img src="https:&#x2F;&#x2F;github.com&#x2F;afunworm.png" width="64px" alt="User avatar: afunworm" /></a>&nbsp;&nbsp;<a href="https://github.com/chip-well"><img src="https:&#x2F;&#x2F;github.com&#x2F;chip-well.png" width="64px" alt="User avatar: chip-well" /></a>&nbsp;&nbsp;<a href="https://github.com/Lancelot-Enguerrand"><img src="https:&#x2F;&#x2F;github.com&#x2F;Lancelot-Enguerrand.png" width="64px" alt="User avatar: Lancelot-Enguerrand" /></a>&nbsp;&nbsp;<!-- sponsors -->
## Acknowledgements ## Acknowledgements

View File

@@ -2,7 +2,7 @@ root = "/tinyauth"
tmp_dir = "tmp" tmp_dir = "tmp"
[build] [build]
pre_cmd = ["mkdir -p internal/assets/dist", "echo 'backend running' > internal/assets/dist/index.html", "go install github.com/go-delve/delve/cmd/dlv@v1.25.0"] pre_cmd = ["mkdir -p internal/assets/dist", "mkdir -p /data", "echo 'backend running' > internal/assets/dist/index.html", "go install github.com/go-delve/delve/cmd/dlv@v1.25.0"]
cmd = "CGO_ENABLED=0 go build -gcflags=\"all=-N -l\" -o tmp/tinyauth ." cmd = "CGO_ENABLED=0 go build -gcflags=\"all=-N -l\" -o tmp/tinyauth ."
bin = "/go/bin/dlv --listen :4000 --headless=true --api-version=2 --accept-multiclient --log=true exec tmp/tinyauth --continue --check-go-version=false" bin = "/go/bin/dlv --listen :4000 --headless=true --api-version=2 --accept-multiclient --log=true exec tmp/tinyauth --continue --check-go-version=false"
include_ext = ["go"] include_ext = ["go"]

View File

@@ -112,6 +112,10 @@ func (c *rootCmd) run(cmd *cobra.Command, args []string) {
log.Logger = log.Level(zerolog.Level(utils.GetLogLevel(conf.LogLevel))) log.Logger = log.Level(zerolog.Level(utils.GetLogLevel(conf.LogLevel)))
log.Info().Str("version", strings.TrimSpace(config.Version)).Msg("Starting Tinyauth") log.Info().Str("version", strings.TrimSpace(config.Version)).Msg("Starting Tinyauth")
if log.Logger.GetLevel() == zerolog.TraceLevel {
log.Warn().Msg("Log level set to trace, this will log sensitive information!")
}
app := bootstrap.NewBootstrapApp(conf) app := bootstrap.NewBootstrapApp(conf)
err = app.Setup() err = app.Setup()

View File

@@ -7,6 +7,7 @@ import (
"net/http" "net/http"
"net/url" "net/url"
"os" "os"
"sort"
"strings" "strings"
"time" "time"
"tinyauth/internal/config" "tinyauth/internal/config"
@@ -157,6 +158,10 @@ func (app *BootstrapApp) Setup() error {
}) })
} }
sort.Slice(configuredProviders, func(i, j int) bool {
return configuredProviders[i].Name < configuredProviders[j].Name
})
if authService.UserAuthConfigured() || ldapService != nil { if authService.UserAuthConfigured() || ldapService != nil {
configuredProviders = append(configuredProviders, controller.Provider{ configuredProviders = append(configuredProviders, controller.Provider{
Name: "Username", Name: "Username",
@@ -173,6 +178,7 @@ func (app *BootstrapApp) Setup() error {
// Create engine // Create engine
engine := gin.New() engine := gin.New()
engine.Use(gin.Recovery())
if len(app.config.TrustedProxies) > 0 { if len(app.config.TrustedProxies) > 0 {
err := engine.SetTrustedProxies(strings.Split(app.config.TrustedProxies, ",")) err := engine.SetTrustedProxies(strings.Split(app.config.TrustedProxies, ","))

View File

@@ -162,7 +162,7 @@ func (controller *OAuthController) oauthCallbackHandler(c *gin.Context) {
var name string var name string
if user.Name != "" { if strings.TrimSpace(user.Name) != "" {
log.Debug().Msg("Using name from OAuth provider") log.Debug().Msg("Using name from OAuth provider")
name = user.Name name = user.Name
} else { } else {
@@ -172,7 +172,7 @@ func (controller *OAuthController) oauthCallbackHandler(c *gin.Context) {
var username string var username string
if user.PreferredUsername != "" { if strings.TrimSpace(user.PreferredUsername) != "" {
log.Debug().Msg("Using preferred username from OAuth provider") log.Debug().Msg("Using preferred username from OAuth provider")
username = user.PreferredUsername username = user.PreferredUsername
} else { } else {

View File

@@ -84,6 +84,8 @@ func (controller *ProxyController) proxyHandler(c *gin.Context) {
return return
} }
log.Trace().Interface("labels", labels).Msg("Labels for resource")
clientIP := c.ClientIP() clientIP := c.ClientIP()
if controller.auth.IsBypassedIP(labels.IP, clientIP) { if controller.auth.IsBypassedIP(labels.IP, clientIP) {
@@ -150,6 +152,8 @@ func (controller *ProxyController) proxyHandler(c *gin.Context) {
userContext = context userContext = context
} }
log.Trace().Interface("context", userContext).Msg("User context from request")
if userContext.Provider == "basic" && userContext.TotpEnabled { if userContext.Provider == "basic" && userContext.TotpEnabled {
log.Debug().Msg("User has TOTP enabled, denying basic auth access") log.Debug().Msg("User has TOTP enabled, denying basic auth access")
userContext.IsLoggedIn = false userContext.IsLoggedIn = false

View File

@@ -318,6 +318,7 @@ func (auth *AuthService) IsInOAuthGroup(c *gin.Context, context config.UserConte
for userGroup := range strings.SplitSeq(context.OAuthGroups, ",") { for userGroup := range strings.SplitSeq(context.OAuthGroups, ",") {
if utils.CheckFilter(requiredGroups, strings.TrimSpace(userGroup)) { if utils.CheckFilter(requiredGroups, strings.TrimSpace(userGroup)) {
log.Trace().Str("group", userGroup).Str("required", requiredGroups).Msg("User group matched")
return true return true
} }
} }

View File

@@ -12,8 +12,9 @@ import (
) )
type DockerService struct { type DockerService struct {
client *client.Client client *client.Client
context context.Context context context.Context
isConnected bool
} }
func NewDockerService() *DockerService { func NewDockerService() *DockerService {
@@ -31,10 +32,24 @@ func (docker *DockerService) Init() error {
docker.client = client docker.client = client
docker.context = ctx docker.context = ctx
_, err = docker.client.Ping(docker.context)
if err != nil {
log.Debug().Err(err).Msg("Docker not connected")
docker.isConnected = false
docker.client = nil
docker.context = nil
return nil
}
docker.isConnected = true
log.Debug().Msg("Docker connected")
return nil return nil
} }
func (docker *DockerService) GetContainers() ([]container.Summary, error) { func (docker *DockerService) getContainers() ([]container.Summary, error) {
containers, err := docker.client.ContainerList(docker.context, container.ListOptions{}) containers, err := docker.client.ContainerList(docker.context, container.ListOptions{})
if err != nil { if err != nil {
return nil, err return nil, err
@@ -42,7 +57,7 @@ func (docker *DockerService) GetContainers() ([]container.Summary, error) {
return containers, nil return containers, nil
} }
func (docker *DockerService) InspectContainer(containerId string) (container.InspectResponse, error) { func (docker *DockerService) inspectContainer(containerId string) (container.InspectResponse, error) {
inspect, err := docker.client.ContainerInspect(docker.context, containerId) inspect, err := docker.client.ContainerInspect(docker.context, containerId)
if err != nil { if err != nil {
return container.InspectResponse{}, err return container.InspectResponse{}, err
@@ -50,26 +65,19 @@ func (docker *DockerService) InspectContainer(containerId string) (container.Ins
return inspect, nil return inspect, nil
} }
func (docker *DockerService) DockerConnected() bool {
_, err := docker.client.Ping(docker.context)
return err == nil
}
func (docker *DockerService) GetLabels(appDomain string) (config.App, error) { func (docker *DockerService) GetLabels(appDomain string) (config.App, error) {
isConnected := docker.DockerConnected() if !docker.isConnected {
if !isConnected {
log.Debug().Msg("Docker not connected, returning empty labels") log.Debug().Msg("Docker not connected, returning empty labels")
return config.App{}, nil return config.App{}, nil
} }
containers, err := docker.GetContainers() containers, err := docker.getContainers()
if err != nil { if err != nil {
return config.App{}, err return config.App{}, err
} }
for _, ctr := range containers { for _, ctr := range containers {
inspect, err := docker.InspectContainer(ctr.ID) inspect, err := docker.inspectContainer(ctr.ID)
if err != nil { if err != nil {
return config.App{}, err return config.App{}, err
} }
@@ -81,12 +89,12 @@ func (docker *DockerService) GetLabels(appDomain string) (config.App, error) {
for appName, appLabels := range labels.Apps { for appName, appLabels := range labels.Apps {
if appLabels.Config.Domain == appDomain { if appLabels.Config.Domain == appDomain {
log.Debug().Str("id", inspect.ID).Msg("Found matching container by domain") log.Debug().Str("id", inspect.ID).Str("name", inspect.Name).Msg("Found matching container by domain")
return appLabels, nil return appLabels, nil
} }
if strings.TrimPrefix(inspect.Name, "/") == appName { if strings.TrimPrefix(inspect.Name, "/") == appName {
log.Debug().Str("id", inspect.ID).Msg("Found matching container by app name") log.Debug().Str("id", inspect.ID).Str("name", inspect.Name).Msg("Found matching container by app name")
return appLabels, nil return appLabels, nil
} }
} }

View File

@@ -12,6 +12,7 @@ import (
"time" "time"
"tinyauth/internal/config" "tinyauth/internal/config"
"github.com/rs/zerolog/log"
"golang.org/x/oauth2" "golang.org/x/oauth2"
) )
@@ -110,6 +111,8 @@ func (generic *GenericOAuthService) Userinfo() (config.Claims, error) {
return user, err return user, err
} }
log.Trace().Str("body", string(body)).Msg("Userinfo response body")
err = json.Unmarshal(body, &user) err = json.Unmarshal(body, &user)
if err != nil { if err != nil {
return user, err return user, err