44. Compliance exports: Group Policy, Ansible and the signed shell bundle
This guide describes Lawliet 1.5.14.s (the current release). Screens, commands and procedures may change in later versions; the usage guide is updated with each release. Check the version your server runs under Settings > Platform or with ./lawliet version, and see the Releases page for what changed.
An export takes a role profile (40.2) outside Lawliet, for hosts you harden with your own tools or that no agent reaches:
| Profile | Exports |
|---|---|
| Windows workstation, member server, domain controller | GPO backup: a Group Policy backup for Group Policy Management "Import Settings" (registry.pol, a security template, advanced audit policy), with a how-to-import and Security Filtering guide |
| Ubuntu server | Ansible role (check mode, idempotent, tags per control and family, a rollback playbook from the recorded prior state); Signed shell bundle (one script with check / apply / rollback / report, signed with the vendor's release key when the release was built) |
In every export:
- Approved exceptions are left out and listed as accepted risk.
- Controls the format cannot carry are listed with how to apply them through Lawliet.
- Controls held back until their audit step has run are counted and listed.
- The README lists the framework ids every control maps to.
Path: Compliance > Exports.
Permissions: seeing the profiles needs View compliance; creating, downloading, listing exports and importing a shell bundle report need Manage compliance. Four-eyes: no. Every creation and download is written to the audit trail.
The changes an export makes happen on your hosts, through your tools - not through Lawliet's approval and planner checks. Review each export before you apply it, and roll it out to a pilot group first.
44.1 What the page shows#
- "Catalog <version>, N controls. Release key fingerprint: <fingerprint>" - or "not configured on this server (shell bundles cannot be verified here)".
- One card per profile with a Level list (Strict (default) or Standard), "N controls, M held back until the audit step." and the export buttons (GPO backup, Ansible role, Signed shell bundle). Without Manage compliance the buttons are disabled ("Needs the Manage compliance permission").
- Import a shell bundle report (44.5).
- Recent exports: Created, Profile (level), Export, Controls, Signature (shell bundles: signed or NOT signed), and a download button. "N exception(s) no longer approved - export again" means an exception the export left out has since been revoked or has expired: that export now leaves out a control it should carry.
Procedure: create an export#
- Choose the Level on the profile's card.
- For a Windows profile, optionally type the AD domain (optional, recorded in the backup), for example
corp.example. - Press the export button ("Building..."). The file downloads.
What you see: "<kind>: N controls in the export, M exceptions excluded, K not expressible, J held back. The README inside lists each one." For a shell bundle, also "Signed with the vendor release key. Verify it before use: VERIFY.txt in the bundle." (or "NOT signed: <reason>") and "profile.conf SHA-256 (the script prints it before apply; it must match): <hash>". Note the hash.
44.2 Procedure: import the GPO backup in Group Policy Management#
You need: a computer with the Group Policy Management Console (a domain controller or RSAT), and rights to create and link GPOs.
Risk: once linked, every computer in scope applies the settings at its next policy refresh. Some settings can break legacy clients (NTLM, SMB, TLS). Rollback: unlink the GPO and run gpupdate /force. Registry values outside SOFTWARE\Policies stay set; the README in the backup says how to revert them.
Unzip the archive. Keep the backup folder (named like
{GUID}) next tomanifest.xml.In GPMC, right-click Group Policy Objects > New. Name it as the export suggests. Do not link it yet.
Right-click the new GPO > Import Settings... > Next > Next > Backup folder: the unzipped folder (the one that contains
manifest.xml) > select the GPO > Next. Review "Scanning Backup" > Next > Finish.PowerShell equivalent (from
HOW-TO-IMPORT.txtin the archive):Import-GPO -BackupId '<backup id>' -Path 'C:\path\to\unzipped' -TargetName '<GPO name>' -CreateIfNeededRight-click the GPO > Edit and check Computer Configuration > Policies > Windows Settings > Security Settings, and the registry values.
Security Filtering - pilot first:
- Create a security group, for example
GPO-Lawliet-Pilot, and add the pilot computer accounts. - Select the GPO > Scope tab > Security Filtering: add the group, remove Authenticated Users.
- Delegation tab > Advanced... > add Authenticated Users (or Domain Computers) with Read only - not "Apply group policy". Without Read for computers the GPO is not processed at all.
- Link the GPO to the OU that holds the computers (right-click the OU > Link an Existing GPO).
- On a pilot computer:
gpupdate /force, thengpresult /h report.html, then run a Lawliet compliance scan to see the controls pass.
- Create a security group, for example
To exclude computers (for example hosts with a host-scoped exception, listed in
README.txt): put them in a group and give that group Apply group policy: Deny on the Delegation tab.
44.3 Procedure: run the Ansible role#
You need: Ansible on a control host that reaches the Ubuntu hosts with sudo.
Risk: the role changes SSH, PAM, firewall, kernel and service settings. Rollback: ansible-playbook rollback.yml puts back what the role recorded.
Unzip the role. It holds
site.yml,rollback.yml,ansible.cfg,inventory.example.ini,README.mdandexport.json.Copy
inventory.example.inito your inventory and list the hosts.Dry run, then apply:
ansible-playbook site.yml --check --diff # what would change ansible-playbook site.yml # apply ansible-playbook site.yml --tags UBU-SSH-ROOT-LOGIN # one control (tags: control id, lawliet_family_<family>) ansible-playbook rollback.yml # put back what the role changedRun a Lawliet compliance scan of the hosts to confirm.
44.4 Procedure: verify and run the signed shell bundle#
For air-gapped Ubuntu hosts with no Ansible. Run as root on the host.
Risk: apply changes the host's configuration. Rollback: ./lawliet-harden.sh rollback puts back what apply changed, from the state in /var/lib/lawliet/bundle - keep that directory until you no longer need to roll back.
- Copy the whole folder to the host.
- Verify the signature first (OpenSSL 3, Ubuntu 22.04 and later):
openssl dgst -sha256 -binary lawliet-harden.sh > /tmp/lawliet.digest base64 -d lawliet-harden.sh.sig > /tmp/lawliet.sig openssl pkeyutl -verify -pubin -inkey lawliet-release-public.pem -rawin \ -in /tmp/lawliet.digest -sigfile /tmp/lawliet.sigIt must print
Signature Verified Successfully. Anything else: do not run the script. Compare the key's fingerprint inVERIFY.txtwith the one on Compliance > Exports (a key shipped inside the same archive proves nothing by itself). A bundle marked NOT signed cannot be verified this way: do not use it on production hosts. - Check, then apply:
./lawliet-harden.sh check --level strict # changes nothing ./lawliet-harden.sh apply --level strict --json apply.jsonBefore applying, the script prints the SHA-256 of
profile.conf. It must match the hash the console showed.profile.confis not signed: it holds the approved exceptions and can only exclude controls, but an edited copy could exclude controls you meant to enforce. - To undo:
./lawliet-harden.sh rollback. - To report:
./lawliet-harden.sh report --level strict > report.json, then import it (44.5)../lawliet-harden.sh helplists every option.
44.5 Procedure: import a shell bundle report as evidence#
Permission: Manage compliance. Four-eyes: no.
- On Compliance > Exports, under Import a shell bundle report, press Choose report.json and pick the file from
lawliet-harden.sh report.
What you see: "Imported N control results as evidence bundle #M." The bundle is on Evidence (chapter 27) and is marked self-reported by the host - the results come from the host's own script, not from a Lawliet agent.
If it fails: "Import failed." or the server's reason (not a bundle report, or malformed JSON).

