Using Filament to Create a WebGUI for FreeRadius

filament radius

Filament is a TALL (Tailwind, Alpine.js, Laravel, Livewire) admin panel framework.

There are several admin panel frameworks available for Laravel. Until recently, my favorite was Laravel Nova — the official Admin Panel, backed by the creators of Laravel.

Having used both Nova And Filament, I decided that I like Filament more.

Filament is free, easy to customize, and backed by a strong community of developers that have created more than 100+ plugins and is there to assist new developers on their discord channel.

In this article, I’m going to use Filament to create a WebGUI for FreeRadius 3.0 – the authentication & authorization protocol used by many HotSpot and VPN providers.

We will cover only the Filament project part. To install the FreeRadius Service, follow this guide: Install FreeRADIUS & daloRADIUS on Ubuntu 22.04 or 20.04 + MySQL/MariaDB (only step 3, which covers Radius installation, and SQL configuration)

Configure the Laravel Database

In our fresh Laravel project, go to the .env and set the database credentials to the ones used by FreeRadius

env settings

The Radius database already contains the tables it requires to operate, you can see the existing database structure in PHPMyAdmin.

radius tables

 

We’re going to add our Laravel tables by running:

php artisan migrate

Install Filament

To install Filament, run:

composer require filament/filament:"^2.0"

Going to http://localhost/admin we should see the login form

Screen Shot 2022 09 09 at 12.01.45 AM

 

Because this is a fresh Laravel project, we need to create a user. One way to do it is by running:

php artisan make:filament-user

We’re ready to log into the Filament AdminPanel for the first time.

Screen Shot 2022 09 09 at 12.06.39 AM

 

Create Models for the FreeRadius tables

The Freeradius database has 8 tables, but we’re going to focus on these 4 for this tutorial.

nas –   it contains data about the radius clients – the device or service sending the auth request (router, VPN server, etc.)

radacct – stores accounting data for every session

radcheck – stores the attributes that will be verified when a user tries to connect

radreply – stores the reply attributes that will be sent once a session is established

radpostauth – logs the connection in the database

 

Let’s create the Laravel models for these 4 tables. No migration files are needed because we already have the table structure by the Freeradius SQL schema.

php artisan make:model Nas
php artisan make:model RadAcct
php artisan make:model RadCheck
php artisan make:model RadReply
php artisan make:model RadPostAuth

 

Because we’re using the Radius Schema for our Laravel Models, we need to make some adjustments.

For each model created, set the $table variable to the corresponding Radius table and $timestamps = false

Screen Shot 2022 09 09 at 12.31.47 PM

Generate the Filament Resource

Filament, as Nova has Resources. The Resource allows us to define our Filament logic for each  Laravel model.

We can generate them using artisan

php artisan make:filament-resource Nas

 

In the file tree, the NasResources has been added.

Screen Shot 2022 09 09 at 12.26.20 AM

 

We can also see them added in the Admin Panel.

Screen Shot 2022 09 09 at 12.29.15 AM

 

Build the Resource

In the NasResource.php file, there are two relevant functions.

The Form function allows us to define the Create and Edit fields.

// NasResource.php    
 public static function form(Form $form): Form
    {
        return $form
            ->schema([

 
    Forms\Components\Fieldset::make('Required')
        ->schema([
            Forms\Components\TextInput::make('nasname')
                ->label('Server IP')
                ->required(),
                
            Forms\Components\TextInput::make('type')
                 ->default('other')
                 ->required(),
            Forms\Components\TextInput::make('ports')
                 ->default(0)
                 ->required(),
                
            Forms\Components\TextInput::make('secret')
                ->placeholder('Use a strong secret')
                ->required(),
        ]),

         Forms\Components\Fieldset::make('Optional')
                ->schema([
                Forms\Components\TextInput::make('shortname'),
                
                 Forms\Components\TextInput::make('server'),
                
                 Forms\Components\TextInput::make('comunity'),
                
                 Forms\Components\TextInput::make('description')
                
            ]),
        ]);
    }

 

