From 95e15d2c8a8d36788a7c87d01b31bcdaa825a7dc Mon Sep 17 00:00:00 2001 From: Matthew Penner Date: Sun, 9 Oct 2022 16:19:16 -0600 Subject: [PATCH] Cleanup FQDN validation logic, fallback to old hostname check (#4409) Co-authored-by: DaneEveritt --- app/Console/Commands/Node/MakeNodeCommand.php | 20 +---- .../Requests/Admin/Node/NodeFormRequest.php | 35 +------- app/Rules/Fqdn.php | 81 +++++++++++++++++++ 3 files changed, 87 insertions(+), 49 deletions(-) create mode 100644 app/Rules/Fqdn.php diff --git a/app/Console/Commands/Node/MakeNodeCommand.php b/app/Console/Commands/Node/MakeNodeCommand.php index 5a0302a0d..3fbd30d6c 100644 --- a/app/Console/Commands/Node/MakeNodeCommand.php +++ b/app/Console/Commands/Node/MakeNodeCommand.php @@ -58,29 +58,13 @@ class MakeNodeCommand extends Command $data['name'] = $this->option('name') ?? $this->ask('Enter a short identifier used to distinguish this node from others'); $data['description'] = $this->option('description') ?? $this->ask('Enter a description to identify the node'); $data['location_id'] = $this->option('locationId') ?? $this->ask('Enter a valid location id'); - $data['fqdn'] = $this->option('fqdn') ?? $this->ask('Enter a domain name (e.g node.example.com) to be used for connecting to the daemon. An IP address may only be used if you are not using SSL for this node'); - - // Note, this function will also resolve CNAMEs for us automatically, - // there is no need to manually resolve them here. - // - // Using @ as workaround to fix https://bugs.php.net/bug.php?id=73149 - $records = @dns_get_record($data['fqdn'], DNS_A + DNS_AAAA); - if (empty($records)) { - $this->error('The FQDN or IP address provided does not resolve to a valid IP address.'); - - return; - } - $data['public'] = $this->option('public') ?? $this->confirm('Should this node be public? As a note, setting a node to private you will be denying the ability to auto-deploy to this node.', true); $data['scheme'] = $this->option('scheme') ?? $this->anticipate( 'Please either enter https for SSL or http for a non-ssl connection', ['https', 'http'], 'https' ); - if (filter_var($data['fqdn'], FILTER_VALIDATE_IP) && $data['scheme'] === 'https') { - $this->error('A fully qualified domain name that resolves to a public IP address is required in order to use SSL for this node.'); - - return; - } + $data['fqdn'] = $this->option('fqdn') ?? $this->ask('Enter a domain name (e.g node.example.com) to be used for connecting to the daemon. An IP address may only be used if you are not using SSL for this node'); + $data['public'] = $this->option('public') ?? $this->confirm('Should this node be public? As a note, setting a node to private you will be denying the ability to auto-deploy to this node.', true); $data['behind_proxy'] = $this->option('proxy') ?? $this->confirm('Is your FQDN behind a proxy?'); $data['maintenance_mode'] = $this->option('maintenance') ?? $this->confirm('Should maintenance mode be enabled?'); $data['memory'] = $this->option('maxMemory') ?? $this->ask('Enter the maximum amount of memory'); diff --git a/app/Http/Requests/Admin/Node/NodeFormRequest.php b/app/Http/Requests/Admin/Node/NodeFormRequest.php index f7dc5a9a7..c4a294cad 100644 --- a/app/Http/Requests/Admin/Node/NodeFormRequest.php +++ b/app/Http/Requests/Admin/Node/NodeFormRequest.php @@ -1,14 +1,8 @@ . - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ namespace Pterodactyl\Http\Requests\Admin\Node; +use Pterodactyl\Rules\Fqdn; use Pterodactyl\Models\Node; use Pterodactyl\Http\Requests\Admin\AdminFormRequest; @@ -23,30 +17,9 @@ class NodeFormRequest extends AdminFormRequest return Node::getRulesForUpdate($this->route()->parameter('node')); } - return Node::getRules(); - } + $data = Node::getRules(); + $data['fqdn'][] = Fqdn::make('scheme'); - /** - * Run validation after the rules above have been applied. - * - * @param \Illuminate\Validation\Validator $validator - */ - public function withValidator($validator) - { - $validator->after(function ($validator) { - // Note, this function will also resolve CNAMEs for us automatically, - // there is no need to manually resolve them here. - // - // Using @ as workaround to fix https://bugs.php.net/bug.php?id=73149 - $records = @dns_get_record($this->input('fqdn'), DNS_A + DNS_AAAA); - if (empty($records)) { - $validator->errors()->add('fqdn', trans('admin/node.validation.fqdn_not_resolvable')); - } - - // Check that if using HTTPS the FQDN is not an IP address. - if (filter_var($this->input('fqdn'), FILTER_VALIDATE_IP) && $this->input('scheme') === 'https') { - $validator->errors()->add('fqdn', trans('admin/node.validation.fqdn_required_for_ssl')); - } - }); + return $data; } } diff --git a/app/Rules/Fqdn.php b/app/Rules/Fqdn.php new file mode 100644 index 000000000..4f4a04dac --- /dev/null +++ b/app/Rules/Fqdn.php @@ -0,0 +1,81 @@ +data = $data; + + return $this; + } + + /** + * Validates that the value provided resolves to an IP address. If a scheme is + * specified when this rule is created additional checks will be applied. + * + * @param string $attribute + * @param mixed $value + * @return bool + */ + public function passes($attribute, $value) + { + if (filter_var($value, FILTER_VALIDATE_IP)) { + // Check if the scheme is set to HTTPS. + // + // Unless someone owns their IP blocks and decides to pay who knows how much for a + // custom SSL cert, IPs will not be able to use HTTPS. This should prevent most + // home users from making this mistake and wondering why their node is not working. + if ($this->schemeField && Arr::get($this->data, $this->schemeField) === 'https') { + $this->message = 'The :attribute must not be an IP address when HTTPS is enabled.'; + + return false; + } + + return true; + } + + // Lookup A and AAAA DNS records for the FQDN. Note, this function will also resolve CNAMEs + // for us automatically, there is no need to manually resolve them here. + // + // The error suppression is intentional, see https://bugs.php.net/bug.php?id=73149 + $records = @dns_get_record($value, DNS_A + DNS_AAAA); + // If no records were returned fall back to trying to resolve the value using the hosts DNS + // resolution. This will not work for IPv6 which is why we prefer to use `dns_get_record` + // first. + if (!empty($records) || filter_var(gethostbyname($value), FILTER_VALIDATE_IP)) { + return true; + } + + $this->message = 'The :attribute could not be resolved to a valid IP address.'; + + return false; + } + + public function message(): string + { + return $this->message; + } + + /** + * Returns a new instance of the rule with a defined scheme set. + */ + public static function make(string $schemeField = null): self + { + return tap(new static(), function ($fqdn) use ($schemeField) { + $fqdn->schemeField = $schemeField; + }); + } +}