Update dependencies and enhance deployment scripts
- Bumped versions of several dependencies in go.mod, including golang.org/x/crypto to v0.43.0 and golang.org/x/net to v0.46.0. - Added new indirect dependencies for improved functionality. - Removed outdated files: package.json, POLICY_TESTS_SUCCESS.md, and POLICY_TESTS_SUMMARY.md. - Introduced a comprehensive deployment script for automated setup and configuration. - Added testing scripts for deployment validation and policy system tests. - Bumped version to v0.19.0.
This commit is contained in:
@@ -64,6 +64,10 @@ type C struct {
|
||||
SpiderMode string `env:"ORLY_SPIDER_MODE" default:"none" usage:"spider mode for syncing events: none, follows"`
|
||||
|
||||
PolicyEnabled bool `env:"ORLY_POLICY_ENABLED" default:"false" usage:"enable policy-based event processing (configuration found in $HOME/.config/ORLY/policy.json)"`
|
||||
|
||||
// TLS configuration
|
||||
TLSDomains []string `env:"ORLY_TLS_DOMAINS" usage:"comma-separated list of domains to respond to for TLS"`
|
||||
Certs []string `env:"ORLY_CERTS" usage:"comma-separated list of paths to certificate root names (e.g., /path/to/cert will load /path/to/cert.pem and /path/to/cert.key)"`
|
||||
}
|
||||
|
||||
// New creates and initializes a new configuration object for the relay
|
||||
@@ -200,9 +204,7 @@ func (kv KVSlice) Swap(i, j int) { kv[i], kv[j] = kv[j], kv[i] }
|
||||
// resulting slice remains sorted by keys as per the KVSlice implementation.
|
||||
func (kv KVSlice) Compose(kv2 KVSlice) (out KVSlice) {
|
||||
// duplicate the initial KVSlice
|
||||
for _, p := range kv {
|
||||
out = append(out, p)
|
||||
}
|
||||
out = append(out, kv...)
|
||||
out:
|
||||
for i, p := range kv2 {
|
||||
for j, q := range out {
|
||||
|
||||
107
app/main.go
107
app/main.go
@@ -4,9 +4,12 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"golang.org/x/crypto/acme/autocert"
|
||||
"lol.mleku.dev/chk"
|
||||
"lol.mleku.dev/log"
|
||||
"next.orly.dev/app/config"
|
||||
@@ -159,25 +162,86 @@ func Run(
|
||||
log.I.F("payment processor started successfully")
|
||||
}
|
||||
}
|
||||
addr := fmt.Sprintf("%s:%d", cfg.Listen, cfg.Port)
|
||||
log.I.F("starting listener on http://%s", addr)
|
||||
|
||||
// Create HTTP server for graceful shutdown
|
||||
srv := &http.Server{
|
||||
Addr: addr,
|
||||
Handler: l,
|
||||
// Check if TLS is enabled
|
||||
var tlsEnabled bool
|
||||
var tlsServer *http.Server
|
||||
var httpServer *http.Server
|
||||
|
||||
if len(cfg.TLSDomains) > 0 {
|
||||
// Validate TLS configuration
|
||||
if err = ValidateTLSConfig(cfg.TLSDomains, cfg.Certs); chk.E(err) {
|
||||
log.E.F("invalid TLS configuration: %v", err)
|
||||
} else {
|
||||
tlsEnabled = true
|
||||
log.I.F("TLS enabled for domains: %v", cfg.TLSDomains)
|
||||
|
||||
// Create cache directory for autocert
|
||||
cacheDir := filepath.Join(cfg.DataDir, "autocert")
|
||||
if err = os.MkdirAll(cacheDir, 0700); chk.E(err) {
|
||||
log.E.F("failed to create autocert cache directory: %v", err)
|
||||
tlsEnabled = false
|
||||
} else {
|
||||
// Set up autocert manager
|
||||
m := &autocert.Manager{
|
||||
Prompt: autocert.AcceptTOS,
|
||||
Cache: autocert.DirCache(cacheDir),
|
||||
HostPolicy: autocert.HostWhitelist(cfg.TLSDomains...),
|
||||
}
|
||||
|
||||
// Create TLS server on port 443
|
||||
tlsServer = &http.Server{
|
||||
Addr: ":443",
|
||||
Handler: l,
|
||||
TLSConfig: TLSConfig(m, cfg.Certs...),
|
||||
}
|
||||
|
||||
// Create HTTP server for ACME challenges and redirects on port 80
|
||||
httpServer = &http.Server{
|
||||
Addr: ":80",
|
||||
Handler: m.HTTPHandler(nil),
|
||||
}
|
||||
|
||||
// Start TLS server
|
||||
go func() {
|
||||
log.I.F("starting TLS listener on https://:443")
|
||||
if err := tlsServer.ListenAndServeTLS("", ""); err != nil && err != http.ErrServerClosed {
|
||||
log.E.F("TLS server error: %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
// Start HTTP server for ACME challenges
|
||||
go func() {
|
||||
log.I.F("starting HTTP listener on http://:80 for ACME challenges")
|
||||
if err := httpServer.ListenAndServe(); err != nil && err != http.ErrServerClosed {
|
||||
log.E.F("HTTP server error: %v", err)
|
||||
}
|
||||
}()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
go func() {
|
||||
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
|
||||
log.E.F("HTTP server error: %v", err)
|
||||
// Start regular HTTP server if TLS is not enabled or as fallback
|
||||
if !tlsEnabled {
|
||||
addr := fmt.Sprintf("%s:%d", cfg.Listen, cfg.Port)
|
||||
log.I.F("starting listener on http://%s", addr)
|
||||
|
||||
httpServer = &http.Server{
|
||||
Addr: addr,
|
||||
Handler: l,
|
||||
}
|
||||
}()
|
||||
|
||||
go func() {
|
||||
if err := httpServer.ListenAndServe(); err != nil && err != http.ErrServerClosed {
|
||||
log.E.F("HTTP server error: %v", err)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// Graceful shutdown handler
|
||||
go func() {
|
||||
<-ctx.Done()
|
||||
log.I.F("shutting down HTTP server gracefully")
|
||||
log.I.F("shutting down servers gracefully")
|
||||
|
||||
// Stop spider manager if running
|
||||
if l.spiderManager != nil {
|
||||
@@ -189,11 +253,22 @@ func Run(
|
||||
shutdownCtx, cancelShutdown := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
defer cancelShutdown()
|
||||
|
||||
// Shutdown the server gracefully
|
||||
if err := srv.Shutdown(shutdownCtx); err != nil {
|
||||
log.E.F("HTTP server shutdown error: %v", err)
|
||||
} else {
|
||||
log.I.F("HTTP server shutdown completed")
|
||||
// Shutdown TLS server if running
|
||||
if tlsServer != nil {
|
||||
if err := tlsServer.Shutdown(shutdownCtx); err != nil {
|
||||
log.E.F("TLS server shutdown error: %v", err)
|
||||
} else {
|
||||
log.I.F("TLS server shutdown completed")
|
||||
}
|
||||
}
|
||||
|
||||
// Shutdown HTTP server
|
||||
if httpServer != nil {
|
||||
if err := httpServer.Shutdown(shutdownCtx); err != nil {
|
||||
log.E.F("HTTP server shutdown error: %v", err)
|
||||
} else {
|
||||
log.I.F("HTTP server shutdown completed")
|
||||
}
|
||||
}
|
||||
|
||||
once.Do(func() { close(quit) })
|
||||
|
||||
132
app/tls.go
Normal file
132
app/tls.go
Normal file
@@ -0,0 +1,132 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"golang.org/x/crypto/acme/autocert"
|
||||
"lol.mleku.dev/chk"
|
||||
"lol.mleku.dev/log"
|
||||
)
|
||||
|
||||
// TLSConfig returns a TLS configuration that works with LetsEncrypt automatic SSL cert issuer
|
||||
// as well as any provided certificate files from providers.
|
||||
//
|
||||
// The certs are provided in the form of paths where .pem and .key files exist
|
||||
func TLSConfig(m *autocert.Manager, certs ...string) (tc *tls.Config) {
|
||||
certMap := make(map[string]*tls.Certificate)
|
||||
var mx sync.Mutex
|
||||
|
||||
for _, certPath := range certs {
|
||||
if certPath == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
var err error
|
||||
var c tls.Certificate
|
||||
|
||||
// Load certificate and key files
|
||||
if c, err = tls.LoadX509KeyPair(
|
||||
certPath+".pem", certPath+".key",
|
||||
); chk.E(err) {
|
||||
log.E.F("failed to load certificate from %s: %v", certPath, err)
|
||||
continue
|
||||
}
|
||||
|
||||
// Extract domain names from certificate
|
||||
if len(c.Certificate) > 0 {
|
||||
if x509Cert, err := x509.ParseCertificate(c.Certificate[0]); err == nil {
|
||||
// Use the common name as the primary domain
|
||||
if x509Cert.Subject.CommonName != "" {
|
||||
certMap[x509Cert.Subject.CommonName] = &c
|
||||
log.I.F("loaded certificate for domain: %s", x509Cert.Subject.CommonName)
|
||||
}
|
||||
// Also add any subject alternative names
|
||||
for _, san := range x509Cert.DNSNames {
|
||||
if san != "" {
|
||||
certMap[san] = &c
|
||||
log.I.F("loaded certificate for SAN domain: %s", san)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if m == nil {
|
||||
// Create a basic TLS config without autocert
|
||||
tc = &tls.Config{
|
||||
GetCertificate: func(helo *tls.ClientHelloInfo) (*tls.Certificate, error) {
|
||||
mx.Lock()
|
||||
defer mx.Unlock()
|
||||
|
||||
// Check for exact match first
|
||||
if cert, exists := certMap[helo.ServerName]; exists {
|
||||
return cert, nil
|
||||
}
|
||||
|
||||
// Check for wildcard matches
|
||||
for domain, cert := range certMap {
|
||||
if strings.HasPrefix(domain, "*.") {
|
||||
baseDomain := domain[2:] // Remove "*."
|
||||
if strings.HasSuffix(helo.ServerName, baseDomain) {
|
||||
return cert, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("no certificate found for %s", helo.ServerName)
|
||||
},
|
||||
}
|
||||
} else {
|
||||
tc = m.TLSConfig()
|
||||
tc.GetCertificate = func(helo *tls.ClientHelloInfo) (*tls.Certificate, error) {
|
||||
mx.Lock()
|
||||
|
||||
// Check for exact match first
|
||||
if cert, exists := certMap[helo.ServerName]; exists {
|
||||
mx.Unlock()
|
||||
return cert, nil
|
||||
}
|
||||
|
||||
// Check for wildcard matches
|
||||
for domain, cert := range certMap {
|
||||
if strings.HasPrefix(domain, "*.") {
|
||||
baseDomain := domain[2:] // Remove "*."
|
||||
if strings.HasSuffix(helo.ServerName, baseDomain) {
|
||||
mx.Unlock()
|
||||
return cert, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mx.Unlock()
|
||||
|
||||
// Fall back to autocert for domains not in our certificate map
|
||||
return m.GetCertificate(helo)
|
||||
}
|
||||
}
|
||||
|
||||
return tc
|
||||
}
|
||||
|
||||
// ValidateTLSConfig checks if the TLS configuration is valid
|
||||
func ValidateTLSConfig(domains []string, certs []string) (err error) {
|
||||
if len(domains) == 0 {
|
||||
return fmt.Errorf("no TLS domains specified")
|
||||
}
|
||||
|
||||
// Validate domain names
|
||||
for _, domain := range domains {
|
||||
if domain == "" {
|
||||
continue
|
||||
}
|
||||
if strings.Contains(domain, " ") || strings.Contains(domain, "\t") {
|
||||
return fmt.Errorf("invalid domain name: %s", domain)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user