Discovery of necessary permissions when creating custom roles

Creating a custom role is largely a process of trial and error. You need to make changes to the filter sets, run the desired command, and note any potential errors from stdout and the Foreman logs and try to piece together the missing permissions required.

Is there a simplified way to discover what permissions are required for a given operation? For example, I’m trying to create a role with the sole purpose of managing host registrations and content view environments. In order even see available environments (subman environments --list) the following permissions are required:

  • view_lifecycle_environments
  • view_content_views

However, I cannot figure out what permissions are necessary to get this to the next step of being able to set the environment(s) to use, e.g. subman environments --set, and continuously get hit with:

Access denied (HTTP error code 403: Forbidden)

So far I’ve tried throwing paint at the wall with various permissions such as hosts, activation keys, facts, subscriptions, repositories, etc but have not uncovered the magic combination to achieve my goal. Even putting Foreman in debug mode provides no insight in the logs as the permission the operation fails on isn’t mentioned.

Foreman: 3.16.0
Katello: 4.18.1

1 Like

Hi,

I suggest the following to discover the needed permissions:

  1. Enable debug logging – set Foreman/Katello to DEBUG (via settings.yml) and re-run the CLI. Check log files (production.log, katello.log, or hammer.log) for entries like:
Permission check failed for user=XYZ action=… resource=…

This usually reveals the missing permission.

  1. Clone a working role - copy a role like Register hosts and remove permissions one by one until --set fails. This identifies the exact permission required.

You will likely need some of these permissions:

  • view_lifecycle_environments (you already have)

  • view_content_views (you already have)

  • edit_lifecycle_environments

  • edit_content_view(needed to change a host’s environment)

  • view_content_hosts (to list/see hosts)

  • view_activation_keys / edit_activation_keys (if using activation keys)

Hi @nofaralfasi,

I’ll take another look at the logs, but I wasn’t seeing anything the first time after enabling debug mode. It may not have taken, though.

As for roles, I configured a user account with all of the default roles provided in Foreman (my test version being 3.15/4.17) for an initial test. Rejections continued until I flipped on the Admin privilege toggle to the account.

Part of my current debugging path has been to make a new role that adds every permission from every resource type, but as you might imagine it’s slow going :sweat_smile: Hopefully the logs provide some more detailed output this time around.

Cheers

Correction: I just went through my 3.13/4.15 system and created a role that has every resource for all resource types enabled and set to unlimited (via WebUI). For this instance that’s 58 resource types. Not even that allowed the user to change environments via subscription-manager on the target host. The production 3.16 instance has a marginal delta of resource types, but looking at the list I don’t imagine any of them behaving significantly differently here.

As for the logs, I put Foreman, its proxy, Katello, and Candlepin into debug mode, along with enabling the component loggers (permissions, etc). I’ve gone through each line with a fine toothed comb — nothing. This is the final snippet of the log where the failure occurs (everything before that passed successfully but would take a while to clean up for sharing so omitting here):

