diff options
Diffstat (limited to 'config.go')
| -rw-r--r-- | config.go | 194 |
1 files changed, 194 insertions, 0 deletions
diff --git a/config.go b/config.go new file mode 100644 index 0000000..ed4b3e0 --- /dev/null +++ b/config.go @@ -0,0 +1,194 @@ +package main + +import ( + "fmt" + "net/netip" + "net/url" + + "github.com/hashicorp/hcl/v2/hclsimple" +) + +type Config struct { + User string + Servers []ServerConfig +} + +type ServerConfig struct { + Protocol string + Host string + Port int + RedirectToHTTPS bool + ACMEChallenge *ACMEChallengeConfig + TLSCertFile string + TLSKeyFile string + Proxies []ProxyConfig +} + +type ACMEChallengeConfig struct { + Root string +} + +type ProxyConfig struct { + Name string + From ProxyFromConfig + To ProxyToConfig +} + +type ProxyFromConfig struct { + Host string +} + +type ProxyToConfig struct { + Host string + Port int +} + +type InternalHCLConfig struct { + User string `hcl:"user,optional"` + Servers []InternalHCLServerConfig `hcl:"server,block"` +} + +type InternalHCLServerConfig struct { + Protocol string `hcl:"protocol,label"` + Host string `hcl:"host"` + Port int `hcl:"port"` + RedirectToHTTPS bool `hcl:"redirect_to_https,optional"` + ACMEChallenge []InternalHCLACMEChallengeConfig `hcl:"acme_challenge,block"` + TLSCertFile string `hcl:"tls_cert_file,optional"` + TLSKeyFile string `hcl:"tls_key_file,optional"` + Proxies []InternalHCLProxyConfig `hcl:"proxy,block"` +} + +type InternalHCLACMEChallengeConfig struct { + Root string `hcl:"root"` +} + +type InternalHCLProxyConfig struct { + Name string `hcl:"name,label"` + From InternalHCLProxyFromConfig `hcl:"from,block"` + To InternalHCLProxyToConfig `hcl:"to,block"` +} + +type InternalHCLProxyFromConfig struct { + Host string `hcl:"host"` +} + +type InternalHCLProxyToConfig struct { + Host string `hcl:"host"` + Port int `hcl:"port"` +} + +func fromHCLConfigToConfig(hclConfig *InternalHCLConfig) *Config { + servers := make([]ServerConfig, len(hclConfig.Servers)) + for i, s := range hclConfig.Servers { + var acmeChallenge *ACMEChallengeConfig + if len(s.ACMEChallenge) != 0 { + acmeChallenge = &ACMEChallengeConfig{ + Root: s.ACMEChallenge[0].Root, + } + } + proxies := make([]ProxyConfig, len(s.Proxies)) + for j, p := range s.Proxies { + proxies[j] = ProxyConfig{ + Name: p.Name, + From: ProxyFromConfig{ + Host: p.From.Host, + }, + To: ProxyToConfig{ + Host: p.To.Host, + Port: p.To.Port, + }, + } + } + servers[i] = ServerConfig{ + Protocol: s.Protocol, + Host: s.Host, + Port: s.Port, + RedirectToHTTPS: s.RedirectToHTTPS, + ACMEChallenge: acmeChallenge, + TLSCertFile: s.TLSCertFile, + TLSKeyFile: s.TLSKeyFile, + Proxies: proxies, + } + } + + return &Config{ + User: hclConfig.User, + Servers: servers, + } +} + +func LoadConfig(fileName string) (*Config, error) { + var hclConfig InternalHCLConfig + err := hclsimple.DecodeFile(fileName, nil, &hclConfig) + if err != nil { + return nil, err + } + + if len(hclConfig.Servers) == 0 { + return nil, fmt.Errorf("No server blocks found") + } + if 2 < len(hclConfig.Servers) { + return nil, fmt.Errorf("Too many server blocks found") + } + + var listenHTTPS = false + var redirectToHTTPS = false + for _, server := range hclConfig.Servers { + if server.Protocol == "https" { + listenHTTPS = true + } else if server.Protocol != "http" { + return nil, fmt.Errorf("Invalid protocol %s", server.Protocol) + } + + _, err = netip.ParseAddr(server.Host) + if err != nil { + return nil, fmt.Errorf("Invalid host %s", server.Host) + } + + if len(server.ACMEChallenge) != 0 && len(server.ACMEChallenge) != 1 { + return nil, fmt.Errorf("Only one acme_challenge block is allowed") + } + if len(server.ACMEChallenge) != 0 && server.Protocol != "http" { + return nil, fmt.Errorf("accept_acme_challenge must be on http listener") + } + + if server.RedirectToHTTPS { + redirectToHTTPS = true + if server.Protocol != "http" { + return nil, fmt.Errorf("redirect_to_https must be on http listener") + } + if len(server.Proxies) != 0 { + return nil, fmt.Errorf("redirect_to_https cannot be used with proxy") + } + } + + if server.Protocol == "https" { + if server.TLSCertFile == "" { + return nil, fmt.Errorf("tls_cert_file is required for https listener") + } + if server.TLSKeyFile == "" { + return nil, fmt.Errorf("tls_key_file is required for https listener") + } + } else { + if server.TLSCertFile != "" { + return nil, fmt.Errorf("tls_cert_file is only allowed for https listener") + } + if server.TLSKeyFile != "" { + return nil, fmt.Errorf("tls_key_file is only allowed for https listener") + } + } + + for _, p := range server.Proxies { + _, err := url.Parse(fmt.Sprintf("http://%s:%d", p.To.Host, p.To.Port)) + if err != nil { + return nil, fmt.Errorf("Invalid host or port: %s:%d", p.To.Host, p.To.Port) + } + } + } + if redirectToHTTPS && !listenHTTPS { + return nil, fmt.Errorf("redirect_to_https requires https listener") + } + + return fromHCLConfigToConfig(&hclConfig), nil +} |
