Checkpoint

This commit is contained in:
2025-10-01 15:37:55 +02:00
parent 4a60059ff2
commit 03352e3312
31 changed files with 2611 additions and 384 deletions

206
cmd/styx/config.go Normal file
View File

@@ -0,0 +1,206 @@
package main
import (
"crypto/tls"
"fmt"
"github.com/hashicorp/hcl/v2"
"github.com/hashicorp/hcl/v2/gohcl"
"github.com/hashicorp/hcl/v2/hclsimple"
"git.maze.io/maze/styx/dataset"
"git.maze.io/maze/styx/internal/cryptutil"
"git.maze.io/maze/styx/logger"
"git.maze.io/maze/styx/policy"
"git.maze.io/maze/styx/proxy"
)
type Config struct {
Proxy ProxyConfig `hcl:"proxy,block"`
Policy []PolicyConfig `hcl:"policy,block"`
Data DataConfig `hcl:"data,block"`
}
func (c Config) Proxies(log logger.Structured) ([]*proxy.Proxy, error) {
policies := make(map[string]*policy.Policy)
for _, pc := range c.Policy {
p, err := policy.New(pc.Path, pc.Package)
if err != nil {
return nil, fmt.Errorf("policy %s: %w", pc.Name, err)
}
policies[pc.Name] = p
}
var (
onRequest []proxy.RequestHandler
onResponse []proxy.ResponseHandler
)
for _, name := range c.Proxy.On.Request {
log.Value("policy", name).Debug("Resolving request policy")
p, ok := policies[name]
if !ok {
return nil, fmt.Errorf("on request: no policy named %q", name)
}
onRequest = append(onRequest, policy.NewRequestHandler(p))
}
for _, name := range c.Proxy.On.Response {
log.Value("policy", name).Debug("Resolving response policy")
p, ok := policies[name]
if !ok {
return nil, fmt.Errorf("on response: no policy named %q", name)
}
onResponse = append(onResponse, policy.NewResponseHandler(p))
}
var proxies []*proxy.Proxy
for _, pc := range c.Proxy.Port {
log.Value("port", pc.Listen).Debug("Configuring proxy port")
p, err := pc.Proxy()
if err != nil {
return nil, err
}
p.OnRequest = append(p.OnRequest, onRequest...)
p.OnResponse = append(p.OnResponse, onResponse...)
proxies = append(proxies, p)
}
return proxies, nil
}
type ProxyConfig struct {
Port []PortConfig `hcl:"port,block"`
Upstream []string `hcl:"upstream"`
On ProxyPolicyConfig `hcl:"on,block"`
}
type PortConfig struct {
Listen string `hcl:"port,label"`
TLS *PortTLSConfig `hcl:"tls,block"`
Transparent int `hcl:"transparent,optional"`
Name string `hcl:"name,optional"`
}
type PortTLSConfig struct {
Cert string `hcl:"cert"`
Key string `hcl:"key,optional"`
CA string `hcl:"ca,optional"`
}
func (c PortConfig) Proxy() (*proxy.Proxy, error) {
p := proxy.New()
if c.Transparent > 0 {
p.OnConnect = append(p.OnConnect, proxy.Transparent(c.Transparent))
} else if c.TLS != nil {
cert, err := cryptutil.LoadTLSCertificate(c.TLS.Cert, c.TLS.Key)
if err != nil {
return nil, err
}
config := new(tls.Config)
config.Certificates = []tls.Certificate{cert}
if c.TLS.CA != "" {
roots, err := cryptutil.LoadRoots(c.TLS.CA)
if err != nil {
return nil, err
}
config.RootCAs = roots
}
p.OnConnect = append(p.OnConnect, proxy.TLS(config))
}
return p, nil
}
type ProxyPolicyConfig struct {
Intercept []string `hcl:"intercept,optional"`
Request []string `hcl:"request,optional"`
Response []string `hcl:"response,optional"`
}
type PolicyConfig struct {
Name string `hcl:"name,label"`
Path string `hcl:"path"`
Package string `hcl:"package,optional"`
}
type DataConfig struct {
Path string `hcl:"path,optional"`
Domains []DomainDataConfig `hcl:"domain,block"`
Networks []NetworkDataConfig `hcl:"network,block"`
}
func (c DataConfig) Configure() error {
for _, dc := range c.Domains {
if err := dc.Configure(); err != nil {
return fmt.Errorf("error setting up domain data %q: %w", dc.Name, err)
}
}
for _, nc := range c.Networks {
if err := nc.Configure(); err != nil {
return fmt.Errorf("error setting up network data %q: %w", nc.Name, err)
}
}
return nil
}
type DomainDataConfig struct {
Name string `hcl:"name,label"`
Type string `hcl:"type"`
Body hcl.Body `hcl:",remain"`
}
func (c DomainDataConfig) Configure() error {
switch c.Type {
case "", "list":
var justTheList struct {
List []string `hcl:"list"`
}
if diag := gohcl.DecodeBody(c.Body, nil, &justTheList); diag.HasErrors() {
return diag
}
dataset.Domains[c.Name] = dataset.NewDomainList(justTheList.List...)
default:
return fmt.Errorf("unknown type %q", c.Type)
}
return nil
}
type NetworkDataConfig struct {
Name string `hcl:"name,label"`
Type string `hcl:"type"`
Body hcl.Body `hcl:",remain"`
}
func (c NetworkDataConfig) Configure() error {
switch c.Type {
case "", "list":
var justTheList struct {
List []string `hcl:"list"`
}
if diag := gohcl.DecodeBody(c.Body, nil, &justTheList); diag.HasErrors() {
return diag
}
list, err := dataset.NewNetworkTree(justTheList.List...)
if err != nil {
return err
}
dataset.Networks[c.Name] = list
default:
return fmt.Errorf("unknown type %q", c.Type)
}
return nil
}
func Load(name string) (*Config, error) {
config := new(Config)
if err := hclsimple.DecodeFile(name, nil, config); err != nil {
return nil, err
}
return config, nil
}

