Skip to content

feat(openframe): shared-DB multi-tenancy (tenant = Fleet team) - #76

Merged
oleksandrd-flamingo merged 4 commits into
mainfrom
feature/mysql-multitenancy
Jul 22, 2026
Merged

feat(openframe): shared-DB multi-tenancy (tenant = Fleet team)#76
oleksandrd-flamingo merged 4 commits into
mainfrom
feature/mysql-multitenancy

Conversation

@oleksandrd-flamingo

@oleksandrd-flamingo oleksandrd-flamingo commented Jul 21, 2026

Copy link
Copy Markdown

Fleet MySQL multi-tenancy (OpenFrame)

Enables one Fleet server + one shared MySQL to serve many tenants, where tenant = Fleet team (team_id), with row-level isolation enforced at the datastore. Gated by FLEET_OPENFRAME_MULTI_TENANCY_ENABLED (openframe.fleet.multi-tenancy.enabled). Flag off ⇒ pre-feature fork behavior (schema change aside — it is semantics-preserving; see below).

Full reference: openframe/docs/mysql-multitenancy-feature.md.

What's included

  • Master flag & modes — off / pinned (one Fleet per tenant) / shared (one Fleet per cluster, every request pinned individually, fail-closed).
  • IdentityOpenframeTeamID(ctx) pin chain; EnsureOpenframeTeamID UUID→team_id bridge, which also auto-seeds a new team's enroll secret so a fresh tenant can enroll agents.
  • Migrations (idempotent) — teams.openframe_tenant_uuid; per-team unique identity/name on hosts/labels via an IFNULL(team_id,0) generated column.
  • Datastore fences — hosts, policies, queries, enroll secrets, live-query targets, host-assignments, teams, team-keyed app_config cache. Each no-ops when unpinned.
  • Per-request pinningWithOpenframeTenant (X-Tenant-Id, agent paths exempt); agent host pins and enroll-secret pins.
  • OpsGET_LOCK migration serialization on the shared DB; Helm flag/env wiring in charts/fleet.

Backward compatibility

Flag off ⇒ identical behavior except the schema: the three migrations run unconditionally but are semantics-preserving (all rows team_id NULL → key 0 → original global uniqueness, byte-for-byte). Verified locally against a branch-native DB: prepare db applies all three; server boots healthy flag-off, flag-on-pinned (team + secret auto-created), and flag-on-shared; upstream TestLabels/TestHosts pass on the migrated schema with no OpenFrame env.

Testing

MYSQL_TEST=1 *_openframe_test.go across every fence + EnsureOpenframeTeamID (incl. secret seeding); flag-parsing unit tests in server/fleet/openframe_test.go; middleware tests in server/service/openframe_middleware_test.go; make openframe-verify.

Deferred (latent, non-applicable today)

Users/software/vuln read-views (UI not exposed past the gateway allowlist), unpinned custom-label creation, legacy 2017 packs stats join, IsEnrollSecretAvailable.

Deploy order

Deploy the fenced code (inert until pinned) → run migrations (prepare db) → backfill each tenant's team_id before flipping its flag (un-backfilled rows fail closed — hide data, never leak) → enable the flag per tenant. Greenfield (empty shared DB, agents re-enroll) skips the backfill at the cost of host history.

Note (reviewer): this branch predates the upstream 4.79 cherry-pick migration re-timestamp that stage runs; it must be synced to that lineage before deploying against a stage-derived DB (verified: Fleet refuses to start against the stage DB with "unrecognized migrations tables=[20251219201524 20251222163753]"). This is independent of the multitenancy feature.

Summary by CodeRabbit

  • New Features

    • Added OpenFrame MySQL multitenancy support with configurable tenant UUID and optional team scoping.
    • Introduced shared-mode tenant handling that requires and validates the control-plane tenant header and pins requests to the resolved tenant.
    • Serialized OpenFrame database migrations to avoid concurrent migration issues.
    • Enabled same-device enrollment across different tenants.
  • Bug Fixes

    • Enforced tenant isolation across hosts, policies, queries, labels, teams, enroll secrets, app configuration, and related CRUD/delete flows to prevent cross-tenant access.

Let one Fleet server + one shared MySQL serve many tenants, with tenant =
Fleet team (team_id) and row-level isolation enforced at the datastore. All
behavior is behind the master flag FLEET_OPENFRAME_MULTI_TENANCY_ENABLED
(platform property openframe.fleet.multi-tenancy.enabled); with the flag off
Fleet behaves exactly as the fork did before, the schema change aside.

