Skip to content

Make enroll secret and node key validation case-sensitive#5

Merged
zwass merged 1 commit into
fleetdm:masterfrom
zwass:case-sensitive-secrets
Nov 4, 2020
Merged

Make enroll secret and node key validation case-sensitive#5
zwass merged 1 commit into
fleetdm:masterfrom
zwass:case-sensitive-secrets

Conversation

@zwass
Copy link
Copy Markdown
Member

@zwass zwass commented Nov 3, 2020

  • Modify column collation to make comparisons case-sensitive.
  • Add tests for case-sensitivity.

Fixes kolide/fleet#2333

- Modify column collation to make comparisons case-sensitive.
- Add tests for case-sensitivity.

Fixes fleetdm#2333
@zwass zwass added the bug Something isn't working as documented label Nov 3, 2020
@zwass zwass merged commit fca44bb into fleetdm:master Nov 4, 2020
@zwass zwass deleted the case-sensitive-secrets branch November 4, 2020 20:09
@zwass
Copy link
Copy Markdown
Member Author

zwass commented Nov 13, 2020

@melkikh we fixed this based on your report in kolide/fleet#2333.

@melkikh
Copy link
Copy Markdown

melkikh commented Nov 17, 2020 via email

@mikermcneil
Copy link
Copy Markdown
Member

thank you for drawing attention to the inconsistency @melkikh!

Some quick math: Let's say your enroll secret is 6NDwyAcGbcBZTZ3I8buIN9IKq1RkSsKB; 32 characters long.

Assuming each character is a-Z0-9, you've got 26+26+10 possibilities per slot, so 62^32 tries to guess it accurately.

Prior to this change the effort of correctly guessing an enroll secret was only 36^32. This increases the number of guesses required to 62^32.

In other words, instead of 6.334028666297328e+49 guesses, after this change, it would take 2.2726578844967515e+57 guesses.

Does that sound right?

@melkikh
Copy link
Copy Markdown

melkikh commented Nov 17, 2020

Yes, you are right

@zwass
Copy link
Copy Markdown
Member Author

zwass commented Nov 17, 2020

Thank you for the clarification. Can you explain a scenario in which this difference in entropy may have allowed an attacker to impersonate a host? We would like to be careful not to issue a CVE unless we can define real risk to users.

@melkikh
Copy link
Copy Markdown

melkikh commented Nov 18, 2020

I think this only applies to the secret generated by the user.
If I understand correctly, the user can specify the following:
https://github.com/kolide/fleet/blob/master/docs/cli/file-format.md#enroll-secrets
This secret can be with low entropy

