<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>AI Nytta</title><description>Writing by ainytta — a personal blog.</description><link>https://ainytta.blog/</link><language>en-GB</language><item><title>Mail Security Hardening: SPF, DMARC Reject, DKIM Keys, and Automated Verification</title><link>https://ainytta.blog/blog/mail-dns-security-hardening/</link><guid isPermaLink="true">https://ainytta.blog/blog/mail-dns-security-hardening/</guid><description>How to secure domain email authentication using SPF, DKIM RSA keypairs, DMARC p=reject enforcement, and automated testing via Pytest and C# MCP.</description><pubDate>Fri, 24 Jul 2026 00:00:00 GMT</pubDate><content:encoded>Email authentication is a fundamental layer of domain security and brand protection. Without strict DNS-level verification policies, malicious actors can easily spoof your domain address in phishing campaigns.

In this post, we explore how to configure a hardened email security posture for `teamsehem.se`, how DKIM asymmetric keys work, and how we automate DNS verification using **Pytest** and a custom **C# .NET Model Context Protocol (MCP)** tool.

---

## 1. The Core Email Authentication Pillars

### **SPF (Sender Policy Framework)**
SPF specifies which mail servers are authorized to send outgoing email on behalf of your domain.

```text
v=spf1 mx a -all
```

- **`v=spf1`**: Identifies the SPF version.
- **`mx a`**: Authorizes servers matching the domain&apos;s MX and A records (e.g., `mail.teamsehem.se`).
- **`-all` (Hard Fail)**: Instructs receiving servers to explicitly reject emails coming from any IP address not listed.

---

### **DKIM (DomainKeys Identified Mail)**
DKIM uses **asymmetric cryptography** (Public/Private RSA keypair) to sign and verify emails:

1. **Private Key Location &amp; Security Model**:
   - **Server Filesystem**: Stored strictly at `/etc/virtual/teamsehem.se/dkim.private.key` (accessible only via Root/Admin SSH with `0600` file permissions).
   - **DirectAdmin Evolution UI (`/evo/dns-records`)**: Control panel skins intentionally hide raw private keys from web users to prevent credential theft. The Exim mail daemon automatically reads the private key from disk when signing outgoing mail.
2. **Public Key**: Published openly in DNS as a TXT record at `x._domainkey.teamsehem.se`:

```text
v=DKIM1; k=rsa; p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwW3NDO...
```

When a receiving mail provider (like Gmail or Proton) receives your email, it queries DNS for `x._domainkey.teamsehem.se` and uses the public key to verify that the message was genuinely signed by your private key and was not modified in transit.

---

### **DMARC (Domain-based Message Authentication, Reporting &amp; Conformance)**
DMARC dictates what receiving servers must do if an incoming email fails SPF or DKIM checks.

```text
v=DMARC1; p=reject; sp=reject
```

- **`p=reject`**: Instructs mail servers to **block and drop** spoofed emails targeting `@teamsehem.se` immediately, preventing them from even reaching a user&apos;s Spam folder.
- **`sp=reject`**: Extends the exact same `reject` policy to all subdomains (e.g. `@sub.teamsehem.se`), closing subdomain spoofing loopholes.

---

## 2. Automated Verification with Pytest

To prevent silent DNS regressions during nameserver or host migrations, we maintain an automated test suite in **Pytest** (`tests/test_mail_dns.py`).

The script queries authoritative nameservers (`ns1.inleed.net` through `ns6.inleed.net`) using `dig` and validates record alignment:

