Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .github/PULL_REQUEST_TEMPLATE.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ know who to ask when it breaks. For example:
## Checklist

- [ ] The record is for a Patchwork project, event, or service.
- [ ] Every record I added or changed has an owner in a comment.
- [ ] Every record I added or changed has an owner in a comment: an email or a
GitHub handle.
- [ ] `./bin/validate` passes, so the records are in the order octoDNS wants.
- [ ] CNAME values end with a dot. A and AAAA values do not.
- [ ] I have read the `octoDNS plan` comment on this pull request and it
Expand Down
13 changes: 7 additions & 6 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@ The repository uses OctoDNS with Cloudflare as the DNS provider and YAML configu
## Architecture

- **Configuration**: `config/config.yaml` defines providers, processors and zone mappings. `enforce_order` with `order_mode: natural` is on.
- **DNS Records**: Domain-specific YAML files (`patchworklabs.org.yaml`, `hackathon.help.yaml`) contain DNS record definitions. Every new record needs an owner email in a comment on the same line as its name.
- **DNS Records**: Domain-specific YAML files (`patchworklabs.org.yaml`, `hackathon.help.yaml`) contain DNS record definitions. Every record needs an owner (an email or a GitHub handle) in a comment on the same line as its name.
- **Scripts**: Shell scripts in `bin/` handle DNS operations. They read the zone list from `config/config.yaml` through `bin/zones`.
- **Tools**: `tools/merge_live.py` merges live Cloudflare state back into the zone files and keeps comments. Tests are in `tools/test_merge_live.py`.
- **Tools**: `tools/merge_live.py` merges live Cloudflare state back into the zone files and keeps comments. `tools/check_zones.py` checks the repository rules (owner comment on every record, TTL of 120 or more, no apex NS, no `octodns-meta`, proxy only on A/AAAA/CNAME). `./bin/validate` runs it. Tests are in `tools/test_*.py`.
- **Workflows**: `validate` (no secrets), `plan` (`pull_request_target`, posts the plan and checks drift), `deploy` (push to `main`), `sync-from-cloudflare` (nightly).
- **Dependencies**: Python dependencies pinned in `requirements.txt`.

Expand Down Expand Up @@ -78,11 +78,12 @@ Use appropriate TTL values based on record type and change frequency:
### Record Comments Format
```yaml
# Google Workspace email routing - DO NOT MODIFY without IT approval
mx:
values:
- exchange: mx1.example.com
priority: 10
"": # @patchworklabsorg/infra
ttl: 3600
type: MX
values:
- exchange: mx1.example.com.
preference: 10
```

## Important Notes
Expand Down
5 changes: 3 additions & 2 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ Thank you for helping run the Patchwork Labs DNS. This file covers the rules. Th
3. Keep records in order inside a zone file. The `dns records` check enforces
it. The order is natural, so `ns2` comes before `ns10`, and inside a record
`octodns` comes before `ttl`, `type` and `value`. Run `./bin/validate`.
4. Give every record an owner in a comment on the same line as its name.
4. Give every record an owner in a comment on the same line as its name. Use
an email or a GitHub handle. The `dns records` check enforces it.
5. Read the `octoDNS plan` comment on your pull request before you ask for a
review. It is the exact list of changes the merge will make.
6. Answer review comments on the same pull request. Do not close it and open a
Expand Down Expand Up @@ -82,7 +83,7 @@ pull request the next morning.
$ python -m unittest discover -s tools -p 'test_*.py' -v
```

Add a test with any change to `tools/merge_live.py`. That script edits the zone
Add a test with any change to `tools/merge_live.py` or `tools/check_zones.py`. That script edits the zone
files by itself every night, so a bug in it is a bug in production DNS.

Never loosen the security note at the top of
Expand Down
23 changes: 20 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,11 @@ Three rules decide whether it works:

- **The name is the part before the domain.** `docs` becomes `docs.patchworklabs.org`.
- **A `CNAME` value ends with a dot.** An `A` or `AAAA` value does not.
- **Every record needs an owner.** Put a Patchwork email in a comment on the same
line as the name. We use it to find out who to ask when the record breaks.
List more than one person if more than one person is responsible.
- **Every record needs an owner.** Put an email or a GitHub handle in a
comment on the same line as the name, for example `# ada@patchworklabs.org`
or `# @patchworklabsorg/infra`. We use it to find out who to ask when the
record breaks. List more than one owner if more than one person is
responsible. The `dns records` check fails on a record without an owner.

### Order is checked

Expand All @@ -54,6 +56,21 @@ a rule and not a request. The order is **natural**, not plain alphabetical:
Run `./bin/validate` to check before you push. The nightly sync writes files
in this order by itself.

### Other checks

`./bin/validate` also runs [`tools/check_zones.py`](./tools/check_zones.py).
It fails when:

- A record has no owner comment, or only a `TODO owner unknown` comment.
- A TTL is lower than 120 seconds. Cloudflare raises a lower TTL to 120, so
the zone file would never match Cloudflare.
- A zone file holds the apex `NS` records. Cloudflare owns them.
- A zone file holds the `octodns-meta` record. octoDNS writes it.
- A record that is not `A`, `AAAA` or `CNAME` is behind the Cloudflare proxy.
- A zone in `config/config.yaml` has no zone file, or a YAML file at the
repository root is not a zone.
- One name appears twice in one file.

### 2. Open a pull request

A bot posts a **plan** on your pull request. It lists every record the merge
Expand Down
8 changes: 6 additions & 2 deletions bin/validate
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
#!/bin/sh
# Check that the config and the zone files parse and that every record is
# valid. Does not contact Cloudflare, so it needs no real token.
# valid. Then check the repository rules that octoDNS does not know about,
# such as the owner comment on every record. See tools/check_zones.py.
#
# Does not contact Cloudflare, so it needs no real token.
set -eu

CLOUDFLARE_TOKEN="${CLOUDFLARE_TOKEN:-validate-only-not-a-real-token}"
export CLOUDFLARE_TOKEN

exec octodns-validate --config-file=./config/config.yaml "$@"
octodns-validate --config-file=./config/config.yaml "$@"
python3 tools/check_zones.py
30 changes: 27 additions & 3 deletions docs/runbook.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,33 @@ There is also one repository **variable**, not a secret:
|---|---|
| `DNS_BOT_CLIENT_ID` | Client ID of the same App. An identifier, not a credential |

The two Cloudflare tokens already exist. Create them at
**Cloudflare > My Profile > API Tokens** with the `Edit zone DNS` template, and
scope each one to `patchworklabs.org` and `hackathon.help` only.
The two zones are in **different Cloudflare accounts**:

| Zone | Cloudflare account |
|---|---|
| `patchworklabs.org` | Patchwork Labs |
| `hackathon.help` | Jasper Mayone |

An account API token can only see the zones in its own account. Use a **user**
token, which can cover zones in every account its owner belongs to. Create
each one at **Cloudflare > My Profile > API Tokens**:

1. Select **Create Custom Token**.
2. **Permissions**: `Zone` `Zone` `Read`, and `Zone` `DNS` `Edit` for
`CLOUDFLARE_TOKEN` or `Zone` `DNS` `Read` for
`CLOUDFLARE_TOKEN_READ_ONLY`.
3. **Zone Resources**: `Include` `Specific zone` `patchworklabs.org`, then add
a second row for `hackathon.help`.
4. Save the value straight into the repository secret. Do not paste it
anywhere else:

```console
$ gh secret set CLOUDFLARE_TOKEN --repo patchworklabsorg/dns
```

A token that cannot see a zone makes octoDNS try to create that zone. The
deploy then fails with `Invalid account identifier passed in your organization
variable`.

> **Rotate `CLOUDFLARE_TOKEN_READ_ONLY` once.** The old `test.yml` workflow
> ran scripts from a pull request while holding it, so anybody who opened a
Expand Down
16 changes: 8 additions & 8 deletions hackathon.help.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
"":
- ttl: 1
"": # @patchworklabsorg/infra
- ttl: 120
type: MX
values:
- exchange: aspmx.l.google.com.
Expand All @@ -12,21 +12,21 @@
preference: 10
- exchange: alt4.aspmx.l.google.com.
preference: 10
- ttl: 1
- ttl: 120
type: TXT
values:
- google-site-verification=dWzvYUBk_oc6spUhOFmqPl8wYeRMvpfhhARS1tb21ag
- Owned by Patchwork Labs Inc (patchworklabs.org) a registered 501(c)(3) nonprofit organization.
- v=spf1 include:_spf.google.com ~all
- slack-domain-verification=hwBycY5FO958m1HWCSDHQIWCM6z566RY95Swq8qo

_dmarc:
ttl: 1
_dmarc: # @patchworklabsorg/infra
ttl: 120
type: TXT
value: v=DMARC1\; p=quarantine\; rua=mailto:dmark_reports@hackathon.help\; pct=100\; ruf=mailto:dmark_reports@hackathon.help\; adkim=s\; aspf=s
value: v=DMARC1\; p=quarantine\; np=reject\; rua=mailto:dmarc_reports@hackathon.help\; ruf=mailto:dmarc_reports@hackathon.help\; adkim=s\; aspf=s