The code above generates this Form:
Screen Shot 2022 09 09 at 12.47.12 PM

 

The Table function allows us to define Columns, Actions, and Filters.

public static function table(Table $table): Table
    {
        return $table
            ->columns([
                TextColumn::make('nasname')
                ->label('Server'),
                TextColumn::make('ports'),
                TextColumn::make('type'),
                TextColumn::make('secret'),
                
            ])
            ->filters([
                //
            ])
            ->actions([
                Tables\Actions\EditAction::make(),
            ])
            ->bulkActions([
                Tables\Actions\DeleteBulkAction::make(),
            ]);
    }

 

Screen Shot 2022 09 09 at 12.41.46 PM

 

Using –generate to generate the form and table automatically

The code from the previous step seemed to be a bit too boilerplate-ish for my taste. Luckily, Filament can generate the Form and Table functions automatically based on the structure of the database table of the model.

To take advantage of this, we need to install an additional package.

composer require doctrine/dbal

 

We’re ready to create Resources using --generate

php artisan make:filament-resource RadPostAuth --generate
php artisan make:filament-resource RadAcct --generate
php artisan make:filament-resource User --generate

We can see that the form and table functions already have all the fields in the table.

That saved us a lot of time!

//UserResource.php
public static function form(Form $form): Form
    {
        return $form
            ->schema([
                Forms\Components\TextInput::make('name')
                    ->required()
                    ->maxLength(255),
                Forms\Components\TextInput::make('email')
                    ->email()
                    ->required()
                    ->maxLength(255),
                Forms\Components\DateTimePicker::make('email_verified_at'),
                Forms\Components\TextInput::make('password')
                    ->password()
                    ->required()
                    ->maxLength(255),
            ]);
    }

    public static function table(Table $table): Table
    {
        return $table
            ->columns([
                Tables\Columns\TextColumn::make('name'),
                Tables\Columns\TextColumn::make('email'),
                Tables\Columns\TextColumn::make('email_verified_at')
                    ->dateTime(),
                Tables\Columns\TextColumn::make('created_at')
                    ->dateTime(),
                Tables\Columns\TextColumn::make('updated_at')
                    ->dateTime(),
            ])
            ->filters([
                //
            ])
            ->actions([
                Tables\Actions\EditAction::make(),
            ])
            ->bulkActions([
                Tables\Actions\DeleteBulkAction::make(),
            ]);
    }

 

Radius authentication of Laravel Users

We’re going to use the Laravel email to authenticate users in Radius. For that, we need to connect our User Model to the RadCheck and RadReply.

Because the email in the User Model is unique, we can use it as a ForeignKey in the HasMany relationship.

One User hasMany RadCheck attributes

One User hasMany RadReply attributes

 

 

Using Filament’s Relationship Manager, we can easily add the relationship logic to our admin panel.

User - is the parent model

radius_replies - is the relationship on the parent model

username - is the attribute used to identify radius_replies

php artisan make:filament-relation-manager User radius_checks username
php artisan make:filament-relation-manager User radius_replies username

Screen Shot 2022 09 09 at 3.48.06 PM

 

We need to add the new relations to our UserResource, in the getRelations() function

public static function getRelations(): array
{
    return [
        UserResource\RelationManagers\RadiusChecksRelationManager::class,
        UserResource\RelationManagers\RadiusRepliesRelationManager::class,
    ];
}

 

There is no --generate utility for the relationship forms so we have to do it by hand.