Identity & pin (server/fleet/openframe.go, server/datastore/mysql/openframe.go):
- OpenframeTeamID(ctx): ctx pin -> UUID-resolved process pin -> env fallback
  (env inert unless the flag is on, so a stray FLEET_OPENFRAME_TEAM_ID cannot
  activate fences). Master-flag + mode helpers, unit-tested.
- EnsureOpenframeTeamID: UUID->team_id bridge (teams.openframe_tenant_uuid),
  resolve-or-create, race-safe; a newly created team is seeded with one random
  team-scoped enroll secret in the same tx so a fresh tenant can enroll.

Migrations (idempotent): teams.openframe_tenant_uuid bridge; hosts + labels
identity/name unique per team via a generated column IFNULL(team_id,0) -- flag
off collapses to the original global uniqueness bit-for-bit.

Datastore fences (no-op when unpinned): hosts (list/count/by-id/identifier/
delete/enroll-match), policies & queries (list/by-id/CRUD/GitOps), enroll
secrets (get/apply/verify), live-query targets, host-assignments, teams reads,
team-keyed app_config cache.

Per-request pinning: WithOpenframeTenant middleware (X-Tenant-Id, fail closed,
agent paths exempt); agent host pins (openframePinHostTeam) and enrollment
pins from secret.TeamID.

Ops: GET_LOCK('openframe_fleet_migrations') serializes prepare-db across
clusters on the shared DB; Helm flag/env wiring in charts/fleet.

Tests: *_openframe_test.go (MYSQL_TEST=1) across enrollment, host/policy/query/
label/target/team/app-config fences, EnsureOpenframeTeamID + secret seeding;
flag-parsing unit tests; middleware tests.

Doc: openframe/docs/mysql-multitenancy-feature.md
@coderabbitai

coderabbitai Bot commented Jul 21, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 9ef88cd3-9a9d-4153-983c-dc4096608717

📥 Commits

Reviewing files that changed from the base of the PR and between ef57781 and e5163b4.

📒 Files selected for processing (7)
  • charts/fleet/templates/configmap.yaml
  • charts/fleet/templates/deployment.yaml
  • charts/fleet/values.yaml
  • server/fleet/openframe.go
  • server/fleet/openframe_test.go
  • server/service/openframe_middleware.go
  • server/service/openframe_middleware_test.go
🚧 Files skipped from review as they are similar to previous changes (6)
  • charts/fleet/templates/configmap.yaml
  • server/fleet/openframe.go
  • charts/fleet/templates/deployment.yaml
  • server/service/openframe_middleware_test.go
  • server/service/openframe_middleware.go
  • server/fleet/openframe_test.go

Walkthrough

Adds OpenFrame MySQL multitenancy configuration and startup handling, shared-mode request tenant pinning, serialized migrations, tenant-scoped schema uniqueness, and datastore fencing. App configuration, enrollment secrets, labels, hosts, policies, queries, targets, and teams now apply tenant boundaries. New unit and MySQL integration tests cover configuration, migrations, cache isolation, request middleware, and cross-tenant access behavior.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 61.96% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: OpenFrame shared-database multi-tenancy using Fleet teams as tenants.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/mysql-multitenancy

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
server/datastore/mysql/app_configs.go (1)

42-80: 🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Keep app_config_json out of the tenant keyspace

