Unmasking UniFi Identity: The Hidden Architecture of Credential Management
⚠️ Nerd warning — This article is intended for security researchers, sysadmins, reverse engineers, identity-management enthusiasts, and other technically curious humans. If PostgreSQL, UUIDs, API traces, log analysis, and credential synchronization sound fun rather than frightening, you're in the right place.
This is Part 1 of an ongoing investigation. Part 2 will focus on LDAP integration, identity synchronization behavior, source attribution, and what changes when users originate from OpenLDAP instead of Site Manager.
For a long time, the UniFi ecosystem felt like a black box. You click a button in the UI, a user appears, and magic happens. But as any sysadmin who has spent a late night staring at a blinking UDM Pro knows, "magic" is just engineering we haven't reverse-engineered yet.
Recently, I decided to pull the curtain back on UniFi Identity and its local soul, the UniFi Credential Server. I wanted to know where the bodies—err, credentials—are buried. Is the UDM just a dumb terminal for the cloud, or is it a local identity powerhouse?
Pull up a chair, grab a coffee, and let's look at the logs.
The crime scene: what databases exist on the UDM?
If you go hunting for classic SQLite files on a modern UDM SE (v5.1.15), you'll find plenty of small stuff, but the heavy lifting isn't happening there.
Fact: UniFi Identity/Credential Server is powered by PostgreSQL, not SQLite.
The heart of the operation is the unifi-credential-server service, which runs as a dedicated process. It manages its own set of databases within the Postgres instance.
Observation: By poking around the service configuration, we can see it's not just one monolith. The environment variables point to several data and workspace directories, including /data/unifi-credential-server and /data/ucs-user-assets.
The technical evidence: service discovery
A quick ps and systemctl reveal the engine under the hood:
# The service definition
ExecStart=/usr/lib/unifi-credential-server/scripts/service/start.sh
# The running app
/usr/sbin/unifi-credential-server-app --prop /usr/lib/unifi-credential-server/config.props
The users: what is stored locally?
When you create a user in the UniFi Site Manager, they don't just exist in the "cloud." They are meticulously synchronized down to your local iron.
Fact: The UDM stores comprehensive user data in two primary tables: user and user_profile.
Observation: A peek at the schema for public.user_profile shows a fascinating mix of standard fields and encrypted "blobs."
Key table: user_profile
| Column | Type | Description |
|---|---|---|
user_id | character varying(255) | The primary key, often a UUID. |
first_name | bytea | Encrypted binary data. |
email | bytea | Encrypted binary data. |
source | smallint | Crucial discovery: values observed are 0 and 4. |
pm_id | character varying(36) | A unique identifier, likely linked to Profile Manager or Site Manager. |
Hypothesis: The bytea columns (like email, first_name, and last_name) suggest that UniFi is using local encryption to protect PII (Personally Identifiable Information) even from someone with root access to the database—unless you also have the keys stored in the unifi-credential-server.vlt vault.
The cache: what secrets does the UDM keep?
The UDM isn't just caching usernames; it's a full-blown local credential vault.
Fact: The user_profile table contains columns for ecdsa_private_key, ecdsa_public_key, and token.
Technical evidence: Running a count on a test system with 90 profiles reveals:
- 87 profiles have an email blob.
- 26 profiles have local
ecdsa_private_keyandpublic_keypairs. - 37 profiles have a token blob.
Observation: The existence of local private keys suggests that the UDM can perform cryptographic operations (like signing requests or authenticating VPN/Door sessions) locally without phoning home to UI.com for every single packet.
The "source" mystery
In our investigation, we found a distinct split in where users come from:
- Source 0: 18 profiles (only 4 had local ECDSA keys).
- Source 4: 72 profiles (22 had local ECDSA keys).
Hypothesis: Source 0 likely represents local-only or legacy users, while source 4 represents users synchronized from the UniFi Cloud/Site Manager.
The onboarding workflow: API tracing
Watching the credential.log is like watching a detective's wiretap. When a user is onboarded, a very specific dance occurs between the cloud and the local server.
Onboarding log excerpts
// An invitation is created
{"level":"info","message":"OnInvitationCreated: user_id: 2a86***a0ef", ...}
// The credential is sent via email (triggered by the UDM/Credential Server)
{"level":"info","message":"OnCredentialSendViaEmail: user_id: 545f***99b4", ...}
// The user accepts, and the credential is "imported" locally
{"level":"info","message":"OnCredentialImported: user_id: 545f***99b4", ...}
Key findings on workflow:
- Invitation: The process often starts with
OnInvitationCreated. - Cloud link: The system tracks
ubnt_sso_idandubnt_sso_accountto bridge the local record with the Ubiquiti SSO world. - Policy sync: We see events for policy assignment and role assignment following the import.
- Agent sync: The
agent.logandsync.logshow constant heartbeat-style updates to ensure local and cloud states match.
Conclusions
Our investigation clearly shows that the UDM is far from a "dumb gateway."
- Local storage is real: Users, policies, and metadata live in a local PostgreSQL database.
- Hybrid identity: While credentials like ECDSA keys are stored locally, they are linked to cloud-linked identity attributes (
ubnt_sso_id,pm_id). - Evidence of local auth: The presence of
radius_usernameandecdsa_private_keyblobs provides the evidence showing that the UDM is prepared to handle authentication events locally once the initial cloud-linked handshake is complete.
Questions still under investigation
The detective work is never done. We're still chewing on these:
- The source code: What exactly triggers the transition from
source=0tosource=4? - LDAP behavior: How does the database schema change when users originate from OpenLDAP instead of Site Manager?
- Synchronization latency: What is the "local caching vs external reference" timeout? If the internet goes dark, how long until those local keys expire?
Coming in Part 2: LDAP, identity sources, and how deep the rabbit hole goes
Next time, we're going to break things. We'll be spinning up an OpenLDAP instance, pointing our UDM at it, and watching the PostgreSQL tables in real-time. We'll compare how directory-backed users differ from cloud-synced users and discover just how much of your "local" identity is actually up in the air.
Stay curious.