Author: Paolo Lulli <paolo@lulli.net>
Add show perms endpoint
server/.goreleaser.yaml | 3 server/config/config.go | 8 - server/db/queries.go | 37 ++++++++ server/go.mod | 2 server/go.sum | 3 server/main.go | 6 + server/model/models.go | 16 +++ server/rest/rest-permissions.go | 38 ++++++++ server/service-rest.go | 2 server/tcp/tcpserver.go | 160 +++++++++++++++++++++++++++++++++++ server/tlv/tlvpackets.go | 54 +++++++++++
diff --git a/server/.goreleaser.yaml b/server/.goreleaser.yaml index 260042f009333556eb9ad3e4110e994ac810ede4..d8f19a8e5da34cc78bc2e962f28d53023a4f0843 100644 --- a/server/.goreleaser.yaml +++ b/server/.goreleaser.yaml @@ -28,6 +28,7 @@ Yats Server - Yet Another Time Serie formats: - rpm - deb + - apk archives: - format: tar.gz @@ -51,4 +52,4 @@ sort: asc filters: exclude: - "^docs:" - - "^test:" \ No newline at end of file + - "^test:" diff --git a/server/config/config.go b/server/config/config.go index eb79fec7366c8746a75ae7184a2a46688b960c34..4e63b02f697cb7ec37185035e20483cf2ebb9d8a 100644 --- a/server/config/config.go +++ b/server/config/config.go @@ -11,8 +11,6 @@ package config import ( - "fmt" - "github.com/tkanos/gonfig" ) @@ -30,11 +28,6 @@ "weekly": Weekly, "monthly": Monthly, } -func main() { - interval := archFrequency["daily"] - fmt.Println(interval) //print the enum value -} - type Configuration struct { DbUsername string `json:"databaseUsername"` DbPassword string `json:"databasePassword"` @@ -44,6 +37,7 @@ DbName string `json:"databaseSchema"` RestAddress string `json:"restAddress"` GrpcAddress string `json:"grpcAddress"` + TcpAddress string `json:"tcpAddress"` ArchiveFrequency string `json:"archiveFrequency"` ArchiveDirectory string `json:"archiveDirectory"` diff --git a/server/db/queries.go b/server/db/queries.go index 2932aedc0d779bbbef916a60a2e4a3ddaf21510f..5d3a2c49fc7a327d920230cb05ed1d41862764da 100644 --- a/server/db/queries.go +++ b/server/db/queries.go @@ -17,6 +17,43 @@ "time" "yats-server/model" ) +func GetExternalSources(session *gocql.Session, idClient string) []model.ExternalSource { + var sources []model.ExternalSource + m := map[string]interface{}{} + + q := fmt.Sprintf("SELECT app,type,name from sources where id_client='%s'", idClient) + fmt.Println(q) + iter := session.Query(q).Iter() + for iter.MapScan(m) { + sources = append(sources, model.ExternalSource{ + Name: m["name"].(string), + App: m["app"].(string), + Type: m["type"].(string), + }) + + m = map[string]interface{}{} + } + return sources +} + +func GetAvailableMetrics(session *gocql.Session, idClient string) []model.AvailableMetric { + var metrics []model.AvailableMetric + m := map[string]interface{}{} + + q := fmt.Sprintf("SELECT name,description from metric_info where id_client='%s'", idClient) + fmt.Println(q) + iter := session.Query(q).Iter() + for iter.MapScan(m) { + metrics = append(metrics, model.AvailableMetric{ + Name: m["name"].(string), + Description: m["description"].(string), + }) + + m = map[string]interface{}{} + } + return metrics +} + func MetricsFrom(session *gocql.Session, idClient string, metricName string, fromTime string, limit int32) []model.MetricModel { var metrics []model.MetricModel m := map[string]interface{}{} diff --git a/server/go.mod b/server/go.mod index 938fec5831fec21f91fd666a4d44bb549aa93cc9..ab487ba5f4d59a678153f08878f14efb36d46aa3 100644 --- a/server/go.mod +++ b/server/go.mod @@ -5,7 +5,9 @@ require ( github.com/gin-gonic/gin v1.10.0 github.com/gocql/gocql v1.6.0 + github.com/pauloavelar/go-tlv v1.1.0 github.com/pd0mz/go-maidenhead v1.0.0 + github.com/pkg/errors v0.9.1 github.com/swaggo/files v1.0.1 github.com/swaggo/gin-swagger v1.6.0 github.com/swaggo/swag v1.16.3 diff --git a/server/go.sum b/server/go.sum index c4193f3be44eb3761e2d678253eeda7dba25f51d..b53dfe07271158987c156b87384f949f3b68c5fb 100644 --- a/server/go.sum +++ b/server/go.sum @@ -167,6 +167,8 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/pauloavelar/go-tlv v1.1.0 h1:xdDbtVt501KYQudQVGdsNlM3vQHLGMRyLXRTw2T03lA= +github.com/pauloavelar/go-tlv v1.1.0/go.mod h1:iF/18VEBGcZotmu6H6Z5DHIS9iRQYHSUJX3lxl47D3w= github.com/pborman/getopt v0.0.0-20180729010549-6fdd0a2c7117/go.mod h1:85jBQOZwpVEaDAr341tbn15RS4fCAsIst0qp7i8ex1o= github.com/pd0mz/go-maidenhead v1.0.0 h1:zl2AXA36LnmP5TDEfshM0fWi1mc08fNc6qhj7YD5xjw= github.com/pd0mz/go-maidenhead v1.0.0/go.mod h1:4Q+QSDCqWqlabstLGUVm47rAcL06nEEty2d3KzsTNMk= @@ -174,6 +176,7 @@ github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM= github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= github.com/pierrec/lz4/v4 v4.1.8 h1:ieHkV+i2BRzngO4Wd/3HGowuZStgq6QkPsD1eolNAO4= github.com/pierrec/lz4/v4 v4.1.8/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= diff --git a/server/main.go b/server/main.go index 3cef44af8e1054c066c14da115b488835db8ea8f..8d1b43da918050d0c9c4c63d704ab75e9e189b26 100644 --- a/server/main.go +++ b/server/main.go @@ -18,6 +18,7 @@ "yats-server/config" "yats-server/db" "yats-server/docs" "yats-server/grpc" + "yats-server/tcp" ) var configuration config.Configuration @@ -62,6 +63,11 @@ if configuration.GrpcAddress != "" { fmt.Printf("Starting GRPC Server on address: %s\n", configuration.GrpcAddress) go grpc.RunUnsecureYatsGrpcServer(configuration.GrpcAddress) + } + + if configuration.TcpAddress != "" { + fmt.Printf("Starting TCP Server on address: %s\n", configuration.TcpAddress) + go tcp.RunTcpServer(configuration) } fmt.Printf("Starting REST Server on address: %s\n", configuration.RestAddress) diff --git a/server/model/models.go b/server/model/models.go index 60caf49170bc31892e5319a83351519518a90a4a..6b664dcce129d9008c2093bb0c870dcd4f98e66d 100644 --- a/server/model/models.go +++ b/server/model/models.go @@ -79,3 +79,19 @@ From int64 `json:"from" parquet:"name=mtime, type=INT64, convertedtype=TIMESTAMP_MILLIS"` To int64 `json:"to" parquet:"name=mtime, type=INT64, convertedtype=TIMESTAMP_MILLIS"` SourceApplication string `json:"source" parquet:"name=source_application, type=BYTE_ARRAY, convertedtype=UTF8"` } + +type AvailableMetric struct { + Name string `json:"name"` + Description string `json:"description"` +} + +type ExternalSource struct { + Name string `json:"name"` + App string `json:"app"` + Type string `json:"type"` +} + +type PermsResponse struct { + Metrics []AvailableMetric `json:"metrics"` + Sources []ExternalSource `json:"sources"` +} diff --git a/server/rest/rest-permissions.go b/server/rest/rest-permissions.go new file mode 100644 index 0000000000000000000000000000000000000000..2d4077d49211c998617201992c18bd9348b99d9f --- /dev/null +++ b/server/rest/rest-permissions.go @@ -0,0 +1,38 @@ +/** + * Yats - yats + * + * This file is licensed under the Affero General Public License version 3 or + * later. See the COPYING file. + * + * @author Paolo Lulli <kevwe.com> + * @copyright Paolo Lulli 2024 + */ +package rest + +import ( + "github.com/gin-gonic/gin" + "net/http" + "yats-server/config" + "yats-server/db" +) + +// ShowPermissions godoc +// @Param X-SSL-Client-CN header string true "clientCN" +// @Summary Show available Metrics and Event for current user +// @Schemes +// @Description Show available Metrics and Event for current user +// @Tags Metrics +// @Produce json +// @Success 200 {string} []model.PermsResponse +// @Router /perms [get] +func ShowPermissions(cfg config.Configuration) gin.HandlerFunc { + return func(c *gin.Context) { + + clientCN := GetClientCN(c, cfg) + + availableMetrics := db.GetAvailableMetrics(db.Session, clientCN) + externalSources := db.GetExternalSources(db.Session, clientCN) + + c.IndentedJSON(http.StatusAccepted, gin.H{"metrics": availableMetrics, "externalSources": externalSources}) + } +} diff --git a/server/service-rest.go b/server/service-rest.go index 06a6fc9560a0768f3ea5720ea8934b994b2f4b81..a97ef26319273df2386044b650b393c88f22be35 100644 --- a/server/service-rest.go +++ b/server/service-rest.go @@ -39,6 +39,8 @@ router.GET("/event/:from", rest.GetEventsFrom(cfg)) router.POST("/position", rest.WritePosition(cfg)) + router.GET("/perms", rest.ShowPermissions(cfg)) + enableSwaggerEndpoint(router) if cfg.GrafanaActive == "true" { diff --git a/server/tcp/tcpserver.go b/server/tcp/tcpserver.go new file mode 100644 index 0000000000000000000000000000000000000000..c041a2782a97f85b40512ead4892149624e98ca7 --- /dev/null +++ b/server/tcp/tcpserver.go @@ -0,0 +1,160 @@ +/** + * Yats - yats + * + * This file is licensed under the Affero General Public License version 3 or + * later. See the COPYING file. + * + * @author Paolo Lulli <kevwe.com> + * @copyright Paolo Lulli 2024 + */ + +package tcp + +import ( + "crypto/tls" + "fmt" + "log" + "net" + "os" + "os/signal" + "sync" + "syscall" + "time" + "yats-server/config" + "yats-server/tlv" +) + +type tcpserver struct { + wg sync.WaitGroup + listener net.Listener + shutdown chan struct{} + connection chan net.Conn +} + +func createServer(cfg config.Configuration) (*tcpserver, error) { + + var listener net.Listener + var err error + + if cfg.TlsActive == "true" { + cert, err := tls.LoadX509KeyPair(cfg.TlsCertificate, cfg.TlsKeyFile) + if err != nil { + log.Fatalf("server: loadkeys: %s", err) + } + tlsconfig := tls.Config{Certificates: []tls.Certificate{cert}, InsecureSkipVerify: false} + //listener, err := tls.Dial("tcp", cfg.TcpAddress, &tlsconfig) + + listener, err = tls.Listen("tcp", cfg.TcpAddress, &tlsconfig) + } else { + listener, err = net.Listen("tcp", cfg.TcpAddress) + } + if err != nil { + return nil, fmt.Errorf("failed to listen on address %s: %w", cfg.TcpAddress, err) + } + + return &tcpserver{ + listener: listener, + shutdown: make(chan struct{}), + connection: make(chan net.Conn), + }, nil +} + +func (s *tcpserver) acceptConnections() { + defer s.wg.Done() + + for { + select { + case <-s.shutdown: + return + default: + conn, err := s.listener.Accept() + if err != nil { + continue + } + s.connection <- conn + } + } +} + +func (s *tcpserver) handleConnections() { + defer s.wg.Done() + + for { + select { + case <-s.shutdown: + return + case conn := <-s.connection: + go s.handleConnection(conn) + } + } +} + +func (s *tcpserver) handleConnection(conn net.Conn) { + defer conn.Close() + + tlscon, ok := conn.(*tls.Conn) + if ok { + state := tlscon.ConnectionState() + log.Println("Server: client public key is:") + clientCN := state.PeerCertificates[0].Subject.CommonName + fmt.Printf("clientCN: %s\n", clientCN) + } + + // Read incoming data + buf := make([]byte, 1024) + _, err := conn.Read(buf) + if err != nil { + fmt.Println(err) + return + } + + fmt.Printf("Received: %s", buf) + decodeTlv, bytes, err := tlv.DecodeTlv(buf) + if err == nil { + fmt.Printf("Decoded: tlv: %s with value: %x", decodeTlv, bytes) + } +} + +func (s *tcpserver) Start() { + s.wg.Add(2) + go s.acceptConnections() + go s.handleConnections() +} + +func (s *tcpserver) Stop() { + close(s.shutdown) + s.listener.Close() + + done := make(chan struct{}) + go func() { + s.wg.Wait() + close(done) + }() + + select { + case <-done: + return + case <-time.After(time.Second): + fmt.Println("Timed out waiting for connections to finish.") + return + } +} + +func RunTcpServer(cfg config.Configuration) { + s, err := createServer(cfg) + if err != nil { + fmt.Println(err) + os.Exit(1) + } + + s.Start() + + // Wait for a SIGINT or SIGTERM signal to gracefully shut down the tcpserver + sigChan := make(chan os.Signal, 1) + signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM) + <-sigChan + + fmt.Println("Shutting down tcpserver...") + s.Stop() + fmt.Println("tcpserver stopped.") +} diff --git a/server/tlv/tlvpackets.go b/server/tlv/tlvpackets.go new file mode 100644 index 0000000000000000000000000000000000000000..ea58be33405dacab4f782c568b890e5a6031c0b6 --- /dev/null +++ b/server/tlv/tlvpackets.go @@ -0,0 +1,54 @@ +package tlv + +import ( + "fmt" + "github.com/pkg/errors" + + "github.com/pauloavelar/go-tlv/tlv" +) + +func GetTlvString(data []byte) (string, error) { + n, _, err := tlv.DecodeSingle(data) + if err != nil { + return "", errors.WithStack(err) + } + + return n.String(), nil +} + +func GetNodes(data []byte) (tlv.Nodes, error) { + n, _, err := tlv.DecodeSingle(data) + if err != nil { + return nil, errors.WithStack(err) + } + + return n.GetNodes() +} + +func DecodeTlv(data []byte) (tlv.Tag, []byte, error) { + n, _, err := tlv.DecodeSingle(data) + if err != nil { + return 0, nil, errors.WithStack(err) + } + return n.Tag, n.Value, nil +} + +func example() { + data := []byte{0xF0, 0x02, 0x00, 0x01, 0xFF} + + n, read, err := tlv.DecodeSingle(data) + if err != nil { + panic(err) // invalid payload length vs bytes available + } + + fmt.Printf("Read: %d", read) + + n.String() // returns a base64 representation of the raw message + n.GetNodes() // parses the value as TLV and returns a Nodes structure (or error) + n.GetUint8() // parses the value as uint8 (returns error if value is too small) + n.GetPaddedUint8() // parses the value as uint8 and pads it if too small + + fmt.Printf("string: %s, tag: %d", n.String(), n.Tag) + + // all available types: bool, uint8, uint16, uint32, uint64, string, time.Time and Nodes +}