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

@zwass zwass commented Nov 3, 2020

Copy link
Copy Markdown
Member
  • 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

zwass commented Nov 13, 2020

Copy link
Copy Markdown
Member Author

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

@melkikh

melkikh commented Nov 17, 2020 via email

Copy link
Copy Markdown

@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

melkikh commented Nov 17, 2020

Copy link
Copy Markdown

Yes, you are right

@zwass

zwass commented Nov 17, 2020

Copy link
Copy Markdown
Member Author

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

melkikh commented Nov 18, 2020

Copy link
Copy Markdown

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 -->
RachelElysia added a commit that referenced this pull request Jul 21, 2026
- (#4) Deep dive: drop the optional onAddFleet prop entirely rather
  than carrying a dead migration path. `browserHistory` is fine in
  frontend/components/ (used by AuthenticationNav and CommandPalette),
  so the fallback becomes the sole path — no dead code, no half-wired
  interface. Consumers that ever need to override navigation can add
  the prop back at that point.
- (#5) Add `aria-label="Search fleets"` to the search input — screen
  readers skip placeholder-as-label once the field has a value.
- (#6) Drop `name="fleet-search-input"` (not a form-submitted field)
  and add `autoComplete="off"` so browsers don't pop autofill
  suggestions on top of the option list.
- (#7) Rename `TeamsDropdown` -> `FleetsDropdown` in
  frontend/docs/patterns.md:459 (Fleet switcher reference).
- (#8) Use getPathWithQueryParams(PATHS.ADMIN_FLEETS, { create_fleet:
  "1" }) instead of the plain template string — matches the
  codebase's URL-construction convention.
RachelElysia added a commit that referenced this pull request Jul 21, 2026
- (#1) Extract getHiddenInput() helper. The double-cast to reach
  react-select's undocumented `inputRef` field was duplicated at two
  sites (menu-open focus effect + forwardNavKey bridge). Both now
  read through the helper — if react-select ever renames the field,
  the escape hatch fails in one place, not two.

- (#2) Stash onClose in onCloseRef so an inline callback from a
  parent doesn't retrigger the menuIsOpen-transition effect on every
  parent render. The effect deps are back to [menuIsOpen] alone and
  reads onCloseRef.current?.() at fire time.

- (#5) Extract isPrimoModeEnabled / isGitOpsModeEnabled locals
  instead of inlining the three nested optional-chain checks. Matches
  the permissions.isPrimoMode / permissions.isSandboxMode naming
  used elsewhere in the codebase and reads at a glance.
RachelElysia added a commit that referenced this pull request Jul 22, 2026
  stale comment, side effects in state updater

- (#2) Rework the scroll-fade to a 0-height sticky anchor with an
  absolutely-positioned ::before pseudo-element toggled via opacity on
  a --visible modifier. The old approach mounted/unmounted a 35px
  sticky div, so MenuList.scrollHeight grew and shrank as the fade
  toggled — browsers can clamp scrollTop when scrollHeight shrinks,
  causing a visible jump near the end of the list. The anchor is
  always in the DOM at 0 flow height; scroll metrics stay stable.

- (#3) Chain react-select's own innerProps.onScroll and innerProps.
  onMouseDown before our custom logic. The previous spread-then-
  override pattern silently dropped whatever react-select provided,
  which could break its own scroll-to-highlighted-option or focus
  tracking on a future version.

- (#5) Fix stale comment on the hidden-input-focus useEffect. It said
  "CustomMenuList focuses the search input on mount," but the search
  input now lives in CustomMenu and uses native autoFocus. Corrected
  the comment to reflect where focus actually comes from.

- (#6) Move side effects (onOpen? / setSearchQuery) out of the
  setMenuIsOpen state updater in toggleMenu. React's Strict Mode
  double-invokes state updaters, which would double-fire onOpen and
  double-clear searchQuery. Reading menuIsOpen directly is safe — a
  single user click can't race with itself.
RachelElysia added a commit that referenced this pull request Jul 29, 2026
- (#3) Deleted orphaned components/UploadListHeading/ folder — no
  external importers left after the tab-header refactor.
- (#4) ConfigurationProfiles: hide the __tab-header (description +
  Add button) when !mdmEnabled so the "Create and upload configuration
  profiles..." copy no longer contradicts the "MDM must be turned on"
  EmptyState directly below.
- (#5) Extracted the duplicated __tab-header block into a
  @mixin tab-header in styles/var/mixins.scss; six copies across five
  files now @include it.
- (#6) Comment on .profiles-tab explaining why ConfigurationProfiles
  splits its card wrapper from its per-tab layout (two inner tabs
  mirroring .assets-tab) while Certificates / ScriptLibrary stay
  single-block (one-tab card).
- (#7) Comment on <span className="controls"> in TableContainer.tsx
  flagging the load-bearing shape that _styles.scss's :has(.controls
  > *) collapse rule depends on.
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