diff --git a/Dockerfile b/Dockerfile index 0694e13..b502413 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,5 @@ # Utiliser une image officielle de Go pour construire l'application -FROM golang:1.20-alpine AS builder +FROM golang:1.23-alpine AS builder # Installer les dépendances pour PostgreSQL RUN apk add --no-cache git diff --git a/chart/templates/config.yaml b/chart/templates/config.yaml index 71f5d50..6b94f22 100644 --- a/chart/templates/config.yaml +++ b/chart/templates/config.yaml @@ -4,5 +4,7 @@ metadata: name: {{ include "webapp.name" . }}-config namespace: {{ .Release.Namespace }} data: + OAUTH_ALLOWED_HTTP2_AUTHORITY: webapp.arcodange.duckdns.org + OAUTH_DEVICE_CODE_ALLOWED_IPS: 90.16.102.250, DATABASE_URL: postgres://pgbouncer_auth:pgbouncer_auth@pgbouncer.tools/postgres?sslmode=disable # DATABASE_URL: postgres://username:password@localhost/dbname?sslmode=disable \ No newline at end of file diff --git a/chart/values.yaml b/chart/values.yaml index 84fa9c4..45877c6 100644 --- a/chart/values.yaml +++ b/chart/values.yaml @@ -104,7 +104,8 @@ volumeMounts: [] # mountPath: "/etc/foo" # readOnly: true -nodeSelector: {} +nodeSelector: + kubernetes.io/hostname: pi1 # entrypoint of the network, allows to avoid NAT and keep user IP tolerations: [] diff --git a/go.mod b/go.mod index 08fb563..0909b0b 100644 --- a/go.mod +++ b/go.mod @@ -1,5 +1,8 @@ module gitea.arcodange.duckdns.org/arcodange-org/webapp -go 1.20 +go 1.23 -require github.com/lib/pq v1.10.9 // indirect +require ( + github.com/lib/pq v1.10.9 // indirect + github.com/patrickmn/go-cache v2.1.0+incompatible // indirect +) diff --git a/go.sum b/go.sum index aeddeae..f735dcd 100644 --- a/go.sum +++ b/go.sum @@ -1,2 +1,4 @@ github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= +github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= diff --git a/main.go b/main.go index 2f04566..a848c7e 100644 --- a/main.go +++ b/main.go @@ -5,15 +5,25 @@ import ( "fmt" "html/template" "log" + "net" "net/http" "os" + "slices" "strconv" "strings" + "time" + + "github.com/patrickmn/go-cache" _ "github.com/lib/pq" // PostgreSQL driver ) -var db *sql.DB // Global database connection +var ( + db *sql.DB // Global database connection + c = cache.New(5*time.Minute, 10*time.Minute) + oauthAllowedHttp2Authority = os.Getenv("OAUTH_ALLOWED_HTTP2_AUTHORITY") // URL authorized for device code + oauthDeviceCodeAllowedIPs = strings.Split(os.Getenv("OAUTH_DEVICE_CODE_ALLOWED_IPS"), ",") // IPS autorisées pour /retrieve +) // dbConnection initializes the database connection. func dbConnection() (*sql.DB, error) { @@ -137,11 +147,26 @@ type CallbackData struct { // oauth2_callback handles HTTP requests and display a message according to queryParams func oauth2_callback(w http.ResponseWriter, r *http.Request) { + + // Vérifier le référent (ou origine) + + authorityHeader := r.Header.Get(":authority") + if oauthAllowedHttp2Authority != "" && authorityHeader != oauthAllowedHttp2Authority { + fmt.Println(":authority: "+authorityHeader) + http.Error(w, "Access denied: invalid referer or origin", http.StatusForbidden) + return + } + // Récupération des paramètres query queryParams := r.URL.Query() code := queryParams.Get("code") state := queryParams.Get("state") + // Stocker state et code dans le cache avec TTL de 5 minutes + if state != "" && code != "" { + c.Set(state, code, cache.DefaultExpiration) + } + // Construction d'une map pour les autres paramètres otherParams := make(map[string]string) for key, values := range queryParams { @@ -250,6 +275,46 @@ func oauth2_callback(w http.ResponseWriter, r *http.Request) { } } +// Handler pour récupérer un code à partir d'un state avec une restriction d'IP +func retrieveHandler(w http.ResponseWriter, r *http.Request) { + // Récupérer l'IP de l'utilisateur + userIP, _, err := net.SplitHostPort(r.RemoteAddr) + userIPforwarded := r.Header.Get("X-Forwarded-For") + if err != nil || + !slices.Contains(oauthDeviceCodeAllowedIPs, userIP) && + !slices.Contains(oauthDeviceCodeAllowedIPs, userIPforwarded) { + fmt.Fprintln(os.Stderr, "denied userIP: "+userIP) + // Parcourir tous les headers + for name, values := range r.Header { + // name représente le nom de l'en-tête + // values est une slice contenant toutes les valeurs associées à cet en-tête + for _, value := range values { + fmt.Fprintf(os.Stderr,"%s: %s\n", name, value) + } + } + http.Error(w, "Access denied: invalid IP", http.StatusForbidden) + return + } + + // Récupérer le paramètre `state` depuis l'URL + state := r.URL.Query().Get("state") + if state == "" { + http.Error(w, "State parameter is required", http.StatusBadRequest) + return + } + + // Vérifier si le state existe dans le cache + code, found := c.Get(state) + if !found { + http.Error(w, "State not found or expired", http.StatusNotFound) + return + } + + // Retourner le code associé au state + w.WriteHeader(http.StatusOK) + fmt.Fprint(w, code) +} + func test_oauth2_callback(w http.ResponseWriter, r *http.Request) { html := ` @@ -325,12 +390,26 @@ func main() { // Define the handler for the `/readiness` probe http.HandleFunc("/readiness", readinessHandler) + /* + Gitea doesn't come with device flow # https://github.com/go-gitea/gitea/issues/27309 + https://gitea.arcodange.duckdns.org/.well-known/openid-configuration + "grant_types_supported": [ + "authorization_code", + "refresh_token" + ] + + 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 + */ http.HandleFunc("/oauth-callback", oauth2_callback) + // Define the handler to exchange a state for a code + http.HandleFunc("/retrieve", retrieveHandler) http.HandleFunc("/test-oauth-callback", test_oauth2_callback) // Start the HTTP server port := ":8080" log.Printf("Server starting on port %s\n", port) + fmt.Println("new version indeed") err = http.ListenAndServe(port, nil) if err != nil { log.Fatalf("Server failed to start: %v", err)