Skip to content

RHINENG-30223: scope Satellite hosts and separate ID sources - #2362

Open
dominikvagner wants to merge 3 commits into
RedHatInsights:masterfrom
dominikvagner:owner-id-consumers
Open

dominikvagner wants to merge 3 commits into
RedHatInsights:masterfrom
dominikvagner:owner-id-consumers

Conversation

@dominikvagner

@dominikvagner dominikvagner commented Oct 14, 2026 •

Copy link
Copy Markdown
Contributor

Summary

This PR makes follow-up changes to #2356.
Switches the code to use subscription manager IDs and owner IDs correctly.
And changes the auth to work correctly for satellite systems using cert auth.
Also makes one small tweak to the local DB setup to not fail on existing roles.
This PR will need to be merged after the owner ID backfill job will be finished
and the prod release for this will also need to be done in order.

RHINENG-30223: separate host identity sources

Owner IDs and subscription-manager IDs have different purposes.
Treating them as interchangeable can resolve the wrong host or send
the wrong consumer identity to Candlepin. Read each ID from its matching
Inventory field. Use owner IDs for cert-auth lookup and
subscription-manager IDs for Candlepin.

RHINENG-30223: scope Satellite host requests

Satellite certificates can represent multiple hosts sharing an owner.
Owner-only lookup cannot distinguish which host a request targets.

Validate optional forwarded subscription-manager IDs for Satellite
certificates and match them alongside the owner and account. Reject
malformed IDs with 403 while preserving owner-only lookup when the
header is absent. Other certificate types ignore the header.

Summary by Sourcery

Correct host identity handling and scope Satellite-authenticated requests to the intended host.

New Features:

  • Scope Satellite certificate requests to a forwarded subscription-manager ID when provided, while retaining owner-only lookup otherwise.

Bug Fixes:

  • Use owner IDs for system certificate lookup and subscription-manager IDs for Candlepin operations and inventory persistence.
  • Reject malformed forwarded Satellite identity headers with a 403 response and keep other certificate types unaffected.

Enhancements:

  • Track owner and subscription-manager identities independently across host events, inventory updates, template operations, and test data.

Documentation:

  • Document the optional forwarded Satellite subscription-manager identity header and its possible 403 response.

Tests:

  • Add coverage for independent identity persistence, Satellite host scoping, authentication behavior, and Candlepin requests.

Chores:

  • Make local database role creation idempotent when the role already exists.

The cyndi role creation is often failing in the local dev setup. This
changes the script to only create it, if it doesn't exist, making the
local dev setup less bothersome.
Owner IDs and subscription-manager IDs have different purposes.
Treating them as interchangeable can resolve the wrong host or send
the wrong consumer identity to Candlepin. Read each ID from its matching
Inventory field. Use owner IDs for cert-auth lookup and
subscription-manager IDs for Candlepin.
Satellite certificates can represent multiple hosts sharing an owner.
Owner-only lookup cannot distinguish which host a request targets.

Validate optional forwarded subscription-manager IDs for Satellite
certificates and match them alongside the owner and account. Reject
malformed IDs with 403 while preserving owner-only lookup when the
header is absent. Other certificate types ignore the header.
@dominikvagner
dominikvagner requested a review from a team as a code owner October 14, 2026 12:11
@sourcery-ai

sourcery-ai Bot commented Oct 14, 2026 •

Copy link
Copy Markdown

Reviewer's Guide

The PR separates owner IDs from subscription-manager IDs across inventory ingestion, persistence, Candlepin calls, and template lookups, while adding Satellite-specific forwarded-identity scoping and validation; it also makes local database role creation safe to rerun.

File-Level Changes

Change Details Files
Separate owner and subscription-manager identities throughout host ingestion and template/Candlepin operations.
  • Add a top-level subscription-manager ID to host events and persist it independently from the inventory owner ID.
  • Use subscription-manager IDs for Candlepin environment assignment and repository-template environment lookup.
  • Update fixtures and tests to cover independent, nullable identity values and prevent regressions.
listener/upload.go
listener/upload_test.go
listener/common_test.go
dev/test_data.sql
manager/controllers/template_systems_update.go
manager/controllers/template_systems_update_test.go
Scope Satellite certificate requests to a specific host when a forwarded subscription-manager ID is provided.
  • Parse and validate the optional forwarded identity header only for Satellite certificates.
  • Return 403 for malformed forwarded IDs while preserving owner/account-only lookup when absent.
  • Match owner, account, and subscription-manager ID when the forwarded value is present; document the header behavior in the API spec.
