◀── Back to Logs
MAY 2026 [SYSTEMS] 15 min read #M365#NORDVPN#CONDITIONAL ACCESS#IDENTITY#AI AGENT#HEADLESS#ENTRA ID

LOG 010.3: Setting Up a Dedicated M365 Identity for Nine

LOG 010.3: Setting Up a Dedicated M365 Identity for Nine

This is Part 3 of the Nine / 9OS series. Part 1 covered standardizing OpenClaw as a headless systemd service on a minimized Ubuntu Server. Part 2 covered connecting it to Microsoft Teams. This entry builds the identity layer — the M365 account, the dedicated static IP, and the access controls that lock Nine to exactly one machine.

Series hub: LOG 010: Building Nine — A Headless AI Agent Operating Layer for Paragon9


01. THE SYSTEMS SOURCE: THE IDENTITY LAYER

Before Nine can operate inside Paragon9's M365 environment, Nine needs an identity in that environment. Not a service account. Not a shared mailbox. A licensed Microsoft 365 user with a real mailbox, real SharePoint access, and real Teams presence.

This matters for reasons that go beyond convenience.

M365's permission model becomes your control plane. When the agent's access is defined at the user level, it is auditable, revocable, and battle-tested by Microsoft's security architecture. The Unified Audit Log captures every action the account takes. Conditional Access policies enforce exactly which machine can authenticate as this identity. You restrict what Nine can do by restricting what the user can do — no custom security framework required, no trust placed in the agent software itself.

The security model doesn't rely on trusting the agent. It relies on the M365 permission boundary. That is the right architecture.

The enforcement point for all of this is a fixed IP address. Nine's M365 account is locked via Conditional Access to a single dedicated static IP — provided by NordVPN Business — so that even if someone obtained the credentials, they could not authenticate from anywhere other than the OpenClaw machine. Getting that VPN locked in and stable before touching M365 is the correct order of operations.

The Insight: Don't build security around trusting your tooling. Build it around boundaries your infrastructure already enforces. M365's Conditional Access, Exchange transport rules, and the Unified Audit Log give you more control over an AI agent than any custom permission layer you could write yourself.


02. THE BUILD SOURCE: THE DEPLOYMENT ROADMAP

Prerequisites


Phase 1: Install NordVPN on the OpenClaw Machine

The OpenClaw machine needs a static public IP so Conditional Access has a fixed, predictable enforcement point. NordVPN Business with a dedicated IP handles this cleanly — the VPN travels with the machine regardless of your ISP's dynamic lease assignments.

SSH into the OpenClaw machine and install:

sh <(curl -sSf https://downloads.nordcdn.com/apps/linux/install.sh)

Verify:

nordvpn --version

⚠️ Gotcha #1 — Permission denied on first login

When you run nordvpn login you'll immediately get a permission denied error. Run these in sequence — the reboot is required, not optional:

sudo groupadd nordvpn
sudo usermod -aG nordvpn $USER
sudo reboot

Group membership doesn't take effect until the session restarts.

⚠️ Gotcha #2 — Browser auth doesn't work on headless servers

After rebooting, run nordvpn login. It returns a URL. You authenticate in a browser — but clicking Continue tries to open the NordVPN desktop app, which doesn't exist on a headless server.

Use a token instead. In your NordVPN account portal at https://my.nordaccount.com, navigate to Services → NordVPN → Access Tokens, generate a token, and on the OpenClaw machine:

nordvpn login --token <your-token-here>

Phase 2: Connect to Your Dedicated IP Server

Your NordVPN Business portal shows your dedicated IP and the server name associated with it. For example:

Connect using the short server name — not the full hostname:

nordvpn connect us12345

(Replace us12345 with your actual server name from the portal. Use the short form only — us12345, not us12345.nordvpn.com or the full hostname. Replace 40.82.255.130 with your actual static IP.)

⚠️ Gotcha #3 — VPN kills your SSH session on connect

When NordVPN connects, it takes over the network interface. Your existing SSH session was mid-flight on the old route and gets cut immediately:

client_loop: send disconnect: Connection reset

This is expected behavior — not a failure. The machine is up and VPN is connected. Reconnect via SSH.

⚠️ Gotcha #4 — VPN blocks LAN SSH access after reconnect

If SSH times out completely after reconnecting, the VPN killswitch is blocking local LAN traffic. Get physical access to the machine and run these in this exact order — sequence matters:

nordvpn set lan-discovery disable
nordvpn whitelist add subnet 192.168.0.0/24
nordvpn connect us12345

Running whitelist add subnet before lan-discovery disable produces: AllowListing a private subnet is not available while local network discovery is turned on.

(Replace us12345 with your server name. Replace 192.168.0.0 with your local subnet.)


Phase 3: Verify the Dedicated IP and Lock Down Settings

Confirm your exit IP is your dedicated static IP:

curl ifconfig.me

Note: nordvpn status may show a different IP — that's the server's shared IP, not your dedicated exit IP. curl ifconfig.me is the ground truth.

Set auto-connect and killswitch so these survive reboots:

nordvpn set autoconnect on us12345
nordvpn set killswitch on

Verify settings:

nordvpn settings

Confirm all three are present:

⚠️ Gotcha #5 — SSH port forwarding breaks after killswitch is enabled

With killswitch on, this standard command:

ssh -L 18789:127.0.0.1:18789 [email protected]

Fails with bind [::1]:18789: Cannot assign requested address. The killswitch blocks IPv6 loopback. Using -4 to force IPv4 gives Address already in use. The fix is using localhost instead of 127.0.0.1:

ssh -L 18789:localhost:18789 [email protected]

Phase 4: Reboot Test

Before touching M365, verify auto-connect survives a full reboot. If the VPN isn't up after restart, Conditional Access will lock out the agent account every time the machine restarts.

sudo reboot

Reconnect via SSH and confirm:

nordvpn status
curl ifconfig.me

Both should show connected and your dedicated static IP — no manual intervention required.


Phase 5: Create the Agent M365 Account

In the Microsoft 365 Admin Center (https://admin.microsoft.com):

Navigate to Users → Active Users → Add a user

Account details:

On display name: The account's display name appears in Teams conversations, SharePoint activity logs, email draft headers, and the Unified Audit Log. A single character like 9 is cryptic in audit logs and team conversations. Make it immediately legible to anyone in your tenant.

On the username convention: [email protected] is the primary agent identity. Per-principal scoped identities in later phases follow the pattern 9.[name]@paragon9.com. The 9 prefix is immediately recognizable as an agent identity — no ambiguity about what kind of account this is when you're reading a log at 11pm.

After creation, verify:


Phase 6: Create a Named Location in Entra

Navigate to https://entra.microsoft.com:

Protection → Conditional Access → Named Locations → + IP ranges location

The /32 means exactly that one IP address — no range, no wiggle room.

(Replace 40.82.255.130 with your static IP.)


Phase 7: Create the Conditional Access Policy

Protection → Conditional Access → Policies → + New policy

Name: Restrict [email protected] to OpenClaw IP

Assignments — Users:

Target resources:

Conditions → Locations:

Grant:

Enable policy: On

Critical — the logic here matters: Include ANY location, Exclude your trusted IP means: block access from everywhere except the OpenClaw dedicated IP. If you reverse Include/Exclude, you get a completely different and weaker effect. The logic reads as a blocklist, not an allowlist — you're blocking everything, then carving out an exception.

Testing the policy:

Test 1 — Verify OpenClaw can reach M365:

curl -s https://login.microsoftonline.com/paragon9.com/.well-known/openid-configuration | head -5

Should return JSON cleanly.

Test 2 — Verify block from other locations: On any other machine, open a private browser window and attempt to sign in to https://myapps.microsoft.com as [email protected]. Should receive an access blocked error.


Phase 8: Exchange Transport Rule

This prevents [email protected] from sending email outside the organization. Nine can create drafts, send internally, and post to Teams — it cannot fire anything external without your explicit review.

Navigate to https://admin.exchange.microsoft.com:

Mail flow → Rules → + Add a rule → Create a new rule

Name: Block [email protected] External Outbound

Conditions (AND logic — both must be true):

Action:

Settings:

Note: The rule is created in a disabled state. Go back to the Rules page and enable it immediately after creation.


03. THE LEGACY SOURCE: THE IDENTITY LAYER IS COMPLETE

At this point, the machine Nine runs on has a fixed, verifiable identity on the public internet. M365 knows the IP. Conditional Access enforces it. The Exchange transport rule closes the one outbound channel that could bypass review.

The stack at this phase:

 ✔ OpenClaw machine locked to dedicated static IP via NordVPN
 ✔ Auto-connect and killswitch configured and reboot-verified
 ✔ LAN SSH access preserved via subnet allowlist
 ✔ [email protected] created with Business Premium license
 ✔ Conditional Access policy blocking all auth except from the dedicated IP
 ✔ Exchange transport rule blocking all external outbound email

The identity layer isn't just a security requirement. It's a declaration of scope. Nine operates from one machine, through one account, under one set of permissions that you control entirely through infrastructure you already own. That clarity — knowing exactly where an agent can operate from and what it is categorically prevented from doing — is what makes autonomous operation trustworthy.

The Lesson: An AI agent without a boundary is a liability. An AI agent with a defined identity, a fixed access point, and logged audit trails is an asset. The infrastructure difference between those two things is what this phase builds.


[SYSTEM NOTES]

Stay Resilient.

— Greg Scott Kirk (Tek)


Part 3 of the Nine / 9OS series.
← Previous: LOG 010.2: Connecting OpenClaw to Microsoft Teams on a Headless Linux Server
→ Next: LOG 010.4: Building the SharePoint 9OS Command Deck and Teams Channels

// NEXT ACTION

This is the architecture Paragon9 installs for mid-market firms — modernized, documented, and built to run without its operator. If you want it running in your business, the conversation is free.