Difference between revisions of "Mimir push reverse proxy in go"
Jump to navigation
Jump to search
| Line 1: | Line 1: | ||
| + | ``` | ||
| + | sqlite3 auth.db | ||
| + | ``` | ||
| + | |||
``` | ``` | ||
CREATE TABLE users ( | CREATE TABLE users ( | ||
| Line 13: | Line 17: | ||
import ( | import ( | ||
| − | + | "database/sql" | |
| − | + | "encoding/base64" | |
| − | + | "log" | |
| − | + | "net/http" | |
| − | + | "net/http/httputil" | |
| − | + | "net/url" | |
| − | + | "os" | |
| − | + | "strings" | |
| − | |||
| − | + | _ "github.com/mattn/go-sqlite3" | |
) | ) | ||
| Line 30: | Line 33: | ||
func main() { | func main() { | ||
| − | + | var err error | |
| − | + | db, err = sql.Open("sqlite3", "./auth.db") | |
| − | + | if err != nil { | |
| − | + | log.Fatalf("Failed to open database: %v", err) | |
| − | + | } | |
| − | + | defer db.Close() | |
| + | |||
| + | // Read environment variables for backend credentials and target URL | ||
| + | backendUsername := os.Getenv("BACKEND_USERNAME") | ||
| + | if backendUsername == "" { | ||
| + | log.Fatal("Environment variable BACKEND_USERNAME is required") | ||
| + | } | ||
| + | |||
| + | backendPassword := os.Getenv("BACKEND_PASSWORD") | ||
| + | if backendPassword == "" { | ||
| + | log.Fatal("Environment variable BACKEND_PASSWORD is required") | ||
| + | } | ||
| + | |||
| + | targetURL := os.Getenv("TARGET_URL") | ||
| + | if targetURL == "" { | ||
| + | log.Fatal("Environment variable TARGET_URL is required") | ||
| + | } | ||
| + | |||
| + | target, err := url.Parse(targetURL) | ||
| + | if err != nil { | ||
| + | log.Fatalf("Invalid target URL: %v", err) | ||
| + | } | ||
| − | + | proxy := httputil.NewSingleHostReverseProxy(target) | |
| − | + | // Modify the proxy's director to add backend Basic Auth credentials | |
| − | + | originalDirector := proxy.Director | |
| − | + | proxy.Director = func(req *http.Request) { | |
| − | + | originalDirector(req) | |
| + | req.SetBasicAuth(backendUsername, backendPassword) | ||
| + | } | ||
| − | + | http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { | |
| − | + | if !authenticate(r) { | |
| − | + | http.Error(w, "Unauthorized", http.StatusUnauthorized) | |
| − | + | return | |
| − | + | } | |
| − | + | proxy.ServeHTTP(w, r) | |
| − | + | }) | |
| − | |||
| − | + | log.Println("Reverse proxy running on :8080") | |
| − | + | log.Fatal(http.ListenAndServe(":8080", nil)) | |
} | } | ||
// Authenticate using Basic Auth with SQLite | // Authenticate using Basic Auth with SQLite | ||
func authenticate(r *http.Request) bool { | func authenticate(r *http.Request) bool { | ||
| − | + | authHeader := r.Header.Get("Authorization") | |
| − | + | if authHeader == "" || !strings.HasPrefix(authHeader, "Basic ") { | |
| − | + | return false | |
| − | + | } | |
| − | + | decoded, err := base64.StdEncoding.DecodeString(strings.TrimPrefix(authHeader, "Basic ")) | |
| − | + | if err != nil { | |
| − | + | return false | |
| − | + | } | |
| − | + | parts := strings.SplitN(string(decoded), ":", 2) | |
| − | + | if len(parts) != 2 { | |
| − | + | return false | |
| − | + | } | |
| − | + | username, password := parts[0], parts[1] | |
| − | + | return validateUser(username, password) | |
} | } | ||
// Validate user from SQLite database | // Validate user from SQLite database | ||
func validateUser(username, password string) bool { | func validateUser(username, password string) bool { | ||
| − | + | var storedPassword string | |
| − | + | err := db.QueryRow("SELECT password FROM users WHERE username = ?", username).Scan(&storedPassword) | |
| − | + | if err != nil { | |
| − | + | return false | |
| − | + | } | |
| − | + | return password == storedPassword | |
} | } | ||
``` | ``` | ||
| + | |||
| + | ``` | ||
| + | mkdir push-proxy | ||
| + | cd push-proxy | ||
| + | go mod init | ||
| + | go mod tidy | ||
| + | go build | ||
| + | ``` | ||
| + | |||
| + | ``` | ||
| + | set -a | ||
| + | TARGET_URL=https://myprompush.example.com | ||
| + | BACKEND_USERNAME=foo | ||
| + | BACKEND_PASSWORD=bar | ||
| + | ``` | ||
| + | |||
| + | |||
``` | ``` | ||
Revision as of 16:27, 23 February 2025
sqlite3 auth.db
CREATE TABLE users (
username TEXT PRIMARY KEY,
password TEXT NOT NULL
);
INSERT INTO users (username, password) VALUES ('admin', 'password123');
package main
import (
"database/sql"
"encoding/base64"
"log"
"net/http"
"net/http/httputil"
"net/url"
"os"
"strings"
_ "github.com/mattn/go-sqlite3"
)
// Database connection
var db *sql.DB
func main() {
var err error
db, err = sql.Open("sqlite3", "./auth.db")
if err != nil {
log.Fatalf("Failed to open database: %v", err)
}
defer db.Close()
// Read environment variables for backend credentials and target URL
backendUsername := os.Getenv("BACKEND_USERNAME")
if backendUsername == "" {
log.Fatal("Environment variable BACKEND_USERNAME is required")
}
backendPassword := os.Getenv("BACKEND_PASSWORD")
if backendPassword == "" {
log.Fatal("Environment variable BACKEND_PASSWORD is required")
}
targetURL := os.Getenv("TARGET_URL")
if targetURL == "" {
log.Fatal("Environment variable TARGET_URL is required")
}
target, err := url.Parse(targetURL)
if err != nil {
log.Fatalf("Invalid target URL: %v", err)
}
proxy := httputil.NewSingleHostReverseProxy(target)
// Modify the proxy's director to add backend Basic Auth credentials
originalDirector := proxy.Director
proxy.Director = func(req *http.Request) {
originalDirector(req)
req.SetBasicAuth(backendUsername, backendPassword)
}
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
if !authenticate(r) {
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
}
proxy.ServeHTTP(w, r)
})
log.Println("Reverse proxy running on :8080")
log.Fatal(http.ListenAndServe(":8080", nil))
}
// Authenticate using Basic Auth with SQLite
func authenticate(r *http.Request) bool {
authHeader := r.Header.Get("Authorization")
if authHeader == "" || !strings.HasPrefix(authHeader, "Basic ") {
return false
}
decoded, err := base64.StdEncoding.DecodeString(strings.TrimPrefix(authHeader, "Basic "))
if err != nil {
return false
}
parts := strings.SplitN(string(decoded), ":", 2)
if len(parts) != 2 {
return false
}
username, password := parts[0], parts[1]
return validateUser(username, password)
}
// Validate user from SQLite database
func validateUser(username, password string) bool {
var storedPassword string
err := db.QueryRow("SELECT password FROM users WHERE username = ?", username).Scan(&storedPassword)
if err != nil {
return false
}
return password == storedPassword
}
mkdir push-proxy cd push-proxy go mod init go mod tidy go build
set -a TARGET_URL=https://myprompush.example.com BACKEND_USERNAME=foo BACKEND_PASSWORD=bar
curl -u admin:password123 http://localhost:8080/api/v1/push -d '{}'