mirror of
https://github.com/safing/portmaster
synced 2025-09-01 18:19:12 +00:00
Derive profile ID from fingerprints
This commit is contained in:
parent
bd410afee6
commit
e81953d8f3
3 changed files with 131 additions and 2 deletions
|
@ -4,6 +4,10 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/safing/jess/lhash"
|
||||||
|
"github.com/safing/portbase/container"
|
||||||
|
"golang.org/x/exp/slices"
|
||||||
)
|
)
|
||||||
|
|
||||||
// # Matching and Scores
|
// # Matching and Scores
|
||||||
|
@ -57,6 +61,12 @@ type (
|
||||||
Key string // Key must always fully match.
|
Key string // Key must always fully match.
|
||||||
Operation string
|
Operation string
|
||||||
Value string
|
Value string
|
||||||
|
|
||||||
|
// MergedFrom holds the ID of the profile from which this fingerprint was
|
||||||
|
// merged from. The merged profile should create a new profile ID derived
|
||||||
|
// from the new fingerprints and add all fingerprints with this field set
|
||||||
|
// to the originating profile ID
|
||||||
|
MergedFrom string
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tag represents a simple key/value kind of tag used in process metadata
|
// Tag represents a simple key/value kind of tag used in process metadata
|
||||||
|
@ -347,3 +357,62 @@ func checkMatchStrength(value int) int {
|
||||||
}
|
}
|
||||||
return value
|
return value
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
deriveFPKeyIDForItemStart = iota + 1
|
||||||
|
deriveFPKeyIDForType
|
||||||
|
deriveFPKeyIDForKey
|
||||||
|
deriveFPKeyIDForOperation
|
||||||
|
deriveFPKeyIDForValue
|
||||||
|
)
|
||||||
|
|
||||||
|
func deriveProfileID(fps []Fingerprint) string {
|
||||||
|
// Sort the fingerprints.
|
||||||
|
sortFingerprints(fps)
|
||||||
|
|
||||||
|
// Compile data for hashing.
|
||||||
|
c := container.New(nil)
|
||||||
|
c.AppendInt(len(fps))
|
||||||
|
for _, fp := range fps {
|
||||||
|
c.AppendNumber(deriveFPKeyIDForItemStart)
|
||||||
|
if fp.Type != "" {
|
||||||
|
c.AppendNumber(deriveFPKeyIDForType)
|
||||||
|
c.AppendAsBlock([]byte(fp.Type))
|
||||||
|
}
|
||||||
|
if fp.Key != "" {
|
||||||
|
c.AppendNumber(deriveFPKeyIDForKey)
|
||||||
|
c.AppendAsBlock([]byte(fp.Key))
|
||||||
|
}
|
||||||
|
if fp.Operation != "" {
|
||||||
|
c.AppendNumber(deriveFPKeyIDForOperation)
|
||||||
|
c.AppendAsBlock([]byte(fp.Operation))
|
||||||
|
}
|
||||||
|
if fp.Value != "" {
|
||||||
|
c.AppendNumber(deriveFPKeyIDForValue)
|
||||||
|
c.AppendAsBlock([]byte(fp.Value))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hash and return.
|
||||||
|
h := lhash.Digest(lhash.SHA3_256, c.CompileData())
|
||||||
|
return h.Base58()
|
||||||
|
}
|
||||||
|
|
||||||
|
func sortFingerprints(fps []Fingerprint) {
|
||||||
|
slices.SortFunc[[]Fingerprint, Fingerprint](fps, func(a, b Fingerprint) int {
|
||||||
|
switch {
|
||||||
|
case a.Type != b.Type:
|
||||||
|
return strings.Compare(a.Type, b.Type)
|
||||||
|
case a.Key != b.Key:
|
||||||
|
return strings.Compare(a.Key, b.Key)
|
||||||
|
case a.Operation != b.Operation:
|
||||||
|
return strings.Compare(a.Operation, b.Operation)
|
||||||
|
case a.Value != b.Value:
|
||||||
|
return strings.Compare(a.Value, b.Value)
|
||||||
|
case a.MergedFrom != b.MergedFrom:
|
||||||
|
return strings.Compare(a.MergedFrom, b.MergedFrom)
|
||||||
|
default:
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
53
profile/fingerprint_test.go
Normal file
53
profile/fingerprint_test.go
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
package profile
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math/rand"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestDeriveProfileID(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
fps := []Fingerprint{
|
||||||
|
{
|
||||||
|
Type: FingerprintTypePathID,
|
||||||
|
Operation: FingerprintOperationEqualsID,
|
||||||
|
Value: "/sbin/init",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: FingerprintTypePathID,
|
||||||
|
Operation: FingerprintOperationPrefixID,
|
||||||
|
Value: "/",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: FingerprintTypeEnvID,
|
||||||
|
Key: "PORTMASTER_PROFILE",
|
||||||
|
Operation: FingerprintOperationEqualsID,
|
||||||
|
Value: "TEST-1",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: FingerprintTypeTagID,
|
||||||
|
Key: "tag-key-1",
|
||||||
|
Operation: FingerprintOperationEqualsID,
|
||||||
|
Value: "tag-key-2",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create rand source for shuffling.
|
||||||
|
rnd := rand.New(rand.NewSource(time.Now().UnixNano())) //nolint:gosec
|
||||||
|
|
||||||
|
// Test 100 times.
|
||||||
|
for i := 0; i < 100; i++ {
|
||||||
|
// Shuffle fingerprints.
|
||||||
|
rnd.Shuffle(len(fps), func(i, j int) {
|
||||||
|
fps[i], fps[j] = fps[j], fps[i]
|
||||||
|
})
|
||||||
|
|
||||||
|
// Check if fingerprint matches.
|
||||||
|
id := deriveProfileID(fps)
|
||||||
|
assert.Equal(t, "PTSRP7rdCnmvdjRoPMTrtjj7qk7PxR1a9YdBWUGwnZXJh2", id)
|
||||||
|
}
|
||||||
|
}
|
|
@ -265,9 +265,16 @@ func New(profile *Profile) *Profile {
|
||||||
profile.Config = make(map[string]interface{})
|
profile.Config = make(map[string]interface{})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generate random ID if none is given.
|
// Generate ID if none is given.
|
||||||
if profile.ID == "" {
|
if profile.ID == "" {
|
||||||
profile.ID = utils.RandomUUID("").String()
|
if len(profile.Fingerprints) > 0 {
|
||||||
|
// Derive from fingerprints.
|
||||||
|
profile.ID = deriveProfileID(profile.Fingerprints)
|
||||||
|
} else {
|
||||||
|
// Generate random ID as fallback.
|
||||||
|
log.Warningf("profile: creating new profile without fingerprints to derive ID from")
|
||||||
|
profile.ID = utils.RandomUUID("").String()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Make key from ID and source.
|
// Make key from ID and source.
|
||||||
|
|
Loading…
Add table
Reference in a new issue