First commit

This commit is contained in:
ghostersk
2025-03-01 11:39:07 +00:00
commit a86ef6fe1d
8 changed files with 507 additions and 0 deletions
+11
View File
@@ -0,0 +1,11 @@
# GoLangProxy
- simple application written in go lang for proxing http and https with built in self signed certificate function.
### setup project
go mod init proxy
### Running Proxy app without compiling.
go run main.go config.go certificate.go proxy.go utils.go
### Building app:
go build -o proxy
+125
View File
@@ -0,0 +1,125 @@
package main
import (
"crypto/rand"
"crypto/rsa"
"crypto/tls"
"crypto/x509"
"crypto/x509/pkix"
"encoding/pem"
"fmt"
"log"
"math/big"
"os"
"time"
)
func generateSelfSignedCert() error {
if err := os.MkdirAll(certDir, 0755); err != nil {
return fmt.Errorf("failed to create certificate directory %s: %v", certDir, err)
}
priv, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
return fmt.Errorf("failed to generate private key: %v", err)
}
notBefore := time.Now()
notAfter := notBefore.Add(365 * 24 * time.Hour)
serialNumber, err := rand.Int(rand.Reader, new(big.Int).Lsh(big.NewInt(1), 128))
if err != nil {
return fmt.Errorf("failed to generate serial number: %v", err)
}
template := x509.Certificate{
SerialNumber: serialNumber,
Subject: pkix.Name{
Organization: []string{"Proxy Self-Signed"},
CommonName: "localhost",
},
NotBefore: notBefore,
NotAfter: notAfter,
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
BasicConstraintsValid: true,
DNSNames: []string{"localhost", "*.example.com"},
}
certDER, err := x509.CreateCertificate(rand.Reader, &template, &template, &priv.PublicKey, priv)
if err != nil {
return fmt.Errorf("failed to create certificate: %v", err)
}
certOut, err := os.Create(certPath)
if err != nil {
return fmt.Errorf("failed to open %s for writing: %v", certPath, err)
}
if err := pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: certDER}); err != nil {
certOut.Close()
return fmt.Errorf("failed to encode certificate: %v", err)
}
certOut.Close()
keyOut, err := os.OpenFile(keyPath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
if err != nil {
return fmt.Errorf("failed to open %s for writing: %v", keyPath, err)
}
if err := pem.Encode(keyOut, &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(priv)}); err != nil {
keyOut.Close()
return fmt.Errorf("failed to encode private key: %v", err)
}
keyOut.Close()
log.Printf("Generated self-signed certificate in %s", certDir)
return nil
}
func loadCertificate() error {
certFile, err := os.ReadFile(certPath)
if err != nil {
return fmt.Errorf("failed to read certificate %s: %v", certPath, err)
}
keyFile, err := os.ReadFile(keyPath)
if err != nil {
return fmt.Errorf("failed to read key %s: %v", keyPath, err)
}
newCert, err := tls.X509KeyPair(certFile, keyFile)
if err != nil {
return fmt.Errorf("failed to parse certificate: %v", err)
}
configMux.Lock()
cert = &newCert
configMux.Unlock()
return nil
}
func monitorCertificates() {
var lastModTime time.Time
for {
certInfo, err := os.Stat(certPath)
if err != nil {
log.Printf("Error checking certificate: %v", err)
time.Sleep(5 * time.Second)
continue
}
keyInfo, err := os.Stat(keyPath)
if err != nil {
log.Printf("Error checking key: %v", err)
time.Sleep(5 * time.Second)
continue
}
if certInfo.ModTime() != lastModTime || keyInfo.ModTime() != lastModTime {
if err := loadCertificate(); err != nil {
log.Printf("Error reloading certificate: %v", err)
} else {
log.Println("Certificate reloaded successfully")
lastModTime = certInfo.ModTime()
}
}
time.Sleep(5 * time.Second)
}
}
+154
View File
@@ -0,0 +1,154 @@
package main
import (
"fmt"
"log"
"os"
"time"
"gopkg.in/yaml.v2"
)
type Config struct {
ListenHTTP string `yaml:"listen_http"`
ListenHTTPS string `yaml:"listen_https"`
CertDir string `yaml:"cert_dir"`
CertFile string `yaml:"cert_file"`
KeyFile string `yaml:"key_file"`
Routes map[string]string `yaml:"routes"`
TrustTarget map[string]bool `yaml:"trust_target"`
NoHTTPSRedirect map[string]bool `yaml:"no_https_redirect"` // New field
}
func loadConfig() (Config, error) {
data, err := os.ReadFile(configPath)
if err != nil {
return Config{}, fmt.Errorf("failed to read config %s: %v", configPath, err)
}
var cfg Config
err = yaml.Unmarshal(data, &cfg)
if err != nil {
return Config{}, fmt.Errorf("failed to unmarshal config: %v", err)
}
return cfg, nil
}
func generateDefaultConfig() Config {
return Config{
ListenHTTP: ":80",
ListenHTTPS: ":443",
CertDir: "./certificates", // default current directory creates certificates folder
CertFile: "certificate.pem",
KeyFile: "key.pem",
Routes: map[string]string{
"*": "http://127.0.0.1:80",
"main.example.com": "http://127.0.0.1:80",
},
TrustTarget: map[string]bool{
"*": true, // default trust all certificates
"main.example.com": false, // use only trusted certificate
},
NoHTTPSRedirect: map[string]bool{
"*": false, // Default: redirect to HTTPS
"main.example.com": true, // set to not redirect HTTP to HTTPS
},
}
}
func saveConfig(cfg Config) error {
if err := os.MkdirAll(baseDir, 0755); err != nil {
return fmt.Errorf("failed to create base directory %s: %v", baseDir, err)
}
data, err := yaml.Marshal(cfg)
if err != nil {
return fmt.Errorf("failed to marshal config: %v", err)
}
err = os.WriteFile(configPath, data, 0644)
if err != nil {
return fmt.Errorf("failed to write config to %s: %v", configPath, err)
}
return nil
}
func monitorConfig() {
var lastModTime time.Time
for {
configInfo, err := os.Stat(configPath)
if err != nil {
log.Printf("Error checking config file: %v", err)
time.Sleep(5 * time.Second)
continue
}
if configInfo.ModTime() != lastModTime {
newConfig, err := loadConfig()
if err != nil {
log.Printf("Error reloading config: %v", err)
} else {
configMux.Lock()
if newConfig.ListenHTTP != config.ListenHTTP {
config.ListenHTTP = newConfig.ListenHTTP
log.Printf("Updated listen_http to %s", config.ListenHTTP)
}
if newConfig.ListenHTTPS != config.ListenHTTPS {
config.ListenHTTPS = newConfig.ListenHTTPS
log.Printf("Updated listen_https to %s", config.ListenHTTPS)
}
if newConfig.CertDir != config.CertDir || newConfig.CertFile != config.CertFile || newConfig.KeyFile != config.KeyFile {
config.CertDir = newConfig.CertDir
config.CertFile = newConfig.CertFile
config.KeyFile = newConfig.KeyFile
updatePaths()
if err := loadCertificate(); err != nil {
log.Printf("Error reloading certificate after path change: %v", err)
} else {
log.Println("Updated certificate paths and reloaded certificate")
}
}
for k, v := range newConfig.Routes {
if oldV, exists := config.Routes[k]; !exists || oldV != v {
config.Routes[k] = v
log.Printf("Updated route %s to %s", k, v)
}
}
for k := range config.Routes {
if _, exists := newConfig.Routes[k]; !exists {
delete(config.Routes, k)
log.Printf("Removed route %s", k)
}
}
for k, v := range newConfig.TrustTarget {
if oldV, exists := config.TrustTarget[k]; !exists || oldV != v {
config.TrustTarget[k] = v
log.Printf("Updated trust_target %s to %v", k, v)
}
}
for k := range config.TrustTarget {
if _, exists := newConfig.TrustTarget[k]; !exists {
delete(config.TrustTarget, k)
log.Printf("Removed trust_target %s", k)
}
}
for k, v := range newConfig.NoHTTPSRedirect {
if oldV, exists := config.NoHTTPSRedirect[k]; !exists || oldV != v {
config.NoHTTPSRedirect[k] = v
log.Printf("Updated no_https_redirect %s to %v", k, v)
}
}
for k := range config.NoHTTPSRedirect {
if _, exists := newConfig.NoHTTPSRedirect[k]; !exists {
delete(config.NoHTTPSRedirect, k)
log.Printf("Removed no_https_redirect %s", k)
}
}
configMux.Unlock()
log.Println("Config reloaded successfully")
lastModTime = configInfo.ModTime()
}
}
time.Sleep(5 * time.Second)
}
}
+5
View File
@@ -0,0 +1,5 @@
module main
go 1.24.0
require gopkg.in/yaml.v2 v2.4.0
+4
View File
@@ -0,0 +1,4 @@
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
+105
View File
@@ -0,0 +1,105 @@
package main
// go run main.go config.go certificate.go proxy.go utils.go
// go build -o proxy
import (
"crypto/tls"
"log"
"net/http"
"os"
"path/filepath"
"runtime"
"sync"
)
// Shared global variables (declared only here)
var (
config Config
configMux sync.RWMutex
cert *tls.Certificate
baseDir string
certDir string
certPath string
keyPath string
configPath string
)
func init() {
if runtime.GOOS == "windows" && len(os.Args) > 0 && filepath.Ext(os.Args[0]) == ".go" {
var err error
baseDir, err = os.Getwd()
if err != nil {
log.Fatalf("Failed to get working directory: %v", err)
}
} else {
exePath, err := os.Executable()
if err != nil {
log.Fatalf("Failed to get executable path: %v", err)
}
baseDir = filepath.Dir(exePath)
}
}
func main() {
configPath = filepath.Join(baseDir, "config.yaml")
if _, err := os.Stat(configPath); os.IsNotExist(err) {
config = generateDefaultConfig()
if err := saveConfig(config); err != nil {
log.Fatalf("Failed to save default config: %v", err)
}
log.Println("Generated default config file")
} else {
cfg, err := loadConfig()
if err != nil {
log.Fatalf("Failed to load config: %v", err)
}
config = cfg
}
updatePaths()
_, certErr := os.Stat(certPath)
_, keyErr := os.Stat(keyPath)
if os.IsNotExist(certErr) || os.IsNotExist(keyErr) {
if err := generateSelfSignedCert(); err != nil {
log.Fatalf("Failed to generate self-signed certificate: %v", err)
}
}
if err := loadCertificate(); err != nil {
log.Fatalf("Failed to load certificate: %v", err)
}
go monitorCertificates()
go monitorConfig()
httpServer := &http.Server{
Addr: config.ListenHTTP,
Handler: http.HandlerFunc(handler),
}
tlsConfig := &tls.Config{
GetCertificate: func(*tls.ClientHelloInfo) (*tls.Certificate, error) {
configMux.RLock()
defer configMux.RUnlock()
return cert, nil
},
}
httpsServer := &http.Server{
Addr: config.ListenHTTPS,
Handler: http.HandlerFunc(handler),
TLSConfig: tlsConfig,
}
go func() {
log.Printf("Starting HTTP server on %s", config.ListenHTTP)
if err := httpServer.ListenAndServe(); err != nil && err != http.ErrServerClosed {
log.Fatalf("HTTP server error: %v", err)
}
}()
log.Printf("Starting HTTPS server on %s", config.ListenHTTPS)
if err := httpsServer.ListenAndServeTLS("", ""); err != nil && err != http.ErrServerClosed {
log.Fatalf("HTTPS server error: %v", err)
}
}
+93
View File
@@ -0,0 +1,93 @@
package main
import (
"crypto/tls"
"log"
"net/http"
"net/http/httputil"
"net/url"
"strings"
)
func getReverseProxy(target string, skipVerify bool) *httputil.ReverseProxy {
targetURL, err := url.Parse(target)
if err != nil {
log.Printf("Error parsing target URL %s: %v", target, err)
return nil
}
director := func(req *http.Request) {
req.URL.Scheme = targetURL.Scheme
req.URL.Host = targetURL.Host
req.Host = targetURL.Host // Set Host header to match target
// Preserve original headers
for k, v := range req.Header {
if k != "Host" { // Host is set above
req.Header[k] = v
}
}
// Ensure the full path is preserved
if targetURL.Path != "" {
req.URL.Path = strings.TrimPrefix(req.URL.Path, "/") // Avoid double slashes
req.URL.Path = singleJoin(targetURL.Path, req.URL.Path)
}
}
transport := &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: skipVerify},
}
return &httputil.ReverseProxy{
Director: director,
Transport: transport,
ErrorHandler: func(w http.ResponseWriter, r *http.Request, err error) {
log.Printf("Proxy error for %s: %v", r.Host, err)
http.Error(w, "Proxy error", http.StatusBadGateway)
},
}
}
// singleJoin ensures a single slash between path segments
func singleJoin(prefix, suffix string) string {
prefix = strings.TrimSuffix(prefix, "/")
suffix = strings.TrimPrefix(suffix, "/")
return prefix + "/" + suffix
}
func handler(w http.ResponseWriter, r *http.Request) {
configMux.RLock()
defer configMux.RUnlock()
target, exists := config.Routes[r.Host]
skipVerify := config.TrustTarget[r.Host]
noHTTPSRedirect := config.NoHTTPSRedirect[r.Host]
if !exists {
if target, exists = config.Routes["*"]; !exists {
http.Error(w, "Host not configured", http.StatusNotFound)
return
}
skipVerify = config.TrustTarget["*"]
noHTTPSRedirect = config.NoHTTPSRedirect["*"]
}
// Check if request is HTTP and target is HTTPS
isHTTPS := target[:5] == "https"
isHTTPReq := r.TLS == nil // r.TLS is nil for HTTP, non-nil for HTTPS
if isHTTPReq && isHTTPS && !noHTTPSRedirect {
// Redirect to HTTPS version of the host
redirectURL := "https://" + r.Host + r.RequestURI
http.Redirect(w, r, redirectURL, http.StatusMovedPermanently)
return
}
proxy := getReverseProxy(target, skipVerify)
if proxy == nil {
http.Error(w, "Invalid target configuration", http.StatusInternalServerError)
return
}
proxy.ServeHTTP(w, r)
}
+10
View File
@@ -0,0 +1,10 @@
package main
import "path/filepath"
func updatePaths() {
certDir = filepath.Join(baseDir, config.CertDir)
certPath = filepath.Join(certDir, config.CertFile)
keyPath = filepath.Join(certDir, config.KeyFile)
configPath = filepath.Join(baseDir, "config.yaml")
}