AWSRoute53.php 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564
  1. <?php
  2. namespace MGModule\DNSManager2\mgLibs\custom\dns\submodules;
  3. use MGModule\DNSManager2\mgLibs\custom\dns;
  4. use MGModule\DNSManager2\mgLibs\custom\dns\exceptions\DNSSubmoduleException;
  5. use MGModule\DNSManager2\mgLibs\custom\dns\interfaces;
  6. use MGModule\DNSManager2\mgLibs\custom\dns\submodules\AWSRoute53 as AwsRouteHelpers;
  7. use MGModule\DNSManager2\mgLibs\custom\dns\submodules\AWSRoute53\AWSRoute53API;
  8. use MGModule\DNSManager2\mgLibs\custom\dns\submodules\AWSRoute53\AWSRoute53ResponseInterface;
  9. class AWSRoute53 extends dns\SubmoduleAbstract implements
  10. interfaces\SubmoduleRDNSInterface,
  11. interfaces\SubmoduleTTLInterface,
  12. interfaces\SubmoduleImportInterface,
  13. interfaces\SubmoduleRemoveDefaultRecords,
  14. interfaces\SubmoduleCustomParseEditZoneInput
  15. {
  16. public $configFields = [
  17. 'accessKeyId' => [
  18. 'friendlyName' => 'Access Key Id',
  19. 'validators' => [
  20. 'required' => 'required'
  21. ]
  22. ],
  23. 'secretAccessKey' => [
  24. 'friendlyName' => 'Secret Access Key',
  25. 'type' => 'password',
  26. 'validators' => [
  27. 'required' => 'required'
  28. ]
  29. ],
  30. 'region' => [
  31. 'friendlyName' => 'Region'
  32. ],
  33. 'delegation_set' => [
  34. 'friendlyName' => 'Delegation Set Id',
  35. 'validators' => [
  36. ]
  37. ],
  38. 'soa_edit' => [
  39. 'friendlyName' => 'Allow To Edit SOA Records',
  40. 'type' => 'yesno'
  41. ],
  42. 'delete_aws_ns' => [
  43. 'friendlyName' => 'Delete AWS NS Records After Zone Creation',
  44. 'type' => 'yesno'
  45. ],
  46. 'use_white_label_nameservers' => [
  47. 'friendlyName' => 'Using White Label Nameservers',
  48. 'type' => 'yesno',
  49. 'help' => 'Check this box if you are using White Label Nameservers'
  50. ],
  51. ];
  52. public $availableTypes = ['A', 'AAAA', 'ALIAS', 'CNAME', 'MX', 'NAPTR', 'NS', 'PTR', 'SOA', 'SPF', 'SRV', 'TXT'];
  53. /** @var AWSRoute53API */
  54. private $connection;
  55. public function testConnection()
  56. {
  57. if(!extension_loaded('SimpleXML'))
  58. {
  59. throw new DNSSubmoduleException('This server requires SimpleXML PHP extension', dns\SubmoduleExceptionCodes::CONNECTION_PROBLEM);
  60. }
  61. $this->loadConnectionInstance();
  62. /** @var AWSRoute53ResponseInterface $zones */
  63. $zones = $this->connection->testConnection();
  64. if($zones->getResponseType() === 'error')
  65. {
  66. throw new DNSSubmoduleException($zones->getResponseMessage(), dns\SubmoduleExceptionCodes::CONNECTION_PROBLEM);
  67. }
  68. return true;
  69. }
  70. public function getNameServers( $index = false )
  71. {
  72. if($this->config['use_white_label_nameservers'] === 'on')
  73. {
  74. return (array)parent::getNameServers();
  75. }
  76. $this->loadConnectionInstance();
  77. /** @var AWSRoute53ResponseInterface $zones */
  78. $zones = $this->connection->listZonesByName($this->domain);
  79. if( $zones->getResponseType() === 'error' )
  80. {
  81. throw new DNSSubmoduleException($zones->getResponseMessage(), dns\SubmoduleExceptionCodes::COMMAND_ERROR);
  82. }
  83. $zone = AwsRouteHelpers\AWSRoute53ResponseParseHelper::findZoneOnZoneList($zones->getParsedResponseBody(), $this->domain);
  84. if( !$zone )
  85. {
  86. throw new DNSSubmoduleException('Zone name is not valid!', dns\SubmoduleExceptionCodes::COMMAND_ERROR);
  87. }
  88. $hostedZone = json_decode(json_encode($this->connection->getHostedZoneById($zone)->getParsedResponseBody()), true);
  89. return (array)$hostedZone['DelegationSet']['NameServers']['NameServer'];
  90. }
  91. public function getRecords($recordType = false)
  92. {
  93. $this->loadConnectionInstance();
  94. /** @var AWSRoute53ResponseInterface $zones */
  95. $zones = $this->connection->listZonesByName($this->domain);
  96. if($zones->getResponseType() === 'error')
  97. {
  98. throw new DNSSubmoduleException($zones->getResponseMessage(), dns\SubmoduleExceptionCodes::COMMAND_ERROR);
  99. }
  100. $zone = AwsRouteHelpers\AWSRoute53ResponseParseHelper::findZoneOnZoneList($zones->getParsedResponseBody(), $this->domain);
  101. if(!$zone)
  102. {
  103. throw new DNSSubmoduleException('Zone name is not valid!', dns\SubmoduleExceptionCodes::COMMAND_ERROR);
  104. }
  105. /** @var AWSRoute53ResponseInterface $records */
  106. $records = $this->connection->listRecords($zone);
  107. if($records->getResponseType() === 'error')
  108. {
  109. throw new DNSSubmoduleException($records->getResponseMessage(), dns\SubmoduleExceptionCodes::COMMAND_ERROR);
  110. }
  111. $availableTypes = $recordType ? [$recordType] : $this->availableTypes;
  112. return AwsRouteHelpers\AWSRoute53ResponseParseHelper::prepareRecordList($records->getParsedResponseBody(), $availableTypes, $this->config['soa_edit'] === 'on');
  113. }
  114. public function addRecord(dns\record\Record $record)
  115. {
  116. $this->loadConnectionInstance();
  117. /** @var AWSRoute53ResponseInterface $zones */
  118. $zones = $this->connection->listZonesByName($this->domain);
  119. if($zones->getResponseType() === 'error')
  120. {
  121. throw new DNSSubmoduleException($zones->getResponseMessage(), dns\SubmoduleExceptionCodes::COMMAND_ERROR);
  122. }
  123. $zone = AwsRouteHelpers\AWSRoute53ResponseParseHelper::findZoneOnZoneList($zones->getParsedResponseBody(), $this->domain);
  124. if(!$zone)
  125. {
  126. throw new DNSSubmoduleException('Zone name is not valid!', dns\SubmoduleExceptionCodes::COMMAND_ERROR);
  127. }
  128. $aliasType = $this->gatAliasTypeIfRequired($record);
  129. if(!$aliasType)
  130. {
  131. $rSet = $this->matchSetForRecord($record);
  132. }
  133. if(isset($rSet) && $rSet)
  134. {
  135. AwsRouteHelpers\AWSRoute53ResponseParseHelper::mergeHostsRecordsForRdata($rSet, $record, $rSet->type);
  136. return $this->editRecord($rSet);
  137. }
  138. $record->name = $record->nameToAbsolute($this->domain);
  139. /** @var AWSRoute53ResponseInterface $aRecord */
  140. $aRecord = $this->connection->createRecord($zone, $record, $aliasType);
  141. if($aRecord->getResponseType() === 'error')
  142. {
  143. throw new DNSSubmoduleException($aRecord->getResponseMessage(), dns\SubmoduleExceptionCodes::COMMAND_ERROR);
  144. }
  145. return true;
  146. }
  147. public function editRecord(dns\record\Record $record)
  148. {
  149. $this->loadConnectionInstance();
  150. /** @var AWSRoute53ResponseInterface $zones */
  151. $zones = $this->connection->listZonesByName($this->domain);
  152. if($zones->getResponseType() === 'error')
  153. {
  154. throw new DNSSubmoduleException($zones->getResponseMessage(), dns\SubmoduleExceptionCodes::COMMAND_ERROR);
  155. }
  156. $zone = AwsRouteHelpers\AWSRoute53ResponseParseHelper::findZoneOnZoneList($zones->getParsedResponseBody(), $this->domain);
  157. if(!$zone)
  158. {
  159. throw new DNSSubmoduleException('Zone name is not valid!', dns\SubmoduleExceptionCodes::COMMAND_ERROR);
  160. }
  161. $aliasType = $this->gatAliasTypeIfRequired($record);
  162. /** @var AWSRoute53ResponseInterface $aRecord */
  163. $aRecord = $this->connection->updateRecord($zone, $record, $aliasType);
  164. if($aRecord->getResponseType() === 'error')
  165. {
  166. throw new DNSSubmoduleException($aRecord->getResponseMessage(), dns\SubmoduleExceptionCodes::COMMAND_ERROR);
  167. }
  168. return true;
  169. }
  170. public function deleteRecord(dns\record\Record $record)
  171. {
  172. $this->loadConnectionInstance();
  173. /** @var AWSRoute53ResponseInterface $zones */
  174. $zones = $this->connection->listZonesByName($this->domain);
  175. if($zones->getResponseType() === 'error')
  176. {
  177. throw new DNSSubmoduleException($zones->getResponseMessage(), dns\SubmoduleExceptionCodes::COMMAND_ERROR);
  178. }
  179. $zone = AwsRouteHelpers\AWSRoute53ResponseParseHelper::findZoneOnZoneList($zones->getParsedResponseBody(), $this->domain);
  180. if(!$zone)
  181. {
  182. throw new DNSSubmoduleException('Zone name is not valid!', dns\SubmoduleExceptionCodes::COMMAND_ERROR);
  183. }
  184. $aliasType = $this->gatAliasTypeIfRequired($record);
  185. if(!$aliasType)
  186. {
  187. $rSet = $this->matchSetForRecord($record);
  188. }
  189. if(isset($rSet) && $rSet)
  190. {
  191. return $this->editRecord($rSet);
  192. }
  193. /** @var AWSRoute53ResponseInterface $aRecord */
  194. $aRecord = $this->connection->deleteRecord($zone, $record, $aliasType);
  195. if($aRecord->getResponseType() === 'error')
  196. {
  197. throw new DNSSubmoduleException($aRecord->getResponseMessage(), dns\SubmoduleExceptionCodes::COMMAND_ERROR);
  198. }
  199. return true;
  200. }
  201. public function zoneExists()
  202. {
  203. $this->loadConnectionInstance();
  204. /** @var AWSRoute53ResponseInterface $zones */
  205. $zones = $this->connection->listZonesByName($this->domain);
  206. if($zones->getResponseType() === 'error')
  207. {
  208. throw new DNSSubmoduleException($zones->getResponseMessage(), dns\SubmoduleExceptionCodes::COMMAND_ERROR);
  209. }
  210. $zone = AwsRouteHelpers\AWSRoute53ResponseParseHelper::findZoneOnZoneList($zones->getParsedResponseBody(), $this->domain);
  211. return $zone ? true : false;
  212. }
  213. public function activateZone()
  214. {
  215. $this->loadConnectionInstance();
  216. $this->log('ACTIVATE ZONE');
  217. if($this->zoneExists())
  218. {
  219. throw new DNSSubmoduleException('Domain name already exists!', dns\SubmoduleExceptionCodes::COMMAND_ERROR);
  220. }
  221. if(empty($this->domain))
  222. {
  223. throw new DNSSubmoduleException('Domain name is not valid!', dns\SubmoduleExceptionCodes::INVALID_PARAMETERS);
  224. }
  225. /** @var AWSRoute53ResponseInterface $zone */
  226. $zone = $this->connection->createZone($this->domain, $this->config['delegation_set']);
  227. if($zone->getResponseType() === 'error')
  228. {
  229. throw new DNSSubmoduleException($zone->getResponseMessage(), dns\SubmoduleExceptionCodes::COMMAND_ERROR);
  230. }
  231. return true;
  232. }
  233. public function terminateZone()
  234. {
  235. $records = $this->getRecords();
  236. foreach($records as $record)
  237. {
  238. try
  239. {
  240. $this->deleteRecord($record);
  241. }
  242. catch(DNSSubmoduleException $exc)
  243. {
  244. }
  245. }
  246. /** @var AWSRoute53ResponseInterface $zones */
  247. $zones = $this->connection->listZonesByName($this->domain);
  248. if($zones->getResponseType() === 'error')
  249. {
  250. throw new DNSSubmoduleException($zones->getResponseMessage(), dns\SubmoduleExceptionCodes::COMMAND_ERROR);
  251. }
  252. $zonesId = AwsRouteHelpers\AWSRoute53ResponseParseHelper::findZoneOnZoneList($zones->getParsedResponseBody(), $this->domain);
  253. if($zonesId === false)
  254. {
  255. throw new DNSSubmoduleException('Zone name is not valid!', dns\SubmoduleExceptionCodes::COMMAND_ERROR);
  256. }
  257. /** @var AWSRoute53ResponseInterface $deleted */
  258. $deleted = $this->connection->deleteHostedZone($zonesId);
  259. if($deleted->getResponseType() === 'error')
  260. {
  261. throw new DNSSubmoduleException($deleted->getResponseMessage(), dns\SubmoduleExceptionCodes::COMMAND_ERROR);
  262. }
  263. return true;
  264. }
  265. public function getZones()
  266. {
  267. $this->loadConnectionInstance();
  268. $zones = $this->connection->listZones();
  269. /** @var AWSRoute53ResponseInterface $zones */
  270. if($zones->getResponseType() === 'error')
  271. {
  272. throw new DNSSubmoduleException($zones->getResponseMessage(), dns\SubmoduleExceptionCodes::COMMAND_ERROR);
  273. }
  274. return AwsRouteHelpers\AWSRoute53ResponseParseHelper::zoneListXmlToArray($zones->getParsedResponseBody());
  275. }
  276. private function loadConnectionInstance()
  277. {
  278. if(!$this->connection)
  279. {
  280. $responseHandler = new AwsRouteHelpers\AWSRoute53Response();
  281. $requestHandler = new AwsRouteHelpers\AWSRoute53Request(
  282. $responseHandler,
  283. $this->config['accessKeyId'],
  284. $this->config['secretAccessKey'],
  285. $this->config['region']
  286. );
  287. $apiHandler = new AwsRouteHelpers\AWSRoute53API($requestHandler);
  288. $this->connection = $apiHandler;
  289. }
  290. }
  291. public function updateRDNS($ip, $ttl = false, $value = false)
  292. {
  293. $revDnsZoneName = dns\utils\ReverseDNSHelper::reverseZoneName($ip);
  294. $zoneId = $this->getRevDNSZoneID($revDnsZoneName);
  295. if(!$zoneId)
  296. {
  297. $zoneId = $this->createRevDnsZone($revDnsZoneName);
  298. }
  299. $revRecord = dns\utils\ReverseDNSHelper::createPTRRecord($ip, $ttl, $value);
  300. $revRecord->name .= '.'.$revDnsZoneName;
  301. /** @var AWSRoute53ResponseInterface $aRecord */
  302. $aRecord = $this->connection->updateRecord($zoneId, $revRecord);
  303. if($aRecord->getResponseType() === 'error')
  304. {
  305. throw new DNSSubmoduleException($aRecord->getResponseMessage(), dns\SubmoduleExceptionCodes::COMMAND_ERROR);
  306. }
  307. return true;
  308. }
  309. public function removeRDNS($ip)
  310. {
  311. $revDnsZoneName = dns\utils\ReverseDNSHelper::reverseZoneName($ip);
  312. $zoneId = $this->getRevDNSZoneID($revDnsZoneName);
  313. if(!$zoneId)
  314. {
  315. return true;
  316. }
  317. /** @var AWSRoute53ResponseInterface $records */
  318. $records = $this->connection->listRecords($zoneId);
  319. if($records->getResponseType() === 'error')
  320. {
  321. throw new DNSSubmoduleException($records->getResponseMessage(), dns\SubmoduleExceptionCodes::COMMAND_ERROR);
  322. }
  323. $recordPrev = dns\utils\ReverseDNSHelper::reverseRecordName($ip);
  324. $recordName = $recordPrev.'.'.$revDnsZoneName;
  325. $recordToRemove = AwsRouteHelpers\AWSRoute53ResponseParseHelper::findPtrRecordByName($records->getParsedResponseBody(), $recordName);
  326. if(!$recordToRemove)
  327. {
  328. return true;
  329. }
  330. /** @var AWSRoute53ResponseInterface $aRecord */
  331. $aRecord = $this->connection->deleteRecord($zoneId, $recordToRemove);
  332. if($aRecord->getResponseType() === 'error')
  333. {
  334. throw new DNSSubmoduleException($aRecord->getResponseMessage(), dns\SubmoduleExceptionCodes::COMMAND_ERROR);
  335. }
  336. return true;
  337. }
  338. public function getRDNSRecord($ip)
  339. {
  340. $revDnsZoneName = dns\utils\ReverseDNSHelper::reverseZoneName($ip);
  341. $zoneId = $this->getRevDNSZoneID($revDnsZoneName);
  342. if(!$zoneId)
  343. {
  344. return [];
  345. }
  346. /** @var AWSRoute53ResponseInterface $records */
  347. $records = $this->connection->listRecords($zoneId);
  348. if($records->getResponseType() === 'error')
  349. {
  350. throw new DNSSubmoduleException($records->getResponseMessage(), dns\SubmoduleExceptionCodes::COMMAND_ERROR);
  351. }
  352. return AwsRouteHelpers\AWSRoute53ResponseParseHelper::prepareRecordList($records->getParsedResponseBody(), $this->availableTypes, $this->config['soa_edit'] === 'on');
  353. }
  354. private function getRevDNSZoneID($zoneName)
  355. {
  356. $this->loadConnectionInstance();
  357. /** @var AWSRoute53ResponseInterface $zones */
  358. $zones = $this->connection->listZonesByName($zoneName);
  359. if($zones->getResponseType() === 'error')
  360. {
  361. throw new DNSSubmoduleException($zones->getResponseMessage(), dns\SubmoduleExceptionCodes::COMMAND_ERROR);
  362. }
  363. $zone = AwsRouteHelpers\AWSRoute53ResponseParseHelper::findZoneOnZoneList($zones->getParsedResponseBody(), $zoneName);
  364. return $zone ? : false;
  365. }
  366. private function createRevDnsZone($revDnsZoneName)
  367. {
  368. /** @var AWSRoute53ResponseInterface $zone */
  369. $zone = $this->connection->createZone($revDnsZoneName);
  370. if($zone->getResponseType() === 'error')
  371. {
  372. throw new DNSSubmoduleException($zone->getResponseMessage(), dns\SubmoduleExceptionCodes::COMMAND_ERROR);
  373. }
  374. return AwsRouteHelpers\AWSRoute53ResponseParseHelper::getZoneIdFromCreateConfirmation($zone->getParsedResponseBody());
  375. }
  376. private function gatAliasTypeIfRequired($record)
  377. {
  378. if($record->type !== 'ALIAS')
  379. {
  380. return false;
  381. }
  382. $dNamePos = stripos($record->name, trim($this->domain, '.'));
  383. if(!$dNamePos && $dNamePos !== 0)
  384. {
  385. $record->name .= '.'.$this->domain;
  386. }
  387. $deafultType = 'A';
  388. $recordsList = $this->getRecords();
  389. foreach($recordsList as $rec)
  390. {
  391. $trimedName = trim($rec->name, '.');
  392. if(($rec->name === $record->rdata->target || $trimedName === $record->rdata->target ) && $rec->type === 'AAAA')
  393. {
  394. return 'AAAA';
  395. }
  396. }
  397. return $deafultType;
  398. }
  399. public function convertInputFormData(&$input)
  400. {
  401. AwsRouteHelpers\AWSRoute53ResponseParseHelper::convertInputFormData($input);
  402. }
  403. /**
  404. * @param dns\record\Record $record
  405. * @return bool|dns\record\Record
  406. * @throws DNSSubmoduleException
  407. */
  408. public function matchSetForRecord( dns\record\Record $record)
  409. {
  410. $cRecord = clone $record;
  411. $allowedTypes = ['MX', 'A', 'AAAA', 'NS'];
  412. if(!in_array($cRecord->type, $allowedTypes, true) )
  413. {
  414. return false;
  415. }
  416. $basicRdata = $cRecord->rdata->toString();
  417. $cRecord->createRDATAObject($cRecord->type);
  418. $cRecord->rdata = null;
  419. $recordList = $this->getRecords();
  420. foreach($recordList as $rec)
  421. {
  422. if($cRecord->type === $rec->type && trim($cRecord->name, '.') === trim($rec->name, '.'))
  423. {
  424. if($rec->rdata->toString() === $basicRdata)
  425. {
  426. continue;
  427. }
  428. AwsRouteHelpers\AWSRoute53ResponseParseHelper::mergeHostsRecordsForRdata($cRecord, $rec, $cRecord->type);
  429. }
  430. }
  431. return $cRecord->rdata !== null ? $cRecord : false;
  432. }
  433. public function removeDefaultServerRecords($defaultModuleRecords)
  434. {
  435. if($this->config['delete_aws_ns'] !== 'on')
  436. {
  437. return false;
  438. }
  439. $cRecords = $this->getRecords();
  440. foreach($cRecords as $cRecord)
  441. {
  442. if($cRecord->type !== 'NS')
  443. {
  444. continue;
  445. }
  446. $found = false;
  447. foreach($defaultModuleRecords as $dRecord)
  448. {
  449. if($this->areRecordsEqual($cRecord, $dRecord))
  450. {
  451. $found = true;
  452. break;
  453. }
  454. }
  455. if($found === false)
  456. {
  457. $this->deleteRecord($cRecord);
  458. }
  459. }
  460. return true;
  461. }
  462. private function areRecordsEqual( dns\record\Record $rec1, dns\record\Record $rec2)
  463. {
  464. $recAname = rtrim($rec1->name,'.');
  465. $recBname = rtrim($rec2->name,'.');
  466. return $recAname === $recBname &&
  467. $rec1->type === $rec2->type &&
  468. $rec1->ttl === $rec2->ttl &&
  469. $rec1->rdata->toString() === $rec2->rdata->toString();
  470. }
  471. }