Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| fd5c5f4e88 | |||
| ccbbceaa47 |
@@ -1,6 +1,3 @@
|
|||||||
# v1.26.1
|
|
||||||
- fix: the legacy `app_super_admin` group is gone — `SUPER_ADMIN_GROUP` (nested into every resource's `_admin` group by auto-provisioning) is now `god_admin`, and `docker-entrypoint.sh` no longer seeds or nests `app_super_admin` (god_admin is nested into the `app_sso_*` groups directly). `isSuperAdmin` still recognizes a pre-existing `app_super_admin` as a migration alias, so an old deployment isn't stripped of rights until it's rebuilt.
|
|
||||||
|
|
||||||
# v1.26.0
|
# v1.26.0
|
||||||
- feat: complete the group model (docs/GROUPS.md) — `god_admin` is now seeded into LDAP and nested into `app_super_admin`; every site auto-provisions `{site}_super_admin`, `{site}_hosts_*`/`{site}_apps_*` aggregates and `{site}_everyone`; per-resource `_admin`/`_access` groups (named `{site}_{slug}_{level}`, the kind carried in the resource slug) are nested into the site aggregates so the inheritance lattice exists in LDAP, not just in the resolver. Site/aggregate groups are self-healed idempotently on every Directory load, so a directory seeded by an older release picks them up without a rebuild.
|
- feat: complete the group model (docs/GROUPS.md) — `god_admin` is now seeded into LDAP and nested into `app_super_admin`; every site auto-provisions `{site}_super_admin`, `{site}_hosts_*`/`{site}_apps_*` aggregates and `{site}_everyone`; per-resource `_admin`/`_access` groups (named `{site}_{slug}_{level}`, the kind carried in the resource slug) are nested into the site aggregates so the inheritance lattice exists in LDAP, not just in the resolver. Site/aggregate groups are self-healed idempotently on every Directory load, so a directory seeded by an older release picks them up without a rebuild.
|
||||||
- feat: the naming convention is now enforced server-side — `POST /api/directory-admin/groups` rejects a group CN that isn't a valid group for the target resource (its own `_admin`/`_access`/capability, a site aggregate, a site-level group, or `god_admin`), so the free-text field can no longer mint `*_accessmember`-style names
|
- feat: the naming convention is now enforced server-side — `POST /api/directory-admin/groups` rejects a group CN that isn't a valid group for the target resource (its own `_admin`/`_access`/capability, a site aggregate, a site-level group, or `god_admin`), so the free-text field can no longer mint `*_accessmember`-style names
|
||||||
|
|||||||
+19
-8
@@ -359,7 +359,7 @@ EOF
|
|||||||
# god_admin is the global super group (docs/GROUPS.md §2), the top of the
|
# god_admin is the global super group (docs/GROUPS.md §2), the top of the
|
||||||
# group-inheritance lattice. It is seeded here so it exists from first boot;
|
# group-inheritance lattice. It is seeded here so it exists from first boot;
|
||||||
# the theta-suite bootstrap puts the first admin person into it.
|
# the theta-suite bootstrap puts the first admin person into it.
|
||||||
for group in god_admin app_sso_admin app_sso_invite app_sso_oauth_admin app_sso_service_account; do
|
for group in god_admin app_super_admin app_sso_admin app_sso_invite app_sso_oauth_admin app_sso_service_account; do
|
||||||
ldapadd -x -D "$LDAP_BIND_DN" -w "$LDAP_ADMIN_PASS" -H ldap://localhost:389 << EOF || true
|
ldapadd -x -D "$LDAP_BIND_DN" -w "$LDAP_ADMIN_PASS" -H ldap://localhost:389 << EOF || true
|
||||||
dn: cn=${group},ou=groups,${LDAP_BASE_DN}
|
dn: cn=${group},ou=groups,${LDAP_BASE_DN}
|
||||||
objectClass: groupOfNames
|
objectClass: groupOfNames
|
||||||
@@ -370,11 +370,11 @@ member: ${LDAP_BIND_DN}
|
|||||||
EOF
|
EOF
|
||||||
done
|
done
|
||||||
|
|
||||||
# Nest god_admin into the SSO admin groups, so god admins hold those rights
|
# Nest app_super_admin into the SSO admin groups, so cross-app super admins
|
||||||
# by membership rather than by a special case in app code. This is what makes
|
# hold those rights by membership rather than by a special case in app code.
|
||||||
# the privilege visible to every consumer -- SSSD, sudo, anything binding
|
# This is what makes the privilege visible to every consumer -- SSSD, sudo,
|
||||||
# LDAP directly -- instead of only to callers that happen to route through
|
# anything binding LDAP directly -- instead of only to callers that happen
|
||||||
# utils/permission.js.
|
# to route through utils/permission.js.
|
||||||
#
|
#
|
||||||
# app_sso_service_account is deliberately excluded: it is a marker for
|
# app_sso_service_account is deliberately excluded: it is a marker for
|
||||||
# non-person accounts, not a permission, and nesting admins into it would
|
# non-person accounts, not a permission, and nesting admins into it would
|
||||||
@@ -385,10 +385,21 @@ EOF
|
|||||||
dn: cn=${group},ou=groups,${LDAP_BASE_DN}
|
dn: cn=${group},ou=groups,${LDAP_BASE_DN}
|
||||||
changetype: modify
|
changetype: modify
|
||||||
add: member
|
add: member
|
||||||
member: cn=god_admin,ou=groups,${LDAP_BASE_DN}
|
member: cn=app_super_admin,ou=groups,${LDAP_BASE_DN}
|
||||||
EOF
|
EOF
|
||||||
done
|
done
|
||||||
info "Nested god_admin into the SSO admin groups"
|
info "Nested app_super_admin into the SSO admin groups"
|
||||||
|
|
||||||
|
# god_admin is the top of the lattice; nesting it into app_super_admin
|
||||||
|
# (which is itself nested into the app_sso_* groups above) makes it
|
||||||
|
# resolve to everything app_super_admin holds at the LDAP level too.
|
||||||
|
ldapmodify -x -D "$LDAP_BIND_DN" -w "$LDAP_ADMIN_PASS" -H ldap://localhost:389 >/dev/null 2>&1 << EOF || true
|
||||||
|
dn: cn=app_super_admin,ou=groups,${LDAP_BASE_DN}
|
||||||
|
changetype: modify
|
||||||
|
add: member
|
||||||
|
member: cn=god_admin,ou=groups,${LDAP_BASE_DN}
|
||||||
|
EOF
|
||||||
|
info "Nested god_admin into app_super_admin"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
info "LDAP directory initialized"
|
info "LDAP directory initialized"
|
||||||
|
|||||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "t42-sso-manager",
|
"name": "t42-sso-manager",
|
||||||
"version": "1.26.1",
|
"version": "1.26.0",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "t42-sso-manager",
|
"name": "t42-sso-manager",
|
||||||
"version": "1.26.1",
|
"version": "1.26.0",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@fortawesome/fontawesome-free": "^7.3.0",
|
"@fortawesome/fontawesome-free": "^7.3.0",
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "t42-sso-manager",
|
"name": "t42-sso-manager",
|
||||||
"version": "1.26.1",
|
"version": "1.26.0",
|
||||||
"description": "A very simple LDAP management and SSO system",
|
"description": "A very simple LDAP management and SSO system",
|
||||||
"author": [
|
"author": [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ const groups = require('../utils/groups');
|
|||||||
|
|
||||||
// Make `childCn` a member of `parentCn`, i.e. everyone in the child is
|
// Make `childCn` a member of `parentCn`, i.e. everyone in the child is
|
||||||
// transitively in the parent. Idempotent and non-fatal: "already a member" is
|
// transitively in the parent. Idempotent and non-fatal: "already a member" is
|
||||||
// the goal state, and a missing group (e.g. god_admin absent on a
|
// the goal state, and a missing group (e.g. app_super_admin absent on a
|
||||||
// directory seeded by an older entrypoint) is a reason to skip, not to fail the
|
// directory seeded by an older entrypoint) is a reason to skip, not to fail the
|
||||||
// caller's real work.
|
// caller's real work.
|
||||||
async function nestGroup(childCn, parentCn) {
|
async function nestGroup(childCn, parentCn) {
|
||||||
@@ -121,7 +121,7 @@ async function ensureSiteGroups(siteSlug, ownerDn, siteName, siteResourceId) {
|
|||||||
// {site}_{slug}_admin -> {site}_{slug}_access
|
// {site}_{slug}_admin -> {site}_{slug}_access
|
||||||
// {site}_{slug}_admin -> {site}_{kind}s_admin (aggregate)
|
// {site}_{slug}_admin -> {site}_{kind}s_admin (aggregate)
|
||||||
// {site}_{slug}_access -> {site}_{kind}s_access (aggregate)
|
// {site}_{slug}_access -> {site}_{kind}s_access (aggregate)
|
||||||
// god_admin -> {site}_{slug}_admin (global super admin)
|
// app_super_admin -> {site}_{slug}_admin (legacy cross-app)
|
||||||
async function provisionResourceGroups(resource, kind, siteSlug, ownerDn) {
|
async function provisionResourceGroups(resource, kind, siteSlug, ownerDn) {
|
||||||
const accessCn = groups.resourceGroupCns(siteSlug, resource.slug, 'access');
|
const accessCn = groups.resourceGroupCns(siteSlug, resource.slug, 'access');
|
||||||
const adminCn = groups.resourceGroupCns(siteSlug, resource.slug, 'admin');
|
const adminCn = groups.resourceGroupCns(siteSlug, resource.slug, 'admin');
|
||||||
@@ -487,7 +487,7 @@ router.get('/access-summary', async (req, res, next) => {
|
|||||||
//
|
//
|
||||||
// Counts come from the transitive closure, not from `member`. Reading the
|
// Counts come from the transitive closure, not from `member`. Reading the
|
||||||
// attribute would report only who is listed on the group, missing anyone
|
// attribute would report only who is listed on the group, missing anyone
|
||||||
// who reaches it through a nested group -- and since god_admin is
|
// who reaches it through a nested group -- and since app_super_admin is
|
||||||
// nested into every resource's _admin group, that is not an edge case.
|
// nested into every resource's _admin group, that is not an edge case.
|
||||||
let members = [];
|
let members = [];
|
||||||
if (group) {
|
if (group) {
|
||||||
|
|||||||
@@ -4,14 +4,13 @@ const {Group} = require('../models/group_ldap');
|
|||||||
const groups = require('./groups');
|
const groups = require('./groups');
|
||||||
|
|
||||||
// The group nested into every resource's _admin group by api_directory_admin
|
// The group nested into every resource's _admin group by api_directory_admin
|
||||||
// (cross-resource super-admin administration). This is `god_admin` -- the global
|
// (cross-resource super-admin administration). KEEP the legacy `app_super_admin`
|
||||||
// super group of the new model (docs/GROUPS.md), seeded by docker-entrypoint.sh.
|
// here: it is the group that actually exists and gets nested. The new schema's
|
||||||
// It used to be the legacy `app_super_admin`, which existed while god_admin
|
// global `god_admin` is recognized in isSuperAdmin() below, and api_directory_admin
|
||||||
// didn't; now that god_admin is created at boot, the provisioning nests it.
|
// nests SUPER_ADMIN_GROUP -- so until `god_admin` is created during bootstrap, this
|
||||||
// LEGACY_SUPER_ADMIN_ALIASES still recognizes a `app_super_admin` that predates
|
// must stay `app_super_admin` or resource auto-provisioning's nesting silently
|
||||||
// the migration, so an existing deployment isn't stripped of rights until it's
|
// no-ops (leaving only the creator as the group's sole member).
|
||||||
// rebuilt.
|
const SUPER_ADMIN_GROUP = 'app_super_admin';
|
||||||
const SUPER_ADMIN_GROUP = 'god_admin';
|
|
||||||
const LEGACY_SUPER_ADMIN_ALIASES = ['app_super_admin'];
|
const LEGACY_SUPER_ADMIN_ALIASES = ['app_super_admin'];
|
||||||
|
|
||||||
// True if the user (by resolved member cns) is a global god/super admin.
|
// True if the user (by resolved member cns) is a global god/super admin.
|
||||||
|
|||||||
Reference in New Issue
Block a user