2025-11-12T14:14:03 [I|app|77bfe51b] Started PUT "/rhsm/consumers/c09e1796-1fe2-4e79-847d-a0e4778adcbe" for [IPV4 ADDRESS] at 2025-11-12 14:14:03 -0800
2025-11-12T14:14:03 [I|app|77bfe51b] Processing by Katello::Api::Rhsm::CandlepinProxiesController#facts as JSON
2025-11-12T14:14:03 [I|app|77bfe51b]   Parameters: {"environments"=>[{"id"=>"618befe05a7cc6d443accd46261243fb"}], "id"=>"c09e1796-1fe2-4e79-847d-a0e4778adcbe"}
2025-11-12T14:14:03 [D|app|77bfe51b] Authenticated user registrar against INTERNAL authentication source
2025-11-12T14:14:03 [D|per|77bfe51b] Current user set to foreman_admin (admin)
2025-11-12T14:14:03 [D|app|77bfe51b] Post-login processing for registrar
2025-11-12T14:14:03 [D|per|77bfe51b] Current user set to foreman_admin (admin)
2025-11-12T14:14:03 [I|per|77bfe51b] Current user set to registrar (regular)
2025-11-12T14:14:03 [I|kat|77bfe51b] Authorized user registrar(registrar)
2025-11-12T14:14:03 [D|app|77bfe51b] Post-login processing for registrar
2025-11-12T14:14:03 [D|per|77bfe51b] Current user set to foreman_admin (admin)
2025-11-12T14:14:03 [I|per|77bfe51b] Current user set to registrar (regular)
2025-11-12T14:14:03 [I|per|77bfe51b] Current user set to registrar (regular)
2025-11-12T14:14:03 [I|per|77bfe51b] Current user set to registrar (regular)
2025-11-12T14:14:03 [D|tax|77bfe51b] Current location set to none
2025-11-12T14:14:03 [D|tax|77bfe51b] Current organization set to none
2025-11-12T14:14:03 [E|kat|77bfe51b] *** ERROR: Access denied (403) ***
2025-11-12T14:14:03 [E|kat|77bfe51b] REQUEST URL: /rhsm/consumers/c09e1796-1fe2-4e79-847d-a0e4778adcbe
2025-11-12T14:14:03 [E|kat|77bfe51b] Katello::HttpErrors::Forbidden: Access denied
 77bfe51b | /usr/share/gems/gems/katello-4.15.0/app/controllers/katello/api/rhsm/candlepin_proxies_controller.rb:304:in `deny_access'
 77bfe51b | /usr/share/foreman/app/controllers/api/base_controller.rb:213:in `authorize'
 77bfe51b | /usr/share/gems/gems/katello-4.15.0/app/controllers/katello/api/rhsm/candlepin_proxies_controller.rb:474:in `authorize_client_or_user'
 77bfe51b | /usr/share/gems/gems/activesupport-6.1.7.10/lib/active_support/callbacks.rb:427:in `block in make_lambda'
 77bfe51b | /usr/share/gems/gems/activesupport-6.1.7.10/lib/active_support/callbacks.rb:179:in `block (2 levels) in halting_and_conditional'
 77bfe51b | /usr/share/gems/gems/actionpack-6.1.7.10/lib/abstract_controller/callbacks.rb:34:in `block (2 levels) in <module:Callbacks>'
 77bfe51b | /usr/share/gems/gems/activesupport-6.1.7.10/lib/active_support/callbacks.rb:180:in `block in halting_and_conditional'
 77bfe51b | /usr/share/gems/gems/activesupport-6.1.7.10/lib/active_support/callbacks.rb:512:in `block in invoke_before'
 77bfe51b | /usr/share/gems/gems/activesupport-6.1.7.10/lib/active_support/callbacks.rb:512:in `each'
 77bfe51b | /usr/share/gems/gems/activesupport-6.1.7.10/lib/active_support/callbacks.rb:512:in `invoke_before'
 77bfe51b | /usr/share/gems/gems/activesupport-6.1.7.10/lib/active_support/callbacks.rb:115:in `block in run_callbacks'
 77bfe51b | /usr/share/gems/gems/katello-4.15.0/app/controllers/katello/api/rhsm/candlepin_proxies_controller.rb:37:in `repackage_message'
 77bfe51b | /usr/share/gems/gems/activesupport-6.1.7.10/lib/active_support/callbacks.rb:126:in `block in run_callbacks'
 77bfe51b | /usr/share/foreman/app/controllers/concerns/foreman/controller/timezone.rb:10:in `set_timezone'
 77bfe51b | /usr/share/gems/gems/activesupport-6.1.7.10/lib/active_support/callbacks.rb:126:in `block in run_callbacks'
 77bfe51b | /usr/share/foreman/app/models/concerns/foreman/thread_session.rb:32:in `clear_thread'
 77bfe51b | /usr/share/gems/gems/activesupport-6.1.7.10/lib/active_support/callbacks.rb:126:in `block in run_callbacks'
 77bfe51b | /usr/share/foreman/app/controllers/concerns/foreman/controller/topbar_sweeper.rb:12:in `set_topbar_sweeper_controller'
 77bfe51b | /usr/share/gems/gems/activesupport-6.1.7.10/lib/active_support/callbacks.rb:126:in `block in run_callbacks'
 77bfe51b | /usr/share/gems/gems/audited-5.7.0/lib/audited/sweeper.rb:16:in `around'
 77bfe51b | /usr/share/gems/gems/activesupport-6.1.7.10/lib/active_support/callbacks.rb:126:in `block in run_callbacks'
 77bfe51b | /usr/share/gems/gems/audited-5.7.0/lib/audited/sweeper.rb:16:in `around'
 77bfe51b | /usr/share/gems/gems/activesupport-6.1.7.10/lib/active_support/callbacks.rb:126:in `block in run_callbacks'
 77bfe51b | /usr/share/gems/gems/activesupport-6.1.7.10/lib/active_support/callbacks.rb:137:in `run_callbacks'
 77bfe51b | /usr/share/gems/gems/actionpack-6.1.7.10/lib/abstract_controller/callbacks.rb:41:in `process_action'
 77bfe51b | /usr/share/gems/gems/actionpack-6.1.7.10/lib/action_controller/metal/rescue.rb:22:in `process_action'
 77bfe51b | /usr/share/gems/gems/actionpack-6.1.7.10/lib/action_controller/metal/instrumentation.rb:34:in `block in process_action'
 77bfe51b | /usr/share/gems/gems/activesupport-6.1.7.10/lib/active_support/notifications.rb:203:in `block in instrument'
 77bfe51b | /usr/share/gems/gems/activesupport-6.1.7.10/lib/active_support/notifications/instrumenter.rb:24:in `instrument'
 77bfe51b | /usr/share/gems/gems/activesupport-6.1.7.10/lib/active_support/notifications.rb:203:in `instrument'
 77bfe51b | /usr/share/gems/gems/actionpack-6.1.7.10/lib/action_controller/metal/instrumentation.rb:33:in `process_action'
 77bfe51b | /usr/share/gems/gems/actionpack-6.1.7.10/lib/action_controller/metal/params_wrapper.rb:249:in `process_action'
 77bfe51b | /usr/share/gems/gems/activerecord-6.1.7.10/lib/active_record/railties/controller_runtime.rb:27:in `process_action'
 77bfe51b | /usr/share/gems/gems/katello-4.15.0/app/controllers/katello/concerns/api/api_controller.rb:46:in `process_action'
 77bfe51b | /usr/share/gems/gems/actionpack-6.1.7.10/lib/abstract_controller/base.rb:165:in `process'
 77bfe51b | /usr/share/gems/gems/actionview-6.1.7.10/lib/action_view/rendering.rb:39:in `process'
 77bfe51b | /usr/share/gems/gems/actionpack-6.1.7.10/lib/action_controller/metal.rb:190:in `dispatch'
 77bfe51b | /usr/share/gems/gems/actionpack-6.1.7.10/lib/action_controller/metal.rb:254:in `dispatch'
 77bfe51b | /usr/share/gems/gems/actionpack-6.1.7.10/lib/action_dispatch/routing/route_set.rb:50:in `dispatch'
 77bfe51b | /usr/share/gems/gems/actionpack-6.1.7.10/lib/action_dispatch/routing/route_set.rb:33:in `serve'
 77bfe51b | /usr/share/gems/gems/actionpack-6.1.7.10/lib/action_dispatch/journey/router.rb:50:in `block in serve'
 77bfe51b | /usr/share/gems/gems/actionpack-6.1.7.10/lib/action_dispatch/journey/router.rb:32:in `each'
 77bfe51b | /usr/share/gems/gems/actionpack-6.1.7.10/lib/action_dispatch/journey/router.rb:32:in `serve'
 77bfe51b | /usr/share/gems/gems/actionpack-6.1.7.10/lib/action_dispatch/routing/route_set.rb:842:in `call'
 77bfe51b | /usr/share/gems/gems/railties-6.1.7.10/lib/rails/engine.rb:539:in `call'
 77bfe51b | /usr/share/gems/gems/railties-6.1.7.10/lib/rails/railtie.rb:207:in `public_send'
 77bfe51b | /usr/share/gems/gems/railties-6.1.7.10/lib/rails/railtie.rb:207:in `method_missing'
 77bfe51b | /usr/share/gems/gems/actionpack-6.1.7.10/lib/action_dispatch/routing/mapper.rb:20:in `block in <class:Constraints>'
 77bfe51b | /usr/share/gems/gems/actionpack-6.1.7.10/lib/action_dispatch/routing/mapper.rb:49:in `serve'
 77bfe51b | /usr/share/gems/gems/actionpack-6.1.7.10/lib/action_dispatch/journey/router.rb:50:in `block in serve'
 77bfe51b | /usr/share/gems/gems/actionpack-6.1.7.10/lib/action_dispatch/journey/router.rb:32:in `each'
 77bfe51b | /usr/share/gems/gems/actionpack-6.1.7.10/lib/action_dispatch/journey/router.rb:32:in `serve'
 77bfe51b | /usr/share/gems/gems/actionpack-6.1.7.10/lib/action_dispatch/routing/route_set.rb:842:in `call'
 77bfe51b | /usr/share/gems/gems/katello-4.15.0/lib/katello/middleware/organization_created_enforcer.rb:18:in `call'
 77bfe51b | /usr/share/gems/gems/katello-4.15.0/lib/katello/middleware/event_daemon.rb:10:in `call'
 77bfe51b | /usr/share/gems/gems/actionpack-6.1.7.10/lib/action_dispatch/middleware/static.rb:24:in `call'
 77bfe51b | /usr/share/gems/gems/actionpack-6.1.7.10/lib/action_dispatch/middleware/static.rb:24:in `call'
 77bfe51b | /usr/share/gems/gems/apipie-dsl-2.6.2/lib/apipie_dsl/static_dispatcher.rb:67:in `call'
 77bfe51b | /usr/share/gems/gems/apipie-rails-1.4.2/lib/apipie/static_dispatcher.rb:74:in `call'
 77bfe51b | /usr/share/gems/gems/actionpack-6.1.7.10/lib/action_dispatch/middleware/static.rb:24:in `call'
 77bfe51b | /usr/share/gems/gems/actionpack-6.1.7.10/lib/action_dispatch/middleware/static.rb:24:in `call'
 77bfe51b | /usr/share/gems/gems/actionpack-6.1.7.10/lib/action_dispatch/middleware/static.rb:24:in `call'
 77bfe51b | /usr/share/gems/gems/actionpack-6.1.7.10/lib/action_dispatch/middleware/static.rb:24:in `call'
 77bfe51b | /usr/share/gems/gems/actionpack-6.1.7.10/lib/action_dispatch/middleware/static.rb:24:in `call'
 77bfe51b | /usr/share/gems/gems/actionpack-6.1.7.10/lib/action_dispatch/middleware/static.rb:24:in `call'
 77bfe51b | /usr/share/gems/gems/actionpack-6.1.7.10/lib/action_dispatch/middleware/static.rb:24:in `call'
 77bfe51b | /usr/share/gems/gems/actionpack-6.1.7.10/lib/action_dispatch/middleware/static.rb:24:in `call'
 77bfe51b | /usr/share/gems/gems/actionpack-6.1.7.10/lib/action_dispatch/middleware/static.rb:24:in `call'
 77bfe51b | /usr/share/foreman/lib/foreman/middleware/libvirt_connection_cleaner.rb:9:in `call'
 77bfe51b | /usr/share/foreman/lib/foreman/middleware/telemetry.rb:10:in `call'
 77bfe51b | /usr/share/gems/gems/apipie-rails-1.4.2/lib/apipie/middleware/checksum_in_headers.rb:27:in `call'
 77bfe51b | /usr/share/gems/gems/rack-2.2.10/lib/rack/tempfile_reaper.rb:15:in `call'
 77bfe51b | /usr/share/gems/gems/rack-2.2.10/lib/rack/etag.rb:27:in `call'
 77bfe51b | /usr/share/gems/gems/rack-2.2.10/lib/rack/conditional_get.rb:40:in `call'
 77bfe51b | /usr/share/gems/gems/rack-2.2.10/lib/rack/head.rb:12:in `call'
 77bfe51b | /usr/share/gems/gems/actionpack-6.1.7.10/lib/action_dispatch/http/permissions_policy.rb:22:in `call'
 77bfe51b | /usr/share/gems/gems/actionpack-6.1.7.10/lib/action_dispatch/http/content_security_policy.rb:19:in `call'
 77bfe51b | /usr/share/foreman/lib/foreman/middleware/logging_context_session.rb:22:in `call'
 77bfe51b | /usr/share/gems/gems/rack-2.2.10/lib/rack/session/abstract/id.rb:266:in `context'
 77bfe51b | /usr/share/gems/gems/rack-2.2.10/lib/rack/session/abstract/id.rb:260:in `call'
 77bfe51b | /usr/share/gems/gems/actionpack-6.1.7.10/lib/action_dispatch/middleware/cookies.rb:697:in `call'
 77bfe51b | /usr/share/gems/gems/actionpack-6.1.7.10/lib/action_dispatch/middleware/callbacks.rb:27:in `block in call'
 77bfe51b | /usr/share/gems/gems/activesupport-6.1.7.10/lib/active_support/callbacks.rb:98:in `run_callbacks'
 77bfe51b | /usr/share/gems/gems/actionpack-6.1.7.10/lib/action_dispatch/middleware/callbacks.rb:26:in `call'
 77bfe51b | /usr/share/gems/gems/actionpack-6.1.7.10/lib/action_dispatch/middleware/actionable_exceptions.rb:18:in `call'
 77bfe51b | /usr/share/gems/gems/actionpack-6.1.7.10/lib/action_dispatch/middleware/debug_exceptions.rb:29:in `call'
 77bfe51b | /usr/share/gems/gems/actionpack-6.1.7.10/lib/action_dispatch/middleware/show_exceptions.rb:33:in `call'
 77bfe51b | /usr/share/gems/gems/railties-6.1.7.10/lib/rails/rack/logger.rb:37:in `call_app'
 77bfe51b | /usr/share/gems/gems/railties-6.1.7.10/lib/rails/rack/logger.rb:28:in `call'
 77bfe51b | /usr/share/gems/gems/sprockets-rails-3.5.2/lib/sprockets/rails/quiet_assets.rb:17:in `call'
 77bfe51b | /usr/share/foreman/lib/foreman/middleware/logging_context_request.rb:11:in `call'
 77bfe51b | /usr/share/gems/gems/actionpack-6.1.7.10/lib/action_dispatch/middleware/remote_ip.rb:81:in `call'
 77bfe51b | /usr/share/gems/gems/actionpack-6.1.7.10/lib/action_dispatch/middleware/request_id.rb:26:in `call'
 77bfe51b | /usr/share/gems/gems/katello-4.15.0/lib/katello/prevent_json_parsing.rb:12:in `call'
 77bfe51b | /usr/share/gems/gems/rack-2.2.10/lib/rack/method_override.rb:24:in `call'
 77bfe51b | /usr/share/gems/gems/rack-2.2.10/lib/rack/runtime.rb:22:in `call'
 77bfe51b | /usr/share/gems/gems/activesupport-6.1.7.10/lib/active_support/cache/strategy/local_cache_middleware.rb:29:in `call'
 77bfe51b | /usr/share/gems/gems/actionpack-6.1.7.10/lib/action_dispatch/middleware/executor.rb:14:in `call'
 77bfe51b | /usr/share/gems/gems/rack-2.2.10/lib/rack/sendfile.rb:110:in `call'
 77bfe51b | /usr/share/gems/gems/actionpack-6.1.7.10/lib/action_dispatch/middleware/ssl.rb:77:in `call'
 77bfe51b | /usr/share/gems/gems/actionpack-6.1.7.10/lib/action_dispatch/middleware/host_authorization.rb:142:in `call'
 77bfe51b | /usr/share/gems/gems/secure_headers-6.7.0/lib/secure_headers/middleware.rb:11:in `call'
 77bfe51b | /usr/share/gems/gems/railties-6.1.7.10/lib/rails/engine.rb:539:in `call'
 77bfe51b | /usr/share/gems/gems/railties-6.1.7.10/lib/rails/railtie.rb:207:in `public_send'
 77bfe51b | /usr/share/gems/gems/railties-6.1.7.10/lib/rails/railtie.rb:207:in `method_missing'
 77bfe51b | /usr/share/gems/gems/rack-2.2.10/lib/rack/urlmap.rb:74:in `block in call'
 77bfe51b | /usr/share/gems/gems/rack-2.2.10/lib/rack/urlmap.rb:58:in `each'
 77bfe51b | /usr/share/gems/gems/rack-2.2.10/lib/rack/urlmap.rb:58:in `call'
 77bfe51b | /usr/share/gems/gems/puma-6.4.3/lib/puma/configuration.rb:272:in `call'
 77bfe51b | /usr/share/gems/gems/puma-6.4.3/lib/puma/request.rb:100:in `block in handle_request'
 77bfe51b | /usr/share/gems/gems/puma-6.4.3/lib/puma/thread_pool.rb:378:in `with_force_shutdown'
 77bfe51b | /usr/share/gems/gems/puma-6.4.3/lib/puma/request.rb:99:in `handle_request'
 77bfe51b | /usr/share/gems/gems/puma-6.4.3/lib/puma/server.rb:464:in `process_client'
 77bfe51b | /usr/share/gems/gems/puma-6.4.3/lib/puma/server.rb:245:in `block in run'
 77bfe51b | /usr/share/gems/gems/puma-6.4.3/lib/puma/thread_pool.rb:155:in `block in spawn_thread'
 77bfe51b | /usr/share/gems/gems/logging-2.4.0/lib/logging/diagnostic_context.rb:474:in `block in create_with_logging_context'
