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