Skip to content

Commit a7d48bc

Browse files
committed
feat(tsigkeys): implement tsigkeys API proxying
1 parent 8bf0201 commit a7d48bc

File tree

5 files changed

+140
-0
lines changed

5 files changed

+140
-0
lines changed

README.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,21 @@ environments:
218218
global_search: true
219219
```
220220

221+
#### Global TSIGKeys
222+
223+
Global TSIGKeys access can be defined under an `environment`.
224+
225+
For this the `Environment` must have the option `global_tsigkeys: true`.
226+
227+
This allows the token to read and modify all TSIGKeys in the PowerDNS.
228+
229+
```yaml
230+
...
231+
environments:
232+
- name: "Test1"
233+
global_tsigkeys: true
234+
```
235+
221236
### Metrics of the proxy
222237

223238
The proxy exposes metrics on the `/metrics` endpoint.

powerdns_api_proxy/config.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,12 @@ def check_acme_record_allowed(zone: ProxyConfigZone, rrset: RRSET) -> bool:
182182
return False
183183

184184

185+
def check_pdns_tsigkeys_allowed(environment: ProxyConfigEnvironment) -> bool:
186+
if environment.global_tsigkeys:
187+
return True
188+
return False
189+
190+
185191
def ensure_rrsets_request_allowed(zone: ProxyConfigZone, request: RRSETRequest) -> bool:
186192
'''Raises HTTPException if RRSET is not allowed'''
187193
if zone.read_only:

powerdns_api_proxy/models.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ class ProxyConfigEnvironment(BaseModel):
5656
zones: list[ProxyConfigZone]
5757
global_read_only: bool = False
5858
global_search: bool = False
59+
global_tsigkeys: bool = False
5960
_zones_lookup: dict[str, ProxyConfigZone] = {}
6061
metrics_proxy: bool = False
6162

@@ -91,6 +92,7 @@ def __hash__(self):
9192
+ self.token_sha512
9293
+ str(self.global_read_only)
9394
+ str(self.global_search)
95+
+ str(self.global_tsigkeys)
9496
+ str(self.zones)
9597
)
9698

powerdns_api_proxy/proxy.py

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
check_pdns_search_allowed,
1616
check_pdns_zone_admin,
1717
check_pdns_zone_allowed,
18+
check_pdns_tsigkeys_allowed,
1819
dependency_check_token_defined,
1920
dependency_metrics_proxy_enabled,
2021
ensure_rrsets_request_allowed,
@@ -442,6 +443,109 @@ async def search_data(
442443
return data
443444

444445

446+
@router_pdns.get('/servers/{server_id}/tsigkeys')
447+
async def list_tsigkeys(
448+
response: Response, server_id: str, X_API_Key: str = Header()
449+
):
450+
'''
451+
Get all TSIGKeys on the server, except the actual key.
452+
453+
<https://doc.powerdns.com/authoritative/http-api/tsigkey.html#get--servers-server_id-tsigkeys>
454+
'''
455+
environment = get_environment_for_token(config, X_API_Key)
456+
if not check_pdns_tsigkeys_allowed(environment):
457+
logger.info(f'TSIGKeys not allowed for environment {environment.name}')
458+
raise ZoneNotAllowedException()
459+
resp = await pdns.get(f'/api/v1/servers/{server_id}/tsigkeys')
460+
response.status_code = resp.status
461+
data = await response_json_or_text(resp)
462+
return data
463+
464+
465+
@router_pdns.get('/servers/{server_id}/tsigkeys/{tsigkey_id}')
466+
async def fetch_tsigkey(
467+
response: Response, server_id: str, tsigkey_id: str, X_API_Key: str = Header()
468+
):
469+
'''
470+
Get a specific TSIGKeys on the server, including the actual key.
471+
472+
<https://doc.powerdns.com/authoritative/http-api/tsigkey.html#get--servers-server_id-tsigkeys-tsigkey_id>
473+
'''
474+
environment = get_environment_for_token(config, X_API_Key)
475+
if not check_pdns_tsigkeys_allowed(environment):
476+
logger.info(f'TSIGKeys not allowed for environment {environment.name}')
477+
raise ZoneNotAllowedException()
478+
resp = await pdns.get(f'/api/v1/servers/{server_id}/tsigkeys/{tsigkey_id}')
479+
response.status_code = resp.status
480+
data = await response_json_or_text(resp)
481+
return data
482+
483+
484+
@router_pdns.post('/servers/{server_id}/tsigkeys')
485+
async def create_tsigkey(
486+
request: Request, response: Response, server_id: str, X_API_Key: str = Header()
487+
):
488+
'''
489+
Add a TSIG key.
490+
491+
This methods add a new TSIGKey. The actual key can be generated by the server or be provided by the client.
492+
493+
<https://doc.powerdns.com/authoritative/http-api/tsigkey.html#post--servers-server_id-tsigkeys>
494+
'''
495+
environment = get_environment_for_token(config, X_API_Key)
496+
if not check_pdns_tsigkeys_allowed(environment):
497+
logger.info(f'TSIGKeys not allowed for environment {environment.name}')
498+
raise ZoneNotAllowedException()
499+
resp = await pdns.post(f'/api/v1/servers/{server_id}/tsigkeys', payload=await request.json())
500+
response.status_code = resp.status
501+
data = await response_json_or_text(resp)
502+
return data
503+
504+
505+
@router_pdns.put('/servers/{server_id}/tsigkeys/{tsigkey_id}')
506+
async def update_tsigkey(
507+
request: Request, response: Response, server_id: str, tsigkey_id: str, X_API_Key: str = Header()
508+
):
509+
'''
510+
The TSIGKey at tsigkey_id can be changed in multiple ways:
511+
512+
* Changing the Name, this will remove the key with tsigkey_id after adding.
513+
* Changing the Algorithm
514+
* Changing the Key
515+
516+
Only the relevant fields have to be provided in the request body.
517+
518+
<https://doc.powerdns.com/authoritative/http-api/tsigkey.html#put--servers-server_id-tsigkeys-tsigkey_id>
519+
'''
520+
environment = get_environment_for_token(config, X_API_Key)
521+
if not check_pdns_tsigkeys_allowed(environment):
522+
logger.info(f'TSIGKeys not allowed for environment {environment.name}')
523+
raise ZoneNotAllowedException()
524+
resp = await pdns.put(f'/api/v1/servers/{server_id}/tsigkeys/{tsigkey_id}', payload=await request.json())
525+
response.status_code = resp.status
526+
data = await response_json_or_text(resp)
527+
return data
528+
529+
530+
@router_pdns.delete('/servers/{server_id}/tsigkeys/{tsigkey_id}')
531+
async def delete_tsigkey(
532+
response: Response, server_id: str, tsigkey_id: str, X_API_Key: str = Header()
533+
):
534+
'''
535+
Delete the TSIGKey with tsigkey_id.
536+
537+
<https://doc.powerdns.com/authoritative/http-api/tsigkey.html#delete--servers-server_id-tsigkeys-tsigkey_id>
538+
'''
539+
environment = get_environment_for_token(config, X_API_Key)
540+
if not check_pdns_tsigkeys_allowed(environment):
541+
logger.info(f'TSIGKeys not allowed for environment {environment.name}')
542+
raise ZoneNotAllowedException()
543+
resp = await pdns.delete(f'/api/v1/servers/{server_id}/tsigkeys/{tsigkey_id}')
544+
response.status_code = resp.status
545+
data = await response_json_or_text(resp)
546+
return data
547+
548+
445549
app.include_router(router_proxy)
446550
app.include_router(router_pdns)
447551
app.include_router(router_health)

tests/unit/config_test.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
check_pdns_search_allowed,
1010
check_pdns_zone_admin,
1111
check_pdns_zone_allowed,
12+
check_pdns_tsigkeys_allowed,
1213
check_rrset_allowed,
1314
check_token_defined,
1415
ensure_rrsets_request_allowed,
@@ -580,3 +581,15 @@ def test_search_allowed_globally():
580581
environment = deepcopy(dummy_proxy_environment)
581582
environment.global_search = True
582583
assert check_pdns_search_allowed(environment, 'test', 'all') is True
584+
585+
586+
def test_tsigkeys_not_allowed():
587+
environment = deepcopy(dummy_proxy_environment)
588+
environment.global_tsigkeys = False
589+
assert check_pdns_tsigkeys_allowed(environment) is False
590+
591+
592+
def test_tsigkeys_allowed_globally():
593+
environment = deepcopy(dummy_proxy_environment)
594+
environment.global_tsigkeys = True
595+
assert check_pdns_tsigkeys_allowed(environment) is True

0 commit comments

Comments
 (0)