Skip to content
This repository has been archived by the owner on Apr 22, 2024. It is now read-only.

Commit

Permalink
Add basic health server
Browse files Browse the repository at this point in the history
  • Loading branch information
sergicastro committed Feb 16, 2024
1 parent dfd15f3 commit 4dd4a76
Show file tree
Hide file tree
Showing 10 changed files with 334 additions and 33 deletions.
2 changes: 2 additions & 0 deletions cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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
)

Expand Down
107 changes: 74 additions & 33 deletions config/gen/go/v1/config.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

15 changes: 15 additions & 0 deletions config/gen/go/v1/config.pb.validate.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

13 changes: 13 additions & 0 deletions config/v1/config.proto
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
5 changes: 5 additions & 0 deletions internal/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions internal/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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}},
}
Expand Down
2 changes: 2 additions & 0 deletions internal/logging.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ const (
Authz = "authz"
Config = "config"
Default = "default"
Health = "health"
JWKS = "jwks"
Requests = "requests"
Server = "server"
Expand All @@ -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",
Expand Down
114 changes: 114 additions & 0 deletions internal/server/health.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
// 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
}

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
}

func (hs *healthServer) Name() string {
return "Health Server"
}

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)
}

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
}

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)
}
Loading

0 comments on commit 4dd4a76

Please sign in to comment.