Compare commits

..

6 Commits

Author SHA1 Message Date
wmantly 3ca4802075 fix: mount docker socket for the docker discovery plugin; roll up sso v1.28.0 + theta-agent v1.3.1 (v1.41.0) (#167)
CI/CD / build-theta-agent (push) Successful in 46s
CI/CD / docker-push (push) Failing after 17s
- docker-compose: mount /var/run/docker.sock into sso-manager so the seeded
  docker-local plugin can list containers (was ENOENT -> 'Last run: error')
- gitlinks: sso-manager-node 49100c9 (v1.28.0), theta-agent 51750d0 (v1.3.1)
2026-08-05 03:04:20 -04:00
wmantly 86026e90e7 fix: skip host self-registration when sso_token empty; roll up ldap-client v1.25.0 (v1.40.0) (#166)
CI/CD / build-theta-agent (push) Successful in 42s
CI/CD / docker-push (push) Failing after 18s
ldap-client no longer POSTs an empty Bearer to /api/directory-admin/resources
(the misleading 'Invalid Credentials, login failed' during setup). Gitlink ->
ldap-client 68fcdb5 (v1.25.0).
2026-08-05 01:34:32 -04:00
wmantly 3354407f04 fix: publish plain LDAP (389) to the host so setup.sh can reach the directory (v1.39.0) (#165)
CI/CD / build-theta-agent (push) Successful in 45s
CI/CD / docker-push (push) Failing after 16s
docker-compose only published LDAPS (636); plain LDAP (389) was not mapped, so
the stack host's own enrollment (ldap://localhost:389) couldn't reach the LDAP
server. Now both 389 + 636 are published (bind 0.0.0.0; LDAP_BIND/LDAPS_BIND to
lock to host). README updated.
2026-08-05 00:15:12 -04:00
wmantly 84d7c96c17 fix: LDAP enrollment uses localhost (not the public domain); align SSH access groups; roll up ldap-client v1.24.0 (v1.38.0) (#164)
CI/CD / build-theta-agent (push) Successful in 42s
CI/CD / docker-push (push) Failing after 16s
- setup.sh: ldap_host defaults to localhost (the public sso.<domain> can't reach
  the 389/636 LDAP ports through NAT); overridable via CFG_LDAPS_HOST
- ldap.vars access groups + ldap-client sssd filter now reference the SSO group
  model (site_<loc>_hosts_access, site_<loc>_host_<host>_access, god_admin)
- GROUPS.md §5/§8 updated to the corrected naming
- gitlink: ldap-client ebaac18 (v1.24.0)
2026-08-04 23:30:04 -04:00
wmantly 72046a8b29 docs: group naming matches docs/GROUPS.md; roll up sso v1.27.0 (v1.37.0) (#163)
CI/CD / build-theta-agent (push) Successful in 44s
CI/CD / docker-push (push) Failing after 17s
- GROUPS.md: per-resource groups are {site}_{kind}_{name}_{level}; site carries god + site-wide only
- gitlink: sso-manager-node e8d0420 (v1.27.0)
2026-08-04 23:02:14 -04:00
wmantly 73e1cc807a fix: setup.sh ldap.vars re-run abort + drop app_super_admin; roll up sso v1.26.1 (v1.36.1) (#162)
CI/CD / build-theta-agent (push) Successful in 45s
CI/CD / docker-push (push) Failing after 19s
- setup.sh: ldap.vars generation read CFG_* first-run vars (unset on re-run);
  now reads real values from sso-secrets.js, so LDAP enrollment works on re-runs
- generated ldap_access_groups now references god_admin (app_super_admin gone)
- gitlink: sso-manager-node 8db00f0 (v1.26.1)
2026-08-04 19:33:32 -04:00
9 changed files with 190 additions and 54 deletions
+35
View File
@@ -8,6 +8,41 @@ orchestration code; see each submodule's own `CHANGELOG.md`
[sso-manager-node](https://github.com/theta42/sso-manager-node/blob/master/CHANGELOG.md))
for what changed inside the apps it composes.
## [v1.41.0] - 2026-08-05
### Fixed
- **The Local Docker daemon discovery plugin no longer errors** — the sso-manager container had no access to the host docker socket, so the seeded `docker-local` plugin (socketPath `/var/run/docker.sock`) failed with `ENOENT` and showed "Last run: error". `docker-compose.yml` now mounts `/var/run/docker.sock` into the container. Recreate the container (`docker compose up -d sso-manager`) and hit "Run now" on the plugin.
- **theta-agent ships the rebuilt binary with the heartbeat fix** (v1.3.1, gitlink `51750d0`) — the prebuilt `theta-agent-linux-amd64` predated the v1.3.0 `heartbeat_ack` fix, so the installed agent still logged "Unknown command type: heartbeat_ack". Now rebuilt + tested.
## [v1.40.0] - 2026-08-05
### Fixed
- **No more spurious "Invalid Credentials, login failed" during LDAP enrollment** (ldap-client v1.25.0, gitlink `68fcdb5`) — `index.sh` self-registered the host in the Directory when `sso_token` was *declared but empty* (it checked `[[ -v ]]`), POSTing an empty Bearer token and getting a misleading `LDAPLoginFailed`. It now only registers with a real token; the stack host (already seeded by the bootstrap) skips registration.
- **The `cn=ldapclient` service account now shows in the SSO Users UI** — it was created as a bare `organizationalRole` (invisible to the `posixAccount` user filter) and never joined `app_sso_service_account`, so it never appeared as a service account. The bootstrap now creates it as a `posixAccount` (uid 10001, above the regular-user reserved floor) and adds it to `app_sso_service_account`; for an existing account it best-effort adds the `posixAccount` shape (auxiliary, so it can't conflict with the structural `organizationalRole`) + the group membership.
## [v1.39.0] - 2026-08-05
### Fixed
- **Plain LDAP (389) now reachable from the host** — `docker-compose.yml` published only LDAPS (636); plain LDAP (389) was deliberately not mapped, so the stack host's own enrollment (`setup.sh` → ldap-client, which configures sssd against `ldap://localhost:389` and `ldaps://localhost:636`) could not reach the directory over loopback. Both 389 and 636 are now published to the host (bind 0.0.0.0; `LDAP_BIND`/`LDAPS_BIND=127.0.0.1` to lock to the host only).
## [v1.38.0] - 2026-08-04
### Fixed
- **LDAP enrollment no longer reaches for the public domain** — `setup.sh` generated `ldap.vars` with `ldap_host` defaulting to the public SSO host (`sso.<domain>`), which the NAT/firewall blocks on the LDAP ports (389/636). It now defaults to `localhost` (the LDAP server is co-located on the stack host; `ldap_tls_reqcert=never` makes this safe), overridable with `CFG_LDAPS_HOST` for an internal hostname/IP.
- **SSH access groups match the SSO group model** (ldap-client v1.24.0) — the generated `sssd.conf` access filter and `ldap-ssh-key.sh` referenced the legacy names (`<location>_access`, `app_super_admin`); they now use `site_<location>_hosts_access` (all-hosts aggregate), `site_<location>_host_<hostname>_access`, and `god_admin`. GROUPS.md §8's example updated to match.
## [v1.37.0] - 2026-08-04
### Changed
- **Group naming corrected to match docs/GROUPS.md** — per-resource groups are `{site}_{kind}_{name}_{level}` (kind always present; a host `host_theta-env``site_local_host_theta-env_access`, a service → `site_local_app_sso-manager_access`). The spec's §3 text was updated to state this explicitly.
- **Roll up sso v1.27.0** — group names match the docs, a site carries only god + site-wide groups, duplicate group links removed, `/api/agent/*` no longer 404s, shared-secrets POST/GET fixed, Vault Apps tab lists minted tokens, discovery promote + plugin run logs fixed. See the [sso changelog](https://github.com/theta42/sso-manager-node/blob/master/CHANGELOG.md).
## [v1.36.1] - 2026-08-04
### Fixed
- **`setup.sh` no longer aborts with `CFG_BASE_DN: unbound variable`** — the ldap-client `ldap.vars` generation read the CFG_* first-run vars, which `ensure_config` only derives once (it returns early on a re-run once `sso-secrets.js` exists). It now reads the real values from the operator-owned `./config/sso-secrets.js` when the CFG_* vars are unset, so LDAP enrollment works on re-runs too. The generated `ldap_access_groups` now references `god_admin` (the legacy `app_super_admin` is gone).
- **Roll up sso v1.26.1** — drops the legacy `app_super_admin`: `SUPER_ADMIN_GROUP` is now `god_admin` (nested into every resource's `_admin` group), and `docker-entrypoint.sh` no longer seeds/nests `app_super_admin`. See the [sso changelog](https://github.com/theta42/sso-manager-node/blob/master/CHANGELOG.md).
## [v1.36.0] - 2026-08-04
### Added
+9 -6
View File
@@ -129,12 +129,15 @@ see browser warnings.)
Optional extra ports (only if you need them):
- **4443** — alternate HTTPS listener (e.g. if 443 is taken by something else).
- **636** (LDAPS) — for direct-LDAP clients on other machines (Linux hosts
via PAM/SSSD, LDAP-native apps). The proxy itself reaches LDAP over the
internal Docker network, so you do **not** need to expose 636 for the stack
to work.
**Do not forward 636 to the public internet.** If you need LAN clients to bind
LDAP, set `CFG_LDAPS_HOST=ldap.internal.example.com` (or `sso-manager` for
- **389** (LDAP) + **636** (LDAPS) — direct-LDAP access. The stack host's **own**
enrollment (`setup.sh` → ldap-client) configures its sssd against
`ldap://localhost:389` / `ldaps://localhost:636`, so both ports are published
to the host by default (bind 0.0.0.0; set `LDAP_BIND`/`LDAPS_BIND=127.0.0.1` to
lock to the host). LAN clients (Linux hosts via PAM/SSSD, LDAP-native apps) can
bind over either; the proxy itself reaches LDAP over the internal Docker
network and doesn't need them.
**Do not forward 389/636 to the public internet.** If you need LAN clients to
bind LDAP, set `CFG_LDAPS_HOST=ldap.internal.example.com` (or `sso-manager` for
same-host Docker clients) in `setup.env` and use an internal DNS record / cert
SAN. The default shows the public SSO hostname, which implies a public route.
+74 -16
View File
@@ -90,10 +90,11 @@ const CLIENT_NAME = 'theta-proxy';
const ADMIN_DN = `cn=${ADMIN_UID},ou=people,${BASE_DN}`;
const SVC_DN = `cn=ldapclient,ou=people,${BASE_DN}`;
// god_admin is the global super group (docs/GROUPS.md §2); the bootstrapped
// admin is its first member. app_sso_admin / app_sso_oauth_admin are the legacy
// admin is its first member. app_sso_admin / app_sso_oauth_admin are the
// per-console admin groups still used by the SSO UI. god_admin is nested into
// app_super_admin by docker-entrypoint.sh, so LDAP-level consumers (SSSD, sudo)
// resolve it transitively.
// the app_sso_* groups (and every resource's _admin group) by
// docker-entrypoint.sh + api_directory_admin, so LDAP-level consumers (SSSD,
// sudo) resolve it transitively.
const ADMIN_GROUPS = ['god_admin', 'app_sso_admin', 'app_sso_oauth_admin'];
const log = (...a) => process.stderr.write('[bootstrap] ' + a.join(' ') + '\n');
@@ -177,31 +178,88 @@ function ldapModify(ldif) {
}
// ── 1. LDAP service account for the proxy ───────────────────────────────────
// The proxy / ldap-client bind as cn=ldapclient. For it to SHOW in the SSO Users
// UI as a service account it must (a) match the user filter (posixAccount) and
// (b) be a member of app_sso_service_account (that membership is what the Users
// page marks as a non-person/service account). Older bootstraps created it as a
// bare organizationalRole (invisible to the Users list) and never joined the
// group, so it never appeared. Both are fixed here; the existing-path shape add
// is best-effort so a pre-existing account still binds even if the upgrade add
// fails.
function ensureServiceAccount() {
const pw = hashPasswordSSHA512(SVC_PASS);
const uidNum = '10001'; // distinct from the bootstrap admin's 10000; above uidGidReservedFloor so regular-user id allocation ignores it
if (entryExists(SVC_DN)) {
log(`Service account ${SVC_DN} exists — resetting password to ./config`);
const r = ldapModify([
log(`Service account ${SVC_DN} exists — ensuring service-account shape + password`);
// Add the auxiliary posixAccount objectClass + required attrs so the entry
// matches the Users list filter. inetOrgPerson is deliberately NOT added:
// it is structural and would conflict with the existing organizationalRole.
const shape = [
`dn: ${SVC_DN}`,
'changetype: modify',
'add: objectClass',
'objectClass: posixAccount',
'-',
'add: uid',
'uid: ldapclient',
'-',
'add: uidNumber',
`uidNumber: ${uidNum}`,
'-',
'add: gidNumber',
`gidNumber: ${uidNum}`,
'-',
'add: homeDirectory',
'homeDirectory: /nonexistent',
'-',
'add: description',
'description: LDAP bind service account (proxy / ldap-client)',
'',
].join('\n');
const rs = ldapModify(shape);
if (rs.code !== 0 && !/already exists|Type or value exists/i.test(rs.stderr)) {
log(' service-account shape warning (account still binds):', rs.stderr.trim());
}
const rp = ldapModify([
`dn: ${SVC_DN}`,
'changetype: modify',
'replace: userPassword',
`userPassword: ${pw}`,
'',
].join('\n'));
if (r.code !== 0) log(' password reset warning:', r.stderr.trim());
return;
if (rp.code !== 0) log(' password reset warning:', rp.stderr.trim());
} else {
log(`Creating service account ${SVC_DN}`);
const entry = [
`dn: ${SVC_DN}`,
'objectClass: inetOrgPerson',
'objectClass: posixAccount',
'objectClass: top',
'cn: ldapclient',
'sn: ldapclient',
'uid: ldapclient',
`uidNumber: ${uidNum}`,
`gidNumber: ${uidNum}`,
'homeDirectory: /nonexistent',
'description: LDAP bind service account (proxy / ldap-client)',
`userPassword: ${pw}`,
'',
].join('\n');
const r = ldapAdd(entry);
if (r.code !== 0) throw new Error(`ldapadd service account failed: ${r.stderr.trim()}`);
}
log(`Creating service account ${SVC_DN}`);
const r = ldapAdd([
`dn: ${SVC_DN}`,
'objectClass: organizationalRole',
'objectClass: simpleSecurityObject',
'objectClass: top',
'cn: ldapclient',
`userPassword: ${pw}`,
// Mark it as a service account (the Users UI's service-account signal).
const gdn = `cn=app_sso_service_account,ou=groups,${BASE_DN}`;
const rm = ldapModify([
`dn: ${gdn}`,
'changetype: modify',
'add: member',
`member: ${SVC_DN}`,
'',
].join('\n'));
if (r.code !== 0) throw new Error(`ldapadd service account failed: ${r.stderr.trim()}`);
if (rm.code === 0) log(` marked ${SVC_DN} as a service account`);
else if (/already exists|Type or value exists/i.test(rm.stderr)) log(` ${SVC_DN} already in app_sso_service_account`);
else log(` app_sso_service_account membership warning:`, rm.stderr.trim());
}
// ── 2. First admin user ─────────────────────────────────────────────────────
+14 -6
View File
@@ -52,12 +52,16 @@ services:
# the UI is reachable on the LAN during setup). Set SSO_BIND=127.0.0.1 to
# lock it to localhost once the proxy fronts it at https://<SSO_HOST>.
- "${SSO_BIND:-0.0.0.0}:${SSO_PORT:-3001}:3001"
# LDAPS for EXTERNAL direct-LDAP clients (legacy apps). The proxy itself
# reaches LDAPS over theta-net (sso-manager:636) without this host mapping.
# Prefer an internal-only hostname (set CFG_LDAPS_HOST in setup.env / ldapsHost
# in sso-secrets.js) and do NOT forward 636 to the public internet.
- "${LDAPS_PORT:-636}:636"
# Plain LDAP (389) is NOT mapped — direct-LDAP clients should use LDAPS.
# LDAPS (636) + plain LDAP (389) for direct-LDAP clients AND for the stack
# host's OWN enrollment: setup.sh / ldap-client configure the host's sssd
# against ldap://localhost and ldaps://localhost, and the LDAP server is
# co-located on this host, so BOTH ports must be reachable from the host
# over loopback — not only over the docker network. Bind 0.0.0.0 (default)
# so LAN clients can use the host's local IP too; set LDAP_BIND and/or
# LDAPS_BIND=127.0.0.1 to lock either to the host only. Prefer an internal
# hostname (CFG_LDAPS_HOST) and do NOT forward 389/636 to the public internet.
- "${LDAP_BIND:-0.0.0.0}:${LDAP_PORT:-389}:389"
- "${LDAPS_BIND:-0.0.0.0}:${LDAPS_PORT:-636}:636"
environment:
# Config (LDAP, OAuth, SMTP, ...) is loaded by @simpleworkjs/conf from
# ./config/sso-secrets.js (see volumes), then @simpleworkjs/bao-conf
@@ -77,6 +81,10 @@ services:
- HTTPS_PROXY=${CFG_HTTPS_PROXY:-}
- NO_PROXY=${CFG_NO_PROXY:-}
volumes:
# The host docker socket so the bundled Docker discovery plugin (seeded as
# 'docker-local' with socketPath /var/run/docker.sock) can list containers.
# Without this the plugin errors with ENOENT and shows 'Last run: error'.
- /var/run/docker.sock:/var/run/docker.sock
# Operator-edited SSO secrets (sso-secrets.js). Read-WRITE so the bootstrap
# can write the generated OAuth client creds into proxy-secrets.js. The
# entrypoint points CONF_SECRETS at /config/sso-secrets.js.
+16 -16
View File
@@ -76,17 +76,15 @@ enumerated as LDAP members, and cannot be used as Unix groups.
- The **structural delimiter is `_`**. It appears only between the fixed segments
of a group name.
- **The `S` site segment is the site resource's slug verbatim.** In the SSO
Directory, site/host resource slugs carry a kind prefix (`site_local`,
`host_theta-env`); the group builders keep them verbatim rather than
re-slugifying (which would corrupt the delimiter: `site_local``site-local`)
or inserting a separate kind segment. So a host resource `host_theta-env` under
site `site_local` yields `site_local_host_theta-env_access` (the `host_` is part
of the resource slug), and the site's own admin group is `site_local_super_admin`.
Services are stored without a prefix, giving `site_local_sso-manager_access`.
The kind (`host`/`app`) is used only to pick the **aggregate** the resource's
group nests into (`{site}_hosts_*` / `{site}_apps_*`), not the resource's own
group name.
- **The `S` site segment is the site resource's slug verbatim** (`site_local`),
NOT re-slugified (which would corrupt the delimiter: `site_local``site-local`).
- **Per-resource groups are `{S}_{kind}_{name}_{level}`.** `kind` is `host` or
`app`; `name` is the resource's **name slug with the kind prefix stripped** — a
host resource `host_theta-env` has name `theta-env`, so its groups are
`site_local_host_theta-env_access` / `_admin`. A service (the group model's
`app`, docs §11) `sso-manager` gives `site_local_app_sso-manager_access`. The
kind segment is always present, which is what makes a resource's name
unambiguous even if a host and a service share a name.
- **Within a segment, normalize to lowercase** — spaces and stray `_``-`; strip
other non-`[a-z0-9-]`. A host named `Web 01` and a site `Main Office` (resource
slugs `host_web-01` and `site_main-office`) yield groups `site_main-office_host_web-01_*`.
@@ -150,7 +148,9 @@ def effective(resource, level_or_cap, site):
if level_or_cap in ("admin","access"):
agg = f"{site}_{resource.kind}s_{level_or_cap}"
if user in agg: return True
specific = f"{site}_{resource.slug}_{level_or_cap}" # slug carries its kind
# resource.name is the resource's name slug (kind prefix stripped); the kind
# is its own segment. A host `host_theta-env` has name `theta-env`, kind `host`.
specific = f"{site}_{resource.kind}_{resource.name}_{level_or_cap}"
if user in specific: return True
if level_or_cap == "access": return effective(resource, "admin", site)
if level_or_cap == "admin": return False # access does not imply admin
@@ -232,12 +232,12 @@ Key ideas:
A host should import its **own** resource groups (plus any explicitly granted
ones). Because the schema is predictable, `ldap-client` can generate the per-host
`ldap_group_search_filter` from the enrolled host's identity, e.g. a host `web01`
at site `main-office` imports:
at site `main-office` (site resource slug `site_main-office`) imports:
```
(&(objectClass=groupOfNames)(|(cn=main-office_host_web01_access)
(cn=main-office_host_web01_admin)
(cn=main-office_host_web01_sudo)))
(&(objectClass=groupOfNames)(|(cn=site_main-office_host_web01_access)
(cn=site_main-office_host_web01_admin)
(cn=site_main-office_host_web01_sudo)))
```
So the operator (or ldap-client) selects a small allowlist of the host's `_access`
+39 -7
View File
@@ -292,6 +292,21 @@ dn_from_domain() {
echo "dc=$1" | sed 's/\./,dc=/g'
}
# Read a value from the (operator-owned) ./config/sso-secrets.js -- the source of
# truth on re-runs, where the CFG_* first-run shell vars are not (re)derived
# (ensure_config returns early once sso-secrets.js exists). Reads `stack.<key>`.
# Prints empty on any failure. Usage: sso_secrets_get ldapBaseDn
sso_secrets_get() {
node -e 'const c=require(process.argv[1]);const k=process.argv[2];console.log(c&&c.stack&&c.stack[k]!=null?c.stack[k]:"")' \
"$PWD/$CONFIG_DIR/sso-secrets.js" "$1" 2>/dev/null || true
}
# Read a top-level (non-stack) secret from sso-secrets.js, e.g. serviceAccountPass.
sso_secrets_get_top() {
node -e 'const c=require(process.argv[1]);const k=process.argv[2];console.log(c&&c[k]!=null?c[k]:"")' \
"$PWD/$CONFIG_DIR/sso-secrets.js" "$1" 2>/dev/null || true
}
# Write ./config/sso-secrets.js from the CFG_* shell vars.
write_sso_secrets() {
local dn="$CFG_BASE_DN" domain="$CFG_DOMAIN"
@@ -1265,15 +1280,32 @@ if [[ "$CFG_THETA_AGENT_ENABLE" == "1" ]] && [[ -x /usr/local/bin/theta-agent ]]
# ldap.vars (cp ldap.vars.template ldap.vars + edit) is always kept.
if [[ ! -f ldap-client/ldap.vars ]]; then
info " Generating ldap-client/ldap.vars from the stack config..."
# CFG_* first-run vars may be unset on a re-run (ensure_config returns
# early once sso-secrets.js exists), so fall back to reading the real
# values from the operator-owned sso-secrets.js. All `:-` guarded so a
# missing value degrades to an empty ldap.vars field, not a set -u abort.
ldap_base_dn="${CFG_BASE_DN:-$(sso_secrets_get ldapBaseDn)}"
ldap_site="${CFG_SITE_NAME:-$(sso_secrets_get siteName)}"
ldap_bind_pass="${CFG_SVC_PASS:-$(sso_secrets_get_top serviceAccountPass)}"
sso_host="${CFG_SSO_HOST:-$(sso_secrets_get ssoHost)}"
# The LDAP server is co-located with the stack on THIS host, so the
# host must reach it over the loopback / a local address -- NEVER the
# public domain (sso.<domain>), which cannot route back to the 389/636
# ports through NAT. localhost is fine because the generated sssd.conf
# sets ldap_tls_reqcert=never (hostname verification is off). An
# operator may override with CFG_LDAPS_HOST (an internal hostname/IP).
ldaps_host="${CFG_LDAPS_HOST:-localhost}"
cat > ldap-client/ldap.vars <<LDAPVARS
export ldap_host="${CFG_LDAPS_HOST:-sso.${CFG_DOMAIN}}"
export ldap_base_dn="${CFG_BASE_DN}"
export ldap_bind_dn="cn=ldapclient,ou=people,${CFG_BASE_DN}"
export ldap_bind_password="${CFG_SVC_PASS}"
export sso_url="https://${CFG_SSO_HOST}"
export ldap_host="${ldaps_host}"
export ldap_base_dn="${ldap_base_dn}"
export ldap_bind_dn="cn=ldapclient,ou=people,${ldap_base_dn}"
export ldap_bind_password="${ldap_bind_pass}"
export sso_url="https://${sso_host}"
export sso_token=""
export ldap_location="${CFG_SITE_NAME:-local}"
ldap_access_groups=( "\${ldap_location}_access" "\${ldap_location}_host_\$(hostname)_access" "app_super_admin" )
export ldap_location="${ldap_site:-local}"
# Groups that grant SSH/access on this host (docs/GROUPS.md §8): the site's
# all-hosts aggregate, this host's own access group, and god_admin.
ldap_access_groups=( "site_\${ldap_location}_hosts_access" "site_\${ldap_location}_host_\$(hostname)_access" "god_admin" )
LDAPVARS
else
info " ldap-client/ldap.vars exists -- keeping it"