Skip to content

Commit 904a5d1

Browse files
committed
Move 1Password credential paths into config file
1 parent aaf2928 commit 904a5d1

6 files changed

Lines changed: 121 additions & 9 deletions

File tree

README.md

Lines changed: 77 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,78 @@
11
# rir-updater
2-
Scripts to do updates to RIR databases
2+
3+
CLI tool for syncing RIPE NCC route objects and RPKI ROAs from a YAML config file.
4+
5+
## Requirements
6+
7+
- Python 3.14+
8+
- [uv](https://docs.astral.sh/uv/)
9+
- [1Password CLI](https://developer.1password.com/docs/cli/) (`op`) for credential access
10+
11+
## Installation
12+
13+
```bash
14+
uv sync
15+
```
16+
17+
## Configuration
18+
19+
Copy `config.example.yaml` and fill in your values:
20+
21+
```yaml
22+
ripe:
23+
maintainer: "MAINT-AS12345"
24+
sso_emails:
25+
- "admin@example.com"
26+
routes:
27+
- prefix: "192.0.2.0/24"
28+
origin: "AS12345"
29+
description: "Example IPv4 prefix"
30+
- prefix: "2001:db8::/32"
31+
origin: "AS12345"
32+
roas:
33+
- prefix: "192.0.2.0/24"
34+
origin: "AS12345"
35+
max_length: 24
36+
- prefix: "2001:db8::/32"
37+
origin: "AS12345"
38+
```
39+
40+
`roas` is optional. If omitted, only route objects are synced. ROA sync only manages prefixes explicitly listed — other ROAs in the account are left untouched.
41+
42+
## Credentials
43+
44+
The following secrets are read from 1Password via the `op` CLI:
45+
46+
| Secret | Used for |
47+
|--------|----------|
48+
| `op://Code/Mozilla - RIPE NNC/username` | RIPE DB REST API (Basic auth) |
49+
| `op://Code/Mozilla - RIPE NNC/credential` | RIPE DB REST API (Basic auth) |
50+
| `op://Code/Mozilla - RIPE NNC/RPKI API Key` | RIPE RPKI Management API |
51+
52+
## Usage
53+
54+
```bash
55+
# Dry-run against the RIPE test database (default)
56+
uv run rir-updater config.yaml
57+
58+
# Dry-run against production
59+
uv run rir-updater config.yaml --production
60+
61+
# Apply changes to production
62+
uv run rir-updater config.yaml --production --commit
63+
64+
# Set up the RIPE test database with objects replicated from production
65+
uv run rir-updater config.yaml --setup-test
66+
```
67+
68+
### Test database bootstrap
69+
70+
The first time you use `--setup-test`, the mntner must be created manually via the RIPE web UI at [apps-test.db.ripe.net](https://apps-test.db.ripe.net) — the API does not allow creating the first mntner programmatically due to a circular person↔mntner dependency. The tool will print instructions if the mntner is not found.
71+
72+
## Development
73+
74+
```bash
75+
uv run ruff check . # lint
76+
uv run ruff format . # format
77+
uv run pytest # test
78+
```

config.example.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
ripe:
22
maintainer: "MAINT-AS12345"
3+
credentials:
4+
db_username: "op://vault/item/username"
5+
db_password: "op://vault/item/password"
6+
rpki_api_key: "op://vault/item/rpki-api-key"
37
sso_emails:
48
- "admin1@example.com"
59
- "admin2@example.com"

src/rir_updater/config.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,8 +73,15 @@ def validate_max_length(cls, v: int | None, info) -> int | None:
7373
return v
7474

7575

76+
class RipeCredentials(BaseModel):
77+
db_username: str
78+
db_password: str
79+
rpki_api_key: str
80+
81+
7682
class RipeConfig(BaseModel):
7783
maintainer: str
84+
credentials: RipeCredentials
7885
sso_emails: list[str] = []
7986
routes: list[RouteObject] = []
8087
roas: list[ROA] = []

src/rir_updater/credentials.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,15 +23,15 @@ def read_op(reference: str) -> str:
2323
) from e
2424

2525

26-
def get_ripe_db_auth() -> str:
26+
def get_ripe_db_auth(username_ref: str, password_ref: str) -> str:
2727
"""Return base64(username:password) for the RIPE DB REST API Basic auth header."""
2828
import base64
2929

30-
username = read_op("op://Code/Mozilla - RIPE NNC/username")
31-
password = read_op("op://Code/Mozilla - RIPE NNC/credential")
30+
username = read_op(username_ref)
31+
password = read_op(password_ref)
3232
return base64.b64encode(f"{username}:{password}".encode()).decode()
3333

3434

35-
def get_ripe_rpki_key() -> str:
35+
def get_ripe_rpki_key(key_ref: str) -> str:
3636
"""Return the API key for the RIPE RPKI Management API."""
37-
return read_op("op://Code/Mozilla - RIPE NNC/RPKI API Key")
37+
return read_op(key_ref)

src/rir_updater/main.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,9 +49,10 @@ def _run(args, parser):
4949
config = load_config(args.config)
5050

5151
if config.ripe:
52+
creds = config.ripe.credentials
5253
with RipeClient(
53-
db_auth=get_ripe_db_auth(),
54-
rpki_key=get_ripe_rpki_key(),
54+
db_auth=get_ripe_db_auth(creds.db_username, creds.db_password),
55+
rpki_key=get_ripe_rpki_key(creds.rpki_api_key),
5556
maintainer=config.ripe.maintainer,
5657
dry_run=not args.commit,
5758
use_test_env=not args.production,

tests/test_config.py

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,12 +61,23 @@ def test_max_length_less_than_prefix_len(self):
6161

6262

6363
class TestLoadConfig:
64+
CREDS = textwrap.dedent("""\
65+
credentials:
66+
db_username: "op://vault/item/username"
67+
db_password: "op://vault/item/password"
68+
rpki_api_key: "op://vault/item/rpki-api-key"
69+
""")
70+
6471
def test_load_valid_config(self, tmp_path: Path):
6572
cfg = tmp_path / "config.yaml"
6673
cfg.write_text(
6774
textwrap.dedent("""\
6875
ripe:
6976
maintainer: MAINT-AS64496
77+
credentials:
78+
db_username: "op://vault/item/username"
79+
db_password: "op://vault/item/password"
80+
rpki_api_key: "op://vault/item/rpki-api-key"
7081
routes:
7182
- prefix: "192.0.2.0/24"
7283
origin: AS64496
@@ -85,7 +96,16 @@ def test_load_valid_config(self, tmp_path: Path):
8596

8697
def test_empty_ripe_section(self, tmp_path: Path):
8798
cfg = tmp_path / "config.yaml"
88-
cfg.write_text("ripe:\n maintainer: MAINT-AS64496\n")
99+
cfg.write_text(
100+
textwrap.dedent("""\
101+
ripe:
102+
maintainer: MAINT-AS64496
103+
credentials:
104+
db_username: "op://vault/item/username"
105+
db_password: "op://vault/item/password"
106+
rpki_api_key: "op://vault/item/rpki-api-key"
107+
""")
108+
)
89109
config = load_config(cfg)
90110
assert config.ripe.routes == []
91111
assert config.ripe.roas == []
@@ -96,6 +116,10 @@ def test_invalid_prefix_in_config(self, tmp_path: Path):
96116
textwrap.dedent("""\
97117
ripe:
98118
maintainer: MAINT-AS64496
119+
credentials:
120+
db_username: "op://vault/item/username"
121+
db_password: "op://vault/item/password"
122+
rpki_api_key: "op://vault/item/rpki-api-key"
99123
routes:
100124
- prefix: "192.0.2.1/24"
101125
origin: AS64496

0 commit comments

Comments
 (0)