Skip to content

Commit

Permalink
Changes (#3)
Browse files Browse the repository at this point in the history
- Track last modification per zone. Bust Packet Cache prior to record_exists(), which returns false positives in high-frequency environments (request ts - modification ts <= cache-ttl)
- Populate auth.yaml pdns placeholders
- Extend RR support
- Unit test pass
- atomicUpdate()- fix RR renames in which the RR category is orphaned
- ALIAS support
- All components of a TXT must be bounded by double quotes
- Switch to the one true form of indentation
- API endpoint validation queries statistics
  • Loading branch information
msaladna authored Aug 5, 2019
1 parent 3b486ec commit 1bbd246
Show file tree
Hide file tree
Showing 7 changed files with 960 additions and 792 deletions.
21 changes: 16 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,12 +57,21 @@ ansible-playbook addin.yml --extra-vars=addin=apnscp-powerdns

allow-axfr-ips and also-notify directives will be set whenever the addin plays are run.

### Restricting submission access
### Enabling ALIAS support
ALIAS is a synthetic record that allows CNAME records to be set on the zone apex. ALIAS records require `powerdns_enable_recursion` to be enabled as well as an optional `powerdns_recursive_ns` to be set otherwise it will default to the system in `/etc/resolv.conf`.

In the above example, only local requests may submit DNS modifications to the server. None of the below examples affect querying; DNS queries occur over 53/UDP typically (or 53/TCP if packet size exceeds UDP limits). Depending upon infrastructure, there are a few options to securely accept record submission, *all of which require an API key for submission*.
```bash
cpcmd config:set apnscp.bootstrapper powerdns_enable_recursion true
cpcmd config:set apnscp.bootstrapper powerdns_recursive_ns '[1.1.1.1,1.0.0.1]'
# Then re-run the addin...
cd /usr/local/apnscp/resources/playbooks
ansible-playbook addin.yml --extra-vars=addin=apnscp-powerdns
```

#### SSL + Apache
## Remote API access
In the above example, only local requests may submit DNS modifications to the server. None of the below examples affect querying; DNS queries occur over 53/UDP typically (or 53/TCP if packet size exceeds UDP limits). Depending upon infrastructure, there are a few options to securely accept record submission, *all of which require an API key for submission*.

### SSL + Apache
Apache's `ProxyPass` directive send requests to the backend. Brute-force attempts are protected by [mod_evasive](https://github.com/apisnetworks/mod_evasive ) bundled with apnscp. Requests over this medium are protected by SSL, without HTTP/2 to ameliorate handshake overhead. In all but the very high volume API request environments, this will be acceptable.

In this situation, the endpoint is https://myserver.apnscp.com/dns. Changes are made to `/etc/httpd/conf/httpd-custom.conf` within the `<VirtualHost ... :443>` bracket (with `SSLEngine On`!). After adding the below changes, `systemctl restart httpd`.
Expand All @@ -83,7 +92,7 @@ In the above example, API requests can be made via https://myserver.apnscp.com/d
curl -q -H 'X-API-Key: SOMEKEY' https://myserver.apnscp.com/dns/api/v1/servers/localhost
```

##### Disabling brute-force throttling
#### Disabling brute-force throttling

As hinted above, placing PowerDNS behind Apache confers brute-force protection by mod_evasive. By default, 10 of the same requests in 2 seconds can trigger a brute-force block. Two solutions exist, either raise the same-page request threshold or disable mod_evasive.

Expand All @@ -99,7 +108,7 @@ Working off the example above *<Location /dns> ... </Location>*
</Location>
```

#### Standalone server
### Standalone server

PowerDNS can also run by itself on a different port. In this situation, the network is configured to block all external requests to port 8081 except those whitelisted. For example, if the entire 32.12.1.1-32.12.1.255 network can be trusted and under your control, then whitelist the IP range:

Expand Down Expand Up @@ -139,11 +148,13 @@ pdns:
ns:
- ns1.yourdomain.com
- ns2.yourdomain.com
recursion: false
## Optional additional nameservers
```
* `uri` value is the hostname of your master PowerDNS server running the HTTP API webserver (without a trailing slash)
* `key` value is the **API Key** in `pdns.conf` on the master nameserver.
* `ns` value is a list of nameservers as in the example above. Put nameservers on their own lines prefixed with a hyphen and indented accordingly. There is not currently a limit for the number of nameservers you may use, 2-5 is typical and should be geographically distributed per RFC 2182.
* `recursion` controls ALIAS records, which are CNAMEs on apex (RFC 1034). Enabling requires configuration of `resolver` and `expand-alias` in pdns.conf.

### Setting as default

Expand Down
25 changes: 24 additions & 1 deletion plays/powerdns-authoritative-setup/defaults/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,16 @@ powerdns_packages:

powerdns_local_dir: "/etc/pdns/local.d"
powerdns_config_file: /etc/pdns/pdns.conf
# PowerDNS database backend, mysql or pgsql
powerdns_driver: mysql
# Duration for Packet Cache
# @XXX To avoid false positives, all participating nodes MUST have
# pdns.deadline set to this value in auth.yml
powerdns_packet_cache_ttl: 20

# required for ALIAS flattening
powerdns_enable_recursion: false
powerdns_recursive_ns: "{{ dns_robust_nameservers | map('regex_replace', '^([^:]+)$', '\\1:53') | list | join(',') }}"
powerdns_api_port: 8081

# Code installation
Expand All @@ -31,9 +40,21 @@ powerdns_db_host: >-
{{ lookup('pipe', 'grep -e "^\s*g' + powerdns_driver + '-host\s*=" ' + (powerdns_config_file | quote) + ' | cut -d= -f2') | default('localhost', true) | trim }}
powerdns_api_key: >-
{{ lookup('pipe', 'grep -e "^\s*api-key\s*=" ' + (powerdns_config_file | quote) + ' | cut -d= -f2') | default(lookup('password', '/dev/null chars=ascii_letters length=24'), true) | trim }}
# Default configuration in auth.yml if not set
powerdns_skeleton:
pdns:
uri: "http://localhost:{{ powerdns_api_port }}/api/v1"
key: "{{ __powerdns_api_key }}"
ns:
- 127.0.0.1
deadline: "{{ powerdns_packet_cache_ttl }}"
recursion: "{{ powerdns_enable_recursion }}"

powerdns_config:
api: yes
"api-key": "{{ powerdns_api_key }}"
"api-key": "{{ __powerdns_api_key }}"
# PacketCache will cause false positives in record_exists()
"cache-ttl": "{{ powerdns_packet_cache_ttl | int }}"
"include-dir": "{{ powerdns_local_dir }}"
webserver: "{{ powerdns_webserver_enable | default((powerdns_version is version('4.1', '>=')) | ternary('no', 'yes')) }}"
"webserver-address": >-
Expand All @@ -47,6 +68,8 @@ powerdns_config:
"g{{powerdns_driver}}-dbname": "{{ powerdns_db_name }}"
"g{{powerdns_driver}}-host": "{{ powerdns_db_host }}"
version-string: anonymous
expand-alias: "{{ powerdns_enable_recursion | ternary('yes', 'no') }}"
resolver: "{{ powerdns_enable_recursion | ternary(powerdns_recursive_ns, 'no') }}"

# Inject overrides via powerdns_custom_config
# powerdns_custom_config:
Expand Down
18 changes: 17 additions & 1 deletion plays/powerdns-authoritative-setup/tasks/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,12 @@
- assert:
that: powerdns_driver in ['mysql','pgsql']
fail_msg: "Unknown driver {{ powerdns_driver }}"

- assert:
that: powerdns_version is version('4.1', '>=') or not powerdns_enable_recursion
fail_msg: "PowerDNS recursion requires PowerDNS 4.1+ - {{ powerdns_version }} requested"
- set_fact:
__powerdns_db_password: "{{ powerdns_db_password }}"
__powerdns_api_key: "{{ powerdns_api_key }}"

- name: Create RPM repo configuration
template:
Expand All @@ -34,6 +37,19 @@
with_items:
- service: dns

- name: Extract pdns configuration from auth.yml
include_vars:
name: __authcfg
file: "{{ apnscp_root }}/config/auth.yaml"
- name: Combine pdns configuration
set_fact:
__powerdns_skeleton: "{{ powerdns_skeleton | combine(__authcfg | default({}), recursive=True) }}"
- name: Update auth.yaml configuration
copy:
content: "{{ __powerdns_skeleton | to_nice_yaml(indent=2) }}"
dest: "{{ apnscp_root }}/config/auth.yaml"
notify: Restart apnscp

- name: Install provider module
include_role: name=common/addin tasks_from="{{ powerdns_enabled | ternary('install', 'remove') }}-library.yml"
vars:
Expand Down
46 changes: 40 additions & 6 deletions src/Api.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,14 @@ class Api {
*/
protected $lastResponse;

/**
* @var int last destructive action
*/
private $lastModification;

// @var int deadline for Packet Cache queries
private $deadline;

/**
* Api constructor.
*/
Expand All @@ -38,6 +46,8 @@ public function __construct()
$this->client = new \GuzzleHttp\Client([
'base_uri' => rtrim($this->endpoint, '/') . '/',
]);
$this->lastModification = time();
$this->deadline = defined('AUTH_PDNS_DEADLINE') ? (int)AUTH_PDNS_DEADLINE : 20;
}

public function do(string $method, string $endpoint, array $params = null): array
Expand All @@ -49,12 +59,19 @@ public function do(string $method, string $endpoint, array $params = null): arra

return [];
}
if ($endpoint[0] === '/')
{
warn("Stripping `/' from endpoint `%s', remove the trailing / from auth.yaml", $endpoint);
$endpoint = ltrim($endpoint, '/');
}
if (strpos($endpoint, 'servers') === false)


if ($endpoint[0] === '/')
{
warn("Stripping `/' from endpoint `%s', remove the trailing / from auth.yaml", $endpoint);
$endpoint = ltrim($endpoint, '/');
}

if ($method !== 'GET' && 0 !== strpos('cache/flush?', $endpoint)) {
$this->lastModification = time();
}

if (strpos($endpoint, 'servers/') === false)
{
$endpoint = 'servers/localhost/' . $endpoint;
}
Expand All @@ -74,4 +91,21 @@ public function getResponse(): Response
{
return $this->lastResponse;
}

/**
* Get last modification time
*
* Used to bypass Packet Cache
*
* @return int
*/
public function getLastModification(): int
{
return $this->lastModification;
}

public function dirty(): bool
{
return (time() - $this->lastModification) <= $this->deadline;
}
}
Loading

0 comments on commit 1bbd246

Please sign in to comment.