Skip to content

Commit

Permalink
Merge pull request #991 from ripienaar/misc_anontls
Browse files Browse the repository at this point in the history
(#987) various improvements to TLS on leafnodes
  • Loading branch information
ripienaar authored Sep 9, 2020
2 parents e76bbe8 + 0739544 commit ba746f3
Show file tree
Hide file tree
Showing 8 changed files with 110 additions and 41 deletions.
22 changes: 21 additions & 1 deletion broker/network/dir_account_store.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,17 +38,37 @@ func newDirAccountStore(s accountNotificationReceiver, store string) (as *dirAcc
}, nil
}

func (f *dirAccountStore) Start(ctx context.Context, wg *sync.WaitGroup) {
func (f *dirAccountStore) StoreStart(ctx context.Context, wg *sync.WaitGroup) {
defer wg.Done()

// TODO monitor files
<-ctx.Done()
}

func (f *dirAccountStore) Start(_ *gnatsd.Server) error {
return nil
}

func (f *dirAccountStore) Stop() {
// noop till we have file notify
}

func (f *dirAccountStore) Close() {
f.Stop()
}

func (f *dirAccountStore) IsReadOnly() bool {
return true
}

func (f *dirAccountStore) IsTrackingUpdate() bool {
return false
}

func (f *dirAccountStore) Reload() error {
return nil
}

// Fetch implements gnatsd.AccountResolver
func (f *dirAccountStore) Fetch(name string) (jwt string, err error) {
f.Lock()
Expand Down
20 changes: 16 additions & 4 deletions broker/network/network.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ type ChoriaFramework interface {
}

type accountStore interface {
Start(context.Context, *sync.WaitGroup)
StoreStart(context.Context, *sync.WaitGroup)
Stop()

gnatsd.AccountResolver
Expand Down Expand Up @@ -62,7 +62,9 @@ func NewServer(c ChoriaFramework, bi BuildInfoProvider, debug bool) (s *Server,
mu: &sync.Mutex{},
}

s.opts.ServerName = s.config.Identity
if s.config.Identity != "" {
s.opts.ServerName = s.config.Identity
}
s.opts.Host = s.config.Choria.NetworkListenAddress
s.opts.Port = s.config.Choria.NetworkClientPort
s.opts.WriteDeadline = s.config.Choria.NetworkWriteDeadline
Expand Down Expand Up @@ -143,7 +145,7 @@ func (s *Server) Start(ctx context.Context, wg *sync.WaitGroup) {

if s.as != nil {
wg.Add(1)
go s.as.Start(ctx, wg)
go s.as.StoreStart(ctx, wg)
}

go s.gnatsd.Start()
Expand Down Expand Up @@ -178,6 +180,7 @@ func (s *Server) Start(ctx context.Context, wg *sync.WaitGroup) {

func (s *Server) setupTLS() (err error) {
if !s.config.Choria.NetworkClientTLSForce && !s.IsTLS() {
s.log.WithField("client_tls_force_required", s.config.Choria.NetworkClientTLSForce).WithField("disable_tls", s.config.DisableTLS).Warn("Skipping broker TLS set up")
return nil
}

Expand All @@ -192,14 +195,22 @@ func (s *Server) setupTLS() (err error) {

s.opts.TLS = true
s.opts.AllowNonTLS = false
s.opts.TLSVerify = !s.config.DisableTLSVerify

s.opts.TLSTimeout = float64(s.config.Choria.NetworkTLSTimeout)

tlsc, err := s.choria.TLSConfig()
if err != nil {
return err
}

s.opts.TLSVerify = true
tlsc.ClientAuth = tls.RequireAndVerifyClientCert

if s.config.DisableTLSVerify {
s.opts.TLSVerify = false
tlsc.ClientAuth = tls.NoClientCert
}

if s.config.Choria.NetworkClientTLSAnon {
if len(s.config.Choria.NetworkLeafRemotes) == 0 {
return fmt.Errorf("can only configure anonymous TLS for client connections when leafnodes are defined using plugin.choria.network.leafnode_remotes")
Expand All @@ -217,6 +228,7 @@ func (s *Server) setupTLS() (err error) {
s.opts.TLSVerify = false
s.opts.TLS = true
tlsc.InsecureSkipVerify = true
tlsc.ClientAuth = tls.NoClientCert
}

s.opts.TLSConfig = tlsc
Expand Down
59 changes: 42 additions & 17 deletions broker/network/network_leafnodes.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
package network

import (
"fmt"
"strings"

"github.com/choria-io/go-choria/srvcache"
gnatsd "github.com/nats-io/nats-server/v2/server"

"github.com/choria-io/go-choria/srvcache"
)

func (s *Server) setupLeafNodes() (err error) {
Expand All @@ -14,17 +16,6 @@ func (s *Server) setupLeafNodes() (err error) {

s.log.Infof("Starting Broker Leafnode support listening on %s:%d", s.config.Choria.NetworkListenAddress, s.config.Choria.NetworkLeafPort)

if s.config.Choria.NetworkLeafPort > 0 {
s.opts.LeafNode.Host = s.config.Choria.NetworkListenAddress
s.opts.LeafNode.Port = s.config.Choria.NetworkLeafPort
s.opts.LeafNode.NoAdvertise = true
}

if s.IsTLS() {
s.opts.LeafNode.TLSConfig = s.opts.TLSConfig
s.opts.LeafNode.TLSTimeout = s.opts.TLSTimeout
}

for _, r := range s.config.Choria.NetworkLeafRemotes {
account := s.extractKeyedConfigString("leafnode_remote", r, "account", "")
credentials := s.extractKeyedConfigString("leafnode_remote", r, "credentials", "")
Expand Down Expand Up @@ -59,31 +50,65 @@ func (s *Server) setupLeafNodes() (err error) {

remote := &gnatsd.RemoteLeafOpts{LocalAccount: account, Credentials: credentials, URLs: urlU}

remote.TLSTimeout = s.opts.LeafNode.TLSTimeout

if s.IsTLS() {
remote.TLS = true
remote.TLSConfig = s.opts.LeafNode.TLSConfig
remote.TLSConfig = s.opts.TLSConfig
} else {
s.log.Warnf("Skipping TLS setup for leafnode remote %s url %s", r, urlsStr)
}

tlsc, disable, err := s.extractTLSCFromKeyedConfig("leafnode_remote", r)
if err != nil {
s.log.Errorf("Could not configure custom TLS for leafnode remote %s: %s", r, err)
continue
}
if disable {

switch {
case disable:
s.log.Warnf("Disabling TLS for leafnode remote %s", r)
remote.TLSConfig = nil
remote.TLS = false
} else if tlsc != nil {

case tlsc != nil:
s.log.Infof("Using custom TLS config for leafnode remote %s", r)
remote.TLSConfig = tlsc
remote.TLS = true

case tlsc == nil && s.config.Choria.NetworkClientTLSAnon:
return fmt.Errorf("leafnodes require specific TLS configuration when using Anonymous client connections")
}

s.opts.LeafNode.Remotes = append(s.opts.LeafNode.Remotes, remote)
s.log.Infof("Added remote Leafnode %s with remote %v", r, remote.URLs)
}

if s.config.Choria.NetworkLeafPort > 0 {
if s.IsTLS() {
s.opts.LeafNode.TLSConfig = s.opts.TLSConfig
s.opts.LeafNode.TLSTimeout = s.opts.TLSTimeout

if s.opts.LeafNode.TLSConfig == nil {
return fmt.Errorf("leafnode TLS is not configured")
}

if s.opts.LeafNode.TLSConfig.InsecureSkipVerify || !s.opts.TLSVerify {
s.log.Warnf("Leafnode connections on port %d are not verifying TLS connections", s.opts.LeafNode.Port)
}
} else {
s.log.Warnf("Skipping TLS setup for leafnode connection on port %d", s.opts.LeafNode.Port)
}

s.opts.LeafNode.Host = s.config.Choria.NetworkListenAddress
s.opts.LeafNode.Port = s.config.Choria.NetworkLeafPort
s.opts.LeafNode.NoAdvertise = true

advertise := s.config.Choria.NetworkClientAdvertiseName
parts := strings.Split(s.config.Choria.NetworkClientAdvertiseName, ":")
if len(parts) > 1 {
advertise = fmt.Sprintf("%s:%d", parts[0], s.opts.LeafNode.Port)
}
s.opts.LeafNode.Advertise = advertise
}

return nil
}
5 changes: 3 additions & 2 deletions broker/network/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,9 @@ import (

func (s *Server) extractKeyedConfigString(prefix string, key string, property string, dflt string) (result string) {
item := "plugin.choria.network." + prefix + "." + key + "." + property
s.log.Debugf("Looking for config item %s", item)
return s.config.Option(item, dflt)
value := s.config.Option(item, dflt)
s.log.Debugf("Looking for config item %s, found %q", item, value)
return value
}

func (s *Server) extractTLSCFromKeyedConfig(prefix string, key string) (tlsc *tls.Config, disabled bool, err error) {
Expand Down
2 changes: 1 addition & 1 deletion config/docstrings.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// auto generated at 2020-08-25 18:19:32.909581 +0200 CEST m=+0.001393248
// auto generated at 2020-09-09 09:26:36.265573 +0200 CEST m=+0.002437471

package config

Expand Down
15 changes: 8 additions & 7 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -21,22 +21,23 @@ require (
github.com/miekg/pkcs11 v1.0.3
github.com/nats-io/jsm.go v0.0.18
github.com/nats-io/jwt v1.0.1
github.com/nats-io/jwt/v2 v2.0.0-20200817224207-b9df3db11eda
github.com/nats-io/nats-server/v2 v2.1.8-0.20200728153118-e2333641f9b7
github.com/nats-io/jwt/v2 v2.0.0-20200908173214-d4f335c8eeb1
github.com/nats-io/nats-server/v2 v2.1.8-0.20200908205505-5cd11bf77d06
github.com/nats-io/nats.go v1.10.1-0.20200817211004-cd74bc037e7c
github.com/nats-io/nsc v0.0.0-20200617223447-2aca79c9d220
github.com/nats-io/stan.go v0.7.0
github.com/olekukonko/tablewriter v0.0.4
github.com/onsi/ginkgo v1.14.0
github.com/onsi/gomega v1.10.1
github.com/open-policy-agent/opa v0.23.0
github.com/onsi/ginkgo v1.14.1
github.com/onsi/gomega v1.10.2
github.com/open-policy-agent/opa v0.23.2
github.com/pkg/errors v0.9.1
github.com/prometheus/client_golang v1.7.1
github.com/prometheus/client_model v0.2.0
github.com/robfig/cron v1.2.0
github.com/sirupsen/logrus v1.6.0
github.com/tidwall/gjson v1.6.0
github.com/tidwall/pretty v1.0.1
github.com/stretchr/testify v1.6.1 // indirect
github.com/tidwall/gjson v1.6.1
github.com/tidwall/pretty v1.0.2
github.com/xeipuuv/gojsonschema v1.2.0
go.uber.org/atomic v1.6.0
golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9
Expand Down
23 changes: 15 additions & 8 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -313,8 +313,9 @@ github.com/nats-io/jwt v1.0.1 h1:71ivoESdfT2K/qDiw5YwX/3W9/dR7c+m83xiGOj/EZ4=
github.com/nats-io/jwt v1.0.1/go.mod h1:n3cvmLfBfnpV4JJRN7lRYCyZnw48ksGsbThGXEk4w9M=
github.com/nats-io/jwt/v2 v2.0.0-20200602193336-473d698956ed h1:nnV8Mw23aNwNpKuQWuVBEuAqyBOEY21hLWKpVdNr6dQ=
github.com/nats-io/jwt/v2 v2.0.0-20200602193336-473d698956ed/go.mod h1:vs+ZEjP+XKy8szkBmQwCB7RjYdIlMaPsFPs4VdS4bTQ=
github.com/nats-io/jwt/v2 v2.0.0-20200817224207-b9df3db11eda h1:HelaewvY98jwbBcPSq9R6VB9nKfgxooNP1h/zAmPIhQ=
github.com/nats-io/jwt/v2 v2.0.0-20200817224207-b9df3db11eda/go.mod h1:7Azg2tp3Pylylcdc1Y+5pY04xlVTgt7gFayx0kQW00w=
github.com/nats-io/jwt/v2 v2.0.0-20200827232814-292806fa48ba/go.mod h1:vs+ZEjP+XKy8szkBmQwCB7RjYdIlMaPsFPs4VdS4bTQ=
github.com/nats-io/jwt/v2 v2.0.0-20200908173214-d4f335c8eeb1 h1:9pYw5yHvRAWLHpAmJIo4uwjhgwGc8FoHBYpB/as4etA=
github.com/nats-io/jwt/v2 v2.0.0-20200908173214-d4f335c8eeb1/go.mod h1:vs+ZEjP+XKy8szkBmQwCB7RjYdIlMaPsFPs4VdS4bTQ=
github.com/nats-io/nats-server/v2 v2.0.1-0.20190625001713-2db76bde3329/go.mod h1:RyVdsHHvY4B6c9pWG+uRLpZ0h0XsqiuKp2XCTurP5LI=
github.com/nats-io/nats-server/v2 v2.0.4/go.mod h1:AWdGEVbjKRS9ZIx4DSP5eKW48nfFm7q3uiSkP/1KD7M=
github.com/nats-io/nats-server/v2 v2.1.2/go.mod h1:Afk+wRZqkMQs/p45uXdrVLuab3gwv3Z8C4HTBu8GD/k=
Expand All @@ -327,8 +328,8 @@ github.com/nats-io/nats-server/v2 v2.1.8-0.20200617224755-fa744fdcdaa3/go.mod h1
github.com/nats-io/nats-server/v2 v2.1.8-0.20200622165314-1a590eea78ef h1:m5oosCnkOCEd1EWEbQhFtv06y7Fx7OpKXfenTJ0H3qU=
github.com/nats-io/nats-server/v2 v2.1.8-0.20200622165314-1a590eea78ef/go.mod h1:uXGA6y1uxwW755SK+LoDZggh+UUVsbVoxh8ZG8MqbsI=
github.com/nats-io/nats-server/v2 v2.1.8-0.20200727232909-fbab1daf063e/go.mod h1:MDO4flDityyw4YxR+/rbdcSzlO+Rw6SbY95tHkrd6EU=
github.com/nats-io/nats-server/v2 v2.1.8-0.20200728153118-e2333641f9b7 h1:mCjXGoVZlQAlj9SwQcnYGVXRqlwhEHwfxnZTqy177bo=
github.com/nats-io/nats-server/v2 v2.1.8-0.20200728153118-e2333641f9b7/go.mod h1:MDO4flDityyw4YxR+/rbdcSzlO+Rw6SbY95tHkrd6EU=
github.com/nats-io/nats-server/v2 v2.1.8-0.20200908205505-5cd11bf77d06 h1:6Mt3Em37YE8Zj+sL1Su6Wr9JTVwYExeuhM4lWFCxnGY=
github.com/nats-io/nats-server/v2 v2.1.8-0.20200908205505-5cd11bf77d06/go.mod h1:G2R/OrOlmpsBIkx7wsDARlY6LLrLSC4v/8YdBkL26iI=
github.com/nats-io/nats-streaming-server v0.16.1-0.20190905144423-ed7405a40a25 h1:MQ4JWoWISowk7+cZHKkUR5TTfSLZ1GZSuNJn+DhdFDE=
github.com/nats-io/nats-streaming-server v0.16.1-0.20190905144423-ed7405a40a25/go.mod h1:P12vTqmBpT6Ufs+cu0W1C4N2wmISqa6G4xdLQeO2e2s=
github.com/nats-io/nats.go v1.8.1/go.mod h1:BrFz9vVn0fU3AcH9Vn4Kd7W0NpJ651tD5omQ3M8LwxM=
Expand Down Expand Up @@ -374,8 +375,8 @@ github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108
github.com/onsi/ginkgo v1.12.3/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY=
github.com/onsi/ginkgo v1.13.0 h1:M76yO2HkZASFjXL0HSoZJ1AYEmQxNJmY41Jx1zNUq1Y=
github.com/onsi/ginkgo v1.13.0/go.mod h1:+REjRxOmWfHCjfv9TTWB1jD1Frx4XydAD3zm1lskyM0=
github.com/onsi/ginkgo v1.14.0 h1:2mOpI4JVVPBN+WQRa0WKH2eXR+Ey+uK4n7Zj0aYpIQA=
github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY=
github.com/onsi/ginkgo v1.14.1 h1:jMU0WaQrP0a/YAEq8eJmJKjBoMs+pClEr1vDMlM/Do4=
github.com/onsi/ginkgo v1.14.1/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY=
github.com/onsi/gomega v1.4.2/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
Expand All @@ -384,12 +385,14 @@ github.com/onsi/gomega v1.9.0/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoT
github.com/onsi/gomega v1.10.0/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA=
github.com/onsi/gomega v1.10.1 h1:o0+MgICZLuZ7xjH7Vx6zS/zcu93/BEp1VwkIW1mEXCE=
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
github.com/onsi/gomega v1.10.2 h1:aY/nuoWlKJud2J6U0E3NWsjlg+0GtwXxgEqthRdzlcs=
github.com/onsi/gomega v1.10.2/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
github.com/open-policy-agent/opa v0.16.0/go.mod h1:P0xUE/GQAAgnvV537GzA0Ikw4+icPELRT327QJPkaKY=
github.com/open-policy-agent/opa v0.19.2/go.mod h1:rrwxoT/b011T0cyj+gg2VvxqTtn6N3gp/jzmr3fjW44=
github.com/open-policy-agent/opa v0.21.0 h1:0CVq4EEUP+fJEzjwd9yNLSTWZk4W5rM+QbjdLcT1nY0=
github.com/open-policy-agent/opa v0.21.0/go.mod h1:cZaTfhxsj7QdIiUI0U9aBtOLLTqVNe+XE60+9kZKLHw=
github.com/open-policy-agent/opa v0.23.0 h1:9SqWUD545w+NtNNldzK1GW3XRjsbTXIomnXgQoDMJcU=
github.com/open-policy-agent/opa v0.23.0/go.mod h1:rrwxoT/b011T0cyj+gg2VvxqTtn6N3gp/jzmr3fjW44=
github.com/open-policy-agent/opa v0.23.2 h1:co9fPjnLPwnvaEThBJjCb5E2iAyvW95Qq2PvSOEIwGE=
github.com/open-policy-agent/opa v0.23.2/go.mod h1:rrwxoT/b011T0cyj+gg2VvxqTtn6N3gp/jzmr3fjW44=
github.com/opencontainers/runc v0.0.0-20161107232042-8779fa57eb4a h1:wvqfuQnd/Z9zDxpkyaabmBVf/3RYUmn4B4A1mG0pf3I=
github.com/opencontainers/runc v0.0.0-20161107232042-8779fa57eb4a/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U=
github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw=
Expand Down Expand Up @@ -487,11 +490,15 @@ github.com/tcnksm/go-gitconfig v0.1.2/go.mod h1:/8EhP4H7oJZdIPyT+/UIsG87kTzrzM4U
github.com/tidwall/gjson v1.4.0/go.mod h1:P256ACg0Mn+j1RXIDXoss50DeIABTYK1PULOJHhxOls=
github.com/tidwall/gjson v1.6.0 h1:9VEQWz6LLMUsUl6PueE49ir4Ka6CzLymOAZDxpFsTDc=
github.com/tidwall/gjson v1.6.0/go.mod h1:P256ACg0Mn+j1RXIDXoss50DeIABTYK1PULOJHhxOls=
github.com/tidwall/gjson v1.6.1 h1:LRbvNuNuvAiISWg6gxLEFuCe72UKy5hDqhxW/8183ws=
github.com/tidwall/gjson v1.6.1/go.mod h1:BaHyNc5bjzYkPqgLq7mdVzeiRtULKULXLgZFKsxEHI0=
github.com/tidwall/match v1.0.1 h1:PnKP62LPNxHKTwvHHZZzdOAOCtsJTjo6dZLCwpKm5xc=
github.com/tidwall/match v1.0.1/go.mod h1:LujAq0jyVjBy028G1WhWfIzbpQfMO8bBZ6Tyb0+pL9E=
github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
github.com/tidwall/pretty v1.0.1 h1:WE4RBSZ1x6McVVC8S/Md+Qse8YUv6HRObAx6ke00NY8=
github.com/tidwall/pretty v1.0.1/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
github.com/tidwall/pretty v1.0.2 h1:Z7S3cePv9Jwm1KwS0513MRaoUe3S01WPbLNV40pwWZU=
github.com/tidwall/pretty v1.0.2/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM=
github.com/ulikunitz/xz v0.5.5 h1:pFrO0lVpTBXLpYw+pnLj6TbvHuyjXMfjGeCwSqCVwok=
github.com/ulikunitz/xz v0.5.5/go.mod h1:2bypXElzHzzJZwzH67Y6wb67pO62Rzfn7BSiF4ABRW8=
Expand Down
5 changes: 4 additions & 1 deletion statistics/stats.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,10 +52,11 @@ var (
)

// Start starts serving exp stats and metrics on the configured statistics port
func Start(fw *choria.Framework, handler http.Handler) {
func Start(cfw *choria.Framework, handler http.Handler) {
mu.Lock()
defer mu.Unlock()

fw = cfw
cfg := fw.Configuration()
port := cfg.Choria.StatsPort

Expand All @@ -74,12 +75,14 @@ func Start(fw *choria.Framework, handler http.Handler) {
if handler == nil {
http.HandleFunc("/choria/", handleRoot)
http.Handle("/choria/prometheus", promhttp.Handler())
http.Handle("/choria/metrics", promhttp.Handler())

go http.ListenAndServe(fmt.Sprintf("%s:%d", cfg.Choria.StatsListenAddress, port), nil)
} else {
hh := handler.(*http.ServeMux)
hh.HandleFunc("/choria/", handleRoot)
hh.Handle("/choria/prometheus", promhttp.Handler())
hh.Handle("/choria/metrics", promhttp.Handler())
}

running = true
Expand Down

0 comments on commit ba746f3

Please sign in to comment.