← Back to Writeups

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

ColumnTypeDescription
user_idcharacter varying(255)The primary key, often a UUID.
first_namebyteaEncrypted binary data.
emailbyteaEncrypted binary data.
sourcesmallintCrucial discovery: values observed are 0 and 4.
pm_idcharacter 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_key and public_key pairs.
  • 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:

  1. Invitation: The process often starts with OnInvitationCreated.
  2. Cloud link: The system tracks ubnt_sso_id and ubnt_sso_account to bridge the local record with the Ubiquiti SSO world.
  3. Policy sync: We see events for policy assignment and role assignment following the import.
  4. Agent sync: The agent.log and sync.log show 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_username and ecdsa_private_key blobs 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=0 to source=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.