2025-11-12T14:14:03 [I|app|77bfe51b] Completed 403 Forbidden in 136ms (Views: 0.2ms | ActiveRecord: 10.2ms | Allocations: 14954)
2025-11-12T14:14:03 [D|app|77bfe51b] With body: {"displayMessage":"Access denied","errors":["Access denied"]}

If you have any extra ideas, let me know! So far the only hack I’ve come up with is to make the user an administrator then use a PAT rather than password. Subman accepts that and it would prevent users from logging in under the account. Crafty users though could get around that, though, via API calls.

Hi @omenos,

Based on the stack trace and code flow, I have a theory about what might be causing the issue:

When you run subscription-manager environments --set, it sends a PUT request to /rhsm/consumers/{uuid} which is handled by the facts action. Looking at the code, this action has a before_action :authorize_client_or_user filter that checks permissions. However, the facts action immediately switches to User.anonymous_admin on line 261, which suggests it may not need user-level permissions.

The authorization check could be failing for your “registrar” user before it has a chance to switch to admin context.

In app/controllers/katello/api/rhsm/candlepin_proxies_controller.rb, around line 17, try changing:

From:

before_action :authorize_client_or_user, :only => [:consumer_show, :regenerate_identity_certificates, :upload_tracer_profile, :facts, :proxy_jobs_get_path]

