-
Notifications
You must be signed in to change notification settings - Fork 3.1k
Added support for ddns lookups for addresses in access lists (resolved merge conflicts from #3364) #4386
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
sylphrena0
wants to merge
24
commits into
NginxProxyManager:develop
Choose a base branch
from
sylphrena0:develop
base: develop
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+359
−16
Open
Added support for ddns lookups for addresses in access lists (resolved merge conflicts from #3364) #4386
Changes from 12 commits
Commits
Show all changes
24 commits
Select commit
Hold shift + click to select a range
5586709
Initial pass at DDNS support for client addresses
vari ec9eb0d
Refactor and integrate ddns resolution with nginx module
vari 972d158
fix linter warnings
vari 33f41f7
Fix utils.js linter error
vari 743cdd8
Eliminate circular dependency
vari 7b09fef
Update configs for active hosts only on ddns update
vari 3b0ff57
doc string update
vari e317900
Add support for '-' in ddns domain names
vari 6033b30
Merge remote-tracking branch 'origin-ddns/access-list-client-ddns-sup…
sylphrena0 cae8ba9
add ddns field to new json files
sylphrena0 00ee20c
updated ddns resolver to accept any domain/subdomain instead of domai…
sylphrena0 ad037fe
remove quotes from regex
sylphrena0 79bdc80
update comments and documentation, validate output from getent with r…
sylphrena0 72e2e09
update access list regex to use \d instead of [0-9]
sylphrena0 1f6fa75
Fix getent params in backend/lib/ddns_resolver/ddns_resolver.js
sylphrena0 f736815
update command parsing to pull first ipv4 address from result of gete…
sylphrena0 98b112a
aligned assessments in ddns_resolver.js
sylphrena0 5e079a4
return loopback address in case of failure to resolve ip address
sylphrena0 6d93f82
avoid adding domains to nginx config instead of resolving to loopback…
sylphrena0 6b5dcad
remove resolvedAddress if it matches the original address in nginx an…
sylphrena0 d70661c
resolve ddns after list is created or updated
sylphrena0 450d8d2
add note that ddns is supported
sylphrena0 893d133
fix alignment whitespace issue
sylphrena0 f4a4d23
fix invalid function call on creation/update
sylphrena0 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
const error = require('../error'); | ||
const logger = require('../../logger').ddns; | ||
const utils = require('../utils'); | ||
|
||
const ddnsResolver = { | ||
/** Pattern to match any valid domain/subdomain */ | ||
ddnsRegex: /^((?!-)[A-Za-z\d-]{1,63}(?<!-)\.)+[A-Za-z]{2,6}$/, | ||
|
||
/** | ||
* Resolves the given address to its IP | ||
* @param {String} address | ||
* @param {boolean} forceUpdate: whether to force resolution instead of using the cached value | ||
*/ | ||
resolveAddress: (address, forceUpdate=false) => { | ||
if (!forceUpdate && ddnsResolver._cache.has(address)) { | ||
// Check if it is still valid | ||
const value = ddnsResolver._cache.get(address); | ||
const ip = value[0]; | ||
const lastUpdated = value[1]; | ||
const nowSeconds = Date.now(); | ||
const delta = nowSeconds - lastUpdated; | ||
if (delta < ddnsResolver._updateIntervalMs) { | ||
return Promise.resolve(ip); | ||
} | ||
} | ||
ddnsResolver._cache.delete(address); | ||
// Reach here only if cache value doesn't exist or needs to be updated | ||
let host = address.toLowerCase(); | ||
return ddnsResolver._queryHost(host) | ||
.then((resolvedIP) => { | ||
ddnsResolver._cache.set(address, [resolvedIP, Date.now()]); | ||
return resolvedIP; | ||
}) | ||
.catch((/*error*/) => { | ||
// return input address in case of failure | ||
return address; | ||
}); | ||
}, | ||
|
||
|
||
/** Private **/ | ||
// Properties | ||
/** | ||
* cache mapping host to (ip address, last updated time) | ||
*/ | ||
_cache: new Map(), | ||
|
||
// Methods | ||
/** | ||
* | ||
* @param {String} host | ||
* @returns {Promise} | ||
*/ | ||
_queryHost: (host) => { | ||
return utils.execSafe('getent', ['ahostsv4', 'hosts', host]) | ||
.then((result) => { | ||
if (result.length < 8) { | ||
logger.error(`IP lookup for ${host} returned invalid output: ${result}`); | ||
throw error.ValidationError('Invalid output from getent hosts'); | ||
} | ||
const out = result.split(/\s+/); | ||
return out[0]; | ||
}, | ||
(error) => { | ||
logger.error('Error looking up IP for ' + host + ': ', error); | ||
throw error; | ||
}); | ||
}, | ||
}; | ||
|
||
module.exports = ddnsResolver; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,167 @@ | ||
const internalNginx = require('../../internal/nginx'); | ||
const logger = require('../../logger').ddns; | ||
const internalAccessList = require('../../internal/access-list'); | ||
const ddnsResolver = require('./ddns_resolver'); | ||
|
||
const ddnsUpdater = { | ||
/** | ||
* Starts a timer to periodically check for ddns updates | ||
*/ | ||
initTimer: () => { | ||
ddnsUpdater._initialize(); | ||
ddnsUpdater._interval = setInterval(ddnsUpdater._checkForDDNSUpdates, ddnsUpdater._updateIntervalMs); | ||
logger.info(`DDNS Update Timer initialized (interval: ${Math.floor(ddnsUpdater._updateIntervalMs / 1000)}s)`); | ||
// Trigger a run so that initial cache is populated and hosts can be updated - delay by 10s to give server time to boot up | ||
setTimeout(ddnsUpdater._checkForDDNSUpdates, 10 * 1000); | ||
}, | ||
|
||
/** Private **/ | ||
// Properties | ||
_initialized: false, | ||
_updateIntervalMs: 60 * 60 * 1000, // 1 hr default (overriden with $DDNS_UPDATE_INTERVAL env var) | ||
_interval: null, // reference to created interval id | ||
_processingDDNSUpdate: false, | ||
|
||
// Methods | ||
|
||
_initialize: () => { | ||
if (ddnsUpdater._initialized) { | ||
return; | ||
} | ||
// Init the resolver | ||
// Read and set custom update interval from env if needed | ||
if (typeof process.env.DDNS_UPDATE_INTERVAL !== 'undefined') { | ||
const interval = Number(process.env.DDNS_UPDATE_INTERVAL.toLowerCase()); | ||
if (!isNaN(interval)) { | ||
// Interval value from env is in seconds. Set min to 60s. | ||
ddnsUpdater._updateIntervalMs = Math.max(interval * 1000, 60 * 1000); | ||
} else { | ||
logger.warn(`[DDNS] invalid value for update interval: '${process.env.DDNS_UPDATE_INTERVAL}'`); | ||
} | ||
} | ||
ddnsUpdater._initialized = true; | ||
}, | ||
|
||
/** | ||
* Triggered by a timer, will check for and update ddns hosts in access list clients | ||
*/ | ||
_checkForDDNSUpdates: () => { | ||
logger.info('Checking for DDNS updates...'); | ||
if (!ddnsUpdater._processingDDNSUpdate) { | ||
ddnsUpdater._processingDDNSUpdate = true; | ||
|
||
const updatedAddresses = new Map(); | ||
|
||
// Get all ddns hostnames in use | ||
return ddnsUpdater._getAccessLists() | ||
.then((rows) => { | ||
// Build map of used addresses that require resolution | ||
const usedAddresses = new Map(); | ||
for (const row of rows) { | ||
if (!row.proxy_host_count) { | ||
// Ignore rows (access lists) that are not associated to any hosts | ||
continue; | ||
} | ||
for (const client of row.clients) { | ||
if (!ddnsResolver.ddnsRegex.test(client.address)) { | ||
continue; | ||
} | ||
if (!usedAddresses.has(client.address)) { | ||
usedAddresses.set(client.address, [row]); | ||
} else { | ||
usedAddresses.get(client.address).push(row); | ||
} | ||
} | ||
} | ||
logger.info(`Found ${usedAddresses.size} address(es) in use.`); | ||
// Remove unused addresses | ||
const addressesToRemove = []; | ||
for (const address of ddnsResolver._cache.keys()) { | ||
if (!usedAddresses.has(address)) { | ||
addressesToRemove.push(address); | ||
} | ||
} | ||
addressesToRemove.forEach((address) => { ddnsResolver._cache.delete(address); }); | ||
|
||
const promises = []; | ||
|
||
for (const [address, rows] of usedAddresses) { | ||
let oldIP = ''; | ||
if (ddnsResolver._cache.has(address)) { | ||
oldIP = ddnsResolver._cache.get(address)[0]; | ||
} | ||
const p = ddnsResolver.resolveAddress(address, true) | ||
.then((resolvedIP) => { | ||
if (resolvedIP !== address && resolvedIP !== oldIP) { | ||
// Mark this as an updated address | ||
updatedAddresses.set(address, rows); | ||
} | ||
}); | ||
promises.push(p); | ||
} | ||
|
||
if (promises.length) { | ||
return Promise.all(promises); | ||
} | ||
return Promise.resolve(); | ||
}) | ||
.then(() => { | ||
logger.info(`${updatedAddresses.size} DDNS IP(s) updated.`); | ||
const updatedRows = new Map(); | ||
const proxy_hosts = []; | ||
for (const rows of updatedAddresses.values()) { | ||
for (const row of rows) { | ||
if (!updatedRows.has(row.id)) { | ||
updatedRows.set(row.id, 1); | ||
for (const host of row.proxy_hosts) { | ||
if (host.enabled) { | ||
proxy_hosts.push(host); | ||
} | ||
} | ||
} | ||
} | ||
} | ||
if (proxy_hosts.length) { | ||
logger.info(`Updating ${proxy_hosts.length} proxy host(s) affected by DDNS changes`); | ||
return internalNginx.bulkGenerateConfigs('proxy_host', proxy_hosts) | ||
.then(internalNginx.reload); | ||
} | ||
return Promise.resolve(); | ||
}) | ||
.then(() => { | ||
logger.info('Finished checking for DDNS updates'); | ||
ddnsUpdater._processingDDNSUpdate = false; | ||
}); | ||
} else { | ||
logger.info('Skipping since previous DDNS update check is in progress'); | ||
} | ||
}, | ||
|
||
_getAccessLists: () => { | ||
const fakeAccess = { | ||
can: (/*role*/) => { | ||
return Promise.resolve({ | ||
permission_visibility: 'all' | ||
}); | ||
} | ||
}; | ||
|
||
return internalAccessList.getAll(fakeAccess) | ||
.then((rows) => { | ||
const promises = []; | ||
for (const row of rows) { | ||
const p = internalAccessList.get(fakeAccess, { | ||
id: row.id, | ||
expand: ['owner', 'items', 'clients', 'proxy_hosts.[certificate,access_list.[clients,items]]'] | ||
}, true /* <- skip masking */); | ||
promises.push(p); | ||
} | ||
if (promises.length) { | ||
return Promise.all(promises); | ||
} | ||
return Promise.resolve([]); | ||
}); | ||
} | ||
}; | ||
|
||
module.exports = ddnsUpdater; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.