# Internal Site Operations

Covers three topics: deploying the CBB root CA to client devices, issuing certificates for new internal hostnames, and bringing up a new internal nginx site.

All internal HTTPS sites use the mkcert local CA. Certificates are valid for three years from issuance. **The CA root material lives on `mac001`** (mkcert CAROOT at `~/Library/Application Support/mkcert/`, also imported into the macOS System keychain). Never commit CA or server key material to any repository.

The internal HTTPS host is **`mac001`**, listening on the LAN secondary IP `172.31.1.200` (aliased onto mac001's wired LAN interface). nginx runs as a system LaunchDaemon (Homebrew nginx, configs under `/opt/homebrew/etc/nginx/servers/`).

---

## 1. Deploying the Root CA to Client Devices

Clients must trust the CBB mkcert root CA before visiting any internal HTTPS site without browser warnings. Do this once per device.

**Get the root CA certificate.** On `mac001` it is at:
```
~/Library/Application Support/mkcert/rootCA.pem
```
It is also published for download (LAN-only) at `https://files.coldboreballisticsllc.com/certs/rootCA.pem`. Copy it to the client machine by any secure means (download, SCP, USB, etc.).

---

### Debian

```bash
sudo cp rootCA.pem /usr/local/share/ca-certificates/cbb-mkcert-root.crt
sudo update-ca-certificates
```

The file **must** have a `.crt` extension — `update-ca-certificates` ignores `.pem` files in that directory.

---

### Ubuntu

Same as Debian:

```bash
sudo cp rootCA.pem /usr/local/share/ca-certificates/cbb-mkcert-root.crt
sudo update-ca-certificates
```

---

### RHEL / Fedora

```bash
sudo cp rootCA.pem /etc/pki/ca-trust/source/anchors/cbb-mkcert-root.pem
sudo update-ca-trust
```

The file may keep a `.pem` extension here — `update-ca-trust` accepts it.

---

### Arch Linux

```bash
sudo cp rootCA.pem /etc/ca-certificates/trust-source/anchors/cbb-mkcert-root.crt
sudo update-ca-trust
```

If `update-ca-trust` is not available, use:
```bash
sudo trust anchor --store rootCA.pem
```

---

### macOS

```bash
sudo security add-trusted-cert -d -r trustRoot -k /Library/Keychains/System.keychain rootCA.pem
```

This installs the cert system-wide and trusts it for SSL. Safari and Chrome pick it up immediately. Firefox on macOS uses its own trust store — see the Firefox note below.

Alternatively via the UI:
1. Double-click `rootCA.pem` — it opens in Keychain Access and imports to the login keychain
2. Find **mkcert** in Keychain Access → double-click → expand **Trust** → set **When using this certificate** to **Always Trust** → close and enter your password

---

### Windows

**Option A — Command line (run as Administrator):**
```
certutil -addstore -f "ROOT" rootCA.pem
```

**Option B — GUI:**
1. Rename `rootCA.pem` to `rootCA.crt` (Windows needs the `.crt` extension to recognise it)
2. Double-click the file → **Install Certificate**
3. Select **Local Machine** → **Next**
4. Select **Place all certificates in the following store** → **Browse** → **Trusted Root Certification Authorities** → **OK** → **Next** → **Finish**
5. Confirm the security prompt

Chrome and Edge pick up the Windows certificate store immediately. Firefox on Windows uses its own trust store — see below.

---

### iOS

iOS trusts user-installed root CAs in Safari and Chrome (both use the system WebKit engine), so this works well.

**Step 1 — Get the cert onto the device.** Transfer `rootCA.pem` via AirDrop, iCloud Drive, email, or download it directly from an already-trusted source. iOS needs to open the file as a profile.

**Step 2 — Install the profile.**
1. Tap the `rootCA.pem` file. iOS will show: *"Profile Downloaded — Review the profile in the Settings app if you want to install it."*
2. Open **Settings** → **General** → **VPN & Device Management**
3. Under **Downloaded Profile**, tap the **mkcert** entry → tap **Install** (top right)
4. Enter your device passcode when prompted → tap **Install** again to confirm

**Step 3 — Enable full trust.**

Installing the profile alone is not enough — iOS requires an explicit second step to trust a root CA for HTTPS:

1. Open **Settings** → **General** → **About**
2. Scroll to the bottom → tap **Certificate Trust Settings**
3. Under **Enable Full Trust For Root Certificates**, toggle on the **mkcert** entry
4. Tap **Continue** on the warning prompt

The CA is now trusted. Safari and Chrome on iOS will accept certificates signed by it.

---

### Android

> **Important limitation:** Chrome on Android 7 and later does not trust user-installed CA certificates. The installation steps below are still required, but **use Firefox** to access internal sites — Chrome will show a certificate error regardless.

**Step 1 — Get the cert onto the device.** Transfer `rootCA.pem` to the device. Rename it to `rootCA.crt` — Android requires the `.crt` extension for the install flow to recognise it as a certificate.

**Step 2 — Install as a user CA.**
1. Open **Settings** → **Security** (may be under **Security & privacy** on newer Android)
2. Tap **More security settings** (or **Advanced** depending on the device)
3. Tap **Install from storage** → **CA certificate**
4. Android will warn that a CA certificate can monitor network traffic — tap **Install anyway**
5. Navigate to `rootCA.crt` and tap it to install

On some devices (Samsung, Pixel) the path may differ slightly — search Settings for **"Install certificate"** if you cannot find it.

**Step 3 — Configure Firefox to use the system certificate store.**

By default, Firefox for Android uses its own certificate store and does not see user-installed CAs. One setting enables it:

1. Open Firefox → type `about:config` in the address bar → tap **OK** on the warning
2. Search for: `security.enterprise_roots.enabled`
3. Tap the toggle to set it to **true**
4. Restart Firefox

Firefox will now trust the system certificate store, including the installed mkcert root CA.

Chrome and other Chromium-based browsers (Brave, Samsung Internet) cannot be made to trust user-installed CAs on a non-rooted device. Firefox is the supported browser for internal HTTPS sites on Android.

---

### Firefox (all platforms)

Firefox maintains its own certificate store independent of the OS. To trust the CBB root CA in Firefox:

1. Open Firefox → **Settings** → search **Certificates** → **View Certificates**
2. Click the **Authorities** tab → **Import**
3. Select `rootCA.pem`
4. Check **Trust this CA to identify websites** → **OK**

---

## 2. Issuing a Certificate for a New Internal Hostname

Run on `mac001` as the working user (the mkcert CA is in that user's `~/Library/Application Support/mkcert/`).

```bash
cd /tmp
mkcert <hostname> [<additional-san> ...]
```

**Examples:**
```bash
# Hostname only
mkcert files.coldboreballisticsllc.com

# Hostname + IP SAN (add the IP if clients may connect by IP)
mkcert files.coldboreballisticsllc.com 172.31.1.200

# Multiple hostnames sharing one cert
mkcert app.coldboreballisticsllc.com app-internal.coldboreballisticsllc.com
```

mkcert creates two files in the current directory:
- `<hostname>.pem` — the certificate (public, safe to read by nginx)
- `<hostname>-key.pem` — the private key (must be protected)

**Move to the cert store:**
```bash
sudo mkdir -p /opt/homebrew/etc/nginx/ssl/cbb
sudo mv <hostname>+<n>.pem     /opt/homebrew/etc/nginx/ssl/cbb/<hostname>.pem
sudo mv <hostname>+<n>-key.pem /opt/homebrew/etc/nginx/ssl/cbb/<hostname>-key.pem

sudo chmod 640 /opt/homebrew/etc/nginx/ssl/cbb/<hostname>-key.pem
sudo chmod 644 /opt/homebrew/etc/nginx/ssl/cbb/<hostname>.pem
```

The `+<n>` suffix appears when multiple SANs are given (n = number of extra SANs). Check the filenames mkcert printed before moving.

(Internal-test vhosts use the multi-SAN cert at `/opt/homebrew/etc/nginx/ssl/cbb-test/cbb-test.{crt,key}` covering all three `*-test.*` hostnames. Add new test hostnames by reissuing that cert with the expanded SAN list — `mkcert` with all hostnames + the `172.31.1.200` IP SAN, then move to that path.)

Certificates expire **3 years from issuance**. Note the expiry date printed by mkcert and set a calendar reminder to reissue before then.

---

## 3. Bringing Up a New Internal Site

### 3.1 Prerequisites

- Certificate issued and in `/etc/ssl/cbb/` (see §2)
- A DNS entry or hosts-file entry pointing the hostname to the server IP on each client
- The content directory created and readable by `www-data` (see §3.3)

### 3.2 DNS / Hosts File

Internal sites do not appear in public DNS. Each client must resolve the hostname locally.

**Option A — Local DNS (preferred for multiple clients):** Add an A record in your local DNS server pointing the hostname to the server's IP.

**Option B — Hosts file (per device):**

| Platform | File |
|---|---|
| Linux / macOS | `/etc/hosts` |
| Windows | `C:\Windows\System32\drivers\etc\hosts` (edit as Administrator) |

Add a line in the format:
```
172.31.1.200    files.coldboreballisticsllc.com
```

### 3.3 Content Directory

The directory served by nginx must be traversable by the nginx worker user (`nobody` by default on Homebrew nginx). Create it and set permissions:

```bash
mkdir -p /path/to/content
chmod 755 /path/to/content
```

All parent directories in the path must also allow execute for others (`o+x`). Check with `ls -la` on each component. On macOS, `/Users/<name>/` is typically `700` by default — verify the path components leading to the content root are traversable, or place the content somewhere that already is.

### 3.4 nginx Site Config

Create `/opt/homebrew/etc/nginx/servers/<hostname>.conf`:

```nginx
server {
    listen <server-ip>:443 ssl;
    server_name <hostname>;

    ssl_certificate     /opt/homebrew/etc/nginx/ssl/cbb/<hostname>.pem;
    ssl_certificate_key /opt/homebrew/etc/nginx/ssl/cbb/<hostname>-key.pem;
    ssl_protocols       TLSv1.2 TLSv1.3;
    ssl_ciphers         HIGH:!aNULL:!MD5;
    ssl_prefer_server_ciphers on;

    root /path/to/content;

    # For a read-only file browser:
    autoindex on;
    autoindex_exact_size off;
    autoindex_localtime on;

    # Restrict to LAN subnets
    allow 172.31.0.0/22;
    allow 172.29.1.0/24;
    deny all;

    add_header X-Frame-Options "SAMEORIGIN" always;
    add_header X-Content-Type-Options "nosniff" always;
    add_header Referrer-Policy "no-referrer" always;
    add_header X-Robots-Tag "noindex, nofollow" always;

    access_log /opt/homebrew/var/log/nginx/<hostname>.access.log;
    error_log  /opt/homebrew/var/log/nginx/<hostname>.error.log;
}

# HTTP → HTTPS redirect
server {
    listen <server-ip>:80;
    server_name <hostname>;

    allow 172.31.0.0/22;
    allow 172.29.1.0/24;
    deny all;

    return 301 https://$host$request_uri;
}
```

Replace `<server-ip>`, `<hostname>`, and `/path/to/content` with actual values.

### 3.5 Validate and Reload

Homebrew nginx auto-includes every file in `/opt/homebrew/etc/nginx/servers/`, so dropping the file in place is enough — no `sites-enabled` symlink step.

```bash
sudo nginx -t
sudo brew services reload nginx
```

Always run `nginx -t` before reloading — it catches config errors without taking the server down.

### 3.6 Verify

```bash
curl -sk --resolve <hostname>:443:<server-ip> https://<hostname>/
```

A successful response (HTML) confirms the site is up. Test from a client device as well to confirm DNS resolution and CA trust are working end-to-end.

---

## Active Internal Sites

Host: **`mac001`** at `172.31.1.200` (LAN secondary alias). nginx config dir: `/opt/homebrew/etc/nginx/servers/`.

| Hostname | IP | Port | Content / backend | Config file |
|---|---|---|---|---|
| `files.coldboreballisticsllc.com` | `172.31.1.200` | 443 | `ColdBoreBallistics/files/` (autoindex) | `files.coldboreballisticsllc.com.conf` |
| `api-test.scope-dope.com` | `172.31.1.200` | 443 | Reverse proxy to `127.0.0.1:5001` (feedback-service backend — currently DEFERRED) | `api-test.scope-dope.com.conf` |
| `www-test.coldboreballisticsllc.com` | `172.31.1.200` | 443 | `web/coldboreballisticsllc.com/www.coldboreballisticsllc.com/` (static site) | `www-test.coldboreballisticsllc.com.conf` |
| `www-test.scope-dope.com` | `172.31.1.200` | 443 | `web/scope-dope.com/www.scope-dope.com/` (static + `/api/` proxy to `:5001`) | `www-test.scope-dope.com.conf` |

---

## Document Control

| Field | Value |
|---|---|
| **Document owner** | `jschwefel@coldboreballisticsllc.com` |
| **Classification** | Confidential (03_-_Confidential/) |
| **Location** | `OneDrive/CBB_Corporate/03_-_Confidential/Infrastructure/Websites/Internal_Site_Operations.md` |
| **Cross-references** | `Website_and_Service_Deployment_Guide.md`; `Azure_Architecture_Reference.md` |
| **Created** | 2026-06-04 |
| **Last updated** | 2026-06-13 |
| **Next full review due** | 2027-06-04 |

---

## Revision Control Block

| Rev | Date | Changed By | Reviewed By | Approved By | Summary |
|-----|------|------------|-------------|-------------|---------|
| 0.9 | 2026-06-04 | jschwefel | — | — | Initial release |
| 1.0 | 2026-06-13 | jschwefel (via Claude Code) | — | — | Internal HTTPS host set to mac001 (`172.31.1.200`); paths set to the Homebrew nginx layout; active sites list expanded to four; corrected `coldboreballistics.com` examples to `.llc.com` form. |
| 1.1 | 2026-06-14 | jschwefel (via Claude Code) | — | — | Scrubbed all references to the prior personal host; mac001 is the sole CBB host. CA-retrieval path corrected to the macOS CAROOT + files-vhost download. |