app_config_json still has a singleton row at id = 1, and teams.id also starts at 1. That means tenant 1 will read/write the legacy global config row, so config can bleed between the shared singleton and the first tenant. Add a migration or separate keyspace before switching pinned reads/writes.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@server/datastore/mysql/app_configs.go` around lines 42 - 80, Prevent pinned
app-config reads from using the legacy singleton keyspace: update appConfigDB
and the corresponding app-config write path to store tenant configs under a
separate, migrated keyspace rather than teams.id values. Add the required
migration and ensure tenant 1 cannot resolve the legacy app_config_json row at
id 1; preserve unpinned access to the singleton configuration.
🧹 Nitpick comments (8)
server/datastore/mysql/openframe.go (1)

21-120: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Duplicate per-table tenant-scoping boilerplate.

filterHostIDsByTeam/filterPolicyIDsByTeam/filterQueryIDsByTeam and openframeScopePolicyHosts/openframeScopeQueryHosts repeat the same shape, differing only by table name and error text. Consider a single generic helper parameterized by table name to reduce duplication as more entities get tenant-scoped.

♻️ Example consolidation
func filterIDsByTeam(ctx context.Context, q sqlx.QueryerContext, table string, ids []uint, teamID uint) ([]uint, error) {
	if len(ids) == 0 {
		return ids, nil
	}
	// table must always be a trusted literal supplied by the caller, never user input.
	stmt, args, err := sqlx.In(fmt.Sprintf(`SELECT id FROM %s WHERE id IN (?) AND team_id = ?`, table), ids, teamID)
	if err != nil {
		return nil, ctxerr.Wrap(ctx, err, "building team-scoped "+table+" id filter")
	}
	var owned []uint
	if err := sqlx.SelectContext(ctx, q, &owned, stmt, args...); err != nil {
		return nil, ctxerr.Wrap(ctx, err, "filtering "+table+" ids by team")
	}
	return owned, nil
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@server/datastore/mysql/openframe.go` around lines 21 - 120, Consolidate
filterHostIDsByTeam, filterPolicyIDsByTeam, and filterQueryIDsByTeam into one
trusted-table helper parameterized by table name, preserving empty-input
behavior and contextual error wrapping. Update openframeScopePolicyHosts and
openframeScopeQueryHosts to use the shared helper while retaining their distinct
parent-team verification and not-found behavior.
server/datastore/mysql/migrations/openframe/20260626000001_ScopeHostIdentityUniqueToTeam.go (1)

54-96: 🧹 Nitpick | 🔵 Trivial

LGTM!

For production rollout on a large hosts table, worth confirming the ADD COLUMN ... VIRTUAL + ADD UNIQUE KEY steps run with an online (ALGORITHM=INPLACE, LOCK=NONE) DDL plan on the actual MySQL version in use, since a fallback to a table copy/lock would extend the migration well beyond the 15-minute lock wait already budgeted in prepare.go.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@server/datastore/mysql/migrations/openframe/20260626000001_ScopeHostIdentityUniqueToTeam.go`
around lines 54 - 96, Update the migration’s generated-column and unique-index
ALTER TABLE statements in Up_20260626000001 to explicitly request the supported
online DDL plan using ALGORITHM=INPLACE and LOCK=NONE, and verify the syntax
against the production MySQL version. Preserve the existing idempotency checks
and error handling for both ADD operations.
server/service/endpoint_middleware.go (2)

111-116: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Sentinel comments omit a documentation link.

As per coding guidelines, `**/*.go,yaml,tpl}`: Wrap every OpenFrame fork edit within a shared upstream file in `OPENFRAME(<slug>)` sentinel comments that explain the change and link to its documentation. These 4 blocks explain the change but don't link to any doc. Same gap recurs in server/service/orbit.go, server/service/osquery.go, and server/datastore/mysql/labels.go — see consolidated comment.

Also applies to: 159-164, 206-211, 258-263

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@server/service/endpoint_middleware.go` around lines 111 - 116, Update every
OPENFRAME(mysql-multitenancy) sentinel block in endpoint_middleware.go and the
corresponding blocks in orbit.go, osquery.go, and labels.go to include the
required documentation link alongside the existing change explanation. Apply the
same link consistently to all referenced blocks without changing their
implementation logic.

Source: Coding guidelines


111-116: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

OPENFRAME sentinel comments across this PR don't link to documentation. As per coding guidelines, `**/*.go,yaml,tpl}`: Wrap every OpenFrame fork edit within a shared upstream file in `OPENFRAME(<slug>)` sentinel comments that explain the change and link to its documentation. Every sentinel added in this cohort explains the change inline but has no link — one root cause repeated at each site below.

  • server/service/endpoint_middleware.go#L111-L116: add a documentation link to this sentinel (and the matching ones at 159-164, 206-211, 258-263).
  • server/service/orbit.go#L191-L200: add a documentation link to this sentinel.
  • server/service/osquery.go#L115-L124: add a documentation link to this sentinel.
  • server/datastore/mysql/labels.go#L630-L638: add a documentation link to this sentinel (and the matching one at 889-934).
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@server/service/endpoint_middleware.go` around lines 111 - 116, Update every
listed OPENFRAME sentinel to include the canonical documentation link while
preserving its inline explanation: server/service/endpoint_middleware.go ranges
111-116, 159-164, 206-211, and 258-263; server/service/orbit.go range 191-200;
server/service/osquery.go range 115-124; and server/datastore/mysql/labels.go
ranges 630-638 and 889-934. Keep the surrounding implementations, including
openframePinHostTeam, unchanged.

