]> git.ipfire.org Git - thirdparty/pdns.git/blob - regression-tests.dnsdist/test_API.py
Merge pull request #6713 from mind04/bogus-ring
[thirdparty/pdns.git] / regression-tests.dnsdist / test_API.py
1 #!/usr/bin/env python
2 import os.path
3
4 import json
5 import requests
6 from dnsdisttests import DNSDistTest
7
8 class TestAPIBasics(DNSDistTest):
9
10 _webTimeout = 2.0
11 _webServerPort = 8083
12 _webServerBasicAuthPassword = 'secret'
13 _webServerAPIKey = 'apisecret'
14 # paths accessible using the API key only
15 _apiOnlyPaths = ['/api/v1/servers/localhost/config', '/api/v1/servers/localhost/config/allow-from', '/api/v1/servers/localhost/statistics']
16 # paths accessible using an API key or basic auth
17 _statsPaths = [ '/jsonstat?command=stats', '/jsonstat?command=dynblocklist', '/api/v1/servers/localhost']
18 # paths accessible using basic auth only (list not exhaustive)
19 _basicOnlyPaths = ['/', '/index.html']
20 _config_params = ['_testServerPort', '_webServerPort', '_webServerBasicAuthPassword', '_webServerAPIKey']
21 _config_template = """
22 setACL({"127.0.0.1/32", "::1/128"})
23 newServer{address="127.0.0.1:%s"}
24 webserver("127.0.0.1:%s", "%s", "%s")
25 """
26
27 def testBasicAuth(self):
28 """
29 API: Basic Authentication
30 """
31 for path in self._basicOnlyPaths + self._statsPaths:
32 url = 'http://127.0.0.1:' + str(self._webServerPort) + path
33 r = requests.get(url, auth=('whatever', self._webServerBasicAuthPassword), timeout=self._webTimeout)
34 self.assertTrue(r)
35 self.assertEquals(r.status_code, 200)
36
37 def testXAPIKey(self):
38 """
39 API: X-Api-Key
40 """
41 headers = {'x-api-key': self._webServerAPIKey}
42 for path in self._apiOnlyPaths + self._statsPaths:
43 url = 'http://127.0.0.1:' + str(self._webServerPort) + path
44 r = requests.get(url, headers=headers, timeout=self._webTimeout)
45 self.assertTrue(r)
46 self.assertEquals(r.status_code, 200)
47
48 def testBasicAuthOnly(self):
49 """
50 API: Basic Authentication Only
51 """
52 headers = {'x-api-key': self._webServerAPIKey}
53 for path in self._basicOnlyPaths:
54 url = 'http://127.0.0.1:' + str(self._webServerPort) + path
55 r = requests.get(url, headers=headers, timeout=self._webTimeout)
56 self.assertEquals(r.status_code, 401)
57
58 def testAPIKeyOnly(self):
59 """
60 API: API Key Only
61 """
62 for path in self._apiOnlyPaths:
63 url = 'http://127.0.0.1:' + str(self._webServerPort) + path
64 r = requests.get(url, auth=('whatever', self._webServerBasicAuthPassword), timeout=self._webTimeout)
65 self.assertEquals(r.status_code, 401)
66
67 def testServersLocalhost(self):
68 """
69 API: /api/v1/servers/localhost
70 """
71 headers = {'x-api-key': self._webServerAPIKey}
72 url = 'http://127.0.0.1:' + str(self._webServerPort) + '/api/v1/servers/localhost'
73 r = requests.get(url, headers=headers, timeout=self._webTimeout)
74 self.assertTrue(r)
75 self.assertEquals(r.status_code, 200)
76 self.assertTrue(r.json())
77 content = r.json()
78
79 self.assertEquals(content['daemon_type'], 'dnsdist')
80
81 rule_groups = ['response-rules', 'cache-hit-response-rules', 'self-answered-response-rules']
82 for key in ['version', 'acl', 'local', 'rules', 'servers', 'frontends', 'pools'] + rule_groups:
83 self.assertIn(key, content)
84
85 for rule in content['rules']:
86 for key in ['id', 'matches', 'rule', 'action', 'uuid']:
87 self.assertIn(key, rule)
88 for key in ['id', 'matches']:
89 self.assertTrue(rule[key] >= 0)
90
91 for rule_group in rule_groups:
92 for rule in content[rule_group]:
93 for key in ['id', 'matches', 'rule', 'action', 'uuid']:
94 self.assertIn(key, rule)
95 for key in ['id', 'matches']:
96 self.assertTrue(rule[key] >= 0)
97
98 for server in content['servers']:
99 for key in ['id', 'latency', 'name', 'weight', 'outstanding', 'qpsLimit',
100 'reuseds', 'state', 'address', 'pools', 'qps', 'queries', 'order', 'sendErrors',
101 'dropRate']:
102 self.assertIn(key, server)
103
104 for key in ['id', 'latency', 'weight', 'outstanding', 'qpsLimit', 'reuseds',
105 'qps', 'queries', 'order']:
106 self.assertTrue(server[key] >= 0)
107
108 self.assertTrue(server['state'] in ['up', 'down', 'UP', 'DOWN'])
109
110 for frontend in content['frontends']:
111 for key in ['id', 'address', 'udp', 'tcp', 'queries']:
112 self.assertIn(key, frontend)
113
114 for key in ['id', 'queries']:
115 self.assertTrue(frontend[key] >= 0)
116
117 for pool in content['pools']:
118 for key in ['id', 'name', 'cacheSize', 'cacheEntries', 'cacheHits', 'cacheMisses', 'cacheDeferredInserts', 'cacheDeferredLookups', 'cacheLookupCollisions', 'cacheInsertCollisions', 'cacheTTLTooShorts']:
119 self.assertIn(key, pool)
120
121 for key in ['id', 'cacheSize', 'cacheEntries', 'cacheHits', 'cacheMisses', 'cacheDeferredInserts', 'cacheDeferredLookups', 'cacheLookupCollisions', 'cacheInsertCollisions', 'cacheTTLTooShorts']:
122 self.assertTrue(pool[key] >= 0)
123
124 def testServersIDontExist(self):
125 """
126 API: /api/v1/servers/idontexist (should be 404)
127 """
128 headers = {'x-api-key': self._webServerAPIKey}
129 url = 'http://127.0.0.1:' + str(self._webServerPort) + '/api/v1/servers/idontexist'
130 r = requests.get(url, headers=headers, timeout=self._webTimeout)
131 self.assertEquals(r.status_code, 404)
132
133 def testServersLocalhostConfig(self):
134 """
135 API: /api/v1/servers/localhost/config
136 """
137 headers = {'x-api-key': self._webServerAPIKey}
138 url = 'http://127.0.0.1:' + str(self._webServerPort) + '/api/v1/servers/localhost/config'
139 r = requests.get(url, headers=headers, timeout=self._webTimeout)
140 self.assertTrue(r)
141 self.assertEquals(r.status_code, 200)
142 self.assertTrue(r.json())
143 content = r.json()
144 values = {}
145 for entry in content:
146 for key in ['type', 'name', 'value']:
147 self.assertIn(key, entry)
148
149 self.assertEquals(entry['type'], 'ConfigSetting')
150 values[entry['name']] = entry['value']
151
152 for key in ['acl', 'control-socket', 'ecs-override', 'ecs-source-prefix-v4',
153 'ecs-source-prefix-v6', 'fixup-case', 'max-outstanding', 'server-policy',
154 'stale-cache-entries-ttl', 'tcp-recv-timeout', 'tcp-send-timeout',
155 'truncate-tc', 'verbose', 'verbose-health-checks']:
156 self.assertIn(key, values)
157
158 for key in ['max-outstanding', 'stale-cache-entries-ttl', 'tcp-recv-timeout',
159 'tcp-send-timeout']:
160 self.assertTrue(values[key] >= 0)
161
162 self.assertTrue(values['ecs-source-prefix-v4'] >= 0 and values['ecs-source-prefix-v4'] <= 32)
163 self.assertTrue(values['ecs-source-prefix-v6'] >= 0 and values['ecs-source-prefix-v6'] <= 128)
164
165 def testServersLocalhostConfigAllowFrom(self):
166 """
167 API: /api/v1/servers/localhost/config/allow-from
168 """
169 headers = {'x-api-key': self._webServerAPIKey}
170 url = 'http://127.0.0.1:' + str(self._webServerPort) + '/api/v1/servers/localhost/config/allow-from'
171 r = requests.get(url, headers=headers, timeout=self._webTimeout)
172 self.assertTrue(r)
173 self.assertEquals(r.status_code, 200)
174 self.assertTrue(r.json())
175 content = r.json()
176 for key in ['type', 'name', 'value']:
177 self.assertIn(key, content)
178
179 self.assertEquals(content['name'], 'allow-from')
180 self.assertEquals(content['type'], 'ConfigSetting')
181 self.assertEquals(content['value'], ["127.0.0.1/32", "::1/128"])
182
183 def testServersLocalhostConfigAllowFromPut(self):
184 """
185 API: PUT /api/v1/servers/localhost/config/allow-from (should be refused)
186
187 The API is read-only by default, so this should be refused
188 """
189 newACL = ["192.0.2.0/24", "198.51.100.0/24", "203.0.113.0/24"]
190 payload = json.dumps({"name": "allow-from",
191 "type": "ConfigSetting",
192 "value": newACL})
193 headers = {'x-api-key': self._webServerAPIKey}
194 url = 'http://127.0.0.1:' + str(self._webServerPort) + '/api/v1/servers/localhost/config/allow-from'
195 r = requests.put(url, headers=headers, timeout=self._webTimeout, data=payload)
196 self.assertFalse(r)
197 self.assertEquals(r.status_code, 405)
198
199 def testServersLocalhostStatistics(self):
200 """
201 API: /api/v1/servers/localhost/statistics
202 """
203 headers = {'x-api-key': self._webServerAPIKey}
204 url = 'http://127.0.0.1:' + str(self._webServerPort) + '/api/v1/servers/localhost/statistics'
205 r = requests.get(url, headers=headers, timeout=self._webTimeout)
206 self.assertTrue(r)
207 self.assertEquals(r.status_code, 200)
208 self.assertTrue(r.json())
209 content = r.json()
210 values = {}
211 for entry in content:
212 self.assertIn('type', entry)
213 self.assertIn('name', entry)
214 self.assertIn('value', entry)
215 self.assertEquals(entry['type'], 'StatisticItem')
216 values[entry['name']] = entry['value']
217
218 expected = ['responses', 'servfail-responses', 'queries', 'acl-drops',
219 'rule-drop', 'rule-nxdomain', 'rule-refused', 'self-answered', 'downstream-timeouts',
220 'downstream-send-errors', 'trunc-failures', 'no-policy', 'latency0-1',
221 'latency1-10', 'latency10-50', 'latency50-100', 'latency100-1000',
222 'latency-slow', 'latency-avg100', 'latency-avg1000', 'latency-avg10000',
223 'latency-avg1000000', 'uptime', 'real-memory-usage', 'noncompliant-queries',
224 'noncompliant-responses', 'rdqueries', 'empty-queries', 'cache-hits',
225 'cache-misses', 'cpu-user-msec', 'cpu-sys-msec', 'fd-usage', 'dyn-blocked',
226 'dyn-block-nmg-size', 'rule-servfail']
227
228 for key in expected:
229 self.assertIn(key, values)
230 self.assertTrue(values[key] >= 0)
231
232 for key in values:
233 self.assertIn(key, expected)
234
235 def testJsonstatStats(self):
236 """
237 API: /jsonstat?command=stats
238 """
239 headers = {'x-api-key': self._webServerAPIKey}
240 url = 'http://127.0.0.1:' + str(self._webServerPort) + '/jsonstat?command=stats'
241 r = requests.get(url, headers=headers, timeout=self._webTimeout)
242 self.assertTrue(r)
243 self.assertEquals(r.status_code, 200)
244 self.assertTrue(r.json())
245 content = r.json()
246
247 expected = ['responses', 'servfail-responses', 'queries', 'acl-drops',
248 'rule-drop', 'rule-nxdomain', 'rule-refused', 'self-answered', 'downstream-timeouts',
249 'downstream-send-errors', 'trunc-failures', 'no-policy', 'latency0-1',
250 'latency1-10', 'latency10-50', 'latency50-100', 'latency100-1000',
251 'latency-slow', 'latency-avg100', 'latency-avg1000', 'latency-avg10000',
252 'latency-avg1000000', 'uptime', 'real-memory-usage', 'noncompliant-queries',
253 'noncompliant-responses', 'rdqueries', 'empty-queries', 'cache-hits',
254 'cache-misses', 'cpu-user-msec', 'cpu-sys-msec', 'fd-usage', 'dyn-blocked',
255 'dyn-block-nmg-size', 'packetcache-hits', 'packetcache-misses', 'over-capacity-drops',
256 'too-old-drops']
257
258 for key in expected:
259 self.assertIn(key, content)
260 self.assertTrue(content[key] >= 0)
261
262 def testJsonstatDynblocklist(self):
263 """
264 API: /jsonstat?command=dynblocklist
265 """
266 headers = {'x-api-key': self._webServerAPIKey}
267 url = 'http://127.0.0.1:' + str(self._webServerPort) + '/jsonstat?command=dynblocklist'
268 r = requests.get(url, headers=headers, timeout=self._webTimeout)
269 self.assertTrue(r)
270 self.assertEquals(r.status_code, 200)
271
272 content = r.json()
273
274 if content:
275 for key in ['reason', 'seconds', 'blocks']:
276 self.assertIn(key, content)
277
278 for key in ['blocks']:
279 self.assertTrue(content[key] >= 0)
280
281 class TestAPIServerDown(DNSDistTest):
282
283 _webTimeout = 2.0
284 _webServerPort = 8083
285 _webServerBasicAuthPassword = 'secret'
286 _webServerAPIKey = 'apisecret'
287 # paths accessible using the API key
288 _config_params = ['_testServerPort', '_webServerPort', '_webServerBasicAuthPassword', '_webServerAPIKey']
289 _config_template = """
290 setACL({"127.0.0.1/32", "::1/128"})
291 newServer{address="127.0.0.1:%s"}
292 getServer(0):setDown()
293 webserver("127.0.0.1:%s", "%s", "%s")
294 """
295
296 def testServerDownNoLatencyLocalhost(self):
297 """
298 API: /api/v1/servers/localhost, no latency for a down server
299 """
300 headers = {'x-api-key': self._webServerAPIKey}
301 url = 'http://127.0.0.1:' + str(self._webServerPort) + '/api/v1/servers/localhost'
302 r = requests.get(url, headers=headers, timeout=self._webTimeout)
303 self.assertTrue(r)
304 self.assertEquals(r.status_code, 200)
305 self.assertTrue(r.json())
306 content = r.json()
307
308 self.assertEquals(content['servers'][0]['latency'], None)
309
310 class TestAPIWritable(DNSDistTest):
311
312 _webTimeout = 2.0
313 _webServerPort = 8083
314 _webServerBasicAuthPassword = 'secret'
315 _webServerAPIKey = 'apisecret'
316 _APIWriteDir = '/tmp'
317 _config_params = ['_testServerPort', '_webServerPort', '_webServerBasicAuthPassword', '_webServerAPIKey', '_APIWriteDir']
318 _config_template = """
319 setACL({"127.0.0.1/32", "::1/128"})
320 newServer{address="127.0.0.1:%s"}
321 webserver("127.0.0.1:%s", "%s", "%s")
322 setAPIWritable(true, "%s")
323 """
324
325 def testSetACL(self):
326 """
327 API: Set ACL
328 """
329 headers = {'x-api-key': self._webServerAPIKey}
330 url = 'http://127.0.0.1:' + str(self._webServerPort) + '/api/v1/servers/localhost/config/allow-from'
331 r = requests.get(url, headers=headers, timeout=self._webTimeout)
332 self.assertTrue(r)
333 self.assertEquals(r.status_code, 200)
334 self.assertTrue(r.json())
335 content = r.json()
336 self.assertEquals(content['value'], ["127.0.0.1/32", "::1/128"])
337
338 newACL = ["192.0.2.0/24", "198.51.100.0/24", "203.0.113.0/24"]
339 payload = json.dumps({"name": "allow-from",
340 "type": "ConfigSetting",
341 "value": newACL})
342 r = requests.put(url, headers=headers, timeout=self._webTimeout, data=payload)
343 self.assertTrue(r)
344 self.assertEquals(r.status_code, 200)
345 self.assertTrue(r.json())
346 content = r.json()
347 self.assertEquals(content['value'], newACL)
348
349 r = requests.get(url, headers=headers, timeout=self._webTimeout)
350 self.assertTrue(r)
351 self.assertEquals(r.status_code, 200)
352 self.assertTrue(r.json())
353 content = r.json()
354 self.assertEquals(content['value'], newACL)
355
356 configFile = self._APIWriteDir + '/' + 'acl.conf'
357 self.assertTrue(os.path.isfile(configFile))
358 fileContent = None
359 with open(configFile, 'rt') as f:
360 fileContent = f.read()
361
362 self.assertEquals(fileContent, """-- Generated by the REST API, DO NOT EDIT
363 setACL({"192.0.2.0/24", "198.51.100.0/24", "203.0.113.0/24"})
364 """)