# Service Account Guide
# Cold Bore Ballistics LLC — Internal Reference
# Classification: Confidential
# Last Updated: 2026-06-07

---

## 1. What Is a Service Account

A service account at CBB is an **Entra ID app registration** paired with its auto-created
service principal. It is the identity used by automated processes — scripts, CI/CD pipelines,
backend services — when calling Microsoft APIs unattended.

Key characteristics:
- No Entra user object. No mailbox. No license. No interactive sign-in.
- Authenticates exclusively via **OAuth 2.0 client credentials flow** (application permissions only — no delegated/user permissions).
- Credentials are a client secret or a self-signed certificate, stored in Azure Key Vault.
- Named with the `svc-` prefix per the Entra App Registration Policy §3.

---

## 2. When to Create One

Create a service account whenever an automated process needs to call a Microsoft API
(Microsoft Graph, Azure Resource Manager, SharePoint, Exchange, etc.) without a human
in the loop.

**Never use a human M365 account for automation.** Human accounts:
- Require MFA — automation cannot satisfy an MFA prompt.
- Are tied to a person — if the person leaves, the automation breaks.
- Have delegated permissions scoped to the user's own rights — unpredictable in automation.
- Generate audit events attributed to a human — misleading in compliance reviews.

---

## 3. Naming Rule

Pattern: `svc-<purpose>`

`purpose` must be:
- **Specific and unambiguous.** A reviewer must understand what the account does from the name alone.
- **Lowercase, hyphen-separated.**

| Good | Bad | Why bad |
|---|---|---|
| `svc-github-actions` | `svc-automation` | Too generic |
| `svc-feedback-service` | `svc-api` | Doesn't say which API or who calls it |
| `svc-ble-bridge-server` | `svc-server` | Too generic |

One registration, one purpose. If a new automation needs different API permissions, create a
new service account — do not add permissions to an existing one (Entra App Registration Policy §4).

---

## 4. Running the Script

### Prerequisites

1. **PowerShell 7+** — verify: `pwsh --version`
2. **Microsoft.Graph modules:**
   ```powershell
   Install-Module Microsoft.Graph -Scope CurrentUser -Force
   ```
3. **Azure CLI** — verify: `az --version`. Install:
   - **Debian/Ubuntu:** Use the Microsoft install script — never the Debian-native package:
     ```bash
     curl -sL https://aka.ms/InstallAzureCLIDeb | sudo bash
     ```
   - **RHEL/Fedora:**
     ```bash
     sudo rpm --import https://packages.microsoft.com/keys/microsoft.asc
     sudo dnf install azure-cli
     ```
   - **Windows:** `winget install Microsoft.AzureCLI`
   - **macOS:** `brew install azure-cli`
4. **Azure CLI authenticated** to the CBB tenant:
   ```bash
   az login --tenant d553115d-3f7e-4bb8-8052-946b328cca87
   ```
5. **Your M365 account** must be a member of `sg-Admins`.

### Running

From the `cbb-utils/M365/` directory:

```bash
pwsh -NoProfile -File provision_service_account.ps1
```

The script uses device-code auth for Microsoft Graph. It will print a URL and a short code.
Open the URL in a browser, enter the code, and authenticate with your
`@coldboreballisticsllc.com` account.

### What the Script Does

1. Verifies you are connected to the correct tenant and are a member of `sg-Admins`.
2. Prompts for: purpose, owner UPN, short description, credential type (secret or certificate).
3. Checks for an existing `svc-<purpose>` registration and aborts if one already exists.
4. Optionally adds the service account to one or more `sg-*` security groups.
5. Creates the Entra app registration and service principal.
6. Generates the credential and stores it in Azure Key Vault (`kv-cbb-shared-prod`).
7. Updates the inventory table in `Entra_App_Registration_Policy.md`.
8. Prints a summary with Key Vault secret names, expiry date, and next steps.

### What the Script Does NOT Do

**API permissions are not assigned by the script.** This is intentional — permissions are a
deliberate, separate step requiring admin consent. See §5 below.

---

## 5. Post-Creation Checklist

Complete these steps immediately after running the script:

- [ ] **Add API permissions** in Entra admin center:
  `portal.azure.com` → Entra ID → App registrations → `svc-<purpose>` → API permissions → Add a permission → Microsoft Graph → Application permissions → select the specific permissions needed → Add
- [ ] **Grant admin consent**: on the same API permissions page, click **Grant admin consent for coldboreballisticsllc.com** and confirm.
- [ ] **Verify Key Vault access**: confirm the consuming service or pipeline can read the secret:
  ```bash
  # Client secret
  az keyvault secret show --vault-name kv-cbb-shared-prod --name svc-<purpose>-client-secret

  # Certificate
  az keyvault secret show --vault-name kv-cbb-shared-prod --name svc-<purpose>-cert-pfx
  ```
- [ ] **Record expiry date** in your rotation calendar. The script prints the exact date.
- [ ] **Verify inventory row** was written correctly to `Entra_App_Registration_Policy.md` §7.

---

## 6. Credential Rotation

