FreeIPA HBAC and Sudo Rules

Host-based access control — who can log in to what, and centrally managed sudo rules.

What HBAC is

HBAC (Host-Based Access Control) is FreeIPA's mechanism for controlling which users (and groups) can log in to which hosts (and host groups). Instead of managing sudoers files on every server, you define rules centrally in FreeIPA and SSSD enforces them on each enrolled host.

Without HBAC rules granting access, a valid FreeIPA user cannot log in to an enrolled host — even if their credentials are correct.

The default allow_all rule

New FreeIPA installations have a rule called allow_all that permits any FreeIPA user to log in to any enrolled host. This is convenient but insecure for production.

Disable allow_all in production. Leave it enabled only in development/testing environments. In production, disable it and create specific rules for each user group and host group.
# Disable the default allow_all rule
ipa hbacrule-disable allow_all

# Verify it is disabled
ipa hbacrule-show allow_all

HBAC rule components

Each HBAC rule has four components:

Creating an HBAC rule

Via web UI

  1. IPA web UI → Policy → Host-Based Access Control → HBAC Rules
  2. Click + Add
  3. Name the rule (e.g. allow-webteam-webservers)
  4. Add users/groups, hosts/hostgroups, and services
  5. Click Add and Edit to configure details, then save

Via CLI

# Create the rule
ipa hbacrule-add allow-webteam-webservers \
  --desc "Web team access to web servers"

# Add who (user group)
ipa hbacrule-add-user allow-webteam-webservers \
  --groups=webteam

# Add where (host group)
ipa hbacrule-add-host allow-webteam-webservers \
  --hostgroups=webservers

# Add which service (sshd = SSH login)
ipa hbacrule-add-service allow-webteam-webservers \
  --hbacsvcs=sshd

# Verify the rule
ipa hbacrule-show allow-webteam-webservers

Testing HBAC rules

Before relying on a rule to work, test it. The test simulates an access check without actually logging in.

# Test if user alice can SSH to web01.example.com
ipa hbactest \
  --user=alice \
  --host=web01.example.com \
  --service=sshd

# Expected output if allowed:
# Access granted
# Matched rules: allow-webteam-webservers

# If denied:
# Access denied
Always use hbactest before asking a user to try logging in. It tells you exactly which rules matched (or did not match) without requiring a live login attempt. Also useful for diagnosing "why can't alice log in?" calls.
# Test all rules for a user across all hosts
ipa hbactest --user=alice --host=web01.example.com --service=sshd --enabled

# Show which rules were evaluated
ipa hbactest --user=alice --host=web01.example.com --service=sshd --detail

HBAC services

Common PAM service names to use in HBAC rules:

# List available HBAC services
ipa hbacsvc-find

# Allow SSH and sudo in one rule
ipa hbacrule-add-service allow-webteam-webservers \
  --hbacsvcs=sshd,sudo

FreeIPA sudo rules

FreeIPA can manage sudo rules centrally. Instead of editing /etc/sudoers on every host, you define rules in FreeIPA and SSSD distributes them to enrolled hosts.

For this to work, the host must have sudoers: sss in /etc/nsswitch.conf (set by ipa-client-install).

# Check nsswitch.conf includes sss for sudo
grep sudoers /etc/nsswitch.conf
# sudoers: files sss

Creating a sudo rule

# Create a sudo rule for the webteam
ipa sudorule-add allow-webteam-restart-nginx \
  --desc "Allow webteam to restart nginx"

# Apply to a user group
ipa sudorule-add-user allow-webteam-restart-nginx \
  --groups=webteam

# Apply to host group
ipa sudorule-add-host allow-webteam-restart-nginx \
  --hostgroups=webservers

# Allow specific commands
ipa sudorule-add-allow-command allow-webteam-restart-nginx \
  --sudocmds="/usr/bin/systemctl restart nginx,/usr/bin/systemctl reload nginx"

# Allow running as specific user
ipa sudorule-mod allow-webteam-restart-nginx \
  --runasusercat=all

