I started coding this project alone, but I crossed paths with Copilot and we decided to join forces and work on it together.
It has been a great help for coding javascript, jquery, as well as testing, debugging, and documentation.
This project is mirrored on Codeberg and Github, and the artifacts are pushed to Codeberg/GEANT/packages.
If Open Source is your thing, please consider starring and contributing on Codeberg: codeberg/GEANT/docker-encompass.
If you are searching for an OpenVox / Puppet ENC implementation, this repository provides an
OpenVox / Puppet External Node Classifier with Docker deployment, host/group classification, and ENC
API endpoints.
enCompass is a Django-based OpenVox / Puppet External Node Classifier (ENC) packaged for Docker.
It provides a web UI to manage hosts and groups, plus read-only ENC endpoints exposed by both enCompass
and enCapsule.
enCapsule is an optional agent for enCompass that can be used to provide high availability for the ENC API.
It does not depend on a database and boots up in just 1 second, making it ideal for an autoscaling setup.
This repository also includes optional enCryptor and deCryptor components that enable certificate
auto-signing flows through CSR challengePassword generation and validation.
An optional puppet-enc Go binary is also provided as a faster, dependency-free drop-in replacement for
the puppet-enc.sh shell script used to integrate Puppet Server with the ENC API.
Demo site: encompass-demo.geant.org
Repository URL: codeberg.org/GEANT/docker-encompass
site.pp vs enCompass ENC: codeberg.org/GEANT/docker-encompass/docs/SITEPP_VS_ENC.md


