Compare commits

..

7 Commits

Author SHA1 Message Date
ca2800a5c9 try accepting self signed cert
All checks were successful
Docker Build / build-and-push-image (push) Successful in 1m32s
2026-01-02 19:09:29 +01:00
9761996957 switch from duckdns.org to internal .lab dns
All checks were successful
Docker Build / build-and-push-image (push) Successful in 53s
2026-01-01 15:03:06 +01:00
23c38fc813 allow both duckdns.org and .fr domains
All checks were successful
Docker Build / build-and-push-image (push) Successful in 4m25s
2025-12-18 10:58:08 +01:00
1d3db54909 cloudflared webapp 2025-11-29 13:29:59 +01:00
70077c1956 update public ip - todo: replace with crowdsec middleware 2025-11-27 18:59:23 +01:00
cbceac786d log denied forwardedIp
All checks were successful
Docker Build / build-and-push-image (push) Successful in 1m31s
2025-08-27 19:53:38 +02:00
ae3eed3ff8 temporary fix: hardcode allowed ip instead of using traefik middleware 2025-08-07 15:33:38 +02:00
8 changed files with 92 additions and 41 deletions

View File

@@ -23,7 +23,7 @@ jobs:
- name: Login to Gitea Container Registry - name: Login to Gitea Container Registry
uses: docker/login-action@v3 uses: docker/login-action@v3
with: with:
registry: gitea.arcodange.duckdns.org registry: gitea.arcodange.lab
username: ${{ github.actor }} username: ${{ github.actor }}
password: ${{ secrets.PACKAGES_TOKEN }} password: ${{ secrets.PACKAGES_TOKEN }}
@@ -35,7 +35,7 @@ jobs:
TAGS="latest ${{ github.ref_name }}" TAGS="latest ${{ github.ref_name }}"
docker build -t app . docker build -t app .
for TAG in $TAGS; do for TAG in $TAGS; do
docker tag app gitea.arcodange.duckdns.org/${{ github.repository }}:$TAG docker tag app gitea.arcodange.lab/${{ github.repository }}:$TAG
docker push gitea.arcodange.duckdns.org/${{ github.repository }}:$TAG docker push gitea.arcodange.lab/${{ github.repository }}:$TAG
done done

View File

@@ -16,10 +16,11 @@ concurrency:
.vault_step: &vault_step .vault_step: &vault_step
name: read vault secret name: read vault secret
uses: https://gitea.arcodange.duckdns.org/arcodange-org/vault-action.git@main uses: https://gitea.arcodange.lab/arcodange-org/vault-action.git@main
id: vault-secrets id: vault-secrets
with: with:
url: https://vault.arcodange.duckdns.org url: https://vault.arcodange.lab
caCertificate: ${{ secrets.HOMELAB_CA_CERT }}
jwtGiteaOIDC: ${{ needs.gitea_vault_auth.outputs.gitea_vault_jwt }} jwtGiteaOIDC: ${{ needs.gitea_vault_auth.outputs.gitea_vault_jwt }}
role: gitea_cicd_webapp role: gitea_cicd_webapp
method: jwt method: jwt
@@ -30,7 +31,7 @@ concurrency:
jobs: jobs:
gitea_vault_auth: gitea_vault_auth:
name: Auth with gitea for vault name: Auth with gitea for vault
runs-on: ubuntu-latest runs-on: ubuntu-latest-ca
outputs: outputs:
gitea_vault_jwt: ${{steps.gitea_vault_jwt.outputs.id_token}} gitea_vault_jwt: ${{steps.gitea_vault_jwt.outputs.id_token}}
steps: steps:
@@ -44,13 +45,16 @@ jobs:
name: Tofu - Vault name: Tofu - Vault
needs: needs:
- gitea_vault_auth - gitea_vault_auth
runs-on: ubuntu-latest runs-on: ubuntu-latest-ca
env: env:
OPENTOFU_VERSION: 1.8.2 OPENTOFU_VERSION: 1.8.2
TERRAFORM_VAULT_AUTH_JWT: ${{ needs.gitea_vault_auth.outputs.gitea_vault_jwt }} TERRAFORM_VAULT_AUTH_JWT: ${{ needs.gitea_vault_auth.outputs.gitea_vault_jwt }}
VAULT_CACERT: "${{ github.workspace }}/homelab.pem"
steps: steps:
- *vault_step - *vault_step
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- name: prepare vault self signed cert
run: echo -n "${{ secrets.HOMELAB_CA_CERT }}" | base64 -d > $VAULT_CACERT
- name: terraform apply - name: terraform apply
uses: dflook/terraform-apply@v1 uses: dflook/terraform-apply@v1
with: with:

View File

@@ -4,7 +4,7 @@ metadata:
name: {{ include "webapp.name" . }}-config name: {{ include "webapp.name" . }}-config
namespace: {{ .Release.Namespace }} namespace: {{ .Release.Namespace }}
data: data:
OAUTH_ALLOWED_HOST: webapp.arcodange.duckdns.org OAUTH_ALLOWED_HOSTS: webapp.arcodange.lab,webapp.arcodange.fr
OAUTH_DEVICE_CODE_ALLOWED_IPS: 90.16.102.250, # OAUTH_DEVICE_CODE_ALLOWED_IPS: 86.238.234.54,
DATABASE_URL: postgres://pgbouncer_auth:pgbouncer_auth@pgbouncer.tools/postgres?sslmode=disable DATABASE_URL: postgres://pgbouncer_auth:pgbouncer_auth@pgbouncer.tools/postgres?sslmode=disable
# DATABASE_URL: postgres://username:password@localhost/dbname?sslmode=disable # DATABASE_URL: postgres://username:password@localhost/dbname?sslmode=disable

View File

@@ -0,0 +1,25 @@
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
annotations:
argocd.argoproj.io/tracking-id: webapp:networking.k8s.io/Ingress:webapp/webapp
traefik.ingress.kubernetes.io/router.entrypoints: websecure
traefik.ingress.kubernetes.io/router.middlewares: localIp@file
traefik.ingress.kubernetes.io/router.tls: 'true'
traefik.ingress.kubernetes.io/router.tls.certresolver: letsencrypt
traefik.ingress.kubernetes.io/router.tls.domains.0.main: arcodange.lab
traefik.ingress.kubernetes.io/router.tls.domains.0.sans: webapp.arcodange.lab
name: webapp-local
namespace: webapp
spec:
rules:
- host: webapp.arcodange.lab
http:
paths:
- backend:
service:
name: webapp
port:
number: 8080
path: /
pathType: Prefix

View File

@@ -5,7 +5,7 @@
replicaCount: 1 replicaCount: 1
image: image:
repository: gitea.arcodange.duckdns.org/arcodange-org/webapp repository: gitea.arcodange.lab/arcodange-org/webapp
pullPolicy: Always pullPolicy: Always
# Overrides the image tag whose default is the chart appVersion. # Overrides the image tag whose default is the chart appVersion.
tag: "" tag: ""
@@ -47,18 +47,31 @@ ingress:
enabled: true enabled: true
className: "" className: ""
annotations: annotations:
traefik.ingress.kubernetes.io/router.entrypoints: websecure traefik.ingress.kubernetes.io/router.entrypoints: web
traefik.ingress.kubernetes.io/router.tls: "true" traefik.ingress.kubernetes.io/router.middlewares: kube-system-crowdsec@kubernetescrd
traefik.ingress.kubernetes.io/router.tls.certresolver: letsencrypt
traefik.ingress.kubernetes.io/router.tls.domains.0.main: arcodange.duckdns.org
traefik.ingress.kubernetes.io/router.tls.domains.0.sans: webapp.arcodange.duckdns.org
traefik.ingress.kubernetes.io/router.middlewares: localIp@file
hosts: hosts:
- host: webapp.arcodange.duckdns.org - host: webapp.arcodange.fr
paths: paths:
- path: / - path: /
pathType: Prefix pathType: Prefix
tls: [] tls: []
# ingress:
# enabled: true
# className: ""
# annotations:
# traefik.ingress.kubernetes.io/router.entrypoints: websecure
# traefik.ingress.kubernetes.io/router.tls: "true"
# traefik.ingress.kubernetes.io/router.tls.certresolver: letsencrypt
# traefik.ingress.kubernetes.io/router.tls.domains.0.main: arcodange.lab
# traefik.ingress.kubernetes.io/router.tls.domains.0.sans: webapp.arcodange.lab
# traefik.ingress.kubernetes.io/router.middlewares: localIp@file
# hosts:
# - host: webapp.arcodange.lab
# paths:
# - path: /
# pathType: Prefix
# tls: []
# - secretName: chart-example-tls # - secretName: chart-example-tls
# hosts: # hosts:
# - chart-example.local # - chart-example.local