Sudo command groups

Group related commands together for easier management:

# Create a command group for web service management
ipa sudocmdgroup-add web-service-commands \
  --desc "Commands for managing web services"

# Add commands to the group
ipa sudocmdgroup-add-member web-service-commands \
  --sudocmds="/usr/bin/systemctl restart nginx" \
  --sudocmds="/usr/bin/systemctl reload nginx" \
  --sudocmds="/usr/bin/systemctl status nginx"

# Use the group in a sudo rule
ipa sudorule-add-allow-command allow-webteam-restart-nginx \
  --sudocmdgroups=web-service-commands

Troubleshooting access denied

User has correct password but cannot log in

# Test HBAC rules
ipa hbactest --user=alice --host=web01.example.com --service=sshd --detail

# If denied, check which groups alice is in
ipa user-show alice

# Check which HBAC rules apply to the host
ipa hbacrule-find --host=web01.example.com

sudo commands denied even with a rule

# On the client host, invalidate the sudo rule cache
sss_cache -R      # invalidate all cached sudo rules (--sudo-rules)
sudo -l -U alice  # list what alice can sudo (if run by root)

# Check nsswitch.conf lists sss for sudoers
grep sudoers /etc/nsswitch.conf

# Check SSSD cache and domain status
sssctl user-show alice
sssctl domain-status example.com

HBAC rule exists but access denied after adding user to group

SSSD caches group membership. Force a cache refresh:

sss_cache -u alice
systemctl restart sssd    # nuclear option — forces full cache refresh

sudo command objects

Instead of specifying raw command strings inline, the proper long-term pattern is to create reusable sudo command objects in FreeIPA and group them. This makes it easier to audit what commands are allowed and update them in one place.

Create individual sudo command objects

# Create a sudo command object for each allowed command
ipa sudocmd-add /usr/bin/systemctl
ipa sudocmd-add /usr/sbin/ss
ipa sudocmd-add /usr/bin/journalctl

# List all defined sudo commands
ipa sudocmd-find

# Show details for a specific command
ipa sudocmd-show /usr/bin/systemctl

Create a sudo command group

# Create a group to hold related commands
ipa sudocmdgroup-add ops-cmds --desc "Common ops commands"

# Add command objects to the group
ipa sudocmdgroup-add-member ops-cmds \
  --sudocmds=/usr/bin/systemctl \
  --sudocmds=/usr/sbin/ss \
  --sudocmds=/usr/bin/journalctl

# Verify the group
ipa sudocmdgroup-show ops-cmds

Assign the command group to a sudo rule

# Create the sudo rule
ipa sudorule-add allow-ops-team

# Assign users/groups
ipa sudorule-add-user allow-ops-team --groups=ops-team

# Assign hosts
ipa sudorule-add-host allow-ops-team --hostgroups=production-servers

# Assign the command group
ipa sudorule-add-allow-command allow-ops-team --sudocmdgroups=ops-cmds

The advantage over inline strings: if a command path changes (e.g. /sbin/ss/usr/sbin/ss), you update the command object once and all rules using it update automatically.

HBAC rule lifecycle

HBAC rules can be enabled or disabled without deleting them. This is useful for temporarily blocking access during incidents or maintenance.

# Disable a rule (users affected immediately after SSSD cache clears)
ipa hbacrule-disable allow-ops-ssh-production

# Re-enable the rule
ipa hbacrule-enable allow-ops-ssh-production

# Check rule status
ipa hbacrule-show allow-ops-ssh-production | grep ipaenabledflag

# List all rules and their enabled status
ipa hbacrule-find --all | grep -E "Rule name|Enabled"
Incident workflow: During a security incident, ipa hbacrule-disable is faster and safer than deleting the rule. You can re-enable it once the incident is resolved, with full history preserved.

Deny and exclusion patterns