TsekNet added a commit to TsekNet/fleet that referenced this pull request Mar 22, 2026
- Extract nested block to tryReuseExistingInstaller helper (fleetdm#5)
- Add 30s timeout to HEAD request client (fleetdm#2)
- Validate URL scheme (http/https only) for SSRF defense (fleetdm#1)
- Weak ETag comparison: strip W/ prefix per RFC 7232 (fleetdm#3)
- Validate ETag/Last-Modified format before storing (fleetdm#6)
- Move HTTPETag/HTTPLastModified into fillSoftwareInstallerPayloadFromExisting (fleetdm#9)
- Remove duplicate store-existence check (fleetdm#7)
- Add ORDER BY si.id DESC to LIMIT 1 query (fleetdm#8)
- Use ds.writer (primary) for GetInstallerByTeamAndURL (fleetdm#13)
- Rename checkURLChanged to hasURLContentChanged (fleetdm#11)
- Rename urlContentUnchanged to canSkipDownload (fleetdm#16)
- Add nil guard in mock to prevent panic in existing tests
- Fix schema.sql collation to match migration output
- Fix lint: use svc.logger instead of bare slog.Warn
- Add tests: weak ETag, both headers precedence, 403/500 status,
  non-HTTP scheme, normalizeETag, validETag (fleetdm#3,12,14,17)
- Document redirect limitation (fleetdm#15)
getvictor added a commit that referenced this pull request Apr 6, 2026
<!-- Add the related story/sub-task/bug number, like Resolves #123, or
remove if NA -->
**Related issue:** Resolves #42836 

This is another hot path optimization.

## Before

When a host submits policy results via `SubmitDistributedQueryResults`,
the system needed to determine which policies "flipped" (changed from
passing to failing or vice versa). Each consumer computed this
independently:

```
SubmitDistributedQueryResults(policyResults)
  |
  +-- processScriptsForNewlyFailingPolicies
  |     filter to failing policies with scripts
  |     BUILD SUBSET of results
  |     CALL FlippingPoliciesForHost(subset)          <-- DB query #1
  |     convert result to set, filter, queue scripts
  |
  +-- processSoftwareForNewlyFailingPolicies
  |     filter to failing policies with installers
  |     BUILD SUBSET of results
  |     CALL FlippingPoliciesForHost(subset)          <-- DB query #2
  |     convert result to set, filter, queue installs
  |
  +-- processVPPForNewlyFailingPolicies
  |     filter to failing policies with VPP apps
  |     BUILD SUBSET of results
  |     CALL FlippingPoliciesForHost(subset)          <-- DB query #3
  |     convert result to set, filter, queue VPP
  |
  +-- webhook filtering
  |     filter to webhook-enabled policies
  |     CALL FlippingPoliciesForHost(subset)          <-- DB query #4
  |     register flipped policies in Redis
  |
  +-- RecordPolicyQueryExecutions
        CALL FlippingPoliciesForHost(all results)     <-- DB query #5
        reset attempt counters for newly passing
        INSERT/UPDATE policy_membership
```

Each `FlippingPoliciesForHost` call runs `SELECT policy_id, passes FROM
policy_membership WHERE host_id = ? AND policy_id IN (?)`. All 5 queries
hit the same table for the same host before `policy_membership` is
updated, so they all see identical state.

Each consumer also built intermediate maps to narrow down to its subset
before calling `FlippingPoliciesForHost`, then converted the result into
yet another set for filtering. This meant 3-4 temporary maps per
consumer.

## After

```
SubmitDistributedQueryResults(policyResults)
  |
  CALL FlippingPoliciesForHost(all results)           <-- single DB query
  build newFailingSet, normalize newPassing
  |
  +-- processScriptsForNewlyFailingPolicies
  |     filter to failing policies with scripts
  |     CHECK newFailingSet (in-memory map lookup)
  |     queue scripts
  |
  +-- processSoftwareForNewlyFailingPolicies
  |     filter to failing policies with installers
  |     CHECK newFailingSet (in-memory map lookup)
  |     queue installs
  |
  +-- processVPPForNewlyFailingPolicies
  |     filter to failing policies with VPP apps
  |     CHECK newFailingSet (in-memory map lookup)
  |     queue VPP
  |
  +-- webhook filtering
  |     filter to webhook-enabled policies
  |     FILTER newFailing/newPassing by policy IDs (in-memory)
  |     register flipped policies in Redis
  |
  +-- RecordPolicyQueryExecutions
        USE pre-computed newPassing (skip DB query)
        reset attempt counters for newly passing
        INSERT/UPDATE policy_membership
```

The intermediate subset maps and per-consumer set conversions are
removed. Each process function goes directly from "policies with
associated automation" to "is this policy in newFailingSet?" in a single
map lookup.

# Checklist for submitter

If some of the following don't apply, delete the relevant line.

- [x] Changes file added for user-visible changes in `changes/`,
`orbit/changes/` or `ee/fleetd-chrome/changes`.

## Testing

- [x] Added/updated automated tests
- [x] QA'd all new/changed functionality manually


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **Performance Improvements**
* Reduced redundant database queries during policy result submissions by
computing flipping policies once per host check-in instead of multiple
times.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working as documented

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Case-insensitive enroll secret and node_key validation

3 participants