//RadiusChecksRelationManager.php  
public static function form(Form $form): Form
    {
        return $form
            ->schema([
               
                    Forms\Components\TextInput::make('attribute')
                    ->required()
                    ->maxLength(255),
                    Forms\Components\Select::make('op')
                    ->options([
                        ':=' => ':=',
                        '=' => '=',
                        '==' => '==',
                        '+=' => '+=',
                        '!=' => '!=',
                        '>' => '>',
                        '>=' => '>=',
                        '<' => '<',
                        '<=' => '<=',

                        '=~' => '=~',
                        '!~' => '!~',
                        '=*' => '=*',
                        '!*' => '!*',
                    ])
                    
                    ->required()
                    ,
                    Forms\Components\TextInput::make('value')
                    ->required()
                    ->maxLength(255),
            ]);
    }

    public static function table(Table $table): Table
    {
        return $table
            ->columns([
                Tables\Columns\TextColumn::make('username'),
                Tables\Columns\TextColumn::make('attribute'),
                Tables\Columns\TextColumn::make('op'),
                Tables\Columns\TextColumn::make('value'),
                
            ])
            ->filters([
                //
            ])
            ->headerActions([
                Tables\Actions\CreateAction::make(),
            ])
            ->actions([
                Tables\Actions\EditAction::make(),
                Tables\Actions\DeleteAction::make(),
            ])
            ->bulkActions([
                Tables\Actions\DeleteBulkAction::make(),
            ]);
    }

 

RadiusRepliesRelationManager
public static function form(Form $form): Form
    {
        return $form
            ->schema([
                Forms\Components\TextInput::make('attribute')
                ->required()
                ->maxLength(255),
                Forms\Components\Select::make('op')
                ->options([
                    ':=' => ':=',
                    '=' => '=',
                    '==' => '==',
                    '+=' => '+=',
                    '!=' => '!=',
                    '>' => '>',
                    '>=' => '>=',
                    '<' => '<',
                    '<=' => '<=',

                    '=~' => '=~',
                    '!~' => '!~',
                    '=*' => '=*',
                    '!*' => '!*',
                ])
                
                ->required()
                ,
                Forms\Components\TextInput::make('value')
                ->required()
                ->maxLength(255),
            ]);
    }

    public static function table(Table $table): Table
    {
        return $table
        ->columns([
            Tables\Columns\TextColumn::make('username'),
            Tables\Columns\TextColumn::make('attribute'),
            Tables\Columns\TextColumn::make('op'),
            Tables\Columns\TextColumn::make('value'),
            
        ])
            ->filters([
                //
            ])
            ->headerActions([
                Tables\Actions\CreateAction::make(),
            ])
            ->actions([
                Tables\Actions\EditAction::make(),
                Tables\Actions\DeleteAction::make(),
            ])
            ->bulkActions([
                Tables\Actions\DeleteBulkAction::make(),
            ]);
    }

 

Creating a Radius Authentication

With the current Laravel and Filament logic, we can create a Radius Client and Users.

Go to the NAS view, and add a client with a secret.

Using the wildcard “0.0.0.0/0” allows connections from all IPs.

Screen Shot 2022 09 09 at 4.07.02 PM

 

Now, go to the Users view, and add a Cleartext-Password radius check attribute to our user.

Screen Shot 2022 09 09 at 3.58.04 PM

 

Screen Shot 2022 09 09 at 3.57.28 PM

From the command line, run freeradius -X  (the X means Debug Mode)

Screen Shot 2022 09 09 at 4.09.57 PM

 

The radius protocol is ready to receive requests. Run radtest for our Filament user.

Make sure to replace the IP with the IP of your radius server.

root@mainpanel:~# radtest -t pap [email protected] mypass '164.92.242.96:1812' 0 's3cret'

 

Screen Shot 2022 09 09 at 4.13.26 PM

 

We can see a log of the connection in our Filament Admin Panel

filam

Conclusion

I only cover a small part of Filament. It can do many more things! We haven’t covered Actions, Custom Pages, Notifications, or any of the 100+ Plugins.

Be sure to check the official documentation for more information.

 

0 Shares:
Subscribe
Notify of
guest
Receive notifications when your comment receives a reply. (Optional)
Your username will link to your website. (Optional)

0 Comments
Inline Feedbacks
View all comments
You May Also Like