FreeIPA does not support deny rules. The ipa hbacrule-* commands only create allow rules — there is no --deny flag, and the deprecated deny type from early IPA releases was removed years ago. This is a deliberate design choice: rule evaluation stops at the first match, so mixing allow and deny produced order-dependent, hard-to-audit behaviour.

The canonical pattern is therefore default deny + explicit allow:

  1. Disable allow_all so the implicit default becomes "deny everything".
  2. Create a narrow allow rule for every who/where/service combination you want to permit.
  3. To "deny" a user, remove them from the groups referenced by the allow rules (or from the rule's user list directly).
# "Deny" alice access to web servers — remove her from the group that grants it
ipa group-remove-member webteam --users=alice

# Temporarily lock alice out of everything without deleting her
ipa user-disable alice            # blocks all authentication, not just HBAC

# Or disable a specific rule without touching membership
ipa hbacrule-disable allow-webteam-webservers

When you need "allow everyone except X", model it as two groups:

# Group of people who should NOT have access
ipa group-add no-ssh-contractors

# Allow group = full group minus the excluded group (maintained by membership)
# FreeIPA has no set-difference primitive — you maintain the "allowed" group directly.
ipa group-add-member allowed-ssh --users=alice,bob,carol   # not dave

If you truly need deny semantics (e.g. PCI auditors asking for an explicit deny), layer it at a different tier: PAM access.conf on the host, SSH DenyUsers/DenyGroups, or firewall rules. Do not try to emulate deny inside HBAC — you will only confuse the next on-call engineer reading ipa hbactest output.

Migrating off allow_all safely

Never disable allow_all before you have working replacement rules. The moment allow_all flips to disabled and SSSD caches expire, every non-admin user in the realm loses SSH access to every enrolled host — including the engineer running the migration. Plan for a transition window and keep at least one console/emergency path to an admins-group account.

A safe migration order:

  1. Inventory. For every hostgroup, list who actually logs in: last -F, journalctl _SYSTEMD_UNIT=sshd.service, or central logs over the past 30–90 days.
  2. Create the replacement rules for every user-group → host-group → service combination discovered above. Leave allow_all enabled.
  3. Dry-run with ipa hbactest. For each user you expect to retain access, run ipa hbactest --user=… --host=… --service=sshd --rules=your-new-rule and confirm every expected login matches one of the new rules.
  4. Cut over during a change window. Disable allow_all (ipa hbacrule-disable allow_all), clear SSSD caches on a canary host (sss_cache -E && systemctl restart sssd), and verify a test user from each group can still log in.
  5. Monitor auth failures for 24–48h via journalctl -u sssd and /var/log/secure. Re-enable allow_all (ipa hbacrule-enable allow_all) immediately if you locked the wrong population out — it is a one-command rollback.
  6. Once stable, consider deleting allow_all (ipa hbacrule-del allow_all) so it cannot be re-enabled accidentally.

runasuser / runasgroup

Sudo rules can restrict which user or group a command may be run as — the "run as" part of sudo. For example, allowing someone to run a command as postgres but not as root.

# Allow ops-team to run backup as the postgres user only
ipa sudorule-add allow-ops-pg-backup
ipa sudorule-add-user allow-ops-pg-backup --groups=ops-team
ipa sudorule-add-host allow-ops-pg-backup --hostgroups=db-servers

# Restrict the "run as" user to postgres
ipa sudorule-add-runasuser allow-ops-pg-backup --users=postgres

# Restrict the "run as" group
ipa sudorule-add-runasgroup allow-ops-pg-backup --groups=dba

# Add the allowed command
ipa sudorule-add-allow-command allow-ops-pg-backup \
  --sudocmds="/usr/bin/pg_dump"

Result in /etc/sudoers terms, this is equivalent to:

# %ops-team  ALL=(postgres:dba) /usr/bin/pg_dump

Usage on the host:

# Run pg_dump as the postgres user
sudo -u postgres /usr/bin/pg_dump mydb > /tmp/mydb.sql

# Run as postgres in the dba group
sudo -u postgres -g dba /usr/bin/pg_dump mydb