2
go.mod
View File

@@ -1,4 +1,4 @@
module gitea.arcodange.duckdns.org/arcodange-org/webapp module gitea.arcodange.lab/arcodange-org/webapp
go 1.23 go 1.23

View File

@@ -8,7 +8,7 @@ terraform {
} }
provider vault { provider vault {
address = "https://vault.arcodange.duckdns.org" address = "https://vault.arcodange.lab"
auth_login_jwt { # TERRAFORM_VAULT_AUTH_JWT environment variable auth_login_jwt { # TERRAFORM_VAULT_AUTH_JWT environment variable
mount = "gitea_jwt" mount = "gitea_jwt"
role = "gitea_cicd_webapp" role = "gitea_cicd_webapp"

53
main.go
View File

@@ -19,10 +19,13 @@ import (
) )
var ( var (
db *sql.DB // Global database connection db *sql.DB // Global database connection
c = cache.New(5*time.Minute, 10*time.Minute) c = cache.New(5*time.Minute, 10*time.Minute)
oauthAllowedHost = os.Getenv("OAUTH_ALLOWED_HOST") // URL authorized for device code oauthAllowedHosts = strings.Split(os.Getenv("OAUTH_ALLOWED_HOSTS"), ",") // authorized HOSTS for device code
oauthDeviceCodeAllowedIPs = strings.Split(os.Getenv("OAUTH_DEVICE_CODE_ALLOWED_IPS"), ",") // IPS autorisées pour /retrieve oauthDeviceCodeAllowedIPs = strings.Split(os.Getenv("OAUTH_DEVICE_CODE_ALLOWED_IPS"), ",") // IPS autorisées pour /retrieve
_, localNetwork, _ = net.ParseCIDR("192.168.0.0/16")
_, localNetworkIPV6, _ = net.ParseCIDR("2a01:cb04:dff:cf00::/56")
_, k3sNetwork, _ = net.ParseCIDR("10.42.0.0/16")
) )
// dbConnection initializes the database connection. // dbConnection initializes the database connection.
@@ -103,7 +106,7 @@ func indexHandler(w http.ResponseWriter, r *http.Request) {
</body> </body>
</html> </html>
` `
fmt.Fprintf(w, tmpl) fmt.Fprint(w, tmpl)
} }
// selectHandler handles HTTP requests and executes a SQL query. // selectHandler handles HTTP requests and executes a SQL query.
@@ -140,9 +143,9 @@ func selectHandler(w http.ResponseWriter, r *http.Request) {
// Structure de base pour passer les données au template HTML // Structure de base pour passer les données au template HTML
type CallbackData struct { type CallbackData struct {
Code string Code string
State string State string
Other map[string]string Other map[string]string
} }
// oauth2_callback handles HTTP requests and display a message according to queryParams // oauth2_callback handles HTTP requests and display a message according to queryParams
@@ -151,7 +154,7 @@ func oauth2_callback(w http.ResponseWriter, r *http.Request) {
// Vérifier le référent (ou origine) // Vérifier le référent (ou origine)
hostHeader := strings.Trim(r.Header.Get("X-Forwarded-Host"), "[]") hostHeader := strings.Trim(r.Header.Get("X-Forwarded-Host"), "[]")
if oauthAllowedHost != "" && hostHeader != oauthAllowedHost { if len(oauthAllowedHosts) > 0 && !slices.Contains(oauthAllowedHosts, hostHeader) {
fmt.Fprintln(os.Stderr, "X-Forwarded-Host: "+hostHeader) fmt.Fprintln(os.Stderr, "X-Forwarded-Host: "+hostHeader)
fmt.Fprintln(os.Stderr, "received headers") fmt.Fprintln(os.Stderr, "received headers")
for key, value := range r.Header { for key, value := range r.Header {
@@ -283,17 +286,24 @@ func oauth2_callback(w http.ResponseWriter, r *http.Request) {
func retrieveHandler(w http.ResponseWriter, r *http.Request) { func retrieveHandler(w http.ResponseWriter, r *http.Request) {
// Récupérer l'IP de l'utilisateur // Récupérer l'IP de l'utilisateur
userIP, _, err := net.SplitHostPort(r.RemoteAddr) userIP, _, err := net.SplitHostPort(r.RemoteAddr)
userIPforwarded := r.Header.Get("X-Forwarded-For") userIPforwarded := net.ParseIP(r.Header.Get("X-Forwarded-For"))
ip := userIPforwarded
if ip == nil {
ip = net.ParseIP(userIP)
}
if err != nil || if err != nil ||
!slices.Contains(oauthDeviceCodeAllowedIPs, userIP) && !slices.Contains(oauthDeviceCodeAllowedIPs, ip.String()) &&
!slices.Contains(oauthDeviceCodeAllowedIPs, userIPforwarded) { !localNetwork.Contains(ip) &&
fmt.Fprintln(os.Stderr, "denied userIP: "+userIP) !localNetworkIPV6.Contains(ip) &&
!k3sNetwork.Contains(ip) {
fmt.Fprintln(os.Stderr, "denied userIP: "+userIP+" forwarded: "+userIPforwarded.String())
fmt.Fprintf(os.Stderr, "alowed ips: %+v", oauthDeviceCodeAllowedIPs)
// Parcourir tous les headers // Parcourir tous les headers
for name, values := range r.Header { for name, values := range r.Header {
// name représente le nom de l'en-tête // name représente le nom de l'en-tête
// values est une slice contenant toutes les valeurs associées à cet en-tête // values est une slice contenant toutes les valeurs associées à cet en-tête
for _, value := range values { for _, value := range values {
fmt.Fprintf(os.Stderr,"%s: %s\n", name, value) fmt.Fprintf(os.Stderr, "%s: %s\n", name, value)
} }
} }
http.Error(w, "Access denied: invalid IP", http.StatusForbidden) http.Error(w, "Access denied: invalid IP", http.StatusForbidden)
@@ -478,22 +488,21 @@ func main() {
http.HandleFunc("/display-info", displayInfoHandler) http.HandleFunc("/display-info", displayInfoHandler)
/* /*
Gitea doesn't come with device flow # https://github.com/go-gitea/gitea/issues/27309 Gitea doesn't come with device flow # https://github.com/go-gitea/gitea/issues/27309
https://gitea.arcodange.duckdns.org/.well-known/openid-configuration https://gitea.arcodange.lab/.well-known/openid-configuration
"grant_types_supported": [ "grant_types_supported": [
"authorization_code", "authorization_code",
"refresh_token" "refresh_token"
] ]
So we can use the authorization_code and redirect to this endpoint So we can use the authorization_code and redirect to this endpoint
and then the client can poll for the code matching the state it chose and then the client can poll for the code matching the state it chose
*/ */
http.HandleFunc("/oauth-callback", oauth2_callback) http.HandleFunc("/oauth-callback", oauth2_callback)
// Define the handler to exchange a state for a code // Define the handler to exchange a state for a code
http.HandleFunc("/retrieve", retrieveHandler) http.HandleFunc("/retrieve", retrieveHandler)
http.HandleFunc("/test-oauth-callback", test_oauth2_callback) http.HandleFunc("/test-oauth-callback", test_oauth2_callback)
// Start the HTTP server // Start the HTTP server
port := ":8080" port := ":8080"
log.Printf("Server starting on port %s\n", port) log.Printf("Server starting on port %s\n", port)