manager/middlewares/authentication.go
manager/middlewares/authentication_test.go
manager/controllers/template_subscribed_systems_update.go
manager/controllers/template_subscribed_systems_update_test.go
base/utils/gin.go
docs/v3/openapi.json
Make local database setup idempotent for the development user.
  • Create the cyndi database role inside a duplicate-object-safe PL/pgSQL block.
  • Emit a notice instead of failing when the role already exists.
dev/create_inventory_hosts.sql

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@sourcery-ai sourcery-ai 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.

Hey - I've found 5 issues

Prompt for AI Agents
Please address the comments from this code review:

## Individual Comments

### Comment 1
<location path="manager/controllers/template_subscribed_systems_update.go" line_range="73" />
<code_context>

+func systemIdentityQuery(c *gin.Context, tx *gorm.DB) *gorm.DB {
+	query := tx.Table("system_inventory").
+		Where("owner_id = ? AND rh_account_id = ?", c.GetString(utils.KeySystem), c.GetInt(utils.KeyAccount))
+	if forwardedID := c.GetString(utils.KeySubscriptionManagerID); forwardedID != "" {
+		query = query.Where("subscription_manager_id = ?", forwardedID)
</code_context>
<issue_to_address>
**Template changes target wrong host**

When a Satellite owner matches multiple hosts in the account and the request omits `X-Forwarded-Identity`, `systemIdentityQuery` omits the subscription-manager filter, and `getSubscribedSystem` uses `LIMIT 1` without ordering or checking for multiple matches. The handler can assign the template to an arbitrary host.

Require a forwarded identity, or reject requests when the owner-and-account query matches multiple hosts.

Also at `manager/controllers/template_subscribed_systems_update.go:74-75`.
</issue_to_address>

### Comment 2
<location path="listener/upload.go" line_range="400" />
<code_context>
 			OSMinor:                          &host.SystemProfile.OperatingSystem.Minor,
 			RhsmVersion:                      utils.EmptyToNil(&host.SystemProfile.Rhsm.Version),
-			SubscriptionManagerID:            host.SystemProfile.OwnerID,
+			SubscriptionManagerID:            host.SubscriptionManagerID,
 			OwnerID:                          host.SystemProfile.OwnerID,
 			SapWorkload:                      host.SystemProfile.Workloads.Sap.SapSystem,
</code_context>
<issue_to_address>
**Older upload overwrites host identities**

When an older or redelivered host event is processed after a newer event for the same inventory ID, `updateSystemPlatform` writes the event's subscription-manager ID on every upsert, but its row lock only serializes writes and does not reject stale events. An older event processed last replaces the current ID, so later host lookup and Candlepin requests use stale identity data.

Reject stale identity updates using a reliable event version or timestamp before applying the upsert.
</issue_to_address>

### Comment 3
<location path="listener/upload.go" line_range="400" />
<code_context>
 			OSMinor:                          &host.SystemProfile.OperatingSystem.Minor,
 			RhsmVersion:                      utils.EmptyToNil(&host.SystemProfile.Rhsm.Version),
-			SubscriptionManagerID:            host.SystemProfile.OwnerID,
+			SubscriptionManagerID:            host.SubscriptionManagerID,
 			OwnerID:                          host.SystemProfile.OwnerID,
 			SapWorkload:                      host.SystemProfile.Workloads.Sap.SapSystem,
</code_context>
<issue_to_address>
**Existing hosts retain owner IDs**

When a host already in the database has not been processed by the new listener since the ID source changed, the backfill job copies the old `subscription_manager_id` into `owner_id` but leaves the old value in `subscription_manager_id`; the new listener only writes the actual subscription-manager ID when it processes a host event. Until then, forwarded-ID lookups return 404 and Candlepin requests use the owner ID as the consumer ID.

Backfill or replay events to populate actual subscription-manager IDs before enabling the new lookup and Candlepin behavior, or make the rollout handle legacy values until they are refreshed.

Also at `manager/controllers/template_subscribed_systems_update.go:75`.
</issue_to_address>

### Comment 4
<location path="dev/create_inventory_hosts.sql" line_range="45" />
<code_context>
+  CREATE USER cyndi;
+  EXCEPTION WHEN DUPLICATE_OBJECT THEN
+    RAISE NOTICE 'cyndi already exists';
+END
+$$;
</code_context>
<issue_to_address>
**Local setup script fails**

When the local database setup executes this script, the `DO` block's `END` lacks a terminating semicolon, so PostgreSQL raises a PL/pgSQL syntax error and the script cannot create or skip the `cyndi` role.

Terminate `END` with a semicolon.
</issue_to_address>

### Comment 5
<location path="manager/controllers/template_subscribed_systems_update.go" line_range="73" />
<code_context>

+func systemIdentityQuery(c *gin.Context, tx *gorm.DB) *gorm.DB {
+	query := tx.Table("system_inventory").
+		Where("owner_id = ? AND rh_account_id = ?", c.GetString(utils.KeySystem), c.GetInt(utils.KeyAccount))
+	if forwardedID := c.GetString(utils.KeySubscriptionManagerID); forwardedID != "" {
+		query = query.Where("subscription_manager_id = ?", forwardedID)
</code_context>
<issue_to_address>
**Satellite lookups scan host partitions**

When a Satellite request reaches an account with a large host partition, the owner-and-account filter has no matching index, so each subscribed-system request scans the account’s `system_inventory` partition; this makes lookup latency grow with that account’s host count.

Add an index supporting the owner/account lookup and the optional subscription-manager ID filter, with a migration for existing databases.
</issue_to_address>

Sourcery assessment

Needs a human reviewer. 5 findings to address first, and the system-certificate identity and optional forwarded UUID now determine which inventory host is resolved for template subscription updates. If that matching selects the wrong host, the template association persists; reverting stops future lookups but does not undo associations already written.

Blocking findings: manager/controllers/template_subscribed_systems_update.go:73, listener/upload.go:400, listener/upload.go:400, dev/create_inventory_hosts.sql:45, manager/controllers/template_subscribed_systems_update.go:73


Sourcery is free for open source - if you like our reviews please consider sharing them ✨

Comment thread manager/controllers/template_subscribed_systems_update.go
Comment thread listener/upload.go
Comment thread listener/upload.go
CREATE USER cyndi;
EXCEPTION WHEN DUPLICATE_OBJECT THEN
RAISE NOTICE 'cyndi already exists';
END

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Medium · Local setup script fails

When the local database setup executes this script, the DO block's END lacks a terminating semicolon, so PostgreSQL raises a PL/pgSQL syntax error and the script cannot create or skip the cyndi role.

Terminate END with a semicolon.

Prompt for AI agents
In `dev/create_inventory_hosts.sql` at line 45:

**Local setup script fails**

When the local database setup executes this script, the `DO` block's `END` lacks a terminating semicolon, so PostgreSQL raises a PL/pgSQL syntax error and the script cannot create or skip the `cyndi` role.

Terminate `END` with a semicolon.


func systemIdentityQuery(c *gin.Context, tx *gorm.DB) *gorm.DB {
query := tx.Table("system_inventory").
Where("owner_id = ? AND rh_account_id = ?", c.GetString(utils.KeySystem), c.GetInt(utils.KeyAccount))

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Medium · Satellite lookups scan host partitions

When a Satellite request reaches an account with a large host partition, the owner-and-account filter has no matching index, so each subscribed-system request scans the account’s system_inventory partition; this makes lookup latency grow with that account’s host count.

Add an index supporting the owner/account lookup and the optional subscription-manager ID filter, with a migration for existing databases.

Prompt for AI agents
In `manager/controllers/template_subscribed_systems_update.go` at line 73:

**Satellite lookups scan host partitions**

When a Satellite request reaches an account with a large host partition, the owner-and-account filter has no matching index, so each subscribed-system request scans the account’s `system_inventory` partition; this makes lookup latency grow with that account’s host count.

Add an index supporting the owner/account lookup and the optional subscription-manager ID filter, with a migration for existing databases.

@codecov-commenter

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 97.14286% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 58.98%. Comparing base (6710aae) to head (daf52ac).

Files with missing lines Patch % Lines
manager/middlewares/authentication.go 95.65% 0 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master    #2362      +/-   ##
==========================================
+ Coverage   58.89%   58.98%   +0.09%     
==========================================
  Files         151      151              
  Lines        9706     9730      +24     
==========================================
+ Hits         5716     5739      +23     
- Misses       3386     3387       +1     
  Partials      604      604              
Flag Coverage Δ
unittests 58.98% <97.14%> (+0.09%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

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.

3 participants