# Google Workspace DKIM authentication
google._domainkey:
ttl: 3600
google._domainkey: # @patchworklabsorg/infra
ttl: 120
type: TXT
value: v=DKIM1\; k=rsa\; p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA1ZPaz12FXM3cVvaD96N1XbWsc1Um/EZYE8UMV+CQqupWmI/aqwfT5bQWHOCwp8RU+eI8ONee12ah7xnUThU4Ls+BNXyyrsRUAVfKowk0pxXpDwLHS0kf8sXBrsOLqQDwNOrSQ7P33YxhghExdwvdQ5O0qL557wjWU+zhbjiF1HzJm6Ved2Nya98cXu1UbkVGQsmlMJVb0nEZIvmD19sIxNkhXFUV6KIALqa7iai+YT+tapiiCc2XUzw4GTcqfIyS9leKn5Gz1gWCCgMCL3n03kxuzxR6PkS5YlgNZPur4MohsUU3UQZsAoF+NNoGTA67uY0HpLT91CVWuNfjMkTtFQIDAQAB
42 changes: 20 additions & 22 deletions patchworklabs.org.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
"":
- ttl: 1
"": # @patchworklabsorg/infra
- ttl: 120
type: MX
values:
- exchange: aspmx.l.google.com.
Expand All @@ -12,7 +12,7 @@
preference: 10
- exchange: alt4.aspmx.l.google.com.
preference: 10
- ttl: 1
- ttl: 120
type: TXT
values:
- google-site-verification=OjTBuEBXcUdIOVzjidh1sCMfvAYvabpQWnkGfFFQQA4
Expand All @@ -23,61 +23,59 @@
- octodns:
cloudflare:
proxied: true
ttl: 1
ttl: 120
type: A
value: 216.198.79.1

_atproto:
ttl: 1
_atproto: # @patchworklabsorg/infra
ttl: 120
type: TXT
value: did=did:plc:bpd7j2a34mmnyu7t64gzptg7

_dmarc:
ttl: 1
_dmarc: # @patchworklabsorg/infra
ttl: 120
type: TXT
value: v=DMARC1\; p=quarantine\; rua=mailto:dmark_reports@patchworklabs.org\; pct=100\; ruf=mailto:dmark_reports@patchworklabs.org\; adkim=s\; aspf=s
value: v=DMARC1\; p=quarantine\; np=reject\; rua=mailto:dmarc_reports@patchworklabs.org\; ruf=mailto:dmarc_reports@patchworklabs.org\; adkim=s\; aspf=s

_gh-patchworklabsorg-o:
ttl: 3600
_gh-patchworklabsorg-o: # @patchworklabsorg/infra
type: TXT
value: cee2c56f89

admin.forms:
admin.forms: # @patchworklabsorg/infra
octodns:
cloudflare:
proxied: true
ttl: 1
ttl: 120
type: A
value: 65.19.76.238

forms:
forms: # @patchworklabsorg/infra
octodns:
cloudflare:
proxied: true
ttl: 1
ttl: 120
type: A
value: 65.19.76.238

# Google Workspace DKIM authentication
google._domainkey:
ttl: 3600
google._domainkey: # @patchworklabsorg/infra
type: TXT
value: v=DKIM1\; k=rsa\; p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzEuGHXgOxIj0qk83hV4ajl/OIpXbhE/MTKhXU1VZDBISO/+yBCQsVyq1j4F13tWxUpYLlw78OJl+7TlAyZ4llYY5z2Oj+EiAIQZCRBLmKN7zDpnbwbiM4hfam5vnhCvFwdgg0aKUY221T/Pe5Mjz11e0VtyOJ3D3enPId010bi99p93Dimf+rFo9YFwps7U/V4O5rWaRyG9snFcl9tshvKTgQ6OoHEvLbvQIU1QTiXR6oHAI5KP/8BzA26djgIKqNuHaU9S70KSVUH/9CBSAjHP50j4i4rGGEr6PopNjxQxN5owwu2jUDEIOrv13RLpNPISu1NaPCgMnGVyfsQ77yQIDAQAB

idp:
ttl: 1
idp: # @patchworklabsorg/infra
ttl: 120
type: A
value: 129.213.163.213

openpgpkey:
openpgpkey: # @patchworklabsorg/infra
ttl: 300
type: CNAME
value: wkd.keys.openpgp.org.

www:
www: # @patchworklabsorg/infra
octodns:
cloudflare:
proxied: true
ttl: 1
ttl: 120
type: CNAME
value: 52fd2b2210caa11f.vercel-dns-017.com.
Loading
Loading