# 2.7 Encrypting Configuration Data

Configuration data stored in the JSONAir database may contain sensitive information — credentials, internal hostnames, API keys, and so on. JSONAir encrypts all `config_data` at rest using **AES-256-GCM** (authenticated encryption). The server decrypts transparently on every request — agents see no difference.

The `jsonair-encrypt` tool handles the encryption step before data is inserted into the database.

***

## How It Works

| Step | Who    | What                                                            |
| ---- | ------ | --------------------------------------------------------------- |
| 1    | Admin  | Base64-encodes the raw configuration                            |
| 2    | Admin  | Pipes the Base64 value through `jsonair-encrypt`                |
| 3    | Admin  | Inserts the encrypted output into `config_data`                 |
| 4    | Server | Decrypts `config_data` on every `/config` request               |
| 5    | Agent  | Receives Base64-encoded config — no change from its perspective |

The encryption key is derived from `CONFIG_ENCRYPT_SECRET` using SHA-256, producing a 32-byte AES-256 key. Each encryption uses a randomly generated nonce (IV), so two encryptions of the same data produce different ciphertext. The GCM authentication tag ensures any tampering with the stored value is detected and rejected by the server.

***

## The jsonair-encrypt Tool

`jsonair-encrypt` reads from **stdin** and writes to **stdout**. It requires `CONFIG_ENCRYPT_SECRET` to be set — either as an environment variable or in a `.env` file in the same directory.

| Flag     | Behaviour                                         |
| -------- | ------------------------------------------------- |
| *(none)* | Encrypt: reads plaintext, writes encrypted Base64 |
| `-d`     | Decrypt: reads encrypted Base64, writes plaintext |

### Building

```bash
make jsonair-encrypt
# output: bin/jsonair-encrypt/jsonair-encrypt
```

### Encrypting

```bash
echo -n "<base64-encoded-config>" | CONFIG_ENCRYPT_SECRET=your-secret ./jsonair-encrypt
```

### Decrypting

```bash
echo -n "<encrypted-value>" | CONFIG_ENCRYPT_SECRET=your-secret ./jsonair-encrypt -d
```

### Encrypting a file

```bash
# Step 1 — Base64-encode the config file
base64 -i /path/to/suricata.yaml > /tmp/suricata.b64

# Step 2 — Encrypt it
CONFIG_ENCRYPT_SECRET=your-secret ./jsonair-encrypt < /tmp/suricata.b64
```

Or as a single pipeline:

```bash
base64 -i /path/to/suricata.yaml | CONFIG_ENCRYPT_SECRET=your-secret ./jsonair-encrypt
```

### Using a .env file

If you place a `.env` file in the same directory as `jsonair-encrypt`, it will be loaded automatically:

```ini
CONFIG_ENCRYPT_SECRET=your-secret
```

Then simply:

```bash
# Encrypt
base64 -i /path/to/config.yaml | ./jsonair-encrypt

# Decrypt
echo -n "<encrypted-value>" | ./jsonair-encrypt -d
```

***

## Full Workflow — Adding a New Configuration

### 1. Base64-encode your configuration

```bash
base64 -i /etc/suricata/suricata.yaml
```

### 2. Encrypt the output

```bash
base64 -i /etc/suricata/suricata.yaml | CONFIG_ENCRYPT_SECRET=your-secret ./jsonair-encrypt
```

Copy the output — it is the value for `config_data`.

### 3. Insert into the database

```sql
INSERT INTO `configurations` (`uuid`, `type`, `name`, `reload`, `debug`, `config_data`, `created`, `updated`)
VALUES (
  'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx',
  'suricata',
  'suricata.yaml',
  'RELOADKEY',
  'INFO',
  '<output from jsonair-encrypt>',
  NOW(),
  NOW()
);
```

***

## Key Management

`CONFIG_ENCRYPT_SECRET` must be the **same value** on both the `jsonair-encrypt` tool (used when inserting data) and the `jsonair` server (used when serving data). If they differ, the server will fail to decrypt and return a `500` error.

**Best practices:**

* Generate a strong secret: `openssl rand -hex 32`
* Keep `CONFIG_ENCRYPT_SECRET` separate from `TOKEN_HMAC_SECRET` and `JWT_TOKEN_SECRET` — use a distinct value for each
* Store the secret in a secrets manager (Vault, AWS Secrets Manager, etc.) in production rather than in a `.env` file
* Treat key rotation as a migration (see below)

***

## Migrating Existing (Unencrypted) Data

If you have existing `config_data` rows that were stored as plain Base64 (before encryption was introduced), you must re-encrypt them before deploying the new server version. The server expects all `config_data` to be encrypted — it will return a `500` on any row that is not.

### Migration steps

1. Set `CONFIG_ENCRYPT_SECRET` in your environment.
2. For each existing row, retrieve the current `config_data` value and pipe it through `jsonair-encrypt`:

```bash
# Retrieve the current value, encrypt it, update the row
OLD=$(mysql -u jsonair -p jsonair -sNe \
  "SELECT config_data FROM configurations WHERE id=1;")

NEW=$(echo -n "$OLD" | CONFIG_ENCRYPT_SECRET=your-secret ./jsonair-encrypt)

mysql -u jsonair -p jsonair -e \
  "UPDATE configurations SET config_data='$NEW' WHERE id=1;"
```

3. Repeat for every row, then deploy the new server.

***

## Key Rotation

To rotate `CONFIG_ENCRYPT_SECRET`:

1. Decrypt every `config_data` row using the **old** key (pass `-d`).
2. Re-encrypt each row using the **new** key.
3. Update `CONFIG_ENCRYPT_SECRET` on the server and restart.

```bash
OLD_VAL=$(mysql -u jsonair -p jsonair -sNe \
  "SELECT config_data FROM configurations WHERE id=1;")

PLAIN=$(printf '%s' "$OLD_VAL" | CONFIG_ENCRYPT_SECRET=old-secret ./jsonair-encrypt -d)

NEW_VAL=$(printf '%s' "$PLAIN" | CONFIG_ENCRYPT_SECRET=new-secret ./jsonair-encrypt)

mysql -u jsonair -p jsonair -e \
  "UPDATE configurations SET config_data='$NEW_VAL' WHERE id=1;"
```

Until step 3 is complete, keep the old server running — it cannot read rows encrypted with the new key.


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.k9.io/key9-identity/jsonair/2-install/2.7-encrypting-config-data.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
