LDAP schema
The embedded directory enforces no schema: no object class is required,
no attribute syntax is checked, and no must/may is consulted. LDAP
explains why and lists the five structural rules it enforces anyway. This
service still writes a large and consistent set of object classes and
attributes, and this page is the map of them: what each container holds, the
classes its entries carry, and every attribute this service reads or writes.
Two things to know first:
- Names starting
sts,oauth,saml,fed,spiffe,xacml,gnap,app,x509,krb5and similar are this service’s own. None is registered and none has an OID. They exist because the standards they serve (OAuth, WebAuthn, SPIFFE, DID Core and so on) came after the LDAP schema documents. Standard names (RFC 4519, RFC 2798 inetOrgPerson, draft-behera password policy, RFC 4530) are used wherever one fits. - Attribute names are case-insensitive. The directory keeps a table of
canonical spellings (
STANDARD_NAMESandOWN_NAMESinldap/ldap_server.js) so that an entry is displayed as written. An attribute missing from that table still works, but is shown lower-cased on/admin/ldap/directory.
In PostgreSQL every entry is one row of
sts_ldap_entries, with these attributes in the attrs JSONB column. With
persistence.mode=ldif an entry is one LDIF record.
The tree
Each trust realm has its own tree, rooted at the RFC 2247 form of the realm’s DNS domain (LDAP → A tree per trust realm).
dc=example,dc=com domain, dcObject
├── ou=users people, and every identity that authenticates
├── ou=groups groupOfNames
├── ou=applications applicationProcess + stsApplication
├── ou=federations applicationProcess + stsFederation
├── ou=roles stsRole
├── ou=policies xacmlPolicy
├── ou=peps xacmlPep
├── ou=passwordPolicies pwdPolicy + stsPasswordPolicy
├── ou=spiffe
│ ├── ou=entries applicationProcess + spiffeRegistrationEntry
│ └── ou=agents applicationProcess + spiffeAgent
├── ou=devices device + stsDevice (#130, #164)
├── ou=oidfed stsOidfedEntry (#132)
├── ou=claimproviders stsClaimProvider (#147)
├── ou=trustAnchors stsTrustAnchor (default realm only)
└── ou=crl cRLDistributionPoint (created on first CRL)
Every container is top, organizationalUnit with a description. All of them
are seeded, in both modes.
Operational attributes
| Attribute | Source | Notes |
|---|---|---|
entryUUID |
RFC 4530 | assigned at creation and kept through a rename. A person’s sub is urn:uuid:<entryUUID>, and a SCIM id is the bare value. Returned only when asked for by name. No client can write it (STS-LDAP-0076) |
createTimestamp, modifyTimestamp |
RFC 4512 | generalized time. Read-only in product mode |
entryDN |
RFC 5020 | computed at search time; read-only in product mode |
stsEntryUuidAlias |
this service | the losing entryUUID of a two-process create race, resolved to the same entry. Protected like entryUUID |
memberOf |
computed | a person’s groups, derived from member values, never stored |
People: ou=users
objectClass: top, person, organizationalPerson, inetOrgPerson. The RDN is
uid=<username>. One person is one entry, however they arrive. An
identity that names nobody by itself (a DID, a SPIFFE ID) gets an entry
named by a digest of its identifier.
Standard attributes
All the inetOrgPerson attributes (RFC 2798, RFC 4519) can be stored, and
/portal draws all fifty of them. This service reads and writes these:
| Attribute | Use |
|---|---|
uid, cn, sn, givenName, displayName, mail |
the person. In development mode an auto-created entry gets invented values; product mode invents none |
userPassword |
scrypt hash ($scrypt$N$r$p$salt$hash), hashed on every write including an LDAP modify. Withheld from reads in product mode |
userCertificate |
standard, but not what this service writes for a presented certificate (see x509*) |
title, employeeType, telephoneNumber, mobile, street, l, st, postalCode, c, postalAddress, o, ou, departmentNumber, employeeNumber, preferredLanguage, labeledURI, manager |
released as claims (oid4vc/vc_claims.ts) and mapped by SCIM (scim/scim_map.ts) |
schacDateOfBirth, schacCountryOfCitizenship, schacPersonalTitle |
SCHAC names, released as birthdate, nationalities, title |
salutation, birthFamilyName, birthGivenName, birthMiddleName, alsoKnownAs, placeOfBirthCountry, placeOfBirthRegion, placeOfBirthLocality |
this service’s own, for Identity Assurance claims |
scimExternalId |
SCIM externalId, on people and groups |
userPrincipalName |
read by certificate enrollment’s smartcard-logon profile. Nothing in the service writes it; a client supplies it |
pwdChangedTime, pwdHistory |
draft-behera password policy: when the password last changed, and earlier hashes for the history rule |
pwdReset |
draft-behera: the password must be changed at the next sign-in |
pwdAccountLockedTime |
draft-behera. The account is disabled while this is set (000001010000Z means an administrator disabled it), and every door refuses the person. common/account_state.ts is the only writer |
Credentials
| Attribute | What it holds | Protection |
|---|---|---|
stsWebauthnCredential |
registered WebAuthn public keys | public by design |
stsTotpCredential |
the RFC 6238 shared secret | sealed under the key-encryption key in product mode. Withheld from reads |
stsBackupCodes |
JSON: each code’s scrypt hash and usedAt, plus counts |
hashed per code (older sealed sets are still read). The comment in OWN_NAMES saying “encrypted” is out of date |
stsAppPassword |
JSON per app password: name, the doors it is scoped to, scrypt hash, last use (#101) | withheld |
stsActivationToken, stsActivationExpires |
an activation link’s hash and expiry | hashed |
stsPasswordResetToken, stsPasswordResetExpires |
a reset link’s hash and expiry | withheld |
stsKrb5Keys, stsKrb5KeyInfo |
Kerberos long-term keys for every enctype, derived from the password, and their public half (kvno, enctypes, when) | keys sealed and withheld, including the ciphertext |
hobaPublicKey |
RFC 7486 HOBA keys, <kid> <base64 DER> |
public |
stsSelfIssuedSubject |
SIOPv2 subjects the person enrolled (a DID or a JWK thumbprint URI), as JSON (#129) | withheld |
stsCibaUserCode |
the CIBA user code the person set on /portal/ciba (#131) |
scrypt-hashed, withheld |
stsAudSub |
the account id a client knows the person by, <client_id> <aud_sub> per value, sent as that client’s ID Token aud_sub (#148) |
plain |
stsClaimSourceTokens |
the person’s access and refresh tokens at each Claims Provider they linked on /portal/claim-sources, one JSON value (#147) |
sealed where keys persist, withheld |
Assertion key pairs (RFC 7523 and RFC 7522)
A person may hold a key pair to sign assertions about themselves only. The two sets share no names.
| JWT (RFC 7523) | SAML (RFC 7522) |
|---|---|
stsAssertionIssuer |
stsSamlAssertionIssuer |
stsAssertionJwks, stsAssertionKid |
stsSamlAssertionThumbprint |
stsAssertionCertificate, stsAssertionCertificateChain |
stsSamlAssertionCertificate, stsSamlAssertionCertificateChain |
stsAssertionPrivateKey (sealed, withheld) |
stsSamlAssertionPrivateKey (sealed) |
stsAssertionExpiresAt, stsAssertionKeySource |
stsSamlAssertionExpiresAt, stsSamlAssertionKeySource |
…KeySource says whether the pair was issued here or a certificate was
uploaded instead.
Certificates issued by enrollment (ACME, EST, SCEP)
common/cert_enrollment.ts keeps every certificate on the entry it names.
| Attribute | Notes |
|---|---|
stsEnrolledCertificate |
public |
stsEnrolledPrivateKey |
present only when this service generated the key (EST /serverkeygen). Withheld |
stsAcmeEabKey |
the ACME External Account Binding key. Withheld |
stsScepChallenge |
a single-use SCEP challenge password. Withheld |
stsCertificateHostName |
host names an administrator registered for this entry |
What this service learned about the person
| Attribute | Meaning |
|---|---|
authnMethod, mfaAuthenticated, mfaLastAuthTime |
how they last authenticated, where the protocol said. mfaLastAuthTime is never cleared |
x509subject, x509issuer, x509serialNumber, x509notBefore, x509notAfter, x509fingerprint256 |
a verified client certificate, or an issued X509-SVID |
x509svidsIssued, x509firstIssued, x509lastIssued |
X509-SVIDs only: how many have been issued, and since when |
didSubject, didMethod |
the DID this entry is. The entry is found by didSubject |
spiffeSubject, spiffePath, spiffeTrustDomain |
the SPIFFE ID that authenticated. These are not the same as spiffeId on a registration entry |
spiffeCredentialStatus, spiffeCredentialStatusReason, spiffeRevokedAt |
whether this identity may still be issued an SVID. This is not a certificate status |
federationRelationship, federationIssuer, federationLastSeen |
where a federated person came from |
federationLink |
<relationship> <issuer> <subject>: the one that decides anything, linking a partner’s subject to this person (#109) |
federationAttribute |
which of the entry’s other attributes came from a partner’s assertion |
oauthConsent |
<time> <scope> <client_id>, one value per (application, scope) |
oauthConsentWithdrawn |
<time> <scope> <client_id> (#172). It stops a re-consent from reviving an older refresh token |
stsIdaVerification |
OpenID Connect for Identity Assurance verifications as JSON (#127). Withheld, because evidence carries document numbers |
Flags an administrator or a flow sets
| Attribute | Meaning |
|---|---|
stsMfaRequired |
a second factor is required of this person |
stsBootstrapAdministrator, stsConsoleClaimedAt |
the bootstrap admin, and when it first claimed the console |
stsNotDelegated |
sensitive and cannot be delegated (Kerberos’s NOT_DELEGATED) (#108) |
stsMayAct |
the one party this person named as a delegate. It becomes may_act in their access tokens |
stsMailVerified |
the address they proved they receive mail at (#63) |
stsMailVerifyToken, stsMailVerifyExpires, stsMailVerifyAddress |
a pending verification link. The token is withheld |
Groups: ou=groups
objectClass: top, groupOfNames, with cn, member (DNs) and description.
Referential integrity is not enforced: deleting a person leaves their DN in
member. A group grants nothing by being a group. It grants only what a
roster or a role names it for:
| Group | Grants |
|---|---|
cn=admin-read |
the console and /admin-api read role |
cn=admin-write |
the console and /admin-api write role |
cn=remote-peps |
REMOTE_PEPS: /xacml/pep/* and POST /xacml/pip |
cn=xacml-users |
XACML_USER: the XACML PDP and policy surfaces |
The console groups are created on first grant, and the bootstrap admin holds both. remote-peps and xacml-users are seeded, empty in product mode. In development, demo data adds cn=developers and cn=directory-admins (which grant nothing), the people alice, bob and carol, and cn=admin,<base>.
The names are the defaults of admin.readGroup, admin.writeGroup, roles.remotePepGroup and roles.xacmlUserGroup. The directory also treats groupOfUniqueNames, posixGroup and groupOfURLs entries as groups, and reads uniqueMember and memberUid as well as member.
Applications: ou=applications
objectClass: top, applicationProcess, stsApplication. One entry per client,
relying party, service provider, Kerberos service or GNAP client. Nothing
caches this registry: an ldapmodify takes effect on the next request.
common/applications.js holds the field table, and /admin/applications
draws it with a description of every field.
| Group | Attributes |
|---|---|
| Identity and observation | appIdentifier, cn, appName, description, appHomePageUrl, appKind, appProtocol, appAllowedProtocol, appAuthorizationServer, appCorsOrigin, appRegistered, appRegisteredBy, appFirstSeen, appLastSeen, appAuthentications, appSessions, appUsers, appLastSession, appLastUser, appRedirectUriObserved, appReturnAddressObserved, appRegistrationJson, appRegistrationAccessToken |
| OAuth client | oauthClientId, oauthConfidential, oauthClientSecret (withheld; one JSON record per secret, each with its own expiry, sealed at rest where keys persist), oauthTokenEndpointAuthMethod, oauthTokenEndpointAuthSigningAlg, oauthJwks, oauthJwksUri, oauthRedirectUri, oauthGrantType, oauthResponseType, oauthScope, oauthAllowedScope, oauthAudience, oauthSubjectType, oauthSectorIdentifierUri |
| Logout | oauthPostLogoutRedirectUri, oauthFrontchannelLogoutUri, oauthFrontchannelLogoutSessionRequired, oauthBackchannelLogoutUri, oauthBackchannelLogoutSessionRequired, oauthRevokeRefreshOnLogout |
| Requests and responses | oauthRequestUri, oauthRequestObjectSigningAlg, oauthRequestObjectEncryptionAlg, oauthRequestObjectEncryptionEnc, oauthRequireSignedRequestObject, oauthRequirePushedAuthorizationRequests, oauthIntrospectionSignedResponseAlg, oauthIntrospectionEncryptedResponseAlg, oauthIntrospectionEncryptedResponseEnc, oauthAuthorizationDetailsType, oauthAuthorizationDetailsTypes, oauthStepUpAcrValues, oauthStepUpMaxAge |
| Resource server and consent | oauthPermissionBaseUri, oauthPermission, oauthDelegatedPermission, oauthResourceMetadata, oauthResourceMetadataUrl, oauthGlobalConsent, oauthGlobalConsentWithdrawn |
| Token lifetimes | oauthAccessTokenTtlS, oauthIdTokenTtlS, oauthRefreshTokenTtlS, oauthRefreshIdleSeconds, oauthTokenExchangeRefreshToken |
| Mutual TLS (RFC 8705) | oauthTlsClientAuthSubjectDn, oauthTlsClientAuthSanDns, oauthTlsClientAuthSanUri, oauthTlsClientAuthSanIp, oauthTlsClientAuthSanEmail, oauthTlsClientCertificateBoundAccessTokens, oauthTlsClientCertificateThumbprint |
| JWT assertion key pair (RFC 7523) | oauthAssertionIssuer, oauthAssertionJwks, oauthAssertionKid, oauthAssertionCertificate, oauthAssertionCertificateChain, oauthAssertionPrivateKey (withheld), oauthAssertionExpiresAt, oauthAssertionKeySource |
| SAML assertion key pair (RFC 7522) | oauthSamlAssertionIssuer, oauthSamlAssertionSigningCertificate, oauthSamlAssertionCertificate, oauthSamlAssertionCertificateChain, oauthSamlAssertionPrivateKey, oauthSamlAssertionThumbprint, oauthSamlAssertionExpiresAt, oauthSamlAssertionKeySource |
| Software statements (RFC 7591) | oauthSoftwareStatementIssuer, oauthIssuedSoftwareStatement, appSoftwareStatementIssuer, appSoftwareStatementTrusted, appSoftwareStatementPublisher |
| OpenID Provider Commands (#151) | oauthCommandEndpoint (https, no fragment: where a Command Token is POSTed) |
| CIBA (#131) | oauthBackchannelTokenDeliveryMode (poll, ping or push), oauthBackchannelClientNotificationEndpoint (https, for ping and push), oauthBackchannelAuthenticationRequestSigningAlg (an asymmetric alg: every request signed), oauthBackchannelUserCodeParameter (TRUE: every request carries the person’s user code) |
| Native SSO (#130) | oauthNativeSso (TRUE lets the client ask for device_sso), oauthNativeSsoGroup (the apps that may share one device session). Settable by a registration only through a trusted software statement |
| SAML service provider | samlEntityId, samlAssertionConsumerService, samlSingleLogoutService, samlNameIdFormat, samlResponseBinding, samlSigningCertificate, samlObservedSigningCertificate, samlEncryptionCertificate, samlAuthnRequestVerification, samlAuthnRequestSigned, and from consumed metadata: samlSpMetadataUrl, samlSpMetadata, samlAcsEndpoint, samlSloEndpoint, samlSpNameIdFormat, samlSpAuthnRequestsSigned, samlSpWantAssertionsSigned, samlSpWantAssertionsEncrypted, samlSpMetadataValidUntil, samlSpMetadataCacheDuration, samlSpMetadataConsumedAt, samlSpMetadataSignature, samlSpMetadataSigningCertificate |
| SAML per-profile | saml2AssertionLifetimeMin, saml2SignAssertion, saml2SignResponse, saml2NameIdFormat, saml2ArtifactTtlS, saml2EncryptAssertion, saml2EncryptionAlgorithm, saml2KeyTransportAlgorithm, saml2EncryptLogoutNameId, saml11AssertionLifetimeMin, saml11SignAssertion, saml11SignResponse, saml11NameIdFormat, saml11ArtifactTtlS |
| WS-* | wsfedRealm, wsfedReplyUrl, wsfedSignOutUri, wsfedAssertionLifetimeMin, wstrustAppliesTo |
| Kerberos service | krb5ServicePrincipalName, krb5ServiceKeys (sealed), krb5ServiceKeyInfo |
| Delegation (#108, #186; Delegation and impersonation) | appAllowedToDelegateTo, appAllowedToActOnBehalfOf, appDelegationSemantics, appDefaultDelegationSemantics, appDelegationSubjectGroup, appNotDelegated, appMayAct, and for a Kerberos service krb5TrustedForDelegation |
| Roles and claims | appRequiredRole, appGroupsClaim, appGroupsClaimName, appGroupsClaimValue, appGroupsClaimFromMemberOf, appAuthnMechanism |
| Certificate enrollment | appEnrolledCertificate, appEnrolledPrivateKey, appAcmeEabKey, appScepChallenge, appCertificateHostName |
| GNAP | gnapInstanceId, gnapClassId, gnapKey, gnapKeyIdentity, gnapKeyReference, gnapKeyProof, gnapMtlsTrust, gnapSymmetricKey (withheld), gnapSymmetricAlg, gnapDisplayUri, gnapLogoUri, gnapFinishUri, gnapInteractionStartModes, gnapAllowedAccess, gnapBearerTokens, gnapSkipInteraction, gnapAccessTokenFormat, gnapAccessTokenLifetimeS, gnapResourceServerUri, gnapOwnerLookupUri, gnapJweKey, gnapMacaroonKey, gnapScopedSignals |
| Links to other registries | oid4vpClientId, federationPartnerId, appFederationRelationship, appFederationAutoRedirect, ldapBindDn, scimClientId, ssfReceiverId, ssfDeliveryEndpoint, ssfAllowedEvents, spiffeWorkloadId |
Federation relationships: ou=federations
objectClass: top, applicationProcess, stsFederation. Each relationship is
created disabled, and changing one is a security change: see
federation. federation/federation.js holds the schema, and
/admin/ldap/federations publishes it.
| Group | Attributes |
|---|---|
| Identity | fedId, fedName, fedEnabled, fedProtocol, fedRole, fedPeer, fedLocalEntityId, fedApplication, fedApplicationUse |
| Endpoints | fedSsoUrl, fedSloUrl, fedSloBinding, fedBinding, fedTokenUrl, fedUserinfoUrl, fedEndSessionUrl, fedJwksUri |
| Keys | fedSigningCertificate (decides whose assertions are believed), fedJwks, fedEncryptionKey (withheld), fedEncryptionKeyType, fedKeyManagementAlgorithm, fedContentEncryptionAlgorithm, fedSignRequest |
| OIDC as client | fedClientId, fedClientSecret (withheld), fedScope, fedResponseType |
| Policy | fedAllowUnsolicited, fedAllowUnencrypted, fedAcceptSignout, fedRequireSignedLogout, fedMayAssertAdministrators, fedAuthnMechanism, fedAuthnRelationship |
| Subjects and provisioning | fedSubjectPolicy, fedSubjectPattern, fedSubjectDomain, fedSubjectGroup, fedHomeRealmDomain (#148, the domains whose domain_hint goes to this partner), fedUsernameSource, fedAutocreateUsers, fedUpdateUserAttributes, fedAttributeMap, fedRelease |
| Observation | fedFirstSeen, fedLastSeen, fedLastUser, fedUsers, fedAuthentications, fedLastError, fedLastErrorAt |
Attribute sources: ou=attributesources
objectClass: top, stsAttributeSource, named cn=<source id>, with the source’s whole definition as
one JSON value, stsAttributeSourceData. It holds the database, the row it reads, the column map, when it reads
and what a failure does, and never a password (#94). On a person, stsAttributeSourced names each attribute a
source wrote (<source>:<attribute>), and stsAttributeSourceSeen records when each source last read them
(<source>=<time>). See Attribute sources.
Roles: ou=roles
objectClass: top, stsRole, named cn=<role>, with roleName, description, the three kinds of
holder (roleMemberUser, roleMemberGroup and roleMemberApplication), and rolePermission: the permissions a
holder may be issued (#303), named as a client asks for them, and roleApplication: the one application a role
belongs to (#310). Such a role is named <role>@<application>, and only a token for that application carries it,
as <role>. roleAllowedMemberType (user, application, or both when absent) says who may hold the
role, and a member of another kind is refused (#93); the two console roles cannot be restricted. displayName
is an optional label for the console and the API, and is never what a token carries. The entry’s entryUUID
is the role’s stable id. A role saved by an earlier version was written with
no object class and gains one on its next save.
common/roles.js keeps apart who holds a role and what requires one
(appRequiredRole on an application). Which of an application’s permissions need a role at all is
oauthRoleGatedPermission on the resource application. Every realm is seeded with two roles, ADMIN_READ and
ADMIN_WRITE, which authorize admin:read and admin:write. Their groups come from admin.readGroup and
admin.writeGroup and are not stored on the entry; sts-management-api is a member of both. A third seeded role,
DEVICE_COMPLIANCE, authorizes device:compliance and has no members until the MDM feed’s application is added
(#309). None of the three can be deleted, and their permissions are fixed. The service also computes eight
built-in roles that are not stored: EVERYBODY, ALL_AUTHENTICATED_USERS, ALL_UNAUTHENTICATED_USERS,
ALL_APPLICATIONS, ALL_AUTHENTICATED_APPLICATIONS, ALL_UNAUTHENTICATED_APPLICATIONS, REMOTE_PEPS and XACML_USER.
XACML: ou=policies and ou=peps
ou=policies is the policy repository itself, not a copy of it
(xacml/xacml_store.ts). objectClass: top, xacmlPolicy, with
xacmlPolicyId, xacmlVersion, xacmlKind (policy or policy set),
xacmlCombiningAlgId, xacmlPolicyDocument (the XACML XML), xacmlPolicy,
xacmlEnabled, xacmlIsRoot and xacmlDetail.
ou=peps holds the remote PEPs (xacml/xacml_pep_registry.ts).
objectClass: top, xacmlPep, with xacmlPepIdentity, xacmlPepEnabled,
xacmlPepCertificateSubject, xacmlPepThumbprint, xacmlPepBias,
xacmlPepResource, xacmlPepNotifyUrl, xacmlPepVersion,
xacmlPepRegisteredAt, xacmlPepLastSeen, xacmlPepLastNotify,
xacmlPepSyncToken, xacmlPepPolicyCount, and the counters
xacmlPepDecisions, xacmlPepAllowed, xacmlPepRefused,
xacmlPepAuthenticated and xacmlPepUndischargeable. See
remote PEP.
Password policies: ou=passwordPolicies
objectClass: top, pwdPolicy, stsPasswordPolicy. The draft-behera attributes
pwdAttribute, pwdMinLength and pwdInHistory sit beside this service’s
stsPwd* attributes (stsPwdRequireUppercase, stsPwdRequireDigit,
stsPwdMinSymbols, stsPwdGeneratedLength and others), which cover the
composition rules and the generator. The default profile is not seeded:
the defaults apply until an administrator saves one. See common/password_policy.ts.
SPIFFE: ou=spiffe
The registry is kept in the directory (spiffe/spiffe_registry.ts).
Registration entries (ou=entries, objectClass: top, applicationProcess, spiffeRegistrationEntry):
spiffeEntryId, spiffeId, spiffeParentId, spiffeSelector,
spiffeX509SvidTtl, spiffeJwtSvidTtl, spiffeDnsName, spiffeFederatesWith,
spiffeAdmin, spiffeDownstream, spiffeHint, spiffeStoreSvid,
spiffeEntryExpiresAt, spiffeRevisionNumber, spiffeCreatedAt,
spiffeOrigin, spiffeSvidsIssued, spiffeLastSvidAt.
Attested agents (ou=agents, cn=agent-<12 hex>, objectClass: top, applicationProcess, spiffeAgent): spiffeAgentId,
spiffeAttestationType, spiffeAgentSelector, spiffeAgentSvidHash,
spiffeAgentExpiresAt, spiffeAgentBanned, spiffeAgentCanReattest,
spiffeAttestations, spiffeFirstSeen, spiffeLastSeen.
Deleting an entry, or banning or deleting an agent, sets
spiffeCredentialStatus on the affected people in ou=users.
Devices: ou=devices (#130, #164)
Every phone, computer and host the realm knows, each owned by ONE person or
ONE application. Made by OpenID Connect Native SSO for Mobile Apps 1.0
(OAuth 2.0 and OpenID Connect) and by an administrator on
Directory → Devices or POST /admin-api/devices/create; see
Devices. People see theirs on /portal/devices;
/admin/ldap/devices shows the entries as the directory holds them, with this
schema.
The DN is cn=<uuid>,ou=devices,<base>, and common/devices.ts owns what an
entry means.
| Attribute | Meaning |
|---|---|
objectClass |
top, device (RFC 4519 §3.4), stsDevice |
cn |
the device id, a UUID this service assigned |
owner |
the DN of its one owner: a person or an application (RFC 4519) |
stsDeviceOwnerKind |
person or application |
description |
what to call it on a page |
stsDeviceApplication |
the DN of every application that has used it |
stsDeviceKey |
one JSON value per key it is recognised by: id, kind (x509, jwk, webauthn), thumbprint, label, added, addedBy, proof, attestation (level, format, summary, verifiedAt) and the public material. Never a secret |
stsDeviceKeyThumbprint |
<kind>:<thumbprint> per key, derived from stsDeviceKey so a filter can find a device by its key. A thumbprint is base64url SHA-256: RFC 7638 for a JWK (DPoP’s jkt) and a WebAuthn credential’s key, the SubjectPublicKeyInfo for a certificate |
stsDeviceAttestation |
attested when any key’s attestation was verified, otherwise self-asserted. Derived |
stsDeviceCompliance |
compliant or not-compliant; absent is unknown |
stsDeviceComplianceChange |
JSON: the last change — status, previous, at, source (admin, mdm, test-control, caep), actor, reason |
stsDeviceStatus |
compromised; absent is active |
stsDeviceStatusChange |
JSON: the last status change |
stsDevicePlatform |
ios, ipados, android, macos, windows, linux, chromeos or other |
stsDeviceModel, stsDeviceOs |
descriptive text |
stsDeviceEnrolment |
JSON: method (native-sso, admin, portal, est, scep), at, actor |
stsDeviceSecretHash |
SHA-256 of its Native SSO device_secret. Withheld from every read |
stsDeviceSession |
the sign-on session the secret is good for. The secret is accepted only while that session is live |
stsDeviceLastUsed |
ISO 8601 |
The secret is never rotated, because every app on the device shares it. A new
sign-in that presents the secret re-binds the same device to the new session;
a device given to another owner loses it. One person holds at most
devices.maxPerPerson devices: at that limit a Native SSO sign-in replaces the
least recently used device whose session has ended (or, if none has ended, the
least recently used one), and an administrator’s registration is refused.
OpenID Federation: ou=oidfed (#132, #136, #137)
The realm’s register as an OpenID Federation entity
(OpenID Federation). Every entry is of class stsOidfedEntry
and is named cn=<prefix><digest>, where the digest is SHA-256 of what the
entry is about (hex, 32 characters). oidfed/oidfed_store.ts owns what an
entry means.
| Attribute | Meaning |
|---|---|
objectClass |
top, stsOidfedEntry |
stsOidfedKind |
keys, subordinate (sub-), anchor (ta-), mark-type (mt-), issued-mark (im-), held-mark (hm-), mark-policy (mp-), events (ev-, #137), suspension (su-, #137) or collection (the one cn=collection entry, #136) |
stsOidfedEntityId |
the Entity Identifier the entry is about |
stsOidfedData |
the record, as one JSON value: a subordinate’s keys, metadata, metadata policy and constraints; an anchor’s pinned keys; a mark and its status; a suspension’s time, reason and page; the last Entity Collection crawl. An events or suspension entry about a realm of this service is keyed realm:<id> rather than by identifier |
stsOidfedEvent |
on an events entry: one subordinate’s history, one JSON event per value (iat, event, and event_description and information_uri where given). Appended and never rewritten, and merged by value when two nodes write at once. The entry outlives the subordinate |
stsOidfedKeys |
on the one cn=keys entry: the realm’s Federation Entity Key table, one JSON row per value, each private key sealed where keys persist. Withheld from every read, and each row’s private key is replaced by a placeholder in searches and in the directory dump |
Claims Providers: ou=claimproviders (#147)
The OpenID Providers this realm fetches aggregated or distributed claims from
(OpenID Connect). One entry per provider, named cn=<id>;
oauth-oidc/claims_providers.ts owns what an entry means.
| Attribute | Meaning |
|---|---|
objectClass |
top, stsClaimProvider |
stsClaimProviderData |
the provider, as one JSON value: its id and name, issuer, authorization, token, claims and JWKS endpoints, this realm’s client id and authentication method there, the scope asked for, the claims it supplies, and aggregated or distributed |
stsClaimProviderSecret |
this realm’s client secret at the provider. Sealed where keys persist, and withheld from every read |
Trust anchors and CRLs
ou=trustAnchors exists only in the default realm, with one
objectClass: top, stsTrustAnchor entry per CA trusted for client
certificates: stsTrustAnchor, stsTrustAnchorCertificate,
stsTrustAnchorFingerprint, stsTrustAnchorAddedBy. A certificate is public,
so none of these is sealed. An entry here decides whose client certificate
becomes an identity. See TLS.
ou=crl holds objectClass: top, cRLDistributionPoint entries (cn=<caId>, certificateRevocationList;binary) once a CRL is published. They are a cache of the register, readable anonymously (PKI).
What an LDAP read never returns
In product mode (mode.withholdsDirectorySecrets()), these attributes are
withheld from every search result, filter and compare, administrators
included (SECRET_ATTRIBUTES in ldap/ldap_server.js):
- passwords:
userPassword,pwdHistory,stsAppPassword,stsActivationToken,stsPasswordResetToken,stsMailVerifyToken - second factors and subjects:
stsTotpCredential,stsBackupCodes,stsSelfIssuedSubject,stsIdaVerification,stsCibaUserCode - devices:
stsDeviceSecretHash - client secrets:
oauthClientSecret,appRegistrationAccessToken,fedClientSecret,stsClaimProviderSecret - tokens held for a person at another provider:
stsClaimSourceTokens - private keys:
oauthAssertionPrivateKey,oauthSamlAssertionPrivateKey,stsAssertionPrivateKey,stsSamlAssertionPrivateKey,stsEnrolledPrivateKey,appEnrolledPrivateKey,fedEncryptionKey - enrollment credentials:
stsAcmeEabKey,appAcmeEabKey,stsScepChallenge,appScepChallenge - Kerberos and GNAP keys:
stsKrb5Keys,krb5ServiceKeys,gnapSymmetricKey,gnapMacaroonKey - devices:
stsDeviceSecretHash(from #130) - the Federation Entity Key table:
stsOidfedKeys(from #132)
Some are withheld in every mode, replaced by a placeholder in searches and
in the directory dump: the certificate-enrollment secrets, the Kerberos keys,
and the private key inside each fedEncryptionKey row and each
stsOidfedKeys row.
Hashed (scrypt): userPassword, pwdHistory, the activation, reset and
mail-verification tokens, each recovery code and each app password.
stsScepChallenge holds a SHA-256 digest. Sealed ($aesgcm$1$… under the
key-encryption key, in product; clear in development): the TOTP secret,
stsKrb5Keys, krb5ServiceKeys, every RFC 7523 and RFC 7522 private key, the
EAB keys, enrolled private keys and fedEncryptionKey private keys.
oauthClientSecret, appRegistrationAccessToken, fedClientSecret and
stsIdaVerification are sealed too, but only where the key-encryption key is
durable (not development’s ephemeral one, and not a product-mode realm inside
a development process).
Encryption at rest has the rest.
pwdHistory and pwdChangedTime are maintained by the service and refused on
an LDAP write. Product refuses a pre-hashed $scrypt$ userPassword.
In product mode, what a bound identity may read and write is decided per identity, and only a person may bind: see LDAP → What a bound identity may read.
Keeping this page current
This page is a copy of tables that live in code, so it can drift. The sources:
ldap/ldap_server.js:STANDARD_NAMES,OWN_NAMES,SECRET_ATTRIBUTES, andseed()for the containerscommon/applications.js: the application field tablefederation/federation.js,spiffe/spiffe_registry.ts,xacml/xacml_store.ts,xacml/xacml_pep_registry.ts,common/roles.js,common/password_policy.ts,common/devices.ts
A running service describes its own containers: /admin/ldap/directory shows
any entry, and /admin/ldap/federations shows the federation schema.
Related
- LDAP: the protocol, the sockets, the five rules and the settings.
- PostgreSQL schema: where these entries are stored.
- SCIM 2.0: how SCIM attributes map onto these.