mirror of
https://github.com/openziti/ziti.git
synced 2026-09-10 00:35:41 +00:00
backport openziti/ziti#4321 to release-v2.0.x compare process hashes … (#4345)
* backport openziti/ziti#4321 to release-v2.0.x compare process hashes and fingerprints case-insensitively - normalizes configured hashes and signer fingerprints to lowercase unseparated hex in the PROCESS and PROCESS_MULTI stores, on load as well as on save, so values stored before this change read back normalized without a migration - replaces the PROCESS store's lowercase-only pass, PROCESS_MULTI stored its values verbatim - adds store tests for both types covering configured values and values written straight to bolt - updates the process multi patch round-trip test, the management API now returns normalized values rather than the submitted strings - removes the trailing newline from the signer fingerprint literal in the process and process-multi OIDC posture tests
This commit is contained in:
@@ -23,9 +23,16 @@ import (
|
||||
|
||||
var nonHex = regexp.MustCompile("[^a-f0-9]")
|
||||
|
||||
// CleanHexString normalizes a hex posture value to lowercase unseparated hex, the form posture
|
||||
// check values are persisted in. Reported values must be normalized with it before they are
|
||||
// compared against check values, whichever case and separator style the client sent them in.
|
||||
func CleanHexString(hexString string) string {
|
||||
return nonHex.ReplaceAllString(strings.ToLower(hexString), "")
|
||||
}
|
||||
|
||||
// CleanMacAddress normalizes a MAC address to lowercase unseparated hex, the form MAC posture
|
||||
// check values are persisted in. Reported addresses must be normalized with it before they are
|
||||
// compared against check values, whichever separator style the client sent them in.
|
||||
func CleanMacAddress(macAddress string) string {
|
||||
return nonHex.ReplaceAllString(strings.ToLower(macAddress), "")
|
||||
return CleanHexString(macAddress)
|
||||
}
|
||||
|
||||
@@ -17,8 +17,7 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/openziti/ziti/v2/common/posture"
|
||||
"github.com/openziti/ziti/v2/controller/storage/boltz"
|
||||
)
|
||||
|
||||
@@ -53,15 +52,19 @@ func (entity *PostureCheckProcess) LoadValues(bucket *boltz.TypedBucket) {
|
||||
entity.OperatingSystem = bucket.GetStringOrError(FieldPostureCheckProcessOs)
|
||||
entity.Path = bucket.GetStringOrError(FieldPostureCheckProcessPath)
|
||||
entity.Hashes = bucket.GetStringList(FieldPostureCheckProcessHashes)
|
||||
entity.Fingerprint = bucket.GetStringOrError(FieldPostureCheckProcessFingerprint)
|
||||
entity.Fingerprint = posture.CleanHexString(bucket.GetStringOrError(FieldPostureCheckProcessFingerprint))
|
||||
|
||||
for i, hash := range entity.Hashes {
|
||||
entity.Hashes[i] = posture.CleanHexString(hash)
|
||||
}
|
||||
}
|
||||
|
||||
func (entity *PostureCheckProcess) SetValues(ctx *boltz.PersistContext, bucket *boltz.TypedBucket) {
|
||||
|
||||
entity.Fingerprint = strings.ToLower(entity.Fingerprint)
|
||||
entity.Fingerprint = posture.CleanHexString(entity.Fingerprint)
|
||||
|
||||
for i, hash := range entity.Hashes {
|
||||
entity.Hashes[i] = strings.ToLower(hash)
|
||||
entity.Hashes[i] = posture.CleanHexString(hash)
|
||||
}
|
||||
|
||||
bucket.SetString(FieldPostureCheckProcessOs, entity.OperatingSystem, ctx.FieldChecker)
|
||||
|
||||
@@ -18,6 +18,7 @@ package db
|
||||
|
||||
import (
|
||||
"github.com/michaelquigley/pfxlog"
|
||||
"github.com/openziti/ziti/v2/common/posture"
|
||||
"github.com/openziti/ziti/v2/controller/storage/boltz"
|
||||
)
|
||||
|
||||
@@ -52,6 +53,18 @@ func (entity *PostureCheckProcessMulti) GetTypeId() string {
|
||||
return PostureCheckTypeProcessMulti
|
||||
}
|
||||
|
||||
// cleanProcessMultiValues normalizes a process's hashes and signer fingerprints to lowercase
|
||||
// unseparated hex, the form they are compared in.
|
||||
func cleanProcessMultiValues(proc *ProcessMulti) {
|
||||
for i, hash := range proc.Hashes {
|
||||
proc.Hashes[i] = posture.CleanHexString(hash)
|
||||
}
|
||||
|
||||
for i, fingerprint := range proc.SignerFingerprints {
|
||||
proc.SignerFingerprints[i] = posture.CleanHexString(fingerprint)
|
||||
}
|
||||
}
|
||||
|
||||
func (entity *PostureCheckProcessMulti) LoadValues(bucket *boltz.TypedBucket) {
|
||||
entity.Semantic = bucket.GetStringOrError(FieldSemantic)
|
||||
|
||||
@@ -68,6 +81,8 @@ func (entity *PostureCheckProcessMulti) LoadValues(bucket *boltz.TypedBucket) {
|
||||
proc.SignerFingerprints = procBucket.GetStringList(FieldPostureCheckProcessMultiSignerFingerprints)
|
||||
proc.Hashes = procBucket.GetStringList(FieldPostureCheckProcessMultiHashes)
|
||||
|
||||
cleanProcessMultiValues(proc)
|
||||
|
||||
entity.Processes = append(entity.Processes, proc)
|
||||
}
|
||||
}
|
||||
@@ -79,6 +94,8 @@ func (entity *PostureCheckProcessMulti) SetValues(ctx *boltz.PersistContext, buc
|
||||
|
||||
seenKeys := map[string]struct{}{}
|
||||
for _, proc := range entity.Processes {
|
||||
cleanProcessMultiValues(proc)
|
||||
|
||||
key := proc.OsType + "-" + proc.Path
|
||||
seenKeys[key] = struct{}{}
|
||||
|
||||
|
||||
@@ -0,0 +1,166 @@
|
||||
/*
|
||||
Copyright NetFoundry Inc.
|
||||
|
||||
Licensed 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
|
||||
|
||||
https://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 CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package db
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/openziti/ziti/v2/common/eid"
|
||||
"github.com/openziti/ziti/v2/controller/storage/boltz"
|
||||
"github.com/openziti/ziti/v2/controller/storage/boltztest"
|
||||
)
|
||||
|
||||
const (
|
||||
dirtyProcessHash = "3C:DA:EF:ED:01:38:A1:D0:1D:F9:AC:5C:8A:57:F0:2B:29:C2:4A:31:20:16:14:C5:1A:59:2E:EE:D2:E5:F7:F3"
|
||||
cleanProcessHash = "3cdaefed0138a1d01df9ac5c8a57f02b29c24a31201614c51a592eeed2e5f7f3"
|
||||
dirtyProcessFingerprint = "F1B2A6E9A37DFC918BD495E79B03DBBE6CB7477E3C6A0C29FF476C2B9A43AD0F\n"
|
||||
cleanProcessFingerprint = "f1b2a6e9a37dfc918bd495e79b03dbbe6cb7477e3c6a0c29ff476c2b9a43ad0f"
|
||||
|
||||
testProcessOsType = "Windows"
|
||||
testProcessPath = "C:\\example\\path\\1.exe"
|
||||
)
|
||||
|
||||
// newProcessPostureCheck builds a PROCESS posture check carrying the given configured hashes and
|
||||
// signer fingerprint.
|
||||
func newProcessPostureCheck(hashes []string, fingerprint string) *PostureCheck {
|
||||
return &PostureCheck{
|
||||
BaseExtEntity: boltz.BaseExtEntity{Id: eid.New()},
|
||||
Name: eid.New(),
|
||||
TypeId: PostureCheckTypeProcess,
|
||||
SubType: &PostureCheckProcess{
|
||||
OperatingSystem: testProcessOsType,
|
||||
Path: testProcessPath,
|
||||
Hashes: hashes,
|
||||
Fingerprint: fingerprint,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// newProcessMultiPostureCheck builds a PROCESS_MULTI posture check with a single process carrying
|
||||
// the given configured hashes and signer fingerprints.
|
||||
func newProcessMultiPostureCheck(hashes, fingerprints []string) *PostureCheck {
|
||||
return &PostureCheck{
|
||||
BaseExtEntity: boltz.BaseExtEntity{Id: eid.New()},
|
||||
Name: eid.New(),
|
||||
TypeId: PostureCheckTypeProcessMulti,
|
||||
SubType: &PostureCheckProcessMulti{
|
||||
Semantic: SemanticAllOf,
|
||||
Processes: []*ProcessMulti{
|
||||
{
|
||||
OsType: testProcessOsType,
|
||||
Path: testProcessPath,
|
||||
Hashes: hashes,
|
||||
SignerFingerprints: fingerprints,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Test_PostureCheckProcessStore_NormalizesConfiguredValues locks in that a PROCESS check's
|
||||
// configured hashes and signer fingerprint are stored as lowercase unseparated hex, whatever case
|
||||
// and separator style they were configured in.
|
||||
func Test_PostureCheckProcessStore_NormalizesConfiguredValues(t *testing.T) {
|
||||
ctx := NewTestContext(t)
|
||||
defer ctx.Cleanup()
|
||||
|
||||
check := newProcessPostureCheck([]string{dirtyProcessHash}, dirtyProcessFingerprint)
|
||||
|
||||
boltztest.RequireCreate(ctx, check)
|
||||
boltztest.RequireReload(ctx, check)
|
||||
|
||||
stored := check.SubType.(*PostureCheckProcess)
|
||||
ctx.Equal([]string{cleanProcessHash}, stored.Hashes)
|
||||
ctx.Equal(cleanProcessFingerprint, stored.Fingerprint)
|
||||
}
|
||||
|
||||
// Test_PostureCheckProcessMultiStore_NormalizesConfiguredValues locks in the same for a
|
||||
// PROCESS_MULTI check, whose configured values are stored per process.
|
||||
func Test_PostureCheckProcessMultiStore_NormalizesConfiguredValues(t *testing.T) {
|
||||
ctx := NewTestContext(t)
|
||||
defer ctx.Cleanup()
|
||||
|
||||
check := newProcessMultiPostureCheck([]string{dirtyProcessHash}, []string{dirtyProcessFingerprint})
|
||||
|
||||
boltztest.RequireCreate(ctx, check)
|
||||
boltztest.RequireReload(ctx, check)
|
||||
|
||||
stored := check.SubType.(*PostureCheckProcessMulti)
|
||||
ctx.Require().Len(stored.Processes, 1)
|
||||
ctx.Equal([]string{cleanProcessHash}, stored.Processes[0].Hashes)
|
||||
ctx.Equal([]string{cleanProcessFingerprint}, stored.Processes[0].SignerFingerprints)
|
||||
}
|
||||
|
||||
// Test_PostureCheckProcessStore_NormalizesStoredValuesOnLoad locks in that a PROCESS check
|
||||
// persisted before normalization reads back normalized, without waiting for the check to be saved
|
||||
// again.
|
||||
func Test_PostureCheckProcessStore_NormalizesStoredValuesOnLoad(t *testing.T) {
|
||||
ctx := NewTestContext(t)
|
||||
defer ctx.Cleanup()
|
||||
|
||||
check := newProcessPostureCheck([]string{cleanProcessHash}, cleanProcessFingerprint)
|
||||
boltztest.RequireCreate(ctx, check)
|
||||
|
||||
err := ctx.GetDb().Update(nil, func(mc boltz.MutateContext) error {
|
||||
bucket := ctx.stores.PostureCheck.GetEntityBucket(mc.Tx(), []byte(check.Id))
|
||||
ctx.Require().NotNil(bucket)
|
||||
|
||||
typeBucket := bucket.GetOrCreateBucket(PostureCheckTypeProcess)
|
||||
typeBucket.SetStringList(FieldPostureCheckProcessHashes, []string{dirtyProcessHash}, nil)
|
||||
typeBucket.SetString(FieldPostureCheckProcessFingerprint, dirtyProcessFingerprint, nil)
|
||||
|
||||
return typeBucket.GetError()
|
||||
})
|
||||
ctx.Require().NoError(err)
|
||||
|
||||
boltztest.RequireReload(ctx, check)
|
||||
|
||||
stored := check.SubType.(*PostureCheckProcess)
|
||||
ctx.Equal([]string{cleanProcessHash}, stored.Hashes)
|
||||
ctx.Equal(cleanProcessFingerprint, stored.Fingerprint)
|
||||
}
|
||||
|
||||
// Test_PostureCheckProcessMultiStore_NormalizesStoredValuesOnLoad locks in the same for a
|
||||
// PROCESS_MULTI check.
|
||||
func Test_PostureCheckProcessMultiStore_NormalizesStoredValuesOnLoad(t *testing.T) {
|
||||
ctx := NewTestContext(t)
|
||||
defer ctx.Cleanup()
|
||||
|
||||
check := newProcessMultiPostureCheck([]string{cleanProcessHash}, []string{cleanProcessFingerprint})
|
||||
boltztest.RequireCreate(ctx, check)
|
||||
|
||||
err := ctx.GetDb().Update(nil, func(mc boltz.MutateContext) error {
|
||||
bucket := ctx.stores.PostureCheck.GetEntityBucket(mc.Tx(), []byte(check.Id))
|
||||
ctx.Require().NotNil(bucket)
|
||||
|
||||
typeBucket := bucket.GetOrCreateBucket(PostureCheckTypeProcessMulti)
|
||||
processesBucket := typeBucket.GetOrCreateBucket(FieldPostureCheckProcessMultiProcesses)
|
||||
procBucket := processesBucket.GetOrCreateBucket(testProcessOsType + "-" + testProcessPath)
|
||||
procBucket.SetStringList(FieldPostureCheckProcessMultiHashes, []string{dirtyProcessHash}, nil)
|
||||
procBucket.SetStringList(FieldPostureCheckProcessMultiSignerFingerprints, []string{dirtyProcessFingerprint}, nil)
|
||||
|
||||
return procBucket.GetError()
|
||||
})
|
||||
ctx.Require().NoError(err)
|
||||
|
||||
boltztest.RequireReload(ctx, check)
|
||||
|
||||
stored := check.SubType.(*PostureCheckProcessMulti)
|
||||
ctx.Require().Len(stored.Processes, 1)
|
||||
ctx.Equal([]string{cleanProcessHash}, stored.Processes[0].Hashes)
|
||||
ctx.Equal([]string{cleanProcessFingerprint}, stored.Processes[0].SignerFingerprints)
|
||||
}
|
||||
@@ -18,8 +18,6 @@ package model
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"regexp"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
@@ -464,9 +462,3 @@ func (pr *PostureResponse) Apply(postureData *PostureData) {
|
||||
type PostureResponseSubType interface {
|
||||
Apply(postureData *PostureData)
|
||||
}
|
||||
|
||||
var macClean = regexp.MustCompile(`[^a-f\d]+`)
|
||||
|
||||
func CleanHexString(hexString string) string {
|
||||
return macClean.ReplaceAllString(strings.ToLower(hexString), "")
|
||||
}
|
||||
|
||||
@@ -18,6 +18,8 @@ package model
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/openziti/ziti/v2/common/posture"
|
||||
)
|
||||
|
||||
type PostureResponseMac struct {
|
||||
@@ -28,7 +30,7 @@ type PostureResponseMac struct {
|
||||
func (pr *PostureResponseMac) Apply(postureData *PostureData) {
|
||||
var cleanedAddresses []string
|
||||
for _, address := range pr.Addresses {
|
||||
cleanedAddresses = append(cleanedAddresses, CleanHexString(address))
|
||||
cleanedAddresses = append(cleanedAddresses, posture.CleanHexString(address))
|
||||
}
|
||||
|
||||
pr.Addresses = cleanedAddresses
|
||||
|
||||
@@ -19,6 +19,8 @@ package model
|
||||
import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/openziti/ziti/v2/common/posture"
|
||||
)
|
||||
|
||||
type PostureResponseProcess struct {
|
||||
@@ -33,10 +35,10 @@ func (pr *PostureResponseProcess) Apply(postureData *PostureData) {
|
||||
found := false
|
||||
|
||||
for i, fingerprint := range pr.SignerFingerprints {
|
||||
pr.SignerFingerprints[i] = CleanHexString(fingerprint)
|
||||
pr.SignerFingerprints[i] = posture.CleanHexString(fingerprint)
|
||||
}
|
||||
|
||||
pr.BinaryHash = CleanHexString(pr.BinaryHash)
|
||||
pr.BinaryHash = posture.CleanHexString(pr.BinaryHash)
|
||||
|
||||
for i, process := range postureData.Processes {
|
||||
if process.PostureCheckId == pr.PostureCheckId {
|
||||
|
||||
@@ -197,7 +197,7 @@ func (instance *Instance) Apply(response *edge_client_pb.PostureResponse, parser
|
||||
instance.Woken = woken
|
||||
updated = true
|
||||
}
|
||||
} else if processList := response.GetProcessList(); processList != nil {
|
||||
} else if processList := normalizedProcessList(response.GetProcessList()); processList != nil {
|
||||
if instance.mergeProcessList(processList) {
|
||||
updated = true
|
||||
}
|
||||
@@ -270,6 +270,28 @@ func (instance *Instance) mergeProcessList(incoming *edge_client_pb.PostureRespo
|
||||
return changed
|
||||
}
|
||||
|
||||
// normalizedProcessList returns a copy of a reported process list with each binary hash and signer
|
||||
// fingerprint normalized to the form the controller stores, so a process that passes a posture
|
||||
// check at the controller passes the same check at the router. The caller's message is left
|
||||
// untouched, and normalizing before the list is compared against the cached copy keeps a report
|
||||
// differing only in formatting from reading as a posture change.
|
||||
func normalizedProcessList(processList *edge_client_pb.PostureResponse_ProcessList) *edge_client_pb.PostureResponse_ProcessList {
|
||||
if processList == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
normalized := proto.Clone(processList).(*edge_client_pb.PostureResponse_ProcessList)
|
||||
|
||||
for _, process := range normalized.Processes {
|
||||
process.Hash = posture.CleanHexString(process.Hash)
|
||||
for i, fingerprint := range process.SignerFingerprints {
|
||||
process.SignerFingerprints[i] = posture.CleanHexString(fingerprint)
|
||||
}
|
||||
}
|
||||
|
||||
return normalized
|
||||
}
|
||||
|
||||
func isOsDifferent(old *edge_client_pb.PostureResponse_Os, new *edge_client_pb.PostureResponse_OperatingSystem) bool {
|
||||
if old == nil || old.Os == nil {
|
||||
return true
|
||||
|
||||
@@ -3,10 +3,10 @@ package posture
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"github.com/michaelquigley/pfxlog"
|
||||
"github.com/openziti/foundation/v2/stringz"
|
||||
"github.com/openziti/sdk-golang/pb/edge_client_pb"
|
||||
"github.com/openziti/ziti/v2/common/pb/edge_ctrl_pb"
|
||||
"github.com/openziti/ziti/v2/controller/db"
|
||||
@@ -169,12 +169,12 @@ func (p *ProcessCheck) compareProcesses(osType string, given *edge_client_pb.Pos
|
||||
return result
|
||||
}
|
||||
|
||||
if !strings.EqualFold(strings.ToLower(valid.OsType), strings.ToLower(osType)) {
|
||||
if !strings.EqualFold(valid.OsType, osType) {
|
||||
result.Reason = fmt.Errorf("os types do not match, given %s, expected: %s", osType, valid.OsType)
|
||||
return result
|
||||
}
|
||||
|
||||
if len(valid.Hashes) > 0 && !stringz.Contains(valid.Hashes, given.Hash) {
|
||||
if len(valid.Hashes) > 0 && !containsFold(valid.Hashes, given.Hash) {
|
||||
result.Reason = fmt.Errorf("hash is not valid, given %s, expected one of: %v", given.Hash, valid.Hashes)
|
||||
return result
|
||||
}
|
||||
@@ -183,12 +183,12 @@ func (p *ProcessCheck) compareProcesses(osType string, given *edge_client_pb.Pos
|
||||
validPrints := map[string]struct{}{}
|
||||
|
||||
for _, validPrint := range valid.Fingerprints {
|
||||
validPrints[validPrint] = struct{}{}
|
||||
validPrints[strings.ToLower(validPrint)] = struct{}{}
|
||||
}
|
||||
|
||||
validPrintFound := false
|
||||
for _, givenPrint := range given.SignerFingerprints {
|
||||
if _, ok := validPrints[givenPrint]; ok {
|
||||
if _, ok := validPrints[strings.ToLower(givenPrint)]; ok {
|
||||
validPrintFound = true
|
||||
break
|
||||
}
|
||||
@@ -202,3 +202,11 @@ func (p *ProcessCheck) compareProcesses(osType string, given *edge_client_pb.Pos
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// containsFold reports whether values holds target, ignoring case. Configured hex values are
|
||||
// stored as the administrator entered them, so they are compared case-insensitively.
|
||||
func containsFold(values []string, target string) bool {
|
||||
return slices.ContainsFunc(values, func(value string) bool {
|
||||
return strings.EqualFold(value, target)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"github.com/openziti/sdk-golang/pb/edge_client_pb"
|
||||
"github.com/openziti/ziti/v2/common/pb/edge_ctrl_pb"
|
||||
"github.com/openziti/ziti/v2/controller/db"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
const testProcPath = "C:\\Windows\\System32\\notepad.exe"
|
||||
@@ -88,3 +89,141 @@ func TestProcessCheck_NilInstanceData(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const (
|
||||
lowerHash = "3a7bd3e2360a3d29eea436fcfb7e44c735d117c42d1c1835420b6b9942dd4f1b"
|
||||
upperHash = "3A7BD3E2360A3D29EEA436FCFB7E44C735D117C42D1C1835420B6B9942DD4F1B"
|
||||
otherHash = "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08"
|
||||
|
||||
lowerPrint = "a1b2c3d4e5f60718293a4b5c6d7e8f9012345678"
|
||||
upperPrint = "A1B2C3D4E5F60718293A4B5C6D7E8F9012345678"
|
||||
separatedPrint = "A1:B2:C3:D4:E5:F6:07:18:29:3A:4B:5C:6D:7E:8F:90:12:34:56:78"
|
||||
otherPrint = "0011223344556677889900aabbccddeeff001122"
|
||||
)
|
||||
|
||||
// newProcessCheckWith builds a single-process AllOf check carrying the configured hashes and
|
||||
// signer fingerprints, as the router receives them from the controller's data state.
|
||||
func newProcessCheckWith(hashes, fingerprints []string) *ProcessCheck {
|
||||
check := newProcessCheck(db.SemanticAllOf)
|
||||
check.Processes[0].Hashes = hashes
|
||||
check.Processes[0].Fingerprints = fingerprints
|
||||
return check
|
||||
}
|
||||
|
||||
// reportedProcess builds posture state for a running process at the required path with the given
|
||||
// reported hash and signer fingerprints.
|
||||
func reportedProcess(hash string, fingerprints []string) *InstanceData {
|
||||
return &InstanceData{
|
||||
Os: &edge_client_pb.PostureResponse_Os{
|
||||
Os: &edge_client_pb.PostureResponse_OperatingSystem{Type: "Windows"},
|
||||
},
|
||||
ProcessList: &edge_client_pb.PostureResponse_ProcessList{
|
||||
Processes: []*edge_client_pb.PostureResponse_Process{
|
||||
{
|
||||
Path: testProcPath,
|
||||
IsRunning: true,
|
||||
Hash: hash,
|
||||
SignerFingerprints: fingerprints,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// reportedProcessResponse builds a single-entry process list response for the required path with
|
||||
// the given reported hash and signer fingerprints.
|
||||
func reportedProcessResponse(hash string, fingerprints []string) *edge_client_pb.PostureResponse {
|
||||
return processListResponse(&edge_client_pb.PostureResponse_Process{
|
||||
Path: testProcPath,
|
||||
IsRunning: true,
|
||||
Hash: hash,
|
||||
SignerFingerprints: fingerprints,
|
||||
})
|
||||
}
|
||||
|
||||
// Test_ProcessCheck_HashCaseIsIgnored locks in that hex hashes differing only in case are the same
|
||||
// hash, as the controller treats them, in both directions.
|
||||
func Test_ProcessCheck_HashCaseIsIgnored(t *testing.T) {
|
||||
t.Run("configured uppercase, reported lowercase", func(t *testing.T) {
|
||||
check := newProcessCheckWith([]string{upperHash}, nil)
|
||||
|
||||
require.Nil(t, check.Evaluate(reportedProcess(lowerHash, nil)))
|
||||
})
|
||||
|
||||
t.Run("configured lowercase, reported uppercase", func(t *testing.T) {
|
||||
check := newProcessCheckWith([]string{lowerHash}, nil)
|
||||
|
||||
require.Nil(t, check.Evaluate(reportedProcess(upperHash, nil)))
|
||||
})
|
||||
}
|
||||
|
||||
// Test_ProcessCheck_DifferentHashFails locks in that case insensitivity does not make a genuinely
|
||||
// different hash pass.
|
||||
func Test_ProcessCheck_DifferentHashFails(t *testing.T) {
|
||||
check := newProcessCheckWith([]string{upperHash}, nil)
|
||||
|
||||
require.NotNil(t, check.Evaluate(reportedProcess(otherHash, nil)))
|
||||
}
|
||||
|
||||
// Test_ProcessCheck_FingerprintCaseIsIgnored locks in the same for signer fingerprints, which the
|
||||
// controller lowercases on both sides before comparing.
|
||||
func Test_ProcessCheck_FingerprintCaseIsIgnored(t *testing.T) {
|
||||
t.Run("configured uppercase, reported lowercase", func(t *testing.T) {
|
||||
check := newProcessCheckWith(nil, []string{upperPrint})
|
||||
|
||||
require.Nil(t, check.Evaluate(reportedProcess(lowerHash, []string{lowerPrint})))
|
||||
})
|
||||
|
||||
t.Run("configured lowercase, reported uppercase", func(t *testing.T) {
|
||||
check := newProcessCheckWith(nil, []string{lowerPrint})
|
||||
|
||||
require.Nil(t, check.Evaluate(reportedProcess(lowerHash, []string{upperPrint})))
|
||||
})
|
||||
}
|
||||
|
||||
// Test_ProcessCheck_DifferentFingerprintFails locks in that case insensitivity does not make an
|
||||
// unrelated signer pass.
|
||||
func Test_ProcessCheck_DifferentFingerprintFails(t *testing.T) {
|
||||
check := newProcessCheckWith(nil, []string{upperPrint})
|
||||
|
||||
require.NotNil(t, check.Evaluate(reportedProcess(lowerHash, []string{otherPrint})))
|
||||
}
|
||||
|
||||
// Test_ProcessCheck_ReportedValuesNormalizedOnIngest locks in that reported hashes and signer
|
||||
// fingerprints are stored in the normalized form the controller stores them in, whatever case and
|
||||
// separator style the client sent.
|
||||
func Test_ProcessCheck_ReportedValuesNormalizedOnIngest(t *testing.T) {
|
||||
instance := newInstance()
|
||||
|
||||
updated := instance.Apply(reportedProcessResponse(upperHash, []string{separatedPrint}), nil)
|
||||
|
||||
data := snapshotData(instance)
|
||||
|
||||
require.True(t, updated)
|
||||
require.Len(t, data.ProcessList.Processes, 1)
|
||||
require.Equal(t, lowerHash, data.ProcessList.Processes[0].Hash)
|
||||
require.Equal(t, []string{lowerPrint}, data.ProcessList.Processes[0].SignerFingerprints)
|
||||
}
|
||||
|
||||
// Test_ProcessCheck_NormalizationDoesNotMutateResponse locks in that ingest normalization leaves
|
||||
// the caller's protobuf message untouched.
|
||||
func Test_ProcessCheck_NormalizationDoesNotMutateResponse(t *testing.T) {
|
||||
instance := newInstance()
|
||||
response := reportedProcessResponse(upperHash, []string{separatedPrint})
|
||||
|
||||
instance.Apply(response, nil)
|
||||
|
||||
require.Equal(t, upperHash, response.GetProcessList().Processes[0].Hash)
|
||||
require.Equal(t, []string{separatedPrint}, response.GetProcessList().Processes[0].SignerFingerprints)
|
||||
}
|
||||
|
||||
// Test_ProcessCheck_RepeatedResponseNotSeenAsChange locks in that re-reporting the same process in
|
||||
// its uppercase form does not read as a posture change once the stored copy is normalized.
|
||||
func Test_ProcessCheck_RepeatedResponseNotSeenAsChange(t *testing.T) {
|
||||
instance := newInstance()
|
||||
require.True(t, instance.Apply(reportedProcessResponse(upperHash, []string{separatedPrint}), nil))
|
||||
|
||||
updated := instance.Apply(reportedProcessResponse(upperHash, []string{separatedPrint}), nil)
|
||||
|
||||
require.False(t, updated)
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ import (
|
||||
"github.com/google/uuid"
|
||||
"github.com/openziti/edge-api/rest_model"
|
||||
"github.com/openziti/ziti/v2/common/eid"
|
||||
"github.com/openziti/ziti/v2/common/posture"
|
||||
)
|
||||
|
||||
func Test_PostureChecks_ProcessMulti(t *testing.T) {
|
||||
@@ -781,12 +782,22 @@ func Test_PostureChecks_ProcessMulti(t *testing.T) {
|
||||
ctx.testContextChanged(t)
|
||||
|
||||
for _, patchProcess := range patchProcesses {
|
||||
var expectedHashes []string
|
||||
for _, hash := range patchProcess.Hashes {
|
||||
expectedHashes = append(expectedHashes, posture.CleanHexString(hash))
|
||||
}
|
||||
|
||||
var expectedFingerprints []string
|
||||
for _, fingerprint := range patchProcess.SignerFingerprints {
|
||||
expectedFingerprints = append(expectedFingerprints, posture.CleanHexString(fingerprint))
|
||||
}
|
||||
|
||||
isMatched := false
|
||||
for _, getProcess := range getCheck.Processes {
|
||||
if *getProcess.OsType == *patchProcess.OsType && *getProcess.Path == *patchProcess.Path {
|
||||
isMatched = true
|
||||
ctx.Req.ElementsMatch(patchProcess.Hashes, getProcess.Hashes)
|
||||
ctx.Req.ElementsMatch(patchProcess.SignerFingerprints, getProcess.SignerFingerprints)
|
||||
ctx.Req.ElementsMatch(expectedHashes, getProcess.Hashes)
|
||||
ctx.Req.ElementsMatch(expectedFingerprints, getProcess.SignerFingerprints)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,7 +47,7 @@ func Test_PostureCheck_SDK_Process_Multi_OIDC(t *testing.T) {
|
||||
|
||||
targetHash := "3cdaefed0138a1d01df9ac5c8a57f02b29c24a31201614c51a592eeed2e5f7f3"
|
||||
targetPath := "C:\\example\\path\\1.exe"
|
||||
targetSignerFingerprint := "f1b2a6e9a37dfc918bd495e79b03dbbe6cb7477e3c6a0c29ff476c2b9a43ad0f\n"
|
||||
targetSignerFingerprint := "f1b2a6e9a37dfc918bd495e79b03dbbe6cb7477e3c6a0c29ff476c2b9a43ad0f"
|
||||
targetProcess := &rest_model.ProcessMulti{
|
||||
Hashes: []string{targetHash},
|
||||
OsType: ToPtr(rest_model.OsTypeWindows),
|
||||
|
||||
@@ -47,7 +47,7 @@ func Test_PostureCheck_SDK_Process_OIDC(t *testing.T) {
|
||||
|
||||
targetHash := "3cdaefed0138a1d01df9ac5c8a57f02b29c24a31201614c51a592eeed2e5f7f3"
|
||||
targetPath := "C:\\example\\path\\1.exe"
|
||||
targetSignerFingerprint := "f1b2a6e9a37dfc918bd495e79b03dbbe6cb7477e3c6a0c29ff476c2b9a43ad0f\n"
|
||||
targetSignerFingerprint := "f1b2a6e9a37dfc918bd495e79b03dbbe6cb7477e3c6a0c29ff476c2b9a43ad0f"
|
||||
targetProcess := &rest_model.Process{
|
||||
Hashes: []string{targetHash},
|
||||
OsType: ToPtr(rest_model.OsTypeWindows),
|
||||
|
||||
Reference in New Issue
Block a user