mirror of
https://github.com/steveiliop56/tinyauth.git
synced 2026-06-06 03:20:24 +00:00
Compare commits
14 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 5c37b488fa | |||
| c3461131f5 | |||
| 3f584ca741 | |||
| 36d0ffc2b5 | |||
| 37b79735f0 | |||
| 09540fbe6e | |||
| 8e60a2e28e | |||
| 9619024c37 | |||
| 1c305bacca | |||
| e532cde2b6 | |||
| 2737a25227 | |||
| 7aa25210f5 | |||
| 55bef72639 | |||
| ae17bd3b66 |
@@ -101,6 +101,10 @@ TINYAUTH_OAUTH_PROVIDERS_name_CLIENTID=
|
|||||||
TINYAUTH_OAUTH_PROVIDERS_name_CLIENTSECRET=
|
TINYAUTH_OAUTH_PROVIDERS_name_CLIENTSECRET=
|
||||||
# Path to the file containing the OAuth client secret.
|
# Path to the file containing the OAuth client secret.
|
||||||
TINYAUTH_OAUTH_PROVIDERS_name_CLIENTSECRETFILE=
|
TINYAUTH_OAUTH_PROVIDERS_name_CLIENTSECRETFILE=
|
||||||
|
# Comma-separated list of allowed OAuth domains for this provider.
|
||||||
|
TINYAUTH_OAUTH_PROVIDERS_name_WHITELIST=
|
||||||
|
# Path to the OAuth whitelist file for this provider.
|
||||||
|
TINYAUTH_OAUTH_PROVIDERS_name_WHITELISTFILE=
|
||||||
# OAuth scopes.
|
# OAuth scopes.
|
||||||
TINYAUTH_OAUTH_PROVIDERS_name_SCOPES=
|
TINYAUTH_OAUTH_PROVIDERS_name_SCOPES=
|
||||||
# OAuth redirect URL.
|
# OAuth redirect URL.
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ jobs:
|
|||||||
run: go mod download
|
run: go mod download
|
||||||
|
|
||||||
- name: Setup sqlc
|
- name: Setup sqlc
|
||||||
uses: sqlc-dev/setup-sqlc@v4
|
uses: sqlc-dev/setup-sqlc@v5
|
||||||
with:
|
with:
|
||||||
sqlc-version: "1.31.1"
|
sqlc-version: "1.31.1"
|
||||||
|
|
||||||
@@ -62,6 +62,6 @@ jobs:
|
|||||||
run: go test -coverprofile=coverage.txt -v ./...
|
run: go test -coverprofile=coverage.txt -v ./...
|
||||||
|
|
||||||
- name: Upload coverage reports to Codecov
|
- name: Upload coverage reports to Codecov
|
||||||
uses: codecov/codecov-action@57e3a136b779b570ffcdbf80b3bdc90e7fab3de2 # v6
|
uses: codecov/codecov-action@e79a6962e0d4c0c17b229090214935d2e33f8354 # v6
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.CODECOV_TOKEN }}
|
token: ${{ secrets.CODECOV_TOKEN }}
|
||||||
|
|||||||
@@ -156,7 +156,7 @@ jobs:
|
|||||||
images: ghcr.io/${{ github.repository_owner }}/tinyauth
|
images: ghcr.io/${{ github.repository_owner }}/tinyauth
|
||||||
|
|
||||||
- name: Login to GitHub Container Registry
|
- name: Login to GitHub Container Registry
|
||||||
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4
|
uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4
|
||||||
with:
|
with:
|
||||||
registry: ghcr.io
|
registry: ghcr.io
|
||||||
username: ${{ github.repository_owner }}
|
username: ${{ github.repository_owner }}
|
||||||
@@ -166,7 +166,7 @@ jobs:
|
|||||||
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4
|
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4
|
||||||
|
|
||||||
- name: Build and push
|
- name: Build and push
|
||||||
uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7
|
uses: docker/build-push-action@f9f3042f7e2789586610d6e8b85c8f03e5195baf # v7
|
||||||
id: build
|
id: build
|
||||||
with:
|
with:
|
||||||
platforms: linux/amd64
|
platforms: linux/amd64
|
||||||
@@ -214,7 +214,7 @@ jobs:
|
|||||||
images: ghcr.io/${{ github.repository_owner }}/tinyauth
|
images: ghcr.io/${{ github.repository_owner }}/tinyauth
|
||||||
|
|
||||||
- name: Login to GitHub Container Registry
|
- name: Login to GitHub Container Registry
|
||||||
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4
|
uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4
|
||||||
with:
|
with:
|
||||||
registry: ghcr.io
|
registry: ghcr.io
|
||||||
username: ${{ github.repository_owner }}
|
username: ${{ github.repository_owner }}
|
||||||
@@ -224,7 +224,7 @@ jobs:
|
|||||||
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4
|
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4
|
||||||
|
|
||||||
- name: Build and push
|
- name: Build and push
|
||||||
uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7
|
uses: docker/build-push-action@f9f3042f7e2789586610d6e8b85c8f03e5195baf # v7
|
||||||
id: build
|
id: build
|
||||||
with:
|
with:
|
||||||
platforms: linux/amd64
|
platforms: linux/amd64
|
||||||
@@ -272,7 +272,7 @@ jobs:
|
|||||||
images: ghcr.io/${{ github.repository_owner }}/tinyauth
|
images: ghcr.io/${{ github.repository_owner }}/tinyauth
|
||||||
|
|
||||||
- name: Login to GitHub Container Registry
|
- name: Login to GitHub Container Registry
|
||||||
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4
|
uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4
|
||||||
with:
|
with:
|
||||||
registry: ghcr.io
|
registry: ghcr.io
|
||||||
username: ${{ github.repository_owner }}
|
username: ${{ github.repository_owner }}
|
||||||
@@ -282,7 +282,7 @@ jobs:
|
|||||||
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4
|
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4
|
||||||
|
|
||||||
- name: Build and push
|
- name: Build and push
|
||||||
uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7
|
uses: docker/build-push-action@f9f3042f7e2789586610d6e8b85c8f03e5195baf # v7
|
||||||
id: build
|
id: build
|
||||||
with:
|
with:
|
||||||
platforms: linux/arm64
|
platforms: linux/arm64
|
||||||
@@ -330,7 +330,7 @@ jobs:
|
|||||||
images: ghcr.io/${{ github.repository_owner }}/tinyauth
|
images: ghcr.io/${{ github.repository_owner }}/tinyauth
|
||||||
|
|
||||||
- name: Login to GitHub Container Registry
|
- name: Login to GitHub Container Registry
|
||||||
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4
|
uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4
|
||||||
with:
|
with:
|
||||||
registry: ghcr.io
|
registry: ghcr.io
|
||||||
username: ${{ github.repository_owner }}
|
username: ${{ github.repository_owner }}
|
||||||
@@ -340,7 +340,7 @@ jobs:
|
|||||||
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4
|
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4
|
||||||
|
|
||||||
- name: Build and push
|
- name: Build and push
|
||||||
uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7
|
uses: docker/build-push-action@f9f3042f7e2789586610d6e8b85c8f03e5195baf # v7
|
||||||
id: build
|
id: build
|
||||||
with:
|
with:
|
||||||
platforms: linux/arm64
|
platforms: linux/arm64
|
||||||
@@ -384,7 +384,7 @@ jobs:
|
|||||||
merge-multiple: true
|
merge-multiple: true
|
||||||
|
|
||||||
- name: Login to GitHub Container Registry
|
- name: Login to GitHub Container Registry
|
||||||
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4
|
uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4
|
||||||
with:
|
with:
|
||||||
registry: ghcr.io
|
registry: ghcr.io
|
||||||
username: ${{ github.repository_owner }}
|
username: ${{ github.repository_owner }}
|
||||||
@@ -423,7 +423,7 @@ jobs:
|
|||||||
merge-multiple: true
|
merge-multiple: true
|
||||||
|
|
||||||
- name: Login to GitHub Container Registry
|
- name: Login to GitHub Container Registry
|
||||||
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4
|
uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4
|
||||||
with:
|
with:
|
||||||
registry: ghcr.io
|
registry: ghcr.io
|
||||||
username: ${{ github.repository_owner }}
|
username: ${{ github.repository_owner }}
|
||||||
|
|||||||
@@ -126,7 +126,7 @@ jobs:
|
|||||||
images: ghcr.io/${{ github.repository_owner }}/tinyauth
|
images: ghcr.io/${{ github.repository_owner }}/tinyauth
|
||||||
|
|
||||||
- name: Login to GitHub Container Registry
|
- name: Login to GitHub Container Registry
|
||||||
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4
|
uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4
|
||||||
with:
|
with:
|
||||||
registry: ghcr.io
|
registry: ghcr.io
|
||||||
username: ${{ github.repository_owner }}
|
username: ${{ github.repository_owner }}
|
||||||
@@ -136,7 +136,7 @@ jobs:
|
|||||||
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4
|
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4
|
||||||
|
|
||||||
- name: Build and push
|
- name: Build and push
|
||||||
uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7
|
uses: docker/build-push-action@f9f3042f7e2789586610d6e8b85c8f03e5195baf # v7
|
||||||
id: build
|
id: build
|
||||||
with:
|
with:
|
||||||
platforms: linux/amd64
|
platforms: linux/amd64
|
||||||
@@ -181,7 +181,7 @@ jobs:
|
|||||||
images: ghcr.io/${{ github.repository_owner }}/tinyauth
|
images: ghcr.io/${{ github.repository_owner }}/tinyauth
|
||||||
|
|
||||||
- name: Login to GitHub Container Registry
|
- name: Login to GitHub Container Registry
|
||||||
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4
|
uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4
|
||||||
with:
|
with:
|
||||||
registry: ghcr.io
|
registry: ghcr.io
|
||||||
username: ${{ github.repository_owner }}
|
username: ${{ github.repository_owner }}
|
||||||
@@ -191,7 +191,7 @@ jobs:
|
|||||||
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4
|
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4
|
||||||
|
|
||||||
- name: Build and push
|
- name: Build and push
|
||||||
uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7
|
uses: docker/build-push-action@f9f3042f7e2789586610d6e8b85c8f03e5195baf # v7
|
||||||
id: build
|
id: build
|
||||||
with:
|
with:
|
||||||
platforms: linux/amd64
|
platforms: linux/amd64
|
||||||
@@ -236,7 +236,7 @@ jobs:
|
|||||||
images: ghcr.io/${{ github.repository_owner }}/tinyauth
|
images: ghcr.io/${{ github.repository_owner }}/tinyauth
|
||||||
|
|
||||||
- name: Login to GitHub Container Registry
|
- name: Login to GitHub Container Registry
|
||||||
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4
|
uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4
|
||||||
with:
|
with:
|
||||||
registry: ghcr.io
|
registry: ghcr.io
|
||||||
username: ${{ github.repository_owner }}
|
username: ${{ github.repository_owner }}
|
||||||
@@ -246,7 +246,7 @@ jobs:
|
|||||||
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4
|
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4
|
||||||
|
|
||||||
- name: Build and push
|
- name: Build and push
|
||||||
uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7
|
uses: docker/build-push-action@f9f3042f7e2789586610d6e8b85c8f03e5195baf # v7
|
||||||
id: build
|
id: build
|
||||||
with:
|
with:
|
||||||
platforms: linux/arm64
|
platforms: linux/arm64
|
||||||
@@ -291,7 +291,7 @@ jobs:
|
|||||||
images: ghcr.io/${{ github.repository_owner }}/tinyauth
|
images: ghcr.io/${{ github.repository_owner }}/tinyauth
|
||||||
|
|
||||||
- name: Login to GitHub Container Registry
|
- name: Login to GitHub Container Registry
|
||||||
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4
|
uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4
|
||||||
with:
|
with:
|
||||||
registry: ghcr.io
|
registry: ghcr.io
|
||||||
username: ${{ github.repository_owner }}
|
username: ${{ github.repository_owner }}
|
||||||
@@ -301,7 +301,7 @@ jobs:
|
|||||||
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4
|
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4
|
||||||
|
|
||||||
- name: Build and push
|
- name: Build and push
|
||||||
uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7
|
uses: docker/build-push-action@f9f3042f7e2789586610d6e8b85c8f03e5195baf # v7
|
||||||
id: build
|
id: build
|
||||||
with:
|
with:
|
||||||
platforms: linux/arm64
|
platforms: linux/arm64
|
||||||
@@ -345,7 +345,7 @@ jobs:
|
|||||||
merge-multiple: true
|
merge-multiple: true
|
||||||
|
|
||||||
- name: Login to GitHub Container Registry
|
- name: Login to GitHub Container Registry
|
||||||
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4
|
uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4
|
||||||
with:
|
with:
|
||||||
registry: ghcr.io
|
registry: ghcr.io
|
||||||
username: ${{ github.repository_owner }}
|
username: ${{ github.repository_owner }}
|
||||||
@@ -386,7 +386,7 @@ jobs:
|
|||||||
merge-multiple: true
|
merge-multiple: true
|
||||||
|
|
||||||
- name: Login to GitHub Container Registry
|
- name: Login to GitHub Container Registry
|
||||||
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4
|
uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4
|
||||||
with:
|
with:
|
||||||
registry: ghcr.io
|
registry: ghcr.io
|
||||||
username: ${{ github.repository_owner }}
|
username: ${{ github.repository_owner }}
|
||||||
|
|||||||
@@ -38,6 +38,6 @@ jobs:
|
|||||||
retention-days: 5
|
retention-days: 5
|
||||||
|
|
||||||
- name: Upload to code-scanning
|
- name: Upload to code-scanning
|
||||||
uses: github/codeql-action/upload-sarif@68bde559dea0fdcac2102bfdf6230c5f70eb485e # v4
|
uses: github/codeql-action/upload-sarif@9e0d7b8d25671d64c341c19c0152d693099fb5ba # v4
|
||||||
with:
|
with:
|
||||||
sarif_file: results.sarif
|
sarif_file: results.sarif
|
||||||
|
|||||||
+1
-1
@@ -1,5 +1,5 @@
|
|||||||
# Site builder
|
# Site builder
|
||||||
FROM node:26.1-alpine3.23 AS frontend-builder
|
FROM node:26.2-alpine3.23 AS frontend-builder
|
||||||
|
|
||||||
WORKDIR /frontend
|
WORKDIR /frontend
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# Site builder
|
# Site builder
|
||||||
FROM node:26.1-alpine3.23 AS frontend-builder
|
FROM node:26.2-alpine3.23 AS frontend-builder
|
||||||
|
|
||||||
WORKDIR /frontend
|
WORKDIR /frontend
|
||||||
|
|
||||||
|
|||||||
@@ -58,8 +58,8 @@
|
|||||||
"invalidInput": "Input non valido",
|
"invalidInput": "Input non valido",
|
||||||
"domainWarningTitle": "Dominio non valido",
|
"domainWarningTitle": "Dominio non valido",
|
||||||
"domainWarningSubtitle": "Stai accedendo a questa istanza da un dominio errato. Scegliendo di procedere, potresti incontrare problemi con l'autenticazione.",
|
"domainWarningSubtitle": "Stai accedendo a questa istanza da un dominio errato. Scegliendo di procedere, potresti incontrare problemi con l'autenticazione.",
|
||||||
"domainWarningCurrent": "Current:",
|
"domainWarningCurrent": "Attuale:",
|
||||||
"domainWarningExpected": "Expected:",
|
"domainWarningExpected": "Previsto:",
|
||||||
"ignoreTitle": "Ignora",
|
"ignoreTitle": "Ignora",
|
||||||
"goToCorrectDomainTitle": "Vai al dominio corretto",
|
"goToCorrectDomainTitle": "Vai al dominio corretto",
|
||||||
"authorizeTitle": "Autorizza",
|
"authorizeTitle": "Autorizza",
|
||||||
|
|||||||
@@ -57,7 +57,7 @@
|
|||||||
"fieldRequired": "Ово поље је неопходно",
|
"fieldRequired": "Ово поље је неопходно",
|
||||||
"invalidInput": "Неисправан унос",
|
"invalidInput": "Неисправан унос",
|
||||||
"domainWarningTitle": "Неисправан домен",
|
"domainWarningTitle": "Неисправан домен",
|
||||||
"domainWarningSubtitle": "You are accessing this instance from an incorrect domain. If you proceed, you may encounter issues with authentication.",
|
"domainWarningSubtitle": "Приступате овој инстанци са неисправног домена. Ако наставите, можете наићи на проблеме са аутентификацијом.",
|
||||||
"domainWarningCurrent": "Тренутни:",
|
"domainWarningCurrent": "Тренутни:",
|
||||||
"domainWarningExpected": "Очекивани:",
|
"domainWarningExpected": "Очекивани:",
|
||||||
"ignoreTitle": "Игнориши",
|
"ignoreTitle": "Игнориши",
|
||||||
|
|||||||
@@ -97,7 +97,12 @@ func (app *BootstrapApp) Setup() error {
|
|||||||
return fmt.Errorf("failed to load users: %w", err)
|
return fmt.Errorf("failed to load users: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if users != nil {
|
||||||
app.runtime.LocalUsers = *users
|
app.runtime.LocalUsers = *users
|
||||||
|
} else {
|
||||||
|
log.App.Debug().Msg("No local users found, local authentication will not be available")
|
||||||
|
app.runtime.LocalUsers = []model.LocalUser{}
|
||||||
|
}
|
||||||
|
|
||||||
// load oauth whitelist
|
// load oauth whitelist
|
||||||
oauthWhitelist, err := utils.GetStringList(app.config.OAuth.Whitelist, app.config.OAuth.WhitelistFile)
|
oauthWhitelist, err := utils.GetStringList(app.config.OAuth.Whitelist, app.config.OAuth.WhitelistFile)
|
||||||
@@ -112,6 +117,13 @@ func (app *BootstrapApp) Setup() error {
|
|||||||
app.runtime.OAuthProviders = app.config.OAuth.Providers
|
app.runtime.OAuthProviders = app.config.OAuth.Providers
|
||||||
|
|
||||||
for id, provider := range app.runtime.OAuthProviders {
|
for id, provider := range app.runtime.OAuthProviders {
|
||||||
|
providerWhitelist, err := utils.GetStringList(provider.Whitelist, provider.WhitelistFile)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to load oauth whitelist for provider %s: %w", id, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
provider.Whitelist = providerWhitelist
|
||||||
|
|
||||||
secret := utils.GetSecret(provider.ClientSecret, provider.ClientSecretFile)
|
secret := utils.GetSecret(provider.ClientSecret, provider.ClientSecretFile)
|
||||||
provider.ClientSecret = secret
|
provider.ClientSecret = secret
|
||||||
provider.ClientSecretFile = ""
|
provider.ClientSecretFile = ""
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ package bootstrap
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/tinyauthapp/tinyauth/internal/service"
|
"github.com/tinyauthapp/tinyauth/internal/service"
|
||||||
@@ -127,6 +126,7 @@ func (app *BootstrapApp) setupPolicyEngine() error {
|
|||||||
})
|
})
|
||||||
policyEngine.RegisterRule(service.RuleIPBypassed, &service.IPBypassedRule{
|
policyEngine.RegisterRule(service.RuleIPBypassed, &service.IPBypassedRule{
|
||||||
Log: app.log,
|
Log: app.log,
|
||||||
|
Config: app.config,
|
||||||
})
|
})
|
||||||
|
|
||||||
app.services.policyEngine = policyEngine
|
app.services.policyEngine = policyEngine
|
||||||
|
|||||||
@@ -183,9 +183,23 @@ func (controller *OAuthController) oauthCallbackHandler(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if !controller.auth.IsEmailWhitelisted(user.Email) {
|
svc, err := controller.auth.GetOAuthService(sessionIdCookie)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
controller.log.App.Error().Err(err).Msg("Failed to get OAuth service for session")
|
||||||
|
c.Redirect(http.StatusTemporaryRedirect, fmt.Sprintf("%s/error", controller.runtime.AppURL))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if svc.ID() != req.Provider {
|
||||||
|
controller.log.App.Warn().Msgf("OAuth provider mismatch: expected %s, got %s", req.Provider, svc.ID())
|
||||||
|
c.Redirect(http.StatusTemporaryRedirect, fmt.Sprintf("%s/error", controller.runtime.AppURL))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !controller.auth.IsEmailWhitelisted(svc.ID(), user.Email) {
|
||||||
controller.log.App.Warn().Str("email", user.Email).Msg("Email not whitelisted, denying access")
|
controller.log.App.Warn().Str("email", user.Email).Msg("Email not whitelisted, denying access")
|
||||||
controller.log.AuditLoginFailure(user.Email, req.Provider, c.ClientIP(), "email not whitelisted")
|
controller.log.AuditLoginFailure(user.Email, svc.ID(), c.ClientIP(), "email not whitelisted")
|
||||||
|
|
||||||
queries, err := query.Values(UnauthorizedQuery{
|
queries, err := query.Values(UnauthorizedQuery{
|
||||||
Username: user.Email,
|
Username: user.Email,
|
||||||
@@ -226,20 +240,6 @@ func (controller *OAuthController) oauthCallbackHandler(c *gin.Context) {
|
|||||||
username = strings.Replace(user.Email, "@", "_", 1)
|
username = strings.Replace(user.Email, "@", "_", 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
svc, err := controller.auth.GetOAuthService(sessionIdCookie)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
controller.log.App.Error().Err(err).Msg("Failed to get OAuth service for session")
|
|
||||||
c.Redirect(http.StatusTemporaryRedirect, fmt.Sprintf("%s/error", controller.runtime.AppURL))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if svc.ID() != req.Provider {
|
|
||||||
controller.log.App.Warn().Msgf("OAuth provider mismatch: expected %s, got %s", req.Provider, svc.ID())
|
|
||||||
c.Redirect(http.StatusTemporaryRedirect, fmt.Sprintf("%s/error", controller.runtime.AppURL))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
sessionCookie := repository.Session{
|
sessionCookie := repository.Session{
|
||||||
Username: username,
|
Username: username,
|
||||||
Name: name,
|
Name: name,
|
||||||
|
|||||||
@@ -160,7 +160,10 @@ func (controller *ProxyController) proxyHandler(c *gin.Context) {
|
|||||||
userContext, err := new(model.UserContext).NewFromGin(c)
|
userContext, err := new(model.UserContext).NewFromGin(c)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
controller.log.App.Debug().Err(err).Msg("Failed to create user context from request, treating as unauthenticated")
|
// No user context found is not an issue
|
||||||
|
if !errors.Is(err, model.ErrUserContextNotFound) {
|
||||||
|
controller.log.App.Error().Err(err).Msg("Failed to create user context from request, treating as unauthenticated")
|
||||||
|
}
|
||||||
userContext = &model.UserContext{
|
userContext = &model.UserContext{
|
||||||
Authenticated: false,
|
Authenticated: false,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -205,7 +205,7 @@ func (m *ContextMiddleware) cookieAuth(ctx context.Context, uuid string, ip stri
|
|||||||
return nil, nil, fmt.Errorf("oauth provider from session cookie not found: %s", userContext.OAuth.ID)
|
return nil, nil, fmt.Errorf("oauth provider from session cookie not found: %s", userContext.OAuth.ID)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !m.auth.IsEmailWhitelisted(userContext.OAuth.Email) {
|
if !m.auth.IsEmailWhitelisted(userContext.OAuth.ID, userContext.OAuth.Email) {
|
||||||
m.auth.DeleteSession(ctx, uuid)
|
m.auth.DeleteSession(ctx, uuid)
|
||||||
return nil, nil, fmt.Errorf("email from session cookie not whitelisted: %s", userContext.OAuth.Email)
|
return nil, nil, fmt.Errorf("email from session cookie not whitelisted: %s", userContext.OAuth.Email)
|
||||||
}
|
}
|
||||||
@@ -251,6 +251,10 @@ func (m *ContextMiddleware) basicAuth(username string, password string) (*model.
|
|||||||
case model.UserLocal:
|
case model.UserLocal:
|
||||||
user := m.auth.GetLocalUser(username)
|
user := m.auth.GetLocalUser(username)
|
||||||
|
|
||||||
|
if user == nil {
|
||||||
|
return nil, nil, fmt.Errorf("user not found locally: %s", username)
|
||||||
|
}
|
||||||
|
|
||||||
if user.TOTPSecret != "" {
|
if user.TOTPSecret != "" {
|
||||||
return nil, nil, fmt.Errorf("user with totp not allowed to login via basic auth: %s", username)
|
return nil, nil, fmt.Errorf("user with totp not allowed to login via basic auth: %s", username)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -156,6 +156,7 @@ type AddressClaim struct {
|
|||||||
type IPConfig struct {
|
type IPConfig struct {
|
||||||
Allow []string `description:"List of allowed IPs or CIDR ranges." yaml:"allow"`
|
Allow []string `description:"List of allowed IPs or CIDR ranges." yaml:"allow"`
|
||||||
Block []string `description:"List of blocked IPs or CIDR ranges." yaml:"block"`
|
Block []string `description:"List of blocked IPs or CIDR ranges." yaml:"block"`
|
||||||
|
Bypass []string `description:"List of IPs or CIDR ranges that bypass authentication entirely." yaml:"bypass"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type OAuthConfig struct {
|
type OAuthConfig struct {
|
||||||
@@ -225,6 +226,8 @@ type OAuthServiceConfig struct {
|
|||||||
ClientID string `description:"OAuth client ID." yaml:"clientId"`
|
ClientID string `description:"OAuth client ID." yaml:"clientId"`
|
||||||
ClientSecret string `description:"OAuth client secret." yaml:"clientSecret"`
|
ClientSecret string `description:"OAuth client secret." yaml:"clientSecret"`
|
||||||
ClientSecretFile string `description:"Path to the file containing the OAuth client secret." yaml:"clientSecretFile"`
|
ClientSecretFile string `description:"Path to the file containing the OAuth client secret." yaml:"clientSecretFile"`
|
||||||
|
Whitelist []string `description:"Comma-separated list of allowed OAuth domains for this provider." yaml:"whitelist"`
|
||||||
|
WhitelistFile string `description:"Path to the OAuth whitelist file for this provider." yaml:"whitelistFile"`
|
||||||
Scopes []string `description:"OAuth scopes." yaml:"scopes"`
|
Scopes []string `description:"OAuth scopes." yaml:"scopes"`
|
||||||
RedirectURL string `description:"OAuth redirect URL." yaml:"redirectUrl"`
|
RedirectURL string `description:"OAuth redirect URL." yaml:"redirectUrl"`
|
||||||
AuthURL string `description:"OAuth authorization URL." yaml:"authUrl"`
|
AuthURL string `description:"OAuth authorization URL." yaml:"authUrl"`
|
||||||
|
|||||||
@@ -114,7 +114,7 @@ type LDAPGroupRule struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (rule *LDAPGroupRule) Evaluate(ctx *ACLContext) Effect {
|
func (rule *LDAPGroupRule) Evaluate(ctx *ACLContext) Effect {
|
||||||
if ctx == nil || ctx.UserContext == nil {
|
if ctx == nil || ctx.UserContext == nil || ctx.ACLs == nil {
|
||||||
return EffectAbstain
|
return EffectAbstain
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -182,13 +182,14 @@ type IPAllowedRule struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (rule *IPAllowedRule) Evaluate(ctx *ACLContext) Effect {
|
func (rule *IPAllowedRule) Evaluate(ctx *ACLContext) Effect {
|
||||||
if ctx.ACLs == nil {
|
// merge global and per-app block/allow lists
|
||||||
return EffectAbstain
|
blockedIps := append([]string{}, rule.Config.Auth.IP.Block...)
|
||||||
}
|
allowedIPs := append([]string{}, rule.Config.Auth.IP.Allow...)
|
||||||
|
|
||||||
// Merge the global and app IP filter
|
if ctx.ACLs != nil {
|
||||||
blockedIps := append(ctx.ACLs.IP.Block, rule.Config.Auth.IP.Block...)
|
blockedIps = append(blockedIps, ctx.ACLs.IP.Block...)
|
||||||
allowedIPs := append(ctx.ACLs.IP.Allow, rule.Config.Auth.IP.Allow...)
|
allowedIPs = append(allowedIPs, ctx.ACLs.IP.Allow...)
|
||||||
|
}
|
||||||
|
|
||||||
for _, blocked := range blockedIps {
|
for _, blocked := range blockedIps {
|
||||||
match, err := utils.CheckIPFilter(blocked, ctx.IP.String())
|
match, err := utils.CheckIPFilter(blocked, ctx.IP.String())
|
||||||
@@ -225,14 +226,17 @@ func (rule *IPAllowedRule) Evaluate(ctx *ACLContext) Effect {
|
|||||||
|
|
||||||
type IPBypassedRule struct {
|
type IPBypassedRule struct {
|
||||||
Log *logger.Logger
|
Log *logger.Logger
|
||||||
|
Config model.Config
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rule *IPBypassedRule) Evaluate(ctx *ACLContext) Effect {
|
func (rule *IPBypassedRule) Evaluate(ctx *ACLContext) Effect {
|
||||||
if ctx.ACLs == nil {
|
// merge global and per-app bypass lists
|
||||||
return EffectDeny
|
bypassList := append([]string{}, rule.Config.Auth.IP.Bypass...)
|
||||||
|
if ctx.ACLs != nil {
|
||||||
|
bypassList = append(bypassList, ctx.ACLs.IP.Bypass...)
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, bypassed := range ctx.ACLs.IP.Bypass {
|
for _, bypassed := range bypassList {
|
||||||
match, err := utils.CheckIPFilter(bypassed, ctx.IP.String())
|
match, err := utils.CheckIPFilter(bypassed, ctx.IP.String())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
rule.Log.App.Warn().Err(err).Str("item", bypassed).Msg("Invalid IP/CIDR in bypass list")
|
rule.Log.App.Warn().Err(err).Str("item", bypassed).Msg("Invalid IP/CIDR in bypass list")
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
"github.com/tinyauthapp/tinyauth/internal/model"
|
"github.com/tinyauthapp/tinyauth/internal/model"
|
||||||
"github.com/tinyauthapp/tinyauth/internal/utils/logger"
|
"github.com/tinyauthapp/tinyauth/internal/utils/logger"
|
||||||
)
|
)
|
||||||
@@ -558,12 +559,12 @@ func TestIPAllowedRule(t *testing.T) {
|
|||||||
expected Effect
|
expected Effect
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "abstains when ACLs are nil",
|
name: "allows when ACLs are nil and no global lists configured",
|
||||||
ctx: &ACLContext{
|
ctx: &ACLContext{
|
||||||
ACLs: nil,
|
ACLs: nil,
|
||||||
IP: net.ParseIP("10.0.0.1"),
|
IP: net.ParseIP("10.0.0.1"),
|
||||||
},
|
},
|
||||||
expected: EffectAbstain,
|
expected: EffectAllow,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "denies when IP matches app block list",
|
name: "denies when IP matches app block list",
|
||||||
@@ -669,23 +670,70 @@ func TestIPBypassedRule(t *testing.T) {
|
|||||||
log := logger.NewLogger().WithTestConfig()
|
log := logger.NewLogger().WithTestConfig()
|
||||||
log.Init()
|
log.Init()
|
||||||
|
|
||||||
rule := &IPBypassedRule{Log: log}
|
defaultIPBR := &IPBypassedRule{Log: log}
|
||||||
|
globBypassIPBR := &IPBypassedRule{
|
||||||
|
Log: log,
|
||||||
|
Config: model.Config{Auth: model.AuthConfig{IP: model.IPConfig{Bypass: []string{"10.0.0.0/24"}}}},
|
||||||
|
}
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
|
rule *IPBypassedRule
|
||||||
ctx *ACLContext
|
ctx *ACLContext
|
||||||
expected Effect
|
expected Effect
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "deny when ACLs are nil",
|
name: "deny when ACLs are nil and no global bypass",
|
||||||
|
rule: defaultIPBR,
|
||||||
ctx: &ACLContext{
|
ctx: &ACLContext{
|
||||||
ACLs: nil,
|
ACLs: nil,
|
||||||
IP: net.ParseIP("10.0.0.1"),
|
IP: net.ParseIP("10.0.0.1"),
|
||||||
},
|
},
|
||||||
expected: EffectDeny,
|
expected: EffectDeny,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "allows when ACLs are nil but IP matches global bypass",
|
||||||
|
rule: globBypassIPBR,
|
||||||
|
ctx: &ACLContext{
|
||||||
|
ACLs: nil,
|
||||||
|
IP: net.ParseIP("10.0.0.5"),
|
||||||
|
},
|
||||||
|
expected: EffectAllow,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "denies when ACLs are nil and IP does not match global bypass",
|
||||||
|
rule: globBypassIPBR,
|
||||||
|
ctx: &ACLContext{
|
||||||
|
ACLs: nil,
|
||||||
|
IP: net.ParseIP("192.168.1.1"),
|
||||||
|
},
|
||||||
|
expected: EffectDeny,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "allows when IP matches per-app bypass but not global bypass",
|
||||||
|
rule: defaultIPBR,
|
||||||
|
ctx: &ACLContext{
|
||||||
|
ACLs: &model.App{
|
||||||
|
IP: model.AppIP{Bypass: []string{"10.0.0.0/24"}},
|
||||||
|
},
|
||||||
|
IP: net.ParseIP("10.0.0.5"),
|
||||||
|
},
|
||||||
|
expected: EffectAllow,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "allows when IP matches global bypass but not per-app bypass",
|
||||||
|
rule: globBypassIPBR,
|
||||||
|
ctx: &ACLContext{
|
||||||
|
ACLs: &model.App{
|
||||||
|
IP: model.AppIP{Bypass: []string{"172.16.0.0/24"}},
|
||||||
|
},
|
||||||
|
IP: net.ParseIP("10.0.0.5"),
|
||||||
|
},
|
||||||
|
expected: EffectAllow,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "allows when IP matches bypass list",
|
name: "allows when IP matches bypass list",
|
||||||
|
rule: defaultIPBR,
|
||||||
ctx: &ACLContext{
|
ctx: &ACLContext{
|
||||||
ACLs: &model.App{
|
ACLs: &model.App{
|
||||||
IP: model.AppIP{Bypass: []string{"10.0.0.0/24"}},
|
IP: model.AppIP{Bypass: []string{"10.0.0.0/24"}},
|
||||||
@@ -696,6 +744,7 @@ func TestIPBypassedRule(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "denies when IP does not match bypass list",
|
name: "denies when IP does not match bypass list",
|
||||||
|
rule: defaultIPBR,
|
||||||
ctx: &ACLContext{
|
ctx: &ACLContext{
|
||||||
ACLs: &model.App{
|
ACLs: &model.App{
|
||||||
IP: model.AppIP{Bypass: []string{"10.0.0.0/24"}},
|
IP: model.AppIP{Bypass: []string{"10.0.0.0/24"}},
|
||||||
@@ -706,6 +755,7 @@ func TestIPBypassedRule(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "denies when bypass list is empty",
|
name: "denies when bypass list is empty",
|
||||||
|
rule: defaultIPBR,
|
||||||
ctx: &ACLContext{
|
ctx: &ACLContext{
|
||||||
ACLs: &model.App{},
|
ACLs: &model.App{},
|
||||||
IP: net.ParseIP("10.0.0.1"),
|
IP: net.ParseIP("10.0.0.1"),
|
||||||
@@ -714,6 +764,7 @@ func TestIPBypassedRule(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "skips invalid bypass entries and allows on later match",
|
name: "skips invalid bypass entries and allows on later match",
|
||||||
|
rule: defaultIPBR,
|
||||||
ctx: &ACLContext{
|
ctx: &ACLContext{
|
||||||
ACLs: &model.App{
|
ACLs: &model.App{
|
||||||
IP: model.AppIP{Bypass: []string{"not-an-ip", "10.0.0.1"}},
|
IP: model.AppIP{Bypass: []string{"not-an-ip", "10.0.0.1"}},
|
||||||
@@ -726,7 +777,7 @@ func TestIPBypassedRule(t *testing.T) {
|
|||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
assert.Equal(t, tt.expected, rule.Evaluate(tt.ctx))
|
assert.Equal(t, tt.expected, tt.rule.Evaluate(tt.ctx))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -285,10 +285,15 @@ func (auth *AuthService) RecordLoginAttempt(identifier string, success bool) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (auth *AuthService) IsEmailWhitelisted(email string) bool {
|
func (auth *AuthService) IsEmailWhitelisted(provider string, email string) bool {
|
||||||
match, err := utils.CheckFilter(strings.Join(auth.runtime.OAuthWhitelist, ","), email)
|
whitelist := auth.runtime.OAuthWhitelist
|
||||||
|
if providerConfig, ok := auth.runtime.OAuthProviders[provider]; ok && len(providerConfig.Whitelist) > 0 {
|
||||||
|
whitelist = providerConfig.Whitelist
|
||||||
|
}
|
||||||
|
|
||||||
|
match, err := utils.CheckFilter(strings.Join(whitelist, ","), email)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
auth.log.App.Warn().Err(err).Str("email", email).Msg("Invalid email filter pattern")
|
auth.log.App.Warn().Err(err).Str("provider", provider).Str("email", email).Msg("Invalid email filter pattern")
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return match
|
return match
|
||||||
|
|||||||
@@ -0,0 +1,39 @@
|
|||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/tinyauthapp/tinyauth/internal/model"
|
||||||
|
"github.com/tinyauthapp/tinyauth/internal/utils/logger"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestIsEmailWhitelistedUsesProviderSpecificList(t *testing.T) {
|
||||||
|
log := logger.NewLogger().WithTestConfig()
|
||||||
|
log.Init()
|
||||||
|
|
||||||
|
auth := &AuthService{
|
||||||
|
log: log,
|
||||||
|
runtime: model.RuntimeConfig{
|
||||||
|
OAuthWhitelist: []string{"global@example.com"},
|
||||||
|
OAuthProviders: map[string]model.OAuthServiceConfig{
|
||||||
|
"github": {
|
||||||
|
Whitelist: []string{"github@example.com"},
|
||||||
|
},
|
||||||
|
"pocketid": {
|
||||||
|
Whitelist: []string{"pocket@example.com"},
|
||||||
|
},
|
||||||
|
"gitlab": {
|
||||||
|
Whitelist: []string{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.True(t, auth.IsEmailWhitelisted("github", "github@example.com"))
|
||||||
|
assert.False(t, auth.IsEmailWhitelisted("github", "pocket@example.com"))
|
||||||
|
assert.True(t, auth.IsEmailWhitelisted("pocketid", "pocket@example.com"))
|
||||||
|
assert.True(t, auth.IsEmailWhitelisted("google", "global@example.com"))
|
||||||
|
assert.True(t, auth.IsEmailWhitelisted("gitlab", "global@example.com"))
|
||||||
|
assert.False(t, auth.IsEmailWhitelisted("gitlab", "unknown@example.com"))
|
||||||
|
}
|
||||||
@@ -3,6 +3,7 @@ package service
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
@@ -167,6 +168,68 @@ func (k *KubernetesService) getByAppName(appName string) *model.App {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (k *KubernetesService) extractPaths(rule map[string]any) ([]string, error) {
|
||||||
|
http, found, err := unstructured.NestedMap(rule, "http")
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("reading http from rule: %w", err)
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
paths, found, err := unstructured.NestedSlice(http, "paths")
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("reading http.paths: %w", err)
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
var result []string
|
||||||
|
for _, p := range paths {
|
||||||
|
path, ok := p.(map[string]any)
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if p, ok := path["path"].(string); ok && p != "" {
|
||||||
|
result = append(result, p)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k *KubernetesService) extractHosts(item *unstructured.Unstructured) ([]string, error) {
|
||||||
|
rules, found, err := unstructured.NestedSlice(item.Object, "spec", "rules")
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("reading spec.rules: %w", err)
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
var hosts []string
|
||||||
|
for _, r := range rules {
|
||||||
|
rule, ok := r.(map[string]any)
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if host, ok := rule["host"].(string); ok && host != "" {
|
||||||
|
hosts = append(hosts, host)
|
||||||
|
}
|
||||||
|
paths, err := k.extractPaths(rule)
|
||||||
|
if err != nil {
|
||||||
|
// This is purely to warn users, it doesn't affect our ability to extract hosts so we won't fail the whole operation
|
||||||
|
k.log.App.Warn().Err(err).Str("namespace", item.GetNamespace()).Str("name", item.GetName()).Msg("Failed to extract paths from ingress rule")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if len(paths) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !slices.Contains(paths, "/") {
|
||||||
|
k.log.App.Warn().Str("namespace", item.GetNamespace()).Str("name", item.GetName()).Strs("paths", paths).Msg("Ingress rule does not contain a catch-all path, another ingress may be able to bypass auth checks if it routes the same host with a different path. Consider adding a catch-all path to this rule to ensure auth checks are applied to all paths for this host.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
k.log.App.Trace().Strs("hosts", hosts).Msg("Extracted hosts from ingress rules")
|
||||||
|
return hosts, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (k *KubernetesService) updateFromItem(item *unstructured.Unstructured) {
|
func (k *KubernetesService) updateFromItem(item *unstructured.Unstructured) {
|
||||||
namespace := item.GetNamespace()
|
namespace := item.GetNamespace()
|
||||||
name := item.GetName()
|
name := item.GetName()
|
||||||
@@ -175,6 +238,11 @@ func (k *KubernetesService) updateFromItem(item *unstructured.Unstructured) {
|
|||||||
k.removeIngress(namespace, name)
|
k.removeIngress(namespace, name)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
hosts, err := k.extractHosts(item)
|
||||||
|
if err != nil {
|
||||||
|
k.removeIngress(namespace, name)
|
||||||
|
return
|
||||||
|
}
|
||||||
labels, err := decoders.DecodeLabels[model.Apps](annotations, "apps")
|
labels, err := decoders.DecodeLabels[model.Apps](annotations, "apps")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
k.log.App.Warn().Err(err).Str("namespace", namespace).Str("name", name).Msg("Failed to decode ingress labels, skipping")
|
k.log.App.Warn().Err(err).Str("namespace", namespace).Str("name", name).Msg("Failed to decode ingress labels, skipping")
|
||||||
@@ -186,6 +254,10 @@ func (k *KubernetesService) updateFromItem(item *unstructured.Unstructured) {
|
|||||||
if appLabels.Config.Domain == "" {
|
if appLabels.Config.Domain == "" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
if len(hosts) > 0 && !slices.Contains(hosts, appLabels.Config.Domain) {
|
||||||
|
k.log.App.Warn().Str("namespace", namespace).Str("name", name).Str("appName", appName).Str("domain", appLabels.Config.Domain).Msg("App domain does not match any hosts defined in ingress rules, skipping")
|
||||||
|
continue
|
||||||
|
}
|
||||||
apps = append(apps, ingressApp{
|
apps = append(apps, ingressApp{
|
||||||
domain: appLabels.Config.Domain,
|
domain: appLabels.Config.Domain,
|
||||||
appName: appName,
|
appName: appName,
|
||||||
|
|||||||
Reference in New Issue
Block a user