Source: Coding guidelines

server/datastore/mysql/labels.go (2)

921-934: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

WHERE/AND choice relies on an implicit invariant, not the query's actual shape.

whereOrAnd is picked purely from len(initialParams) > 0, assuming a query with initial params always already has a WHERE clause and one without never does. This holds for all current call sites but isn't guaranteed by the function's contract — a future caller with a param-less pre-existing WHERE (or a params-bearing query with no WHERE) would get malformed SQL. Consider deriving this from whether query actually contains WHERE, or making it an explicit parameter.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@server/datastore/mysql/labels.go` around lines 921 - 934, Update the
query-building logic around the WHERE/AND selection to inspect the SQL shape of
query rather than infer clause presence from len(initialParams). Ensure existing
WHERE clauses append conditions with AND, queries without WHERE clauses add
WHERE, and preserve the current parameter expansion behavior.

895-910: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

gocritic: rewrite if/else-if/else to a switch.

Static analysis flags this chain (inside the filter.TeamID != nil case) as an ifElseChain.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@server/datastore/mysql/labels.go` around lines 895 - 910, Rewrite the nested
if/else-if/else chain within the filter.TeamID != nil case of the surrounding
switch into a switch statement, preserving the existing behavior for TeamID 0,
inaccessible teams, and accessible team filtering.

Source: Linters/SAST tools

server/service/orbit.go (1)

191-200: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Enroll-secret tenant-pinning logic is duplicated between two service files. Both blocks implement the same rule (fail closed if secret.TeamID is nil/0, else pin ctx via fleet.NewOpenframeTeamContext) independently, risking future drift.

  • server/service/orbit.go#L191-L200: extract this block (and the one in osquery.go) into a shared helper, e.g. openframePinEnrollSecretTeam(ctx, secret) alongside openframePinHostTeam in openframe_middleware.go, then call it here.
  • server/service/osquery.go#L115-L124: call the same shared helper here instead of duplicating the check.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@server/service/orbit.go` around lines 191 - 200, Extract the duplicated
enroll-secret tenant-pinning logic into a shared openframePinEnrollSecretTeam
helper alongside openframePinHostTeam in openframe_middleware.go, preserving the
nil/zero TeamID failure and context pinning behavior. Update
server/service/orbit.go lines 191-200 and server/service/osquery.go lines
115-124 to call the helper instead of implementing the check inline.
server/datastore/mysql/hosts.go (1)

664-665: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

OPENFRAME sentinels omit the required documentation link.

The mysql-multitenancy sentinel blocks throughout this PR explain the change but don't link to its documentation, unlike the host-assignments blocks in this same file (e.g. Lines 536, 1777 reference openframe/docs/architecture-host-assignments.md). Add the corresponding multitenancy doc reference to each OPENFRAME(mysql-multitenancy) block. Flagging once here as this applies to every mysql-multitenancy sentinel across the changed files.

As per coding guidelines: "Wrap every OpenFrame fork edit within a shared upstream file in OPENFRAME(<slug>) sentinel comments that explain the change and link to its documentation."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@server/datastore/mysql/hosts.go` around lines 664 - 665, Update every
OPENFRAME(mysql-multitenancy) sentinel block across the changed files, including
the block near the host deletion logic, to include a reference link to the
multitenancy documentation. Preserve each block’s existing explanation and match
the documentation-link style used by the OPENFRAME(host-assignments) sentinels.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@server/fleet/openframe.go`:
- Around line 156-181: Extend ValidateOpenframeMultitenancy and its pure helper
openframeMultitenancyConfigError to read and validate
FLEET_OPENFRAME_TENANT_UUID when multitenancy is enabled. Reject any non-empty
malformed UUID before EnsureOpenframeTeamID can create a team, using the
existing UUID validation/parsing convention and an error that identifies the
invalid variable; preserve valid pinned, valid team-ID, and unpinned shared-mode
behavior.

In `@server/service/openframe_middleware.go`:
- Around line 79-88: Update openframeTenantExemptPath to remove the broad
"/mdm/" substring exemption and replace it with checks for only the intended
device/agent MDM route templates. Preserve exemptions for the other existing
markers, while ensuring user-authenticated control-plane MDM APIs such as apple
commands, enrollment profiles, and MDM commands still require X-Tenant-Id.

---

