From 2fb9d77218c4eabd351d25444d3a176f31dfa32d Mon Sep 17 00:00:00 2001 From: Jozef Kralik Date: Thu, 3 Aug 2023 09:39:44 +0000 Subject: [PATCH 1/2] enable TLS as default configuration (self-signed-certificate) redirect http request to https --- .gitignore | 5 +- .golangci.yml | 2 - cmd/defaultConfig.go | 2 +- cmd/generateSelfSigned.go | 172 +++++++++++++++++++++++++++++++ cmd/main.go | 35 +++++-- config.yaml | 18 ++-- go.mod | 31 +++--- go.sum | 60 +++++------ pkg/net/listener/tls/config.go | 23 +++++ pkg/net/listener/tls/listener.go | 155 ++++++++++++++++++++++++++++ service/config/config.go | 6 +- service/config/grpc/config.go | 31 +++--- service/config/http/config.go | 15 ++- service/http/service.go | 102 +++++++++++++----- 14 files changed, 547 insertions(+), 110 deletions(-) create mode 100644 cmd/generateSelfSigned.go create mode 100644 pkg/net/listener/tls/config.go create mode 100644 pkg/net/listener/tls/listener.go diff --git a/.gitignore b/.gitignore index 0813e884..0aa528f6 100644 --- a/.gitignore +++ b/.gitignore @@ -33,4 +33,7 @@ pids # Coverage directory used by tools like istanbul coverage -*.lcov \ No newline at end of file +*.lcov + +# vscode +.vscode/launch.json \ No newline at end of file diff --git a/.golangci.yml b/.golangci.yml index 6c97e81a..f580e5f8 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -21,7 +21,6 @@ linters: - bidichk # Checks for dangerous unicode character sequences # - bodyclose # checks whether HTTP response body is closed successfully # - contextcheck # check the function whether use a non-inherited context - - deadcode # Finds unused code - decorder # check declaration order and count of types, constants, variables and functions # - depguard # Go linter that checks if package imports are in a list of acceptable packages - dogsled # Checks assignments with too many blank identifiers (e.g. x, _, _, _, := f()) @@ -74,7 +73,6 @@ linters: - unconvert # Remove unnecessary type conversions - unparam # Reports unused function parameters - unused # Checks Go code for unused constants, variables, functions and types - - varcheck # Finds unused global variables and constants # - wastedassign # wastedassign finds wasted assignment statements - whitespace # Tool for detection of leading and trailing whitespace - makezero # Finds slice declarations with non-zero initial length diff --git a/cmd/defaultConfig.go b/cmd/defaultConfig.go index 8730b0ce..6036e5c2 100644 --- a/cmd/defaultConfig.go +++ b/cmd/defaultConfig.go @@ -43,7 +43,7 @@ func resolveDefaultConfig(configPath string) error { return nil } configDirectoryPath := filepath.Dir(configPath) - cfg := config.DefaultConfig(configDirectoryPath + "/www") + cfg := config.DefaultConfig(configDirectoryPath) if err := os.WriteFile(configPath, []byte(cfg.String()), 0o600); err != nil { return fmt.Errorf("cannot write default config: %w", err) } diff --git a/cmd/generateSelfSigned.go b/cmd/generateSelfSigned.go new file mode 100644 index 00000000..d29137cc --- /dev/null +++ b/cmd/generateSelfSigned.go @@ -0,0 +1,172 @@ +package main + +import ( + "context" + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" + "crypto/tls" + "crypto/x509" + "crypto/x509/pkix" + "encoding/pem" + "fmt" + "math/big" + "net" + "os" + "path" + "strings" + "time" + + "github.com/apex/log" + externalip "github.com/glendc/go-external-ip" +) + +func getExternalIP() (net.IP, error) { + consensus := externalip.DefaultConsensus(&externalip.ConsensusConfig{ + Timeout: time.Second * 3, + }, nil) + return consensus.ExternalIP() +} + +func getExternalDomains(ip net.IP) ([]string, error) { + ctx, cancel := context.WithTimeout(context.Background(), time.Second*3) + defer cancel() + dns, err := net.DefaultResolver.LookupAddr(ctx, ip.String()) + if err != nil { + return nil, err + } + // remove trailing dot + for i := range dns { + dns[i] = strings.TrimSuffix(dns[i], ".") + } + return dns, nil +} + +func setupIPsFromInterface(template *x509.Certificate, iface net.Interface) { + if iface.Flags&net.FlagLoopback != 0 { + return + } + if iface.Flags&net.FlagUp == 0 { + return + } + addrs, err := iface.Addrs() + if err != nil { + log.Warnf("cannot get network interface(%v) addresses: %v", iface.Name, err) + return + } + for _, addr := range addrs { + if ip, _, err3 := net.ParseCIDR(addr.String()); err3 == nil && !ip.IsUnspecified() { + template.IPAddresses = append(template.IPAddresses, ip) + } else { + log.Warnf("cannot parse network interface(%v) address(%v): %v", iface.Name, addr.String(), err3) + } + } +} + +func setupIPsFromInterfaces(template *x509.Certificate) { + ifaces, err := net.Interfaces() + if err != nil { + log.Warnf("cannot get network interfaces: %v", err) + return + } + for _, iface := range ifaces { + setupIPsFromInterface(template, iface) + } +} + +func setupIPAndDomains(template *x509.Certificate) { + template.DNSNames = []string{"localhost"} + template.IPAddresses = []net.IP{net.ParseIP("127.0.0.1"), net.ParseIP("::1")} + if ip, err := getExternalIP(); err != nil { + log.Warnf("cannot get external IP: %v", err) + } else { + template.IPAddresses = append(template.IPAddresses, ip) + domains, err := getExternalDomains(ip) + if err != nil { + log.Warnf("cannot get external domains: %v", err) + } else { + template.DNSNames = append(template.DNSNames, domains...) + } + } + setupIPsFromInterfaces(template) +} + +const cn = "self-signed-certificate" + +func checkSelfSignedCertificate(certFile, keyFile string) bool { + crt, err := tls.LoadX509KeyPair(certFile, keyFile) + if err != nil { + return false + } + cert, err := x509.ParseCertificate(crt.Certificate[0]) + if err != nil { + return false + } + if cert.Subject.CommonName != cn { + // it is not self signed certificate + return true + } + now := time.Now() + if now.Before(cert.NotBefore) || now.After(cert.NotAfter) { + return false + } + return true +} + +func generateSelfSigned(certFile, keyFile string) error { + err := os.MkdirAll(path.Dir(certFile), 0o700) + if err != nil { + return fmt.Errorf("cannot create directory(%v) for certificate: %w", path.Dir(certFile), err) + } + err = os.MkdirAll(path.Dir(keyFile), 0o700) + if err != nil { + return fmt.Errorf("cannot create directory(%v) for key: %w", path.Dir(keyFile), err) + } + priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + if err != nil { + return fmt.Errorf("cannot generate key: %w", err) + } + template := x509.Certificate{ + SerialNumber: big.NewInt(1), + Subject: pkix.Name{ + Organization: []string{"plgd.dev"}, + CommonName: cn, + }, + NotBefore: time.Now(), + NotAfter: time.Now().Add(time.Hour * 24 * 356), + + KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign, + ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth}, + BasicConstraintsValid: true, + IsCA: true, + } + setupIPAndDomains(&template) + + derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, &priv.PublicKey, priv) + if err != nil { + return fmt.Errorf("cannot create certificate: %w", err) + } + + derKeyBytes, err := x509.MarshalPKCS8PrivateKey(priv) + if err != nil { + return fmt.Errorf("cannot marshal private key: %w", err) + } + + err = os.WriteFile(certFile, pem.EncodeToMemory(&pem.Block{ + Type: "CERTIFICATE", + Bytes: derBytes, + }), 0o600) + if err != nil { + return fmt.Errorf("cannot write certificate to file(%v): %w", certFile, err) + } + + err = os.WriteFile(keyFile, pem.EncodeToMemory(&pem.Block{ + Type: "PRIVATE KEY", + Bytes: derKeyBytes, + }), 0o600) + if err != nil { + return fmt.Errorf("cannot write key to file(%v): %w", keyFile, err) + } + + return nil +} diff --git a/cmd/main.go b/cmd/main.go index 574f39cc..b29451f4 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -37,7 +37,7 @@ var ( ReleaseURL = "unknown url" ) -func main() { +func loadConfig() config.Config { var opts struct { Version bool `short:"v" long:"version" description:"version"` ConfigPath string `long:"config" description:"yaml config file path"` @@ -45,34 +45,49 @@ func main() { _, _ = flags.NewParser(&opts, flags.Default|flags.IgnoreUnknown).Parse() if opts.Version { fmt.Println(Version) - return + os.Exit(0) } if err := resolveDefaultConfig(opts.ConfigPath); err != nil { log.Errorf("cannot create default config: %v", err) - return + os.Exit(1) } // parse line arguments again because resolveDefaultConfig can set config path _, _ = flags.NewParser(&opts, flags.Default|flags.IgnoreUnknown).Parse() cfg, err := config.New(opts.ConfigPath) if err != nil { log.Errorf("cannot load config: %v", err) - return + os.Exit(1) } if _, err = os.Stat(cfg.APIs.HTTP.UI.Directory); cfg.APIs.HTTP.UI.Enabled && err != nil { if err = extractUI(cfg.APIs.HTTP.UI.Directory); err != nil { log.Errorf("cannot extract UI: %v", err) + os.Exit(1) + } + } + if cfg.APIs.HTTP.Enabled && cfg.APIs.HTTP.TLS.Enabled && !checkSelfSignedCertificate(cfg.APIs.HTTP.TLS.CertFile, cfg.APIs.HTTP.TLS.KeyFile) { + if err = generateSelfSigned(cfg.APIs.HTTP.TLS.CertFile, cfg.APIs.HTTP.TLS.KeyFile); err != nil { + log.Errorf("cannot generate self signed certificate for HTTP: %v", err) + os.Exit(1) + } + } + if cfg.APIs.GRPC.Enabled && cfg.APIs.GRPC.TLS.Enabled && !checkSelfSignedCertificate(cfg.APIs.GRPC.TLS.CertFile, cfg.APIs.GRPC.TLS.KeyFile) { + if err = generateSelfSigned(cfg.APIs.GRPC.TLS.CertFile, cfg.APIs.GRPC.TLS.KeyFile); err != nil { + log.Errorf("cannot generate self signed certificate for GRPC: %v", err) + os.Exit(1) } } + return cfg +} + +func main() { + cfg := loadConfig() logger := log.NewLogger(cfg.Log) log.Set(logger) fileWatcher, err := fsnotify.NewWatcher(logger) if err != nil { log.Errorf("cannot create file fileWatcher: %v", err) - return + os.Exit(1) } - defer func() { - _ = fileWatcher.Close() - }() log.Debugf("version: %v, buildDate: %v, buildRevision %v", Version, BuildDate, CommitHash) log.Debugf("config:\n%v", cfg.String()) info := grpc.ServiceInformation{ @@ -86,11 +101,11 @@ func main() { s, err := service.New(context.Background(), cfg, &info, fileWatcher, logger) if err != nil { log.Errorf("cannot create service: %v", err) - return + os.Exit(1) } err = s.Serve() if err != nil { log.Errorf("cannot serve service: %v", err) - return + os.Exit(1) } } diff --git a/config.yaml b/config.yaml index 5bd50a4b..fa642bb6 100644 --- a/config.yaml +++ b/config.yaml @@ -16,9 +16,9 @@ apis: idleTimeout: 30s tls: enabled: false - caPool: /certs/ca.pem - keyFile: /certs/key.pem - certFile: /certs/crt.pem + caPool: certs/ca.pem + keyFile: certs/key.pem + certFile: certs/crt.pem clientCertificateRequired: true cors: allowedOrigins: @@ -59,9 +59,9 @@ apis: timeout: 0s tls: enabled: false - caPool: /certs/ca.pem - keyFile: /certs/key.pem - certFile: /certs/crt.pem + caPool: certs/ca.pem + keyFile: certs/key.pem + certFile: certs/crt.pem clientCertificateRequired: true clients: device: @@ -77,9 +77,9 @@ clients: - justWorks manufacturerCertificate: tls: - caPool: /certs/mfg_ca.pem - keyFile: /certs/mfg_key.pem - certFile: /certs/mfg_crt.pem + caPool: certs/mfg_ca.pem + keyFile: certs/mfg_key.pem + certFile: certs/mfg_crt.pem tls: authentication: preSharedKey preSharedKey: diff --git a/go.mod b/go.mod index 73be4ba4..dc0b2457 100644 --- a/go.mod +++ b/go.mod @@ -3,9 +3,11 @@ module github.com/plgd-dev/client-application go 1.18 require ( - cloud.google.com/go v0.110.3 + cloud.google.com/go v0.110.4 + github.com/apex/log v1.9.0 github.com/favadi/protoc-go-inject-tag v1.4.0 github.com/fullstorydev/grpchan v1.1.1 + github.com/glendc/go-external-ip v0.1.0 github.com/golang-jwt/jwt/v4 v4.5.0 github.com/google/uuid v1.3.0 github.com/goreleaser/goreleaser v1.9.2 @@ -16,8 +18,8 @@ require ( github.com/jessevdk/go-flags v1.5.0 github.com/lestrrat-go/jwx v1.2.26 github.com/pion/dtls/v2 v2.2.7 - github.com/plgd-dev/device/v2 v2.2.1-0.20230602173355-efc3a81993c3 - github.com/plgd-dev/go-coap/v3 v3.1.3 + github.com/plgd-dev/device/v2 v2.2.1-0.20230803141537-60c77f6f4864 + github.com/plgd-dev/go-coap/v3 v3.1.4-0.20230802114331-351cd00bab2d github.com/plgd-dev/hub/v2 v2.7.19 github.com/plgd-dev/kit/v2 v2.0.0-20211006190727-057b33161b90 github.com/stretchr/testify v1.8.4 @@ -25,7 +27,7 @@ require ( go.uber.org/atomic v1.11.0 go.uber.org/zap v1.24.0 google.golang.org/api v0.129.0 - google.golang.org/grpc v1.56.1 + google.golang.org/grpc v1.57.0 google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.3.0 google.golang.org/protobuf v1.31.0 gopkg.in/yaml.v3 v3.0.1 @@ -34,8 +36,8 @@ require ( require ( cloud.google.com/go/compute v1.20.1 // indirect cloud.google.com/go/compute/metadata v0.2.3 // indirect - cloud.google.com/go/iam v0.13.0 // indirect - cloud.google.com/go/kms v1.10.1 // indirect + cloud.google.com/go/iam v1.1.1 // indirect + cloud.google.com/go/kms v1.12.1 // indirect cloud.google.com/go/storage v1.30.1 // indirect code.gitea.io/sdk/gitea v0.15.1 // indirect github.com/AlekSi/pointer v1.2.0 // indirect @@ -63,7 +65,6 @@ require ( github.com/ProtonMail/go-crypto v0.0.0-20210512092938-c05353c2d58c // indirect github.com/acomagu/bufpipe v1.0.3 // indirect github.com/alecthomas/jsonschema v0.0.0-20211209230136-e2b41affa5c1 // indirect - github.com/apex/log v1.9.0 // indirect github.com/atc0005/go-teams-notify/v2 v2.6.1 // indirect github.com/aws/aws-sdk-go v1.40.34 // indirect github.com/aws/aws-sdk-go-v2 v1.9.0 // indirect @@ -206,19 +207,19 @@ require ( go.opentelemetry.io/proto/otlp v0.20.0 // indirect go.uber.org/multierr v1.9.0 // indirect gocloud.dev v0.24.0 // indirect - golang.org/x/crypto v0.10.0 // indirect - golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df // indirect - golang.org/x/net v0.11.0 // indirect + golang.org/x/crypto v0.11.0 // indirect + golang.org/x/exp v0.0.0-20230801115018-d63ba01acd4b // indirect + golang.org/x/net v0.13.0 // indirect golang.org/x/oauth2 v0.9.0 // indirect golang.org/x/sync v0.3.0 // indirect - golang.org/x/sys v0.9.0 // indirect - golang.org/x/text v0.10.0 // indirect + golang.org/x/sys v0.10.0 // indirect + golang.org/x/text v0.11.0 // indirect golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac // indirect golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect google.golang.org/appengine v1.6.7 // indirect - google.golang.org/genproto v0.0.0-20230629202037-9506855d4529 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20230629202037-9506855d4529 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20230629202037-9506855d4529 // indirect + google.golang.org/genproto v0.0.0-20230726155614-23370e0ffb3e // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20230706204954-ccb25ca9f130 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20230731193218-e0aa005b6bdf // indirect gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect gopkg.in/mail.v2 v2.3.1 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect diff --git a/go.sum b/go.sum index 4c537887..09fd7ed5 100644 --- a/go.sum +++ b/go.sum @@ -31,8 +31,8 @@ cloud.google.com/go v0.92.2/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+Y cloud.google.com/go v0.92.3/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+YI= cloud.google.com/go v0.93.3/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+YI= cloud.google.com/go v0.94.0/go.mod h1:qAlAugsXlC+JWO+Bke5vCtc9ONxjQT3drlTTnAplMW4= -cloud.google.com/go v0.110.3 h1:wwearW+L7sAPSomPIgJ3bVn6Ck00HGQnn5HMLwf0azo= -cloud.google.com/go v0.110.3/go.mod h1:+EYjdK8e5RME/VY/qLCAtuyALQ9q67dvuum8i+H5xsI= +cloud.google.com/go v0.110.4 h1:1JYyxKMN9hd5dR2MYTPWkGUgcoxVVhg0LKNKEo0qvmk= +cloud.google.com/go v0.110.4/go.mod h1:+EYjdK8e5RME/VY/qLCAtuyALQ9q67dvuum8i+H5xsI= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= @@ -47,11 +47,11 @@ cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7 cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= cloud.google.com/go/firestore v1.5.0/go.mod h1:c4nNYR1qdq7eaZ+jSc5fonrQN2k3M7sWATcYTiakjEo= -cloud.google.com/go/iam v0.13.0 h1:+CmB+K0J/33d0zSQ9SlFWUeCCEn5XJA0ZMZ3pHE9u8k= -cloud.google.com/go/iam v0.13.0/go.mod h1:ljOg+rcNfzZ5d6f1nAUJ8ZIxOaZUVoS14bKCtaLZ/D0= +cloud.google.com/go/iam v1.1.1 h1:lW7fzj15aVIXYHREOqjRBV9PsH0Z6u8Y46a1YGvQP4Y= +cloud.google.com/go/iam v1.1.1/go.mod h1:A5avdyVL2tCppe4unb0951eI9jreack+RJ0/d+KUZOU= cloud.google.com/go/kms v0.1.0/go.mod h1:8Qp8PCAypHg4FdmlyW1QRAv09BGQ9Uzh7JnmIZxPk+c= -cloud.google.com/go/kms v1.10.1 h1:7hm1bRqGCA1GBRQUrp831TwJ9TWhP+tvLuP497CQS2g= -cloud.google.com/go/kms v1.10.1/go.mod h1:rIWk/TryCkR59GMC3YtHtXeLzd634lBbKenvyySAyYI= +cloud.google.com/go/kms v1.12.1 h1:xZmZuwy2cwzsocmKDOPu4BL7umg8QXagQx6fKVmf45U= +cloud.google.com/go/kms v1.12.1/go.mod h1:c9J991h5DTl+kg7gi3MYomh12YEENGrf48ee/N/2CDM= cloud.google.com/go/monitoring v0.1.0/go.mod h1:Hpm3XfzJv+UTiXzCG5Ffp0wijzHTC7Cv4eR7o3x/fEE= cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= @@ -319,6 +319,8 @@ github.com/fxamacker/cbor/v2 v2.4.0/go.mod h1:TA1xS00nchWmaBnEIxPSE5oHLuJBAVvqrt github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M= +github.com/glendc/go-external-ip v0.1.0 h1:iX3xQ2Q26atAmLTbd++nUce2P5ht5P4uD4V7caSY/xg= +github.com/glendc/go-external-ip v0.1.0/go.mod h1:CNx312s2FLAJoWNdJWZ2Fpf5O4oLsMFwuYviHjS4uJE= github.com/gliderlabs/ssh v0.2.2 h1:6zsha5zo/TWhRhwqCD3+EarCAgZ2yN28ipRnGPnwkI0= github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= github.com/go-acme/lego v2.7.2+incompatible/go.mod h1:yzMNe9CasVUhkquNvti5nAtPmG94USbYxYrZfTkIn0M= @@ -754,12 +756,12 @@ github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI= -github.com/plgd-dev/device/v2 v2.2.1-0.20230602173355-efc3a81993c3 h1:whe0TFMRCL3rNkS2vuCj65oW/ukYjiMPhHvS3xqNUUc= -github.com/plgd-dev/device/v2 v2.2.1-0.20230602173355-efc3a81993c3/go.mod h1:5PhpR7Gig3gn+hGZBIpyyAKtn7zaIwg5uQBMX3ST9dU= +github.com/plgd-dev/device/v2 v2.2.1-0.20230803141537-60c77f6f4864 h1:56Qovnaj/dOjn9UK/m50/fUMidqUF/xiGU0YuqCr8eM= +github.com/plgd-dev/device/v2 v2.2.1-0.20230803141537-60c77f6f4864/go.mod h1:lv93I6qbzHIDtXYdNBUkfTxpa7CdRia92OfVnjdU6ls= github.com/plgd-dev/go-coap/v2 v2.0.4-0.20200819112225-8eb712b901bc/go.mod h1:+tCi9Q78H/orWRtpVWyBgrr4vKFo2zYtbbxUllerBp4= github.com/plgd-dev/go-coap/v2 v2.4.1-0.20210517130748-95c37ac8e1fa/go.mod h1:rA7fc7ar+B/qa+Q0hRqv7yj/EMtIlmo1l7vkQGSrHPU= -github.com/plgd-dev/go-coap/v3 v3.1.3 h1:zE2k8iFojeWdpZeROY16qk4nmvlE49AwXMzL+/Cw5ZE= -github.com/plgd-dev/go-coap/v3 v3.1.3/go.mod h1:BnqQC1zU8tcjHAeCgqD5dpyrLKiSy/43q7JAO9Oj6xA= +github.com/plgd-dev/go-coap/v3 v3.1.4-0.20230802114331-351cd00bab2d h1:dHabSkw9dQxuphK8s0lkm3EMlkyPo5C3ESQKbVrKT18= +github.com/plgd-dev/go-coap/v3 v3.1.4-0.20230802114331-351cd00bab2d/go.mod h1:qJihIo3PI5NCzvMUE4eE0/T38PUw6Zg0JDq50Z8bQ2k= github.com/plgd-dev/hub/v2 v2.7.19 h1:W4Sq+IDTjvcMpQEu0nfe84t2bLCqk95SKj83tr80Szg= github.com/plgd-dev/hub/v2 v2.7.19/go.mod h1:2zKO5CAWWa2aKhbC1UAtG+urKeg/aoBTMfafdyqq/ww= github.com/plgd-dev/kit v0.0.0-20200819113605-d5fcf3e94f63/go.mod h1:Yl9zisyXfPdtP9hTWlJqjJYXmgU/jtSDKttz9/CeD90= @@ -977,8 +979,8 @@ golang.org/x/crypto v0.0.0-20220314234659-1baeb1ce4c0b/go.mod h1:IxCIyHEi3zRg3s0 golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE= golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0= -golang.org/x/crypto v0.10.0 h1:LKqV2xt9+kDzSTfOhx4FrkEBcMrAgHSYgzywV9zcGmM= -golang.org/x/crypto v0.10.0/go.mod h1:o4eNf7Ede1fv+hwOwZsTHl9EsPFO6q6ZvYR8vYfY45I= +golang.org/x/crypto v0.11.0 h1:6Ewdq3tDic1mg5xRO4milcWCfMVQhI4NkqWWvqejpuA= +golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -989,8 +991,8 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0 golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= -golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df h1:UA2aFVmmsIlefxMk29Dp2juaUSth8Pyn3Tq5Y5mJGME= -golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc= +golang.org/x/exp v0.0.0-20230801115018-d63ba01acd4b h1:r+vk0EmXNmekl0S0BascoeeoHk/L7wmaW2QF90K+kYI= +golang.org/x/exp v0.0.0-20230801115018-d63ba01acd4b/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= @@ -1073,8 +1075,8 @@ golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= -golang.org/x/net v0.11.0 h1:Gi2tvZIJyBtO9SDr1q9h5hEQCp/4L2RQ+ar0qjx2oNU= -golang.org/x/net v0.11.0/go.mod h1:2L/ixqYpgIVXmeoSA/4Lu7BzTG4KIyPIryS4IsOd1oQ= +golang.org/x/net v0.13.0 h1:Nvo8UFsZ8X3BhAC9699Z1j7XQ3rsZnUUm7jfBEk1ueY= +golang.org/x/net v0.13.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -1187,14 +1189,14 @@ golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.9.0 h1:KS/R3tvhPqvJvwcKfnBHJwwthS11LRhmM5D59eEXa0s= -golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA= +golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= -golang.org/x/term v0.9.0 h1:GRRCnKYhdQrD8kfRAdQ6Zcw1P0OcELxGLKJvtjVMZ28= +golang.org/x/term v0.10.0 h1:3R7pNqamzBraeqj/Tj8qt1aQ2HpmlC+Cx/qL/7hn4/c= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -1207,8 +1209,8 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= -golang.org/x/text v0.10.0 h1:UpjohKhiEgNc0CSauXmwYftY1+LlaC75SJwh0SgCX58= -golang.org/x/text v0.10.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.11.0 h1:LAntKIrcmeSKERyiOh0XMV39LXS8IE9UL2yP7+f5ij4= +golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -1389,12 +1391,12 @@ google.golang.org/genproto v0.0.0-20210821163610-241b8fcbd6c8/go.mod h1:eFjDcFEc google.golang.org/genproto v0.0.0-20210825212027-de86158e7fda/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= google.golang.org/genproto v0.0.0-20210828152312-66f60bf46e71/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= google.golang.org/genproto v0.0.0-20210831024726-fe130286e0e2/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= -google.golang.org/genproto v0.0.0-20230629202037-9506855d4529 h1:9JucMWR7sPvCxUFd6UsOUNmA5kCcWOfORaT3tpAsKQs= -google.golang.org/genproto v0.0.0-20230629202037-9506855d4529/go.mod h1:xZnkP7mREFX5MORlOPEzLMr+90PPZQ2QWzrVTWfAq64= -google.golang.org/genproto/googleapis/api v0.0.0-20230629202037-9506855d4529 h1:s5YSX+ZH5b5vS9rnpGymvIyMpLRJizowqDlOuyjXnTk= -google.golang.org/genproto/googleapis/api v0.0.0-20230629202037-9506855d4529/go.mod h1:vHYtlOoi6TsQ3Uk2yxR7NI5z8uoV+3pZtR4jmHIkRig= -google.golang.org/genproto/googleapis/rpc v0.0.0-20230629202037-9506855d4529 h1:DEH99RbiLZhMxrpEJCZ0A+wdTe0EOgou/poSLx9vWf4= -google.golang.org/genproto/googleapis/rpc v0.0.0-20230629202037-9506855d4529/go.mod h1:66JfowdXAEgad5O9NnYcsNPLCPZJD++2L9X0PCMODrA= +google.golang.org/genproto v0.0.0-20230726155614-23370e0ffb3e h1:xIXmWJ303kJCuogpj0bHq+dcjcZHU+XFyc1I0Yl9cRg= +google.golang.org/genproto v0.0.0-20230726155614-23370e0ffb3e/go.mod h1:0ggbjUrZYpy1q+ANUS30SEoGZ53cdfwtbuG7Ptgy108= +google.golang.org/genproto/googleapis/api v0.0.0-20230706204954-ccb25ca9f130 h1:XVeBY8d/FaK4848myy41HBqnDwvxeV3zMZhwN1TvAMU= +google.golang.org/genproto/googleapis/api v0.0.0-20230706204954-ccb25ca9f130/go.mod h1:mPBs5jNgx2GuQGvFwUvVKqtn6HsUw9nP64BedgvqEsQ= +google.golang.org/genproto/googleapis/rpc v0.0.0-20230731193218-e0aa005b6bdf h1:guOdSPaeFgN+jEJwTo1dQ71hdBm+yKSCCKuTRkJzcVo= +google.golang.org/genproto/googleapis/rpc v0.0.0-20230731193218-e0aa005b6bdf/go.mod h1:zBEcrKX2ZOcEkHWxBPAIvYUWOKKMIhYcmNiUIu2ji3I= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= @@ -1421,8 +1423,8 @@ google.golang.org/grpc v1.39.0/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnD google.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ= -google.golang.org/grpc v1.56.1 h1:z0dNfjIl0VpaZ9iSVjA6daGatAYwPGstTjt5vkRMFkQ= -google.golang.org/grpc v1.56.1/go.mod h1:I9bI3vqKfayGqPUAwGdOSu7kt6oIJLixfffKrpXqQ9s= +google.golang.org/grpc v1.57.0 h1:kfzNeI/klCGD2YPMUlaGNT3pxvYfga7smW3Vth8Zsiw= +google.golang.org/grpc v1.57.0/go.mod h1:Sd+9RMTACXwmub0zcNY2c4arhtrbBYD1AUHI/dt16Mo= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.3.0 h1:rNBFJjBCOgVr9pWD7rs/knKL4FRTKgpZmsRfV214zcA= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.3.0/go.mod h1:Dk1tviKTvMCz5tvh7t+fh94dhmQVHuCt2OzJB3CTW9Y= diff --git a/pkg/net/listener/tls/config.go b/pkg/net/listener/tls/config.go new file mode 100644 index 00000000..f1e4136a --- /dev/null +++ b/pkg/net/listener/tls/config.go @@ -0,0 +1,23 @@ +package tls + +import ( + "fmt" + "net" + + "github.com/plgd-dev/hub/v2/pkg/security/certManager/server" +) + +type Config struct { + Addr string `yaml:"address" json:"address"` + TLS server.Config `yaml:"tls" json:"tls"` +} + +func (c *Config) Validate() error { + if _, err := net.ResolveTCPAddr("tcp", c.Addr); err != nil { + return fmt.Errorf("address('%v') - %w", c.Addr, err) + } + if err := c.TLS.Validate(); err != nil { + return fmt.Errorf("tls.%w", err) + } + return nil +} diff --git a/pkg/net/listener/tls/listener.go b/pkg/net/listener/tls/listener.go new file mode 100644 index 00000000..add5fe0a --- /dev/null +++ b/pkg/net/listener/tls/listener.go @@ -0,0 +1,155 @@ +package tls + +import ( + "bufio" + "crypto/tls" + "fmt" + "net" + "time" + + "github.com/plgd-dev/hub/v2/pkg/fsnotify" + "github.com/plgd-dev/hub/v2/pkg/log" + "github.com/plgd-dev/hub/v2/pkg/security/certManager/server" +) + +type ConnectionType uint8 + +const ( + ConnectionTypeHTTP ConnectionType = iota + ConnectionTypeTLS +) + +type Conn struct { + net.Conn + ConnectionType ConnectionType + buf *bufio.Reader +} + +func (c *Conn) Read(b []byte) (int, error) { + return c.buf.Read(b) +} + +type chConn struct { + c net.Conn + err error +} + +type SplitListener struct { + net.Listener + config *tls.Config + cons chan chConn + logger log.Logger +} + +// tlsRecordHeaderLooksLikeHTTP reports whether a TLS record header +// looks like it might've been a misdirected plaintext HTTP request. +func tlsRecordHeaderLooksLikeHTTP(hdr []byte) bool { + if len(hdr) < 5 { + return false + } + switch string(hdr[:5]) { + case "GET /", "HEAD ", "POST ", "PUT /", "OPTIO": + return true + } + return false +} + +func (l *SplitListener) prepareConnection(c net.Conn) { + // buffer reads on our conn + bconn := &Conn{ + Conn: c, + buf: bufio.NewReader(c), + } + n := 5 + _ = c.SetReadDeadline(time.Now().Add(1 * time.Second)) + hdr, err := bconn.buf.Peek(n) + _ = c.SetReadDeadline(time.Time{}) + if err != nil { + l.logger.Warnf("closing connection for error while peeking for first 5bytes in 1sec: %v", err) + _ = bconn.Close() + return + } + + // I don't remember what the TLS handshake looks like, + // but this works as a POC + if tlsRecordHeaderLooksLikeHTTP(hdr) { + bconn.ConnectionType = ConnectionTypeHTTP + l.cons <- chConn{c: bconn, err: nil} + return + } + bconn.ConnectionType = ConnectionTypeTLS + l.cons <- chConn{c: tls.Server(bconn, l.config), err: nil} +} + +func (l *SplitListener) run() { + for { + c, err := l.Listener.Accept() + if err != nil { + l.cons <- chConn{nil, err} + return + } + go l.prepareConnection(c) + } +} + +func (l *SplitListener) Accept() (net.Conn, error) { + d := <-l.cons + return d.c, d.err +} + +func NewSplitListener(l net.Listener, config *tls.Config, logger log.Logger) net.Listener { + sl := &SplitListener{ + Listener: l, + config: config, + cons: make(chan chConn), + logger: logger, + } + go sl.run() + return sl +} + +// Server handles gRPC requests to the service. +type Server struct { + listener net.Listener + closeFunc []func() +} + +// NewServer instantiates a listen server. +// When passing addr with an unspecified port or ":", use Addr(). +func New(config Config, fileWatcher *fsnotify.Watcher, logger log.Logger) (*Server, error) { + certManager, err := server.New(config.TLS, fileWatcher, logger) + if err != nil { + return nil, fmt.Errorf("cannot create cert manager %w", err) + } + + lis, err := net.Listen("tcp", config.Addr) + if err != nil { + certManager.Close() + return nil, fmt.Errorf("listening failed: %w", err) + } + splitListener := NewSplitListener(lis, certManager.GetTLSConfig(), logger) + + return &Server{listener: splitListener, closeFunc: []func(){certManager.Close}}, nil +} + +// AddCloseFunc adds a function to be called by the Close method. +// This eliminates the need for wrapping the Server. +func (s *Server) AddCloseFunc(f func()) { + s.closeFunc = append(s.closeFunc, f) +} + +func (s *Server) Close() error { + err := s.listener.Close() + for _, f := range s.closeFunc { + f() + } + return err +} + +func (s *Server) Accept() (net.Conn, error) { + return s.listener.Accept() +} + +func (s *Server) Addr() net.Addr { + return s.listener.Addr() +} diff --git a/service/config/config.go b/service/config/config.go index 1646c0a3..75fbc290 100644 --- a/service/config/config.go +++ b/service/config/config.go @@ -120,7 +120,7 @@ func (c Config) Store() error { return Store(c, c.configPath) } -func DefaultConfig(uiDirectory string) Config { +func DefaultConfig(directory string) Config { logCfg := log.MakeDefaultConfig() logCfg.Encoding = "console" return Config{ @@ -128,11 +128,11 @@ func DefaultConfig(uiDirectory string) Config { APIs: APIsConfig{ HTTP: HTTPConfig{ Enabled: true, - Config: http.DefaultConfig(uiDirectory), + Config: http.DefaultConfig(directory), }, GRPC: GRPCConfig{ Enabled: true, - Config: grpc.DefaultConfig(), + Config: grpc.DefaultConfig(directory), }, }, Clients: ClientsConfig{ diff --git a/service/config/grpc/config.go b/service/config/grpc/config.go index f22bf37c..525fcd6c 100644 --- a/service/config/grpc/config.go +++ b/service/config/grpc/config.go @@ -17,27 +17,34 @@ package grpc import ( + "path" "time" "github.com/plgd-dev/client-application/pb" "github.com/plgd-dev/client-application/pkg/net/grpc/server" grpcServer "github.com/plgd-dev/hub/v2/pkg/net/grpc/server" + certManagerServer "github.com/plgd-dev/hub/v2/pkg/security/certManager/server" ) type Config = server.Config type ServiceInformation = pb.BuildInfo -var defaultConfig = Config{ - Addr: ":8081", - TLS: server.TLSConfig{ - Enabled: false, - }, - EnforcementPolicy: grpcServer.EnforcementPolicyConfig{ - MinTime: 5 * time.Minute, - }, -} - -func DefaultConfig() Config { - return defaultConfig +func DefaultConfig(directory string) Config { + return Config{ + Addr: ":8081", + TLS: server.TLSConfig{ + Enabled: true, + Config: certManagerServer.Config{ + // we use the same cert for CA because certManagerServer.Config doesn't allow nil values + CAPool: path.Join(directory, "certs", "crt.pem"), + KeyFile: path.Join(directory, "certs", "key.pem"), + CertFile: path.Join(directory, "certs", "crt.pem"), + ClientCertificateRequired: false, + }, + }, + EnforcementPolicy: grpcServer.EnforcementPolicyConfig{ + MinTime: 5 * time.Minute, + }, + } } diff --git a/service/config/http/config.go b/service/config/http/config.go index fe4834a8..fca229a6 100644 --- a/service/config/http/config.go +++ b/service/config/http/config.go @@ -18,10 +18,12 @@ package http import ( "fmt" + "path" "time" "github.com/plgd-dev/client-application/pkg/net/listener" "github.com/plgd-dev/hub/v2/pkg/net/http/server" + certManagerServer "github.com/plgd-dev/hub/v2/pkg/security/certManager/server" ) type CORSConfig struct { @@ -57,12 +59,19 @@ func (c *Config) Validate() error { return c.Config.Validate() } -func DefaultConfig(uiDirectory string) Config { +func DefaultConfig(directory string) Config { return Config{ Config: listener.Config{ Addr: ":8080", TLS: listener.TLSConfig{ - Enabled: false, + Enabled: true, + Config: certManagerServer.Config{ + // we use the same cert for CA because certManagerServer.Config doesn't allow nil values + CAPool: path.Join(directory, "certs", "crt.pem"), + KeyFile: path.Join(directory, "certs", "key.pem"), + CertFile: path.Join(directory, "certs", "crt.pem"), + ClientCertificateRequired: false, + }, }, }, CORS: CORSConfig{ @@ -72,7 +81,7 @@ func DefaultConfig(uiDirectory string) Config { }, UI: UIConfig{ Enabled: true, - Directory: uiDirectory, + Directory: path.Join(directory, "www"), }, Server: server.Config{ ReadTimeout: time.Second * 8, diff --git a/service/http/service.go b/service/http/service.go index 9d3f0f45..2e109309 100644 --- a/service/http/service.go +++ b/service/http/service.go @@ -20,6 +20,7 @@ import ( "context" "errors" "fmt" + "net" "net/http" "net/http/httptest" "regexp" @@ -31,6 +32,7 @@ import ( "github.com/grpc-ecosystem/grpc-gateway/v2/runtime" "github.com/plgd-dev/client-application/pb" "github.com/plgd-dev/client-application/pkg/net/listener" + "github.com/plgd-dev/client-application/pkg/net/listener/tls" configHttp "github.com/plgd-dev/client-application/service/config/http" "github.com/plgd-dev/client-application/service/grpc" "github.com/plgd-dev/hub/v2/http-gateway/serverMux" @@ -105,21 +107,48 @@ func createAuthFunc(config configHttp.Config, clientApplicationServer *grpc.Clie return auth } -// New creates new HTTP service -func New(ctx context.Context, serviceName string, config configHttp.Config, clientApplicationServer *grpc.ClientApplicationServer, fileWatcher *fsnotify.Watcher, logger log.Logger, tracerProvider trace.TracerProvider) (*Service, error) { - listener, err := listener.New(config.Config, fileWatcher, logger) - if err != nil { - return nil, fmt.Errorf("cannot create grpc server: %w", err) - } +type contextKey struct { + key string +} - ch := new(inprocgrpc.Channel) - pb.RegisterClientApplicationServer(ch, clientApplicationServer) - grpcClient := pb.NewClientApplicationClient(ch) +var connContextKey = &contextKey{"http-conn"} - auth := createAuthFunc(config, clientApplicationServer) - mux := serverMux.New() - r := serverMux.NewRouter(queryCaseInsensitive, auth) +func saveConnInContext(ctx context.Context, c net.Conn) context.Context { + return context.WithValue(ctx, connContextKey, c) +} + +func getTLSConn(r *http.Request) (*tls.Conn, bool) { + c, ok := r.Context().Value(connContextKey).(*tls.Conn) + return c, ok +} +func newListener(config configHttp.Config, fileWatcher *fsnotify.Watcher, logger log.Logger) (listener.Listener, error) { + if config.Config.TLS.Enabled { + return tls.New(tls.Config{ + Addr: config.Config.Addr, + TLS: config.Config.TLS.Config, + }, fileWatcher, logger) + } else { + return listener.New(config.Config, fileWatcher, logger) + } +} + +func wrapHandler(handler http.Handler, serviceName string, tracerProvider trace.TracerProvider) http.Handler { + h := kitNetHttp.OpenTelemetryNewHandler(handler, serviceName, tracerProvider) + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if c, ok := getTLSConn(r); ok && c.ConnectionType == tls.ConnectionTypeHTTP { + http.Redirect(w, r, "https://"+r.Host+r.RequestURI, http.StatusMovedPermanently) + if f, ok := w.(http.Flusher); ok { + f.Flush() + } + _ = c.Close() + return + } + h.ServeHTTP(w, r) + }) +} + +func newCORSHandler(config configHttp.Config, r *router.Router) http.Handler { corsOptions := make([]handlers.CORSOption, 0, 5) corsOptions = append(corsOptions, handlers.AllowedHeaders(config.CORS.AllowedHeaders)) corsOptions = append(corsOptions, handlers.AllowedOrigins(config.CORS.AllowedOrigins)) @@ -127,18 +156,10 @@ func New(ctx context.Context, serviceName string, config configHttp.Config, clie if config.CORS.AllowCredentials { corsOptions = append(corsOptions, handlers.AllowCredentials()) } - handler := handlers.CORS(corsOptions...)(r) + return handlers.CORS(corsOptions...)(r) +} - // register grpc-proxy handler - if err := pb.RegisterClientApplicationHandlerClient(context.Background(), mux, grpcClient); err != nil { - _ = listener.Close() - return nil, fmt.Errorf("failed to register grpc-gateway handler: %w", err) - } - requestHandler := &RequestHandler{mux: mux, clientApplicationServer: clientApplicationServer, config: config} - r.PathPrefix(Devices).Methods(http.MethodPut).MatcherFunc(resourceMatcher).HandlerFunc(requestHandler.updateResource) - r.PathPrefix(Devices).Methods(http.MethodPost).MatcherFunc(resourceMatcher).HandlerFunc(requestHandler.createResource) - r.PathPrefix(ApiV1).Handler(mux) - r.PathPrefix(WellKnown).Handler(mux) +func setUIHandlers(config configHttp.Config, r *router.Router) { // serve www directory if config.UI.Enabled { fs := http.FileServer(http.Dir(config.UI.Directory)) @@ -159,18 +180,49 @@ func New(ctx context.Context, serviceName string, config configHttp.Config, clie } })) } +} + +// New creates new HTTP service +func New(ctx context.Context, serviceName string, config configHttp.Config, clientApplicationServer *grpc.ClientApplicationServer, fileWatcher *fsnotify.Watcher, logger log.Logger, tracerProvider trace.TracerProvider) (*Service, error) { + lis, err := newListener(config, fileWatcher, logger) + if err != nil { + return nil, fmt.Errorf("cannot create grpc server: %w", err) + } + + ch := new(inprocgrpc.Channel) + pb.RegisterClientApplicationServer(ch, clientApplicationServer) + grpcClient := pb.NewClientApplicationClient(ch) + + auth := createAuthFunc(config, clientApplicationServer) + mux := serverMux.New() + r := serverMux.NewRouter(queryCaseInsensitive, auth) + handler := newCORSHandler(config, r) + + // register grpc-proxy handler + if err := pb.RegisterClientApplicationHandlerClient(context.Background(), mux, grpcClient); err != nil { + _ = lis.Close() + return nil, fmt.Errorf("failed to register grpc-gateway handler: %w", err) + } + requestHandler := &RequestHandler{mux: mux, clientApplicationServer: clientApplicationServer, config: config} + r.PathPrefix(Devices).Methods(http.MethodPut).MatcherFunc(resourceMatcher).HandlerFunc(requestHandler.updateResource) + r.PathPrefix(Devices).Methods(http.MethodPost).MatcherFunc(resourceMatcher).HandlerFunc(requestHandler.createResource) + r.PathPrefix(ApiV1).Handler(mux) + r.PathPrefix(WellKnown).Handler(mux) + + setUIHandlers(config, r) httpServer := &http.Server{ - Handler: kitNetHttp.OpenTelemetryNewHandler(handler, serviceName, tracerProvider), + Handler: wrapHandler(handler, serviceName, tracerProvider), ReadTimeout: config.Server.ReadTimeout, ReadHeaderTimeout: config.Server.ReadHeaderTimeout, WriteTimeout: config.Server.WriteTimeout, IdleTimeout: config.Server.IdleTimeout, + ConnContext: saveConnInContext, } return &Service{ httpServer: httpServer, - listener: listener, + listener: lis, }, nil } From 810c1ae7a1ddedd15c11a224b5c12bfe4dd4bf7b Mon Sep 17 00:00:00 2001 From: Jozef Kralik Date: Sat, 5 Aug 2023 21:12:21 +0200 Subject: [PATCH 2/2] fix comment --- cmd/generateSelfSigned.go | 2 +- pkg/net/listener/tls/listener.go | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/cmd/generateSelfSigned.go b/cmd/generateSelfSigned.go index d29137cc..3bad27e1 100644 --- a/cmd/generateSelfSigned.go +++ b/cmd/generateSelfSigned.go @@ -133,7 +133,7 @@ func generateSelfSigned(certFile, keyFile string) error { CommonName: cn, }, NotBefore: time.Now(), - NotAfter: time.Now().Add(time.Hour * 24 * 356), + NotAfter: time.Now().Add(time.Hour * 24 * 365), KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign, ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth}, diff --git a/pkg/net/listener/tls/listener.go b/pkg/net/listener/tls/listener.go index add5fe0a..9f99968c 100644 --- a/pkg/net/listener/tls/listener.go +++ b/pkg/net/listener/tls/listener.go @@ -54,6 +54,8 @@ func tlsRecordHeaderLooksLikeHTTP(hdr []byte) bool { return false } +// prepareConnection accepts connections asynchronously to avoid blocking subsequent connection attempts due to potential blocking caused by c.Peek. +// The use of asynchronous connection acceptance ensures that the program can continue accepting incoming connections while waiting for c.Peek to complete func (l *SplitListener) prepareConnection(c net.Conn) { // buffer reads on our conn bconn := &Conn{ @@ -69,9 +71,7 @@ func (l *SplitListener) prepareConnection(c net.Conn) { _ = bconn.Close() return } - - // I don't remember what the TLS handshake looks like, - // but this works as a POC + // The first 5 bytes is a TLS header, so we can use it to check for HTTP if tlsRecordHeaderLooksLikeHTTP(hdr) { bconn.ConnectionType = ConnectionTypeHTTP l.cons <- chConn{c: bconn, err: nil}