View File

@@ -2,84 +2,90 @@ package main
import (
"flag"
"net"
"os"
"os/signal"
"syscall"
"github.com/hashicorp/hcl/v2/hclsimple"
"git.maze.io/maze/styx/internal/log"
"git.maze.io/maze/styx/logger"
"git.maze.io/maze/styx/proxy"
"git.maze.io/maze/styx/proxy/cache"
"git.maze.io/maze/styx/proxy/match"
"git.maze.io/maze/styx/proxy/mitm"
"git.maze.io/maze/styx/proxy/resolver"
)
func main() {
configFlag := flag.String("config", "styx.hcl", "Configuration file")
traceFlag := flag.Bool("T", false, "Enable trace level logging")
debugFlag := flag.Bool("D", false, "Enable debug level logging")
var (
configFile = "styx.hcl"
traceFlag bool
debugFlag bool
)
flag.StringVar(&configFile, "config", configFile, "configuration file")
flag.BoolVar(&traceFlag, "T", traceFlag, "enable trace logging")
flag.BoolVar(&debugFlag, "D", debugFlag, "enable debug logging")
flag.Parse()
if *traceFlag {
log.SetLevel(log.TraceLevel)
} else if *debugFlag {
log.SetLevel(log.DebugLevel)
log := logger.StandardLog.Value("path", configFile)
if traceFlag {
log.SetLevel(logger.TraceLevel)
} else if debugFlag {
log.SetLevel(logger.DebugLevel)
}
config, err := load(*configFlag)
config, err := Load(configFile)
if err != nil {
log.Fatal().Err(err).Msg("")
log.Err(err).Fatal("Invalid configuration file")
}
matchers, err := config.Match.Matchers()
if err = config.Data.Configure(); err != nil {
log.Err(err).Fatal("Invalid data configuration")
}
proxies, err := config.Proxies(log)
if err != nil {
log.Fatal().Err(err).Msg("")
} else if err = config.Proxy.Policy.Configure(matchers); err != nil {
log.Fatal().Err(err).Msg("")
log.Err(err).Fatal("Error configuring proxy ports")
}
var ca mitm.Authority
if config.MITM != nil {
if ca, err = mitm.New(config.MITM); err != nil {
log.Fatal().Err(err).Msg("error configuring mitm")
var (
errs = make(chan error, 1)
done = make(chan struct{}, 1)
sigs = make(chan os.Signal, 1)
)
for i, p := range proxies {
go run(config.Proxy.Port[i].Listen, p, errs)
}
signal.Notify(sigs, syscall.SIGINT, syscall.SIGHUP)
for {
select {
case sig := <-sigs:
switch sig {
case syscall.SIGHUP:
log.Value("signal", sig.String()).Warn("Ignored reload signal ¯\\_(ツ)_/¯")
default:
log.Value("signal", sig.String()).Info("Shutting down on signal")
return
}
case <-done:
log.Info("Shutting down gracefully")
return
case err = <-errs:
log.Err(err).Fatal("Shutting down because of fatal error in proxy")
}
}
}
server, err := proxy.New(&config.Proxy, ca)
func run(addr string, port *proxy.Proxy, errors chan<- error) {
log := logger.StandardLog.Value("port", addr)
log.Info("Proxy port starting")
l, err := net.Listen("tcp", addr)
if err != nil {
log.Fatal().Err(err).Msg("")
errors <- err
return
}
if err = server.Start(); err != nil {
log.Fatal().Err(err).Msg("")
}
signalChannel := make(chan os.Signal, 1)
signal.Notify(signalChannel, syscall.SIGINT, syscall.SIGTERM)
<-signalChannel
server.Close()
}
type Config struct {
DNS *resolver.Config `hcl:"dns,block"`
Proxy proxy.Config `hcl:"proxy,block"`
MITM *mitm.Config `hcl:"mitm,block"`
Cache *cache.Config `hcl:"cache,block"`
Match *match.Config `hcl:"match,block"`
}
func load(name string) (*Config, error) {
config := new(Config)
if err := hclsimple.DecodeFile(name, nil, config); err != nil {
return nil, err
}
if config.DNS != nil {
config.Proxy.Resolver = resolver.New(*config.DNS)
}
return config, nil
log.Info("Proxy port ready")
errors <- port.Serve(l)
}