Skip to content

Commit 79451e2

Browse files
committed
feat: config & statistics routes
1 parent 0eab3c6 commit 79451e2

6 files changed

Lines changed: 116 additions & 12 deletions

File tree

README.md

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,38 @@ environments:
254254
cryptokeys: true
255255
```
256256

257+
#### Global Config
258+
259+
Global configuration access can be defined under an `environment`.
260+
261+
For this the `environment` must have the option `global_config: true`.
262+
263+
This allows the token to read PowerDNS server configuration via:
264+
- `GET /api/v1/servers/:server_id/config` - Returns all ConfigSettings
265+
- `GET /api/v1/servers/:server_id/config/:config_setting_name` - Retrieve a single setting
266+
267+
```yaml
268+
...
269+
environments:
270+
- name: "Test1"
271+
global_config: true
272+
```
273+
274+
#### Global Statistics
275+
276+
Global statistics access can be defined under an `environment`.
277+
278+
For this the `environment` must have the option `global_statistics: true`.
279+
280+
This allows the token to read PowerDNS server statistics.
281+
282+
```yaml
283+
...
284+
environments:
285+
- name: "Test1"
286+
global_statistics: true
287+
```
288+
257289
### Metrics of the proxy
258290

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

powerdns_api_proxy/config.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,14 @@ def check_pdns_tsigkeys_allowed(environment: ProxyConfigEnvironment) -> bool:
212212
return False
213213

214214

215+
def check_pdns_config_allowed(environment: ProxyConfigEnvironment) -> bool:
216+
return environment.global_config
217+
218+
219+
def check_pdns_statistics_allowed(environment: ProxyConfigEnvironment) -> bool:
220+
return environment.global_statistics
221+
222+
215223
def ensure_rrsets_request_allowed(zone: ProxyConfigZone, request: RRSETRequest) -> bool:
216224
"""Raises HTTPException if RRSET is not allowed"""
217225
if zone.read_only:

powerdns_api_proxy/models.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,8 @@ class ProxyConfigEnvironment(BaseModel):
6060
global_search: bool = False
6161
global_cryptokeys: bool = False
6262
global_tsigkeys: bool = False
63+
global_config: bool = False
64+
global_statistics: bool = False
6365
_zones_lookup: dict[str, ProxyConfigZone] = {}
6466
metrics_proxy: bool = False
6567

powerdns_api_proxy/proxy.py

Lines changed: 46 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@
1919
check_pdns_search_allowed,
2020
check_pdns_cryptokeys_allowed,
2121
check_pdns_tsigkeys_allowed,
22+
check_pdns_config_allowed,
23+
check_pdns_statistics_allowed,
2224
check_pdns_zone_admin,
2325
check_pdns_zone_allowed,
2426
dependency_check_token_defined,
@@ -216,31 +218,64 @@ async def get_server(server_id: str):
216218

217219

218220
@router_pdns.get(
219-
"/servers/{server_id}/configuration",
221+
"/servers/{server_id}/config",
220222
)
221-
async def get_configuration(server_id: str):
223+
async def get_configuration(server_id: str, X_API_Key: str = Header()):
222224
"""
223225
Retrieve a list of configuration items for the server.
224-
Currently returns empty, as we don't want to expose the global backend configuration.
226+
227+
Requires global_config permission.
225228
"""
226-
_ = server_id
227-
raise RessourceNotAllowedException()
229+
environment = get_environment_for_token(config, X_API_Key)
230+
if not check_pdns_config_allowed(environment):
231+
raise RessourceNotAllowedException()
232+
233+
resp = await pdns.get(f"/api/v1/servers/{server_id}/config")
234+
pdns_response = await handle_pdns_response(resp)
235+
status_code = pdns_response.raise_for_error()
236+
return JSONResponse(content=pdns_response.data, status_code=status_code)
228237

229238

230239
@router_pdns.get(
231-
"/servers/{server_id}/statistics",
240+
"/servers/{server_id}/config/{config_setting_name}",
232241
)
233-
async def get_statistics(
234-
server_id: str,
242+
async def get_configuration_setting(
243+
server_id: str, config_setting_name: str, X_API_Key: str = Header()
235244
):
245+
"""
246+
Retrieve a single configuration setting.
247+
248+
Requires global_config permission.
249+
"""
250+
environment = get_environment_for_token(config, X_API_Key)
251+
if not check_pdns_config_allowed(environment):
252+
raise RessourceNotAllowedException()
253+
254+
resp = await pdns.get(f"/api/v1/servers/{server_id}/config/{config_setting_name}")
255+
pdns_response = await handle_pdns_response(resp)
256+
status_code = pdns_response.raise_for_error()
257+
return JSONResponse(content=pdns_response.data, status_code=status_code)
258+
259+
260+
@router_pdns.get(
261+
"/servers/{server_id}/statistics",
262+
)
263+
async def get_statistics(server_id: str, X_API_Key: str = Header()):
236264
"""
237265
Retrieve a list of statistics about the server.
238-
Currently returns empty, as we don't want to expose the global backend statistics.
266+
267+
Requires global_statistics permission.
239268
240269
<https://doc.powerdns.com/authoritative/http-api/statistics.html#get--servers-server_id-statistics>
241270
"""
242-
_ = server_id
243-
raise RessourceNotAllowedException()
271+
environment = get_environment_for_token(config, X_API_Key)
272+
if not check_pdns_statistics_allowed(environment):
273+
raise RessourceNotAllowedException()
274+
275+
resp = await pdns.get(f"/api/v1/servers/{server_id}/statistics")
276+
pdns_response = await handle_pdns_response(resp)
277+
status_code = pdns_response.raise_for_error()
278+
return JSONResponse(content=pdns_response.data, status_code=status_code)
244279

245280

246281
@router_pdns.get(

tests/unit/config_test.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
check_pdns_search_allowed,
1010
check_pdns_cryptokeys_allowed,
1111
check_pdns_tsigkeys_allowed,
12+
check_pdns_config_allowed,
13+
check_pdns_statistics_allowed,
1214
check_pdns_zone_admin,
1315
check_pdns_zone_allowed,
1416
check_rrset_allowed,
@@ -684,6 +686,30 @@ def test_tsigkeys_allowed_globally():
684686
assert check_pdns_tsigkeys_allowed(environment) is True
685687

686688

689+
def test_config_not_allowed():
690+
environment = deepcopy(dummy_proxy_environment)
691+
environment.global_config = False
692+
assert check_pdns_config_allowed(environment) is False
693+
694+
695+
def test_config_allowed_globally():
696+
environment = deepcopy(dummy_proxy_environment)
697+
environment.global_config = True
698+
assert check_pdns_config_allowed(environment) is True
699+
700+
701+
def test_statistics_not_allowed():
702+
environment = deepcopy(dummy_proxy_environment)
703+
environment.global_statistics = False
704+
assert check_pdns_statistics_allowed(environment) is False
705+
706+
707+
def test_statistics_allowed_globally():
708+
environment = deepcopy(dummy_proxy_environment)
709+
environment.global_statistics = True
710+
assert check_pdns_statistics_allowed(environment) is True
711+
712+
687713
def test_global_read_only_without_zones():
688714
"""Test that global_read_only=True allows empty zones list"""
689715
env = ProxyConfigEnvironment(

tests/unit/proxy_test.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,8 @@ def _token_missing_request(client: TestClient, method: str, path: str):
120120
"/api",
121121
"/api/v1/servers",
122122
"/api/v1/servers/localhost",
123-
"/api/v1/servers/localhost/configuration",
123+
"/api/v1/servers/localhost/config",
124+
"/api/v1/servers/localhost/config/api-key",
124125
"/api/v1/servers/localhost/statistics",
125126
"/api/v1/servers/localhost/zones",
126127
"/api/v1/servers/localhost/zones/test.example.com.",

0 commit comments

Comments
 (0)