initial commit
This commit is contained in:
commit
c9565782b4
5 changed files with 308 additions and 0 deletions
108
config.go
Normal file
108
config.go
Normal file
|
@ -0,0 +1,108 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/sha256"
|
||||||
|
"net/url"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/goccy/go-yaml"
|
||||||
|
)
|
||||||
|
|
||||||
|
type listenerDef struct {
|
||||||
|
protocol string
|
||||||
|
address string
|
||||||
|
acceptProxy bool
|
||||||
|
}
|
||||||
|
|
||||||
|
var listenerDefs []listenerDef
|
||||||
|
|
||||||
|
type credentials struct {
|
||||||
|
username []byte
|
||||||
|
password []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
type pathDef struct {
|
||||||
|
path string
|
||||||
|
targetURL *url.URL
|
||||||
|
credentials []credentials
|
||||||
|
}
|
||||||
|
|
||||||
|
var hostDefs map[string][]pathDef
|
||||||
|
|
||||||
|
func parseConfig(configFile string) {
|
||||||
|
configData, err := os.ReadFile(configFile)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var config struct {
|
||||||
|
Listeners []struct {
|
||||||
|
Protocol string
|
||||||
|
Address string
|
||||||
|
AcceptProxy bool
|
||||||
|
}
|
||||||
|
Hosts []struct {
|
||||||
|
Host string
|
||||||
|
Paths []struct {
|
||||||
|
Path string
|
||||||
|
Address string
|
||||||
|
Credentials []struct {
|
||||||
|
Username string
|
||||||
|
Password string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
err = yaml.Unmarshal(configData, &config)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
hostDefs = make(map[string][]pathDef, len(config.Hosts))
|
||||||
|
for _, host := range config.Hosts {
|
||||||
|
hostDef := make([]pathDef, len(host.Paths))
|
||||||
|
hostDefs[host.Host] = hostDef
|
||||||
|
for i, path := range host.Paths {
|
||||||
|
targetURL, err := url.Parse(path.Address)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var pdCredentials []credentials
|
||||||
|
if path.Credentials != nil {
|
||||||
|
pdCredentials = make([]credentials, len(path.Credentials))
|
||||||
|
for j, cfgCredentials := range path.Credentials {
|
||||||
|
hash := sha256.New()
|
||||||
|
hash.Write([]byte(cfgCredentials.Username))
|
||||||
|
usernameSum := hash.Sum(nil)
|
||||||
|
|
||||||
|
hash = sha256.New()
|
||||||
|
hash.Write([]byte(cfgCredentials.Password))
|
||||||
|
passwordSum := hash.Sum(nil)
|
||||||
|
|
||||||
|
pdCredentials[j] = credentials{
|
||||||
|
username: usernameSum,
|
||||||
|
password: passwordSum,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
hostDef[i] = pathDef{
|
||||||
|
path: path.Path,
|
||||||
|
targetURL: targetURL,
|
||||||
|
credentials: pdCredentials,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
listenerDefs = make([]listenerDef, len(config.Listeners))
|
||||||
|
for i, listener := range config.Listeners {
|
||||||
|
listenerDef := listenerDef{
|
||||||
|
protocol: listener.Protocol,
|
||||||
|
address: listener.Address,
|
||||||
|
acceptProxy: listener.AcceptProxy,
|
||||||
|
}
|
||||||
|
listenerDefs[i] = listenerDef
|
||||||
|
}
|
||||||
|
}
|
8
go.mod
Normal file
8
go.mod
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
module git.linuxping.win/dgrp/v2
|
||||||
|
|
||||||
|
go 1.24.5
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/armon/go-proxyproto v0.1.0 // indirect
|
||||||
|
github.com/goccy/go-yaml v1.18.0 // indirect
|
||||||
|
)
|
4
go.sum
Normal file
4
go.sum
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
github.com/armon/go-proxyproto v0.1.0 h1:TWWcSsjco7o2itn6r25/5AqKBiWmsiuzsUDLT/MTl7k=
|
||||||
|
github.com/armon/go-proxyproto v0.1.0/go.mod h1:Xj90dce2VKbHzRAeiVQAMBtj4M5oidoXJ8lmgyW21mw=
|
||||||
|
github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw=
|
||||||
|
github.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
|
49
log.go
Normal file
49
log.go
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type responseWriter struct {
|
||||||
|
http.ResponseWriter
|
||||||
|
status int
|
||||||
|
wroteHeader bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rw *responseWriter) WriteHeader(code int) {
|
||||||
|
if rw.wroteHeader {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
rw.status = code
|
||||||
|
rw.ResponseWriter.WriteHeader(code)
|
||||||
|
rw.wroteHeader = true
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func loghttpreq(next http.Handler) http.Handler {
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
method := r.Method
|
||||||
|
path := r.URL.EscapedPath()
|
||||||
|
endpoint := strings.Split(r.RemoteAddr, ":")
|
||||||
|
ip := endpoint[0]
|
||||||
|
wrapped := &responseWriter{
|
||||||
|
ResponseWriter: w,
|
||||||
|
}
|
||||||
|
start := time.Now()
|
||||||
|
next.ServeHTTP(wrapped, r)
|
||||||
|
responseTime := time.Since(start)
|
||||||
|
log.Printf(
|
||||||
|
"%-15v %14v %v %-7v %v",
|
||||||
|
ip,
|
||||||
|
responseTime,
|
||||||
|
wrapped.status,
|
||||||
|
method,
|
||||||
|
path,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
139
main.go
Normal file
139
main.go
Normal file
|
@ -0,0 +1,139 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"crypto/sha256"
|
||||||
|
"crypto/subtle"
|
||||||
|
"flag"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httputil"
|
||||||
|
"net/url"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/armon/go-proxyproto"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
var configFile string
|
||||||
|
flag.StringVar(&configFile, "config", "config.yaml", "Config file")
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
|
parseConfig(configFile)
|
||||||
|
|
||||||
|
handler := loghttpreq(proxy())
|
||||||
|
|
||||||
|
wg := sync.WaitGroup{}
|
||||||
|
|
||||||
|
for _, ld := range listenerDefs {
|
||||||
|
listener, err := net.Listen(ld.protocol, ld.address)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if ld.acceptProxy {
|
||||||
|
listener = &proxyproto.Listener{
|
||||||
|
Listener: listener,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
wg.Add(1)
|
||||||
|
go func() {
|
||||||
|
http.Serve(listener, handler)
|
||||||
|
wg.Done()
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
wg.Wait()
|
||||||
|
}
|
||||||
|
|
||||||
|
func rewrite(proxyRequest *httputil.ProxyRequest) {
|
||||||
|
target, ok := proxyRequest.In.Context().Value("target").(*url.URL)
|
||||||
|
if !ok {
|
||||||
|
panic("unreachable")
|
||||||
|
}
|
||||||
|
proxyRequest.SetURL(target)
|
||||||
|
proxyRequest.SetXForwarded()
|
||||||
|
}
|
||||||
|
|
||||||
|
func proxy() http.Handler {
|
||||||
|
revproxy := &httputil.ReverseProxy{
|
||||||
|
Rewrite: rewrite,
|
||||||
|
}
|
||||||
|
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
tmp := strings.Split(r.Host, ":")
|
||||||
|
if len(tmp) == 0 {
|
||||||
|
w.WriteHeader(400)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
hst := tmp[0]
|
||||||
|
|
||||||
|
hd, ok := hostDefs[hst]
|
||||||
|
if !ok {
|
||||||
|
hd, ok = hostDefs[""]
|
||||||
|
if !ok {
|
||||||
|
w.WriteHeader(400)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var pathDef pathDef
|
||||||
|
matched := false
|
||||||
|
for _, hostPathDef := range hd {
|
||||||
|
if !strings.HasPrefix(r.URL.Path, hostPathDef.path) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !matched || len(hostPathDef.path) > len(pathDef.path) {
|
||||||
|
matched = true
|
||||||
|
pathDef = hostPathDef
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !matched {
|
||||||
|
w.WriteHeader(400)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
r.URL.Path = r.URL.Path[len(pathDef.path):]
|
||||||
|
|
||||||
|
if pathDef.credentials != nil {
|
||||||
|
match := false
|
||||||
|
|
||||||
|
username, password, ok := r.BasicAuth()
|
||||||
|
if ok {
|
||||||
|
hash := sha256.New()
|
||||||
|
hash.Write([]byte(username))
|
||||||
|
usernameSum := hash.Sum(nil)
|
||||||
|
|
||||||
|
hash = sha256.New()
|
||||||
|
hash.Write([]byte(password))
|
||||||
|
passwordSum := hash.Sum(nil)
|
||||||
|
|
||||||
|
for _, cred := range pathDef.credentials {
|
||||||
|
res1 := subtle.ConstantTimeCompare(cred.username, usernameSum) == 1
|
||||||
|
res2 := subtle.ConstantTimeCompare(cred.password, passwordSum) == 1
|
||||||
|
if res1 && res2 {
|
||||||
|
match = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !match {
|
||||||
|
w.Header().Set("WWW-Authenticate", "Basic")
|
||||||
|
w.WriteHeader(401)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := r.Context()
|
||||||
|
ctx = context.WithValue(ctx, "target", pathDef.targetURL)
|
||||||
|
r = r.WithContext(ctx)
|
||||||
|
|
||||||
|
w.Header().Add("Server", "dgrp")
|
||||||
|
|
||||||
|
revproxy.ServeHTTP(w, r)
|
||||||
|
})
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue