feat(proxy): Init commit

This commit is contained in:
Björn Benouarets
2025-11-10 19:08:13 +01:00
commit 56d609fafd
9 changed files with 208 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
docker-compose.newt.yaml

17
Dockerfile Normal file
View File

@@ -0,0 +1,17 @@
FROM golang:1.25.3-alpine3.22 AS builder
WORKDIR /app
COPY ./app/go.mod ./app/go.sum ./
RUN go mod download && go mod verify
COPY ./app ./.
RUN go build -o proxy .
FROM alpine:latest AS runner
COPY --from=builder /app/proxy /app/proxy
CMD ["/app/proxy"]

17
README.md Normal file
View File

@@ -0,0 +1,17 @@
# Secure Ollama API
This is a simple API that proxies requests to the Ollama API and adds authentication.
## Configuration
- `OLLAMA_SERVER_URL`: The URL of the Ollama server.
- `OLLAMA_API_KEY`: The API key to use for authentication.
## Running
```bash
git clone https://git.secnex.io/secnex/secure-ollama-api.git
cd secure-ollama-api
docker build -t secure-ollama-api .
docker run -d -p 8080:8080 secure-ollama-api
```

5
app/go.mod Normal file
View File

@@ -0,0 +1,5 @@
module secure-ollama-api
go 1.25.3
require git.secnex.io/secnex/masterlog v0.1.0

2
app/go.sum Normal file
View File

@@ -0,0 +1,2 @@
git.secnex.io/secnex/masterlog v0.1.0 h1:74j9CATpfeK0lxpWIQC9ag9083akwG8khi5BwLedD8E=
git.secnex.io/secnex/masterlog v0.1.0/go.mod h1:OnDlwEzdkKMnqY+G5O9kHdhoJ6fH1llbVdXpgSc5SdM=

18
app/main.go Normal file
View File

@@ -0,0 +1,18 @@
package main
import (
"git.secnex.io/secnex/masterlog"
)
func main() {
proxy, err := NewProxy()
if err != nil {
masterlog.Error("Failed to create proxy", map[string]interface{}{
"error": err.Error(),
})
return
}
masterlog.Info("Starting proxy server on :8080")
proxy.Start()
}

110
app/proxy.go Normal file
View File

@@ -0,0 +1,110 @@
package main
import (
"net/http"
"net/http/httputil"
"net/url"
"strings"
"time"
"secure-ollama-api/utils"
"git.secnex.io/secnex/masterlog"
)
type Proxy struct {
ReverseProxy *httputil.ReverseProxy
TargetURL *url.URL
APIKey string
}
type responseWriter struct {
http.ResponseWriter
statusCode int
}
func NewProxy() (*Proxy, error) {
remote, err := url.Parse(utils.GetEnv("OLLAMA_SERVER_URL", "http://ollama:11434"))
if err != nil {
masterlog.Error(err.Error())
return nil, err
}
apiKey := utils.GetEnv("OLLAMA_API_KEY", "")
if apiKey == "" {
masterlog.Error("OLLAMA_API_KEY environment variable is not set")
}
return &Proxy{
ReverseProxy: httputil.NewSingleHostReverseProxy(remote),
TargetURL: remote,
APIKey: apiKey,
}, nil
}
func (rw *responseWriter) WriteHeader(code int) {
rw.statusCode = code
rw.ResponseWriter.WriteHeader(code)
}
func (p *Proxy) RequestHandler() func(w http.ResponseWriter, r *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
masterlog.Info("Request received", map[string]interface{}{
"url": r.URL.String(),
"ip": r.RemoteAddr,
})
startTime := time.Now()
// Check Authorization header
authHeader := r.Header.Get("Authorization")
expectedAuth := "Bearer " + p.APIKey
if p.APIKey == "" {
masterlog.Error("OLLAMA_API_KEY is not configured", map[string]interface{}{
"url": r.URL.String(),
"ip": r.RemoteAddr,
})
http.Error(w, "Internal server error", http.StatusInternalServerError)
return
}
if authHeader == "" || !strings.HasPrefix(authHeader, "Bearer ") {
masterlog.Info("Unauthorized request - missing or invalid Authorization header", map[string]interface{}{
"url": r.URL.String(),
"ip": r.RemoteAddr,
})
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
}
if authHeader != expectedAuth {
masterlog.Info("Unauthorized request - invalid API key", map[string]interface{}{
"url": r.URL.String(),
"ip": r.RemoteAddr,
})
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
}
// Wrap response writer to capture status code
rw := &responseWriter{
ResponseWriter: w,
statusCode: http.StatusOK, // default status code
}
p.ReverseProxy.ServeHTTP(rw, r)
duration := time.Since(startTime)
masterlog.Info("Request completed", map[string]interface{}{
"url": r.URL.String(),
"duration": duration.Milliseconds(),
"statusCode": rw.statusCode,
"ip": r.RemoteAddr,
})
}
}
func (p *Proxy) Start() {
http.HandleFunc("/", p.RequestHandler())
http.ListenAndServe(":8080", nil)
}

11
app/utils/env.go Normal file
View File

@@ -0,0 +1,11 @@
package utils
import "os"
func GetEnv(key, defaultValue string) string {
value := os.Getenv(key)
if value == "" {
return defaultValue
}
return value
}

27
docker-compose.yaml Normal file
View File

@@ -0,0 +1,27 @@
networks:
ollama:
name: ollama
external: false
services:
proxy:
build:
context: .
dockerfile: Dockerfile
ports:
- 8080:8080
restart: always
environment:
- OLLAMA_SERVER_URL=http://ollama:11434
- OLLAMA_API_KEY=sk-P+NXFFhYFuH1Rqk8WX7nDb/KROxFUTGrDCcqFtR5WQw
networks:
- ollama
depends_on:
- ollama
ollama:
image: ollama/ollama:latest
networks:
- ollama
restart: always
volumes:
- /opt/ollama:/root/.ollama