Rotate before the expiry date printed in the script summary (2 years from creation).
A missed rotation locks out all automation using this account.

### Client Secret Rotation

1. In Entra admin center: App registrations → `svc-<purpose>` → Certificates & secrets → **New client secret**. Set expiry 2 years. Copy the value immediately (shown once only).
2. Store the new secret in Key Vault, replacing the old one:
   ```bash
   az keyvault secret set \
     --vault-name kv-cbb-shared-prod \
     --name svc-<purpose>-client-secret \
     --value "<new-secret-value>" \
     --content-type text/plain \
     --tags "app=svc-<purpose> expires=<new-expiry-ISO> type=client-secret"
   ```
3. If consuming services read the secret by name from Key Vault, the updated value takes effect automatically. If the secret value is baked into config, update those configs now.
4. Verify the consuming service authenticates successfully.
5. Delete the old secret from the app registration: Certificates & secrets → find the old entry by display name → Delete.

### Certificate Rotation

1. Generate a new self-signed certificate (2-year validity):
   ```bash
   # Linux/macOS
   openssl req -x509 -newkey rsa:2048 -days 730 -subj "/CN=svc-<purpose>" \
     -keyout /tmp/svc-new.key -out /tmp/svc-new.cer -nodes
   openssl pkcs12 -export -in /tmp/svc-new.cer -inkey /tmp/svc-new.key \
     -out /tmp/svc-new.pfx -passout "pass:<new-password>"
   rm /tmp/svc-new.key
   ```
2. Upload the new public key (`.cer`) to the app registration:
   App registrations → `svc-<purpose>` → Certificates & secrets → **Upload certificate** → select `/tmp/svc-new.cer`.
3. Store the new PFX and password in Key Vault (overwrites old values):
   ```bash
   az keyvault secret set --vault-name kv-cbb-shared-prod \
     --name svc-<purpose>-cert-pfx \
     --value "$(base64 /tmp/svc-new.pfx)" \
     --content-type application/x-pkcs12 \
     --tags "app=svc-<purpose> expires=<new-expiry-ISO> type=cert-pfx"

   az keyvault secret set --vault-name kv-cbb-shared-prod \
     --name svc-<purpose>-cert-password \
     --value "<new-password>" \
     --content-type text/plain \
     --tags "app=svc-<purpose> expires=<new-expiry-ISO> type=cert-password"
   ```
4. Verify the consuming service authenticates with the new certificate.
5. Remove the old certificate from the app registration: Certificates & secrets → find old thumbprint → Delete.
6. Securely delete temp files: `rm /tmp/svc-new.pfx /tmp/svc-new.cer`

---

## 7. Retirement and Offboarding

When a service account is no longer needed, follow the App Registration Policy §10 exactly:

1. **Disable** the registration: Entra admin center → App registrations → `svc-<purpose>` → Properties → **Enabled for users to sign-in → No**.
2. **Revoke all tokens**: App registrations → `svc-<purpose>` → Overview → **Revoke all tokens**.
3. **Delete Key Vault secrets**:
   ```bash
   # Client secret
   az keyvault secret delete --vault-name kv-cbb-shared-prod --name svc-<purpose>-client-secret

   # Certificate
   az keyvault secret delete --vault-name kv-cbb-shared-prod --name svc-<purpose>-cert-pfx
   az keyvault secret delete --vault-name kv-cbb-shared-prod --name svc-<purpose>-cert-password
   ```
4. **Hold for 30 days.** Monitor consuming services for any unexpected breakage.
5. **After 30 days:** Delete the app registration in Entra admin center.
6. **Update inventory**: mark the row in `Entra_App_Registration_Policy.md` §7 as `Retired — <YYYY-MM-DD>`.

---

## 8. Acquisition Relevance

A clean service account inventory is a material positive in technical due diligence:

- Every `svc-` registration maps to one documented purpose — no mystery accounts.
- Credentials live in Key Vault, not in code or config files — auditable and rotatable without a code deployment.
- Minimum-permission posture (no permissions until explicitly granted post-creation) demonstrates security maturity.
- Clear retirement process means no orphaned accounts accumulating over time.

Acquirers look for: orphaned app registrations, over-scoped permissions, secrets in source
control, and accounts tied to departed employees. This process is designed to produce none of
those.

---

## Document Control

| Field | Value |
|---|---|
| **Document owner** | `jschwefel@coldboreballisticsllc.com` |
| **Classification** | Confidential (03_-_Confidential/) |
| **Location** | `OneDrive/CBB_Corporate/03_-_Confidential/Infrastructure/M365/Service_Account_Guide.md` |
| **Cross-references** | `Entra_App_Registration_Policy.md`; `M365_Operating_Runbook.md`; `cbb-utils/M365/README.md` |
| **Created** | 2026-06-07 |
| **Last updated** | 2026-06-07 |
| **Next full review due** | 2027-06-07 |

---

## Revision Control Block

| Rev | Date | Changed By | Reviewed By | Approved By | Summary |
|-----|------|------------|-------------|-------------|---------|
| 0.9 | 2026-06-07 | jschwefel | — | — | Initial release |