Outside diff comments:
In `@server/datastore/mysql/app_configs.go`:
- Around line 42-80: Prevent pinned app-config reads from using the legacy
singleton keyspace: update appConfigDB and the corresponding app-config write
path to store tenant configs under a separate, migrated keyspace rather than
teams.id values. Add the required migration and ensure tenant 1 cannot resolve
the legacy app_config_json row at id 1; preserve unpinned access to the
singleton configuration.

---

Nitpick comments:
In `@server/datastore/mysql/hosts.go`:
- Around line 664-665: Update every OPENFRAME(mysql-multitenancy) sentinel block
across the changed files, including the block near the host deletion logic, to
include a reference link to the multitenancy documentation. Preserve each
block’s existing explanation and match the documentation-link style used by the
OPENFRAME(host-assignments) sentinels.

In `@server/datastore/mysql/labels.go`:
- Around line 921-934: Update the query-building logic around the WHERE/AND
selection to inspect the SQL shape of query rather than infer clause presence
from len(initialParams). Ensure existing WHERE clauses append conditions with
AND, queries without WHERE clauses add WHERE, and preserve the current parameter
expansion behavior.
- Around line 895-910: Rewrite the nested if/else-if/else chain within the
filter.TeamID != nil case of the surrounding switch into a switch statement,
preserving the existing behavior for TeamID 0, inaccessible teams, and
accessible team filtering.

In
`@server/datastore/mysql/migrations/openframe/20260626000001_ScopeHostIdentityUniqueToTeam.go`:
- Around line 54-96: Update the migration’s generated-column and unique-index
ALTER TABLE statements in Up_20260626000001 to explicitly request the supported
online DDL plan using ALGORITHM=INPLACE and LOCK=NONE, and verify the syntax
against the production MySQL version. Preserve the existing idempotency checks
and error handling for both ADD operations.

In `@server/datastore/mysql/openframe.go`:
- Around line 21-120: Consolidate filterHostIDsByTeam, filterPolicyIDsByTeam,
and filterQueryIDsByTeam into one trusted-table helper parameterized by table
name, preserving empty-input behavior and contextual error wrapping. Update
openframeScopePolicyHosts and openframeScopeQueryHosts to use the shared helper
while retaining their distinct parent-team verification and not-found behavior.

In `@server/service/endpoint_middleware.go`:
- Around line 111-116: Update every OPENFRAME(mysql-multitenancy) sentinel block
in endpoint_middleware.go and the corresponding blocks in orbit.go, osquery.go,
and labels.go to include the required documentation link alongside the existing
change explanation. Apply the same link consistently to all referenced blocks
without changing their implementation logic.
- Around line 111-116: Update every listed OPENFRAME sentinel to include the
canonical documentation link while preserving its inline explanation:
server/service/endpoint_middleware.go ranges 111-116, 159-164, 206-211, and
258-263; server/service/orbit.go range 191-200; server/service/osquery.go range
115-124; and server/datastore/mysql/labels.go ranges 630-638 and 889-934. Keep
the surrounding implementations, including openframePinHostTeam, unchanged.

In `@server/service/orbit.go`:
- Around line 191-200: Extract the duplicated enroll-secret tenant-pinning logic
into a shared openframePinEnrollSecretTeam helper alongside openframePinHostTeam
in openframe_middleware.go, preserving the nil/zero TeamID failure and context
pinning behavior. Update server/service/orbit.go lines 191-200 and
server/service/osquery.go lines 115-124 to call the helper instead of
implementing the check inline.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 91309fca-deab-4698-ba1a-954b88898427

📥 Commits

Reviewing files that changed from the base of the PR and between 8b5cadc and 4d438d0.

