From 2014d434d55bc62470eef1e339dbd8db195849eb Mon Sep 17 00:00:00 2001 From: Savely Krendelhoff Date: Sat, 23 Aug 2025 13:52:44 +0700 Subject: [PATCH 01/10] [PHASE-8] Update implementation plan --- docs/IMPLEMENTATION.md | 99 +++--------------------------------------- 1 file changed, 6 insertions(+), 93 deletions(-) diff --git a/docs/IMPLEMENTATION.md b/docs/IMPLEMENTATION.md index 978b028..9814e9c 100644 --- a/docs/IMPLEMENTATION.md +++ b/docs/IMPLEMENTATION.md @@ -102,90 +102,13 @@ - [X] Verify server successfully handles slow writer attacks - [X] Test end-to-end client-server communication flow -## Phase 8: Basic Server Architecture -- [ ] Set up metrics collection (prometheus) -- [ ] Create configuration management -- [ ] Integrate all components into server architecture +## Phase 8: Server Instrumentation & Configuration +- [ ] Add `/metrics` HTTP endpoint for Prometheus collection +- [ ] Add `/debug/pprof` endpoint for performance profiling +- [ ] Create Dockerfile to build server image +- [ ] Implement configuration management using cleanenv library +- [ ] Read configuration from file with environment variable support -## Phase 9: Advanced Server Features -- [ ] Add connection pooling and advanced connection management -- [ ] Implement graceful shutdown mechanism -- [ ] Add health check endpoints -- [ ] Add request/response logging middleware -- [ ] Create health check endpoints -- [ ] Write integration tests for server core - -## Phase 10: DDOS Protection & Rate Limiting -- [ ] Implement IP-based connection limiting -- [ ] Create rate limiting service with time windows -- [ ] Add automatic difficulty adjustment based on load -- [ ] Implement temporary IP blacklisting -- [ ] Create circuit breaker for overload protection -- [ ] Add monitoring for attack detection -- [ ] Write tests for protection mechanisms - -## Phase 11: Observability & Monitoring -- [ ] Add structured logging throughout application -- [ ] Implement metrics for key performance indicators: - - [ ] Active connections count - - [ ] Challenge generation rate - - [ ] Solution verification rate - - [ ] Success/failure ratios - - [ ] Response time histograms -- [ ] Create logging middleware for request tracing -- [ ] Add error categorization and reporting -- [ ] Implement health check endpoints - -## Phase 12: Configuration & Environment Setup -- [ ] Create configuration structure with validation -- [ ] Support environment variables and config files -- [ ] Add configuration for different environments (dev/prod) -- [ ] Implement feature flags for protection levels -- [ ] Create deployment configuration templates -- [ ] Add configuration validation and defaults - -## Phase 13: Docker & Deployment -- [ ] Create multi-stage Dockerfile for server -- [ ] Create Dockerfile for client -- [ ] Create docker-compose.yml for local development -- [ ] Add docker-compose for production deployment -- [ ] Create health check scripts for containers -- [ ] Add environment-specific configurations -- [ ] Create deployment documentation - -## Phase 14: Testing & Quality Assurance -- [ ] Write comprehensive unit tests (>80% coverage): - - [ ] PoW algorithm tests - - [ ] Protocol handler tests - - [ ] Rate limiting tests - - [ ] Quote service tests - - [ ] Configuration tests -- [ ] Create integration tests: - - [ ] End-to-end client-server communication - - [ ] Load testing scenarios - - [ ] Failure recovery tests - - [ ] DDOS protection validation -- [ ] Add benchmark tests for performance validation -- [ ] Create stress testing scenarios - -## Phase 15: Documentation & Final Polish -- [ ] Write comprehensive README with setup instructions -- [ ] Create API documentation for all interfaces -- [ ] Add inline code documentation -- [ ] Create deployment guide -- [ ] Write troubleshooting guide -- [ ] Add performance tuning recommendations -- [ ] Create monitoring and alerting guide - -## Phase 16: Production Readiness Checklist -- [ ] Security audit of all components -- [ ] Performance benchmarking and optimization -- [ ] Memory leak detection and prevention -- [ ] Resource cleanup validation -- [ ] Error handling coverage review -- [ ] Logging security (no sensitive data exposure) -- [ ] Configuration security (secrets management) -- [ ] Container security hardening ## Directory Structure ``` @@ -208,13 +131,3 @@ ├── deployments/ # Deployment configurations └── docs/ # Additional documentation ``` - -## Success Criteria -- [ ] Server handles 1000+ concurrent connections -- [ ] PoW protection prevents DDOS attacks effectively -- [ ] All tests pass with >80% code coverage -- [ ] Docker containers build and run successfully -- [ ] Client successfully solves challenges and receives quotes -- [ ] Comprehensive logging and metrics in place -- [ ] Production-ready error handling and recovery -- [ ] Clear documentation for deployment and operation -- 2.44.1 From 0ba0888d2b5d621be433432d3e6823822837c1d5 Mon Sep 17 00:00:00 2001 From: Savely Krendelhoff Date: Sat, 23 Aug 2025 16:01:09 +0700 Subject: [PATCH 02/10] [PHASE-8] Update dependencies --- go.mod | 17 ++++++++++++++--- go.sum | 50 ++++++++++++++++++++++++++++++++++++++++++-------- 2 files changed, 56 insertions(+), 11 deletions(-) diff --git a/go.mod b/go.mod index 77507a5..70ddb28 100644 --- a/go.mod +++ b/go.mod @@ -4,17 +4,28 @@ go 1.24.3 require ( github.com/go-resty/resty/v2 v2.16.5 + github.com/ilyakaznacheev/cleanenv v1.5.0 + github.com/prometheus/client_golang v1.23.0 github.com/stretchr/testify v1.10.0 ) require ( + github.com/BurntSushi/toml v1.2.1 // indirect + github.com/beorn7/perks v1.0.1 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect + github.com/joho/godotenv v1.5.1 // indirect github.com/kr/text v0.2.0 // indirect - github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect + github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect + github.com/prometheus/client_model v0.6.2 // indirect + github.com/prometheus/common v0.65.0 // indirect + github.com/prometheus/procfs v0.16.1 // indirect github.com/stretchr/objx v0.5.2 // indirect - golang.org/x/net v0.35.0 // indirect + golang.org/x/net v0.40.0 // indirect + golang.org/x/sys v0.33.0 // indirect golang.org/x/time v0.8.0 // indirect - gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect + google.golang.org/protobuf v1.36.6 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect + olympos.io/encoding/edn v0.0.0-20201019073823-d3554ca0b0a3 // indirect ) diff --git a/go.sum b/go.sum index 2677afe..36b2f00 100644 --- a/go.sum +++ b/go.sum @@ -1,26 +1,60 @@ +github.com/BurntSushi/toml v1.2.1 h1:9F2/+DoOYIOksmaJFPw1tGFy1eDnIJXg+UHjuD8lTak= +github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= +github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/go-resty/resty/v2 v2.16.5 h1:hBKqmWrr7uRc3euHVqmh1HTHcKn99Smr7o5spptdhTM= github.com/go-resty/resty/v2 v2.16.5/go.mod h1:hkJtXbA2iKHzJheXYvQ8snQES5ZLGKMwQ07xAwp/fiA= -github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/ilyakaznacheev/cleanenv v1.5.0 h1:0VNZXggJE2OYdXE87bfSSwGxeiGt9moSR2lOrsHHvr4= +github.com/ilyakaznacheev/cleanenv v1.5.0/go.mod h1:a5aDzaJrLCQZsazHol1w8InnDcOX0OColm64SlIi6gk= +github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= +github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= +github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= +github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= -github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= +github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_golang v1.23.0 h1:ust4zpdl9r4trLY/gSjlm07PuiBq2ynaXXlptpfy8Uc= +github.com/prometheus/client_golang v1.23.0/go.mod h1:i/o0R9ByOnHX0McrTMTyhYvKE4haaf2mW08I+jGAjEE= +github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= +github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= +github.com/prometheus/common v0.65.0 h1:QDwzd+G1twt//Kwj/Ww6E9FQq1iVMmODnILtW1t2VzE= +github.com/prometheus/common v0.65.0/go.mod h1:0gZns+BLRQ3V6NdaerOhMbwwRbNh9hkGINtQAsP5GS8= +github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg= +github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is= +github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= +github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8= -golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY= +golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds= +golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= +golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/time v0.8.0 h1:9i3RxcPv3PZnitoVGMPDKZSq1xW1gK1Xy3ArNOGZfEg= golang.org/x/time v0.8.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= +google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= -gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +olympos.io/encoding/edn v0.0.0-20201019073823-d3554ca0b0a3 h1:slmdOY3vp8a7KQbHkL+FLbvbkgMqmXojpFUO/jENuqQ= +olympos.io/encoding/edn v0.0.0-20201019073823-d3554ca0b0a3/go.mod h1:oVgVk4OWVDi43qWBEyGhXgYxt7+ED4iYNpTngSLX2Iw= -- 2.44.1 From 0bf84576a899b387968ec5e95ec79b62a32a358e Mon Sep 17 00:00:00 2001 From: Savely Krendelhoff Date: Sat, 23 Aug 2025 16:12:45 +0700 Subject: [PATCH 03/10] [PHASE-8] Add proper configuration --- config.yaml | 22 +++ internal/config/config.go | 72 +++++++++ internal/server/config.go | 12 -- internal/server/tcp.go | 12 +- test/integration/slowloris_test.go | 241 +++++++++++++++++------------ test/integration/timeout_test.go | 63 +++++--- 6 files changed, 276 insertions(+), 146 deletions(-) create mode 100644 config.yaml create mode 100644 internal/config/config.go diff --git a/config.yaml b/config.yaml new file mode 100644 index 0000000..d85e8e6 --- /dev/null +++ b/config.yaml @@ -0,0 +1,22 @@ +server: + address: ":8080" + timeouts: + read: 5s + write: 5s + connection: 15s + +pow: + difficulty: 25 + max_difficulty: 30 + ttl: 5m + hmac_secret: "development-secret-change-in-production" + +quotes: + timeout: 10s + +metrics: + address: ":8081" + +logging: + level: "info" + format: "text" diff --git a/internal/config/config.go b/internal/config/config.go new file mode 100644 index 0000000..2c86969 --- /dev/null +++ b/internal/config/config.go @@ -0,0 +1,72 @@ +package config + +import ( + "time" + + "github.com/ilyakaznacheev/cleanenv" +) + +type Config struct { + Server ServerConfig `yaml:"server"` + PoW PoWConfig `yaml:"pow"` + Quotes QuotesConfig `yaml:"quotes"` + Metrics MetricsConfig `yaml:"metrics"` + Logging LoggingConfig `yaml:"logging"` +} + +type ServerConfig struct { + Address string `yaml:"address" env:"SERVER_ADDRESS" env-default:":8080"` + Timeouts TimeoutConfig `yaml:"timeouts"` +} + +type TimeoutConfig struct { + Read time.Duration `yaml:"read" env:"SERVER_READ_TIMEOUT" env-default:"5s"` + Write time.Duration `yaml:"write" env:"SERVER_WRITE_TIMEOUT" env-default:"5s"` + Connection time.Duration `yaml:"connection" env:"SERVER_CONNECTION_TIMEOUT" env-default:"15s"` +} + +type PoWConfig struct { + Difficulty int `yaml:"difficulty" env:"POW_DIFFICULTY" env-default:"4"` + MaxDifficulty int `yaml:"max_difficulty" env:"POW_MAX_DIFFICULTY" env-default:"10"` + TTL time.Duration `yaml:"ttl" env:"POW_TTL" env-default:"5m"` + HMACSecret string `yaml:"hmac_secret" env:"POW_HMAC_SECRET" env-default:"development-secret-change-in-production"` +} + +type QuotesConfig struct { + Timeout time.Duration `yaml:"timeout" env:"QUOTES_TIMEOUT" env-default:"10s"` +} + +type MetricsConfig struct { + Address string `yaml:"address" env:"METRICS_ADDRESS" env-default:":8081"` +} + +type LoggingConfig struct { + Level string `yaml:"level" env:"LOG_LEVEL" env-default:"info"` + Format string `yaml:"format" env:"LOG_FORMAT" env-default:"text"` +} + +func Load(configPath string) (*Config, error) { + cfg := &Config{} + + if configPath != "" { + err := cleanenv.ReadConfig(configPath, cfg) + if err != nil { + return nil, err + } + } else { + err := cleanenv.ReadEnv(cfg) + if err != nil { + return nil, err + } + } + + return cfg, nil +} + +func (c *Config) ToServerConfig() *ServerConfig { + return &c.Server +} + +func (c *Config) ToPoWConfig() *PoWConfig { + return &c.PoW +} diff --git a/internal/server/config.go b/internal/server/config.go index 3047106..2ce56df 100644 --- a/internal/server/config.go +++ b/internal/server/config.go @@ -17,15 +17,3 @@ type TimeoutConfig struct { // Connection timeout is the maximum total connection lifetime Connection time.Duration } - -// DefaultConfig returns default server configuration -func DefaultConfig() *Config { - return &Config{ - Address: ":8080", - Timeouts: TimeoutConfig{ - Read: 5 * time.Second, - Write: 5 * time.Second, - Connection: 15 * time.Second, - }, - } -} diff --git a/internal/server/tcp.go b/internal/server/tcp.go index 6d1f4c8..33f550e 100644 --- a/internal/server/tcp.go +++ b/internal/server/tcp.go @@ -29,12 +29,6 @@ type TCPServer struct { // Option is a functional option for configuring TCPServer type option func(*TCPServer) -// WithConfig sets a custom configuration -func WithConfig(config *Config) option { - return func(s *TCPServer) { - s.config = config - } -} // WithLogger sets a custom logger func WithLogger(logger *slog.Logger) option { @@ -43,10 +37,10 @@ func WithLogger(logger *slog.Logger) option { } } -// NewTCPServer creates a new TCP server with optional configuration -func NewTCPServer(wisdomService *service.WisdomService, opts ...option) *TCPServer { +// NewTCPServer creates a new TCP server with required configuration +func NewTCPServer(wisdomService *service.WisdomService, config *Config, opts ...option) *TCPServer { server := &TCPServer{ - config: DefaultConfig(), + config: config, wisdomApplication: application.NewWisdomApplication(wisdomService), decoder: protocol.NewMessageDecoder(), logger: slog.Default(), diff --git a/test/integration/slowloris_test.go b/test/integration/slowloris_test.go index 7f756fa..4e6c7dd 100644 --- a/test/integration/slowloris_test.go +++ b/test/integration/slowloris_test.go @@ -14,13 +14,16 @@ import ( func TestSlowlorisProtection_SlowReader(t *testing.T) { // Setup server with very short read timeout for testing - config := server.DefaultConfig() - config.Address = ":0" - config.Timeouts.Read = 100 * time.Millisecond - config.Timeouts.Write = 5 * time.Second - config.Timeouts.Connection = 15 * time.Second + serverConfig := &server.Config{ + Address: ":0", + Timeouts: server.TimeoutConfig{ + Read: 100 * time.Millisecond, + Write: 5 * time.Second, + Connection: 15 * time.Second, + }, + } - srv := setupTestServerWithConfig(t, config) + srv := setupTestServerWithConfig(t, serverConfig) defer srv.Stop() // Connect to server @@ -47,13 +50,16 @@ func TestSlowlorisProtection_SlowReader(t *testing.T) { func TestSlowlorisProtection_SlowWriter(t *testing.T) { // Setup server with very short write timeout for testing - config := server.DefaultConfig() - config.Address = ":0" - config.Timeouts.Read = 5 * time.Second - config.Timeouts.Write = 100 * time.Millisecond - config.Timeouts.Connection = 15 * time.Second + serverConfig := &server.Config{ + Address: ":0", + Timeouts: server.TimeoutConfig{ + Read: 5 * time.Second, + Write: 100 * time.Millisecond, + Connection: 15 * time.Second, + }, + } - srv := setupTestServerWithConfig(t, config) + srv := setupTestServerWithConfig(t, serverConfig) defer srv.Stop() // Connect to server but don't read responses (simulate slow writer client) @@ -66,133 +72,164 @@ func TestSlowlorisProtection_SlowWriter(t *testing.T) { err = challengeReq.Encode(conn) require.NoError(t, err) - // Don't read the response to simulate slow writer - // Server should timeout when trying to write response + // Don't read the response - this should trigger write timeout time.Sleep(200 * time.Millisecond) - // Try to send another request - connection should be closed - err = challengeReq.Encode(conn) + // Try to write again - connection should be timed out + _, err = conn.Write([]byte{0x01}) + + // Verify connection is closed + buffer := make([]byte, 1024) + conn.SetReadDeadline(time.Now().Add(500 * time.Millisecond)) + _, err = conn.Read(buffer) assert.Error(t, err, "Connection should be closed due to slow writing") } -func TestSlowlorisProtection_ConnectionTimeout(t *testing.T) { - // Setup server with very short connection timeout - config := server.DefaultConfig() - config.Address = ":0" - config.Timeouts.Read = 5 * time.Second - config.Timeouts.Write = 5 * time.Second - config.Timeouts.Connection = 100 * time.Millisecond +func TestSlowlorisProtection_SlowConnectionTimeout(t *testing.T) { + // Setup server with very short connection timeout for testing + serverConfig := &server.Config{ + Address: ":0", + Timeouts: server.TimeoutConfig{ + Read: 5 * time.Second, + Write: 5 * time.Second, + Connection: 200 * time.Millisecond, + }, + } - srv := setupTestServerWithConfig(t, config) + srv := setupTestServerWithConfig(t, serverConfig) defer srv.Stop() - // Connect to server + // Connect to server but do nothing conn, err := net.Dial("tcp", srv.Address()) require.NoError(t, err) defer conn.Close() - // Wait longer than connection timeout without sending any data - time.Sleep(200 * time.Millisecond) + // Wait longer than connection timeout + time.Sleep(300 * time.Millisecond) - // Try to read from connection - should get EOF or connection reset + // Try to read - connection should be closed buffer := make([]byte, 1024) - conn.SetReadDeadline(time.Now().Add(100 * time.Millisecond)) + conn.SetReadDeadline(time.Now().Add(500 * time.Millisecond)) _, err = conn.Read(buffer) assert.Error(t, err, "Connection should be closed due to connection timeout") } -func TestSlowlorisProtection_MultipleSlowConnections(t *testing.T) { +func TestSlowlorisProtection_MultipleSlowClients(t *testing.T) { // Setup server with short timeouts - config := server.DefaultConfig() - config.Address = ":0" - config.Timeouts.Read = 50 * time.Millisecond - config.Timeouts.Write = 50 * time.Millisecond - config.Timeouts.Connection = 200 * time.Millisecond - - srv := setupTestServerWithConfig(t, config) - defer srv.Stop() - - // Create multiple slow connections (simulating slowloris attack) - var conns []net.Conn - for i := 0; i < 3; i++ { - conn, err := net.Dial("tcp", srv.Address()) - require.NoError(t, err) - conns = append(conns, conn) - - // Send partial data to trigger slow reader behavior - _, err = conn.Write([]byte{0x01}) // Just message type - require.NoError(t, err) + serverConfig := &server.Config{ + Address: ":0", + Timeouts: server.TimeoutConfig{ + Read: 1 * time.Second, + Write: 1 * time.Second, + Connection: 2 * time.Second, + }, } - // Clean up connections - defer func() { - for _, conn := range conns { - conn.Close() - } - }() + srv := setupTestServerWithConfig(t, serverConfig) + defer srv.Stop() - // Wait for read timeouts to kick in - time.Sleep(100 * time.Millisecond) + // Create multiple slow connections + var connections []net.Conn + for i := 0; i < 5; i++ { + conn, err := net.Dial("tcp", srv.Address()) + require.NoError(t, err) + connections = append(connections, conn) + // Send partial data on each connection + conn.Write([]byte{0x01}) // Challenge request type only + } - // Verify slow connections are closed by trying to read from them - for i, conn := range conns { - buffer := make([]byte, 1024) - conn.SetReadDeadline(time.Now().Add(50 * time.Millisecond)) + // Wait for timeouts to kick in + time.Sleep(1500 * time.Millisecond) + + // All connections should be closed + buffer := make([]byte, 1024) + for i, conn := range connections { + conn.SetReadDeadline(time.Now().Add(500 * time.Millisecond)) _, err := conn.Read(buffer) - assert.Error(t, err, "Slow connection %d should be closed", i) + assert.Error(t, err, "Connection %d should be closed", i) + conn.Close() } } -func TestSlowlorisProtection_NormalOperationWithinTimeouts(t *testing.T) { - // Setup server with reasonable timeouts - config := server.DefaultConfig() - config.Address = ":0" +func TestSlowlorisProtection_AttackMitigation(t *testing.T) { + // Test that server can still serve legitimate clients during an attack + serverConfig := &server.Config{ + Address: ":0", + Timeouts: server.TimeoutConfig{ + Read: 500 * time.Millisecond, + Write: 500 * time.Millisecond, + Connection: 1 * time.Second, + }, + } - srv := setupTestServerWithConfig(t, config) + srv := setupTestServerWithConfig(t, serverConfig) defer srv.Stop() - // Connect and complete normal flow quickly + // Start slowloris attack - multiple slow connections + var attackConnections []net.Conn + for i := 0; i < 3; i++ { + conn, err := net.Dial("tcp", srv.Address()) + require.NoError(t, err) + attackConnections = append(attackConnections, conn) + // Send partial data + conn.Write([]byte{0x01}) + } + + // Meanwhile, legitimate client should still work + legitimateConn, err := net.Dial("tcp", srv.Address()) + require.NoError(t, err) + defer legitimateConn.Close() + + // Send complete request quickly + challengeReq := &protocol.ChallengeRequest{} + err = challengeReq.Encode(legitimateConn) + require.NoError(t, err) + + // Should receive response despite ongoing attack + decoder := protocol.NewMessageDecoder() + msg, err := decoder.Decode(legitimateConn) + assert.NoError(t, err) + assert.Equal(t, protocol.ChallengeResponseType, msg.Type) + + // Clean up attack connections + for _, conn := range attackConnections { + conn.Close() + } +} + +func TestSlowlorisProtection_SlowChunkReading(t *testing.T) { + // Test protection against clients that read responses very slowly + serverConfig := &server.Config{ + Address: ":0", + Timeouts: server.TimeoutConfig{ + Read: 5 * time.Second, + Write: 200 * time.Millisecond, + Connection: 10 * time.Second, + }, + } + + srv := setupTestServerWithConfig(t, serverConfig) + defer srv.Stop() + + // Connect and send valid request conn, err := net.Dial("tcp", srv.Address()) require.NoError(t, err) defer conn.Close() - // Request challenge challengeReq := &protocol.ChallengeRequest{} err = challengeReq.Encode(conn) require.NoError(t, err) - // Should receive challenge response without timeout - decoder := protocol.NewMessageDecoder() - msg, err := decoder.Decode(conn) - require.NoError(t, err) - assert.Equal(t, protocol.ChallengeResponseType, msg.Type) - assert.Greater(t, msg.PayloadLength, uint32(0), "Challenge payload should not be empty") -} - -func TestSlowlorisProtection_PartialHeaderAttack(t *testing.T) { - // Setup server with short read timeout - config := server.DefaultConfig() - config.Address = ":0" - config.Timeouts.Read = 100 * time.Millisecond - - srv := setupTestServerWithConfig(t, config) - defer srv.Stop() - - // Connect to server - conn, err := net.Dial("tcp", srv.Address()) - require.NoError(t, err) - defer conn.Close() - - // Send only message type byte, then stall - _, err = conn.Write([]byte{0x01}) - require.NoError(t, err) - - // Wait for read timeout - time.Sleep(200 * time.Millisecond) - - // Try to read from connection - should be closed - buffer := make([]byte, 1024) - conn.SetReadDeadline(time.Now().Add(100 * time.Millisecond)) + // Read only first byte of response very slowly + buffer := make([]byte, 1) _, err = conn.Read(buffer) - assert.Error(t, err, "Connection should be closed due to partial header") + require.NoError(t, err) + + // Wait longer than write timeout before reading more + time.Sleep(300 * time.Millisecond) + + // Try to read more - connection should be closed due to slow reading + conn.SetReadDeadline(time.Now().Add(500 * time.Millisecond)) + _, err = conn.Read(buffer) + assert.Error(t, err, "Connection should be closed due to slow chunk reading") } diff --git a/test/integration/timeout_test.go b/test/integration/timeout_test.go index 18d3570..8c81a90 100644 --- a/test/integration/timeout_test.go +++ b/test/integration/timeout_test.go @@ -6,6 +6,7 @@ import ( "testing" "time" + "hash-of-wisdom/internal/config" "hash-of-wisdom/internal/lib/sl" "hash-of-wisdom/internal/pow/challenge" "hash-of-wisdom/internal/protocol" @@ -19,12 +20,15 @@ import ( func TestTCPServer_TimeoutProtection_SlowReader(t *testing.T) { // Setup server with very short read timeout for testing - config := server.DefaultConfig() - config.Address = ":0" - config.Timeouts.Read = 500 * time.Millisecond - config.Timeouts.Write = 5 * time.Second - config.Timeouts.Connection = 15 * time.Second - srv := setupTestServerWithConfig(t, config) + serverConfig := &server.Config{ + Address: ":0", + Timeouts: server.TimeoutConfig{ + Read: 500 * time.Millisecond, + Write: 5 * time.Second, + Connection: 15 * time.Second, + }, + } + srv := setupTestServerWithConfig(t, serverConfig) defer srv.Stop() // Connect to server @@ -51,12 +55,16 @@ func TestTCPServer_TimeoutProtection_SlowReader(t *testing.T) { func TestTCPServer_TimeoutProtection_ConnectionTimeout(t *testing.T) { // Setup server with very short connection timeout - config := server.DefaultConfig() - config.Address = ":0" - config.Timeouts.Read = 5 * time.Second - config.Timeouts.Write = 5 * time.Second - config.Timeouts.Connection = 1 * time.Second - srv := setupTestServerWithConfig(t, config) + _ = config.Load + serverConfig := &server.Config{ + Address: ":0", + Timeouts: server.TimeoutConfig{ + Read: 5 * time.Second, + Write: 5 * time.Second, + Connection: 1 * time.Second, + }, + } + srv := setupTestServerWithConfig(t, serverConfig) defer srv.Stop() // Connect to server @@ -97,12 +105,16 @@ func TestTCPServer_NormalOperation_WithinTimeouts(t *testing.T) { } func TestTCPServer_MultipleConnections_IndependentTimeouts(t *testing.T) { - config := server.DefaultConfig() - config.Address = ":0" - config.Timeouts.Read = 1 * time.Second - config.Timeouts.Write = 5 * time.Second - config.Timeouts.Connection = 3 * time.Second - srv := setupTestServerWithConfig(t, config) + _ = config.Load + serverConfig := &server.Config{ + Address: ":0", + Timeouts: server.TimeoutConfig{ + Read: 1 * time.Second, + Write: 5 * time.Second, + Connection: 3 * time.Second, + }, + } + srv := setupTestServerWithConfig(t, serverConfig) defer srv.Stop() // Start two connections @@ -143,9 +155,15 @@ func TestTCPServer_MultipleConnections_IndependentTimeouts(t *testing.T) { // Helper function to create test server with default config func setupTestServer(t *testing.T) *server.TCPServer { - config := server.DefaultConfig() - config.Address = ":0" - return setupTestServerWithConfig(t, config) + serverConfig := &server.Config{ + Address: ":0", + Timeouts: server.TimeoutConfig{ + Read: 5 * time.Second, + Write: 5 * time.Second, + Connection: 15 * time.Second, + }, + } + return setupTestServerWithConfig(t, serverConfig) } // Helper function to create test server with custom config @@ -164,8 +182,7 @@ func setupTestServerWithConfig(t *testing.T, serverConfig *server.Config) *serve // Create server with custom config using functional options logger := sl.NewMockLogger() - srv := server.NewTCPServer(wisdomService, - server.WithConfig(serverConfig), + srv := server.NewTCPServer(wisdomService, serverConfig, server.WithLogger(logger)) // Start server -- 2.44.1 From 451403f8d178e66081ba23271741405ad1829a6d Mon Sep 17 00:00:00 2001 From: Savely Krendelhoff Date: Sat, 23 Aug 2025 16:06:16 +0700 Subject: [PATCH 04/10] [PHASE-8] Add config load --- cmd/server/main.go | 30 +++++++++++++++++++++++------- 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/cmd/server/main.go b/cmd/server/main.go index 055e4af..2283447 100644 --- a/cmd/server/main.go +++ b/cmd/server/main.go @@ -2,32 +2,48 @@ package main import ( "context" + "flag" "log/slog" + "net/http" "os" "os/signal" "syscall" "time" + "hash-of-wisdom/internal/config" "hash-of-wisdom/internal/lib/sl" "hash-of-wisdom/internal/pow/challenge" "hash-of-wisdom/internal/quotes" "hash-of-wisdom/internal/server" "hash-of-wisdom/internal/service" + + "github.com/prometheus/client_golang/prometheus/promhttp" + _ "net/http/pprof" ) func main() { - addr := ":8080" - if len(os.Args) > 1 { - addr = os.Args[1] + configPath := flag.String("config", "", "path to configuration file") + flag.Parse() + + // Load configuration + cfg, err := config.Load(*configPath) + if err != nil { + slog.Error("failed to load config", sl.Err(err)) + os.Exit(1) } logger := slog.Default() - logger.Info("starting word of wisdom server", "address", addr) + logger.Info("starting word of wisdom server", "address", cfg.Server.Address) - // Create components - challengeConfig, err := challenge.NewConfig() + // Create components using config + challengeConfig, err := challenge.NewConfig( + challenge.WithDefaultDifficulty(cfg.PoW.Difficulty), + challenge.WithMaxDifficulty(cfg.PoW.MaxDifficulty), + challenge.WithChallengeTTL(cfg.PoW.TTL), + challenge.WithHMACSecret([]byte(cfg.PoW.HMACSecret)), + ) if err != nil { - logger.Error("failed to create config", sl.Err(err)) + logger.Error("failed to create challenge config", sl.Err(err)) os.Exit(1) } generator := challenge.NewGenerator(challengeConfig) -- 2.44.1 From 54b75835a42e1987cf3d30ff1101675356901bef Mon Sep 17 00:00:00 2001 From: Savely Krendelhoff Date: Sat, 23 Aug 2025 16:47:42 +0700 Subject: [PATCH 05/10] [PHASE-8] Expose metrics and pprof endpoints --- cmd/server/main.go | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/cmd/server/main.go b/cmd/server/main.go index 2283447..136970a 100644 --- a/cmd/server/main.go +++ b/cmd/server/main.go @@ -55,12 +55,26 @@ func main() { wisdomService := service.NewWisdomService(genAdapter, verifier, quoteService) // Create server configuration - serverConfig := server.DefaultConfig() - serverConfig.Address = addr + serverConfig := &server.Config{ + Address: cfg.Server.Address, + Timeouts: server.TimeoutConfig{ + Read: cfg.Server.Timeouts.Read, + Write: cfg.Server.Timeouts.Write, + Connection: cfg.Server.Timeouts.Connection, + }, + } + + // Start metrics and pprof HTTP server + go func() { + http.Handle("/metrics", promhttp.Handler()) + logger.Info("starting metrics server", "address", cfg.Metrics.Address) + if err := http.ListenAndServe(cfg.Metrics.Address, nil); err != nil { + logger.Error("metrics server failed", sl.Err(err)) + } + }() // Create server - srv := server.NewTCPServer(wisdomService, - server.WithConfig(serverConfig), + srv := server.NewTCPServer(wisdomService, serverConfig, server.WithLogger(logger)) // Start server -- 2.44.1 From f68b05553894bed0c9c9b565adfaf3f9a7cd17a7 Mon Sep 17 00:00:00 2001 From: Savely Krendelhoff Date: Sat, 23 Aug 2025 17:03:27 +0700 Subject: [PATCH 06/10] [PHASE-8] Define metrics --- internal/metrics/metrics.go | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 internal/metrics/metrics.go diff --git a/internal/metrics/metrics.go b/internal/metrics/metrics.go new file mode 100644 index 0000000..5432f46 --- /dev/null +++ b/internal/metrics/metrics.go @@ -0,0 +1,33 @@ +package metrics + +import ( + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/promauto" +) + +var ( + ActiveConnections = promauto.NewGauge(prometheus.GaugeOpts{ + Name: "wisdom_active_connections", + Help: "Number of currently active TCP connections", + }) + + RequestsTotal = promauto.NewCounter(prometheus.CounterOpts{ + Name: "wisdom_requests_total", + Help: "Total number of requests processed", + }) + + RequestErrors = promauto.NewCounterVec(prometheus.CounterOpts{ + Name: "wisdom_request_errors_total", + Help: "Total number of request errors by type", + }, []string{"error_type"}) + + RequestDuration = promauto.NewHistogram(prometheus.HistogramOpts{ + Name: "wisdom_request_duration_seconds", + Help: "Time taken to process requests", + }) + + QuotesServed = promauto.NewCounter(prometheus.CounterOpts{ + Name: "wisdom_quotes_served_total", + Help: "Total number of quotes successfully served to clients", + }) +) -- 2.44.1 From ad042dd9aa129f1952507d89da39250c4f4b981b Mon Sep 17 00:00:00 2001 From: Savely Krendelhoff Date: Sat, 23 Aug 2025 17:18:22 +0700 Subject: [PATCH 07/10] [PHASE-8] Implement proper graceful shutdown --- internal/server/tcp.go | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/internal/server/tcp.go b/internal/server/tcp.go index 33f550e..3c91df0 100644 --- a/internal/server/tcp.go +++ b/internal/server/tcp.go @@ -23,7 +23,7 @@ type TCPServer struct { listener net.Listener logger *slog.Logger wg sync.WaitGroup - shutdown chan struct{} + cancel context.CancelFunc } // Option is a functional option for configuring TCPServer @@ -44,7 +44,6 @@ func NewTCPServer(wisdomService *service.WisdomService, config *Config, opts ... wisdomApplication: application.NewWisdomApplication(wisdomService), decoder: protocol.NewMessageDecoder(), logger: slog.Default(), - shutdown: make(chan struct{}), } for _, opt := range opts { @@ -65,14 +64,22 @@ func (s *TCPServer) Start(ctx context.Context) error { s.listener = listener s.logger.Info("tcp server started", "address", s.config.Address) - go s.acceptLoop(ctx) + // Create cancellable context for server lifecycle + serverCtx, cancel := context.WithCancel(ctx) + s.cancel = cancel + + go s.acceptLoop(serverCtx) return nil } // Stop gracefully stops the server func (s *TCPServer) Stop() error { s.logger.Info("stopping tcp server") - close(s.shutdown) + + // Cancel server context to stop accept loop and active connections + if s.cancel != nil { + s.cancel() + } if s.listener != nil { s.listener.Close() @@ -95,8 +102,6 @@ func (s *TCPServer) Address() string { func (s *TCPServer) acceptLoop(ctx context.Context) { for { select { - case <-s.shutdown: - return case <-ctx.Done(): return default: @@ -105,7 +110,7 @@ func (s *TCPServer) acceptLoop(ctx context.Context) { rawConn, err := s.listener.Accept() if err != nil { select { - case <-s.shutdown: + case <-ctx.Done(): return default: s.logger.Error("accept error", sl.Err(err)) -- 2.44.1 From d780b4f2ea8ead01ce4c78d32b1709f8ac06ec1d Mon Sep 17 00:00:00 2001 From: Savely Krendelhoff Date: Sat, 23 Aug 2025 17:25:04 +0700 Subject: [PATCH 08/10] [PHASE-8] Gather metrics in tcp server --- internal/server/tcp.go | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/internal/server/tcp.go b/internal/server/tcp.go index 3c91df0..3d3008a 100644 --- a/internal/server/tcp.go +++ b/internal/server/tcp.go @@ -11,6 +11,7 @@ import ( "hash-of-wisdom/internal/application" "hash-of-wisdom/internal/lib/sl" + "hash-of-wisdom/internal/metrics" "hash-of-wisdom/internal/protocol" "hash-of-wisdom/internal/service" ) @@ -130,6 +131,10 @@ func (s *TCPServer) acceptLoop(ctx context.Context) { func (s *TCPServer) handleConnection(ctx context.Context, rawConn net.Conn) { defer rawConn.Close() + // Track active connections + metrics.ActiveConnections.Inc() + defer metrics.ActiveConnections.Dec() + connLogger := s.logger.With("remote_addr", rawConn.RemoteAddr().String()) connLogger.Info("connection accepted") @@ -187,19 +192,38 @@ func (s *TCPServer) processConnection(ctx context.Context, conn net.Conn, logger logger.Debug("client closed connection gracefully") return nil } + metrics.RequestErrors.WithLabelValues("decode_error").Inc() logger.Error("failed to decode message", sl.Err(err)) return fmt.Errorf("decode error: %w", err) } logger.Debug("message decoded", "type", msg.Type, "payload_length", msg.PayloadLength) - // Process message through application layer + // Track all requests + metrics.RequestsTotal.Inc() + + // Process message through application layer with timing + start := time.Now() response, err := s.wisdomApplication.HandleMessage(ctx, msg) + duration := time.Since(start) + metrics.RequestDuration.Observe(duration.Seconds()) + if err != nil { + metrics.RequestErrors.WithLabelValues("internal_error").Inc() logger.Error("application error", sl.Err(err)) return fmt.Errorf("application error: %w", err) } + // Check if response is an error response + if errorResp, isError := response.(*protocol.ErrorResponse); isError { + metrics.RequestErrors.WithLabelValues(string(errorResp.Code)).Inc() + } else { + // Track quotes served for successful solution requests + if msg.Type == protocol.SolutionRequestType { + metrics.QuotesServed.Inc() + } + } + logger.Debug("sending response to client") // Send response using the response's own Encode method if err := response.Encode(dc); err != nil { -- 2.44.1 From 7c2560422d0e6dd4a0396eda45a8615808d46501 Mon Sep 17 00:00:00 2001 From: Savely Krendelhoff Date: Sat, 23 Aug 2025 17:32:35 +0700 Subject: [PATCH 09/10] [PHASE-8] Introduce Dockerfile --- Dockerfile | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 Dockerfile diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..ce80c73 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,36 @@ +# Build stage +FROM golang:1.24-alpine AS builder + +WORKDIR /app + +# Copy go mod files +COPY go.mod go.sum ./ +RUN go mod download + +# Copy source code +COPY . . + +# Build server +RUN CGO_ENABLED=0 go build -o hash-of-wisdom ./cmd/server + +# Runtime stage +FROM alpine:3.19 + +# Create non-root user +RUN addgroup -g 1001 -S hash-of-wisdom && \ + adduser -u 1001 -S hash-of-wisdom -G hash-of-wisdom + +WORKDIR /app + +# Copy binary and config from builder stage with correct ownership +COPY --from=builder --chown=hash-of-wisdom:hash-of-wisdom /app/hash-of-wisdom . +COPY --from=builder --chown=hash-of-wisdom:hash-of-wisdom /app/config.yaml . + +# Switch to non-root user +USER hash-of-wisdom + +# Expose ports +EXPOSE 8080 8081 + +# Run server with config file +CMD ["./hash-of-wisdom", "-config", "config.yaml"] -- 2.44.1 From 64513deaf826d368c1f7f81d5bf277bf8cc5d5eb Mon Sep 17 00:00:00 2001 From: Savely Krendelhoff Date: Sat, 23 Aug 2025 17:52:44 +0700 Subject: [PATCH 10/10] [PHASE-8] Update implementation plan --- docs/IMPLEMENTATION.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/IMPLEMENTATION.md b/docs/IMPLEMENTATION.md index 9814e9c..c720499 100644 --- a/docs/IMPLEMENTATION.md +++ b/docs/IMPLEMENTATION.md @@ -103,11 +103,11 @@ - [X] Test end-to-end client-server communication flow ## Phase 8: Server Instrumentation & Configuration -- [ ] Add `/metrics` HTTP endpoint for Prometheus collection -- [ ] Add `/debug/pprof` endpoint for performance profiling -- [ ] Create Dockerfile to build server image -- [ ] Implement configuration management using cleanenv library -- [ ] Read configuration from file with environment variable support +- [X] Add `/metrics` HTTP endpoint for Prometheus collection +- [X] Add `/debug/pprof` endpoint for performance profiling +- [X] Create Dockerfile to build server image +- [X] Implement configuration management using cleanenv library +- [X] Read configuration from file with environment variable support ## Directory Structure -- 2.44.1