lock family docs GitHub

Managed · Managed configuration

Sign, deploy and verify managed.toml

Signing #

A signature proves the file is the one the company published, not one an administrator account wrote by hand. v1 supports keyless cosign only: the file is signed by a CI workflow whose identity is in the certificate. Signing with a key pair waits for lockkit#15 and is not supported.

  1. Keep managed.toml in a repository of the company, and sign it with the workflow examples/managed/sign.yml (.github/workflows/sign.yml there). Its core:

    permissions:
      contents: write
      id-token: write   # the OIDC token cosign signs with
    steps:
      - uses: sigstore/cosign-installer@6f9f17788090df1f26f669e9d70d6ae9567deba6 # v4.1.2
      - run: cosign sign-blob --yes --bundle managed.toml.sigstore.json managed.toml
    
  2. Write signer.toml, the identity the tools accept. Both keys are required: certificate_identity_regexp must start with ^https:// and oidc_issuer must be an https:// URL (examples/managed/signer.toml):

    certificate_identity_regexp = "^https://github.com/<org>/<repo>/.github/workflows/sign.yml@refs/heads/main$"
    oidc_issuer = "https://token.actions.githubusercontent.com"
    
  3. Deploy the three files together, root-owned, in the managed directory (every recipe has a switch for it), and install cosign on the hosts (brew install cosign, or the release the family's installer pins: docs/install.md).

  4. Set [family] require_signature = true once every host has the bundle: from then on, a host without it refuses to start.

Check a bundle before deploying it, with the same identity:

cosign verify-blob --bundle managed.toml.sigstore.json \
  --certificate-identity-regexp '^https://github.com/<org>/<repo>/.github/workflows/sign.yml@refs/heads/main$' \
  --certificate-oidc-issuer https://token.actions.githubusercontent.com \
  managed.toml

<tool> managed show and lox managed show then print signed: yes and Managed by <organisation> (sha256:…, signed). Any change to one byte of managed.toml, of the bundle or of signer.toml without signing again is a refusal: the signature does not verify. The tools re-check the signature only when one of the three files changes.

Deploying #

Each recipe installs the directory 0755 and the files 0644, owned by root:root (macOS root:wheel). It installs managed.toml, and signer.toml with the bundle when you sign. No restart is needed: a running console reads the new file at its next policy check, and lox run applies a change of the file alone without an Apply? question.

Ansible #

examples/managed/ansible/tasks.yml creates the directory and copies the files with ansible.builtin.copy (owner, group, mode); playbook.yml picks the paths and group per OS (Linux and macOS):

- name: Lock Family managed files
  ansible.builtin.copy:
    src: "{{ lockfamily_files }}/{{ item }}"
    dest: "{{ lockfamily_managed_dir }}/{{ item }}"
    owner: root
    group: "{{ lockfamily_root_group }}"
    mode: "0644"
  loop: "{{ ['managed.toml'] + (['signer.toml', 'managed.toml.sigstore.json'] if lockfamily_signed | bool else []) }}"
ansible-playbook -i inventory examples/managed/ansible/playbook.yml -e lockfamily_signed=true

Debian and rpm packages #

examples/managed/nfpm.yaml builds a deb and an rpm with nfpm: contents: entries with file_info modes and owners, the signature entries ready to uncomment. Bump version with every change of the policy and publish the package in the repository your hosts already update from:

cd examples/managed
nfpm package -p deb -f nfpm.yaml    # example-corp-lockfamily-managed_1.0.0-1_all.deb
nfpm package -p rpm -f nfpm.yaml
sudo apt install ./example-corp-lockfamily-managed_1.0.0-1_all.deb

The files are not marked as configuration files: an upgrade of the package replaces them, even if someone changed them by hand.

macOS #

examples/managed/macos/build.sh builds a .pkg with pkgbuild: its payload is /Library/Application Support/lockfamily/, and its postinstall script sets root:wheel, 0755 and 0644:

cd examples/managed
sh macos/build.sh 1.0.0           # example-corp-lockfamily-managed-1.0.0.pkg
sudo installer -pkg example-corp-lockfamily-managed-1.0.0.pkg -target /

To deploy it through an MDM, sign the .pkg with your Developer ID Installer certificate (productsign) and upload it as a package:

  • Jamf Pro: Settings → Computer management → Packages, then a policy that installs it, scoped to the fleet, triggered at check-in.
  • Microsoft Intune: Apps → macOS → Add → macOS app (PKG), assigned as required to the device groups.

Verifying a deployment #

On a host, after the deployment:

  1. ls -ld <directory> <directory>/*: root root (root wheel on macOS), drwxr-xr-x and -rw-r--r--.
  2. lox managed show: Managed by <organisation> (sha256:…), signed: yes when you sign, and the rules you wrote.
  3. <tool> managed check in a project of each Lock: … within the company policy, or the lines to fix in the user's or project's profiles.
  4. lox doctor: managed: Managed by ….

Troubleshooting #

Every tool reports the same reason, after managed: <path>: (lox run: …, <tool> managed show: …, blocked by company policy: … in managed check); a refused file always exits 3.

Message Fix
must be writable by root only (mode 0666) chmod 0644 the file (0755 its directory).
must be owned by root (it is owned by uid 1000) chown root:root (root:wheel) the file and its directory.
is not a regular file Copy the file in place of the symbolic link.
invalid TOML at line N Fix the syntax; python3 -c 'import tomllib,sys; tomllib.load(open(sys.argv[1],"rb"))' managed.toml finds it.
version = 1 is required, version 2 is not supported (this build reads version 1) Set version = 1.
unknown key "lockvpn", [lox]: unknown key "egress_allow" A section or key of no tool, or a typo: only the tool whose section it is refuses.
key "token" looks like a secret; the managed file holds no secrets Remove it: the file is readable by every user.
[locksql.profiles.prod-erp]: the managed profile is beyond the managed bounds: locksql.bounds.auto.ddl = deny (profile prod-erp: auto.ddl = ask) A managed profile must sit within the bounds, built-in defaults included: set the field in the profile (auto = { ddl = "deny" }).
is signed but …/signer.toml, the company identity to check it against, is missing Deploy signer.toml with the bundle.
certificate_identity_regexp must be an anchored ^https:// regexp and oidc_issuer an https:// URL Fix signer.toml.
the signature does not verify: cosign is not installed Install cosign on the host's PATH.
the signature does not verify: … The file, the bundle or signer.toml changed since signing, or the identity does not match: sign again, or fix signer.toml.
[family] require_signature is set but managed.toml.sigstore.json is missing Deploy the bundle.
is signed, but this lox cannot vouch for its signature inside the locks' containers (…): upgrade lox Upgrade lox to v0.11.0 or later.
<tool> vX.Y.Z predates managed support: upgrade it or set [lox] image_min Upgrade that Lock (lox run updates it unless --no-upgrade).
blocked by company policy: lox.image_min.locksql = v0.18.0 (the image is locksql v0.17.2) Run lox run without --no-upgrade, or pin a newer release.
blocked by company policy: <key> … in managed check The user's or project's profile goes beyond the bounds: tighten it (see Violations).
profile "<name>" is provided by the company's managed configuration and cannot be redefined in the project config: rename yours Rename the user's or project's profile.
managed: … changed while lox read it: lox run again The file was replaced during lox run's read: run it again.