From 053bc5371f7f817f391a4fb4d2e2f395f76fe852 Mon Sep 17 00:00:00 2001 From: Sergi Castro Date: Fri, 16 Feb 2024 19:03:06 +0100 Subject: [PATCH 1/2] Add basic health server --- cmd/main.go | 2 + config/gen/go/v1/config.pb.go | 107 ++++++++++++------ config/gen/go/v1/config.pb.validate.go | 15 +++ config/v1/config.proto | 13 +++ internal/config.go | 5 + internal/config_test.go | 1 + internal/logging.go | 2 + internal/server/health.go | 120 +++++++++++++++++++++ internal/server/health_test.go | 104 ++++++++++++++++++ internal/testdata/invalid-health-port.json | 4 + 10 files changed, 340 insertions(+), 33 deletions(-) create mode 100644 internal/server/health.go create mode 100644 internal/server/health_test.go create mode 100644 internal/testdata/invalid-health-port.json diff --git a/cmd/main.go b/cmd/main.go index c1b05b5..55452cf 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -36,6 +36,7 @@ func main() { sessions = oidc.NewSessionStoreFactory(&configFile.Config) envoyAuthz = server.NewExtAuthZFilter(&configFile.Config, jwks, sessions) authzServer = server.New(&configFile.Config, envoyAuthz.Register) + healthz = server.NewHealthServer(&configFile.Config) ) configLog := run.NewPreRunner("config-log", func() error { @@ -55,6 +56,7 @@ func main() { jwks, // start the JWKS provider sessions, // start the session store authzServer, // start the server + healthz, // start the health server &signal.Handler{}, // handle graceful termination ) diff --git a/config/gen/go/v1/config.pb.go b/config/gen/go/v1/config.pb.go index 3832557..2417fee 100644 --- a/config/gen/go/v1/config.pb.go +++ b/config/gen/go/v1/config.pb.go @@ -368,6 +368,16 @@ type Config struct { // If true will allow the the requests even no filter chain match is found. Default false. // Optional. AllowUnmatchedRequests bool `protobuf:"varint,11,opt,name=allow_unmatched_requests,json=allowUnmatchedRequests,proto3" json:"allow_unmatched_requests,omitempty"` + // The Authservice provides an HTTP server to check the health state. + // This configures the address for the health server to listen for. + // Optional. Defaults to the value of `listen_address`. + HealthListenAddress string `protobuf:"bytes,12,opt,name=health_listen_address,json=healthListenAddress,proto3" json:"health_listen_address,omitempty"` + // The TCP port for the health server to listen for. + // Optional. Defaults 10004. + HealthListenPort int32 `protobuf:"varint,13,opt,name=health_listen_port,json=healthListenPort,proto3" json:"health_listen_port,omitempty"` + // The path for the health server to attend. + // Optional. Defaults to "/healthz". + HealthListenPath string `protobuf:"bytes,14,opt,name=health_listen_path,json=healthListenPath,proto3" json:"health_listen_path,omitempty"` } func (x *Config) Reset() { @@ -458,6 +468,27 @@ func (x *Config) GetAllowUnmatchedRequests() bool { return false } +func (x *Config) GetHealthListenAddress() string { + if x != nil { + return x.HealthListenAddress + } + return "" +} + +func (x *Config) GetHealthListenPort() int32 { + if x != nil { + return x.HealthListenPort + } + return 0 +} + +func (x *Config) GetHealthListenPath() string { + if x != nil { + return x.HealthListenPath + } + return "" +} + // Trigger rule to match against a request. The trigger rule is satisfied if // and only if both rules, excluded_paths and include_paths are satisfied. type TriggerRule struct { @@ -679,7 +710,7 @@ var file_v1_config_proto_rawDesc = []byte{ 0x72, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x76, 0x31, 0x2e, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x42, 0x08, 0xfa, 0x42, 0x05, 0x92, 0x01, 0x02, 0x08, - 0x01, 0x52, 0x07, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x73, 0x22, 0xf1, 0x03, 0x0a, 0x06, 0x43, + 0x01, 0x52, 0x07, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x73, 0x22, 0x8c, 0x05, 0x0a, 0x06, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x44, 0x0a, 0x06, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x76, 0x31, 0x2e, 0x46, 0x69, @@ -710,40 +741,50 @@ var file_v1_config_proto_rawDesc = []byte{ 0x6e, 0x66, 0x69, 0x67, 0x12, 0x38, 0x0a, 0x18, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x5f, 0x75, 0x6e, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x65, 0x64, 0x5f, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x73, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x08, 0x52, 0x16, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x55, 0x6e, 0x6d, - 0x61, 0x74, 0x63, 0x68, 0x65, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x73, 0x22, 0xa3, - 0x01, 0x0a, 0x0b, 0x54, 0x72, 0x69, 0x67, 0x67, 0x65, 0x72, 0x52, 0x75, 0x6c, 0x65, 0x12, 0x49, - 0x0a, 0x0e, 0x65, 0x78, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x64, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x73, - 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x73, 0x65, 0x72, - 0x76, 0x69, 0x63, 0x65, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x76, 0x31, 0x2e, 0x53, - 0x74, 0x72, 0x69, 0x6e, 0x67, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x52, 0x0d, 0x65, 0x78, 0x63, 0x6c, - 0x75, 0x64, 0x65, 0x64, 0x50, 0x61, 0x74, 0x68, 0x73, 0x12, 0x49, 0x0a, 0x0e, 0x69, 0x6e, 0x63, - 0x6c, 0x75, 0x64, 0x65, 0x64, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, + 0x61, 0x74, 0x63, 0x68, 0x65, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x73, 0x12, 0x32, + 0x0a, 0x15, 0x68, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x5f, 0x6c, 0x69, 0x73, 0x74, 0x65, 0x6e, 0x5f, + 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x09, 0x52, 0x13, 0x68, + 0x65, 0x61, 0x6c, 0x74, 0x68, 0x4c, 0x69, 0x73, 0x74, 0x65, 0x6e, 0x41, 0x64, 0x64, 0x72, 0x65, + 0x73, 0x73, 0x12, 0x37, 0x0a, 0x12, 0x68, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x5f, 0x6c, 0x69, 0x73, + 0x74, 0x65, 0x6e, 0x5f, 0x70, 0x6f, 0x72, 0x74, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x05, 0x42, 0x09, + 0xfa, 0x42, 0x06, 0x1a, 0x04, 0x10, 0x80, 0x80, 0x04, 0x52, 0x10, 0x68, 0x65, 0x61, 0x6c, 0x74, + 0x68, 0x4c, 0x69, 0x73, 0x74, 0x65, 0x6e, 0x50, 0x6f, 0x72, 0x74, 0x12, 0x2c, 0x0a, 0x12, 0x68, + 0x65, 0x61, 0x6c, 0x74, 0x68, 0x5f, 0x6c, 0x69, 0x73, 0x74, 0x65, 0x6e, 0x5f, 0x70, 0x61, 0x74, + 0x68, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x68, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x4c, + 0x69, 0x73, 0x74, 0x65, 0x6e, 0x50, 0x61, 0x74, 0x68, 0x22, 0xa3, 0x01, 0x0a, 0x0b, 0x54, 0x72, + 0x69, 0x67, 0x67, 0x65, 0x72, 0x52, 0x75, 0x6c, 0x65, 0x12, 0x49, 0x0a, 0x0e, 0x65, 0x78, 0x63, + 0x6c, 0x75, 0x64, 0x65, 0x64, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, - 0x4d, 0x61, 0x74, 0x63, 0x68, 0x52, 0x0d, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x64, 0x50, - 0x61, 0x74, 0x68, 0x73, 0x22, 0x7f, 0x0a, 0x0b, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x4d, 0x61, - 0x74, 0x63, 0x68, 0x12, 0x16, 0x0a, 0x05, 0x65, 0x78, 0x61, 0x63, 0x74, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x09, 0x48, 0x00, 0x52, 0x05, 0x65, 0x78, 0x61, 0x63, 0x74, 0x12, 0x18, 0x0a, 0x06, 0x70, - 0x72, 0x65, 0x66, 0x69, 0x78, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x06, 0x70, - 0x72, 0x65, 0x66, 0x69, 0x78, 0x12, 0x18, 0x0a, 0x06, 0x73, 0x75, 0x66, 0x66, 0x69, 0x78, 0x18, - 0x03, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x06, 0x73, 0x75, 0x66, 0x66, 0x69, 0x78, 0x12, - 0x16, 0x0a, 0x05, 0x72, 0x65, 0x67, 0x65, 0x78, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, - 0x52, 0x05, 0x72, 0x65, 0x67, 0x65, 0x78, 0x42, 0x0c, 0x0a, 0x0a, 0x6d, 0x61, 0x74, 0x63, 0x68, - 0x5f, 0x74, 0x79, 0x70, 0x65, 0x42, 0xdd, 0x01, 0x0a, 0x19, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x75, - 0x74, 0x68, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, - 0x2e, 0x76, 0x31, 0x42, 0x0b, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x50, 0x72, 0x6f, 0x74, 0x6f, - 0x50, 0x01, 0x5a, 0x3d, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x74, - 0x65, 0x74, 0x72, 0x61, 0x74, 0x65, 0x69, 0x6f, 0x2f, 0x61, 0x75, 0x74, 0x68, 0x73, 0x65, 0x72, - 0x76, 0x69, 0x63, 0x65, 0x2d, 0x67, 0x6f, 0x2f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2f, 0x67, - 0x65, 0x6e, 0x2f, 0x67, 0x6f, 0x2f, 0x76, 0x31, 0x3b, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x76, - 0x31, 0xa2, 0x02, 0x03, 0x41, 0x43, 0x58, 0xaa, 0x02, 0x15, 0x41, 0x75, 0x74, 0x68, 0x73, 0x65, - 0x72, 0x76, 0x69, 0x63, 0x65, 0x2e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x56, 0x31, 0xca, - 0x02, 0x15, 0x41, 0x75, 0x74, 0x68, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x5c, 0x43, 0x6f, - 0x6e, 0x66, 0x69, 0x67, 0x5c, 0x56, 0x31, 0xe2, 0x02, 0x21, 0x41, 0x75, 0x74, 0x68, 0x73, 0x65, - 0x72, 0x76, 0x69, 0x63, 0x65, 0x5c, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x5c, 0x56, 0x31, 0x5c, - 0x47, 0x50, 0x42, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0xea, 0x02, 0x17, 0x41, 0x75, - 0x74, 0x68, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x3a, 0x3a, 0x43, 0x6f, 0x6e, 0x66, 0x69, - 0x67, 0x3a, 0x3a, 0x56, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x4d, 0x61, 0x74, 0x63, 0x68, 0x52, 0x0d, 0x65, 0x78, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x64, 0x50, + 0x61, 0x74, 0x68, 0x73, 0x12, 0x49, 0x0a, 0x0e, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x64, + 0x5f, 0x70, 0x61, 0x74, 0x68, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x61, + 0x75, 0x74, 0x68, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, + 0x67, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x4d, 0x61, 0x74, 0x63, 0x68, + 0x52, 0x0d, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x64, 0x50, 0x61, 0x74, 0x68, 0x73, 0x22, + 0x7f, 0x0a, 0x0b, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x12, 0x16, + 0x0a, 0x05, 0x65, 0x78, 0x61, 0x63, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, + 0x05, 0x65, 0x78, 0x61, 0x63, 0x74, 0x12, 0x18, 0x0a, 0x06, 0x70, 0x72, 0x65, 0x66, 0x69, 0x78, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x06, 0x70, 0x72, 0x65, 0x66, 0x69, 0x78, + 0x12, 0x18, 0x0a, 0x06, 0x73, 0x75, 0x66, 0x66, 0x69, 0x78, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, + 0x48, 0x00, 0x52, 0x06, 0x73, 0x75, 0x66, 0x66, 0x69, 0x78, 0x12, 0x16, 0x0a, 0x05, 0x72, 0x65, + 0x67, 0x65, 0x78, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x05, 0x72, 0x65, 0x67, + 0x65, 0x78, 0x42, 0x0c, 0x0a, 0x0a, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x5f, 0x74, 0x79, 0x70, 0x65, + 0x42, 0xdd, 0x01, 0x0a, 0x19, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x73, 0x65, 0x72, + 0x76, 0x69, 0x63, 0x65, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x76, 0x31, 0x42, 0x0b, + 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x3d, 0x67, + 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x74, 0x65, 0x74, 0x72, 0x61, 0x74, + 0x65, 0x69, 0x6f, 0x2f, 0x61, 0x75, 0x74, 0x68, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2d, + 0x67, 0x6f, 0x2f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2f, 0x67, 0x65, 0x6e, 0x2f, 0x67, 0x6f, + 0x2f, 0x76, 0x31, 0x3b, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x76, 0x31, 0xa2, 0x02, 0x03, 0x41, + 0x43, 0x58, 0xaa, 0x02, 0x15, 0x41, 0x75, 0x74, 0x68, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, + 0x2e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x56, 0x31, 0xca, 0x02, 0x15, 0x41, 0x75, 0x74, + 0x68, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x5c, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x5c, + 0x56, 0x31, 0xe2, 0x02, 0x21, 0x41, 0x75, 0x74, 0x68, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, + 0x5c, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x5c, 0x56, 0x31, 0x5c, 0x47, 0x50, 0x42, 0x4d, 0x65, + 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0xea, 0x02, 0x17, 0x41, 0x75, 0x74, 0x68, 0x73, 0x65, 0x72, + 0x76, 0x69, 0x63, 0x65, 0x3a, 0x3a, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x3a, 0x3a, 0x56, 0x31, + 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( diff --git a/config/gen/go/v1/config.pb.validate.go b/config/gen/go/v1/config.pb.validate.go index c357db6..f068ac2 100644 --- a/config/gen/go/v1/config.pb.validate.go +++ b/config/gen/go/v1/config.pb.validate.go @@ -807,6 +807,21 @@ func (m *Config) validate(all bool) error { // no validation rules for AllowUnmatchedRequests + // no validation rules for HealthListenAddress + + if m.GetHealthListenPort() >= 65536 { + err := ConfigValidationError{ + field: "HealthListenPort", + reason: "value must be less than 65536", + } + if !all { + return err + } + errors = append(errors, err) + } + + // no validation rules for HealthListenPath + if len(errors) > 0 { return ConfigMultiError(errors) } diff --git a/config/v1/config.proto b/config/v1/config.proto index fd45b62..1fda6c8 100644 --- a/config/v1/config.proto +++ b/config/v1/config.proto @@ -140,6 +140,19 @@ message Config { // If true will allow the the requests even no filter chain match is found. Default false. // Optional. bool allow_unmatched_requests = 11; + + // The Authservice provides an HTTP server to check the health state. + // This configures the address for the health server to listen for. + // Optional. Defaults to the value of `listen_address`. + string health_listen_address = 12; + + // The TCP port for the health server to listen for. + // Optional. Defaults 10004. + int32 health_listen_port = 13 [(validate.rules).int32.lt = 65536]; + + // The path for the health server to attend. + // Optional. Defaults to "/healthz". + string health_listen_path = 14; } // Trigger rule to match against a request. The trigger rule is satisfied if diff --git a/internal/config.go b/internal/config.go index 648e7fd..9fbfda6 100644 --- a/internal/config.go +++ b/internal/config.go @@ -38,6 +38,7 @@ var ( ErrMultipleOIDCConfig = errors.New("multiple OIDC configurations") ErrInvalidURL = errors.New("invalid URL") ErrRequiredURL = errors.New("required URL") + ErrHealthPortInUse = errors.New("health port is already in use by listen port") ) // LocalConfigFile is a run.Config that loads the configuration file. @@ -71,6 +72,10 @@ func (l *LocalConfigFile) Validate() error { return err } + if l.Config.GetListenPort() == l.Config.GetHealthListenPort() { + return ErrHealthPortInUse + } + // Validate the URLs before merging the OIDC configurations if err = validateURLs(&l.Config); err != nil { return err diff --git a/internal/config_test.go b/internal/config_test.go index 77a375f..935b876 100644 --- a/internal/config_test.go +++ b/internal/config_test.go @@ -61,6 +61,7 @@ func TestValidateConfig(t *testing.T) { {"multiple-oidc", "testdata/multiple-oidc.json", errCheck{is: ErrMultipleOIDCConfig}}, {"invalid-redis", "testdata/invalid-redis.json", errCheck{is: ErrInvalidURL}}, {"invalid-oidc-uris", "testdata/invalid-oidc-uris.json", errCheck{is: ErrRequiredURL}}, + {"invalid-health-port", "testdata/invalid-health-port.json", errCheck{is: ErrHealthPortInUse}}, {"oidc-dynamic", "testdata/oidc-dynamic.json", errCheck{is: nil}}, {"valid", "testdata/mock.json", errCheck{is: nil}}, } diff --git a/internal/logging.go b/internal/logging.go index 186c31f..e36ef19 100644 --- a/internal/logging.go +++ b/internal/logging.go @@ -30,6 +30,7 @@ const ( Authz = "authz" Config = "config" Default = "default" + Health = "health" JWKS = "jwks" Requests = "requests" Server = "server" @@ -41,6 +42,7 @@ var scopes = map[string]string{ Authz: "Envoy ext-authz filter implementation messages", Config: "Configuration messages", Default: "Default", + Health: "Health server messages", JWKS: "JWKS update and parse messages", Requests: "Logs all requests and responses received by the server", Server: "Server request handling messages", diff --git a/internal/server/health.go b/internal/server/health.go new file mode 100644 index 0000000..1cd436d --- /dev/null +++ b/internal/server/health.go @@ -0,0 +1,120 @@ +// Copyright 2024 Tetrate +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package server + +import ( + "fmt" + "net" + "net/http" + + "github.com/tetratelabs/run" + "github.com/tetratelabs/telemetry" + + configv1 "github.com/tetrateio/authservice-go/config/gen/go/v1" + "github.com/tetrateio/authservice-go/internal" +) + +const ( + HealthzPath = "/healthz" + HealthzPort = 10004 +) + +var ( + _ http.Handler = (*healthServer)(nil) + _ run.Service = (*healthServer)(nil) +) + +type healthServer struct { + log telemetry.Logger + config *configv1.Config + server *http.Server + + // Listen allows overriding the default listener. It is meant to + // be used in tests. + l net.Listener +} + +// NewHealthServer creates a new health server. +func NewHealthServer(config *configv1.Config) run.Unit { + hs := &healthServer{ + log: internal.Logger(internal.Health), + config: config, + } + httpServer := &http.Server{Handler: hs} + hs.server = httpServer + return hs +} + +// Name implements run.Unit. +func (hs *healthServer) Name() string { + return "Health Server" +} + +// Serve implements run.Service. +func (hs *healthServer) Serve() error { + // use test listener if set + if hs.l == nil { + var err error + hs.l, err = net.Listen("tcp", hs.getAddressAndPort()) + if err != nil { + return err + } + } + + hs.log.Info("starting health server", "addr", hs.l.Addr(), "path", hs.getPath()) + return hs.server.Serve(hs.l) +} + +// GracefulStop implements run.Service. +func (hs *healthServer) GracefulStop() { + hs.log.Info("stopping health server") + _ = hs.server.Close() +} + +func (hs *healthServer) getAddressAndPort() string { + addr := hs.config.GetHealthListenAddress() + if addr == "" { + addr = hs.config.GetListenAddress() + } + + port := hs.config.GetHealthListenPort() + if port == 0 { + port = HealthzPort + } + + return fmt.Sprintf("%s:%d", addr, port) +} + +func (hs *healthServer) getPath() string { + path := hs.config.GetHealthListenPath() + if path != "" { + return path + } + return HealthzPath +} + +// ServeHTTP implements http.Handler. +func (hs *healthServer) ServeHTTP(w http.ResponseWriter, r *http.Request) { + log := hs.log.With("method", r.Method, "path", r.URL.Path) + listenPath := hs.getPath() + + if r.Method != http.MethodGet || r.URL.Path != listenPath { + log.Debug("invalid request") + http.Error(w, fmt.Sprintf("only GET %s is allowed", listenPath), http.StatusBadRequest) + return + } + + w.WriteHeader(http.StatusOK) +} diff --git a/internal/server/health_test.go b/internal/server/health_test.go new file mode 100644 index 0000000..036684a --- /dev/null +++ b/internal/server/health_test.go @@ -0,0 +1,104 @@ +// Copyright 2024 Tetrate +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package server + +import ( + "context" + "net" + "net/http" + "testing" + + "github.com/stretchr/testify/require" + "github.com/tetratelabs/run" + "github.com/tetratelabs/run/pkg/test" + "github.com/tetratelabs/telemetry" + "google.golang.org/grpc/test/bufconn" + + configv1 "github.com/tetrateio/authservice-go/config/gen/go/v1" +) + +func TestHealthServer(t *testing.T) { + + var ( + g = run.Group{Logger: telemetry.NoopLogger()} + irq = test.NewIRQService(func() {}) + l = bufconn.Listen(1024) + hs = NewHealthServer(nil) + ) + + hs.(*healthServer).l = l + g.Register(hs, irq) + + go func() { + require.NoError(t, g.Run()) + }() + t.Cleanup(func() { + require.NoError(t, irq.Close()) + }) + + client := http.Client{ + Transport: &http.Transport{ + DialContext: func(_ context.Context, _, _ string) (net.Conn, error) { + return l.Dial() + }, + }, + } + + tests := []struct { + method string + url string + want int + }{ + {http.MethodGet, "http://bufconn" + HealthzPath, http.StatusOK}, + {http.MethodGet, "http://bufconn" + "/other", http.StatusBadRequest}, + {http.MethodPost, "http://bufconn" + HealthzPath, http.StatusBadRequest}, + {http.MethodPost, "http://bufconn" + "/other", http.StatusBadRequest}, + } + + for _, tt := range tests { + t.Run(tt.method+" "+tt.url, func(t *testing.T) { + req, err := http.NewRequest(tt.method, tt.url, nil) + require.NoError(t, err) + resp, err := client.Do(req) + require.NoError(t, err) + require.Equal(t, tt.want, resp.StatusCode) + }) + } +} + +func TestHealthConfig(t *testing.T) { + + tests := []struct { + name string + config *configv1.Config + wantAddress, wantPath string + }{ + {"default", nil, ":10004", "/healthz"}, + {"address", &configv1.Config{HealthListenAddress: "test"}, "test:10004", "/healthz"}, + {"port", &configv1.Config{HealthListenPort: 8000}, ":8000", "/healthz"}, + {"address and port", &configv1.Config{HealthListenAddress: "test", HealthListenPort: 8000}, "test:8000", "/healthz"}, + {"path", &configv1.Config{HealthListenPath: "/test"}, ":10004", "/test"}, + {"all", &configv1.Config{HealthListenAddress: "test", HealthListenPort: 8000, HealthListenPath: "/test"}, "test:8000", "/test"}, + {"address defaults to listen address", &configv1.Config{ListenAddress: "test"}, "test:10004", "/healthz"}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + hs := NewHealthServer(tt.config) + require.Equal(t, tt.wantAddress, hs.(*healthServer).getAddressAndPort()) + require.Equal(t, tt.wantPath, hs.(*healthServer).getPath()) + }) + } +} diff --git a/internal/testdata/invalid-health-port.json b/internal/testdata/invalid-health-port.json new file mode 100644 index 0000000..169f65e --- /dev/null +++ b/internal/testdata/invalid-health-port.json @@ -0,0 +1,4 @@ +{ + "listen_port": 8000, + "health_listen_port": 8000 +} From 0bd8607e2b5024f4557b06d78c0d87c04efe06a9 Mon Sep 17 00:00:00 2001 From: Sergi Castro Date: Fri, 16 Feb 2024 19:15:07 +0100 Subject: [PATCH 2/2] update e2e config --- e2e/mock/authz-config.json | 2 +- e2e/mock/envoy-config.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/e2e/mock/authz-config.json b/e2e/mock/authz-config.json index 9fe51fc..976af13 100644 --- a/e2e/mock/authz-config.json +++ b/e2e/mock/authz-config.json @@ -1,6 +1,6 @@ { "listen_address": "0.0.0.0", - "listen_port": 10004, + "listen_port": 10003, "log_level": "debug", "chains": [ { diff --git a/e2e/mock/envoy-config.yaml b/e2e/mock/envoy-config.yaml index 50d08e8..c86b026 100644 --- a/e2e/mock/envoy-config.yaml +++ b/e2e/mock/envoy-config.yaml @@ -68,4 +68,4 @@ static_resources: address: socket_address: address: ext-authz - port_value: 10004 + port_value: 10003