Skip to content

Commit

Permalink
Better docs (#14)
Browse files Browse the repository at this point in the history
  • Loading branch information
jkroepke authored Sep 17, 2023
1 parent 2cee089 commit e627e7a
Show file tree
Hide file tree
Showing 6 changed files with 80 additions and 39 deletions.
57 changes: 38 additions & 19 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package cmd

import (
"fmt"
"log/slog"
"net/http"
"os"
"strings"
Expand All @@ -15,22 +16,21 @@ import (
"github.com/knadh/koanf/providers/posflag"
"github.com/knadh/koanf/v2"
"go.uber.org/zap"
"go.uber.org/zap/exp/zapslog"
)

var k = koanf.New(".")

func Execute() {
zapConfig := zap.NewProductionConfig()
zapConfig.Level = zap.NewAtomicLevel()
logger, _ := zapConfig.Build()
logger, _ := zap.NewProduction()
defer logger.Sync() //nolint:errcheck

f := config.FlagSet()
if err := f.Parse(os.Args[1:]); err != nil {
logger.Fatal(fmt.Sprintf("error loading config: %v", err))
logger.Fatal(fmt.Sprintf("error parsing cli args: %v", err))
}

configFile, _ := f.GetString("config")
configFile, _ := f.GetString("configfile")
if configFile != "" {
if err := k.Load(file.Provider(configFile), yaml.Parser()); err != nil {
logger.Fatal(fmt.Sprintf("error loading config: %v", err))
Expand All @@ -56,54 +56,73 @@ func Execute() {
}

var conf config.Config
if err := k.UnmarshalWithConf("", &conf, koanf.UnmarshalConf{Tag: "koanf"}); err != nil {
if err := k.UnmarshalWithConf("", &conf, koanf.UnmarshalConf{}); err != nil {
logger.Fatal(fmt.Sprintf("error loading config: %v", err))
}

if err := config.Validate(&conf); err != nil {
logger.Fatal(fmt.Sprintf("error validating config: %v", err))
}

level, err := zap.ParseAtomicLevel(conf.Log.Level)
logger, err := configureLogger(&conf)
if err != nil {
logger.Fatal(fmt.Sprintf("invalid log level: %v", err))
logger.Fatal(fmt.Sprintf("error configure logger: %v", err))
}
zapConfig.Level.SetLevel(level.Level())
sugaredLogger := logger.Sugar()
defer logger.Sync() //nolint:errcheck

sl := slog.New(zapslog.NewHandler(logger.Core(), nil))

oidcClient, err := oauth2.Configure(&conf)
if err != nil {
sugaredLogger.Fatal(err)
sl.Error(err.Error())
os.Exit(1)
}

openvpnClient := openvpn.NewClient(sugaredLogger, &conf)
openvpnClient := openvpn.NewClient(sl, &conf)

go func() {
stdLogger, err := zap.NewStdLogAt(logger, zap.ErrorLevel)
if err != nil {
sugaredLogger.Fatal(err)
sl.Error(err.Error())
os.Exit(1)
}

server := &http.Server{
Addr: conf.Http.Listen,
ErrorLog: stdLogger,
Handler: oauth2.Handler(sugaredLogger, &oidcClient, &conf, openvpnClient),
Handler: oauth2.Handler(sl, &oidcClient, &conf, openvpnClient),
}

if conf.Http.Tls {
sugaredLogger.Infof("HTTPS server listen on %s", conf.Http.Listen)
sl.Info(fmt.Sprintf("HTTPS server listen on %s", conf.Http.Listen))
if err := server.ListenAndServeTLS(conf.Http.CertFile, conf.Http.KeyFile); err != nil {
sugaredLogger.Fatal(err)
sl.Error(err.Error())
os.Exit(1)
}
} else {
sugaredLogger.Infof("HTTP server listen on %s", conf.Http.Listen)
sl.Info(fmt.Sprintf("HTTP server listen on %s", conf.Http.Listen))
if err := server.ListenAndServe(); err != nil {
sugaredLogger.Fatal(err)
sl.Error(err.Error())
os.Exit(1)
}
}
}()

if err := openvpnClient.Connect(); err != nil {
sugaredLogger.Fatal(err)
sl.Error(err.Error())
os.Exit(1)
}
}

func configureLogger(conf *config.Config) (*zap.Logger, error) {
level, err := zap.ParseAtomicLevel(conf.Log.Level)
if err != nil {
return nil, fmt.Errorf("invalid log level: %v", err)
}

zapConfig := zap.NewProductionConfig()
zapConfig.Level = level
zapConfig.Encoding = conf.Log.Format

return zapConfig.Build()
}
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ require (
github.com/spf13/pflag v1.0.5
github.com/zitadel/oidc/v2 v2.11.0
go.uber.org/zap v1.26.0
go.uber.org/zap/exp v0.2.0
)

require (
Expand Down
18 changes: 18 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,22 +1,28 @@
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ=
github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4=
github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
github.com/gorilla/schema v1.2.0 h1:YufUaxZYCKGFuAq3c96BOhjgd5nmXiOY9NGzF247Tsc=
github.com/gorilla/schema v1.2.0/go.mod h1:kgLaKoK1FELgZqMAVxx/5cbj0kT+57qxUrAlIO2eleU=
github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ=
github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
github.com/jeremija/gosubmit v0.2.7 h1:At0OhGCFGPXyjPYAsCchoBUhE099pcBXmsb4iZqROIc=
github.com/jeremija/gosubmit v0.2.7/go.mod h1:Ui+HS073lCFREXBbdfrJzMB57OI/bdxTiLtrDHHhFPI=
github.com/knadh/koanf/maps v0.1.1 h1:G5TjmUh2D7G2YWf5SQQqSiHRJEjaicvU0KpypqB3NIs=
github.com/knadh/koanf/maps v0.1.1/go.mod h1:npD/QZY3V6ghQDdcQzl1W4ICNVTkohC8E73eI2xW4yI=
github.com/knadh/koanf/parsers/yaml v0.1.0 h1:ZZ8/iGfRLvKSaMEECEBPM1HQslrZADk8fP1XFUxVI5w=
Expand All @@ -38,23 +44,34 @@ github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx
github.com/muhlemmer/gu v0.3.1 h1:7EAqmFrW7n3hETvuAdmFmn4hS8W+z3LgKtrnow+YzNM=
github.com/muhlemmer/gu v0.3.1/go.mod h1:YHtHR+gxM+bKEIIs7Hmi9sPT3ZDUvTN/i88wQpZkrdM=
github.com/muhlemmer/httpforwarded v0.1.0 h1:x4DLrzXdliq8mprgUMR0olDvHGkou5BJsK/vWUetyzY=
github.com/muhlemmer/httpforwarded v0.1.0/go.mod h1:yo9czKedo2pdZhoXe+yDkGVbU0TJ0q9oQ90BVoDEtw0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rs/cors v1.10.0 h1:62NOS1h+r8p1mW6FM0FSB0exioXLhd/sh15KpjWBZ+8=
github.com/rs/cors v1.10.0/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
github.com/zitadel/oidc/v2 v2.11.0 h1:Am4/yQr4iiM5bznRgF3FOp+wLdKx2gzSU73uyI9vvBE=
github.com/zitadel/oidc/v2 v2.11.0/go.mod h1:enFSVBQI6aE0TEB1ntjXs9r6O6DEosxX4uhEBLBVD8o=
go.opentelemetry.io/otel v1.17.0 h1:MW+phZ6WZ5/uk2nd93ANk/6yJ+dVrvNWUjGhnnFU5jM=
go.opentelemetry.io/otel v1.17.0/go.mod h1:I2vmBGtFaODIVMBSTPVDlJSzBDNf93k60E6Ft0nyjo0=
go.opentelemetry.io/otel/metric v1.17.0 h1:iG6LGVz5Gh+IuO0jmgvpTB6YVrCGngi8QGm+pMd8Pdc=
go.opentelemetry.io/otel/metric v1.17.0/go.mod h1:h4skoxdZI17AxwITdmdZjjYJQH5nzijUUjm+wtPph5o=
go.opentelemetry.io/otel/trace v1.17.0 h1:/SWhSRHmDPOImIAetP1QAeMnZYiQXrTy4fMMYOdSKWQ=
go.opentelemetry.io/otel/trace v1.17.0/go.mod h1:I/4vKTgFclIsXRVucpH25X0mpFSczM7aHeaz0ZBLWjY=
go.uber.org/goleak v1.2.0 h1:xqgm/S+aQvhWFTtR0XK3Jvg7z8kGV8P4X14IzwN3Eqk=
go.uber.org/goleak v1.2.0/go.mod h1:XJYK+MuIchqpmGmUSAzotztawfKvYLUIgg7guXrwVUo=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo=
go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so=
go.uber.org/zap/exp v0.2.0 h1:FtGenNNeCATRB3CmB/yEUnjEFeJWpB/pMcy7e2bKPYs=
go.uber.org/zap/exp v0.2.0/go.mod h1:t0gqAIdh1MfKv9EwN/dLwfZnJxe9ITAZN78HEWPFWDQ=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.13.0 h1:mvySKfSWJ+UKUii46M40LOvyWfN0s2U+46/jDd0e6Ck=
Expand All @@ -64,6 +81,7 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.15.0 h1:ugBLEUaxABaB5AJqW9enI0ACdci2RUd4eP51NTBvuJ8=
golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
golang.org/x/oauth2 v0.12.0 h1:smVPGxink+n1ZI5pkQa8y6fZT0RW0MgCO5bFpepy4B4=
golang.org/x/oauth2 v0.12.0/go.mod h1:A74bZ3aGXgCY0qaIC9Ahg6Lglin4AMAco8cIv9baba4=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
Expand Down
5 changes: 4 additions & 1 deletion internal/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ type Http struct {
}

type Log struct {
Level string `koanf:"level"`
Format string `koanf:"format"`
Level string `koanf:"level"`
}

type OpenVpn struct {
Expand Down Expand Up @@ -70,6 +71,8 @@ func FlagSet() *flag.FlagSet {
}

f.String("configfile", "", "path to one .yaml config files. (env: CONFIG_CONFIGFILE)")
f.String("log.format", "json", "log format. json or console (env: CONFIG_LOG_FORMAT)")
f.String("log.level", "info", "log level. (env: CONFIG_LOG_LEVEL)")
f.String("http.listen", ":9000", "listen addr for client listener. (env: CONFIG_HTTP_LISTEN)")
f.Bool("http.tls", false, "enable TLS listener. (env: CONFIG_HTTP_TLS)")
f.String("http.baseurl", "http://localhost:9000", "listen addr for client listener. (env: CONFIG_HTTP_BASEURL)")
Expand Down
20 changes: 10 additions & 10 deletions internal/oauth2/handler.go
Original file line number Diff line number Diff line change
@@ -1,25 +1,26 @@
package oauth2

import (
"fmt"
"log/slog"
"net/http"

"github.com/jkroepke/openvpn-auth-oauth2/internal/config"
"github.com/jkroepke/openvpn-auth-oauth2/internal/openvpn"
"github.com/jkroepke/openvpn-auth-oauth2/internal/state"
"github.com/zitadel/oidc/v2/pkg/client/rp"
"github.com/zitadel/oidc/v2/pkg/oidc"
"go.uber.org/zap"
)

func Handler(logger *zap.SugaredLogger, oidcClient *rp.RelyingParty, conf *config.Config, openvpnClient *openvpn.Client) *http.ServeMux {
func Handler(logger *slog.Logger, oidcClient *rp.RelyingParty, conf *config.Config, openvpnClient *openvpn.Client) *http.ServeMux {
mux := http.NewServeMux()
mux.Handle("/oauth2/start", oauth2Start(logger, oidcClient, conf))
mux.Handle("/oauth2/callback", oauth2Callback(logger, oidcClient, conf, openvpnClient))

return mux
}

func oauth2Start(logger *zap.SugaredLogger, oidcClient *rp.RelyingParty, conf *config.Config) http.Handler {
func oauth2Start(logger *slog.Logger, oidcClient *rp.RelyingParty, conf *config.Config) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
sessionState := r.URL.Query().Get("state")
if sessionState == "" {
Expand All @@ -29,12 +30,12 @@ func oauth2Start(logger *zap.SugaredLogger, oidcClient *rp.RelyingParty, conf *c

session := state.NewEncoded(sessionState)
if err := session.Decode(conf.Http.SessionSecret); err != nil {
logger.Warnf("invalid state: %s", sessionState)
logger.Warn(fmt.Sprintf("invalid state: %s", sessionState))
w.WriteHeader(http.StatusBadRequest)
return
}

logger.Infow("initialize authorization via oauth2",
logger.Info("initialize authorization via oauth2",
"common_name", session.CommonName,
"cid", session.Cid,
"kid", session.Kid,
Expand All @@ -46,13 +47,12 @@ func oauth2Start(logger *zap.SugaredLogger, oidcClient *rp.RelyingParty, conf *c
})
}

func oauth2Callback(
logger *zap.SugaredLogger, oidcClient *rp.RelyingParty, conf *config.Config, openvpnClient *openvpn.Client) http.Handler {
func oauth2Callback(logger *slog.Logger, oidcClient *rp.RelyingParty, conf *config.Config, openvpnClient *openvpn.Client) http.Handler {

return rp.CodeExchangeHandler(func(w http.ResponseWriter, r *http.Request, tokens *oidc.Tokens[*oidc.IDTokenClaims], encryptedState string, rp rp.RelyingParty) {
session := state.NewEncoded(encryptedState)
if err := session.Decode(conf.Http.SessionSecret); err != nil {
logger.Warnw(err.Error(),
logger.Warn(err.Error(),
"subject", tokens.IDTokenClaims.Subject,
"preferred_username", tokens.IDTokenClaims.PreferredUsername,
)
Expand All @@ -61,7 +61,7 @@ func oauth2Callback(
}

if err := validateToken(conf, session, tokens); err != nil {
logger.Warnw(err.Error(),
logger.Warn(err.Error(),
"subject", tokens.IDTokenClaims.Subject,
"preferred_username", tokens.IDTokenClaims.PreferredUsername,
"common_name", session.CommonName,
Expand All @@ -74,7 +74,7 @@ func oauth2Callback(
return
}

logger.Infow("successful authorization via oauth2",
logger.Info("successful authorization via oauth2",
"subject", tokens.IDTokenClaims.Subject,
"preferred_username", tokens.IDTokenClaims.PreferredUsername,
"common_name", session.CommonName,
Expand Down
18 changes: 9 additions & 9 deletions internal/openvpn/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,21 @@ import (
"bufio"
"errors"
"fmt"
"log/slog"
"net"
"net/url"
"slices"
"strings"

"github.com/jkroepke/openvpn-auth-oauth2/internal/config"
"github.com/jkroepke/openvpn-auth-oauth2/internal/state"
"go.uber.org/zap"
)

type Client struct {
conf *config.Config
conn net.Conn
reader *bufio.Reader
logger *zap.SugaredLogger
logger *slog.Logger

clients chan *ClientConnection
commandResponse chan string
Expand All @@ -27,7 +27,7 @@ type Client struct {
shutdown chan struct{}
}

func NewClient(logger *zap.SugaredLogger, conf *config.Config) *Client {
func NewClient(logger *slog.Logger, conf *config.Config) *Client {
return &Client{
conf: conf,
logger: logger,
Expand Down Expand Up @@ -142,7 +142,7 @@ func (c *Client) processClient(client *ClientConnection) error {
case "CONNECT":
fallthrough
case "REAUTH":
c.logger.Infow("new client connection",
c.logger.Info("new client connection",
"cid", client.Cid,
"kid", client.Kid,
"reason", client.Reason,
Expand All @@ -151,7 +151,7 @@ func (c *Client) processClient(client *ClientConnection) error {
)

if slices.Contains(c.conf.OpenVpn.Bypass.CommonNames, client.Env["common_name"]) {
c.logger.Infow("client bypass authentication",
c.logger.Info("client bypass authentication",
"cid", client.Cid,
"kid", client.Kid,
"reason", client.Reason,
Expand All @@ -164,7 +164,7 @@ func (c *Client) processClient(client *ClientConnection) error {
}

if val, ok := client.Env["IV_SSO"]; !ok || !strings.Contains(val, "webauth") {
c.logger.Warnw(ErrorSsoNotSupported,
c.logger.Warn(ErrorSsoNotSupported,
"cid", client.Cid,
"kid", client.Kid,
"reason", client.Reason,
Expand All @@ -182,7 +182,7 @@ func (c *Client) processClient(client *ClientConnection) error {
}

sessionUrl := fmt.Sprintf("%s/oauth2/start?state=%s", c.conf.Http.BaseUrl, url.QueryEscape(session.Encoded))
c.logger.Infow("start pending auth",
c.logger.Info("start pending auth",
"cid", client.Cid,
"kid", client.Kid,
"reason", client.Reason,
Expand All @@ -191,14 +191,14 @@ func (c *Client) processClient(client *ClientConnection) error {
)
c.SendCommand(`client-pending-auth %d %d "WEB_AUTH::%s" %d`, client.Cid, client.Kid, sessionUrl, 600)
case "ESTABLISHED":
c.logger.Warnw("client established",
c.logger.Warn("client established",
"cid", client.Cid,
"reason", client.Reason,
"common_name", client.Env["common_name"],
"username", client.Env["username"],
)
case "DISCONNECT":
c.logger.Warnw("client disconnected",
c.logger.Warn("client disconnected",
"cid", client.Cid,
"reason", client.Reason,
"common_name", client.Env["common_name"],
Expand Down

0 comments on commit e627e7a

Please sign in to comment.