```python
# tests/test_mail_dns.py
import subprocess
import pytest

DOMAIN = &quot;teamsehem.se&quot;
NAMESERVERS = [f&quot;ns{i}.inleed.net&quot; for i in range(1, 7)]

EXPECTED = {
    &quot;MX&quot;: {&quot;name&quot;: DOMAIN, &quot;type&quot;: &quot;MX&quot;, &quot;expected&quot;: &quot;10 mail.teamsehem.se.&quot;},
    &quot;SPF&quot;: {&quot;name&quot;: DOMAIN, &quot;type&quot;: &quot;TXT&quot;, &quot;expected&quot;: &quot;v=spf1 mx a -all&quot;},
    &quot;DMARC&quot;: {&quot;name&quot;: f&quot;_dmarc.{DOMAIN}&quot;, &quot;type&quot;: &quot;TXT&quot;, &quot;expected&quot;: &quot;v=DMARC1; p=reject; sp=reject&quot;},
    &quot;DKIM_TXT&quot;: {&quot;name&quot;: f&quot;x._domainkey.{DOMAIN}&quot;, &quot;type&quot;: &quot;TXT&quot;, &quot;expected_partial&quot;: &quot;v=DKIM1; k=rsa;&quot;},
}

def resolve_record(ns, name, rtype):
    cmd = [&quot;dig&quot;, f&quot;@{ns}&quot;, name, rtype, &quot;+short&quot;, &quot;+time=3&quot;, &quot;+tries=3&quot;]
    try:
        result = subprocess.run(cmd, capture_output=True, text=True, timeout=12)
        return result.stdout.strip().split(&apos;\n&apos;)
    except subprocess.TimeoutExpired:
        return []

@pytest.mark.parametrize(&quot;record_key, config&quot;, EXPECTED.items())
@pytest.mark.parametrize(&quot;ns&quot;, NAMESERVERS)
def test_mail_dns_record(ns, record_key, config):
    records = resolve_record(ns, config[&quot;name&quot;], config[&quot;type&quot;])
    found = any(config.get(&quot;expected&quot;) in r or config.get(&quot;expected_partial&quot;) in r for r in records if r)
    assert found
```

Running the test suite yields **28 passed assertions** in under 5 seconds:

```bash
python3 -m pytest tests/test_mail_dns.py -v
# 28 passed in 3.95s
```

---

## 3. Integration with C# .NET MCP Tooling

To allow autonomous AI coding agents to verify mail DNS status on demand, we expose a tool via a C# .NET **Model Context Protocol (MCP)** server (`MailDnsTools.cs`).

The MCP server runs the Pytest verification in the background and transforms raw terminal output into structured JSON for agent reasoning:

```csharp
[McpServerTool(Name = &quot;check_mail_dns&quot;)]
[Description(&quot;Runs pytest test_mail_dns.py and returns structured per-record DNS results.&quot;)]
public string CheckMailDns()
{
    var result = ProcessRunner.Run(&quot;python3&quot;, &quot;-m pytest tests/test_mail_dns.py -v&quot;);
    return ParseVerboseOutput(result);
}
```

This pattern ensures AI agents can verify live DNS security configurations without parsing raw shell streams or leaking sensitive credentials.</content:encoded><category>security</category><category>dns</category><category>automation</category><category>devsecops</category></item><item><title>Welcome to ainytta</title><link>https://ainytta.blog/blog/welcome/</link><guid isPermaLink="true">https://ainytta.blog/blog/welcome/</guid><description>The first post on this blog — what it is and what to expect.</description><pubDate>Sun, 19 Jul 2026 00:00:00 GMT</pubDate><content:encoded># Welcome

This is the first post on **ainytta**. This blog is built with
[Astro](https://astro.build) and hosted on
[statichost.eu](https://statichost.eu) — a Swedish, fully European static
hosting provider.

## What to expect

- Sales pitch of me as consultant :-) 
- Short notes and longer writing on AI and engineering topics that interest me
- I am on a quest to discover what can be built with AI outside of the beaten path, what can we use AI for?
- AI tools, models and patterns
- Other tecnical labs and solutions
- Tagged posts so related writing is easy to find
- An RSS feed at `/rss.xml`</content:encoded><category>meta</category><category>intro</category></item></channel></rss>