You can use encompass.nomad and encapsule.nomad and adjust them to your needs.
The job contains:
Help needed!
8080, 8081, 8443, 8444The following instructions are not intended for a production grade deployment.
Copy environment variables file:
bash
cp vars.example vars
Review and update vars for your environment (LDAP host, secrets, allowed hosts, SSL paths, etc.).
Build and start:
bash
docker compose up --build
Open UI:
http://localhost:8080/encompass/8080: web UI (HTTP)8081: ENC read-only endpoint (HTTP)8443: web UI (HTTPS, when ENC_USE_SSL=true)8444: ENC read-only endpoint (HTTPS, when ENC_USE_SSL=true)9081: ENC read-only endpoint (HTTP; container 8081 exposed on host 9081 in Docker Compose)9444: ENC read-only endpoint (HTTPS; container 8444 exposed on host 9444 when ENC_USE_SSL=true)Additional enCompass CSR endpoints (usable for external provisioning):
GET /hosts/<fqdn>/csr_attributesGET /groups/<name>/csr_attributesResponse example:
---
custom_attributes:
challengePassword: secure_password
CSR endpoint authentication:
CSR_API_KEY in vars.X-CSR-API-KEY.Example:
curl -s -H "X-CSR-API-KEY: $CSR_API_KEY" \
http://enc.example.org:8081/hosts/node1.example.org/csr_attributes
enCryptor is an optional helper component for provisioning workflows that
retrieve CSR challengePassword values from enCompass/enCapsule and write the
expected YAML blob for autosign use cases.
deCryptor is an optional Puppet autosign policy helper that validates incoming
CSR challengePassword values against enCompass/enCapsule.
Component names are enCryptor and deCryptor; executable names are
encryptor and decryptor.
Full documentation:
puppet-enc is a compiled Go binary that calls the ENC API and serves as a
drop-in replacement for puppet-enc.sh. On busy Puppet Servers it is roughly
2–3× faster and uses 3–4× less CPU per invocation compared to the shell script.
Full documentation:
This project is relevant to searches and documentation around:
In principle you can simply use curl against the ENC endpoint as follows:
curl -s http://enc.example.org:8081/hosts/\$1
For production use on a busy Puppet Server, the recommended approach is the
puppet-enc Go binary (see puppet-enc (Optional) and
docs/PUPPET-ENC.md). If deploying a compiled binary is
not practical, the shell script puppet-enc.sh is available as a fallback.
sudo install -m 0755 puppet-enc /etc/puppetlabs/puppet/enc/puppet-enc
No external dependencies required.
Create a wrapper so Puppet can pass the node certname ($1):
sudo tee /etc/puppetlabs/puppet/enc/enc-wrapper.sh >/dev/null <<'EOF'
#!/usr/bin/env bash
exec /etc/puppetlabs/puppet/enc/puppet-enc \
--node "$1" \
--server encompass.example.org \
--srv
EOF
sudo chmod 0755 /etc/puppetlabs/puppet/enc/enc-wrapper.sh
sudo install -m 0755 examples/puppet-enc.sh /etc/puppetlabs/puppet/enc/puppet-enc.sh
Required tools on Puppet Server: bash, curl, dig, getopt
sudo tee /etc/puppetlabs/puppet/enc/enc-wrapper.sh >/dev/null <<'EOF'
#!/usr/bin/env bash
exec /etc/puppetlabs/puppet/enc/puppet-enc.sh \
--node "$1" \
--server encompass.example.org \
--srv
EOF
sudo chmod 0755 /etc/puppetlabs/puppet/enc/enc-wrapper.sh
Help:
Usage: puppet-enc --node <node> --server <hostname> [--srv | --rrdns --port <port> | --port <port>] [--user <username> --password <password>]
puppet-enc -h | --help
-n, --node Node to query
-s, --server Server hostname/IP to connect
-u, --user Username (jointly required with --password)
-p, --password Password (jointly required with --user)
--srv Resolve endpoint via SRV record _puppet8._tcp.<server>
--rrdns Resolve <server> to multiple A/AAAA records and try each with --port
--port Static port (required for non-SRV mode)
In /etc/puppetlabs/puppet/puppet.conf:
[server]
node_terminus = exec
external_nodes = /etc/puppetlabs/puppet/enc/enc-wrapper.sh
Apply and verify:
sudo systemctl restart puppetserver
sudo puppet config print node_terminus external_nodes --section master
The ENC command must return valid YAML for the requested node and exit with code 0.
If you are migrating from site.pp-based classification to ENC, you can start safely with a minimal ENC setup:
default in ENC and assign one test profile/class.About lookup/precedence:
default.site.pp is evaluated before ENC classification is applied.site.pp can conflict with or dilute the intended ENC-driven model.Operational guidance:
site.pp for the same nodes.site.pp when adopting ENC.environment, Puppet uses the node/default environment (commonly production).Main runtime configuration is in vars (copied from vars.example).
DEBUG: Django debug modeDJANGO_SECRET_KEY: Django secret key (generate a unique value)CSR_CHALLENGE_KEY: dedicated symmetric key for encrypted CSR challengePassword storageALLOWED_HOSTS, CSRF_TRUSTED_ORIGINS, CORS_ALLOWED_ORIGINSTIME_ZONE, LANGUAGE_CODEMYSQL_NODES is the single configuration knob for the database endpoint:
/run/haproxy-mysql.sock).MYSQL_NODES=mysql.example.org
# or with explicit port:
MYSQL_NODES=192.168.0.1:3306
MYSQL_NODES=10.0.0.10:3306,10.0.0.11:3306,10.0.0.12:3306
HTTP check against a monitoring port (e.g. Galera clustercheck on 9200):
MYSQL_NODES=10.0.0.10:3306,10.0.0.11:3306,10.0.0.12:3306
MYSQL_MONITORING_PORT=9200
MySQL-protocol check (requires a haproxy user on the DB with no password):
MYSQL_NODES=10.0.0.10:3306,10.0.0.11:3306,10.0.0.12:3306
MYSQL_HAPROXY_CHECK_USER=haproxy
-- on each Galera node:
CREATE USER 'haproxy'@'%';
If neither option is set, plain TCP connection checks are used.
/data/csr_challenges.yaml.host/<fqdn> and group/<groupname>.python manage.py rotate_csr_challenge --host node1.example.org --old-token "$OLD_TOKEN" --new-token "$NEW_TOKEN"
python manage.py rotate_csr_challenge --group default --old-token "$OLD_TOKEN" --new-token "$NEW_TOKEN"
python manage.py rotate_csr_challenge --all --old-token "$OLD_TOKEN" --new-token "$NEW_TOKEN"
--old-token defaults to CSR_CHALLENGE_OLD_KEY when omitted.--new-token defaults to CSR_CHALLENGE_KEY when omitted.ENCOMPASS_LOGGING: Django/UI runtime log level (DEBUG|INFO|WARNING|ERROR|CRITICAL)ENCAPSULE_LOGGING: enCapsule agent log level (DEBUG|INFO|WARNING|ERROR|CRITICAL)LDAP_LOGGING: runtime dropdown in Global Settings -> LDAP Settings (DEBUG|INFO|WARNING|ERROR|CRITICAL), default ERROR (restart required)["production"].UI behavior:
Feature Branch can be enabled/disabled by the local Admin in the Global Settings.
Host selectors in groups.yaml (hosts list):
web-) => prefix match (hostname.startswith("web-"))/^web-[0-9]+\.example\.org$/) => regex full matchResolution order:
hosts.yamlgroups.yaml order (and hosts list order inside each group)default groupValidation guardrails:
test- and test-app-) are rejected with a validation error.ENC_OVERLAPPING_DEFINITIONS_ENABLED=true disables that overlap rejection and allows overlapping enCompass definitions.The UI page /encompass/unclassified_hosts/ lists nodes considered unclassified.
A node is marked unclassified when its resolved ENC payload (environment, classes, parameters) matches the ENC default group profile.
Configuration:
UNCLASSIFIED_HOSTS_ENABLED: enable/disable the unclassified hosts page logic (true/false, default: true)PUPPETDB_SCHEMA (http or https, default: http)PUPPETDB_HOST (default: puppetdb.example.org)PUPPETDB_PORT (integer, default: 8080)PUPPETDB_TIMEOUT (integer seconds, default: 20)none, token, basicThe nodes endpoint is built internally as:
${PUPPETDB_SCHEMA}://${PUPPETDB_HOST}:${PUPPETDB_PORT}/pdb/query/v4/nodesAuthentication behavior:
LDAP connection/search settings are managed in Global Settings.
Access control note:
admin account.enc_admin) cannot open Global Settings.LDAP group mirroring behavior:
auth_group entries on login.Local Django auth mode bootstraps two local users on first start:
admin (initial password: admin)viewer (initial password: viewer)Change these initial passwords immediately from the User Settings page.
Set ENC_VIEWER_PASSWORD to protect read-only ENC endpoints with basic auth.
encompassENC_VIEWER_PASSWORDLeave empty to disable endpoint basic auth.
Enable HTTPS listeners by setting:
ENC_USE_SSL=trueENC_SSL_CERT_PATHENC_SSL_KEY_PATHThe application accepts the Git SSH private key in either of these forms:
GIT_SSH_PRIVATE_KEY: inline key content (works well in docker-compose env files)GIT_SSH_PRIVATE_KEY_FILE: path to a file containing the key (recommended for Kubernetes/Nomad secrets)Set only one of the two variables above (they are mutually exclusive).
Branch behavior:
GIT_BRANCH: branch used by enCompass for clone/fetch/checkout/commits/pushes.GIT_HOST: SSH Git host.GIT_REPO_PATH: repository path on the host (for example puppet/enc-data.git).GIT_REPO_USERNAME: SSH username used for Git operations.GIT_SSH_KEY_TYPE: SSH key algorithm (rsa, ed25519, ecdsa).GIT_REPO_URL: optional full repository URL override. If unset, runtime scripts compose ssh://<GIT_REPO_USERNAME>@<GIT_HOST>/<GIT_REPO_PATH>.Typical setup:
GIT_BRANCH=main (or your target branch, for example dev or a feature branch).Once configured on the Vox/Puppet server, ENC becomes essential for its operation and must be highly resilient.
enCompass is stateless and supports autoscaling. It can be set up to run at least two instances for High Availability and inherently load balancing.
The database is only crucial for the UI’s operation but is irrelevant for the ENC endpoint.
The repository now includes an agent runtime named enCapsule.
/hosts, /groups) and /healthz.A full description of enCapsule and its sync flow with enCompass is available in the enCapsule documentation.
docker compose --profile encapsule up --build encapsule
Default exposed port in compose profile:
9081 -> enCapsule read-only ENC API9444 -> enCapsule read-only ENC API (HTTPS, when ENC_USE_SSL=true)You can configure a token and trigger a pull/update of ENC data:
ENCAPSULE_SYNC_TOKEN=<your-token>POST /sync with header X-Encapsule-Token: <your-token>Example:
curl -X POST \
-H "X-Encapsule-Token: ${ENCAPSULE_SYNC_TOKEN}" \
http://localhost:9081/sync
Use /usr/local/bin/encapsule-sync.sh from the enCompass runtime after a successful Git push.
When host/group data is changed from enCompass, the application automatically:
Git sync execution mode is configurable:
GIT_SYNC_MODE=sync (default): request waits for commit/push/sync resultGIT_SYNC_MODE=async: request returns quickly and sync runs in a background workerReliability and latency controls:
GIT_SYNC_TIMEOUT (seconds, default 30)GIT_SYNC_RETRIES (default 2)GIT_SYNC_RETRY_DELAY (seconds, default 2)Common variables:
ENCAPSULE_SYNC_TOKEN: shared token expected by each enCapsule /sync (must be set in environment on both services)ENCAPSULE_SYNC_SCHEME: http or https (default http)ENCAPSULE_SYNC_TIMEOUT: curl timeout in seconds (default 5)ENCAPSULE_SYNC_PORT: default port for host-only targets (default 8081)ENCAPSULE_SYNC_USE_SRV: true for SRV names, false for direct targetsENCAPSULE_SYNC_HOST: one or more comma-separated entriesAccepted ENCAPSULE_SYNC_HOST entries:
ENCAPSULE_SYNC_USE_SRV=false (default):enc-a.internal (uses default port 8081)enc-a.internal:9081 (explicit port)http://enc-a.internal:9081/sync (full URL)ENCAPSULE_SYNC_USE_SRV=true:encapsule-sync.service.internal (SRV name)_encapsule-sync._tcp.enc.example.org (SRV name)Example:
ENCAPSULE_SYNC_HOST="encapsule-a.internal,encapsule-b.internal"
ENCAPSULE_SYNC_TOKEN="<shared-token>"
Example:
ENCAPSULE_SYNC_HOST="_encapsule-sync._tcp.enc.example.org"
ENCAPSULE_SYNC_TOKEN="<shared-token>"
Run manually:
/usr/local/bin/encapsule-sync.sh
The script sends requests in parallel and fails if any target fails.
If ENCAPSULE_SYNC_HOST is empty, the script exits successfully without sending requests.
In Nomad, SRV entries are typically easiest. In non-SRV environments, use explicit hostnames/IPs.
Host/group YAML data is stored in the configured Git repository.
When using LDAP authentication, the data stored in the database is not critical. It can be rebuilt from scratch and only session cookies will be lost.
When the authentication backend is MySQL, the database stores user information. It’s recommended to back up your MySQL database when Database authentication is used.
DJANGO_SECRET_KEYDEBUG in productionALLOWED_HOSTS and ALLOWED_CIDR_NETSUSE_SSL for production exposurehosts/groups, which is valid and it might come to hand in some edge cases. Is it sensible and appropriate?This project is licensed under the GNU General Public License v3.0 or later (GPL-3.0-or-later). See LICENSE for details.
SPDX-License-Identifier: GPL-3.0-or-later