⛔ Files ignored due to path filters (3)
  • CLAUDE.md is excluded by !**/*.md
  • openframe/docs/helm-chart.md is excluded by !**/*.md
  • openframe/docs/mysql-multitenancy-feature.md is excluded by !**/*.md
📒 Files selected for processing (41)
  • charts/fleet/templates/deployment.yaml
  • charts/fleet/templates/job-migration.yaml
  • charts/fleet/values.yaml
  • cmd/fleet/prepare.go
  • cmd/fleet/serve.go
  • openframe/scripts/verify.sh
  • server/datastore/cached_mysql/cached_mysql.go
  • server/datastore/cached_mysql/cached_mysql_test.go
  • server/datastore/mysql/app_configs.go
  • server/datastore/mysql/app_configs_openframe_test.go
  • server/datastore/mysql/enroll_secrets_openframe_test.go
  • server/datastore/mysql/host_assignments_openframe_test.go
  • server/datastore/mysql/hosts.go
  • server/datastore/mysql/hosts_openframe_test.go
  • server/datastore/mysql/labels.go
  • server/datastore/mysql/labels_openframe_test.go
  • server/datastore/mysql/migrations/openframe/20260620000001_ScopeLabelUniqueNameToTeam.go
  • server/datastore/mysql/migrations/openframe/20260626000001_ScopeHostIdentityUniqueToTeam.go
  • server/datastore/mysql/migrations/openframe/20260629000001_AddTeamsOpenframeTenantUUID.go
  • server/datastore/mysql/migrations/openframe/migration.go
  • server/datastore/mysql/migrations_openframe_test.go
  • server/datastore/mysql/openframe.go
  • server/datastore/mysql/policies.go
  • server/datastore/mysql/policies_queries_openframe_test.go
  • server/datastore/mysql/queries.go
  • server/datastore/mysql/targets.go
  • server/datastore/mysql/targets_openframe_test.go
  • server/datastore/mysql/teams.go
  • server/datastore/mysql/teams_openframe_test.go
  • server/datastore/mysql/teams_transfer_openframe_test.go
  • server/fleet/labels.go
  • server/fleet/openframe.go
  • server/fleet/openframe_test.go
  • server/service/endpoint_middleware.go
  • server/service/global_policies.go
  • server/service/global_policies_openframe_test.go
  • server/service/openframe_middleware.go
  • server/service/openframe_middleware_test.go
  • server/service/orbit.go
  • server/service/osquery.go
  • server/service/osquery_header_auth.go

Comment thread server/fleet/openframe.go
Comment thread server/service/openframe_middleware.go Outdated
Comment thread charts/fleet/templates/deployment.yaml
…ionally

Address review: remove the if/else-if in deployment.yaml that inlined the tenant
UUID as an env value. The Deployment now always reads FLEET_OPENFRAME_TENANT_UUID
via configMapKeyRef — either the operator's own ConfigMap (existingConfigMap) or
the chart-managed "fleet-openframe-tenant" that configmap.yaml creates when no
existingConfigMap is supplied (same pattern as the DB/cache config). Empty in
shared mode / flag-off ⇒ Fleet treats "" as unset.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@charts/fleet/templates/configmap.yaml`:
- Around line 27-37: Update the opening OPENFRAME(mysql-multitenancy) sentinel
in the ConfigMap template to include the repository’s actual documentation path,
while preserving its existing slug and rationale metadata.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 316df16f-d0ce-43ba-9195-82766a602f3b

📥 Commits

Reviewing files that changed from the base of the PR and between 4d438d0 and ef57781.

⛔ Files ignored due to path filters (1)
  • openframe/docs/helm-chart.md is excluded by !**/*.md
📒 Files selected for processing (3)
  • charts/fleet/templates/configmap.yaml
  • charts/fleet/templates/deployment.yaml
  • charts/fleet/values.yaml
🚧 Files skipped from review as they are similar to previous changes (1)
  • charts/fleet/values.yaml

Comment thread charts/fleet/templates/configmap.yaml Outdated
- Validate FLEET_OPENFRAME_TENANT_UUID at startup: pinned mode now rejects a
  malformed tenant UUID (uuid.Parse) instead of letting EnsureOpenframeTeamID
  create a garbage "openframe-<junk>" team and seed a secret. Mirrors the
  shared-mode X-Tenant-Id check; keeps valid pinned / team-id / shared behavior.
- Narrow the tenant-middleware MDM exemption from the broad "/mdm/" to
  "/api/mdm/" (device enroll/installer/Microsoft), so user-authenticated admin
  MDM APIs under /api/{v}/fleet/mdm/ (commands, enrollment_profile) still
  require X-Tenant-Id in shared mode. Residual device-facing DEP paths that
  collide by HTTP method are documented (MDM unused in OpenFrame).
- Add the openframe/docs path to the OPENFRAME sentinels in the Helm
  configmap.yaml/deployment.yaml blocks (fork convention).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Comment thread charts/fleet/values.yaml Outdated
Comment thread charts/fleet/templates/deployment.yaml Outdated
Comment thread charts/fleet/templates/configmap.yaml Outdated
@oleksandrd-flamingo
oleksandrd-flamingo merged commit 3c43e07 into main Jul 22, 2026
6 checks passed
@oleksandrd-flamingo
oleksandrd-flamingo deleted the feature/mysql-multitenancy branch July 22, 2026 10:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants