Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update obfuscation #136

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/go.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: 1.18
go-version: 1.23

- name: Test
run: go test -v ./...
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/golangci-lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,4 @@ jobs:
- name: golangci-lint
uses: golangci/golangci-lint-action@v6
with:
version: v1.56.2
version: v1.61
8 changes: 6 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,13 @@ You can also combine your CLI arguments and the wizard. All arguments you pass f

> **WARNING:** Some passwords or secrets are automatically removed, but this no guarantee, so be careful what you share!

The `--hide` flag can be used multiple times to hide sensitive data, it supports regular expressions.
The `--hide` flag can be used multiple times to hide sensitive data.
As these obfuscators are based on regex, you must add a regex pattern that meets your requirements.

`# support-collector --hide "Secret:.*" --hide "Password:.*"`
`# support-collector --hide "Secret:\s*(.*)"`

This will replace:
* Values like `Secret: DummyValue`

In addition, files and folders that follow a specific pattern are not collected. This affects all files that correspond to the following filters:
`.*`, `*~`, `*.key`, `*.csr`, `*.crt`, and `*.pem`
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
module github.com/NETWAYS/support-collector

go 1.20
go 1.22

require (
github.com/Showmax/go-fqdn v1.0.0
Expand Down
17 changes: 12 additions & 5 deletions internal/collection/collection.go
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,7 @@ func (c *Collection) AddFileYAML(fileName string, data interface{}) {
_ = c.AddFileToOutput(file)
}

// AddFileJSON will add raw file data without obfuscation
func (c *Collection) AddFileJSON(fileName string, data []byte) {
var jsonData interface{}

Expand All @@ -158,7 +159,7 @@ func (c *Collection) AddFileJSON(fileName string, data []byte) {
file := NewFile(fileName)
file.Data = prettyJSON

_ = c.AddFileToOutput(file)
_ = c.AddFileToOutputRaw(file)
}

func (c *Collection) AddFiles(prefix, source string) {
Expand Down Expand Up @@ -250,16 +251,19 @@ func (c *Collection) AddJournalLog(fileName, service string) {
c.AddCommandOutput(fileName, "journalctl", "-u", service, "-S", c.JournalLoggingInterval)
}

// RegisterObfuscator adds the given Obfuscator to the Obfuscators of Collection
func (c *Collection) RegisterObfuscator(o *obfuscate.Obfuscator) {
c.Obfuscators = append(c.Obfuscators, o)
}

// RegisterObfuscators adds the given list of Obfuscator to the Obfuscators of Collection
func (c *Collection) RegisterObfuscators(list ...*obfuscate.Obfuscator) {
for _, o := range list {
c.RegisterObfuscator(o)
}
}

// ClearObfuscators clears the list of Obfuscators in the Collection
func (c *Collection) ClearObfuscators() {
c.Obfuscators = c.Obfuscators[:0]
}
Expand All @@ -272,14 +276,17 @@ func (c *Collection) callObfuscators(kind obfuscate.Kind, name string, data []by

for _, o := range c.Obfuscators {
if o.IsAccepting(kind, name) {
count, out, err = o.Process(data)
count, out, err = o.Process(data, name)
if err != nil {
return
}

if count > 0 {
c.Log.Debugf("Obfuscation replaced %d token in %s", count, name)
}
data = out
}

if count > 0 {
c.Log.Debugf("ReplacePattern '%s' replaced %d token in %s", o.ReplacePattern, count, name)
count = 0
}
}

Expand Down
47 changes: 27 additions & 20 deletions internal/obfuscate/obfuscate.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,24 +29,24 @@ const (

// Obfuscator provides the basic functionality of an obfuscation engine.
//
// Kind filters the variant of resource we want to work on, while Affecting defines a list of regexp.Regexp, to match
// Kind filters the variant of resource we want to work on, while ShouldAffect defines a list of regexp.Regexp, to match
// against for the file names, or command.
//
// Replacements will be iterated, so all matches or matched groups will be replaced.
// Replacement will be iterated, so all matches or matched groups will be replaced.
type Obfuscator struct {
Kind
Affecting []*regexp.Regexp
Replacements []*regexp.Regexp
Files uint
Replaced uint
ShouldAffect []*regexp.Regexp
ReplacePattern *regexp.Regexp
ObfuscatedFiles []string
Replaced uint
}

// New returns a basic Obfuscator with provided regexp.Regexp.
func New(kind Kind, affects, replace *regexp.Regexp) *Obfuscator {
return &Obfuscator{
Kind: kind,
Affecting: []*regexp.Regexp{affects},
Replacements: []*regexp.Regexp{replace},
Kind: kind,
ShouldAffect: []*regexp.Regexp{affects},
ReplacePattern: replace,
}
}

Expand Down Expand Up @@ -77,27 +77,30 @@ func NewAny(replace string) *Obfuscator {
return o
}

// WithAffecting adds a new element to the list.
// WithAffecting adds a new pattern to the list where the Obfuscator will be applied
func (o *Obfuscator) WithAffecting(a *regexp.Regexp) *Obfuscator {
o.Affecting = append(o.Affecting, a)
o.ShouldAffect = append(o.ShouldAffect, a)

return o
}

// WithReplacement adds a new element to the list.
// WithReplacement adds a new pattern to the list.
func (o *Obfuscator) WithReplacement(r *regexp.Regexp) *Obfuscator {
o.Replacements = append(o.Replacements, r)
o.ReplacePattern = r

return o
}

// IsAccepting checks if we want to work on the resource.
//
// Checks if the Obfuscator.Kind is matching the given kind. If not returns false.
// Checks if the Obfuscator.ShouldAffect is matching the given name.
func (o Obfuscator) IsAccepting(kind Kind, name string) bool {
if o.Kind != KindAny && o.Kind != kind {
return false
}

for _, re := range o.Affecting {
for _, re := range o.ShouldAffect {
if re.MatchString(name) {
return true
}
Expand All @@ -106,16 +109,18 @@ func (o Obfuscator) IsAccepting(kind Kind, name string) bool {
return false
}

// Process takes data and returns it obfuscated.
func (o *Obfuscator) Process(data []byte) (uint, []byte, error) {
count, out, err := o.ProcessReader(bytes.NewReader(data))
// Process takes data and returns it obfuscated. Also takes name of file that is obfuscated
//
// Returns count of replaced values as uint, obfuscated data as []byte, and error
func (o *Obfuscator) Process(data []byte, name string) (uint, []byte, error) {
count, out, err := o.ProcessReader(bytes.NewReader(data), name)

//goland:noinspection GoNilness
return count, out.Bytes(), err
}

// ProcessReader takes an io.Reader and returns a new one obfuscated.
func (o *Obfuscator) ProcessReader(r io.Reader) (count uint, out bytes.Buffer, err error) {
func (o *Obfuscator) ProcessReader(r io.Reader, name string) (count uint, out bytes.Buffer, err error) {
rd := bufio.NewReader(r)

var (
Expand Down Expand Up @@ -153,7 +158,7 @@ func (o *Obfuscator) ProcessReader(r io.Reader) (count uint, out bytes.Buffer, e

// Replace any matches, but skip empty lines
if strings.TrimSpace(line) != "" {
line, c = ReplacePatterns(line, o.Replacements)
line, c = ReplacePattern(line, o.ReplacePattern)
}

// Add line ending back after replacement
Expand All @@ -168,12 +173,13 @@ func (o *Obfuscator) ProcessReader(r io.Reader) (count uint, out bytes.Buffer, e
}

if count > 0 {
o.Files++
o.ObfuscatedFiles = append(o.ObfuscatedFiles, name)
}

return count, out, err
}

/*
// ReplacePatterns replaces all the patterns matches in a line.
func ReplacePatterns(line string, patterns []*regexp.Regexp) (s string, count uint) {
for _, pattern := range patterns {
Expand All @@ -185,6 +191,7 @@ func ReplacePatterns(line string, patterns []*regexp.Regexp) (s string, count ui

return line, count
}
*/

// ReplacePattern replaces all matches in a line.
func ReplacePattern(line string, pattern *regexp.Regexp) (s string, count uint) {
Expand Down
18 changes: 8 additions & 10 deletions internal/obfuscate/obfuscate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ func ExampleObfuscator() {
content := []byte(`password = "secret"`)

if o.IsAccepting(KindFile, "test.ini") {
count, data, err := o.Process(content)
count, data, err := o.Process(content, "")
fmt.Println(err)
fmt.Println(count)
fmt.Println(string(data))
Expand Down Expand Up @@ -64,17 +64,15 @@ func TestObfuscator_IsAccepting(t *testing.T) {

func TestObfuscator_Process(t *testing.T) {
o := &Obfuscator{
Replacements: []*regexp.Regexp{
regexp.MustCompile(`^password\s*=\s*(.*)`),
},
ReplacePattern: regexp.MustCompile(`^password\s*=\s*(.*)`),
}

count, out, err := o.Process([]byte("default content\r\n"))
count, out, err := o.Process([]byte("default content\r\n"), "")
assert.NoError(t, err)
assert.Equal(t, uint(0), count)
assert.Equal(t, "default content\r\n", string(out))

count, out, err = o.Process([]byte(iniExample))
count, out, err = o.Process([]byte(iniExample), "")
assert.NoError(t, err)
assert.Equal(t, uint(1), count)
assert.Equal(t, iniResult, string(out))
Expand All @@ -83,15 +81,15 @@ func TestObfuscator_Process(t *testing.T) {
func TestNewFile(t *testing.T) {
o := NewFile(`^password\s*=\s*(.*)`, "conf")
assert.Equal(t, KindFile, o.Kind)
assert.Len(t, o.Affecting, 1)
assert.Len(t, o.Replacements, 1)
assert.Len(t, o.ShouldAffect, 1)
assert.NotEmpty(t, o.ShouldAffect)
}

func TestNewOutput(t *testing.T) {
o := NewOutput(`^password\s*=\s*(.*)`, "icinga2", "daemon", "-C")
assert.Equal(t, KindOutput, o.Kind)
assert.Len(t, o.Affecting, 1)
assert.Len(t, o.Replacements, 1)
assert.Len(t, o.ShouldAffect, 1)
assert.NotEmpty(t, o.ReplacePattern)

assert.True(t, o.IsAccepting(KindOutput, "icinga2 daemon -C"))
assert.False(t, o.IsAccepting(KindOutput, "icinga2 daemon"))
Expand Down
2 changes: 1 addition & 1 deletion internal/util/testing.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ func AssertObfuscation(t *testing.T, obfuscators []*obfuscate.Obfuscator,
continue
}

_, out, err := o.Process([]byte(input))
_, out, err := o.Process([]byte(input), name)
if err != nil {
t.Errorf("error during obfuscation: %s", err)
return
Expand Down
16 changes: 16 additions & 0 deletions internal/util/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,22 @@ func StringInSlice(a string, list []string) bool {
return false
}

// DistinctStringSlice returns the given slice with unique values
func DistinctStringSlice(arr []string) []string {
seen := make(map[string]bool)

var result []string

for _, val := range arr {
if !seen[val] {
result = append(result, val)
seen[val] = true
}
}

return result
}

// IsPrivilegedUser returns true when the current user is root.
func IsPrivilegedUser() bool {
u, err := user.Current()
Expand Down
13 changes: 8 additions & 5 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -246,16 +246,19 @@ func main() {
c.Log.Infof("Collection complete, took us %.3f seconds", metric.Timings["total"].Seconds())

// Collect obfuscation info
var files, count uint
var (
count uint
affectedFiles []string
)

for _, o := range c.Obfuscators {
files += o.Files

count += o.Replaced

affectedFiles = append(affectedFiles, o.ObfuscatedFiles...)
}

if files > 0 {
c.Log.Infof("Obfuscation replaced %d token in %d files (%d definitions)", count, files, len(c.Obfuscators))
if len(affectedFiles) > 0 {
c.Log.Infof("Obfuscation replaced %d token in %d files (%d definitions)", count, len(util.DistinctStringSlice(affectedFiles)), len(c.Obfuscators))
}

// get absolute path of outputFile
Expand Down
2 changes: 1 addition & 1 deletion modules/base/kernel.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ func CharsToString(chars []int8) string {
break
}

s[i] = uint8(chars[i])
s[i] = byte(chars[i])
}

return string(s[0:i])
Expand Down
4 changes: 2 additions & 2 deletions modules/icinga2/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ func InitAPICollection(c *collection.Collection) error {

// endpointIsReachable checks if the given endpoint is reachable within 5 sec
func endpointIsReachable(endpoint string) error {
timeout := 5 * time.Second
timeout := 5 * time.Second //nolint:mnd

// try to dial tcp connection within 5 seconds
conn, err := net.DialTimeout("tcp", endpoint, timeout)
Expand All @@ -84,7 +84,7 @@ func collectStatus(endpoint string, c *collection.Collection) error {
client := &http.Client{Transport: tr}

// build context for request
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) //nolint:mnd
defer cancel()

// build request
Expand Down
2 changes: 1 addition & 1 deletion modules/icinga2/collector.go
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
package icinga2

import (
"github.com/NETWAYS/support-collector/internal/obfuscate"
"github.com/NETWAYS/support-collector/internal/util"
"os"
"path/filepath"
"regexp"

"github.com/NETWAYS/support-collector/internal/collection"
"github.com/NETWAYS/support-collector/internal/obfuscate"
)

const ModuleName = "icinga2"
Expand Down