Azure DNS support for Dynamic DNS

PR: https://github.com/opnsense/plugins/pull/1547
This commit is contained in:
Martin
2020-01-03 12:51:08 +01:00
committed by Franco Fichtner
parent 2d0c6ac443
commit ab0ccc3be9
3 changed files with 146 additions and 18 deletions
@@ -97,6 +97,8 @@ function dyndns_list()
return array(
'3322' => '3322',
'azure' => 'Azure DNS',
'azurev6' => 'Azure DNS (v6)',
'citynetwork' => 'City Network',
'cloudflare' => 'CloudFlare',
'cloudflare-v6' => 'CloudFlare (v6)',
@@ -167,6 +169,7 @@ function dyndns_configure_client($conf)
$dnsUpdateURL = "{$conf['updateurl']}",
$forceUpdate = $conf['force'],
$dnsZoneID = $conf['zoneid'],
$dnsResourceID = $conf['resourceid'],
$dnsTTL = $conf['ttl'],
$dnsResultMatch = "{$conf['resultmatch']}",
$dnsRequestIf = "{$conf['requestif']}",
@@ -40,6 +40,7 @@
* - regfish IPv6 (regfish.de)
* - dynv6 IPv6 (dynv6.com)
* - DigitalOcean (digitalocean.com)
* - Azure DNS (azure.microsoft.com)
* - Linode (linode.com)
* - Linode IPv6 (linode.com)
* +----------------------------------------------------+
@@ -95,6 +96,7 @@
* dynv6 - Last Tested: 25 June 2019
* dynv6 v6 - Last Tested: 25 June 2019
* DigitalOcean - Last Tested: 25 June 2019
* Azure DNS - Last Tested: 16 October 2019
* Linode - Last Tested: 12 November 2019
* Linode v6 - Last Tested: 12 November 2019
* +====================================================+
@@ -127,6 +129,7 @@ class updatedns
var $_dnsPort;
var $_dnsUpdateURL;
var $_dnsZoneID;
var $_dnsResourceID;
var $_dnsTTL;
var $status;
var $_debugID;
@@ -165,6 +168,7 @@ class updatedns
$dnsUpdateURL = '',
$forceUpdate = false,
$dnsZoneID = '',
$dnsResourceID = '',
$dnsTTL = '',
$dnsResultMatch = '',
$dnsRequestIf = '',
@@ -204,8 +208,7 @@ class updatedns
case 'namecheap':
if (!$dnsPass) {
$this->_error(4);
}
if (!$dnsHost) {
} elseif (!$dnsHost) {
$this->_error(5);
}
break;
@@ -213,8 +216,7 @@ class updatedns
case 'route53-v6':
if (!$dnsZoneID) {
$this->_error(8);
}
if (!$dnsTTL) {
} elseif (!$dnsTTL) {
$this->_error(9);
}
break;
@@ -231,25 +233,37 @@ class updatedns
case 'regfish-v6':
if (!$dnsUser) {
$this->_error(3);
}
if (!$dnsHost) {
} elseif (!$dnsHost) {
$this->_error(5);
}
break;
case 'azure':
case 'azurev6':
if (!$dnsUser) {
$this->_error(3);
} elseif (!$dnsPass) {
$this->_error(4);
} elseif (!$dnsHost) {
$this->_error(5);
} elseif (!$dnsResourceID) {
$this->_error(8);
} elseif (!$dnsTTL) {
$this->_error(9);
}
break;
default:
if (!$dnsUser) {
$this->_error(3);
}
if (!$dnsPass) {
} elseif (!$dnsPass) {
$this->_error(4);
}
if (!$dnsHost) {
} elseif (!$dnsHost) {
$this->_error(5);
}
break;
}
switch ($dnsService) {
case 'azurev6':
case 'cloudflare-v6':
case 'custom-v6':
case 'dynv6-v6':
@@ -271,6 +285,7 @@ class updatedns
$this->_dnsWildcard = $dnsWildcard;
$this->_dnsMX = $dnsMX;
$this->_dnsZoneID = $dnsZoneID;
$this->_dnsResourceID = $dnsResourceID;
$this->_dnsTTL = $dnsTTL;
$this->_if = dyndns_failover_interface($dnsIf, $this->_useIPv6 ? 'inet6' : 'all');
$this->_checkIP();
@@ -299,6 +314,8 @@ class updatedns
} else {
switch ($this->_dnsService) {
case '3322':
case 'azure':
case 'azurev6':
case 'citynetwork':
case 'cloudflare':
case 'cloudflare-v6':
@@ -973,6 +990,73 @@ class updatedns
} else {
log_error("Dynamic DNS($fqdn): No zone found for domain");
}
case 'azurev6':
case 'azure':
$hostname = "{$this->_dnsHost}";
$resourceid = trim($this->_dnsResourceID);
$app_id = $this->_dnsUser;
$client_secret = $this->_dnsPass;
$newip = $this->_dnsIP;
$newttl = $this->_dnsTTL;
// ensure resourceid starts with / and has no trailing /
$resourceid = '/' . trim($resourceid, '/');
// extract subscription id from resource id
preg_match('/\\/subscriptions\\/(?<sid>[^\\/]*)/', $resourceid, $result);
$subscriptionid = isset($result['sid']) ? $result['sid'] : '';
if (isset($result['sid'])) {
$subscriptionid = $result['sid'];
} else {
log_error("Azure subscription id not found in resource id ({$resourceid})");
return false;
}
// find tenant id from subscription id
curl_setopt($ch, CURLOPT_URL, "https://management.azure.com/subscriptions/" . $subscriptionid . "?api-version=2016-09-01");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_HEADER, 1);
curl_setopt($ch, CURLOPT_NOBODY, 1);
$output = curl_exec($ch);
$pattern = '/Bearer authorization_uri="https:\\/\\/login.windows.net\\/(?<tid>[^"]*)/i';
preg_match($pattern, $output, $result);
if (isset($result['tid'])) {
$tenantid = $result['tid'];
} else {
log_error("Tenant ID not found");
return false;
}
// get an bearer token
curl_setopt($ch, CURLOPT_URL, "https://login.microsoftonline.com/" . $tenantid . "/oauth2/token");
curl_setopt($ch, CURLOPT_POST, 1);
$body = "resource=" . urlencode("https://management.core.windows.net/") . "&grant_type=client_credentials&client_id=" . $app_id . "&client_secret=" . urlencode($client_secret);
curl_setopt($ch, CURLOPT_POSTFIELDS, $body);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$server_output = curl_exec($ch);
$httpcode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
preg_match("/\"access_token\":\"(?<tok>[^\"]*)\"/", $server_output, $result);
if (isset($result['tok'])) {
$bearertoken = $result['tok'];
} else {
log_error("no valid bearer token");
return false;
}
// Update the DNS record
if ($this->_useIPv6) {
$url = "https://management.azure.com" . $resourceid . "/AAAA/" . $hostname . "?api-version=2017-09-01";
$body = '{"properties":{"TTL":"' . $newttl . '", "AAAARecords":[{"ipv6Address":"' . $newip . '"}]}}';
} else {
$url = "https://management.azure.com" . $resourceid . "/A/" . $hostname . "?api-version=2017-09-01";
$body = '{"properties":{"TTL":"' . $newttl . '", "ARecords":[{"ipv4Address":"' . $newip . '"}]}}';
}
$request_headers = array();
$request_headers[] = 'Accept: application/json';
$request_headers[] = 'Authorization: Bearer ' . $bearertoken;
$request_headers[] = 'Content-Type: application/json';
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_USERAGENT, $this->_UserAgent);
curl_setopt($ch, CURLOPT_HTTPHEADER, $request_headers);
curl_setopt($ch, CURLOPT_POSTFIELDS, $body);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HEADER, 1);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PUT');
break;
default:
break;
@@ -1402,6 +1486,23 @@ class updatedns
log_error("Dynamic DNS ($fqdn): PAYLOAD: {$data}");
}
break;
case 'azure':
case 'azurev6':
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($http_code == 401) {
$status = $status_intro . '(Error) User Authorization Failed';
} else if ($http_code == 201) {
$status = $status_intro . '(Success) IP Address Changed Successfully!';
$successful_update = true;
} else if ($http_code == 200) {
$status = $status_intro . '(Success) IP Address Changed Successfully!';
$successful_update = true;
} else {
$status = $status_intro . '(Error)" Unknown Response"';
log_error("Dynamic DNS: HTTP Status: {$http_code} PAYLOAD: {$data}");
$this->_debug($data);
}
break;
default:
break;
}
+32 -8
View File
@@ -50,7 +50,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'GET') {
if (isset($_GET['id']) && !empty($a_dyndns[$_GET['id']])) {
$id = $_GET['id'];
}
$config_copy_fieldnames = array('username', 'password', 'host', 'mx', 'type', 'zoneid', 'ttl', 'updateurl',
$config_copy_fieldnames = array('username', 'password', 'host', 'mx', 'type', 'zoneid','resourceid', 'ttl', 'updateurl',
'resultmatch', 'requestif', 'descr', 'interface');
foreach ($config_copy_fieldnames as $fieldname) {
if (isset($id) && isset($a_dyndns[$id][$fieldname])) {
@@ -84,7 +84,15 @@ if ($_SERVER['REQUEST_METHOD'] === 'GET') {
$reqdfieldsn = array();
$reqdfields = array('type');
$reqdfieldsn = array(gettext('Service type'));
if ($pconfig['type'] != 'custom' && $pconfig['type'] != 'custom-v6') {
if (in_array($pconfig['type'], array('azure', 'azurev6'))) {
$reqdfields[] = 'password';
$reqdfieldsn[] = gettext('Password');
$reqdfields[] = 'resourceid';
$reqdfieldsn[] = gettext('Resource Id');
$reqdfields[] = 'ttl';
$reqdfieldsn[] = gettext('TTL');
} elseif ($pconfig['type'] != 'custom' && $pconfig['type'] != 'custom-v6') {
$reqdfields[] = 'host';
$reqdfieldsn[] = gettext('Hostname');
$reqdfields[] = 'username';
@@ -144,6 +152,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'GET') {
$dyndns['enable'] = !empty($pconfig['enable']);
$dyndns['interface'] = $pconfig['interface'];
$dyndns['zoneid'] = $pconfig['zoneid'];
$dyndns['resourceid'] = $pconfig['resourceid'];
$dyndns['ttl'] = $pconfig['ttl'];
$dyndns['updateurl'] = $pconfig['updateurl'];
// Trim hard-to-type but sometimes returned characters
@@ -199,6 +208,10 @@ include("head.inc");
case "route53-v6":
$(".type_route53").show();
break;
case "azure":
case "azurev6":
$(".type_azure").show();
break;
default:
$(".type_default").show();
break;
@@ -335,6 +348,7 @@ include("head.inc");
<br /><?= gettext('Route 53: Enter your Access Key ID.') ?>
<br /><?= gettext('Duck DNS: Enter your Token.') ?>
<br /><?= gettext('dynv6: Enter your Token.') ?>
<br /><?= gettext('Azure: Enter your Azure AD application ID.') ?>
<br /><?= gettext('For Custom Entries, Username and Password represent HTTP Authentication username and passwords.') ?>
</div>
</td>
@@ -348,6 +362,7 @@ include("head.inc");
<br /><?= gettext('Route 53: Enter your Secret Access Key.') ?>
<br /><?= gettext('Duck DNS: Leave blank.') ?>
<br /><?= gettext('dynv6: Leave blank.') ?>
<br /><?= gettext('Azure: client secret of the AD application') ?>
<br /><?= gettext('Linode: Enter your Personal Access Token.') ?>
</div>
</td>
@@ -361,6 +376,15 @@ include("head.inc");
</div>
</td>
</tr>
<tr class="opt_field type_azure">
<td><a id="help_for_resourceid" href="#" class="showhelp"><i class="fa fa-info-circle"></i></a> <?= gettext('Resource Id') ?></td>
<td>
<input name="resourceid" type="text" id="resourceid" value="<?= $pconfig['resourceid'] ?>" />
<div class="hidden" data-for="help_for_resourceid">
<?= gettext("Enter the resource id of the DNS Zone in Azure.") ?>
</div>
</td>
</tr>
<tr class="opt_field type_custom">
<td><a id="help_for_updateurl" href="#" class="showhelp"><i class="fa fa-info-circle"></i></a> <?=gettext("Update URL") ?></td>
<td>
@@ -389,7 +413,7 @@ include("head.inc");
</div>
</td>
</tr>
<tr class="opt_field type_route53">
<tr class="opt_field type_route53 type_azure">
<td><a id="help_for_ttl" href="#" class="showhelp"><i class="fa fa-info-circle"></i></a> <?=gettext("TTL");?></td>
<td>
<input name="ttl" type="text" id="ttl" value="<?= $pconfig['ttl'] ?>" />
@@ -408,12 +432,10 @@ include("head.inc");
<td>&nbsp;</td>
<td>
<button name="submit" type="submit" class="btn btn-primary" value="save"><?= gettext('Save') ?></button>
<?php
if (isset($id)): ?>
<?php if (isset($id)): ?>
<button name="force" type="submit" class="btn btn-primary" value="force"><?= gettext('Save and Force Update') ?></button>
<input name="id" type="hidden" value="<?= $id ?>" />
<?php
endif; ?>
<?php endif ?>
<a href="services_dyndns.php" class="btn btn-default"><?= gettext('Cancel') ?></a>
</td>
</tr>
@@ -433,4 +455,6 @@ include("head.inc");
</div>
</div>
</section>
<?php include("foot.inc"); ?>
<?php
include("foot.inc");