diff --git a/common/posture/hex.go b/common/posture/hex.go index a94a0479b..08106a090 100644 --- a/common/posture/hex.go +++ b/common/posture/hex.go @@ -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) } diff --git a/controller/db/posture_check_process.go b/controller/db/posture_check_process.go index a45da6ac9..90d302c0b 100644 --- a/controller/db/posture_check_process.go +++ b/controller/db/posture_check_process.go @@ -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) diff --git a/controller/db/posture_check_process_multi.go b/controller/db/posture_check_process_multi.go index 407795609..1c2555905 100644 --- a/controller/db/posture_check_process_multi.go +++ b/controller/db/posture_check_process_multi.go @@ -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{}{} diff --git a/controller/db/posture_check_process_store_test.go b/controller/db/posture_check_process_store_test.go new file mode 100644 index 000000000..a3074f6f8 --- /dev/null +++ b/controller/db/posture_check_process_store_test.go @@ -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) +} diff --git a/controller/model/posture_response_model.go b/controller/model/posture_response_model.go index 299660c18..c1cbed03b 100644 --- a/controller/model/posture_response_model.go +++ b/controller/model/posture_response_model.go @@ -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), "") -} diff --git a/controller/model/posture_response_model_mac.go b/controller/model/posture_response_model_mac.go index eb511d6a3..70ec04e16 100644 --- a/controller/model/posture_response_model_mac.go +++ b/controller/model/posture_response_model_mac.go @@ -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 diff --git a/controller/model/posture_response_model_process.go b/controller/model/posture_response_model_process.go index bf70978ec..e9d1178be 100644 --- a/controller/model/posture_response_model_process.go +++ b/controller/model/posture_response_model_process.go @@ -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 { diff --git a/router/posture/checks.go b/router/posture/checks.go index 18baa12d7..f0228c3c7 100644 --- a/router/posture/checks.go +++ b/router/posture/checks.go @@ -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 diff --git a/router/posture/process.go b/router/posture/process.go index 0b01e39af..776c2320c 100644 --- a/router/posture/process.go +++ b/router/posture/process.go @@ -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) + }) +} diff --git a/router/posture/process_test.go b/router/posture/process_test.go index fa0ef8dd5..bc1f49ce2 100644 --- a/router/posture/process_test.go +++ b/router/posture/process_test.go @@ -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) +} diff --git a/tests/posture_check_process_multi_test.go b/tests/posture_check_process_multi_test.go index 4e39910c7..ed3091778 100644 --- a/tests/posture_check_process_multi_test.go +++ b/tests/posture_check_process_multi_test.go @@ -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 } } diff --git a/tests/posture_check_sdk_process_multi_oidc_test.go b/tests/posture_check_sdk_process_multi_oidc_test.go index e5685580c..eea99b43f 100644 --- a/tests/posture_check_sdk_process_multi_oidc_test.go +++ b/tests/posture_check_sdk_process_multi_oidc_test.go @@ -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), diff --git a/tests/posture_check_sdk_process_oidc_test.go b/tests/posture_check_sdk_process_oidc_test.go index 4c8e09ecc..9cab98d0a 100644 --- a/tests/posture_check_sdk_process_oidc_test.go +++ b/tests/posture_check_sdk_process_oidc_test.go @@ -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),