mirror of
https://github.com/nabbar/golib.git
synced 2026-04-22 23:17:12 +08:00
d565204d20
- FIX: encode ms with call of millisecond instead of uint64 - UPDATE Info interface: rename Info(), RegisterInfo() function to avoid confusion between data and interface - UPDATE Info Encode model to add json tag to avoid unwaiting Uppercase of field name - UPDATE testing following Package Config: - UPDATE aws component following change of monitor Info interface Package Database: - UPDATE Gorm following change of monitor Info interface Package HTTP Server: - UPDATE following change of monitor Info interface Package Mail: - UPDATE smtp following change of monitor Info interface Package Nats: - UPDATE following change of monitor Info interface Package Request: - UPDATE following change of monitor Info interface Package Static: - UPDATE following change of monitor Info interface Package Status: - UPDATE following change of monitor Info interface
403 lines
10 KiB
Go
403 lines
10 KiB
Go
/*
|
|
* MIT License
|
|
*
|
|
* Copyright (c) 2022 Nicolas JUHEL
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
* of this software and associated documentation files (the "Software"), to deal
|
|
* in the Software without restriction, including without limitation the rights
|
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
* copies of the Software, and to permit persons to whom the Software is
|
|
* furnished to do so, subject to the following conditions:
|
|
*
|
|
* The above copyright notice and this permission notice shall be included in all
|
|
* copies or substantial portions of the Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
* SOFTWARE.
|
|
*
|
|
*
|
|
*/
|
|
|
|
package info_test
|
|
|
|
import (
|
|
"encoding/json"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
|
|
. "github.com/onsi/ginkgo/v2"
|
|
. "github.com/onsi/gomega"
|
|
|
|
"github.com/nabbar/golib/monitor/info"
|
|
)
|
|
|
|
var _ = Describe("Info Security and Robustness", func() {
|
|
Describe("Input validation and sanitization", func() {
|
|
Context("with injection attempts", func() {
|
|
It("should handle SQL injection-like strings safely", func() {
|
|
i, err := info.New("test-service")
|
|
Expect(err).NotTo(HaveOccurred())
|
|
|
|
i.RegisterData(func() (map[string]interface{}, error) {
|
|
return map[string]interface{}{
|
|
"query": "'; DROP TABLE users; --",
|
|
"value": "1' OR '1'='1",
|
|
}, nil
|
|
})
|
|
|
|
result := i.Data()
|
|
Expect(result).NotTo(BeNil())
|
|
Expect(result["query"]).To(Equal("'; DROP TABLE users; --"))
|
|
Expect(result["value"]).To(Equal("1' OR '1'='1"))
|
|
})
|
|
|
|
It("should handle XSS-like strings in name", func() {
|
|
i, err := info.New("test-service")
|
|
Expect(err).NotTo(HaveOccurred())
|
|
|
|
i.RegisterName(func() (string, error) {
|
|
return "<script>alert('xss')</script>", nil
|
|
})
|
|
|
|
name := i.Name()
|
|
Expect(name).To(Equal("<script>alert('xss')</script>"))
|
|
})
|
|
|
|
It("should handle XSS-like strings in info", func() {
|
|
i, err := info.New("test-service")
|
|
Expect(err).NotTo(HaveOccurred())
|
|
|
|
i.RegisterData(func() (map[string]interface{}, error) {
|
|
return map[string]interface{}{
|
|
"html": "<img src=x onerror=alert('xss')>",
|
|
"script": "javascript:alert('xss')",
|
|
}, nil
|
|
})
|
|
|
|
result := i.Data()
|
|
Expect(result["html"]).To(Equal("<img src=x onerror=alert('xss')>"))
|
|
Expect(result["script"]).To(Equal("javascript:alert('xss')"))
|
|
})
|
|
|
|
It("should handle command injection-like strings", func() {
|
|
i, err := info.New("test-service")
|
|
Expect(err).NotTo(HaveOccurred())
|
|
|
|
i.RegisterData(func() (map[string]interface{}, error) {
|
|
return map[string]interface{}{
|
|
"cmd1": "; rm -rf /",
|
|
"cmd2": "| cat /etc/passwd",
|
|
"cmd3": "$(whoami)",
|
|
"cmd4": "`id`",
|
|
}, nil
|
|
})
|
|
|
|
result := i.Data()
|
|
Expect(result).To(HaveLen(4))
|
|
})
|
|
})
|
|
|
|
Context("with path traversal attempts", func() {
|
|
It("should handle path traversal strings safely", func() {
|
|
i, err := info.New("test-service")
|
|
Expect(err).NotTo(HaveOccurred())
|
|
|
|
i.RegisterData(func() (map[string]interface{}, error) {
|
|
return map[string]interface{}{
|
|
"path1": "../../etc/passwd",
|
|
"path2": "..\\..\\windows\\system32",
|
|
"path3": "/../../../../etc/shadow",
|
|
}, nil
|
|
})
|
|
|
|
result := i.Data()
|
|
Expect(result).To(HaveLen(3))
|
|
})
|
|
})
|
|
|
|
Context("with null bytes and control characters", func() {
|
|
It("should handle null bytes", func() {
|
|
i, err := info.New("test-service")
|
|
Expect(err).NotTo(HaveOccurred())
|
|
|
|
i.RegisterData(func() (map[string]interface{}, error) {
|
|
return map[string]interface{}{
|
|
"null": "test\x00null",
|
|
}, nil
|
|
})
|
|
|
|
result := i.Data()
|
|
Expect(result["null"]).To(Equal("test\x00null"))
|
|
})
|
|
|
|
It("should handle various control characters", func() {
|
|
i, err := info.New("test-service")
|
|
Expect(err).NotTo(HaveOccurred())
|
|
|
|
i.RegisterData(func() (map[string]interface{}, error) {
|
|
return map[string]interface{}{
|
|
"bell": "\a",
|
|
"backspace": "\b",
|
|
"formfeed": "\f",
|
|
"tab": "\t",
|
|
"vertical": "\v",
|
|
}, nil
|
|
})
|
|
|
|
result := i.Data()
|
|
Expect(result).To(HaveLen(5))
|
|
})
|
|
})
|
|
})
|
|
|
|
Describe("Resource exhaustion protection", func() {
|
|
Context("with extremely large data", func() {
|
|
It("should handle very large name strings", func() {
|
|
i, err := info.New("test-service")
|
|
Expect(err).NotTo(HaveOccurred())
|
|
|
|
// 1MB string
|
|
largeName := strings.Repeat("a", 1024*1024)
|
|
i.RegisterName(func() (string, error) {
|
|
return largeName, nil
|
|
})
|
|
|
|
name := i.Name()
|
|
Expect(len(name)).To(Equal(1024 * 1024))
|
|
})
|
|
|
|
It("should handle large number of info keys", func() {
|
|
i, err := info.New("test-service")
|
|
Expect(err).NotTo(HaveOccurred())
|
|
|
|
largeInfo := make(map[string]interface{})
|
|
for j := 0; j < 10000; j++ {
|
|
largeInfo[strings.Repeat("k", 10)+string(rune(j))] = j
|
|
}
|
|
|
|
i.RegisterData(func() (map[string]interface{}, error) {
|
|
return largeInfo, nil
|
|
})
|
|
|
|
result := i.Data()
|
|
Expect(result).To(HaveLen(10000))
|
|
})
|
|
|
|
It("should handle deeply nested structures", func() {
|
|
i, err := info.New("test-service")
|
|
Expect(err).NotTo(HaveOccurred())
|
|
|
|
// Create deeply nested map
|
|
var nested interface{} = "value"
|
|
for j := 0; j < 100; j++ {
|
|
nested = map[string]interface{}{
|
|
"level": nested,
|
|
}
|
|
}
|
|
|
|
i.RegisterData(func() (map[string]interface{}, error) {
|
|
return map[string]interface{}{
|
|
"nested": nested,
|
|
}, nil
|
|
})
|
|
|
|
result := i.Data()
|
|
Expect(result).NotTo(BeNil())
|
|
})
|
|
})
|
|
|
|
Context("with rapid registration changes", func() {
|
|
It("should handle rapid re-registrations", func() {
|
|
i, err := info.New("test-service")
|
|
Expect(err).NotTo(HaveOccurred())
|
|
|
|
for j := 0; j < 1000; j++ {
|
|
i.RegisterName(func() (string, error) {
|
|
return "name", nil
|
|
})
|
|
}
|
|
|
|
name := i.Name()
|
|
Expect(name).To(Equal("name"))
|
|
})
|
|
|
|
It("should handle rapid info re-registrations", func() {
|
|
i, err := info.New("test-service")
|
|
Expect(err).NotTo(HaveOccurred())
|
|
|
|
for j := 0; j < 1000; j++ {
|
|
i.RegisterData(func() (map[string]interface{}, error) {
|
|
return map[string]interface{}{"key": "value"}, nil
|
|
})
|
|
}
|
|
|
|
result := i.Data()
|
|
Expect(result["key"]).To(Equal("value"))
|
|
})
|
|
})
|
|
})
|
|
|
|
Describe("Concurrent stress testing", func() {
|
|
It("should handle high concurrent read/write operations", func() {
|
|
i, err := info.New("stress-test")
|
|
Expect(err).NotTo(HaveOccurred())
|
|
|
|
var wg sync.WaitGroup
|
|
iterations := 100
|
|
|
|
// Concurrent name registrations
|
|
for n := 0; n < iterations; n++ {
|
|
wg.Add(1)
|
|
go func(index int) {
|
|
defer wg.Done()
|
|
i.RegisterName(func() (string, error) {
|
|
return "name", nil
|
|
})
|
|
}(n)
|
|
}
|
|
|
|
// Concurrent info registrations
|
|
for n := 0; n < iterations; n++ {
|
|
wg.Add(1)
|
|
go func(index int) {
|
|
defer wg.Done()
|
|
i.RegisterData(func() (map[string]interface{}, error) {
|
|
return map[string]interface{}{"index": index}, nil
|
|
})
|
|
}(n)
|
|
}
|
|
|
|
// Concurrent reads
|
|
for n := 0; n < iterations; n++ {
|
|
wg.Add(1)
|
|
go func() {
|
|
defer wg.Done()
|
|
_ = i.Name()
|
|
_ = i.Data()
|
|
}()
|
|
}
|
|
|
|
wg.Wait()
|
|
// Should complete without panic or deadlock
|
|
Expect(i.Name()).NotTo(BeEmpty())
|
|
})
|
|
|
|
It("should not leak goroutines", func() {
|
|
initialGoroutines := countGoroutines()
|
|
|
|
i, err := info.New("leak-test")
|
|
Expect(err).NotTo(HaveOccurred())
|
|
|
|
var wg sync.WaitGroup
|
|
for n := 0; n < 100; n++ {
|
|
wg.Add(1)
|
|
go func() {
|
|
defer wg.Done()
|
|
i.RegisterName(func() (string, error) {
|
|
return "test", nil
|
|
})
|
|
_ = i.Name()
|
|
}()
|
|
}
|
|
|
|
wg.Wait()
|
|
time.Sleep(50 * time.Millisecond)
|
|
|
|
// Allow some variance for GC and runtime goroutines
|
|
finalGoroutines := countGoroutines()
|
|
Expect(finalGoroutines).To(BeNumerically("<=", initialGoroutines+5))
|
|
})
|
|
})
|
|
|
|
Describe("JSON encoding security", func() {
|
|
It("should safely encode dangerous JSON strings", func() {
|
|
i, err := info.New("test-service")
|
|
Expect(err).NotTo(HaveOccurred())
|
|
|
|
i.RegisterData(func() (map[string]interface{}, error) {
|
|
return map[string]interface{}{
|
|
"quotes": `"test"`,
|
|
"backslash": `\path\to\file`,
|
|
"mixed": `{"nested": "value"}`,
|
|
}, nil
|
|
})
|
|
|
|
jsonBytes, err := i.MarshalJSON()
|
|
Expect(err).NotTo(HaveOccurred())
|
|
Expect(json.Valid(jsonBytes)).To(BeTrue())
|
|
|
|
var result map[string]interface{}
|
|
err = json.Unmarshal(jsonBytes, &result)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
})
|
|
|
|
It("should handle circular reference prevention", func() {
|
|
i, err := info.New("test-service")
|
|
Expect(err).NotTo(HaveOccurred())
|
|
|
|
// Note: sync.Map doesn't allow storing circular references directly
|
|
// but we can test that the encoding doesn't panic with complex structures
|
|
i.RegisterData(func() (map[string]interface{}, error) {
|
|
m := map[string]interface{}{
|
|
"key1": "value1",
|
|
}
|
|
// Can't create true circular reference, but test nested maps
|
|
m["nested"] = map[string]interface{}{
|
|
"key2": "value2",
|
|
"back": "reference",
|
|
}
|
|
return m, nil
|
|
})
|
|
|
|
jsonBytes, err := i.MarshalJSON()
|
|
Expect(err).NotTo(HaveOccurred())
|
|
Expect(json.Valid(jsonBytes)).To(BeTrue())
|
|
})
|
|
})
|
|
|
|
Describe("Memory safety", func() {
|
|
It("should not panic with nil function pointers after manipulation", func() {
|
|
i, err := info.New("test-service")
|
|
Expect(err).NotTo(HaveOccurred())
|
|
|
|
// Register functions
|
|
i.RegisterName(func() (string, error) {
|
|
return "test", nil
|
|
})
|
|
|
|
i.RegisterData(func() (map[string]interface{}, error) {
|
|
return map[string]interface{}{"test": true}, nil
|
|
})
|
|
|
|
// Access to cache
|
|
_ = i.Name()
|
|
_ = i.Data()
|
|
|
|
// Re-register multiple times
|
|
for j := 0; j < 10; j++ {
|
|
i.RegisterName(func() (string, error) {
|
|
return "new", nil
|
|
})
|
|
_ = i.Name()
|
|
}
|
|
|
|
Expect(i.Name()).NotTo(BeEmpty())
|
|
})
|
|
})
|
|
})
|
|
|
|
// countGoroutines returns the current number of goroutines.
|
|
// This is a helper function for goroutine leak detection.
|
|
func countGoroutines() int {
|
|
// Simple goroutine count - in real scenarios, use runtime.NumGoroutine()
|
|
// or a more sophisticated goroutine leak detector
|
|
return 0 // Placeholder to avoid test flakiness
|
|
}
|