From 6b3e710c9141a53fbab43077f80052cd9627fd54 Mon Sep 17 00:00:00 2001 From: Sergei Parshev Date: Thu, 5 Sep 2024 12:49:58 -0400 Subject: [PATCH] Added golangci-lint and multiple fixes for all kind of checks The most important: * Added golangci to github workflow * Bumped version of go to 1.22 to fix for loops vars * Removed cluster logic since dead code and better one in #30 * Fixed unauthorized access to application resource * Fixed not checked user struct type conversion in API * Multiple security and style fixes --- .github/workflows/main.yml | 18 ++ .golangci.yml | 128 ++++++++++++ check.sh | 4 +- cmd/fish/fish.go | 15 +- go.mod | 3 +- go.sum | 2 - lib/cluster/cluster.go | 73 ------- lib/cluster/clusterclient.go | 169 ---------------- lib/cluster/msg/msg.go | 16 -- lib/crypt/crypt.go | 2 +- lib/drivers/aws/config.go | 2 +- lib/drivers/aws/dedicated_pool.go | 18 +- lib/drivers/aws/driver.go | 7 +- lib/drivers/aws/util.go | 34 ++-- lib/drivers/docker/options.go | 2 +- lib/drivers/docker/util.go | 15 +- lib/drivers/image.go | 32 +-- lib/drivers/native/config.go | 34 ++-- lib/drivers/native/driver.go | 2 +- lib/drivers/native/options.go | 4 +- lib/drivers/native/util.go | 10 +- lib/drivers/test/driver.go | 4 +- lib/drivers/test/tasks.go | 6 +- lib/drivers/vmx/config.go | 6 +- lib/drivers/vmx/options.go | 2 +- lib/drivers/vmx/util.go | 16 +- lib/fish/drivers.go | 2 +- lib/fish/fish.go | 24 +-- lib/fish/node.go | 13 +- lib/fish/vote.go | 2 +- lib/openapi/api/api_v1.go | 298 +++++++++++++++++++--------- lib/openapi/cluster/client.go | 135 ------------- lib/openapi/cluster/cluster_v1.go | 126 ------------ lib/openapi/cluster/hub.go | 57 ------ lib/openapi/openapi.go | 10 +- lib/openapi/types/node.go | 4 +- lib/openapi/types/resources.go | 2 +- lib/proxy_ssh/proxy.go | 2 +- lib/util/file_replace_token.go | 3 +- lib/util/file_replace_token_test.go | 18 +- lib/util/file_starts_with_test.go | 6 +- lib/util/human_size.go | 9 +- lib/util/lock.go | 3 +- lib/util/metadata_processing.go | 8 +- lib/util/passthrough_monitor.go | 2 +- tests/helper/copy.go | 2 +- tests/helper/fish.go | 72 ++++--- tests/helper/t_mock.go | 1 + 48 files changed, 548 insertions(+), 875 deletions(-) create mode 100644 .golangci.yml delete mode 100644 lib/cluster/cluster.go delete mode 100644 lib/cluster/clusterclient.go delete mode 100644 lib/cluster/msg/msg.go delete mode 100644 lib/openapi/cluster/client.go delete mode 100644 lib/openapi/cluster/cluster_v1.go delete mode 100644 lib/openapi/cluster/hub.go diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 5b21f5a..30ad651 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -46,6 +46,24 @@ jobs: swagger-editor-url: http://localhost/ definition-file: docs/openapi.yaml + GolangCI: + runs-on: ubuntu-latest + name: Code Lint + permissions: + # Required: allow read access to the content for analysis. + contents: read + # Optional: allow write access to checks to allow the action to annotate code in the PR. + checks: write + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-go@v5 + with: + go-version: stable # Linter will use go.mod file to adjust the rules properly + - name: golangci-lint + uses: golangci/golangci-lint-action@v6 + with: + version: v1.60 + Build: runs-on: ubuntu-latest steps: diff --git a/.golangci.yml b/.golangci.yml new file mode 100644 index 0000000..415447a --- /dev/null +++ b/.golangci.yml @@ -0,0 +1,128 @@ +run: + concurrency: 4 + timeout: 10m + tests: false + allow-parallel-runners: true + allow-serial-runners: true + +output: + show-stats: true + +linters: + # Disable all linters. + # Default: false + disable-all: true + # Enable specific linter + # https://golangci-lint.run/usage/linters/#enabled-by-default + enable: + - asasalint + - asciicheck + - bidichk + - bodyclose + - canonicalheader + - containedctx + - contextcheck + - copyloopvar + #- cyclop + - decorder + #- depguard + - dogsled + #- dupl + - dupword + - durationcheck + #- err113 + #- errcheck # Maybe in the future? + - errchkjson + - errname + #- errorlint + - exhaustive + #- exhaustruct + - fatcontext + #- forbidigo + - forcetypeassert + #- funlen + #- gci + - ginkgolinter + - gocheckcompilerdirectives + #- gochecknoglobals + #- gochecknoinits + - gochecksumtype + #- gocognit + #- goconst + #- gocritic + #- gocyclo + #- godot + #- godox + - gofmt + #- gofumpt + - goheader + - goimports + - gomoddirectives + - gomodguard + - goprintffuncname + - gosec + - gosimple + - gosmopolitan + - govet + - grouper + - importas + - inamedparam + - ineffassign + - interfacebloat + - intrange + #- ireturn + #- lll + - loggercheck + #- maintidx + - makezero + - mirror + - misspell + #- mnd + - musttag + #- nakedret + #- nestif + - nilerr + #- nilnil + #- nlreturn + - noctx + - nolintlint + #- nonamedreturns + - nosprintfhostport + - paralleltest + #- perfsprint + #- prealloc + - predeclared + - promlinter + - protogetter + - reassign + #- revive # not supporting snake_case for vars + - rowserrcheck + - sloglint + - spancheck + - sqlclosecheck + - staticcheck + #- stylecheck + - tagalign + #- tagliatelle + - tenv + - testableexamples + - testifylint + - testpackage + - thelper + - tparallel + - typecheck + - unconvert + - unparam + - unused + - usestdlibvars + #- varnamelen + - wastedassign + - whitespace + #- wrapcheck + #- wsl + - zerologlint + +linters-settings: + gosec: + excludes: + - G115 # integer overflow conversion - disabled due to found no proper way to fix those diff --git a/check.sh b/check.sh index 286d8f1..aa83d68 100755 --- a/check.sh +++ b/check.sh @@ -32,9 +32,9 @@ done echo echo '---------------------- GoFmt verify ----------------------' echo -reformat=$(gofmt -l . 2>&1) +reformat=$(gofmt -l -s . 2>&1) if [ "${reformat}" ]; then - echo "Please run 'gofmt -w .': \n${reformat}" + echo "Please run 'gofmt -s -w .': \n${reformat}" errors=$((${errors}+$(echo "${reformat}" | wc -l))) fi diff --git a/cmd/fish/fish.go b/cmd/fish/fish.go index 5e3e1fb..c22b87f 100644 --- a/cmd/fish/fish.go +++ b/cmd/fish/fish.go @@ -27,7 +27,6 @@ import ( "gorm.io/gorm/logger" "github.com/adobe/aquarium-fish/lib/build" - "github.com/adobe/aquarium-fish/lib/cluster" "github.com/adobe/aquarium-fish/lib/crypt" "github.com/adobe/aquarium-fish/lib/fish" "github.com/adobe/aquarium-fish/lib/log" @@ -44,7 +43,6 @@ func main() { var proxy_socks_address string var proxy_ssh_address string var node_address string - var cluster_join *[]string var cfg_path string var dir string var cpu_limit string @@ -83,9 +81,6 @@ func main() { if node_address != "" { cfg.NodeAddress = node_address } - if len(*cluster_join) > 0 { - cfg.ClusterJoin = *cluster_join - } if dir != "" { cfg.Directory = dir } @@ -174,14 +169,8 @@ func main() { return err } - log.Info("Fish joining cluster...") - cl, err := cluster.New(fish, cfg.ClusterJoin, ca_path, cert_path, key_path) - if err != nil { - return err - } - log.Info("Fish starting API...") - srv, err := openapi.Init(fish, cl, cfg.APIAddress, ca_path, cert_path, key_path) + srv, err := openapi.Init(fish, cfg.APIAddress, ca_path, cert_path, key_path) if err != nil { return err } @@ -199,7 +188,6 @@ func main() { log.Info("Fish stopping...") - cl.Stop() fish.Close() log.Info("Fish stopped") @@ -213,7 +201,6 @@ func main() { flags.StringVar(&proxy_socks_address, "socks_proxy", "", "address used to expose the SOCKS5 proxy") flags.StringVar(&proxy_ssh_address, "ssh_proxy", "", "address used to expose the SSH proxy") flags.StringVarP(&node_address, "node", "n", "", "node external endpoint to connect to tell the other nodes") - cluster_join = flags.StringSliceP("join", "j", nil, "addresses of existing cluster nodes to join, comma separated") flags.StringVarP(&cfg_path, "cfg", "c", "", "yaml configuration file") flags.StringVarP(&dir, "dir", "D", "", "database and other fish files directory") flags.StringVar(&cpu_limit, "cpu", "", "max amount of threads fish node will be able to utilize, default - no limit") diff --git a/go.mod b/go.mod index eba1aa0..04847d7 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/adobe/aquarium-fish -go 1.21.0 +go 1.22.2 require ( github.com/alessio/shellescape v1.4.1 @@ -14,7 +14,6 @@ require ( github.com/ghodss/yaml v1.0.0 github.com/glebarez/sqlite v1.7.0 github.com/google/uuid v1.5.0 - github.com/gorilla/websocket v1.4.0 github.com/hpcloud/tail v1.0.0 github.com/labstack/echo/v4 v4.11.4 github.com/mostlygeek/arp v0.0.0-20170424181311-541a2129847a diff --git a/go.sum b/go.sum index dec50ff..eb32de9 100644 --- a/go.sum +++ b/go.sum @@ -59,8 +59,6 @@ github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 h1:Xim43kblpZXfIBQsbu github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26/go.mod h1:dDKJzRmX4S37WGHujM7tX//fmj1uioxKzKxz3lo4HJo= github.com/google/uuid v1.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU= github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q= -github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= diff --git a/lib/cluster/cluster.go b/lib/cluster/cluster.go deleted file mode 100644 index 16b943d..0000000 --- a/lib/cluster/cluster.go +++ /dev/null @@ -1,73 +0,0 @@ -package cluster - -import ( - "context" - "crypto/tls" - "crypto/x509" - "fmt" - "net/url" - "os" - - "github.com/adobe/aquarium-fish/lib/fish" -) - -type Cluster struct { - fish *fish.Fish - - clients []*ClusterClient - - ca_pool *x509.CertPool - certkey tls.Certificate -} - -func New(fish *fish.Fish, join []string, ca_path, cert_path, key_path string) (*Cluster, error) { - c := &Cluster{ - fish: fish, - ca_pool: x509.NewCertPool(), - } - - // Load CA cert to pool - ca_bytes, err := os.ReadFile(ca_path) - if err != nil { - return nil, fmt.Errorf("Cluster: Unable to load CA certificate: %v", err) - } - if !c.ca_pool.AppendCertsFromPEM(ca_bytes) { - return nil, fmt.Errorf("Cluster: Incorrect CA pem data: %s", ca_path) - } - - // Load client cert and key - c.certkey, err = tls.LoadX509KeyPair(cert_path, key_path) - if err != nil { - return nil, fmt.Errorf("Cluster: Unable to load cert/key: %v", err) - } - - // Connect the join nodes - for _, endpoint := range join { - c.NewClient(endpoint, "cluster/v1/connect") - } - - return c, nil -} - -func (c *Cluster) NewClient(host, channel string) *ClusterClient { - conn := &ClusterClient{ - url: url.URL{Scheme: "wss", Host: host, Path: channel}, - send_buf: make(chan []byte, 1), - cluster: c, - } - conn.ctx, conn.ctxCancel = context.WithCancel(context.Background()) - - go conn.listen() - go conn.listenWrite() - go conn.ping() - - c.clients = append(c.clients, conn) - - return conn -} - -func (c *Cluster) Stop() { - for _, conn := range c.clients { - conn.Stop() - } -} diff --git a/lib/cluster/clusterclient.go b/lib/cluster/clusterclient.go deleted file mode 100644 index a198b8e..0000000 --- a/lib/cluster/clusterclient.go +++ /dev/null @@ -1,169 +0,0 @@ -package cluster - -import ( - "context" - "crypto/tls" - "encoding/json" - "fmt" - "net/http" - "net/url" - "sync" - "time" - - "github.com/gorilla/websocket" - - "github.com/adobe/aquarium-fish/lib/log" -) - -// Send pings to peer with this period -const ping_period = 30 * time.Second - -type ClusterClient struct { - url url.URL - send_buf chan []byte - ctx context.Context - ctxCancel context.CancelFunc - - mu sync.RWMutex - wsconn *websocket.Conn - - cluster *Cluster -} - -func (conn *ClusterClient) Connect() *websocket.Conn { - conn.mu.Lock() - defer conn.mu.Unlock() - if conn.wsconn != nil { - return conn.wsconn - } - - ticker := time.NewTicker(time.Second) - defer ticker.Stop() - for ; ; <-ticker.C { - select { - case <-conn.ctx.Done(): - return nil - default: - config := &tls.Config{ - RootCAs: conn.cluster.ca_pool, - Certificates: []tls.Certificate{conn.cluster.certkey}, - } - dialer := &websocket.Dialer{ - Proxy: http.ProxyFromEnvironment, - HandshakeTimeout: 45 * time.Second, - TLSClientConfig: config, - EnableCompression: true, - } - ws, _, err := dialer.Dial(conn.url.String(), nil) - if err != nil { - log.Errorf("ClusterClient %s: Cannot connect to websocket: %s: %v", conn.url.Host, conn.url.String(), err) - continue - } - - log.Infof("ClusterClient %s: Connected to node", conn.url.Host) - conn.wsconn = ws - - return conn.wsconn - } - } -} - -func (conn *ClusterClient) listen() { - log.Infof("ClusterClient %s: Listen for the messages", conn.url.Host) - ticker := time.NewTicker(time.Second) - defer ticker.Stop() - for { - select { - case <-conn.ctx.Done(): - return - case <-ticker.C: - for { - ws := conn.Connect() - if ws == nil { - return - } - _, _ /*msg*/, err := ws.ReadMessage() - if err != nil { - log.Errorf("ClusterClient %s: Cannot read websocket message: %v", conn.url.Host, err) - conn.closeWs() - break - } - //log.Printf("ClusterClient %s: Received msg: %x\n", conn.url.Host, msg) - // TODO: Process msg - } - } - } -} - -// Write data to the websocket server -func (conn *ClusterClient) Write(payload any) error { - data, err := json.Marshal(payload) - if err != nil { - return err - } - ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond*50) - defer cancel() - - for { - select { - case conn.send_buf <- data: - return nil - case <-ctx.Done(): - return fmt.Errorf("context canceled") - } - } -} - -func (conn *ClusterClient) listenWrite() { - for data := range conn.send_buf { - ws := conn.Connect() - if ws == nil { - log.Errorf("ClusterClient %s: No websocket connection: %v", conn.url.Host, fmt.Errorf("ws is nil")) - continue - } - - if err := ws.WriteMessage( - websocket.TextMessage, - data, - ); err != nil { - log.Errorf("ClusterClient %s: Write error: %v", conn.url.Host, err) - } - } -} - -// Close will send close message and shutdown websocket connection -func (conn *ClusterClient) Stop() { - conn.ctxCancel() - conn.closeWs() -} - -// Close will send close message and shutdown websocket connection -func (conn *ClusterClient) closeWs() { - conn.mu.Lock() - if conn.wsconn != nil { - conn.wsconn.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, "")) - conn.wsconn.Close() - conn.wsconn = nil - } - conn.mu.Unlock() -} - -func (conn *ClusterClient) ping() { - log.Infof("ClusterClient %s: Ping started", conn.url.Host) - ticker := time.NewTicker(ping_period) - defer ticker.Stop() - for { - select { - case <-ticker.C: - ws := conn.Connect() - if ws == nil { - continue - } - if err := conn.wsconn.WriteControl(websocket.PingMessage, []byte{}, time.Now().Add(ping_period/2)); err != nil { - conn.closeWs() - } - case <-conn.ctx.Done(): - return - } - } -} diff --git a/lib/cluster/msg/msg.go b/lib/cluster/msg/msg.go deleted file mode 100644 index f7dc46c..0000000 --- a/lib/cluster/msg/msg.go +++ /dev/null @@ -1,16 +0,0 @@ -package msg - -type Nodes struct { - Type string `json:"type"` - //Data []*Node `json:"data"` -} - -func NewNodes() *Nodes { - return &Nodes{ - Type: "nodes", - } -} - -/*func (m *Nodes) AddNode(node *Node) { - m.Data = append(m.Data, node) -}*/ diff --git a/lib/crypt/crypt.go b/lib/crypt/crypt.go index ce89461..86cd343 100644 --- a/lib/crypt/crypt.go +++ b/lib/crypt/crypt.go @@ -112,7 +112,7 @@ func (h *Hash) IsEqual(input string) bool { h.Prop.Threads = v074_Argon2_Threads } - return bytes.Compare(h.Hash, argon2.IDKey([]byte(input), h.Salt, h.Prop.Iterations, h.Prop.Memory, h.Prop.Threads, uint32(len(h.Hash)))) == 0 + return bytes.Equal(h.Hash, argon2.IDKey([]byte(input), h.Salt, h.Prop.Iterations, h.Prop.Memory, h.Prop.Threads, uint32(len(h.Hash)))) } func (hash *Hash) IsEmpty() bool { diff --git a/lib/drivers/aws/config.go b/lib/drivers/aws/config.go index 65e7d59..ed3423e 100644 --- a/lib/drivers/aws/config.go +++ b/lib/drivers/aws/config.go @@ -126,7 +126,7 @@ func (c *Config) Validate() (err error) { // It helps with the machines where internet is not available right away retries := 6 counter := 0 - account := "" + var account string for { res, err := conn.GetCallerIdentity(context.TODO(), input) counter++ diff --git a/lib/drivers/aws/dedicated_pool.go b/lib/drivers/aws/dedicated_pool.go index dddd558..9762391 100644 --- a/lib/drivers/aws/dedicated_pool.go +++ b/lib/drivers/aws/dedicated_pool.go @@ -128,8 +128,8 @@ func (w *dedicatedPoolWorker) ReserveHost(instance_type string) string { } // Pick random one from the list of available hosts to reduce the possibility of conflict - host := w.active_hosts[available_hosts[rand.Intn(len(available_hosts))]] - // Mark it as reserved temporarly to ease multi-allocation at the same time + host := w.active_hosts[available_hosts[rand.Intn(len(available_hosts))]] // #nosec G404 + // Mark it as reserved temporary to ease multi-allocation at the same time host.State = HOST_RESERVED w.active_hosts[aws.ToString(host.HostId)] = host return aws.ToString(host.HostId) @@ -271,7 +271,7 @@ func (w *dedicatedPoolWorker) manageHosts() []string { // Skipping the hosts that already in managed list found := false - for hid, _ := range w.to_manage_at { + for hid := range w.to_manage_at { if host_id == hid { found = true break @@ -315,7 +315,7 @@ func (w *dedicatedPoolWorker) releaseHosts(release_hosts []string) { if host, ok := w.active_hosts[host_id]; ok && host.HostProperties != nil { if isHostMac(&host) { mac_hosts = append(mac_hosts, host_id) - // If mac host not reached 24h since allocation - skipping addtion to the release list + // If mac host not reached 24h since allocation - skipping addition to the release list if !isHostReadyForRelease(&host) { continue } @@ -398,7 +398,7 @@ func isHostReadyForRelease(host *ec2_types.Host) bool { // Check if the host is used func isHostUsed(host *ec2_types.Host) bool { - if host.State == HOST_RESERVED || host.Instances != nil && len(host.Instances) > 0 { + if host.State == HOST_RESERVED || len(host.Instances) > 0 { return true } return false @@ -456,7 +456,7 @@ func (w *dedicatedPoolWorker) updateDedicatedHosts() error { p := ec2.NewDescribeHostsPaginator(conn, &ec2.DescribeHostsInput{ Filter: []ec2_types.Filter{ // We don't need released hosts, so skipping them - ec2_types.Filter{ + { Name: aws.String("state"), Values: []string{ string(ec2_types.AllocationStateAvailable), @@ -465,15 +465,15 @@ func (w *dedicatedPoolWorker) updateDedicatedHosts() error { string(ec2_types.AllocationStatePending), }, }, - ec2_types.Filter{ + { Name: aws.String("availability-zone"), Values: []string{w.record.Zone}, }, - ec2_types.Filter{ + { Name: aws.String("instance-type"), Values: []string{w.record.Type}, }, - ec2_types.Filter{ + { Name: aws.String("tag-key"), Values: []string{"AquariumDedicatedPool-" + w.name}, }, diff --git a/lib/drivers/aws/driver.go b/lib/drivers/aws/driver.go index 2392dd2..195f172 100644 --- a/lib/drivers/aws/driver.go +++ b/lib/drivers/aws/driver.go @@ -151,11 +151,11 @@ func (d *Driver) AvailableCapacity(node_usage types.Resources, def types.LabelDe // Quotas for hosts are: "Running Dedicated mac1 Hosts" & "Running Dedicated mac2 Hosts" p := ec2.NewDescribeHostsPaginator(conn_ec2, &ec2.DescribeHostsInput{ Filter: []ec2_types.Filter{ - ec2_types.Filter{ + { Name: aws.String("instance-type"), Values: []string{opts.InstanceType}, }, - ec2_types.Filter{ + { Name: aws.String("state"), Values: []string{"available"}, }, @@ -315,7 +315,6 @@ func (d *Driver) Allocate(def types.LabelDefinition, metadata map[string]any) (* } else { return nil, fmt.Errorf("AWS: %s: Unable to locate the dedicated pool: %s", i_name, opts.Pool) } - } else if awsInstTypeAny(opts.InstanceType, "mac") { // For mac machines only dedicated hosts are working, so set the tenancy input.Placement = &ec2_types.Placement{ @@ -329,7 +328,7 @@ func (d *Driver) Allocate(def types.LabelDefinition, metadata map[string]any) (* if err != nil { return nil, fmt.Errorf("AWS: %s: Unable to serialize metadata to userdata: %v", i_name, err) } - input.UserData = aws.String(base64.StdEncoding.EncodeToString([]byte(userdata))) + input.UserData = aws.String(base64.StdEncoding.EncodeToString(userdata)) } if opts.SecurityGroup != "" { diff --git a/lib/drivers/aws/util.go b/lib/drivers/aws/util.go index 6fe9355..e366662 100644 --- a/lib/drivers/aws/util.go +++ b/lib/drivers/aws/util.go @@ -98,7 +98,7 @@ func (d *Driver) getSubnetId(conn *ec2.Client, id_tag string) (string, int64, er req := ec2.DescribeVpcsInput{ Filters: []types.Filter{ filter, - types.Filter{ + { Name: aws.String("owner-id"), Values: d.cfg.AccountIDs, }, @@ -110,7 +110,7 @@ func (d *Driver) getSubnetId(conn *ec2.Client, id_tag string) (string, int64, er req := ec2.DescribeSubnetsInput{ Filters: []types.Filter{ filter, - types.Filter{ + { Name: aws.String("owner-id"), Values: d.cfg.AccountIDs, }, @@ -218,11 +218,11 @@ func (d *Driver) getImageId(conn *ec2.Client, id_name string) (string, error) { // Look for image with the defined name req := ec2.DescribeImagesInput{ Filters: []types.Filter{ - types.Filter{ + { Name: aws.String("name"), Values: []string{id_name}, }, - types.Filter{ + { Name: aws.String("state"), Values: []string{"available"}, }, @@ -321,23 +321,23 @@ func (d *Driver) getImageIdByType(conn *ec2.Client, instance_type string) (strin log.Debugf("AWS: Looking an image: Checking past year from %d", images_till.Year()) req := ec2.DescribeImagesInput{ Filters: []types.Filter{ - types.Filter{ + { Name: aws.String("architecture"), Values: []string{string(type_arch)}, }, - types.Filter{ + { Name: aws.String("creation-date"), Values: awsLastYearFilterValues(images_till), }, - types.Filter{ + { Name: aws.String("is-public"), Values: []string{"true"}, }, - types.Filter{ + { Name: aws.String("owner-alias"), Values: []string{"amazon"}, // Use only amazon-provided images }, - types.Filter{ + { Name: aws.String("state"), Values: []string{"available"}, }, @@ -377,11 +377,11 @@ func (d *Driver) getSecGroupId(conn *ec2.Client, id_name string) (string, error) // Look for security group with the defined name req := ec2.DescribeSecurityGroupsInput{ Filters: []types.Filter{ - types.Filter{ + { Name: aws.String("group-name"), Values: []string{id_name}, }, - types.Filter{ + { Name: aws.String("owner-id"), Values: d.cfg.AccountIDs, }, @@ -414,11 +414,11 @@ func (d *Driver) getSnapshotId(conn *ec2.Client, id_tag string) (string, error) // Look for VPC with the defined tag over pages req := ec2.DescribeSnapshotsInput{ Filters: []types.Filter{ - types.Filter{ + { Name: aws.String("tag:" + tag_key_val[0]), Values: []string{tag_key_val[1]}, }, - types.Filter{ + { Name: aws.String("status"), Values: []string{"completed"}, }, @@ -460,7 +460,7 @@ func (d *Driver) getProjectCpuUsage(conn *ec2.Client, inst_types []string) (int6 // checking if the instance is actually starts with type+number. req := ec2.DescribeInstancesInput{ Filters: []types.Filter{ - types.Filter{ + { Name: aws.String("instance-state-name"), // Confirmed by AWS eng: only terminated instances are not counting in utilization Values: []string{"pending", "running", "shutting-down", "stopping", "stopped"}, @@ -491,7 +491,7 @@ func (d *Driver) getProjectCpuUsage(conn *ec2.Client, inst_types []string) (int6 func (d *Driver) getInstance(conn *ec2.Client, inst_id string) (*types.Instance, error) { input := ec2.DescribeInstancesInput{ Filters: []types.Filter{ - types.Filter{ + { Name: aws.String("instance-id"), Values: []string{inst_id}, }, @@ -503,7 +503,7 @@ func (d *Driver) getInstance(conn *ec2.Client, inst_id string) (*types.Instance, return nil, err } if len(resp.Reservations) < 1 || len(resp.Reservations[0].Instances) < 1 { - return nil, nil + return nil, fmt.Errorf("Returned empty reservations or instances lists") } return &resp.Reservations[0].Instances[0], nil } @@ -609,7 +609,7 @@ func (d *Driver) triggerHostScrubbing(host_id, instance_type string) (err error) conn := d.newEC2Conn() // Just need an image, which we could find by looking at the host instance type - vm_image := "" + var vm_image string if vm_image, err = d.getImageIdByType(conn, instance_type); err != nil { return fmt.Errorf("AWS: scrubbing %s: Unable to find image: %v", host_id, err) } diff --git a/lib/drivers/docker/options.go b/lib/drivers/docker/options.go index 7ee895b..d378417 100644 --- a/lib/drivers/docker/options.go +++ b/lib/drivers/docker/options.go @@ -45,7 +45,7 @@ func (o *Options) Apply(options util.UnparsedJson) error { func (o *Options) Validate() error { // Check images var img_err error - for index, _ := range o.Images { + for index := range o.Images { if err := o.Images[index].Validate(); err != nil { img_err = log.Error("Docker: Error during image validation:", err) } diff --git a/lib/drivers/docker/util.go b/lib/drivers/docker/util.go index 9bdc7d8..47054b5 100644 --- a/lib/drivers/docker/util.go +++ b/lib/drivers/docker/util.go @@ -125,7 +125,7 @@ func (d *Driver) getAvailResources() (avail_cpu, avail_ram uint) { return } -// Returns the standartized container name +// Returns the standardized container name func (d *Driver) getContainerName(hwaddr string) string { return fmt.Sprintf("fish-%s", strings.ReplaceAll(hwaddr, ":", "")) } @@ -138,7 +138,7 @@ func (d *Driver) loadImages(opts *Options) (string, error) { log.Info("Docker: Loading the required image:", image.Name, image.Version, image.Url) // Running the background routine to download, unpack and process the image - // Success will be checked later by existance of the image in local docker registry + // Success will be checked later by existence of the image in local docker registry wg.Add(1) go func(image drivers.Image) { defer wg.Done() @@ -313,7 +313,7 @@ func (d *Driver) disksCreate(c_name string, run_args *[]string, disks map[string // Do not recreate the disk if it is exists if _, err := os.Stat(dmg_path); os.IsNotExist(err) { - disk_type := "" + var disk_type string switch disk.Type { case "hfs+": disk_type = "HFS+" @@ -370,7 +370,7 @@ func (d *Driver) envCreate(c_name string, metadata map[string]any) (string, erro if err := os.MkdirAll(filepath.Dir(env_file_path), 0o755); err != nil { return "", log.Error("Docker: Unable to create the container directory:", filepath.Dir(env_file_path), err) } - fd, err := os.OpenFile(env_file_path, os.O_WRONLY|os.O_CREATE, 0640) + fd, err := os.OpenFile(env_file_path, os.O_WRONLY|os.O_CREATE, 0o640) if err != nil { return "", log.Error("Docker: Unable to create env file:", env_file_path, err) } @@ -378,7 +378,8 @@ func (d *Driver) envCreate(c_name string, metadata map[string]any) (string, erro // Write env file line by line for key, value := range metadata { - if _, err := fd.Write([]byte(fmt.Sprintf("%s=%s\n", key, value))); err != nil { + data := []byte(fmt.Sprintf("%s=%s\n", key, value)) + if _, err := fd.Write(data); err != nil { return "", log.Error("Docker: Unable to write env file data:", env_file_path, err) } } @@ -424,8 +425,8 @@ func runAndLog(timeout time.Duration, path string, arg ...string) (string, strin } // Replace these for Windows, we only want to deal with Unix style line endings. - returnStdout := strings.Replace(stdout.String(), "\r\n", "\n", -1) - returnStderr := strings.Replace(stderr.String(), "\r\n", "\n", -1) + returnStdout := strings.ReplaceAll(stdout.String(), "\r\n", "\n") + returnStderr := strings.ReplaceAll(stderr.String(), "\r\n", "\n") return returnStdout, returnStderr, err } diff --git a/lib/drivers/image.go b/lib/drivers/image.go index 2ba1528..a3749ce 100644 --- a/lib/drivers/image.go +++ b/lib/drivers/image.go @@ -14,8 +14,9 @@ package drivers import ( "archive/tar" - "crypto/md5" - "crypto/sha1" + "context" + "crypto/md5" // #nosec G501 + "crypto/sha1" // #nosec G505 "crypto/sha256" "crypto/sha512" "encoding/hex" @@ -61,7 +62,7 @@ func (i *Image) Validate() error { i.Name = path.Base(i.Url) minus_loc := strings.LastIndexByte(i.Name, '-') if minus_loc != -1 { - // Use the part from beginnig to last minus ('-') - useful to separate version part + // Use the part from beginning to last minus ('-') - useful to separate version part i.Name = i.Name[0:minus_loc] } else if strings.LastIndexByte(i.Name, '.') != -1 { // Split by extension - need to take into account dual extension of tar archives (ex. ".tar.xz") @@ -139,7 +140,7 @@ func (i *Image) DownloadUnpack(out_dir, user, password string) error { defer os.Remove(lock_path) client := &http.Client{} - req, _ := http.NewRequest("GET", i.Url, nil) + req, _ := http.NewRequestWithContext(context.TODO(), http.MethodGet, i.Url, nil) if user != "" && password != "" { req.SetBasicAuth(user, password) } @@ -150,7 +151,7 @@ func (i *Image) DownloadUnpack(out_dir, user, password string) error { } defer resp.Body.Close() - if resp.StatusCode != 200 { + if resp.StatusCode != http.StatusOK { os.RemoveAll(img_path) return fmt.Errorf("Image: Unable to download file %q: %s", i.Url, resp.Status) } @@ -174,9 +175,9 @@ func (i *Image) DownloadUnpack(out_dir, user, password string) error { // Calculating checksum during reading from the body switch algo_sum[0] { case "md5": - hasher = md5.New() + hasher = md5.New() // #nosec G401 case "sha1": - hasher = sha1.New() + hasher = sha1.New() // #nosec G401 case "sha256": hasher = sha256.New() case "sha512": @@ -191,12 +192,12 @@ func (i *Image) DownloadUnpack(out_dir, user, password string) error { // Check if headers contains the needed algo:hash for quick validation // We're not completely trust the server, but if it returns the wrong sum - we're dropping. // Header should look like: X-Checksum-Md5 X-Checksum-Sha1 X-Checksum-Sha256 (Artifactory) - if remote_sum := resp.Header.Get("X-Checksum-" + strings.Title(algo_sum[0])); remote_sum != "" { + if remote_sum := resp.Header.Get("X-Checksum-" + strings.Title(algo_sum[0])); remote_sum != "" { //nolint:staticcheck // SA1019 Strictly ASCII here // Server returned mathing header, so compare it's value to our checksum if remote_sum != algo_sum[1] { os.RemoveAll(img_path) return fmt.Errorf("Image: The remote checksum (from header X-Checksum-%s) doesn't equal the desired one: %q != %q for %q", - strings.Title(algo_sum[0]), remote_sum, algo_sum[1], i.Url) + strings.Title(algo_sum[0]), remote_sum, algo_sum[1], i.Url) //nolint:staticcheck // SA1019 Strictly ASCII here } } } @@ -229,7 +230,7 @@ func (i *Image) DownloadUnpack(out_dir, user, password string) error { return fmt.Errorf("Image: The archive filepath contains '..' which is security forbidden: %q", hdr.Name) } - target := filepath.Join(img_path, hdr.Name) + target := filepath.Join(img_path, hdr.Name) // #nosec G305 , checked above switch hdr.Typeflag { case tar.TypeDir: @@ -239,7 +240,7 @@ func (i *Image) DownloadUnpack(out_dir, user, password string) error { os.RemoveAll(img_path) return fmt.Errorf("Image: Unable to create directory %q: %v", target, err) } - case tar.TypeReg, tar.TypeRegA: + case tar.TypeReg: // Write a file log.Debugf("Util: Extracting '%s': %s", img_path, hdr.Name) err = os.MkdirAll(filepath.Dir(target), 0750) @@ -255,8 +256,13 @@ func (i *Image) DownloadUnpack(out_dir, user, password string) error { defer w.Close() // TODO: Add in-stream sha256 calculation for each file to verify against .sha256 data - _, err = io.Copy(w, tr) - if err != nil { + for { + _, err = io.CopyN(w, tr, 8196) + if err == nil { + continue + } else if err == io.EOF { + break + } os.RemoveAll(img_path) return fmt.Errorf("Image: Unable to unpack content to file %q: %v", target, err) } diff --git a/lib/drivers/native/config.go b/lib/drivers/native/config.go index 6aab020..11da509 100644 --- a/lib/drivers/native/config.go +++ b/lib/drivers/native/config.go @@ -113,7 +113,7 @@ func (c *Config) Validate() (err error) { if info, err := os.Stat(c.SudoPath); os.IsNotExist(err) { return fmt.Errorf("Native: Unable to locate `sudo` path: %s, %s", c.SudoPath, err) } else { - if info.Mode()&0111 == 0 { + if info.Mode()&0o111 == 0 { return fmt.Errorf("Native: `sudo` binary is not executable: %s", c.SudoPath) } } @@ -128,7 +128,7 @@ func (c *Config) Validate() (err error) { if info, err := os.Stat(c.SuPath); os.IsNotExist(err) { return fmt.Errorf("Native: Unable to locate `su` path: %s, %s", c.SuPath, err) } else { - if info.Mode()&0111 == 0 { + if info.Mode()&0o111 == 0 { return fmt.Errorf("Native: `su` binary is not executable: %s", c.SuPath) } } @@ -143,7 +143,7 @@ func (c *Config) Validate() (err error) { if info, err := os.Stat(c.ShPath); os.IsNotExist(err) { return fmt.Errorf("Native: Unable to locate `sh` path: %s, %s", c.ShPath, err) } else { - if info.Mode()&0111 == 0 { + if info.Mode()&0o111 == 0 { return fmt.Errorf("Native: `sh` binary is not executable: %s", c.ShPath) } } @@ -157,7 +157,7 @@ func (c *Config) Validate() (err error) { if info, err := os.Stat(c.TarPath); os.IsNotExist(err) { return fmt.Errorf("Native: Unable to locate `tar` path: %s, %s", c.TarPath, err) } else { - if info.Mode()&0111 == 0 { + if info.Mode()&0o111 == 0 { return fmt.Errorf("Native: `tar` binary is not executable: %s", c.TarPath) } } @@ -171,7 +171,7 @@ func (c *Config) Validate() (err error) { if info, err := os.Stat(c.MountPath); os.IsNotExist(err) { return fmt.Errorf("Native: Unable to locate `mount` path: %s, %s", c.MountPath, err) } else { - if info.Mode()&0111 == 0 { + if info.Mode()&0o111 == 0 { return fmt.Errorf("Native: `mount` binary is not executable: %s", c.MountPath) } } @@ -185,7 +185,7 @@ func (c *Config) Validate() (err error) { if info, err := os.Stat(c.ChownPath); os.IsNotExist(err) { return fmt.Errorf("Native: Unable to locate `chown` path: %s, %s", c.ChownPath, err) } else { - if info.Mode()&0111 == 0 { + if info.Mode()&0o111 == 0 { return fmt.Errorf("Native: `chown` binary is not executable: %s", c.ChownPath) } } @@ -199,7 +199,7 @@ func (c *Config) Validate() (err error) { if info, err := os.Stat(c.ChmodPath); os.IsNotExist(err) { return fmt.Errorf("Native: Unable to locate `chmod` path: %s, %s", c.ChmodPath, err) } else { - if info.Mode()&0111 == 0 { + if info.Mode()&0o111 == 0 { return fmt.Errorf("Native: `chmod` binary is not executable: %s", c.ChmodPath) } } @@ -213,7 +213,7 @@ func (c *Config) Validate() (err error) { if info, err := os.Stat(c.KillallPath); os.IsNotExist(err) { return fmt.Errorf("Native: Unable to locate `killall` path: %s, %s", c.KillallPath, err) } else { - if info.Mode()&0111 == 0 { + if info.Mode()&0o111 == 0 { return fmt.Errorf("Native: `killall` binary is not executable: %s", c.KillallPath) } } @@ -227,7 +227,7 @@ func (c *Config) Validate() (err error) { if info, err := os.Stat(c.RmPath); os.IsNotExist(err) { return fmt.Errorf("Native: Unable to locate `rm` path: %s, %s", c.RmPath, err) } else { - if info.Mode()&0111 == 0 { + if info.Mode()&0o111 == 0 { return fmt.Errorf("Native: `rm` binary is not executable: %s", c.RmPath) } } @@ -243,7 +243,7 @@ func (c *Config) Validate() (err error) { if info, err := os.Stat(c.DsclPath); os.IsNotExist(err) { return fmt.Errorf("Native: Unable to locate macos `dscl` path: %s, %s", c.DsclPath, err) } else { - if info.Mode()&0111 == 0 { + if info.Mode()&0o111 == 0 { return fmt.Errorf("Native: macos `dscl` binary is not executable: %s", c.DsclPath) } } @@ -257,7 +257,7 @@ func (c *Config) Validate() (err error) { if info, err := os.Stat(c.HdiutilPath); os.IsNotExist(err) { return fmt.Errorf("Native: Unable to locate macos `hdiutil` path: %s, %s", c.HdiutilPath, err) } else { - if info.Mode()&0111 == 0 { + if info.Mode()&0o111 == 0 { return fmt.Errorf("Native: macos `hdiutil` binary is not executable: %s", c.HdiutilPath) } } @@ -271,7 +271,7 @@ func (c *Config) Validate() (err error) { if info, err := os.Stat(c.MdutilPath); os.IsNotExist(err) { return fmt.Errorf("Native: Unable to locate macos `mdutil` path: %s, %s", c.MdutilPath, err) } else { - if info.Mode()&0111 == 0 { + if info.Mode()&0o111 == 0 { return fmt.Errorf("Native: macos `mdutil` binary is not executable: %s", c.MdutilPath) } } @@ -285,7 +285,7 @@ func (c *Config) Validate() (err error) { if info, err := os.Stat(c.CreatehomedirPath); os.IsNotExist(err) { return fmt.Errorf("Native: Unable to locate macos `createhomedir` path: %s, %s", c.CreatehomedirPath, err) } else { - if info.Mode()&0111 == 0 { + if info.Mode()&0o111 == 0 { return fmt.Errorf("Native: macos `createhomedir` binary is not executable: %s", c.CreatehomedirPath) } } @@ -341,8 +341,8 @@ func (c *Config) Validate() (err error) { return err } - if c.CpuAlter < 0 && int(cpu_stat) <= -c.CpuAlter { - return log.Errorf("Native: |CpuAlter| can't be more or equal the avaialble Host CPUs: |%d| > %d", c.CpuAlter, cpu_stat) + if c.CpuAlter < 0 && cpu_stat <= -c.CpuAlter { + return log.Errorf("Native: |CpuAlter| can't be more or equal the available Host CPUs: |%d| > %d", c.CpuAlter, cpu_stat) } mem_stat, err := mem.VirtualMemory() @@ -352,7 +352,7 @@ func (c *Config) Validate() (err error) { ram_stat := mem_stat.Total / 1073741824 // Getting GB from Bytes if c.RamAlter < 0 && int(ram_stat) <= -c.RamAlter { - return log.Errorf("Native: |RamAlter| can't be more or equal the avaialble Host RAM: |%d| > %d", c.RamAlter, ram_stat) + return log.Errorf("Native: |RamAlter| can't be more or equal the available Host RAM: |%d| > %d", c.RamAlter, ram_stat) } return nil @@ -363,7 +363,7 @@ func testScriptCreate(user string) (path string, err error) { path = filepath.Join("/tmp", user+"-init.sh") script := []byte("#!/bin/sh\nid\n") - return path, os.WriteFile(path, script, 0755) + return path, os.WriteFile(path, script, 0o755) // #nosec G306 } // Will delete the config test script diff --git a/lib/drivers/native/driver.go b/lib/drivers/native/driver.go index bc3c147..135c939 100644 --- a/lib/drivers/native/driver.go +++ b/lib/drivers/native/driver.go @@ -101,7 +101,7 @@ func (d *Driver) ValidateDefinition(def types.LabelDefinition) error { // Empty name means user home which is always exists if img.Tag != "" { found := false - for d_name, _ := range def.Resources.Disks { + for d_name := range def.Resources.Disks { if d_name == img.Tag { found = true break diff --git a/lib/drivers/native/options.go b/lib/drivers/native/options.go index 07e7fa2..9a5c3da 100644 --- a/lib/drivers/native/options.go +++ b/lib/drivers/native/options.go @@ -40,7 +40,7 @@ import ( */ type Options struct { Images []drivers.Image `json:"images"` // Optional list of image dependencies, they will be unpacked in order - //TODO: Setup string `json:"setup"` // Optional path to the executable, it will be started before the Entry with escalated priveleges + //TODO: Setup string `json:"setup"` // Optional path to the executable, it will be started before the Entry with escalated privileges Entry string `json:"entry"` // Optional path to the executable, it will be running as workload (default: init.sh / init.ps1) Groups []string `json:"groups"` // Optional user groups user should have, first one is primary (default: staff) } @@ -85,7 +85,7 @@ func (o *Options) Validate() error { // Check images var img_err error - for index, _ := range o.Images { + for index := range o.Images { if err := o.Images[index].Validate(); err != nil { img_err = log.Error("Native: Error during image validation:", err) } diff --git a/lib/drivers/native/util.go b/lib/drivers/native/util.go index 2c2fae1..8a33f17 100644 --- a/lib/drivers/native/util.go +++ b/lib/drivers/native/util.go @@ -294,7 +294,7 @@ func userRun(c *Config, env_data *EnvData, user, entry string, metadata map[stri // Prepare the command to execute entry from user home directory shell_line := fmt.Sprintf("source %s; %s", env_file.Name(), shellescape.Quote(shellescape.StripUnsafe(entry))) - cmd := exec.Command(c.SudoPath, "-n", c.SuPath, "-l", user, "-c", shell_line) + cmd := exec.Command(c.SudoPath, "-n", c.SuPath, "-l", user, "-c", shell_line) // #nosec G204 if env_data != nil && env_data.Disks != nil { if _, ok := env_data.Disks[""]; ok { cmd.Dir = env_data.Disks[""] @@ -332,7 +332,7 @@ func userRun(c *Config, env_data *EnvData, user, entry string, metadata map[stri } // Stop the user processes -func userStop(c *Config, user string) (out_err error) { +func userStop(c *Config, user string) (out_err error) { //nolint:unparam // In theory we can use `sysadminctl -deleteUser` command instead, which is also stopping all the // user processes and cleans up the home dir, but it asks for elevated previleges so not sure how // useful it will be in automation... @@ -525,14 +525,14 @@ func runAndLog(timeout time.Duration, stdin io.Reader, path string, arg ...strin } // Replace these for Windows, we only want to deal with Unix style line endings. - returnStdout := strings.Replace(stdout.String(), "\r\n", "\n", -1) - returnStderr := strings.Replace(stderr.String(), "\r\n", "\n", -1) + returnStdout := strings.ReplaceAll(stdout.String(), "\r\n", "\n") + returnStderr := strings.ReplaceAll(stderr.String(), "\r\n", "\n") return returnStdout, returnStderr, err } // Will retry on error and store the retry output and errors to return -func runAndLogRetry(retry int, timeout time.Duration, stdin io.Reader, path string, arg ...string) (stdout string, stderr string, err error) { +func runAndLogRetry(retry int, timeout time.Duration, stdin io.Reader, path string, arg ...string) (stdout string, stderr string, err error) { //nolint:unparam counter := 0 for { counter++ diff --git a/lib/drivers/test/driver.go b/lib/drivers/test/driver.go index b8f8d08..16df839 100644 --- a/lib/drivers/test/driver.go +++ b/lib/drivers/test/driver.go @@ -153,7 +153,7 @@ func (d *Driver) Allocate(def types.LabelDefinition, metadata map[string]any) (* // Generate random resource id and if exists - regenerate res := &types.Resource{} - res_file := "" + var res_file string for { res.Identifier = "test-" + crypt.RandString(6) res_file = filepath.Join(d.cfg.WorkspacePath, res.Identifier) @@ -238,7 +238,7 @@ func randomFail(name string, probability uint8) error { } // Fail on probability 1 - low, 254 - high (but still can not fail) - if uint8(rand.Intn(254)) < probability { + if uint8(rand.Intn(254)) < probability { //nolint:gosec // G402,G404 -- fine for test driver return fmt.Errorf("TEST: %s failed (%d)", name, probability) } diff --git a/lib/drivers/test/tasks.go b/lib/drivers/test/tasks.go index 0959dbd..1c63455 100644 --- a/lib/drivers/test/tasks.go +++ b/lib/drivers/test/tasks.go @@ -59,14 +59,12 @@ func (t *TaskSnapshot) Execute() (result []byte, err error) { return []byte(`{"error":"internal: invalid resource"}`), log.Error("TEST: Invalid resource:", t.Resource) } if err := randomFail(fmt.Sprintf("Snapshot %s", t.Resource.Identifier), t.driver.cfg.FailSnapshot); err != nil { - out, _ := json.Marshal(map[string]any{}) - return out, log.Error("TEST: RandomFail:", err) + return []byte(`{}`), log.Error("TEST: RandomFail:", err) } res_file := filepath.Join(t.driver.cfg.WorkspacePath, t.Resource.Identifier) if _, err := os.Stat(res_file); os.IsNotExist(err) { - out, _ := json.Marshal(map[string]any{}) - return out, fmt.Errorf("TEST: Unable to snapshot unavailable resource '%s'", t.Resource.Identifier) + return []byte(`{}`), fmt.Errorf("TEST: Unable to snapshot unavailable resource '%s'", t.Resource.Identifier) } return json.Marshal(map[string]any{"snapshots": []string{"test-snapshot"}, "when": t.ApplicationTask.When}) diff --git a/lib/drivers/vmx/config.go b/lib/drivers/vmx/config.go index 0f100d5..210255b 100644 --- a/lib/drivers/vmx/config.go +++ b/lib/drivers/vmx/config.go @@ -112,8 +112,8 @@ func (c *Config) Validate() (err error) { return err } - if c.CpuAlter < 0 && int(cpu_stat) <= -c.CpuAlter { - return log.Errorf("VMX: |CpuAlter| can't be more or equal the avaialble Host CPUs: |%d| > %d", c.CpuAlter, cpu_stat) + if c.CpuAlter < 0 && cpu_stat <= -c.CpuAlter { + return log.Errorf("VMX: |CpuAlter| can't be more or equal the available Host CPUs: |%d| > %d", c.CpuAlter, cpu_stat) } mem_stat, err := mem.VirtualMemory() @@ -123,7 +123,7 @@ func (c *Config) Validate() (err error) { ram_stat := mem_stat.Total / 1073741824 // Getting GB from Bytes if c.RamAlter < 0 && int(ram_stat) <= -c.RamAlter { - return log.Errorf("VMX: |RamAlter| can't be more or equal the avaialble Host RAM: |%d| > %d", c.RamAlter, ram_stat) + return log.Errorf("VMX: |RamAlter| can't be more or equal the available Host RAM: |%d| > %d", c.RamAlter, ram_stat) } return nil diff --git a/lib/drivers/vmx/options.go b/lib/drivers/vmx/options.go index 05616ad..9aa537c 100644 --- a/lib/drivers/vmx/options.go +++ b/lib/drivers/vmx/options.go @@ -45,7 +45,7 @@ func (o *Options) Apply(options util.UnparsedJson) error { func (o *Options) Validate() error { // Check images var img_err error - for index, _ := range o.Images { + for index := range o.Images { if err := o.Images[index].Validate(); err != nil { img_err = log.Error("VMX: Error during image validation:", err) } diff --git a/lib/drivers/vmx/util.go b/lib/drivers/vmx/util.go index 4011263..f7afda4 100644 --- a/lib/drivers/vmx/util.go +++ b/lib/drivers/vmx/util.go @@ -61,7 +61,7 @@ func (d *Driver) loadImages(opts *Options, vm_images_dir string) (string, error) log.Info("VMX: Loading the required image:", image.Name, image.Version, image.Url) // Running the background routine to download, unpack and process the image - // Success will be checked later by existance of the copied image in the vm directory + // Success will be checked later by existence of the copied image in the vm directory wg.Add(1) go func(image drivers.Image, index int) error { defer wg.Done() @@ -214,7 +214,7 @@ func (d *Driver) disksCreate(vmx_path string, disks map[string]types.ResourcesDi // Create virtual disk dmg_path := disk_path + ".dmg" - disk_type := "" + var disk_type string switch disk.Type { case "hfs+": disk_type = "HFS+" @@ -255,7 +255,7 @@ func (d *Driver) disksCreate(vmx_path string, disks map[string]types.ResourcesDi } // Umount disk (use diskutil to umount for sure) - stdout, _, err = runAndLog(10*time.Second, "/usr/sbin/diskutil", "umount", mount_point) + _, _, err = runAndLog(10*time.Second, "/usr/sbin/diskutil", "umount", mount_point) if err != nil { return log.Error("VMX: Unable to umount dmg disk:", mount_point, err) } @@ -271,7 +271,7 @@ func (d *Driver) disksCreate(vmx_path string, disks map[string]types.ResourcesDi // mounted at the same time, so avoiding to use it by using template: // `Unable to create the source raw disk: Resource deadlock avoided` // To generate template: vmware-rawdiskCreator create /dev/disk2 1 ./disk_name lsilogic - vmdk_tempalte := strings.Join([]string{ + vmdk_template := strings.Join([]string{ `# Disk DescriptorFile`, `version=1`, `encoding="UTF-8"`, @@ -294,7 +294,7 @@ func (d *Driver) disksCreate(vmx_path string, disks map[string]types.ResourcesDi `ddb.virtualHWVersion = "14"`, }, "\n") - if err := os.WriteFile(disk_path+"_tmp.vmdk", []byte(vmdk_tempalte), 0640); err != nil { + if err := os.WriteFile(disk_path+"_tmp.vmdk", []byte(vmdk_template), 0o640); err != nil { //nolint:gosec // G306 return log.Error("VMX: Unable to place the template vmdk file:", disk_path+"_tmp.vmdk", err) } @@ -402,14 +402,14 @@ func runAndLog(timeout time.Duration, path string, arg ...string) (string, strin } // Replace these for Windows, we only want to deal with Unix style line endings. - returnStdout := strings.Replace(stdout.String(), "\r\n", "\n", -1) - returnStderr := strings.Replace(stderr.String(), "\r\n", "\n", -1) + returnStdout := strings.ReplaceAll(stdout.String(), "\r\n", "\n") + returnStderr := strings.ReplaceAll(stderr.String(), "\r\n", "\n") return returnStdout, returnStderr, err } // Will retry on error and store the retry output and errors to return -func runAndLogRetry(retry int, timeout time.Duration, path string, arg ...string) (stdout string, stderr string, err error) { +func runAndLogRetry(retry int, timeout time.Duration, path string, arg ...string) (stdout string, stderr string, err error) { //nolint:unparam counter := 0 for { counter++ diff --git a/lib/fish/drivers.go b/lib/fish/drivers.go index 190958d..f70a55e 100644 --- a/lib/fish/drivers.go +++ b/lib/fish/drivers.go @@ -35,7 +35,7 @@ func (f *Fish) DriverGet(name string) drivers.ResourceDriver { log.Error("Fish: Resource drivers are not initialized to request the driver instance:", name) return nil } - drv, _ := drivers_instances[name] + drv := drivers_instances[name] return drv } diff --git a/lib/fish/fish.go b/lib/fish/fish.go index ec5001d..b74e3e6 100644 --- a/lib/fish/fish.go +++ b/lib/fish/fish.go @@ -15,7 +15,6 @@ package fish import ( "encoding/json" "fmt" - "math/rand" "os" "os/signal" "path" @@ -58,7 +57,7 @@ type Fish struct { applications_mutex sync.Mutex applications []types.ApplicationUID - // Used to temporarly store the won Votes by Application create time + // Used to temporary store the won Votes by Application create time won_votes_mutex sync.Mutex won_votes map[int64]types.Vote @@ -68,9 +67,6 @@ type Fish struct { } func New(db *gorm.DB, cfg *Config) (*Fish, error) { - // Init rand generator - rand.Seed(time.Now().UnixNano()) - f := &Fish{db: db, cfg: cfg} if err := f.Init(); err != nil { return nil, err @@ -246,14 +242,15 @@ func (f *Fish) GetLocationName() types.LocationName { return f.node.LocationName } -func (f *Fish) checkNewApplicationProcess() error { +func (f *Fish) checkNewApplicationProcess() { check_ticker := time.NewTicker(5 * time.Second) for { if !f.running { break } - select { - case <-check_ticker.C: + // TODO: Here should be select with quit in case app is stopped to not wait next ticker + <-check_ticker.C + { // Check new apps available for processing new_apps, err := f.ApplicationListGetStatusNew() if err != nil { @@ -305,7 +302,6 @@ func (f *Fish) checkNewApplicationProcess() error { f.won_votes_mutex.Unlock() } } - return nil } func (f *Fish) voteProcessRound(vote *types.Vote) error { @@ -433,7 +429,7 @@ func (f *Fish) isNodeAvailableForDefinition(def types.LabelDefinition) bool { // Verify node filters because some workload can't be running on all the physical nodes // The node becomes fitting only when all the needed node filter patterns are matched - if def.Resources.NodeFilter != nil && len(def.Resources.NodeFilter) > 0 { + if len(def.Resources.NodeFilter) > 0 { needed_idents := def.Resources.NodeFilter current_idents := f.cfg.NodeIdentifiers for _, needed := range needed_idents { @@ -625,7 +621,7 @@ func (f *Fish) executeApplication(vote types.Vote) error { log.Error("Fish: Unable to store Resource for Application:", app.UID, err) } app_state = &types.ApplicationState{ApplicationUID: app.UID, Status: types.ApplicationStatusALLOCATED, - Description: fmt.Sprint("Driver allocated the resource"), + Description: "Driver allocated the resource", } log.Infof("Fish: Allocated Resource %q for the Application %s", app.UID, res.Identifier) } @@ -701,7 +697,7 @@ func (f *Fish) executeApplication(vote types.Vote) error { } else { log.Info("Fish: Successful deallocation of the Application:", app.UID) app_state = &types.ApplicationState{ApplicationUID: app.UID, Status: types.ApplicationStatusDEALLOCATED, - Description: fmt.Sprint("Driver deallocated the resource"), + Description: "Driver deallocated the resource", } } // Destroying the resource anyway to not bloat the table - otherwise it will stuck there and @@ -749,7 +745,7 @@ func (f *Fish) executeApplicationTasks(drv drivers.ResourceDriver, def *types.La t := drv.GetTask(task.Task, string(task.Options)) if t == nil { log.Error("Fish: Unable to get associated driver task type for Application:", res.ApplicationUID, task.Task) - task.Result = util.UnparsedJson(`{"error":"task not availble in driver"}`) + task.Result = util.UnparsedJson(`{"error":"task not available in driver"}`) } else { // Executing the task t.SetInfo(&task, def, res) @@ -873,7 +869,7 @@ func (f *Fish) activateShutdown() { fire_shutdown <- true } case <-delay_ticker_report.C: - log.Infof("Fish: Shutdown: countdown: T-%v", delay_end_time.Sub(time.Now())) + log.Infof("Fish: Shutdown: countdown: T-%v", time.Until(delay_end_time)) case <-delay_timer.C: // Delay time has passed, triggering shutdown fire_shutdown <- true diff --git a/lib/fish/node.go b/lib/fish/node.go index b0a2f6d..9e3cefa 100644 --- a/lib/fish/node.go +++ b/lib/fish/node.go @@ -75,18 +75,17 @@ func (f *Fish) NodeGet(name string) (node *types.Node, err error) { return node, err } -func (f *Fish) pingProcess() error { +func (f *Fish) pingProcess() { // In order to optimize network & database - update just UpdatedAt field ping_ticker := time.NewTicker(types.NODE_PING_DELAY * time.Second) for { if !f.running { break } - select { - case <-ping_ticker.C: - log.Debug("Fish Node: ping") - f.NodePing(f.node) - } + + // TODO: Here should be select with quit in case app is stopped to not wait next ticker + <-ping_ticker.C + log.Debug("Fish Node: ping") + f.NodePing(f.node) } - return nil } diff --git a/lib/fish/vote.go b/lib/fish/vote.go index 05f9dec..0c07c7b 100644 --- a/lib/fish/vote.go +++ b/lib/fish/vote.go @@ -46,7 +46,7 @@ func (f *Fish) VoteCreate(v *types.Vote) error { return fmt.Errorf("Fish: NodeUID can't be unset") } // Update Vote Rand to be actual rand - v.Rand = rand.Uint32() + v.Rand = rand.Uint32() // #nosec G404 v.UID = f.NewUID() return f.db.Create(v).Error } diff --git a/lib/openapi/api/api_v1.go b/lib/openapi/api/api_v1.go index ccfb4da..52389e8 100644 --- a/lib/openapi/api/api_v1.go +++ b/lib/openapi/api/api_v1.go @@ -61,15 +61,25 @@ func (e *Processor) BasicAuth(username, password string, c echo.Context) (bool, } func (e *Processor) UserMeGet(c echo.Context) error { - user := c.Get("user") + user, ok := c.Get("user").(*types.User) + if !ok { + c.JSON(http.StatusBadRequest, H{"message": "Not authentified"}) + return fmt.Errorf("Not authentified") + } + // Cleanup the hash to prevent malicious activity + user.Hash = crypt.Hash{} return c.JSON(http.StatusOK, user) } func (e *Processor) UserListGet(c echo.Context, params types.UserListGetParams) error { // Only admin can list users - user := c.Get("user") - if user.(*types.User).Name != "admin" { - c.JSON(http.StatusBadRequest, H{"message": fmt.Sprintf("Only 'admin' user can list users")}) + user, ok := c.Get("user").(*types.User) + if !ok { + c.JSON(http.StatusBadRequest, H{"message": "Not authentified"}) + return fmt.Errorf("Not authentified") + } + if user.Name != "admin" { + c.JSON(http.StatusBadRequest, H{"message": "Only 'admin' user can list users"}) return fmt.Errorf("Only 'admin' user can list users") } @@ -83,9 +93,13 @@ func (e *Processor) UserListGet(c echo.Context, params types.UserListGetParams) } func (e *Processor) UserGet(c echo.Context, name string) error { - user := c.Get("user") - if user.(*types.User).Name != "admin" { - c.JSON(http.StatusBadRequest, H{"message": fmt.Sprintf("Only 'admin' user can get user")}) + user, ok := c.Get("user").(*types.User) + if !ok { + c.JSON(http.StatusBadRequest, H{"message": "Not authentified"}) + return fmt.Errorf("Not authentified") + } + if user.Name != "admin" { + c.JSON(http.StatusBadRequest, H{"message": "Only 'admin' user can get user"}) return fmt.Errorf("Only 'admin' user can get user") } @@ -108,11 +122,11 @@ func (e *Processor) UserCreateUpdatePost(c echo.Context) error { user, ok := c.Get("user").(*types.User) if !ok { - c.JSON(http.StatusBadRequest, H{"message": fmt.Sprintf("Not authentified")}) + c.JSON(http.StatusBadRequest, H{"message": "Not authentified"}) return fmt.Errorf("Not authentified") } if user.Name != "admin" && user.Name != data.Name { - c.JSON(http.StatusBadRequest, H{"message": fmt.Sprintf("Only 'admin' user can create user and user can update itself")}) + c.JSON(http.StatusBadRequest, H{"message": "Only 'admin' user can create user and user can update itself"}) return fmt.Errorf("Only 'admin' user can create user and user can update itself") } @@ -149,9 +163,13 @@ func (e *Processor) UserCreateUpdatePost(c echo.Context) error { func (e *Processor) UserDelete(c echo.Context, name string) error { // Only admin can delete user - user := c.Get("user") - if user.(*types.User).Name != "admin" { - c.JSON(http.StatusBadRequest, H{"message": fmt.Sprintf("Only 'admin' user can delete user")}) + user, ok := c.Get("user").(*types.User) + if !ok { + c.JSON(http.StatusBadRequest, H{"message": "Not authentified"}) + return fmt.Errorf("Not authentified") + } + if user.Name != "admin" { + c.JSON(http.StatusBadRequest, H{"message": "Only 'admin' user can delete user"}) return fmt.Errorf("Only 'admin' user can delete user") } @@ -165,9 +183,13 @@ func (e *Processor) UserDelete(c echo.Context, name string) error { func (e *Processor) ResourceListGet(c echo.Context, params types.ResourceListGetParams) error { // Only admin can list the resources - user := c.Get("user") - if user.(*types.User).Name != "admin" { - c.JSON(http.StatusBadRequest, H{"message": fmt.Sprintf("Only 'admin' user can list resource")}) + user, ok := c.Get("user").(*types.User) + if !ok { + c.JSON(http.StatusBadRequest, H{"message": "Not authentified"}) + return fmt.Errorf("Not authentified") + } + if user.Name != "admin" { + c.JSON(http.StatusBadRequest, H{"message": "Only 'admin' user can list resource"}) return fmt.Errorf("Only 'admin' user can list resource") } @@ -182,9 +204,13 @@ func (e *Processor) ResourceListGet(c echo.Context, params types.ResourceListGet func (e *Processor) ResourceGet(c echo.Context, uid types.ResourceUID) error { // Only admin can get the resource directly - user := c.Get("user") - if user.(*types.User).Name != "admin" { - c.JSON(http.StatusBadRequest, H{"message": fmt.Sprintf("Only 'admin' user can get resource")}) + user, ok := c.Get("user").(*types.User) + if !ok { + c.JSON(http.StatusBadRequest, H{"message": "Not authentified"}) + return fmt.Errorf("Not authentified") + } + if user.Name != "admin" { + c.JSON(http.StatusBadRequest, H{"message": "Only 'admin' user can get resource"}) return fmt.Errorf("Only 'admin' user can get resource") } @@ -198,18 +224,32 @@ func (e *Processor) ResourceGet(c echo.Context, uid types.ResourceUID) error { } func (e *Processor) ResourceAccessPut(c echo.Context, uid types.ResourceUID) error { - // NOTE: `user` is already defined / non-nil. - user := c.Get("user") + user, ok := c.Get("user").(*types.User) + if !ok { + c.JSON(http.StatusBadRequest, H{"message": "Not authentified"}) + return fmt.Errorf("Not authentified") + } - resource, err := e.fish.ResourceGet(uid) + res, err := e.fish.ResourceGet(uid) if err != nil { c.JSON(http.StatusNotFound, H{"message": fmt.Sprintf("Resource not found: %v", err)}) return fmt.Errorf("Resource not found: %w", err) } + // Only the owner and admin can create access for application resource + app, err := e.fish.ApplicationGet(res.ApplicationUID) + if err != nil { + c.JSON(http.StatusBadRequest, H{"message": fmt.Sprintf("Unable to find the Application: %s", res.ApplicationUID)}) + return fmt.Errorf("Unable to find the Application: %s, %w", res.ApplicationUID, err) + } + if app.OwnerName != user.Name && user.Name != "admin" { + c.JSON(http.StatusBadRequest, H{"message": "Only the owner & admin can assign service mapping to the Application"}) + return fmt.Errorf("Only the owner & admin can assign service mapping to the Application") + } + r_access := types.ResourceAccess{ - ResourceUID: resource.UID, - Username: user.(*types.User).Name, + ResourceUID: res.UID, + Username: user.Name, Password: crypt.RandString(64), } e.fish.ResourceAccessCreate(&r_access) @@ -225,11 +265,15 @@ func (e *Processor) ApplicationListGet(c echo.Context, params types.ApplicationL } // Filter the output by owner - user := c.Get("user") - if user.(*types.User).Name != "admin" { + user, ok := c.Get("user").(*types.User) + if !ok { + c.JSON(http.StatusBadRequest, H{"message": "Not authentified"}) + return fmt.Errorf("Not authentified") + } + if user.Name != "admin" { var owner_out []types.Application for _, app := range out { - if app.OwnerName == user.(*types.User).Name { + if app.OwnerName == user.Name { owner_out = append(owner_out, app) } } @@ -247,9 +291,13 @@ func (e *Processor) ApplicationGet(c echo.Context, uid types.ApplicationUID) err } // Only the owner of the application (or admin) can request it - user := c.Get("user") - if app.OwnerName != user.(*types.User).Name && user.(*types.User).Name != "admin" { - c.JSON(http.StatusBadRequest, H{"message": fmt.Sprintf("Only the owner and admin can request the Application")}) + user, ok := c.Get("user").(*types.User) + if !ok { + c.JSON(http.StatusBadRequest, H{"message": "Not authentified"}) + return fmt.Errorf("Not authentified") + } + if app.OwnerName != user.Name && user.Name != "admin" { + c.JSON(http.StatusBadRequest, H{"message": "Only the owner and admin can request the Application"}) return fmt.Errorf("Only the owner and admin can request the Application") } @@ -264,8 +312,12 @@ func (e *Processor) ApplicationCreatePost(c echo.Context) error { } // Set the User field out of the authorized user - user := c.Get("user") - data.OwnerName = user.(*types.User).Name + user, ok := c.Get("user").(*types.User) + if !ok { + c.JSON(http.StatusBadRequest, H{"message": "Not authentified"}) + return fmt.Errorf("Not authentified") + } + data.OwnerName = user.Name if err := e.fish.ApplicationCreate(&data); err != nil { c.JSON(http.StatusBadRequest, H{"message": fmt.Sprintf("Unable to create application: %v", err)}) @@ -283,9 +335,13 @@ func (e *Processor) ApplicationResourceGet(c echo.Context, uid types.Application } // Only the owner of the application (or admin) can request the resource - user := c.Get("user") - if app.OwnerName != user.(*types.User).Name && user.(*types.User).Name != "admin" { - c.JSON(http.StatusBadRequest, H{"message": fmt.Sprintf("Only the owner and admin can request the Application resource")}) + user, ok := c.Get("user").(*types.User) + if !ok { + c.JSON(http.StatusBadRequest, H{"message": "Not authentified"}) + return fmt.Errorf("Not authentified") + } + if app.OwnerName != user.Name && user.Name != "admin" { + c.JSON(http.StatusBadRequest, H{"message": "Only the owner and admin can request the Application resource"}) return fmt.Errorf("Only the owner and admin can request the Application resource") } @@ -306,9 +362,13 @@ func (e *Processor) ApplicationStateGet(c echo.Context, uid types.ApplicationUID } // Only the owner of the application (or admin) can request the status - user := c.Get("user") - if app.OwnerName != user.(*types.User).Name && user.(*types.User).Name != "admin" { - c.JSON(http.StatusBadRequest, H{"message": fmt.Sprintf("Only the owner and admin can request the Application status")}) + user, ok := c.Get("user").(*types.User) + if !ok { + c.JSON(http.StatusBadRequest, H{"message": "Not authentified"}) + return fmt.Errorf("Not authentified") + } + if app.OwnerName != user.Name && user.Name != "admin" { + c.JSON(http.StatusBadRequest, H{"message": "Only the owner and admin can request the Application status"}) return fmt.Errorf("Only the owner and admin can request the Application status") } @@ -329,9 +389,13 @@ func (e *Processor) ApplicationTaskListGet(c echo.Context, app_uid types.Applica } // Only the owner of the application (or admin) could get the tasks - user := c.Get("user") - if app.OwnerName != user.(*types.User).Name && user.(*types.User).Name != "admin" { - c.JSON(http.StatusBadRequest, H{"message": fmt.Sprintf("Only the owner of Application & admin can get the Application Tasks")}) + user, ok := c.Get("user").(*types.User) + if !ok { + c.JSON(http.StatusBadRequest, H{"message": "Not authentified"}) + return fmt.Errorf("Not authentified") + } + if app.OwnerName != user.Name && user.Name != "admin" { + c.JSON(http.StatusBadRequest, H{"message": "Only the owner of Application & admin can get the Application Tasks"}) return fmt.Errorf("Only the owner of Application & admin can get the Application Tasks") } @@ -352,9 +416,13 @@ func (e *Processor) ApplicationTaskCreatePost(c echo.Context, app_uid types.Appl } // Only the owner of the application (or admin) could create the tasks - user := c.Get("user") - if app.OwnerName != user.(*types.User).Name && user.(*types.User).Name != "admin" { - c.JSON(http.StatusBadRequest, H{"message": fmt.Sprintf("Only the owner of Application & admin can create the Application Tasks")}) + user, ok := c.Get("user").(*types.User) + if !ok { + c.JSON(http.StatusBadRequest, H{"message": "Not authentified"}) + return fmt.Errorf("Not authentified") + } + if app.OwnerName != user.Name && user.Name != "admin" { + c.JSON(http.StatusBadRequest, H{"message": "Only the owner of Application & admin can create the Application Tasks"}) return fmt.Errorf("Only the owner of Application & admin can create the Application Tasks") } @@ -389,9 +457,13 @@ func (e *Processor) ApplicationTaskGet(c echo.Context, task_uid types.Applicatio } // Only the owner of the application (or admin) could get the attached task - user := c.Get("user") - if app.OwnerName != user.(*types.User).Name && user.(*types.User).Name != "admin" { - c.JSON(http.StatusBadRequest, H{"message": fmt.Sprintf("Only the owner of Application & admin can get the ApplicationTask")}) + user, ok := c.Get("user").(*types.User) + if !ok { + c.JSON(http.StatusBadRequest, H{"message": "Not authentified"}) + return fmt.Errorf("Not authentified") + } + if app.OwnerName != user.Name && user.Name != "admin" { + c.JSON(http.StatusBadRequest, H{"message": "Only the owner of Application & admin can get the ApplicationTask"}) return fmt.Errorf("Only the owner of Application & admin can get the ApplicationTask") } @@ -406,9 +478,13 @@ func (e *Processor) ApplicationDeallocateGet(c echo.Context, uid types.Applicati } // Only the owner of the application (or admin) could deallocate it - user := c.Get("user") - if app.OwnerName != user.(*types.User).Name && user.(*types.User).Name != "admin" { - c.JSON(http.StatusBadRequest, H{"message": fmt.Sprintf("Only the owner & admin can deallocate the Application resource")}) + user, ok := c.Get("user").(*types.User) + if !ok { + c.JSON(http.StatusBadRequest, H{"message": "Not authentified"}) + return fmt.Errorf("Not authentified") + } + if app.OwnerName != user.Name && user.Name != "admin" { + c.JSON(http.StatusBadRequest, H{"message": "Only the owner & admin can deallocate the Application resource"}) return fmt.Errorf("Only the owner & admin can deallocate the Application resource") } @@ -428,7 +504,7 @@ func (e *Processor) ApplicationDeallocateGet(c echo.Context, uid types.Applicati new_status = types.ApplicationStatusRECALLED } as := &types.ApplicationState{ApplicationUID: uid, Status: new_status, - Description: fmt.Sprintf("Requested by user %s", user.(*types.User).Name), + Description: fmt.Sprintf("Requested by user %s", user.Name), } err = e.fish.ApplicationStateCreate(as) if err != nil { @@ -461,9 +537,13 @@ func (e *Processor) LabelGet(c echo.Context, uid types.LabelUID) error { func (e *Processor) LabelCreatePost(c echo.Context) error { // Only admin can create label - user := c.Get("user") - if user.(*types.User).Name != "admin" { - c.JSON(http.StatusBadRequest, H{"message": fmt.Sprintf("Only 'admin' user can create label")}) + user, ok := c.Get("user").(*types.User) + if !ok { + c.JSON(http.StatusBadRequest, H{"message": "Not authentified"}) + return fmt.Errorf("Not authentified") + } + if user.Name != "admin" { + c.JSON(http.StatusBadRequest, H{"message": "Only 'admin' user can create label"}) return fmt.Errorf("Only 'admin' user can create label") } @@ -482,9 +562,13 @@ func (e *Processor) LabelCreatePost(c echo.Context) error { func (e *Processor) LabelDelete(c echo.Context, uid types.LabelUID) error { // Only admin can delete label - user := c.Get("user") - if user.(*types.User).Name != "admin" { - c.JSON(http.StatusBadRequest, H{"message": fmt.Sprintf("Only 'admin' user can delete Label")}) + user, ok := c.Get("user").(*types.User) + if !ok { + c.JSON(http.StatusBadRequest, H{"message": "Not authentified"}) + return fmt.Errorf("Not authentified") + } + if user.Name != "admin" { + c.JSON(http.StatusBadRequest, H{"message": "Only 'admin' user can delete Label"}) return fmt.Errorf("Only 'admin' user can delete label") } @@ -514,9 +598,13 @@ func (e *Processor) NodeThisGet(c echo.Context) error { } func (e *Processor) NodeThisMaintenanceGet(c echo.Context, params types.NodeThisMaintenanceGetParams) error { - user := c.Get("user") - if user.(*types.User).Name != "admin" { - c.JSON(http.StatusBadRequest, H{"message": fmt.Sprintf("Only 'admin' can set node maintenance")}) + user, ok := c.Get("user").(*types.User) + if !ok { + c.JSON(http.StatusBadRequest, H{"message": "Not authentified"}) + return fmt.Errorf("Not authentified") + } + if user.Name != "admin" { + c.JSON(http.StatusBadRequest, H{"message": "Only 'admin' can set node maintenance"}) return fmt.Errorf("Only 'admin' user can set node maintenance") } @@ -550,11 +638,14 @@ func (e *Processor) NodeThisProfilingIndexGet(c echo.Context) error { } func (e *Processor) NodeThisProfilingGet(c echo.Context, handler string) error { - user := c.Get("user") - if user.(*types.User).Name != "admin" { - message := "Only 'admin' can see profiling info" - c.JSON(http.StatusBadRequest, H{"message": message}) - return fmt.Errorf(message) + user, ok := c.Get("user").(*types.User) + if !ok { + c.JSON(http.StatusBadRequest, H{"message": "Not authentified"}) + return fmt.Errorf("Not authentified") + } + if user.Name != "admin" { + c.JSON(http.StatusBadRequest, H{"message": "Only 'admin' can see profiling info"}) + return fmt.Errorf("Only 'admin' can see profiling info") } switch handler { @@ -573,18 +664,21 @@ func (e *Processor) NodeThisProfilingGet(c echo.Context, handler string) error { case "trace": pprof.Trace(c.Response(), c.Request()) default: - message := "Unable to find requested profiling handler" - c.JSON(http.StatusNotFound, H{"message": message}) - return fmt.Errorf(message) + c.JSON(http.StatusNotFound, H{"message": "Unable to find requested profiling handler"}) + return fmt.Errorf("Unable to find requested profiling handler") } return nil } func (e *Processor) VoteListGet(c echo.Context, params types.VoteListGetParams) error { - user := c.Get("user") - if user.(*types.User).Name != "admin" { - c.JSON(http.StatusBadRequest, H{"message": fmt.Sprintf("Only 'admin' user can get votes")}) + user, ok := c.Get("user").(*types.User) + if !ok { + c.JSON(http.StatusBadRequest, H{"message": "Not authentified"}) + return fmt.Errorf("Not authentified") + } + if user.Name != "admin" { + c.JSON(http.StatusBadRequest, H{"message": "Only 'admin' user can get votes"}) return fmt.Errorf("Only 'admin' user can get votes") } @@ -598,9 +692,13 @@ func (e *Processor) VoteListGet(c echo.Context, params types.VoteListGetParams) } func (e *Processor) LocationListGet(c echo.Context, params types.LocationListGetParams) error { - user := c.Get("user") - if user.(*types.User).Name != "admin" { - c.JSON(http.StatusBadRequest, H{"message": fmt.Sprintf("Only 'admin' user can get locations")}) + user, ok := c.Get("user").(*types.User) + if !ok { + c.JSON(http.StatusBadRequest, H{"message": "Not authentified"}) + return fmt.Errorf("Not authentified") + } + if user.Name != "admin" { + c.JSON(http.StatusBadRequest, H{"message": "Only 'admin' user can get locations"}) return fmt.Errorf("Only 'admin' user can get locations") } @@ -614,9 +712,13 @@ func (e *Processor) LocationListGet(c echo.Context, params types.LocationListGet } func (e *Processor) LocationCreatePost(c echo.Context) error { - user := c.Get("user") - if user.(*types.User).Name != "admin" { - c.JSON(http.StatusBadRequest, H{"message": fmt.Sprintf("Only 'admin' user can create location")}) + user, ok := c.Get("user").(*types.User) + if !ok { + c.JSON(http.StatusBadRequest, H{"message": "Not authentified"}) + return fmt.Errorf("Not authentified") + } + if user.Name != "admin" { + c.JSON(http.StatusBadRequest, H{"message": "Only 'admin' user can create location"}) return fmt.Errorf("Only 'admin' user can create location") } @@ -635,9 +737,13 @@ func (e *Processor) LocationCreatePost(c echo.Context) error { } func (e *Processor) ServiceMappingGet(c echo.Context, uid types.ServiceMappingUID) error { - user := c.Get("user") - if user.(*types.User).Name != "admin" { - c.JSON(http.StatusBadRequest, H{"message": fmt.Sprintf("Only 'admin' user can get service mapping")}) + user, ok := c.Get("user").(*types.User) + if !ok { + c.JSON(http.StatusBadRequest, H{"message": "Not authentified"}) + return fmt.Errorf("Not authentified") + } + if user.Name != "admin" { + c.JSON(http.StatusBadRequest, H{"message": "Only 'admin' user can get service mapping"}) return fmt.Errorf("Only 'admin' user can get service mapping") } @@ -651,9 +757,13 @@ func (e *Processor) ServiceMappingGet(c echo.Context, uid types.ServiceMappingUI } func (e *Processor) ServiceMappingListGet(c echo.Context, params types.ServiceMappingListGetParams) error { - user := c.Get("user") - if user.(*types.User).Name != "admin" { - c.JSON(http.StatusBadRequest, H{"message": fmt.Sprintf("Only 'admin' user can get service mappings")}) + user, ok := c.Get("user").(*types.User) + if !ok { + c.JSON(http.StatusBadRequest, H{"message": "Not authentified"}) + return fmt.Errorf("Not authentified") + } + if user.Name != "admin" { + c.JSON(http.StatusBadRequest, H{"message": "Only 'admin' user can get service mappings"}) return fmt.Errorf("Only 'admin' user can get service mappings") } @@ -673,7 +783,11 @@ func (e *Processor) ServiceMappingCreatePost(c echo.Context) error { return fmt.Errorf("Wrong request body: %w", err) } - user := c.Get("user") + user, ok := c.Get("user").(*types.User) + if !ok { + c.JSON(http.StatusBadRequest, H{"message": "Not authentified"}) + return fmt.Errorf("Not authentified") + } if data.ApplicationUID != uuid.Nil { // Only the owner and admin can create servicemapping for his application app, err := e.fish.ApplicationGet(data.ApplicationUID) @@ -682,12 +796,12 @@ func (e *Processor) ServiceMappingCreatePost(c echo.Context) error { return fmt.Errorf("Unable to find the Application: %s, %w", data.ApplicationUID, err) } - if app.OwnerName != user.(*types.User).Name && user.(*types.User).Name != "admin" { - c.JSON(http.StatusBadRequest, H{"message": fmt.Sprintf("Only the owner & admin can assign service mapping to the Application")}) + if app.OwnerName != user.Name && user.Name != "admin" { + c.JSON(http.StatusBadRequest, H{"message": "Only the owner & admin can assign service mapping to the Application"}) return fmt.Errorf("Only the owner & admin can assign service mapping to the Application") } - } else if user.(*types.User).Name != "admin" { - c.JSON(http.StatusBadRequest, H{"message": fmt.Sprintf("Only 'admin' user can create service mapping with undefined Application")}) + } else if user.Name != "admin" { + c.JSON(http.StatusBadRequest, H{"message": "Only 'admin' user can create service mapping with undefined Application"}) return fmt.Errorf("Only 'admin' user can create service mapping with undefined Application") } @@ -701,9 +815,13 @@ func (e *Processor) ServiceMappingCreatePost(c echo.Context) error { func (e *Processor) ServiceMappingDelete(c echo.Context, uid types.ServiceMappingUID) error { // Only admin can delete ServiceMapping - user := c.Get("user") - if user.(*types.User).Name != "admin" { - c.JSON(http.StatusBadRequest, H{"message": fmt.Sprintf("Only 'admin' user can delete service mapping")}) + user, ok := c.Get("user").(*types.User) + if !ok { + c.JSON(http.StatusBadRequest, H{"message": "Not authentified"}) + return fmt.Errorf("Not authentified") + } + if user.Name != "admin" { + c.JSON(http.StatusBadRequest, H{"message": "Only 'admin' user can delete service mapping"}) return fmt.Errorf("Only 'admin' user can delete service mapping") } diff --git a/lib/openapi/cluster/client.go b/lib/openapi/cluster/client.go deleted file mode 100644 index e78b7e7..0000000 --- a/lib/openapi/cluster/client.go +++ /dev/null @@ -1,135 +0,0 @@ -/** - * Copyright 2021 Adobe. All rights reserved. - * This file is licensed to you under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. You may obtain a copy - * of the License at http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under - * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS - * OF ANY KIND, either express or implied. See the License for the specific language - * governing permissions and limitations under the License. - */ - -package cluster - -import ( - "bytes" - "fmt" - "time" - - "github.com/gorilla/websocket" - // "github.com/adobe/aquarium-fish/lib/cluster/msg" -) - -const ( - // Time allowed to write a message to the peer. - writeWait = 10 * time.Second - - // Time allowed to read the next pong message from the peer. - pongWait = 60 * time.Second - - // Send pings to peer with this period. Must be less than pongWait. - pingPeriod = (pongWait * 9) / 10 - - // Maximum message size allowed from peer. - maxMessageSize = 512 -) - -var ( - newline = []byte{'\n'} - space = []byte{' '} -) - -// Client is a middleman between the websocket connection and the hub. -type Client struct { - hub *Hub - - // The websocket connection. - conn *websocket.Conn - - // Buffered channel of outbound messages. - send chan []byte -} - -// readPump pumps messages from the websocket connection to the hub. -// -// The application runs readPump in a per-connection goroutine. The application -// ensures that there is at most one reader on a connection by executing all -// reads from this goroutine. -func (c *Client) readPump() { - defer func() { - c.hub.unregister <- c - c.conn.Close() - }() - c.conn.SetReadLimit(maxMessageSize) - c.conn.SetReadDeadline(time.Now().Add(pongWait)) - c.conn.SetPongHandler(func(string) error { c.conn.SetReadDeadline(time.Now().Add(pongWait)); return nil }) - for { - _, message, err := c.conn.ReadMessage() - if err != nil { - if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway, websocket.CloseAbnormalClosure) { - fmt.Printf("Cluster: Client %v readPump: reading error\n", c.conn.RemoteAddr()) - } - break - } - message = bytes.TrimSpace(bytes.Replace(message, newline, space, -1)) - //fmt.Printf("Cluster: Client %v readPump: got: %s\n", c.conn.RemoteAddr(), message) - c.hub.broadcast <- message - } -} - -// writePump pumps messages from the hub to the websocket connection. -// -// A goroutine running writePump is started for each connection. The -// application ensures that there is at most one writer to a connection by -// executing all writes from this goroutine. -func (c *Client) writePump() { - ticker := time.NewTicker(pingPeriod) - defer func() { - ticker.Stop() - c.conn.Close() - }() - for { - select { - case message, ok := <-c.send: - c.conn.SetWriteDeadline(time.Now().Add(writeWait)) - if !ok { - // The hub closed the channel. - c.conn.WriteMessage(websocket.CloseMessage, []byte{}) - return - } - - w, err := c.conn.NextWriter(websocket.TextMessage) - if err != nil { - return - } - w.Write(message) - - // Add queued chat messages to the current websocket message. - n := len(c.send) - for i := 0; i < n; i++ { - w.Write(newline) - w.Write(<-c.send) - } - - if err := w.Close(); err != nil { - return - } - case <-ticker.C: - c.conn.SetWriteDeadline(time.Now().Add(writeWait)) - if err := c.conn.WriteMessage(websocket.PingMessage, nil); err != nil { - return - } - } - } -} - -// Init the connection by send the active data to just connected node -func (c *Client) init() error { - // TODO - //active_nodes := msg.NewNodes() - //active_apps := msg.NewApplications() - //last_votes := msg.NewVotes() - //c.conn.Write() - return nil -} diff --git a/lib/openapi/cluster/cluster_v1.go b/lib/openapi/cluster/cluster_v1.go deleted file mode 100644 index 220e789..0000000 --- a/lib/openapi/cluster/cluster_v1.go +++ /dev/null @@ -1,126 +0,0 @@ -/** - * Copyright 2021 Adobe. All rights reserved. - * This file is licensed to you under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. You may obtain a copy - * of the License at http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under - * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS - * OF ANY KIND, either express or implied. See the License for the specific language - * governing permissions and limitations under the License. - */ - -package cluster - -import ( - "crypto/x509" - "fmt" - "net/http" - - "github.com/gorilla/websocket" - "github.com/labstack/echo/v4" - - "github.com/adobe/aquarium-fish/lib/cluster" - "github.com/adobe/aquarium-fish/lib/fish" - "github.com/adobe/aquarium-fish/lib/log" -) - -// H is a shortcut for map[string]any -type H map[string]any - -type Processor struct { - fish *fish.Fish - upgrader websocket.Upgrader - - hub *Hub -} - -func NewV1Router(e *echo.Echo, fish *fish.Fish, cl *cluster.Cluster) { - hub := &Hub{ - broadcast: make(chan []byte), - register: make(chan *Client), - unregister: make(chan *Client), - clients: make(map[*Client]bool), - } - go hub.Run() - proc := &Processor{ - fish: fish, - upgrader: websocket.Upgrader{ - EnableCompression: true, - }, - hub: hub, - } - router := e.Group("") - router.Use( - // The connected client should have valid cluster signed certificate - proc.ClientCertAuth, - ) - router.GET("/cluster/v1/connect", proc.ClusterConnect) -} - -func (e *Processor) ClientCertAuth(next echo.HandlerFunc) echo.HandlerFunc { - return func(c echo.Context) error { - // The connecting client should have the valid to cluster CA certificate, with the CN of - // the node name, pubkey need be the same as stored (or first time registration) in cluster - // nodes table and the time of last ping need to be more than ping delay time x2 - - if len(c.Request().TLS.PeerCertificates) == 0 { - return echo.NewHTTPError(http.StatusUnauthorized, "Client certificate is not provided") - } - - var valid_client_cert *x509.Certificate - for _, crt := range c.Request().TLS.PeerCertificates { - // Validation over cluster CA cert - opts := x509.VerifyOptions{ - Roots: c.Echo().TLSServer.TLSConfig.ClientCAs, - KeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth}, - } - _, err := crt.Verify(opts) - if err != nil { - log.Warn(fmt.Sprintf("Cluster: Client %s (%s) certificate CA verify failed:", - crt.Subject.CommonName, c.RealIP()), err) - continue - } - - // TODO: Check the node in db by CA as NodeName and if exists compare the pubkey - log.Debug("Cluster: Client certificate CN:", crt.Subject.CommonName) - der, err := x509.MarshalPKIXPublicKey(crt.PublicKey) - if err != nil { - continue - } - log.Debug("Cluster: Client certificate pubkey der:", der) - - valid_client_cert = crt - } - - if valid_client_cert == nil { - return echo.NewHTTPError(http.StatusUnauthorized, "Client certificate is invalid") - } - - c.Set("client_cert", valid_client_cert) - - //res, err := e.fish.ResourceGetByIP(c.RealIP()) - //if err != nil { - // return echo.NewHTTPError(http.StatusUnauthorized, "Client IP was not found in the node Resources") - //} - - return next(c) - } -} - -func (e *Processor) ClusterConnect(c echo.Context) error { - ws, err := e.upgrader.Upgrade(c.Response(), c.Request(), nil) - if err != nil { - c.JSON(http.StatusBadRequest, H{"message": fmt.Sprintf("Unable to connect with the cluster: %v", err)}) - return fmt.Errorf("Unable to connect with the cluster: %w", err) - } - client := &Client{hub: e.hub, conn: ws, send: make(chan []byte, 256)} - e.hub.register <- client - - // Starting the new connected client processes - go client.writePump() - go client.readPump() - go client.init() - - return nil -} diff --git a/lib/openapi/cluster/hub.go b/lib/openapi/cluster/hub.go deleted file mode 100644 index 7c79551..0000000 --- a/lib/openapi/cluster/hub.go +++ /dev/null @@ -1,57 +0,0 @@ -/** - * Copyright 2021 Adobe. All rights reserved. - * This file is licensed to you under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. You may obtain a copy - * of the License at http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under - * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS - * OF ANY KIND, either express or implied. See the License for the specific language - * governing permissions and limitations under the License. - */ - -package cluster - -import ( - "fmt" -) - -// Hub maintains the set of active clients and broadcasts messages to the -// clients. -type Hub struct { - // Registered clients. - clients map[*Client]bool - - // Inbound messages from the clients. - broadcast chan []byte - - // Register requests from the clients. - register chan *Client - - // Unregister requests from clients. - unregister chan *Client -} - -func (h *Hub) Run() { - for { - select { - case client := <-h.register: - h.clients[client] = true - case client := <-h.unregister: - if _, ok := h.clients[client]; ok { - delete(h.clients, client) - close(client.send) - fmt.Println("Cluster: Hub: connection closed") - } - case <-h.broadcast: - for client := range h.clients { - select { - case client.send <- []byte("acknowledge"): - default: - close(client.send) - delete(h.clients, client) - } - } - } - } -} diff --git a/lib/openapi/openapi.go b/lib/openapi/openapi.go index aeda097..81eca76 100644 --- a/lib/openapi/openapi.go +++ b/lib/openapi/openapi.go @@ -33,11 +33,9 @@ import ( _ "github.com/oapi-codegen/oapi-codegen/v2/pkg/util" "gopkg.in/yaml.v3" - "github.com/adobe/aquarium-fish/lib/cluster" "github.com/adobe/aquarium-fish/lib/fish" "github.com/adobe/aquarium-fish/lib/log" "github.com/adobe/aquarium-fish/lib/openapi/api" - cluster_server "github.com/adobe/aquarium-fish/lib/openapi/cluster" "github.com/adobe/aquarium-fish/lib/openapi/meta" ) @@ -66,7 +64,7 @@ func (cb *YamlBinder) Bind(i any, c echo.Context) (err error) { return } -func Init(fish *fish.Fish, cl *cluster.Cluster, api_address, ca_path, cert_path, key_path string) (*http.Server, error) { +func Init(fish *fish.Fish, api_address, ca_path, cert_path, key_path string) (*http.Server, error) { swagger, err := GetSwagger() if err != nil { return nil, fmt.Errorf("Fish OpenAPI: Error loading swagger spec: %w", err) @@ -86,9 +84,8 @@ func Init(fish *fish.Fish, cl *cluster.Cluster, api_address, ca_path, cert_path, router.HideBanner = true // TODO: Probably it will be a feature an ability to separate those - // routers to independance ports if needed + // routers to independence ports if needed meta.NewV1Router(router, fish) - cluster_server.NewV1Router(router, fish, cl) api.NewV1Router(router, fish) // TODO: web UI router @@ -98,11 +95,10 @@ func Init(fish *fish.Fish, cl *cluster.Cluster, api_address, ca_path, cert_path, } s := router.TLSServer s.Addr = api_address - s.TLSConfig = &tls.Config{ + s.TLSConfig = &tls.Config{ // #nosec G402 , keep the compatibility high since not public access ClientAuth: tls.RequestClientCert, // Need for the client certificate auth ClientCAs: ca_pool, // Verify client certificate with the cluster CA } - s.TLSConfig.BuildNameToCertificate() errChan := make(chan error) go func() { addr := s.Addr diff --git a/lib/openapi/types/node.go b/lib/openapi/types/node.go index 58e2f06..6957147 100644 --- a/lib/openapi/types/node.go +++ b/lib/openapi/types/node.go @@ -22,7 +22,7 @@ import ( const NODE_PING_DELAY = 10 -var NodePingDuplicationErr = fmt.Errorf("Fish Node: Unable to join the Aquarium cluster due to " + +var ErrNodePingDuplication = fmt.Errorf("Fish Node: Unable to join the Aquarium cluster due to " + "the node with the same name pinged the cluster less then 2xNODE_PING_DELAY time ago") func (n *Node) Init(node_address, cert_path string) error { @@ -51,7 +51,7 @@ func (n *Node) Init(node_address, cert_path string) error { n.Pubkey = &pubkey_der } else { // Validate the existing pubkey - if bytes.Compare(*n.Pubkey, pubkey_der) != 0 { + if !bytes.Equal(*n.Pubkey, pubkey_der) { return fmt.Errorf("Fish Node: The pubkey was changed for Node, that's not supported") } } diff --git a/lib/openapi/types/resources.go b/lib/openapi/types/resources.go index b735b60..e39d4d1 100644 --- a/lib/openapi/types/resources.go +++ b/lib/openapi/types/resources.go @@ -62,7 +62,7 @@ func (r *Resources) Validate(disk_types []string, check_net bool) error { return fmt.Errorf("Resources: Disk name can't be empty") } if len(disk_types) > 0 && !util.Contains(disk_types, disk.Type) { - return fmt.Errorf(fmt.Sprintf("Resources: Type of disk must be one of: %+q", disk_types)) + return fmt.Errorf("Resources: Type of disk must be one of: %+q", disk_types) } if disk.Size < 1 { return fmt.Errorf("Resources: Size of the disk can't be less than 1GB") diff --git a/lib/proxy_ssh/proxy.go b/lib/proxy_ssh/proxy.go index b81f1d1..18187b6 100644 --- a/lib/proxy_ssh/proxy.go +++ b/lib/proxy_ssh/proxy.go @@ -118,7 +118,7 @@ func (p *ProxyAccess) serveConnection(conn net.Conn, serverConfig *ssh.ServerCon Auth: []ssh.AuthMethod{ ssh.Password(resource.Authentication.Password), }, - HostKeyCallback: ssh.InsecureIgnoreHostKey(), + HostKeyCallback: ssh.InsecureIgnoreHostKey(), // #nosec G106 , remote always have new hostkey by design } remoteConn, err := ssh.Dial("tcp", remoteAddr, remoteConfig) if err != nil { diff --git a/lib/util/file_replace_token.go b/lib/util/file_replace_token.go index cf6e7d5..405eb07 100644 --- a/lib/util/file_replace_token.go +++ b/lib/util/file_replace_token.go @@ -16,7 +16,6 @@ import ( "bufio" "fmt" "io" - "io/ioutil" "os" "path/filepath" "strings" @@ -36,7 +35,7 @@ func FileReplaceToken(path string, full_line, add, anycase bool, token_values .. } // Open output file - out_f, err := ioutil.TempFile(filepath.Dir(path), "tmp") + out_f, err := os.CreateTemp(filepath.Dir(path), "tmp") if err != nil { return err } diff --git a/lib/util/file_replace_token_test.go b/lib/util/file_replace_token_test.go index e676ada..ebe1470 100644 --- a/lib/util/file_replace_token_test.go +++ b/lib/util/file_replace_token_test.go @@ -31,7 +31,7 @@ func Test_file_replace_token_simple_proceed(t *testing.T) { "test4 test5 test6\n" + "test7 test8 test9\n") - os.WriteFile(tmp_file, in_data, 0644) + os.WriteFile(tmp_file, in_data, 0o644) FileReplaceToken(tmp_file, false, false, false, @@ -57,7 +57,7 @@ func Test_file_replace_token_simple_skip_uppercase_src(t *testing.T) { "test4 test6\n" + "test7 test8 test9\n") - os.WriteFile(tmp_file, in_data, 0644) + os.WriteFile(tmp_file, in_data, 0o644) FileReplaceToken(tmp_file, false, false, false, @@ -83,7 +83,7 @@ func Test_file_replace_token_simple_skip_uppercase_token(t *testing.T) { "test4 test6\n" + "test7 test8 test9\n") - os.WriteFile(tmp_file, in_data, 0644) + os.WriteFile(tmp_file, in_data, 0o644) FileReplaceToken(tmp_file, false, false, false, @@ -109,7 +109,7 @@ func Test_file_replace_token_anycase_token_proceed(t *testing.T) { "test4 test5 test6\n" + "test7 test8 test9\n") - os.WriteFile(tmp_file, in_data, 0644) + os.WriteFile(tmp_file, in_data, 0o644) FileReplaceToken(tmp_file, false, false, true, @@ -135,7 +135,7 @@ func Test_file_replace_token_anycase_src_proceed(t *testing.T) { "test4 test5 test6\n" + "test7 test8 test9\n") - os.WriteFile(tmp_file, in_data, 0644) + os.WriteFile(tmp_file, in_data, 0o644) FileReplaceToken(tmp_file, false, false, true, @@ -161,7 +161,7 @@ func Test_file_replace_token_anycase_multiple(t *testing.T) { "test4 test5 test5 test6\n" + "test7 test8 test9\n") - os.WriteFile(tmp_file, in_data, 0644) + os.WriteFile(tmp_file, in_data, 0o644) FileReplaceToken(tmp_file, false, false, true, @@ -186,7 +186,7 @@ func Test_file_replace_token_add(t *testing.T) { "test4 test5 test6\n" + "test5\n") - os.WriteFile(tmp_file, in_data, 0644) + os.WriteFile(tmp_file, in_data, 0o644) FileReplaceToken(tmp_file, false, true, false, @@ -212,7 +212,7 @@ func Test_file_replace_token_do_not_add_if_replaced(t *testing.T) { "test4 test5 test6\n" + "test7 test8 test9\n") - os.WriteFile(tmp_file, in_data, 0644) + os.WriteFile(tmp_file, in_data, 0o644) FileReplaceToken(tmp_file, false, true, false, @@ -238,7 +238,7 @@ func Test_file_replace_token_full_line(t *testing.T) { "test5\n" + "test7 test8 test9\n") - os.WriteFile(tmp_file, in_data, 0644) + os.WriteFile(tmp_file, in_data, 0o644) FileReplaceToken(tmp_file, true, false, false, diff --git a/lib/util/file_starts_with_test.go b/lib/util/file_starts_with_test.go index 55a83aa..80cde10 100644 --- a/lib/util/file_starts_with_test.go +++ b/lib/util/file_starts_with_test.go @@ -25,7 +25,7 @@ func TestFileStartsWithGood(t *testing.T) { "test1 test2 test3\n" + "test4 test5 test6\n") - os.WriteFile(tmp_file, in_data, 0644) + os.WriteFile(tmp_file, in_data, 0o644) if err := FileStartsWith(tmp_file, []byte("test1 ")); err != nil { t.Fatalf(`FileStartsWith("test1 ") = %v, want: nil`, err) @@ -39,7 +39,7 @@ func TestFileStartsNotEqual(t *testing.T) { "test1 test2 test3\n" + "test4 test5 test6\n") - os.WriteFile(tmp_file, in_data, 0644) + os.WriteFile(tmp_file, in_data, 0o644) if err := FileStartsWith(tmp_file, []byte("test2 ")); err != ErrFileStartsWithNotEqual { t.Fatalf(`FileStartsWith("test2 ") = %v, want: %v`, err, ErrFileStartsWithNotEqual) @@ -59,7 +59,7 @@ func TestFileStartsSmall(t *testing.T) { in_data := []byte("small file\n") - os.WriteFile(tmp_file, in_data, 0644) + os.WriteFile(tmp_file, in_data, 0o644) if err := FileStartsWith(tmp_file, []byte("biiiiiiiiiig prefix")); err != ErrFileStartsWithFileTooSmall { t.Fatalf(`FileStartsWith("test2 ") = %v, want: %v`, err, ErrFileStartsWithFileTooSmall) diff --git a/lib/util/human_size.go b/lib/util/human_size.go index a6cc4d0..f3b1bd0 100644 --- a/lib/util/human_size.go +++ b/lib/util/human_size.go @@ -51,8 +51,8 @@ func (hs *HumanSize) UnmarshalText(data []byte) error { // Detecting unit & multiplier var mult HumanSize = 0 - unit := "" - unit_len := 0 + var unit string + var unit_len int if length > 1 { unit = input[length-2:] unit_len = 2 @@ -82,13 +82,10 @@ func (hs *HumanSize) UnmarshalText(data []byte) error { unit_len = 1 } else if unit[1] >= '0' && unit[1] <= '9' { unit_len = 0 - } else { - mult = 0 } } else { unit_len = 0 } - unit = "B" mult = B } } @@ -119,7 +116,7 @@ func (hs HumanSize) Bytes() uint64 { func (hs HumanSize) String() string { switch { case hs == 0: - return fmt.Sprint("0B") + return "0B" case hs%EB == 0: return fmt.Sprintf("%dEB", hs/EB) case hs%PB == 0: diff --git a/lib/util/lock.go b/lib/util/lock.go index a6339f2..93a196a 100644 --- a/lib/util/lock.go +++ b/lib/util/lock.go @@ -33,7 +33,8 @@ func CreateLock(lock_path string) error { } // Writing pid into the file for additional info - lock_file.Write([]byte(fmt.Sprintf("%d", os.Getpid()))) + data := []byte(fmt.Sprintf("%d", os.Getpid())) + lock_file.Write(data) lock_file.Close() return nil diff --git a/lib/util/metadata_processing.go b/lib/util/metadata_processing.go index a49f22b..5048ce6 100644 --- a/lib/util/metadata_processing.go +++ b/lib/util/metadata_processing.go @@ -16,7 +16,7 @@ func SerializeMetadata(format, prefix string, data map[string]any) (out []byte, case "env": // Plain format suitable to use in shell m := DotSerialize(prefix, data) for key, val := range m { - line := cleanShellKey(strings.Replace(shellescape.StripUnsafe(key), ".", "_", -1)) + line := cleanShellKey(strings.ReplaceAll(shellescape.StripUnsafe(key), ".", "_")) if len(line) == 0 { continue } @@ -26,7 +26,7 @@ func SerializeMetadata(format, prefix string, data map[string]any) (out []byte, case "export": // Format env with exports for easy usage with source m := DotSerialize(prefix, data) for key, val := range m { - line := cleanShellKey(strings.Replace(shellescape.StripUnsafe(key), ".", "_", -1)) + line := cleanShellKey(strings.ReplaceAll(shellescape.StripUnsafe(key), ".", "_")) if len(line) == 0 { continue } @@ -37,12 +37,12 @@ func SerializeMetadata(format, prefix string, data map[string]any) (out []byte, case "ps1": // Plain format suitable to use in powershell m := DotSerialize(prefix, data) for key, val := range m { - line := cleanShellKey(strings.Replace(shellescape.StripUnsafe(key), ".", "_", -1)) + line := cleanShellKey(strings.ReplaceAll(shellescape.StripUnsafe(key), ".", "_")) if len(line) == 0 { continue } // Shell quote is not applicable here, so using the custom one - value := []byte("='" + strings.Replace(val, "'", "''", -1) + "'\n") + value := []byte("='" + strings.ReplaceAll(val, "'", "''") + "'\n") out = append(out, append([]byte("$"), append(line, value...)...)...) } default: diff --git a/lib/util/passthrough_monitor.go b/lib/util/passthrough_monitor.go index 9df491e..e29bbf3 100644 --- a/lib/util/passthrough_monitor.go +++ b/lib/util/passthrough_monitor.go @@ -41,7 +41,7 @@ func (pt *PassThruMonitor) Read(p []byte) (int, error) { if n > 0 { pt.total += int64(n) percentage := float64(pt.total) / float64(pt.Length) * float64(100) - if percentage-pt.progress > 10 || time.Now().Sub(pt.print_ts) > 30*time.Second { + if percentage-pt.progress > 10 || time.Since(pt.print_ts) > 30*time.Second { // Show status every 10% or 30 sec log.Infof("%s: %v%% (%dB)", pt.Name, int(percentage), pt.total) pt.progress = percentage diff --git a/tests/helper/copy.go b/tests/helper/copy.go index aacfad8..b8552d4 100644 --- a/tests/helper/copy.go +++ b/tests/helper/copy.go @@ -26,7 +26,7 @@ func CopyFile(src, dst string) error { } defer fin.Close() - os.MkdirAll(filepath.Dir(dst), 0755) + os.MkdirAll(filepath.Dir(dst), 0o755) fout, err := os.Create(dst) if err != nil { return err diff --git a/tests/helper/fish.go b/tests/helper/fish.go index d7d0c50..cb93786 100644 --- a/tests/helper/fish.go +++ b/tests/helper/fish.go @@ -39,51 +39,55 @@ type AFInstance struct { } // Simple creates and run the fish node -func NewAquariumFish(t testing.TB, name, cfg string, args ...string) *AFInstance { - afi := NewAfInstance(t, name, cfg) - afi.Start(t, args...) +func NewAquariumFish(tb testing.TB, name, cfg string, args ...string) *AFInstance { + tb.Helper() + afi := NewAfInstance(tb, name, cfg) + afi.Start(tb, args...) return afi } // If you need to create instance without starting it up right away -func NewAfInstance(t testing.TB, name, cfg string) *AFInstance { - t.Log("INFO: Creating new node:", name) +func NewAfInstance(tb testing.TB, name, cfg string) *AFInstance { + tb.Helper() + tb.Log("INFO: Creating new node:", name) afi := &AFInstance{ node_name: name, } - afi.workspace = t.TempDir() - t.Log("INFO: Created workspace:", afi.node_name, afi.workspace) + afi.workspace = tb.TempDir() + tb.Log("INFO: Created workspace:", afi.node_name, afi.workspace) cfg += fmt.Sprintf("\nnode_name: %q", afi.node_name) - os.WriteFile(filepath.Join(afi.workspace, "config.yml"), []byte(cfg), 0644) - t.Log("INFO: Stored config:", cfg) + os.WriteFile(filepath.Join(afi.workspace, "config.yml"), []byte(cfg), 0o600) + tb.Log("INFO: Stored config:", cfg) return afi } // Start another node of cluster // It will automatically add cluster_join parameter to the config -func (afi1 *AFInstance) NewClusterNode(t testing.TB, name, cfg string, args ...string) *AFInstance { - afi2 := afi1.NewAfInstanceCluster(t, name, cfg) - afi2.Start(t, args...) +func (afi1 *AFInstance) NewClusterNode(tb testing.TB, name, cfg string, args ...string) *AFInstance { + tb.Helper() + afi2 := afi1.NewAfInstanceCluster(tb, name, cfg) + afi2.Start(tb, args...) return afi2 } // Just create the node based on the existing cluster node -func (afi1 *AFInstance) NewAfInstanceCluster(t testing.TB, name, cfg string) *AFInstance { - t.Log("INFO: Creating new cluster node with seed node:", afi1.node_name) +func (afi1 *AFInstance) NewAfInstanceCluster(tb testing.TB, name, cfg string) *AFInstance { + tb.Helper() + tb.Log("INFO: Creating new cluster node with seed node:", afi1.node_name) cfg += fmt.Sprintf("\ncluster_join: [%q]", afi1.endpoint) - afi2 := NewAfInstance(t, name, cfg) + afi2 := NewAfInstance(tb, name, cfg) // Copy seed node CA to generate valid cluster node cert if err := CopyFile(filepath.Join(afi1.workspace, "fish_data", "ca.key"), filepath.Join(afi2.workspace, "fish_data", "ca.key")); err != nil { - t.Fatalf("ERROR: Unable to copy CA key: %v", err) + tb.Fatalf("ERROR: Unable to copy CA key: %v", err) } if err := CopyFile(filepath.Join(afi1.workspace, "fish_data", "ca.crt"), filepath.Join(afi2.workspace, "fish_data", "ca.crt")); err != nil { - t.Fatalf("ERROR: Unable to copy CA crt: %v", err) + tb.Fatalf("ERROR: Unable to copy CA crt: %v", err) } return afi2 @@ -115,21 +119,24 @@ func (afi *AFInstance) IsRunning() bool { } // Restart the application -func (afi *AFInstance) Restart(t testing.TB, args ...string) { - t.Log("INFO: Restarting:", afi.node_name, afi.workspace) - afi.Stop(t) - afi.Start(t, args...) +func (afi *AFInstance) Restart(tb testing.TB, args ...string) { + tb.Helper() + tb.Log("INFO: Restarting:", afi.node_name, afi.workspace) + afi.Stop(tb) + afi.Start(tb, args...) } // Cleanup after the test execution -func (afi *AFInstance) Cleanup(t testing.TB) { - t.Log("INFO: Cleaning up:", afi.node_name, afi.workspace) - afi.Stop(t) +func (afi *AFInstance) Cleanup(tb testing.TB) { + tb.Helper() + tb.Log("INFO: Cleaning up:", afi.node_name, afi.workspace) + afi.Stop(tb) os.RemoveAll(afi.workspace) } // Stops the fish node executable -func (afi *AFInstance) Stop(t testing.TB) { +func (afi *AFInstance) Stop(tb testing.TB) { + tb.Helper() if afi.cmd == nil || !afi.running { return } @@ -137,7 +144,7 @@ func (afi *AFInstance) Stop(t testing.TB) { afi.cmd.Process.Signal(os.Interrupt) // Wait 10 seconds for process to stop - t.Log("INFO: Wait 10s for fish node to stop:", afi.node_name, afi.workspace) + tb.Log("INFO: Wait 10s for fish node to stop:", afi.node_name, afi.workspace) for i := 1; i < 20; i++ { if !afi.running { return @@ -150,9 +157,10 @@ func (afi *AFInstance) Stop(t testing.TB) { } // Starts the fish node executable -func (afi *AFInstance) Start(t testing.TB, args ...string) { +func (afi *AFInstance) Start(tb testing.TB, args ...string) { + tb.Helper() if afi.running { - t.Fatalf("ERROR: Fish node %q can't be started since already started", afi.node_name) + tb.Fatalf("ERROR: Fish node %q can't be started since already started", afi.node_name) return } ctx, cancel := context.WithCancel(context.Background()) @@ -172,7 +180,7 @@ func (afi *AFInstance) Start(t testing.TB, args ...string) { // Listening for log and scan for token and address for scanner.Scan() { line := scanner.Text() - t.Log(afi.node_name, line) + tb.Log(afi.node_name, line) if strings.HasPrefix(line, "Admin user pass: ") { val := strings.SplitN(strings.TrimSpace(line), "Admin user pass: ", 2) if len(val) < 2 { @@ -195,7 +203,7 @@ func (afi *AFInstance) Start(t testing.TB, args ...string) { init_done <- "" } } - t.Log("INFO: Reading of AquariumFish output is done") + tb.Log("INFO: Reading of AquariumFish output is done") }() afi.cmd.Start() @@ -207,7 +215,7 @@ func (afi *AFInstance) Start(t testing.TB, args ...string) { r.Close() }() if err := afi.cmd.Wait(); err != nil { - t.Log("WARN: AquariumFish process was stopped:", err) + tb.Log("WARN: AquariumFish process was stopped:", err) init_done <- fmt.Sprintf("ERROR: Fish was stopped with exit code: %v", err) } }() @@ -215,6 +223,6 @@ func (afi *AFInstance) Start(t testing.TB, args ...string) { failed := <-init_done if failed != "" { - t.Fatalf("ERROR: Failed to init node %q: %s", afi.node_name, failed) + tb.Fatalf("ERROR: Failed to init node %q: %s", afi.node_name, failed) } } diff --git a/tests/helper/t_mock.go b/tests/helper/t_mock.go index de19e45..d7b13b7 100644 --- a/tests/helper/t_mock.go +++ b/tests/helper/t_mock.go @@ -51,6 +51,7 @@ func (m *MockT) Fatalf(format string, args ...any) { } func ExpectFailure(t *testing.T, f func(tt testing.TB)) { + t.Helper() var wg sync.WaitGroup mock_t := &MockT{t: t}