test_rrsets.py 73 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634
  1. from contextlib import nullcontext
  2. from ipaddress import IPv4Network
  3. import re
  4. from itertools import product
  5. from math import ceil, floor
  6. from django.conf import settings
  7. from django.core.exceptions import ValidationError
  8. from django.core.management import call_command
  9. from rest_framework import status
  10. from desecapi.models import BlockedSubnet, Domain, RR, RRset
  11. from desecapi.models.records import RR_SET_TYPES_AUTOMATIC, RR_SET_TYPES_UNSUPPORTED
  12. from desecapi.tests.base import DesecTestCase, AuthenticatedRRSetBaseTestCase
  13. class UnauthenticatedRRSetTestCase(DesecTestCase):
  14. def test_unauthorized_access(self):
  15. url = self.reverse("v1:rrsets", name="example.com")
  16. for method in [
  17. self.client.get,
  18. self.client.post,
  19. self.client.put,
  20. self.client.delete,
  21. self.client.patch,
  22. ]:
  23. response = method(url)
  24. self.assertStatus(response, status.HTTP_401_UNAUTHORIZED)
  25. class AuthenticatedRRSetTestCase(AuthenticatedRRSetBaseTestCase):
  26. def test_subname_validity(self):
  27. for subname in [
  28. "aEroport",
  29. "AEROPORT",
  30. "aéroport",
  31. "a" * 64,
  32. ]:
  33. with self.assertRaises(ValidationError):
  34. RRset(domain=self.my_domain, subname=subname, ttl=60, type="A").save()
  35. for subname in [
  36. "aeroport",
  37. "a" * 63,
  38. ]:
  39. RRset(domain=self.my_domain, subname=subname, ttl=60, type="A").save()
  40. def test_retrieve_my_rr_sets(self):
  41. for response in [
  42. self.client.get_rr_sets(self.my_domain.name),
  43. self.client.get_rr_sets(self.my_domain.name, subname=""),
  44. ]:
  45. self.assertStatus(response, status.HTTP_200_OK)
  46. self.assertEqual(len(response.data), 1, response.data)
  47. def test_retrieve_my_rr_sets_pagination(self):
  48. def convert_links(links):
  49. mapping = {}
  50. for link in links.split(", "):
  51. _url, label = link.split("; ")
  52. label = re.search('rel="(.*)"', label).group(1)
  53. _url = _url[1:-1]
  54. assert label not in mapping
  55. mapping[label] = _url
  56. return mapping
  57. def assertPaginationResponse(
  58. response, expected_length, expected_directional_links=[]
  59. ):
  60. self.assertStatus(response, status.HTTP_200_OK)
  61. self.assertEqual(len(response.data), expected_length)
  62. _links = convert_links(response["Link"])
  63. self.assertEqual(
  64. len(_links), len(expected_directional_links) + 1
  65. ) # directional links, plus "first"
  66. self.assertTrue(_links["first"].endswith("/?cursor="))
  67. for directional_link in expected_directional_links:
  68. self.assertEqual(
  69. _links["first"].find("/?cursor="),
  70. _links[directional_link].find("/?cursor="),
  71. )
  72. self.assertTrue(len(_links[directional_link]) > len(_links["first"]))
  73. # Prepare extra records so that we get three pages (total: n + 1)
  74. n = int(settings.REST_FRAMEWORK["PAGE_SIZE"] * 2.5)
  75. RRset.objects.bulk_create(
  76. [
  77. RRset(domain=self.my_domain, subname=str(i), ttl=123, type="A")
  78. for i in range(n)
  79. ]
  80. )
  81. # No pagination
  82. response = self.client.get_rr_sets(self.my_domain.name)
  83. self.assertStatus(response, status.HTTP_400_BAD_REQUEST)
  84. self.assertEqual(
  85. response.data["detail"],
  86. f'Pagination required. You can query up to {settings.REST_FRAMEWORK["PAGE_SIZE"]} items at a time ({n+1} total). '
  87. "Please use the `first` page link (see Link header).",
  88. )
  89. links = convert_links(response["Link"])
  90. self.assertEqual(len(links), 1)
  91. self.assertTrue(links["first"].endswith("/?cursor="))
  92. # First page
  93. url = links["first"]
  94. response = self.client.get(url)
  95. assertPaginationResponse(
  96. response, settings.REST_FRAMEWORK["PAGE_SIZE"], ["next"]
  97. )
  98. # Next
  99. url = convert_links(response["Link"])["next"]
  100. response = self.client.get(url)
  101. assertPaginationResponse(
  102. response, settings.REST_FRAMEWORK["PAGE_SIZE"], ["next", "prev"]
  103. )
  104. data_next = response.data.copy()
  105. # Next-next (last) page
  106. url = convert_links(response["Link"])["next"]
  107. response = self.client.get(url)
  108. assertPaginationResponse(response, n / 5 + 1, ["prev"])
  109. # Prev
  110. url = convert_links(response["Link"])["prev"]
  111. response = self.client.get(url)
  112. assertPaginationResponse(
  113. response, settings.REST_FRAMEWORK["PAGE_SIZE"], ["next", "prev"]
  114. )
  115. # Make sure that one step forward equals two steps forward and one step back
  116. self.assertEqual(response.data, data_next)
  117. def test_retrieve_other_rr_sets(self):
  118. self.assertStatus(
  119. self.client.get_rr_sets(self.other_domain.name), status.HTTP_404_NOT_FOUND
  120. )
  121. self.assertStatus(
  122. self.client.get_rr_sets(self.other_domain.name, subname="test"),
  123. status.HTTP_404_NOT_FOUND,
  124. )
  125. self.assertStatus(
  126. self.client.get_rr_sets(self.other_domain.name, type="A"),
  127. status.HTTP_404_NOT_FOUND,
  128. )
  129. def test_retrieve_my_rr_sets_filter(self):
  130. response = self.client.get_rr_sets(self.my_rr_set_domain.name, query="?cursor=")
  131. self.assertStatus(response, status.HTTP_200_OK)
  132. expected_number_of_rrsets = min(
  133. len(self._test_rr_sets()), settings.REST_FRAMEWORK["PAGE_SIZE"]
  134. )
  135. self.assertEqual(len(response.data), expected_number_of_rrsets)
  136. for subname in self.SUBNAMES:
  137. response = self.client.get_rr_sets(
  138. self.my_rr_set_domain.name, subname=subname
  139. )
  140. self.assertStatus(response, status.HTTP_200_OK)
  141. self.assertRRSetsCount(
  142. response.data,
  143. [dict(subname=subname)],
  144. count=len(self._test_rr_sets(subname=subname)),
  145. )
  146. for type_ in self.ALLOWED_TYPES:
  147. response = self.client.get_rr_sets(self.my_rr_set_domain.name, type=type_)
  148. self.assertStatus(response, status.HTTP_200_OK)
  149. def test_create_my_rr_sets(self):
  150. for subname in [
  151. None,
  152. "create-my-rr-sets",
  153. "foo.create-my-rr-sets",
  154. "bar.baz.foo.create-my-rr-sets",
  155. ]:
  156. for data in [
  157. {"subname": subname, "records": ["1.2.3.4"], "ttl": 3660, "type": "A"},
  158. {
  159. "subname": "" if subname is None else subname,
  160. "records": ["desec.io."],
  161. "ttl": 36900,
  162. "type": "PTR",
  163. },
  164. {
  165. "subname": "" if subname is None else subname,
  166. "ttl": 3650,
  167. "type": "TXT",
  168. "records": ['"foo"'],
  169. },
  170. {
  171. "subname": f"{subname}.cname".lower(),
  172. "ttl": 3600,
  173. "type": "CNAME",
  174. "records": ["example.com."],
  175. },
  176. ]:
  177. # Try POST with missing subname
  178. if data["subname"] is None:
  179. data.pop("subname")
  180. with self.assertRequests(
  181. self.requests_desec_rr_sets_update(name=self.my_empty_domain.name)
  182. ):
  183. response = self.client.post_rr_set(
  184. domain_name=self.my_empty_domain.name, **data
  185. )
  186. self.assertStatus(response, status.HTTP_201_CREATED)
  187. self.assertTrue(
  188. all(
  189. field in response.data
  190. for field in [
  191. "created",
  192. "domain",
  193. "subname",
  194. "name",
  195. "records",
  196. "ttl",
  197. "type",
  198. "touched",
  199. ]
  200. )
  201. )
  202. self.assertEqual(
  203. self.my_empty_domain.touched,
  204. max(
  205. rrset.touched
  206. for rrset in self.my_empty_domain.rrset_set.all()
  207. ),
  208. )
  209. # Check for uniqueness on second attempt
  210. response = self.client.post_rr_set(
  211. domain_name=self.my_empty_domain.name, **data
  212. )
  213. self.assertContains(
  214. response,
  215. "Another RRset with the same subdomain and type exists for this domain.",
  216. status_code=status.HTTP_400_BAD_REQUEST,
  217. )
  218. response = self.client.get_rr_sets(self.my_empty_domain.name)
  219. self.assertStatus(response, status.HTTP_200_OK)
  220. self.assertRRSetsCount(response.data, [data])
  221. response = self.client.get_rr_set(
  222. self.my_empty_domain.name, data.get("subname", ""), data["type"]
  223. )
  224. self.assertStatus(response, status.HTTP_200_OK)
  225. self.assertRRSet(response.data, **data)
  226. def test_create_my_rr_sets_type_restriction(self):
  227. for subname in [
  228. "",
  229. "create-my-rr-sets",
  230. "foo.create-my-rr-sets",
  231. "bar.baz.foo.create-my-rr-sets",
  232. ]:
  233. for data in (
  234. [
  235. {"subname": subname, "ttl": 60, "type": "a"},
  236. {
  237. "subname": subname,
  238. "records": ["10 example.com."],
  239. "ttl": 60,
  240. "type": "txt",
  241. },
  242. ]
  243. + [
  244. {
  245. "subname": subname,
  246. "records": ["10 example.com."],
  247. "ttl": 60,
  248. "type": type_,
  249. }
  250. for type_ in self.UNSUPPORTED_TYPES
  251. ]
  252. + [
  253. {
  254. "subname": subname,
  255. "records": [
  256. "get.desec.io. get.desec.io. 2584 10800 3600 604800 60"
  257. ],
  258. "ttl": 60,
  259. "type": type_,
  260. }
  261. for type_ in self.AUTOMATIC_TYPES
  262. ]
  263. ):
  264. response = self.client.post_rr_set(self.my_domain.name, **data)
  265. self.assertStatus(response, status.HTTP_400_BAD_REQUEST)
  266. response = self.client.get_rr_sets(self.my_domain.name)
  267. self.assertStatus(response, status.HTTP_200_OK)
  268. self.assertRRSetsCount(response.data, [data], count=0)
  269. def test_create_my_rr_sets_only_at_apex(self):
  270. for type_, records in {
  271. "DNSKEY": ["257 3 15 l02Woi0iS8Aa25FQkUd9RMzZHJpBoRQwAQEX1SxZJA4="],
  272. }.items():
  273. data = {
  274. "subname": "non-apex",
  275. "ttl": 3600,
  276. "type": type_,
  277. "records": records,
  278. }
  279. r = self.client.post_rr_set(self.my_empty_domain.name, **data)
  280. self.assertContains(
  281. r,
  282. f"{type_} RRset must have empty subname",
  283. status_code=status.HTTP_400_BAD_REQUEST,
  284. )
  285. def test_create_my_rr_sets_restricted_at_apex(self):
  286. for type_, records in {
  287. "CNAME": ["foobar.com."],
  288. "DS": ["45586 5 1 D0FDF996D1AF2CCDBDC942B02CB02D379629E20B"],
  289. }.items():
  290. data = {"subname": "", "ttl": 3600, "type": type_, "records": records}
  291. r = self.client.post_rr_set(self.my_empty_domain.name, **data)
  292. self.assertContains(
  293. r,
  294. f"{type_} RRset cannot have empty subname",
  295. status_code=status.HTTP_400_BAD_REQUEST,
  296. )
  297. def test_create_my_rr_sets_cname_multiple_records(self):
  298. for records in (["foobar.com.", "foobar.com."], ["foobar.com.", "foobar.org."]):
  299. data = {"subname": "asdf", "ttl": 3600, "type": "CNAME", "records": records}
  300. response = self.client.post_rr_set(self.my_empty_domain.name, **data)
  301. self.assertContains(
  302. response,
  303. "CNAME RRset cannot have multiple records",
  304. status_code=status.HTTP_400_BAD_REQUEST,
  305. )
  306. def test_create_my_rr_sets_dname_multiple_records(self):
  307. for records in (["foobar.com.", "foobar.com."], ["foobar.com.", "foobar.org."]):
  308. data = {"subname": "asdf", "ttl": 3600, "type": "DNAME", "records": records}
  309. response = self.client.post_rr_set(self.my_empty_domain.name, **data)
  310. self.assertContains(
  311. response,
  312. "DNAME RRset cannot have multiple records",
  313. status_code=status.HTTP_400_BAD_REQUEST,
  314. )
  315. def test_create_my_rr_sets_cname_exclusivity(self):
  316. self.create_rr_set(self.my_domain, ["1.2.3.4"], type="A", ttl=3600, subname="a")
  317. self.create_rr_set(
  318. self.my_domain, ["example.com."], type="CNAME", ttl=3600, subname="cname"
  319. )
  320. # Can't add a CNAME where something else is
  321. data = {
  322. "subname": "a",
  323. "ttl": 3600,
  324. "type": "CNAME",
  325. "records": ["foobar.com."],
  326. }
  327. response = self.client.post_rr_set(self.my_domain.name, **data)
  328. self.assertStatus(response, status.HTTP_400_BAD_REQUEST)
  329. # Can't add something else where a CNAME is
  330. data = {"subname": "cname", "ttl": 3600, "type": "A", "records": ["4.3.2.1"]}
  331. response = self.client.post_rr_set(self.my_domain.name, **data)
  332. self.assertStatus(response, status.HTTP_400_BAD_REQUEST)
  333. def test_create_my_rr_sets_without_records(self):
  334. for subname in [
  335. "",
  336. "create-my-rr-sets",
  337. "foo.create-my-rr-sets",
  338. "bar.baz.foo.create-my-rr-sets",
  339. ]:
  340. for data in [
  341. {"subname": subname, "records": [], "ttl": 60, "type": "A"},
  342. {"subname": subname, "ttl": 60, "type": "A"},
  343. ]:
  344. response = self.client.post_rr_set(self.my_empty_domain.name, **data)
  345. self.assertStatus(response, status.HTTP_400_BAD_REQUEST)
  346. response = self.client.get_rr_sets(self.my_empty_domain.name)
  347. self.assertStatus(response, status.HTTP_200_OK)
  348. self.assertRRSetsCount(response.data, [], count=0)
  349. def test_create_other_rr_sets(self):
  350. data = {"records": ["1.2.3.4"], "ttl": 60, "type": "A"}
  351. response = self.client.post_rr_set(self.other_domain.name, **data)
  352. self.assertStatus(response, status.HTTP_404_NOT_FOUND)
  353. @staticmethod
  354. def _create_test_txt_record(record, type_="TXT"):
  355. return {
  356. "records": [f"{record}"],
  357. "ttl": 3600,
  358. "type": type_,
  359. "subname": f"name{len(record)}",
  360. }
  361. def test_create_my_rr_sets_chunk_too_long(self):
  362. for l, t in product([1, 255, 256, 498], ["TXT", "SPF"]):
  363. with self.assertRequests(
  364. self.requests_desec_rr_sets_update(self.my_empty_domain.name)
  365. ):
  366. response = self.client.post_rr_set(
  367. self.my_empty_domain.name,
  368. **self._create_test_txt_record(f'"{"A" * l}"', t),
  369. )
  370. self.assertStatus(response, status.HTTP_201_CREATED)
  371. with self.assertRequests(
  372. self.requests_desec_rr_sets_update(self.my_empty_domain.name)
  373. ):
  374. self.client.delete_rr_set(
  375. self.my_empty_domain.name, type_=t, subname=f"name{l+2}"
  376. )
  377. def test_create_my_rr_sets_too_long_content(self):
  378. def token(length):
  379. if length == 0:
  380. return ""
  381. if length <= 255:
  382. return f'"{"A" * length}"'
  383. return f"{token(255)} " * (length // 255) + token(length % 255)
  384. def p2w_length(length):
  385. return ceil(length / 255 * 256)
  386. def w2p_length(length):
  387. return floor(length / 256 * 255)
  388. max_wirelength = 64000
  389. max_preslength = w2p_length(max_wirelength)
  390. assert max_preslength == 63750
  391. assert p2w_length(max_preslength) == 64000
  392. assert p2w_length(max_preslength + 1) == 64002
  393. for t in ["SPF", "TXT"]:
  394. response = self.client.post_rr_set(
  395. self.my_empty_domain.name,
  396. # record of wire length 501 bytes in chunks of max 255 each (RFC 4408)
  397. **self._create_test_txt_record(token(max_preslength + 1), t),
  398. )
  399. self.assertStatus(response, status.HTTP_400_BAD_REQUEST)
  400. self.assertIn(
  401. f"Ensure this value has no more than {max_wirelength} byte in wire format (it has {p2w_length(max_preslength + 1)}).",
  402. str(response.data),
  403. )
  404. with self.assertRequests(
  405. self.requests_desec_rr_sets_update(self.my_empty_domain.name)
  406. ):
  407. response = self.client.post_rr_set(
  408. self.my_empty_domain.name,
  409. # record of wire length 500 bytes in chunks of max 255 each (RFC 4408)
  410. **self._create_test_txt_record(token(max_preslength)),
  411. )
  412. self.assertStatus(response, status.HTTP_201_CREATED)
  413. def test_create_my_rr_sets_too_large_rrset(self):
  414. network = IPv4Network("127.0.0.0/20") # size: 4096 IP addresses
  415. data = {
  416. "records": [str(ip) for ip in network],
  417. "ttl": 3600,
  418. "type": "A",
  419. "subname": "name",
  420. }
  421. response = self.client.post_rr_set(self.my_empty_domain.name, **data)
  422. self.assertStatus(response, status.HTTP_400_BAD_REQUEST)
  423. excess_length = 28743 + len(self.my_empty_domain.name)
  424. self.assertIn(
  425. f"Total length of RRset exceeds limit by {excess_length} bytes.",
  426. str(response.data),
  427. )
  428. def test_create_my_rr_sets_twice(self):
  429. data = {"records": ["1.2.3.4"], "ttl": 3660, "type": "A"}
  430. with self.assertRequests(
  431. self.requests_desec_rr_sets_update(self.my_empty_domain.name)
  432. ):
  433. response = self.client.post_rr_set(self.my_empty_domain.name, **data)
  434. self.assertStatus(response, status.HTTP_201_CREATED)
  435. data["records"][0] = "3.2.2.1"
  436. response = self.client.post_rr_set(self.my_empty_domain.name, **data)
  437. self.assertStatus(response, status.HTTP_400_BAD_REQUEST)
  438. def test_create_my_rr_sets_duplicate_content(self):
  439. for records in [
  440. ["::1", "0::1"],
  441. # TODO add more examples
  442. ]:
  443. data = {"records": records, "ttl": 3660, "type": "AAAA"}
  444. response = self.client.post_rr_set(self.my_empty_domain.name, **data)
  445. self.assertContains(
  446. response, "Duplicate", status_code=status.HTTP_400_BAD_REQUEST
  447. )
  448. def test_create_my_rr_sets_upper_case(self):
  449. for subname in ["asdF", "cAse", "asdf.FOO", "--F", "ALLCAPS"]:
  450. data = {"records": ["1.2.3.4"], "ttl": 60, "type": "A", "subname": subname}
  451. response = self.client.post_rr_set(self.my_empty_domain.name, **data)
  452. self.assertStatus(response, status.HTTP_400_BAD_REQUEST)
  453. self.assertIn("Subname can only use (lowercase)", str(response.data))
  454. def test_create_my_rr_sets_subname_too_long(self):
  455. name = (
  456. "9.1.f.1.0.6.1.d.f.2.2.b.9.1.6.3.c.2.f.7.9.5.2.4.7.8.3.2.6.6.c.6.9.3.5.6.9.2.4.d.c.e.a.e.f.1.8"
  457. ".b.a.c.9.0.6.2.c.b.c.1.6.3.8.2.7.9.0.5.2.a.c.f.f.2.6.a.c.3.c.e.3.0.6.1.8.0.7.4.0.1.0.0.2.ip6.test"
  458. )
  459. domain = self.create_domain(name=name, owner=self.owner)
  460. subname = (
  461. "e.8.c.f.e.0.9.f.4.9.1.f.1.0.6.1.d.f.2.2.b.9.1.6.3.c.9.3.5.6.9.2.4.d.c.e.a.e.f.1.8.b.a.c.9.0"
  462. ".6.2.c.b.c.1.6.3.8.2.6.7.9.0.5.2.a.c.f.f.2.6"
  463. )
  464. data = {"subname": subname, "records": ["1.2.3.4"], "ttl": 3600, "type": "A"}
  465. response = self.client.post_rr_set(domain.name, **data)
  466. self.assertStatus(response, status.HTTP_400_BAD_REQUEST)
  467. self.assertEqual(response.data["subname"][0].code, "name_too_long")
  468. def test_create_my_rr_sets_subname_too_many_dots(self):
  469. for subname in ["dottest.", ".dottest", "dot..test"]:
  470. data = {
  471. "subname": subname,
  472. "records": ["10 example.com."],
  473. "ttl": 3600,
  474. "type": "MX",
  475. }
  476. response = self.client.post_rr_set(self.my_domain.name, **data)
  477. self.assertStatus(response, status.HTTP_400_BAD_REQUEST)
  478. response = self.client.get_rr_sets(self.my_domain.name)
  479. self.assertStatus(response, status.HTTP_200_OK)
  480. self.assertRRSetsCount(response.data, [data], count=0)
  481. def test_create_my_rr_sets_empty_payload(self):
  482. response = self.client.post_rr_set(self.my_empty_domain.name)
  483. self.assertContains(
  484. response, "No data provided", status_code=status.HTTP_400_BAD_REQUEST
  485. )
  486. def test_create_my_rr_sets_cname_two_records(self):
  487. data = {
  488. "subname": "sub",
  489. "records": ["example.com.", "example.org."],
  490. "ttl": 3600,
  491. "type": "CNAME",
  492. }
  493. response = self.client.post_rr_set(self.my_domain.name, **data)
  494. self.assertStatus(response, status.HTTP_400_BAD_REQUEST)
  495. def test_create_my_rr_sets_canonical_content(self):
  496. # TODO fill in more examples
  497. datas = [
  498. # record type: (non-canonical input, canonical output expectation)
  499. ("A", ("127.0.0.1", "127.0.0.1")),
  500. ("AAAA", ("0000::0000:0001", "::1")),
  501. ("AAAA", ("::ffff:127.0.0.1", "::ffff:7f00:1")),
  502. ("AAAA", ("2001:db8::128.2.129.4", "2001:db8::8002:8104")),
  503. ("AFSDB", ("02 turquoise.FEMTO.edu.", "2 turquoise.femto.edu.")),
  504. (
  505. "APL",
  506. (
  507. "2:FF00:0:0:0:0::/8 !1:192.168.38.0/28",
  508. "2:ff00::/8 !1:192.168.38.0/28",
  509. ),
  510. ),
  511. ("CAA", ('0128 "issue" "letsencrypt.org"', '128 issue "letsencrypt.org"')),
  512. (
  513. "CDNSKEY",
  514. (
  515. "0256 03 08 AwEAAday3UX323uVzQqtOMQ7EHQYfD5Ofv4akjQGN2zY5AgB/2jmdR/+ 1PvXFqzKCAGJv4wjABEBNWLLFm7ew1hHMDZEKVL17aml0EBKI6Dsz6Mx t6n7ScvLtHaFRKaxT4i2JxiuVhKdQR9XGMiWAPQKrRM5SLG0P+2F+TLK l3D0L/cD",
  516. "256 3 8 AwEAAday3UX323uVzQqtOMQ7EHQYfD5Ofv4akjQGN2zY5AgB/2jmdR/+1PvXFqzKCAGJv4wjABEBNWLLFm7ew1hHMDZEKVL17aml0EBKI6Dsz6Mxt6n7ScvLtHaFRKaxT4i2JxiuVhKdQR9XGMiWAPQKrRM5SLG0P+2F+TLKl3D0L/cD",
  517. ),
  518. ),
  519. (
  520. "CDNSKEY",
  521. (
  522. "257 3 8 AwEAAcw5QLr0IjC0wKbGoBPQv4qmeqHy9mvL5qGQTuaG5TSrNqEAR6b/ qvxDx6my4JmEmjUPA1JeEI9YfTUieMr2UZflu7aIbZFLw0vqiYrywCGr CHXLalOrEOmrvAxLvq4vHtuTlH7JIszzYBSes8g1vle6KG7xXiP3U5Ll 96Qiu6bZ31rlMQSPB20xbqJJh6psNSrQs41QvdcXAej+K2Hl1Wd8kPri ec4AgiBEh8sk5Pp8W9ROLQ7PcbqqttFaW2m7N/Wy4qcFU13roWKDEAst bxH5CHPoBfZSbIwK4KM6BK/uDHpSPIbiOvOCW+lvu9TAiZPc0oysY6as lO7jXv16Gws=",
  523. "257 3 8 AwEAAcw5QLr0IjC0wKbGoBPQv4qmeqHy9mvL5qGQTuaG5TSrNqEAR6b/qvxDx6my4JmEmjUPA1JeEI9YfTUieMr2UZflu7aIbZFLw0vqiYrywCGrCHXLalOrEOmrvAxLvq4vHtuTlH7JIszzYBSes8g1vle6KG7xXiP3U5Ll96Qiu6bZ31rlMQSPB20xbqJJh6psNSrQs41QvdcXAej+K2Hl1Wd8kPriec4AgiBEh8sk5Pp8W9ROLQ7PcbqqttFaW2m7N/Wy4qcFU13roWKDEAstbxH5CHPoBfZSbIwK4KM6BK/uDHpSPIbiOvOCW+lvu9TAiZPc0oysY6aslO7jXv16Gws=",
  524. ),
  525. ),
  526. (
  527. "CDNSKEY",
  528. (
  529. "257 3 13 aCoEWYBBVsP9Fek2oC8yqU8ocKmnS1iDSFZNORnQuHKtJ9Wpyz+kNryq uB78Pyk/NTEoai5bxoipVQQXzHlzyg==",
  530. "257 3 13 aCoEWYBBVsP9Fek2oC8yqU8ocKmnS1iDSFZNORnQuHKtJ9Wpyz+kNryquB78Pyk/NTEoai5bxoipVQQXzHlzyg==",
  531. ),
  532. ),
  533. ("CDNSKEY", ("0 3 0 AA==", "0 3 0 AA==")),
  534. (
  535. "CDS",
  536. (
  537. "047883 013 02 43BD262211B2A748335149408F67BC95B9A4A3174FD86E6A83830380 446E7AFD",
  538. "47883 13 2 43BD262211B2A748335149408F67BC95B9A4A3174FD86E6A83830380446E7AFD".lower(),
  539. ),
  540. ),
  541. ("CDS", ("0 0 0 00", "0 0 0 00")),
  542. ("CERT", ("04 257 RSASHA256 sadfdd==", "4 257 8 sadfdQ==")),
  543. ("CNAME", ("EXAMPLE.COM.", "example.com.")),
  544. ("CSYNC", ("066 03 NS AAAA A", "66 3 A NS AAAA")),
  545. ("DHCID", ("xxxx", "xxxx")),
  546. (
  547. "DLV",
  548. (
  549. "6454 8 2 5CBA665A006F6487625C6218522F09BD3673C25FA10F25CB18459AA1 0DF1F520",
  550. "6454 8 2 5CBA665A006F6487625C6218522F09BD3673C25FA10F25CB18459AA10DF1F520".lower(),
  551. ),
  552. ),
  553. (
  554. "DLV",
  555. (
  556. "6454 8 2 5C BA665A006F6487625C6218522F09BD3673C25FA10F25CB18459AA1 0DF1F520",
  557. "6454 8 2 5CBA665A006F6487625C6218522F09BD3673C25FA10F25CB18459AA10DF1F520".lower(),
  558. ),
  559. ),
  560. ("DNAME", ("EXAMPLE.COM.", "example.com.")),
  561. (
  562. "DNSKEY",
  563. (
  564. "0256 03 08 AwEAAday3UX323uVzQqtOMQ7EHQYfD5Ofv4akjQGN2zY5AgB/2jmdR/+ 1PvXFqzKCAGJv4wjABEBNWLLFm7ew1hHMDZEKVL17aml0EBKI6Dsz6Mx t6n7ScvLtHaFRKaxT4i2JxiuVhKdQR9XGMiWAPQKrRM5SLG0P+2F+TLK l3D0L/cD",
  565. "256 3 8 AwEAAday3UX323uVzQqtOMQ7EHQYfD5Ofv4akjQGN2zY5AgB/2jmdR/+1PvXFqzKCAGJv4wjABEBNWLLFm7ew1hHMDZEKVL17aml0EBKI6Dsz6Mxt6n7ScvLtHaFRKaxT4i2JxiuVhKdQR9XGMiWAPQKrRM5SLG0P+2F+TLKl3D0L/cD",
  566. ),
  567. ),
  568. (
  569. "DNSKEY",
  570. (
  571. "257 3 8 AwEAAcw5QLr0IjC0wKbGoBPQv4qmeqHy9mvL5qGQTuaG5TSrNqEAR6b/ qvxDx6my4JmEmjUPA1JeEI9YfTUieMr2UZflu7aIbZFLw0vqiYrywCGr CHXLalOrEOmrvAxLvq4vHtuTlH7JIszzYBSes8g1vle6KG7xXiP3U5Ll 96Qiu6bZ31rlMQSPB20xbqJJh6psNSrQs41QvdcXAej+K2Hl1Wd8kPri ec4AgiBEh8sk5Pp8W9ROLQ7PcbqqttFaW2m7N/Wy4qcFU13roWKDEAst bxH5CHPoBfZSbIwK4KM6BK/uDHpSPIbiOvOCW+lvu9TAiZPc0oysY6as lO7jXv16Gws=",
  572. "257 3 8 AwEAAcw5QLr0IjC0wKbGoBPQv4qmeqHy9mvL5qGQTuaG5TSrNqEAR6b/qvxDx6my4JmEmjUPA1JeEI9YfTUieMr2UZflu7aIbZFLw0vqiYrywCGrCHXLalOrEOmrvAxLvq4vHtuTlH7JIszzYBSes8g1vle6KG7xXiP3U5Ll96Qiu6bZ31rlMQSPB20xbqJJh6psNSrQs41QvdcXAej+K2Hl1Wd8kPriec4AgiBEh8sk5Pp8W9ROLQ7PcbqqttFaW2m7N/Wy4qcFU13roWKDEAstbxH5CHPoBfZSbIwK4KM6BK/uDHpSPIbiOvOCW+lvu9TAiZPc0oysY6aslO7jXv16Gws=",
  573. ),
  574. ),
  575. (
  576. "DNSKEY",
  577. (
  578. "257 3 13 aCoEWYBBVsP9Fek2oC8yqU8ocKmnS1iDSFZNORnQuHKtJ9Wpyz+kNryq uB78Pyk/NTEoai5bxoipVQQXzHlzyg==",
  579. "257 3 13 aCoEWYBBVsP9Fek2oC8yqU8ocKmnS1iDSFZNORnQuHKtJ9Wpyz+kNryquB78Pyk/NTEoai5bxoipVQQXzHlzyg==",
  580. ),
  581. ),
  582. (
  583. "DS",
  584. (
  585. "6454 8 2 5CBA665A006F6487625C6218522F09BD3673C25FA10F25CB18459AA1 0DF1F520",
  586. "6454 8 2 5CBA665A006F6487625C6218522F09BD3673C25FA10F25CB18459AA10DF1F520".lower(),
  587. ),
  588. ),
  589. (
  590. "DS",
  591. (
  592. "6454 8 2 5C BA665A006F6487625C6218522F09BD3673C25FA10F25CB18459AA1 0DF1F520",
  593. "6454 8 2 5CBA665A006F6487625C6218522F09BD3673C25FA10F25CB18459AA10DF1F520".lower(),
  594. ),
  595. ),
  596. ("EUI48", ("AA-BB-CC-DD-EE-FF", "aa-bb-cc-dd-ee-ff")),
  597. ("EUI64", ("AA-BB-CC-DD-EE-FF-aa-aa", "aa-bb-cc-dd-ee-ff-aa-aa")),
  598. ("HINFO", ("cpu os", '"cpu" "os"')),
  599. ("HINFO", ('"cpu" "os"', '"cpu" "os"')),
  600. (
  601. "HTTPS",
  602. ("01 h3POOL.exaMPLe. aLPn=h2,h3", "1 h3POOL.exaMPLe. alpn=h2,h3"),
  603. ),
  604. (
  605. "HTTPS",
  606. (
  607. "01 h3POOL.exaMPLe. aLPn=h2,h3 ECH=MTIzLi4uCg==",
  608. '1 h3POOL.exaMPLe. alpn=h2,h3 ech="MTIzLi4uCg=="',
  609. ),
  610. ),
  611. # ('IPSECKEY', ('01 00 02 . ASDFAF==', '1 0 2 . ASDFAA==')),
  612. # ('IPSECKEY', ('01 00 02 . 000000==', '1 0 2 . 00000w==')),
  613. ("KX", ("010 example.com.", "10 example.com.")),
  614. ("L32", ("010 10.1.2.0", "10 10.1.2.0")),
  615. ("L64", ("010 2001:0Db8:2140:2000", "10 2001:0db8:2140:2000")),
  616. (
  617. "LOC",
  618. (
  619. "023 012 59 N 042 022 48.500 W 65.00m 20.00m 10.00m 10.00m",
  620. "23 12 59.000 N 42 22 48.500 W 65.00m 20.00m 10.00m 10.00m",
  621. ),
  622. ),
  623. ("LP", ("010 l64-subnet1.example.com.", "10 l64-subnet1.example.com.")),
  624. ("MX", ("10 010.1.1.1.", "10 010.1.1.1.")),
  625. ("MX", ("010 010.1.1.2.", "10 010.1.1.2.")),
  626. ("MX", ("0 .", "0 .")),
  627. (
  628. "NAPTR",
  629. (
  630. '100 50 "s" "z3950+I2L+I2C" "" _z3950._tcp.gatech.edu.',
  631. '100 50 "s" "z3950+I2L+I2C" "" _z3950._tcp.gatech.edu.',
  632. ),
  633. ),
  634. ("NID", ("010 0014:4fff:ff20:Ee64", "10 0014:4fff:ff20:ee64")),
  635. ("NS", ("EXaMPLE.COM.", "example.com.")),
  636. (
  637. "OPENPGPKEY",
  638. (
  639. "mG8EXtVIsRMFK4EEACIDAwQSZPNqE4tS xLFJYhX+uabSgMrhOqUizJhkLx82",
  640. "mG8EXtVIsRMFK4EEACIDAwQSZPNqE4tSxLFJYhX+uabSgMrhOqUizJhkLx82",
  641. ),
  642. ),
  643. ("PTR", ("EXAMPLE.COM.", "example.com.")),
  644. ("RP", ("hostmaster.EXAMPLE.com. .", "hostmaster.example.com. .")),
  645. ("SMIMEA", ("3 01 0 aaBBccddeeff", "3 1 0 aabbccddeeff")),
  646. (
  647. "SPF",
  648. (
  649. '"v=spf1 ip4:10.1" ".1.1 ip4:127" ".0.0.0/16 ip4:192.168.0.0/27 include:example.com -all"',
  650. '"v=spf1 ip4:10.1" ".1.1 ip4:127" ".0.0.0/16 ip4:192.168.0.0/27 include:example.com -all"',
  651. ),
  652. ),
  653. ("SPF", ('"foo" "bar"', '"foo" "bar"')),
  654. ("SPF", ('"foobar"', '"foobar"')),
  655. ("SRV", ("0 000 0 .", "0 0 0 .")),
  656. ("SRV", ("100 1 5061 EXAMPLE.com.", "100 1 5061 example.com.")),
  657. ("SRV", ("100 1 5061 example.com.", "100 1 5061 example.com.")),
  658. ("SSHFP", ("2 2 aabbccEEddff", "2 2 aabbcceeddff")),
  659. (
  660. "SVCB",
  661. (
  662. "2 sVc2.example.NET. IPV6hint=2001:db8:00:0::2 port=01234",
  663. "2 sVc2.example.NET. port=1234 ipv6hint=2001:db8::2",
  664. ),
  665. ),
  666. (
  667. "SVCB",
  668. (
  669. "2 sVc2.example.NET. ECH=MjIyLi4uCg== IPV6hint=2001:db8:00:0::2 port=01234",
  670. '2 sVc2.example.NET. port=1234 ech="MjIyLi4uCg==" ipv6hint=2001:db8::2',
  671. ),
  672. ),
  673. (
  674. "TLSA",
  675. (
  676. "3 0001 1 000AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
  677. "3 1 1 000aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
  678. ),
  679. ),
  680. (
  681. "TLSA",
  682. (
  683. "003 00 002 696B8F6B92A913560b23ef5720c378881faffe74432d04eb35db957c0a93987b47adf26abb5dac10ba482597ae16edb069b511bec3e26010d1927bf6392760dd",
  684. "3 0 2 696b8f6b92a913560b23ef5720c378881faffe74432d04eb35db957c0a93987b47adf26abb5dac10ba482597ae16edb069b511bec3e26010d1927bf6392760dd",
  685. ),
  686. ),
  687. (
  688. "TLSA",
  689. (
  690. "3 0 2 696B8F6B92A913560b23ef5720c378881faffe74432d04eb35db957c0a93987b47adf26abb5dac10ba482597ae16edb069b511bec3e26010d1927bf6392760dd696B8F6B92A913560b23ef5720c378881faffe74432d04eb35db957c0a93987b47adf26abb5dac10ba482597ae16edb069b511bec3e26010d1927bf6392760dd",
  691. "3 0 2 696b8f6b92a913560b23ef5720c378881faffe74432d04eb35db957c0a93987b47adf26abb5dac10ba482597ae16edb069b511bec3e26010d1927bf6392760dd696b8f6b92a913560b23ef5720c378881faffe74432d04eb35db957c0a93987b47adf26abb5dac10ba482597ae16edb069b511bec3e26010d1927bf6392760dd",
  692. ),
  693. ),
  694. ("TXT", ('"foo" "bar"', '"foo" "bar"')),
  695. ("TXT", ('"foobar"', '"foobar"')),
  696. ("TXT", ('"foo" "" "bar"', '"foo" "" "bar"')),
  697. ("TXT", ('"" "" "foo" "" "bar"', '"" "" "foo" "" "bar"')),
  698. (
  699. "TXT",
  700. (
  701. r'"\130\164name\164Boss\164type\1611"',
  702. r'"\130\164name\164Boss\164type\1611"',
  703. ),
  704. ),
  705. (
  706. "TXT",
  707. (
  708. '"' + "".join(rf"\{n:03}" for n in range(256)) + '"', # all bytes
  709. r'"\000\001\002\003\004\005\006\007\008\009\010\011\012\013\014\015\016\017\018\019\020\021\022\023\024\025\026\027\028\029\030\031 !\"#$%&'
  710. + "'"
  711. + r'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\127\128\129\130\131\132\133\134\135\136\137\138\139\140\141\142\143\144\145\146\147\148\149\150\151\152\153\154\155\156\157\158\159\160\161\162\163\164\165\166\167\168\169\170\171\172\173\174\175\176\177\178\179\180\181\182\183\184\185\186\187\188\189\190\191\192\193\194\195\196\197\198\199\200\201\202\203\204\205\206\207\208\209\210\211\212\213\214\215\216\217\218\219\220\221\222\223\224\225\226\227\228\229\230\231\232\233\234\235\236\237\238\239\240\241\242\243\244\245\246\247\248\249\250\251\252\253\254" "\255"',
  712. ),
  713. ),
  714. (
  715. "URI",
  716. (
  717. '10 01 "ftp://ftp1.example.com/public"',
  718. '10 1 "ftp://ftp1.example.com/public"',
  719. ),
  720. ),
  721. ]
  722. for t, (record, canonical_record) in datas:
  723. if not record:
  724. continue
  725. subname = "" if t == "DNSKEY" else "test"
  726. data = {"records": [record], "ttl": 3660, "type": t, "subname": subname}
  727. with self.assertRequests(
  728. self.requests_desec_rr_sets_update(name=self.my_empty_domain.name)
  729. ):
  730. response = self.client.post_rr_set(self.my_empty_domain.name, **data)
  731. self.assertStatus(response, status.HTTP_201_CREATED)
  732. self.assertEqual(
  733. canonical_record,
  734. response.data["records"][0],
  735. f"For RR set type {t}, expected '{canonical_record}' to be the canonical form of "
  736. f'\'{record}\', but saw \'{response.data["records"][0]}\'.',
  737. )
  738. with self.assertRequests(
  739. self.requests_desec_rr_sets_update(name=self.my_empty_domain.name)
  740. ):
  741. response = self.client.delete_rr_set(
  742. self.my_empty_domain.name, subname=subname, type_=t
  743. )
  744. self.assertStatus(response, status.HTTP_204_NO_CONTENT)
  745. self.assertAllSupportedRRSetTypes(set(t for t, _ in datas))
  746. def test_create_my_rr_sets_known_type_benign(self):
  747. # TODO fill in more examples
  748. datas = {
  749. "A": ["127.0.0.1", "127.0.0.2"],
  750. "AAAA": ["::1", "::2"],
  751. "AFSDB": ["2 turquoise.femto.edu."],
  752. "APL": [
  753. # from RFC 3123 Sec. 4
  754. "1:192.168.32.0/21 !1:192.168.38.0/28",
  755. "1:192.168.42.0/26 1:192.168.42.64/26 1:192.168.42.128/25",
  756. "1:127.0.0.1/32 1:172.16.64.0/22",
  757. "1:224.0.0.0/4 2:FF00:0:0:0:0:0:0:0/8",
  758. # made-up (not from RFC)
  759. "1:1.2.3.4/32 2:::/128",
  760. "2:FF00::/8 !1:192.168.38.0/28",
  761. ],
  762. "CAA": [
  763. '128 issue "letsencrypt.org"',
  764. '128 iodef "mailto:desec@example.com"',
  765. '1 issue "letsencrypt.org"',
  766. ],
  767. "CERT": [
  768. "06 0 0 sadfdd==",
  769. "IPGP 0 0 sadfdd==",
  770. "4 257 RSASHA256 sadfdd==",
  771. ],
  772. "CDNSKEY": [
  773. "256 3 8 AwEAAday3UX323uVzQqtOMQ7EHQYfD5Ofv4akjQGN2zY5AgB/2jmdR/+ 1PvXFqzKCAGJv4wjABEBNWLLFm7ew1hHMDZEKVL17aml0EBKI6Dsz6Mx t6n7ScvLtHaFRKaxT4i2JxiuVhKdQR9XGMiWAPQKrRM5SLG0P+2F+TLK l3D0L/cD",
  774. "257 3 8 AwEAAcw5QLr0IjC0wKbGoBPQv4qmeqHy9mvL5qGQTuaG5TSrNqEAR6b/ qvxDx6my4JmEmjUPA1JeEI9YfTUieMr2UZflu7aIbZFLw0vqiYrywCGr CHXLalOrEOmrvAxLvq4vHtuTlH7JIszzYBSes8g1vle6KG7xXiP3U5Ll 96Qiu6bZ31rlMQSPB20xbqJJh6psNSrQs41QvdcXAej+K2Hl1Wd8kPri ec4AgiBEh8sk5Pp8W9ROLQ7PcbqqttFaW2m7N/Wy4qcFU13roWKDEAst bxH5CHPoBfZSbIwK4KM6BK/uDHpSPIbiOvOCW+lvu9TAiZPc0oysY6as lO7jXv16Gws=",
  775. "257 3 13 aCoEWYBBVsP9Fek2oC8yqU8ocKmnS1iDSFZNORnQuHKtJ9Wpyz+kNryq uB78Pyk/NTEoai5bxoipVQQXzHlzyg==",
  776. ],
  777. "CDS": [
  778. "6454 8 1 24396E17E36D031F71C354B06A979A67A01F503E",
  779. "6454 8 2 5CBA665A006F6487625C6218522F09BD3673C25FA10F25CB18459AA1 0DF1F520",
  780. "62703 13 2 085BF1EE0ADBBC99D4D9328229EBDCAEC5FAB20E38610072AD055474 4C7AF4A0",
  781. "61655 13 4 C838A5C66FCBF83B8B6B50C3CEEC3524777FE4AF8A9FE0172ECAD242 48B0CA1A216DD0D538F20C130DD3059538204B04",
  782. "6454 8 5 24396E17E36D031F71C354B06A979A67A01F503E",
  783. ],
  784. "CNAME": ["example.com.", "*._under-score.-foo_bar.example.net.", "."],
  785. "CSYNC": ["0 0", "66 1 A", "66 2 AAAA", "66 3 A NS AAAA", "66 15 NSEC"],
  786. "DHCID": ["aaaaaaaaaaaa", "aa aaa aaaa a a a"],
  787. "DLV": [
  788. "6454 8 1 24396E17E36D031F71C354B06A979A67A01F503E",
  789. "6454 8 2 5CBA665A006F6487625C6218522F09BD3673C25FA10F25CB18459AA1 0DF1F520",
  790. "62703 13 2 085BF1EE0ADBBC99D4D9328229EBDCAEC5FAB20E38610072AD055474 4C7AF4A0",
  791. "61655 13 4 C838A5C66FCBF83B8B6B50C3CEEC3524777FE4AF8A9FE0172ECAD242 48B0CA1A216DD0D538F20C130DD3059538204B04",
  792. "6454 8 5 24396E17E36D031F71C354B06A979A67A01F503E",
  793. ],
  794. "DNAME": ["example.com."],
  795. "DNSKEY": [
  796. "256 3 8 AwEAAday3UX323uVzQqtOMQ7EHQYfD5Ofv4akjQGN2zY5AgB/2jmdR/+ 1PvXFqzKCAGJv4wjABEBNWLLFm7ew1hHMDZEKVL17aml0EBKI6Dsz6Mx t6n7ScvLtHaFRKaxT4i2JxiuVhKdQR9XGMiWAPQKrRM5SLG0P+2F+TLK l3D0L/cD",
  797. "257 3 8 AwEAAcw5QLr0IjC0wKbGoBPQv4qmeqHy9mvL5qGQTuaG5TSrNqEAR6b/ qvxDx6my4JmEmjUPA1JeEI9YfTUieMr2UZflu7aIbZFLw0vqiYrywCGr CHXLalOrEOmrvAxLvq4vHtuTlH7JIszzYBSes8g1vle6KG7xXiP3U5Ll 96Qiu6bZ31rlMQSPB20xbqJJh6psNSrQs41QvdcXAej+K2Hl1Wd8kPri ec4AgiBEh8sk5Pp8W9ROLQ7PcbqqttFaW2m7N/Wy4qcFU13roWKDEAst bxH5CHPoBfZSbIwK4KM6BK/uDHpSPIbiOvOCW+lvu9TAiZPc0oysY6as lO7jXv16Gws=",
  798. "257 3 13 aCoEWYBBVsP9Fek2oC8yqU8ocKmnS1iDSFZNORnQuHKtJ9Wpyz+kNryq uB78Pyk/NTEoai5bxoipVQQXzHlzyg==",
  799. ],
  800. "DS": [
  801. "6454 8 1 24396E17E36D031F71C354B06A979A67A01F503E",
  802. "6454 8 2 5CBA665A006F6487625C6218522F09BD3673C25FA10F25CB18459AA1 0DF1F520",
  803. "62703 13 2 085BF1EE0ADBBC99D4D9328229EBDCAEC5FAB20E38610072AD055474 4C7AF4A0",
  804. "61655 13 4 C838A5C66FCBF83B8B6B50C3CEEC3524777FE4AF8A9FE0172ECAD242 48B0CA1A216DD0D538F20C130DD3059538204B04",
  805. "6454 8 5 24396E17E36D031F71C354B06A979A67A01F503E",
  806. ],
  807. "EUI48": ["aa-bb-cc-dd-ee-ff", "AA-BB-CC-DD-EE-FF"],
  808. "EUI64": ["aa-bb-cc-dd-ee-ff-00-11", "AA-BB-CC-DD-EE-FF-00-11"],
  809. "HINFO": ['"ARMv8-A" "Linux"'],
  810. "HTTPS": [
  811. # from https://www.ietf.org/archive/id/draft-ietf-dnsop-svcb-https-06.html#name-examples, with ech base64'd
  812. "1 . alpn=h3",
  813. "0 pool.svc.example.",
  814. '1 h3pool.example. alpn=h2,h3 ech="MTIzLi4uCg=="',
  815. '2 . alpn=h2 ech="YWJjLi4uCg=="',
  816. # made-up (not from RFC)
  817. "1 pool.svc.example. no-default-alpn alpn=h2 port=1234 ipv4hint=192.168.123.1",
  818. "2 . ech=... key65333=ex1 key65444=ex2 mandatory=key65444,ech", # see #section-7
  819. ],
  820. # 'IPSECKEY': [
  821. # '12 0 2 . asdfdf==',
  822. # '03 1 1 127.0.0.1 asdfdf==',
  823. # '10 02 2 bade::affe AQNRU3mG7TVTO2BkR47usntb102uFJtugbo6BSGvgqt4AQ==',
  824. # '12 3 01 example.com. asdfdf==',
  825. # ],
  826. "KX": ["4 example.com.", "28 io."],
  827. "L32": ["010 10.1.2.0", "65535 1.2.3.4"],
  828. "L64": ["010 2001:0DB8:1140:1000", "10 2001:0DB8:1140:1000"],
  829. "LOC": ["23 12 59.000 N 42 22 48.500 W 65.00m 20.00m 10.00m 10.00m"],
  830. "LP": ["10 l64-subnet1.example.com.", "65535 ."],
  831. "MX": ["10 example.com.", "20 1.1.1.1."],
  832. "NAPTR": ['100 50 "s" "z3950+I2L+I2C" "" _z3950._tcp.gatech.edu.'],
  833. "NID": ["65535 0014:4fff:ff20:ee64"],
  834. "NS": ["ns1.example.com."],
  835. "OPENPGPKEY": [
  836. "mG8EXtVIsRMFK4EEACIDAwQSZPNqE4tSxLFJYhX+uabSgMrhOqUizJhkLx82", # key incomplete
  837. "YWFh\xf0\x9f\x92\xa9YWFh", # valid as non-alphabet bytes will be ignored
  838. ],
  839. "PTR": ["example.com.", "*.example.com."],
  840. "RP": ["hostmaster.example.com. ."],
  841. "SMIMEA": ["3 1 0 aabbccddeeff"],
  842. "SPF": [
  843. '"v=spf1 include:example.com ~all"',
  844. '"v=spf1 ip4:10.1.1.1 ip4:127.0.0.0/16 ip4:192.168.0.0/27 include:example.com -all"',
  845. '"spf2.0/pra,mfrom ip6:2001:558:fe14:76:68:87:28:0/120 -all"',
  846. ],
  847. "SRV": ["0 0 0 .", "100 1 5061 example.com."],
  848. "SSHFP": ["2 2 aabbcceeddff"],
  849. "SVCB": [
  850. "0 svc4-baz.example.net.",
  851. "1 . key65333=...",
  852. '2 svc2.example.net. ech="MjIyLi4uCg==" ipv6hint=2001:db8::2 port=1234',
  853. ],
  854. "TLSA": [
  855. "3 1 1 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
  856. "3 0 2 696b8f6b92a913560b23ef5720c378881faffe74432d04eb35db957c0a93987b47adf26abb5dac10ba482597ae16edb069b511bec3e26010d1927bf6392760dd",
  857. "3 0 2 696b8f6b92a913560b23ef5720c378881faffe74432d04eb35db957c0a93987b47adf26abb5dac10ba482597ae16edb069b511bec3e26010d1927bf6392760dd696b8f6b92a913560b23ef5720c378881faffe74432d04eb35db957c0a93987b47adf26abb5dac10ba482597ae16edb069b511bec3e26010d1927bf6392760dd",
  858. ],
  859. "TXT": [
  860. '"foobar"',
  861. '"foo" "bar"',
  862. '"“红色联合”对“四·二八兵团”总部大楼的攻击已持续了两天"',
  863. '"new\\010line"'
  864. '"🧥 👚 👕 👖 👔 👗 👙 👘 👠 👡 👢 👞 👟 🥾 🥿 🧦 🧤 🧣 🎩 🧢 👒 🎓 ⛑ 👑 👝 👛 👜 💼 🎒 👓 🕶 🥽 🥼 🌂 🧵"',
  865. ],
  866. "URI": ['10 1 "ftp://ftp1.example.com/public"'],
  867. }
  868. self.assertAllSupportedRRSetTypes(set(datas.keys()))
  869. for t, records in datas.items():
  870. subname = "" if t == "DNSKEY" else "test"
  871. for r in records:
  872. data = {"records": [r], "ttl": 3660, "type": t, "subname": subname}
  873. with self.assertRequests(
  874. self.requests_desec_rr_sets_update(name=self.my_empty_domain.name)
  875. ):
  876. response = self.client.post_rr_set(
  877. self.my_empty_domain.name, **data
  878. )
  879. self.assertStatus(response, status.HTTP_201_CREATED)
  880. with self.assertRequests(
  881. self.requests_desec_rr_sets_update(name=self.my_empty_domain.name)
  882. ):
  883. response = self.client.delete_rr_set(
  884. self.my_empty_domain.name, subname=subname, type_=t
  885. )
  886. self.assertStatus(response, status.HTTP_204_NO_CONTENT)
  887. def test_create_my_rr_sets_known_type_invalid(self):
  888. # TODO fill in more examples
  889. datas = {
  890. # recordtype: [list of examples expected to be rejected, individually]
  891. "A": [
  892. "127.0.0.999",
  893. "127.000.0.01",
  894. "127.0.0.256",
  895. "::1",
  896. "foobar",
  897. "10.0.1",
  898. "10!",
  899. ],
  900. "AAAA": ["::g", "1:1:1:1:1:1:1:1:", "1:1:1:1:1:1:1:1:1"],
  901. "AFSDB": ["example.com.", "1 1", "1 de"],
  902. "APL": [
  903. "0:192.168.32.0/21 !1:192.168.38.0/28",
  904. "1:192.168.32.0/21 !!1:192.168.38.0/28",
  905. "1:192.168.32.0/33",
  906. "18:12345/2",
  907. "1:127.0.0.1",
  908. "2:FF00:0:0:0:0:0:0:0:0/8" "2:::/129",
  909. ],
  910. "CAA": ['43235 issue "letsencrypt.org"'],
  911. "CERT": ["6 0 sadfdd=="],
  912. "CDNSKEY": [
  913. "a 3 13 aCoEWYBBVsP9Fek2oC8yqU8ocKmnS1iDSFZNORnQuHKtJ9Wpyz+kNryq uB78Pyk/NTEoai5bxoipVQQXzHlzyg=="
  914. "257 b 13 aCoEWYBBVsP9Fek2oC8yqU8ocKmnS1iDSFZNORnQuHKtJ9Wpyz+kNryq uB78Pyk/NTEoai5bxoipVQQXzHlzyg=="
  915. "257 3 c aCoEWYBBVsP9Fek2oC8yqU8ocKmnS1iDSFZNORnQuHKtJ9Wpyz+kNryq uB78Pyk/NTEoai5bxoipVQQXzHlzyg=="
  916. "257 3 13 d",
  917. "0 3 0 0",
  918. ],
  919. "CDS": [
  920. "a 8 1 24396E17E36D031F71C354B06A979A67A01F503E",
  921. "-6454 8 1 24396E17E36D031F71C354B06A979A67A01F503E",
  922. "6454 b 1 24396E17E36D031F71C354B06A979A67A01F503E",
  923. "6454 8 c 24396E17E36D031F71C354B06A979A67A01F503E",
  924. "6454 8 1 d",
  925. "6454 8 0 24396E17E36D031F71C354B06A979A67A01F503E",
  926. "6454 8 1 aabbccddeeff",
  927. "0 0 0 0",
  928. ],
  929. "CNAME": ["example.com", "10 example.com.", "@."],
  930. "CSYNC": ["0 -1 A", "444 65536 A", "0 3 AAA"],
  931. "DHCID": ["x", "xx", "xxx"],
  932. "DLV": [
  933. "a 8 1 24396E17E36D031F71C354B06A979A67A01F503E",
  934. "-6454 8 1 24396E17E36D031F71C354B06A979A67A01F503E",
  935. "6454 b 1 24396E17E36D031F71C354B06A979A67A01F503E",
  936. "6454 8 c 24396E17E36D031F71C354B06A979A67A01F503E",
  937. "6454 8 1 d",
  938. "6454 8 0 24396E17E36D031F71C354B06A979A67A01F503E",
  939. "6454 8 1 aabbccddeeff",
  940. ],
  941. "DNAME": ["example.com", "10 example.com."],
  942. "DNSKEY": [
  943. "a 3 13 aCoEWYBBVsP9Fek2oC8yqU8ocKmnS1iDSFZNORnQuHKtJ9Wpyz+kNryq uB78Pyk/NTEoai5bxoipVQQXzHlzyg=="
  944. "257 b 13 aCoEWYBBVsP9Fek2oC8yqU8ocKmnS1iDSFZNORnQuHKtJ9Wpyz+kNryq uB78Pyk/NTEoai5bxoipVQQXzHlzyg=="
  945. "257 3 c aCoEWYBBVsP9Fek2oC8yqU8ocKmnS1iDSFZNORnQuHKtJ9Wpyz+kNryq uB78Pyk/NTEoai5bxoipVQQXzHlzyg=="
  946. "257 3 13 d"
  947. ],
  948. "DS": [
  949. "a 8 1 24396E17E36D031F71C354B06A979A67A01F503E",
  950. "-6454 8 1 24396E17E36D031F71C354B06A979A67A01F503E",
  951. "6454 b 1 24396E17E36D031F71C354B06A979A67A01F503E",
  952. "6454 8 c 24396E17E36D031F71C354B06A979A67A01F503E",
  953. "6454 8 1 d",
  954. "6454 8 0 24396E17E36D031F71C354B06A979A67A01F503E",
  955. "6454 8 1 aabbccddeeff",
  956. ],
  957. "EUI48": ["aa-bb-ccdd-ee-ff", "AA-BB-CC-DD-EE-GG"],
  958. "EUI64": ["aa-bb-cc-dd-ee-ff-gg-11", "AA-BB-C C-DD-EE-FF-00-11"],
  959. "HINFO": ['"ARMv8-A"', f'"a" "{"b"*256}"'],
  960. "HTTPS": [
  961. # from https://tools.ietf.org/html/draft-ietf-dnsop-svcb-https-02#section-10.3, with ech base64'd
  962. '1 h3pool alpn=h2,h3 ech="MTIzLi4uCg=="',
  963. # made-up (not from RFC)
  964. "0 pool.svc.example. no-default-alpn port=1234 ipv4hint=192.168.123.1", # no keys in alias mode
  965. "1 pool.svc.example. no-default-alpn port=1234 ipv4hint=192.168.123.1 ipv4hint=192.168.123.2", # dup
  966. ],
  967. # 'IPSECKEY': [],
  968. "KX": ["-1 example.com", "10 example.com"],
  969. "L32": ["65536 10.1.2.0", "5 a.1.2.0", "10 10.1.02.0"],
  970. "L64": ["65536 2001:0DB8:4140:4000", "5 01:0DB8:4140:4000"],
  971. "LOC": [
  972. "23 12 61.000 N 42 22 48.500 W 65.00m 20.00m 10.00m 10.00m",
  973. "foo",
  974. "1.1.1.1",
  975. ],
  976. "LP": [
  977. "10 l64-subnet1.example.com",
  978. "-3 l64-subnet1.example.com.",
  979. "65536 l64-subnet1.example.com.",
  980. ],
  981. "MX": [
  982. "10 example.com",
  983. "example.com.",
  984. "-5 asdf.",
  985. "65537 asdf.",
  986. "10 _foo.example.com.",
  987. "10 $url.",
  988. ],
  989. "NAPTR": [
  990. '100 50 "s" "z3950+I2L+I2C" "" _z3950._tcp.gatech.edu',
  991. '100 50 "s" "" _z3950._tcp.gatech.edu.',
  992. '100 50 3 2 "z3950+I2L+I2C" "" _z3950._tcp.gatech.edu.',
  993. ],
  994. "NID": ["010 14:4fff:ff20:Ee64", "d 0014:4fff:ff20:ee64", "20 ::14::ee64"],
  995. "NS": ["ns1.example.com", "127.0.0.1", "_foobar.example.dedyn.io.", "."],
  996. "OPENPGPKEY": ["1 2 3"],
  997. "PTR": ['"example.com."', "10 *.example.com."],
  998. "RP": ["hostmaster.example.com.", "10 foo."],
  999. "SMIMEA": ["3 1 0 aGVsbG8gd29ybGQh"],
  1000. "SPF": ['"v=spf1', "v=spf1 include:example.com ~all"],
  1001. "SRV": [
  1002. "0 0 0 0",
  1003. "100 5061 example.com.",
  1004. "0 0 16920 _foo.example.com.",
  1005. "0 0 16920 $url.",
  1006. ],
  1007. "SSHFP": ["aabbcceeddff"],
  1008. "SVCB": [
  1009. "0 svc4-baz.example.net. keys=val",
  1010. "1 not.fully.qualified key65333=...",
  1011. '2 duplicate.key. ech="MjIyLi4uCg==" ech="MjIyLi4uCg=="',
  1012. ],
  1013. "TLSA": ["3 1 1 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"],
  1014. "TXT": [
  1015. 'foob"ar',
  1016. "v=spf1 include:example.com ~all",
  1017. '"foo\nbar"',
  1018. '"\x00" "NUL byte yo"',
  1019. '"'
  1020. + "".join(rf"\{n:03}" for n in range(257))
  1021. + '"', # \256 does not exist
  1022. ],
  1023. "URI": ['"1" "2" "3"'],
  1024. }
  1025. self.assertAllSupportedRRSetTypes(set(datas.keys()))
  1026. for t, records in datas.items():
  1027. for r in records:
  1028. data = {"records": [r], "ttl": 3660, "type": t, "subname": "subname"}
  1029. response = self.client.post_rr_set(self.my_empty_domain.name, **data)
  1030. self.assertNotContains(
  1031. response, "Duplicate", status_code=status.HTTP_400_BAD_REQUEST
  1032. )
  1033. def test_create_my_rr_sets_no_ip_block_unless_lps(self):
  1034. # IP block should not be effective unless domain is under Local Public Suffix
  1035. BlockedSubnet.from_ip("3.2.2.3").save()
  1036. with self.assertRequests(
  1037. self.requests_desec_rr_sets_update(name=self.my_empty_domain.name)
  1038. ):
  1039. response = self.client.post_rr_set(
  1040. self.my_empty_domain.name,
  1041. records=["3.2.2.5"],
  1042. ttl=3660,
  1043. subname="blocktest",
  1044. type="A",
  1045. )
  1046. self.assertStatus(response, status.HTTP_201_CREATED)
  1047. def test_create_my_rr_sets_txt_splitting(self):
  1048. for t in ["TXT", "SPF"]:
  1049. for l in [200, 255, 256, 300, 400]:
  1050. data = {
  1051. "records": [f'"{"a"*l}"'],
  1052. "ttl": 3660,
  1053. "type": t,
  1054. "subname": f"x{l}",
  1055. }
  1056. with self.assertRequests(
  1057. self.requests_desec_rr_sets_update(name=self.my_empty_domain.name)
  1058. ):
  1059. response = self.client.post_rr_set(
  1060. self.my_empty_domain.name, **data
  1061. )
  1062. self.assertStatus(response, status.HTTP_201_CREATED)
  1063. response = self.client.get_rr_set(self.my_empty_domain.name, f"x{l}", t)
  1064. num_tokens = response.data["records"][0].count(" ") + 1
  1065. num_tokens_expected = l // 256 + 1
  1066. self.assertEqual(
  1067. num_tokens,
  1068. num_tokens_expected,
  1069. f"For a {t} record with a token of length of {l}, expected to see "
  1070. f"{num_tokens_expected} tokens in the canonical format, but saw {num_tokens}.",
  1071. )
  1072. self.assertEqual(
  1073. "".join(r.strip('" ') for r in response.data["records"][0]), "a" * l
  1074. )
  1075. def test_create_my_rr_sets_unknown_type(self):
  1076. for _type in ["AA", "ASDF"] + list(
  1077. RR_SET_TYPES_AUTOMATIC | RR_SET_TYPES_UNSUPPORTED
  1078. ):
  1079. response = self.client.post_rr_set(
  1080. self.my_domain.name, records=["1234"], ttl=3660, type=_type
  1081. )
  1082. self.assertContains(
  1083. response,
  1084. text="managed automatically"
  1085. if _type in RR_SET_TYPES_AUTOMATIC
  1086. else "type is currently unsupported",
  1087. status_code=status.HTTP_400_BAD_REQUEST,
  1088. )
  1089. def test_create_my_rr_sets_ttl_too_small(self):
  1090. ttl = settings.MINIMUM_TTL_DEFAULT - 1
  1091. response = self.client.post_rr_set(
  1092. self.my_empty_domain.name, records=["1.2.3.4"], ttl=ttl, type="A"
  1093. )
  1094. self.assertStatus(response, status.HTTP_400_BAD_REQUEST)
  1095. detail = f"Ensure this value is greater than or equal to {self.my_empty_domain.minimum_ttl}."
  1096. self.assertEqual(response.data["ttl"][0], detail)
  1097. ttl += 1
  1098. with self.assertRequests(
  1099. self.requests_desec_rr_sets_update(name=self.my_empty_domain.name)
  1100. ):
  1101. response = self.client.post_rr_set(
  1102. self.my_empty_domain.name, records=["1.2.23.4"], ttl=ttl, type="A"
  1103. )
  1104. self.assertStatus(response, status.HTTP_201_CREATED)
  1105. def test_create_my_rr_sets_ttl_too_large(self):
  1106. max_ttl = 24 * 3600
  1107. response = self.client.post_rr_set(
  1108. self.my_empty_domain.name, records=["1.2.3.4"], ttl=max_ttl + 1, type="A"
  1109. )
  1110. self.assertStatus(response, status.HTTP_400_BAD_REQUEST)
  1111. detail = f"Ensure this value is less than or equal to {max_ttl}."
  1112. self.assertEqual(response.data["ttl"][0], detail)
  1113. with self.assertRequests(
  1114. self.requests_desec_rr_sets_update(name=self.my_empty_domain.name)
  1115. ):
  1116. response = self.client.post_rr_set(
  1117. self.my_empty_domain.name, records=["1.2.23.4"], ttl=max_ttl, type="A"
  1118. )
  1119. self.assertStatus(response, status.HTTP_201_CREATED)
  1120. def test_retrieve_my_rr_sets_apex(self):
  1121. response = self.client.get_rr_set(
  1122. self.my_rr_set_domain.name, subname="", type_="A"
  1123. )
  1124. self.assertStatus(response, status.HTTP_200_OK)
  1125. self.assertEqual(response.data["records"][0], "1.2.3.4")
  1126. self.assertEqual(response.data["ttl"], 3620)
  1127. def test_retrieve_my_rr_sets_restricted_types(self):
  1128. for type_ in self.AUTOMATIC_TYPES:
  1129. response = self.client.get_rr_sets(self.my_domain.name, type=type_)
  1130. self.assertStatus(response, status.HTTP_403_FORBIDDEN)
  1131. response = self.client.get_rr_sets(
  1132. self.my_domain.name, type=type_, subname=""
  1133. )
  1134. self.assertStatus(response, status.HTTP_403_FORBIDDEN)
  1135. def test_update_my_rr_sets(self):
  1136. for subname in self.SUBNAMES:
  1137. with self.assertRequests(
  1138. self.requests_desec_rr_sets_update(name=self.my_rr_set_domain.name)
  1139. ):
  1140. data = {
  1141. "records": ["2.2.3.4"],
  1142. "ttl": 3630,
  1143. "type": "A",
  1144. "subname": subname,
  1145. }
  1146. response = self.client.put_rr_set(
  1147. self.my_rr_set_domain.name, subname, "A", data
  1148. )
  1149. self.assertStatus(response, status.HTTP_200_OK)
  1150. response = self.client.get_rr_set(self.my_rr_set_domain.name, subname, "A")
  1151. self.assertStatus(response, status.HTTP_200_OK)
  1152. self.assertEqual(response.data["records"], ["2.2.3.4"])
  1153. self.assertEqual(response.data["ttl"], 3630)
  1154. response = self.client.put_rr_set(
  1155. self.my_rr_set_domain.name, subname, "A", {"records": ["2.2.3.5"]}
  1156. )
  1157. self.assertStatus(response, status.HTTP_400_BAD_REQUEST)
  1158. response = self.client.put_rr_set(
  1159. self.my_rr_set_domain.name, subname, "A", {"ttl": 3637}
  1160. )
  1161. self.assertStatus(response, status.HTTP_400_BAD_REQUEST)
  1162. def test_update_my_rr_set_with_invalid_payload_type(self):
  1163. for subname in self.SUBNAMES:
  1164. data = [
  1165. {"records": ["2.2.3.4"], "ttl": 30, "type": "A", "subname": subname}
  1166. ]
  1167. response = self.client.put_rr_set(
  1168. self.my_rr_set_domain.name, subname, "A", data
  1169. )
  1170. self.assertStatus(response, status.HTTP_400_BAD_REQUEST)
  1171. self.assertEqual(
  1172. response.data["non_field_errors"][0],
  1173. "Invalid data. Expected a dictionary, but got list.",
  1174. )
  1175. data = "foobar"
  1176. response = self.client.put_rr_set(
  1177. self.my_rr_set_domain.name, subname, "A", data
  1178. )
  1179. self.assertStatus(response, status.HTTP_400_BAD_REQUEST)
  1180. self.assertEqual(
  1181. response.data["non_field_errors"][0],
  1182. "Invalid data. Expected a dictionary, but got str.",
  1183. )
  1184. def test_partially_update_my_rr_sets(self):
  1185. for subname in self.SUBNAMES:
  1186. current_rr_set = self.client.get_rr_set(
  1187. self.my_rr_set_domain.name, subname, "A"
  1188. ).data
  1189. for data in [
  1190. {"records": ["2.2.3.4"], "ttl": 3630},
  1191. {"records": ["3.2.3.4"]},
  1192. {"records": ["3.2.3.4", "9.8.8.7"]},
  1193. {"ttl": 3637},
  1194. ]:
  1195. with self.assertRequests(
  1196. self.requests_desec_rr_sets_update(name=self.my_rr_set_domain.name)
  1197. ):
  1198. response = self.client.patch_rr_set(
  1199. self.my_rr_set_domain.name, subname, "A", data
  1200. )
  1201. self.assertStatus(response, status.HTTP_200_OK)
  1202. response = self.client.get_rr_set(
  1203. self.my_rr_set_domain.name, subname, "A"
  1204. )
  1205. self.assertStatus(response, status.HTTP_200_OK)
  1206. current_rr_set.update(data)
  1207. self.assertEqual(
  1208. set(response.data["records"]), set(current_rr_set["records"])
  1209. )
  1210. self.assertEqual(response.data["ttl"], current_rr_set["ttl"])
  1211. response = self.client.patch_rr_set(
  1212. self.my_rr_set_domain.name, subname, "A", {}
  1213. )
  1214. self.assertStatus(response, status.HTTP_200_OK)
  1215. def test_rr_sets_touched_if_noop(self):
  1216. for subname in self.SUBNAMES:
  1217. touched_old = RRset.objects.get(
  1218. domain=self.my_rr_set_domain, type="A", subname=subname
  1219. ).touched
  1220. response = self.client.patch_rr_set(
  1221. self.my_rr_set_domain.name, subname, "A", {}
  1222. )
  1223. self.assertStatus(response, status.HTTP_200_OK)
  1224. touched_new = RRset.objects.get(
  1225. domain=self.my_rr_set_domain, type="A", subname=subname
  1226. ).touched
  1227. self.assertGreater(touched_new, touched_old)
  1228. self.assertEqual(
  1229. Domain.objects.get(name=self.my_rr_set_domain.name).touched, touched_new
  1230. )
  1231. def test_partially_update_other_rr_sets(self):
  1232. data = {"records": ["3.2.3.4"], "ttl": 334}
  1233. for subname in self.SUBNAMES:
  1234. response = self.client.patch_rr_set(
  1235. self.other_rr_set_domain.name, subname, "A", data
  1236. )
  1237. self.assertStatus(response, status.HTTP_404_NOT_FOUND)
  1238. def test_update_other_rr_sets(self):
  1239. data = {"ttl": 305}
  1240. for subname in self.SUBNAMES:
  1241. response = self.client.patch_rr_set(
  1242. self.other_rr_set_domain.name, subname, "A", data
  1243. )
  1244. self.assertStatus(response, status.HTTP_404_NOT_FOUND)
  1245. def test_update_essential_properties(self):
  1246. # Changing the subname is expected to cause an error
  1247. url = self.reverse(
  1248. "v1:rrset", name=self.my_rr_set_domain.name, subname="test", type="A"
  1249. )
  1250. data = {"records": ["3.2.3.4"], "ttl": 3620, "subname": "test2", "type": "A"}
  1251. response = self.client.patch(url, data)
  1252. self.assertStatus(response, status.HTTP_400_BAD_REQUEST)
  1253. self.assertEqual(response.data["subname"][0].code, "read-only-on-update")
  1254. response = self.client.put(url, data)
  1255. self.assertStatus(response, status.HTTP_400_BAD_REQUEST)
  1256. self.assertEqual(response.data["subname"][0].code, "read-only-on-update")
  1257. # Changing the type is expected to cause an error
  1258. data = {"records": ["3.2.3.4"], "ttl": 3620, "subname": "test", "type": "TXT"}
  1259. response = self.client.patch(url, data)
  1260. self.assertStatus(response, status.HTTP_400_BAD_REQUEST)
  1261. self.assertEqual(response.data["type"][0].code, "read-only-on-update")
  1262. response = self.client.put(url, data)
  1263. self.assertStatus(response, status.HTTP_400_BAD_REQUEST)
  1264. self.assertEqual(response.data["type"][0].code, "read-only-on-update")
  1265. # Changing "created" is no-op
  1266. response = self.client.get(url)
  1267. data = response.data
  1268. created = data["created"]
  1269. data["created"] = "2019-07-19T17:22:49.575717Z"
  1270. response = self.client.patch(url, data)
  1271. self.assertStatus(response, status.HTTP_200_OK)
  1272. response = self.client.put(url, data)
  1273. self.assertStatus(response, status.HTTP_200_OK)
  1274. # Check that nothing changed
  1275. response = self.client.get(url)
  1276. self.assertStatus(response, status.HTTP_200_OK)
  1277. self.assertEqual(response.data["records"][0], "2.2.3.4")
  1278. self.assertEqual(response.data["ttl"], 3620)
  1279. self.assertEqual(
  1280. response.data["name"], "test." + self.my_rr_set_domain.name + "."
  1281. )
  1282. self.assertEqual(response.data["subname"], "test")
  1283. self.assertEqual(response.data["type"], "A")
  1284. self.assertEqual(response.data["created"], created)
  1285. # This is expected to work, but the fields are ignored
  1286. data = {"records": ["3.2.3.4"], "name": "example.com.", "domain": "example.com"}
  1287. with self.assertRequests(
  1288. self.requests_desec_rr_sets_update(name=self.my_rr_set_domain.name)
  1289. ):
  1290. response = self.client.patch(url, data)
  1291. self.assertStatus(response, status.HTTP_200_OK)
  1292. response = self.client.get(url)
  1293. self.assertStatus(response, status.HTTP_200_OK)
  1294. self.assertEqual(response.data["records"][0], "3.2.3.4")
  1295. self.assertEqual(response.data["domain"], self.my_rr_set_domain.name)
  1296. self.assertEqual(
  1297. response.data["name"], "test." + self.my_rr_set_domain.name + "."
  1298. )
  1299. def test_update_unknown_rrset(self):
  1300. url = self.reverse(
  1301. "v1:rrset",
  1302. name=self.my_rr_set_domain.name,
  1303. subname="doesnotexist",
  1304. type="A",
  1305. )
  1306. data = {"records": ["3.2.3.4"], "ttl": 3620}
  1307. response = self.client.patch(url, data)
  1308. self.assertStatus(response, status.HTTP_404_NOT_FOUND)
  1309. response = self.client.put(url, data)
  1310. self.assertStatus(response, status.HTTP_404_NOT_FOUND)
  1311. def test_delete_my_rr_sets_with_patch(self):
  1312. data = {"records": []}
  1313. for subname in self.SUBNAMES:
  1314. with self.assertRequests(
  1315. self.requests_desec_rr_sets_update(name=self.my_rr_set_domain.name)
  1316. ):
  1317. response = self.client.patch_rr_set(
  1318. self.my_rr_set_domain.name, subname, "A", data
  1319. )
  1320. self.assertStatus(response, status.HTTP_204_NO_CONTENT)
  1321. # Deletion is only idempotent via DELETE. For PATCH/PUT, the view raises 404 if the instance does not
  1322. # exist. By that time, the view has not parsed the payload yet and does not know it is a deletion.
  1323. response = self.client.patch_rr_set(
  1324. self.my_rr_set_domain.name, subname, "A", data
  1325. )
  1326. self.assertStatus(response, status.HTTP_404_NOT_FOUND)
  1327. response = self.client.get_rr_set(self.my_rr_set_domain.name, subname, "A")
  1328. self.assertStatus(response, status.HTTP_404_NOT_FOUND)
  1329. def test_delete_my_rr_sets_with_delete(self):
  1330. for subname in self.SUBNAMES:
  1331. with self.assertRequests(
  1332. self.requests_desec_rr_sets_update(name=self.my_rr_set_domain.name)
  1333. ):
  1334. response = self.client.delete_rr_set(
  1335. self.my_rr_set_domain.name, subname=subname, type_="A"
  1336. )
  1337. self.assertStatus(response, status.HTTP_204_NO_CONTENT)
  1338. domain = Domain.objects.get(name=self.my_rr_set_domain.name)
  1339. self.assertEqual(domain.touched, domain.published)
  1340. response = self.client.delete_rr_set(
  1341. self.my_rr_set_domain.name, subname=subname, type_="A"
  1342. )
  1343. self.assertStatus(response, status.HTTP_204_NO_CONTENT)
  1344. response = self.client.get_rr_set(
  1345. self.my_rr_set_domain.name, subname=subname, type_="A"
  1346. )
  1347. self.assertStatus(response, status.HTTP_404_NOT_FOUND)
  1348. def test_delete_other_rr_sets(self):
  1349. data = {"records": []}
  1350. for subname in self.SUBNAMES:
  1351. # Try PATCH empty
  1352. response = self.client.patch_rr_set(
  1353. self.other_rr_set_domain.name, subname, "A", data
  1354. )
  1355. self.assertStatus(response, status.HTTP_404_NOT_FOUND)
  1356. # Try DELETE
  1357. response = self.client.delete_rr_set(
  1358. self.other_rr_set_domain.name, subname, "A"
  1359. )
  1360. self.assertStatus(response, status.HTTP_404_NOT_FOUND)
  1361. # Make sure it actually is still there
  1362. self.assertGreater(
  1363. len(
  1364. self.other_rr_set_domain.rrset_set.filter(subname=subname, type="A")
  1365. ),
  1366. 0,
  1367. )
  1368. def test_import_rr_sets(self):
  1369. with self.assertRequests(
  1370. self.request_pdns_zone_retrieve(name=self.my_domain.name)
  1371. ):
  1372. call_command("sync-from-pdns", self.my_domain.name)
  1373. for response in [
  1374. self.client.get_rr_sets(self.my_domain.name),
  1375. self.client.get_rr_sets(self.my_domain.name, subname=""),
  1376. ]:
  1377. self.assertStatus(response, status.HTTP_200_OK)
  1378. self.assertEqual(len(response.data), 1, response.data)
  1379. self.assertContainsRRSets(
  1380. response.data,
  1381. [dict(subname="", records=settings.DEFAULT_NS, type="NS")],
  1382. )
  1383. def test_extra_dnskeys(self):
  1384. name = "ietf.org"
  1385. dnskeys = [
  1386. "256 3 5 AwEAAdDECajHaTjfSoNTY58WcBah1BxPKVIHBz4IfLjfqMvium4lgKtKZLe97DgJ5/NQrNEGGQmr6fKvUj67cfrZUojZ2cGRiz"
  1387. "VhgkOqZ9scaTVXNuXLM5Tw7VWOVIceeXAuuH2mPIiEV6MhJYUsW6dvmNsJ4XwCgNgroAmXhoMEiWEjBB+wjYZQ5GtZHBFKVXACSWTiCtdd"
  1388. "HcueOeSVPi5WH94VlubhHfiytNPZLrObhUCHT6k0tNE6phLoHnXWU+6vpsYpz6GhMw/R9BFxW5PdPFIWBgoWk2/XFVRSKG9Lr61b2z1R12"
  1389. "6xeUwvw46RVy3hanV3vNO7LM5HniqaYclBbhk=",
  1390. "257 3 5 AwEAAavjQ1H6pE8FV8LGP0wQBFVL0EM9BRfqxz9p/sZ+8AByqyFHLdZcHoOGF7CgB5OKYMvGOgysuYQloPlwbq7Ws5WywbutbX"
  1391. "yG24lMWy4jijlJUsaFrS5EvUu4ydmuRc/TGnEXnN1XQkO+waIT4cLtrmcWjoY8Oqud6lDaJdj1cKr2nX1NrmMRowIu3DIVtGbQJmzpukpD"
  1392. "VZaYMMAm8M5vz4U2vRCVETLgDoQ7rhsiD127J8gVExjO8B0113jCajbFRcMtUtFTjH4z7jXP2ZzDcXsgpe4LYFuenFQAcRBRlE6oaykHR7"
  1393. "rlPqqmw58nIELJUFoMcb/BdRLgbyTeurFlnxs=",
  1394. ]
  1395. expected_ds = [
  1396. "45586 5 2 67fcd7e0b9e0366309f3b6f7476dff931d5226edc5348cd80fd82a081dfcf6ee",
  1397. "45586 5 4 aee6931c7790c428bca35dab9179cb27f042715e38e5a8adb6bb24c57c21c65dbd02a5b09887787f30128bfac8b6f0b5",
  1398. ]
  1399. domain = Domain.objects.create(name=name, owner=self.owner)
  1400. rrset = domain.rrset_set.create(subname="", type="DNSKEY", ttl=3600)
  1401. rrset.records.bulk_create(
  1402. [RR(rrset=rrset, content=dnskey) for dnskey in dnskeys]
  1403. )
  1404. url = self.reverse("v1:domain-detail", name=domain.name)
  1405. with self.assertRequests(
  1406. self.request_pdns_zone_retrieve_crypto_keys(name=domain.name)
  1407. ):
  1408. response = self.client.get(url)
  1409. self.assertStatus(response, status.HTTP_200_OK)
  1410. self.assertEqual(
  1411. response.data["keys"],
  1412. [
  1413. {
  1414. "dnskey": key["dnskey"],
  1415. "ds": key["cds"] if key["flags"] & 1 else [],
  1416. "flags": key["flags"],
  1417. "keytype": key["keytype"],
  1418. "managed": True,
  1419. }
  1420. for key in self.get_body_pdns_zone_retrieve_crypto_keys()
  1421. ]
  1422. + [
  1423. {
  1424. "dnskey": dnskeys[0],
  1425. "ds": [],
  1426. "flags": 256,
  1427. "keytype": None,
  1428. "managed": False,
  1429. },
  1430. {
  1431. "dnskey": dnskeys[1],
  1432. "ds": expected_ds,
  1433. "flags": 257,
  1434. "keytype": None,
  1435. "managed": False,
  1436. },
  1437. ],
  1438. )
  1439. def test_rrsets_policies(self):
  1440. domain = self.my_empty_domain
  1441. def assertRequests(*, allowed):
  1442. cm = (
  1443. self.assertRequests(self.requests_desec_rr_sets_update(domain.name))
  1444. if allowed
  1445. else nullcontext()
  1446. )
  1447. data = {"subname": "www", "type": "A", "ttl": 3600, "records": ["1.2.3.4"]}
  1448. with cm:
  1449. self.assertStatus(
  1450. self.client.post_rr_set(domain_name=domain.name, **data),
  1451. status.HTTP_201_CREATED if allowed else status.HTTP_403_FORBIDDEN,
  1452. )
  1453. data["records"] = ["4.3.2.1"]
  1454. with cm:
  1455. self.assertStatus(
  1456. self.client.put_rr_set(domain.name, "www", "A", data),
  1457. status.HTTP_200_OK if allowed else status.HTTP_404_NOT_FOUND,
  1458. )
  1459. data["records"] = [] # delete
  1460. with cm:
  1461. self.assertStatus(
  1462. self.client.patch_rr_set(domain.name, "www", "A", data),
  1463. status.HTTP_204_NO_CONTENT
  1464. if allowed
  1465. else status.HTTP_404_NOT_FOUND,
  1466. )
  1467. self.assertStatus(
  1468. self.client.patch_rr_set(domain.name, "www", "A", data),
  1469. status.HTTP_404_NOT_FOUND, # no permission needed to see that
  1470. )
  1471. self.assertStatus(
  1472. self.client.delete_rr_set(domain.name, "www", "A"),
  1473. status.HTTP_204_NO_CONTENT, # no permission needed for idempotency
  1474. )
  1475. if not allowed:
  1476. # Create RRset manually so we cn try manipulating it
  1477. data["contents"] = data.pop("records")
  1478. self.my_empty_domain.rrset_set.create(**data)
  1479. data["records"] = data.pop("contents")
  1480. for response in [
  1481. self.client.patch_rr_set(domain.name, "www", "A", data),
  1482. self.client.put_rr_set(domain.name, "www", "A", data),
  1483. self.client.delete_rr_set(domain.name, "www", "A"),
  1484. ]:
  1485. self.assertStatus(response, status.HTTP_403_FORBIDDEN)
  1486. # Clean up
  1487. rrset_qs = domain.rrset_set.filter(subname="www", type="A")
  1488. if not allowed:
  1489. self.assertTrue(rrset_qs.exists())
  1490. rrset_qs.delete()
  1491. self.assertFalse(rrset_qs.exists())
  1492. assertRequests(allowed=True)
  1493. qs = self.token.tokendomainpolicy_set
  1494. qs.create(domain=None, subname=None, type=None)
  1495. assertRequests(allowed=False)
  1496. qs.create(domain=domain, subname=None, type="A", perm_write=True)
  1497. assertRequests(allowed=True)
  1498. qs.create(domain=domain, subname="www", type="A", perm_write=False)
  1499. assertRequests(allowed=False)
  1500. class AuthenticatedRRSetLPSTestCase(AuthenticatedRRSetBaseTestCase):
  1501. DYN = True
  1502. def test_create_my_rr_sets_ip_block(self):
  1503. BlockedSubnet.from_ip("3.2.2.3").save()
  1504. response = self.client.post_rr_set(
  1505. self.my_domain.name,
  1506. records=["3.2.2.5"],
  1507. ttl=3660,
  1508. subname="blocktest",
  1509. type="A",
  1510. )
  1511. self.assertStatus(response, status.HTTP_400_BAD_REQUEST)
  1512. self.assertIn("IP address 3.2.2.5 not allowed.", str(response.data))