133 lines
2.8 KiB
Go
133 lines
2.8 KiB
Go
package main
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
|
|
// "fmt"
|
|
"log"
|
|
"net/http"
|
|
"os"
|
|
"time"
|
|
|
|
"gopkg.in/yaml.v3"
|
|
)
|
|
|
|
type Config struct {
|
|
Listen string `yaml:"listen"`
|
|
Auth AuthConfig `yaml:"auth"`
|
|
Credentials map[string]Credential `yaml:"credentials"`
|
|
}
|
|
|
|
type AuthConfig struct {
|
|
User string `yaml:"user"`
|
|
Pass string `yaml:"pass"`
|
|
}
|
|
|
|
type Credential struct {
|
|
Username string `yaml:"username"`
|
|
Password string `yaml:"password"`
|
|
Note string `yaml:"note"`
|
|
}
|
|
|
|
type Response struct {
|
|
Service string `json:"service"`
|
|
Username string `json:"username"`
|
|
Password string `json:"password"`
|
|
Note string `json:"note,omitempty"`
|
|
IssuedAt string `json:"issued_at"`
|
|
}
|
|
|
|
func basicAuthOK(r *http.Request, cfg *Config) bool {
|
|
user, pass, ok := r.BasicAuth()
|
|
if !ok {
|
|
return false
|
|
}
|
|
return user == cfg.Auth.User && pass == cfg.Auth.Pass
|
|
}
|
|
|
|
func newMux(cfg *Config) *http.ServeMux {
|
|
mux := http.NewServeMux()
|
|
mux.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) {
|
|
w.WriteHeader(http.StatusOK)
|
|
_, _ = w.Write([]byte("ok"))
|
|
})
|
|
|
|
mux.HandleFunc("/creds", func(w http.ResponseWriter, r *http.Request) {
|
|
if !basicAuthOK(r, cfg) {
|
|
w.Header().Set("WWW-Authenticate", `Basic realm="proxyfier"`)
|
|
http.Error(w, "unauthorized", http.StatusUnauthorized)
|
|
return
|
|
}
|
|
|
|
service := r.URL.Query().Get("service")
|
|
if service == "" {
|
|
http.Error(w, "service is required", http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
cred, ok := cfg.Credentials[service]
|
|
if !ok {
|
|
http.Error(w, "service not found", http.StatusNotFound)
|
|
return
|
|
}
|
|
|
|
resp := Response{
|
|
Service: service,
|
|
Username: cred.Username,
|
|
Password: cred.Password,
|
|
Note: cred.Note,
|
|
IssuedAt: time.Now().UTC().Format(time.RFC3339),
|
|
}
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
if err := json.NewEncoder(w).Encode(resp); err != nil {
|
|
http.Error(w, "encode error", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
})
|
|
|
|
return mux
|
|
}
|
|
|
|
func loadConfig(path string) (*Config, error) {
|
|
data, err := os.ReadFile(path)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
var cfg Config
|
|
if err := yaml.Unmarshal(data, &cfg); err != nil {
|
|
return nil, err
|
|
}
|
|
if cfg.Listen == "" {
|
|
cfg.Listen = "0.0.0.0:9000"
|
|
}
|
|
if cfg.Auth.User == "" || cfg.Auth.Pass == "" {
|
|
return nil, errors.New("auth.user/auth.pass must be set")
|
|
}
|
|
return &cfg, nil
|
|
}
|
|
|
|
func main() {
|
|
cfgPath := os.Getenv("PROXYFIER_CONFIG")
|
|
if cfgPath == "" {
|
|
cfgPath = "config.yaml"
|
|
}
|
|
|
|
cfg, err := loadConfig(cfgPath)
|
|
if err != nil {
|
|
log.Fatalf("config error: %v", err)
|
|
}
|
|
|
|
srv := &http.Server{
|
|
Addr: cfg.Listen,
|
|
Handler: newMux(cfg),
|
|
ReadHeaderTimeout: 5 * time.Second,
|
|
}
|
|
|
|
log.Printf("proxyfier listening on %s", cfg.Listen)
|
|
if err := srv.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
|
|
log.Fatalf("server error: %v", err)
|
|
}
|
|
}
|