To:

before_action :authorize_client_or_user, :only => [:consumer_show, :regenerate_identity_certificates, :upload_tracer_profile, :proxy_jobs_get_path]
# facts action doesn't require authorization - it immediately switches to anonymous_admin

(Remove :facts from the list)

Would you be willing to test this change? It would help confirm whether this is actually the issue, which would speed up the debugging process significantly since reproducing your exact setup locally would take me considerable time.

After making the change, restart your Foreman/Katello service and try subscription-manager environments --set again with your “registrar” user.

Please let me know the results - whether it works or still fails (and if it fails, any new error messages would be helpful).

Thanks!

subscription-manager environments –set may need the edit_hosts permission.

pry(main)> Foreman::AccessControl.permissions_for_controller_action("katello/api/rhsm/candlepin_proxies/facts").map(&:name)
=> [:edit_hosts]

To see what permissions are needed for a certain endpoint, you can go into foreman-rake console and use Foreman::AccessControl.permissions_for_controller_action with the matching controller “action name”. The trick is knowing the correct syntax for that action name, which takes some poking around (I ended up just querying the entire list with ::Foreman::AccessControl.permissions and then scrolling around until I found it.)

1 Like

Apologies for the ghosting, I’ll see if I can test this out next week. Though I might not need to worry about this since support for multiple-content-view activation keys should be supported in the Foreman Ansible collection sometime soon™.

@jeremylenz If edit_hosts is the required permission then that would be extremely weird. I gave my test user every permission exposed in the WebUI, which included edit_hosts. But that rake tip is nifty, I’m definitely noting it down!

Based on your initial description, it sounds like the issue may be that only admin users have the required permissions. Still, try what Jeremy suggested and let us know what you find.