Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 22 additions & 7 deletions subdomains/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,32 @@ The token needs to have read permissions for `Zone.Zone` and write for `Zone.Dns

By default every server has a subdomain limit of 0. You can change this limit by editing the server in the admin area.

Note: You can't create subdomains for servers with `0.0.0.0` or `::` as allocation!

## Configuring domains
### Domains

Each domain is composed of a name and an optional prefix. The name must be a valid Cloudflare Zone, while the prefix can be used to specify a subdomain on which the server subdomains will be created.

For example: when creating a subdomain `server1` on a domain with name `example.com` and prefix `abc`, the created record will be `server1.abc.example.com`.

## SRV Records
## Configuration

Subdomains support several different DNS Record types. Each type has different requirements before it can be created.

If a DNS Record type is not available, check whether all of it's requirements have been met.

### Valid primary allocation addresses

A and AAAA Subdomains point to the IP address of the server's primary allocation, so they require that IP address to be valid.

The only invalid values are `0.0.0.0` and `::`. They should be changed to proper IP addresses on which your servers can be reached.

### Subdomain targets

CNAME and SRV Subdomains must point to a specific Subdomain target. These can be configured for every node individually in the admin area.

Note: According to [RFC2782](https://www.rfc-editor.org/info/rfc2782/), SRV records must always point to either an A or AAAA record. While some applications may handle SRV records pointing to CNAME records correctly, this can lead to undefined behavior.

### SRV service types

In order to create SRV records instead of A/AAAA you need to do the following:
SRV Subdomains require an SRV service type. This must be configured in the egg features section. The format is `srv-` and then the service name, e.g. `srv-minecraft` or `srv-rust`.

1. Set a `SRV target` for the node
2. Add a [SRV service type](https://github.com/pelican/plugins/blob/main/subdomains/src/Enums/SRVServiceType.php#L10-L15) to the features of the egg. The format is `srv-` and then the service name, e.g. `srv-minecraft` or `srv-rust`.
You can find the list of currently supported SRV service types [here](https://github.com/pelican/plugins/blob/main/subdomains/src/Enums/SRVServiceType.php#L10-L15).
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
public function up(): void
{
Schema::table('nodes', function (Blueprint $table) {
$table->renameColumn('srv_target', 'subdomain_target');
});
}

public function down(): void
{
Schema::table('nodes', function (Blueprint $table) {
$table->renameColumn('subdomain_target', 'srv_target');
});
}
};
4 changes: 2 additions & 2 deletions subdomains/lang/de/strings.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@
'prefix' => 'Präfix',
'record_type' => 'Record Typ',
'is_synced' => 'Ist synchronisiert?',
'srv_target' => 'SRV Ziel',
'no_srv_target' => 'Kein SRV Ziel',
'subdomain_target' => 'Subdomain Ziel',
'no_subdomain_target' => 'Kein Subdomain Ziel',

'sync' => 'Synchronisieren',

Expand Down
4 changes: 2 additions & 2 deletions subdomains/lang/en/strings.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@
'prefix' => 'Prefix',
'record_type' => 'Record type',
'is_synced' => 'Is Synced?',
'srv_target' => 'SRV target',
'no_srv_target' => 'No SRV target',
'subdomain_target' => 'Subdomain target',
'no_subdomain_target' => 'No Subdomain target',

'sync' => 'Sync',

Expand Down
47 changes: 47 additions & 0 deletions subdomains/src/Enums/RecordType.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
<?php

namespace Boy132\Subdomains\Enums;

use App\Models\Server;
use Filament\Support\Contracts\HasLabel;

enum RecordType: string implements HasLabel
{
case A = 'A';
case AAAA = 'AAAA';
case CNAME = 'CNAME';
case SRV = 'SRV';

public function getLabel(): string
{
return $this->name;
}

/**
* @return array<string>
*/
public static function availableRecordTypes(Server $server): array
Comment thread
gavidroselj marked this conversation as resolved.
{
if (!$server->allocation) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CNAME doesn't require an allocation, so that should be available.

return [];
}

$types = [];

if (!in_array($server->allocation->ip, ['0.0.0.0', '::'])) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Allocations with 0.0.0.0 or :: should ALWAYS be blocked. Doesn't matter which record type.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do not understand why SRV and CNAME records should be blocked while having an invalid allocation. The values are never used in the created records, since they only use Subdomain target.

If the user has a working install where they use 0.0.0.0 on nodes, and can create Subdomain target to route to server correctly, I think we should allow them to create SRV and CNAME records.
I cannot see a scenario where a user could connect via Subdomain target, but couldn't connect via a subdomain, where allocation IP was the cause.

I think we should keep restrictions here as open as functionally possible and let the user deal with ensuring everything outside wings routes correctly.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because I have a personal vendetta against 0.0.0.0/:: and I will deny their existence.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah yes, of course.

I am still not a fan of this logic. Since having 0.0.0.0 and :: is explicitly allowed in allocation settings, it feels wrong to disable functionality here, even when it should be unaffected.
I would expect the wings documentation to mention that using 0.0.0.0 and :: for allocations are discouraged, and that the wings system_ips config option exists for docker, as well as needing to clear panel cache for it to apply, before I would be fine with changing this, since I had to spend some time searching through the discord and reading the code to fix that on my setup.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Boy132 Additionally, if you insist on blocking all subdomain types for 0.0.0.0, including CNAME, then this PR will NOT close #79.
@Svenum described having a server with a dynamic IP, which pretty much requires using 0.0.0.0 allocation binds. While I have static IPs on servers and will be unaffected by this, I do not see a clean workaround for their setup.

if (is_ipv6($server->allocation->ip)) {
$types[self::AAAA->name] = self::AAAA->value;
} else {
$types[self::A->name] = self::A->value;
}
}

// @phpstan-ignore property.notFound
if ($server->node->subdomain_target) {
$types[self::CNAME->name] = self::CNAME->value;
$types[self::SRV->name] = self::SRV->value;
}

return $types;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace Boy132\Subdomains\Filament\Admin\Resources\Servers\RelationManagers;

use App\Models\Server;
use Boy132\Subdomains\Enums\RecordType;
use Boy132\Subdomains\Models\CloudflareDomain;
use Boy132\Subdomains\Models\Subdomain;
use Boy132\Subdomains\Rules\NotOnBlacklist;
Expand Down Expand Up @@ -84,7 +85,7 @@ public function table(Table $table): Table
}),
CreateAction::make()
->visible(fn () => CloudflareDomain::count() > 0)
->disabled(fn () => !$this->getOwnerRecord()->allocation || in_array($this->getOwnerRecord()->allocation->ip, ['0.0.0.0', '::']))
->disabled(fn () => count(RecordType::availableRecordTypes($this->getOwnerRecord())) <= 0)
->createAnother(false)
->action(function (array $data, SubdomainService $service) {
try {
Expand Down Expand Up @@ -132,21 +133,11 @@ public function form(Schema $schema): Schema
Select::make('record_type')
->label(trans('subdomains::strings.record_type'))
->disabledOn('edit')
->hidden(fn () => is_null($this->getOwnerRecord()->node->srv_target)) // @phpstan-ignore property.notFound
->dehydratedWhenHidden()
->disabled(fn () => count(RecordType::availableRecordTypes($this->getOwnerRecord())) <= 1)
->required()
->selectablePlaceholder(false)
->options(function () {
$types = is_ipv6($this->getOwnerRecord()->allocation->ip) ? ['AAAA' => 'AAAA'] : ['A' => 'A'];

// @phpstan-ignore property.notFound
if (!is_null($this->getOwnerRecord()->node->srv_target)) {
$types['SRV'] = 'SRV';
}

return $types;
})
->default(fn () => is_ipv6($this->getOwnerRecord()->allocation->ip) ? 'AAAA' : 'A'),
->options(RecordType::availableRecordTypes($this->getOwnerRecord()))
->default(array_first(RecordType::availableRecordTypes($this->getOwnerRecord()))),
]);
}
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?php

namespace Boy132\Subdomains\Filament\Admin\Resources\SubdomainTargets\Pages;

use Boy132\Subdomains\Filament\Admin\Resources\SubdomainTargets\SubdomainTargetResource;
use Filament\Resources\Pages\ManageRecords;

class ManageSubdomainTargets extends ManageRecords
{
protected static string $resource = SubdomainTargetResource::class;
}
Original file line number Diff line number Diff line change
@@ -1,26 +1,26 @@
<?php

namespace Boy132\Subdomains\Filament\Admin\Resources\SrvTargets;
namespace Boy132\Subdomains\Filament\Admin\Resources\SubdomainTargets;

use App\Filament\Admin\Resources\Nodes\Pages\EditNode;
use App\Models\Node;
use Boy132\Subdomains\Filament\Admin\Resources\SrvTargets\Pages\ManageSrvTargets;
use Boy132\Subdomains\Filament\Admin\Resources\SubdomainTargets\Pages\ManageSubdomainTargets;
use Filament\Resources\Resource;
use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Columns\TextInputColumn;
use Filament\Tables\Table;

class SrvTargetResource extends Resource
class SubdomainTargetResource extends Resource
{
protected static ?string $model = Node::class;

protected static ?string $slug = 'srv-targets';
protected static ?string $slug = 'subdomain-targets';

protected static string|\BackedEnum|null $navigationIcon = 'tabler-world-www';

public static function getModelLabel(): string
{
return trans('subdomains::strings.srv_target');
return trans('subdomains::strings.subdomain_target');
}

public static function getNavigationGroup(): ?string
Expand All @@ -37,12 +37,12 @@ public static function table(Table $table): Table
->url(fn (Node $node) => user()?->can('update', $node) ? EditNode::getUrl(['record' => $node]) : null),
TextColumn::make('fqdn')
->label(trans('admin/node.table.address')),
TextInputColumn::make('srv_target')
->label(trans('subdomains::strings.srv_target'))
->placeholder(trans('subdomains::strings.no_srv_target'))
TextInputColumn::make('subdomain_target')
->label(trans('subdomains::strings.subdomain_target'))
->placeholder(trans('subdomains::strings.no_subdomain_target'))
->updateStateUsing(function (Node $node, $state) {
$node->forceFill([
'srv_target' => $state,
'subdomain_target' => $state,
])->save();
}),
])
Expand All @@ -54,7 +54,7 @@ public static function table(Table $table): Table
public static function getPages(): array
{
return [
'index' => ManageSrvTargets::route('/'),
'index' => ManageSubdomainTargets::route('/'),
];
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
use App\Models\Server;
use App\Traits\Filament\BlockAccessInConflict;
use App\Traits\Filament\HasLimitBadge;
use Boy132\Subdomains\Enums\RecordType;
use Boy132\Subdomains\Filament\Server\Resources\Subdomains\Pages\ListSubdomains;
use Boy132\Subdomains\Models\CloudflareDomain;
use Boy132\Subdomains\Models\Subdomain;
Expand Down Expand Up @@ -42,7 +43,7 @@ public static function canAccess(): bool
/** @var Server $server */
$server = Filament::getTenant();

return parent::canAccess() && $server->allocation && !in_array($server->allocation->ip, ['0.0.0.0', '::']) && CloudflareDomain::count() > 0;
return parent::canAccess() && CloudflareDomain::count() > 0 && count(RecordType::availableRecordTypes($server)) > 0;
}

public static function getNavigationLabel(): string
Expand Down Expand Up @@ -138,6 +139,9 @@ public static function table(Table $table): Table

public static function form(Schema $schema): Schema
{
/** @var Server $server */
$server = Filament::getTenant();

return $schema
->components([
TextInput::make('name')
Expand All @@ -163,35 +167,11 @@ public static function form(Schema $schema): Schema
Select::make('record_type')
->label(trans('subdomains::strings.record_type'))
->disabledOn('edit')
->hidden(function () {
/** @var Server $server */
$server = Filament::getTenant();

// @phpstan-ignore property.notFound
return is_null($server->node->srv_target);
})
->dehydratedWhenHidden()
->disabled(fn () => count(RecordType::availableRecordTypes($server)) <= 1)
->required()
->selectablePlaceholder(false)
->options(function () {
/** @var Server $server */
$server = Filament::getTenant();

$types = is_ipv6($server->allocation->ip) ? ['AAAA' => 'AAAA'] : ['A' => 'A'];

// @phpstan-ignore property.notFound
if (!is_null($server->node->srv_target)) {
$types['SRV'] = 'SRV';
}

return $types;
})
->default(function () {
/** @var Server $server */
$server = Filament::getTenant();

return is_ipv6($server->allocation->ip) ? 'AAAA' : 'A';
}),
->options(RecordType::availableRecordTypes($server))
->default(array_first(RecordType::availableRecordTypes($server))),
]);
}

Expand Down
Loading