Skip to content
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

feat: Full SSL Support for all Databases #5027

Open
wants to merge 90 commits into
base: next
Choose a base branch
from

Conversation

peaklabs-dev
Copy link
Member

@peaklabs-dev peaklabs-dev commented Jan 31, 2025

Changes

  • feat: automatic SSL configuration during startup for all databases
    • If SSL is disabled, delete the SSL file mounts in the DB
    • If SSL is disabled, delete the SSL folder
    • If SSL is enabled automatically create ssl directory
    • create a new SSL certificate if one does not already exist -> signed by the CA cert of Coolify
    • add SSL certificate and private key to local file volumes so that they are created as file mounts automatically
    • set proper permissions on cert and key for PostgreSQL to work correctly
    • add SSL startup commands
  • feat: full CA cert and server cert logic for secure configuration of verify-full or equivalent
  • feat: CA certificate UI for easy configuration of verify-full or equivalent
    • brief instructions and recommendations
    • copy button to copy the CA file mount for easy configuration of client containers
    • ability to display the CA certificate
    • ability to save your own custom CA Cert
    • ability to generate a new CA Cert (if it is compromised for example)
    • UI for expiration date of the CA Cert
  • feat: prod and dev seeder for automatic CA certificate generation on all existing servers
  • feat: automatic CA cert generation when adding a new server to Coolify
  • feat: add ssl folder during Coolify installation
  • feat: full automatic SSL certificate generation with private key and singing with the CA cert of Coolify
    • generation works for server and CA certs
    • generation of the cert and private key is done via ECC with the curve secp521r1 (after a lot of testing determined to be the best) -> This is highly secure (most secure), super fast and provides super clean and short certificates and private keys
    • automatic setting of the commonName to the container name so that verfiy-full works
    • secure serial number generation
    • full SSL certificate extensions configuration via a temp conf file -> subjectAltName, basicConstraints, keyUsage, extendedKeyUsage, subjectKeyIdentifier ...
    • full certificate signing implementation with sha512 for best security
    • delete old certificate and file mounts before creating new one
    • automatically create file mounts for ssl cert and key
  • feat: Automatic SSL expiration and renewal after 1 year -> checked twice a day.
  • feat: SSL expiration notifications, 14 days before an SSL Certificate expires, it will be renewed and you will be notified to restart the resources.
  • feat: Ability to re-generate the certificate for the current DB (if it has been compromised, for example).
  • feat: new ssl_certificates table (will be used for custom SSL certs for application/services in the future)
  • feat: local file volume paths and content is now stored encrypted in the database for security reasons
  • feat: new copy button component - so all copy buttons now work the same
  • fix: select component should not always uses title case for the label of the select
  • fix(databases): fix database name uses new uuid instead of the DB uuid
  • fix(databases): volume and file mounts are unmounted if there is more than 1 mount
  • fix(databases): fix deletion of DBs
    • Delete file mounts from the DB if specified
    • Delete volume mounts from the DB if specified
    • Delete envs, ssl certs, backups when a DB is deleted
    • Detach tags correctly when a DB is deleted
      when deleting
  • chore: removed unnecessary and duplicate code

Full SSL support for PostgreSQL

  • feat: SSL support supporting all available SSL modes -> allow, prefer, require, verify-ca, verify-full
  • feat: new default of require for SSL -> This requires no user interaction (new URL), and DB connections will simply start using SSL automatically
  • feat: SSL Settings UI for PostgreSQL that also pre-configures connection URLs with all required SSL settings

Full SSL support for MySQL

  • feat: Full SSL support, supporting all available SSL modes -> PREFERRED, REQUIRED, VERIFY_CA, VERIFY_IDENTITY
  • feat: new default of REQUIRED for SSL -> This requires no user interaction (new URL), and DB connections will simply start using SSL automatically
  • feat: SSL Settings UI for MySQL that also pre-configures connection URLs with all required SSL settings

Full SSL support for MariaDB

  • feat: Full SSL support, the CA cert can be provided optionally to have modes equivalent to -> REQUIRED, VERIFY_IDENTITY
  • feat: SSL enabled by default
  • feat: SSL Settings UI for MariaDB

Full SSL support for Redis

  • feat: Full SSL support, the CA cert can be provided optionally to have modes equivalent to -> require, verify-ca
  • feat: SSL disabled by default
  • feat: SSL Settings UI for Redis that also pre-configures connection URLs with all required SSL settings

Full SSL support for KeyDB

  • feat: Full SSL support, the CA cert can be provided optionally to have modes equivalent to -> require, verify-ca
  • feat: SSL disabled by default
  • feat: SSL Settings UI for KeyDB that also pre-configures connection URLs with all required SSL settings

Full SSL support for DragonflyDB

  • feat: Full SSL support (mode equivalent to require)
  • feat: SSL disabled by default
  • feat: SSL Settings UI for DragonflyDB that also pre-configures connection URLs with all required SSL settings

Full SSL support for MongoDB

  • feat: Full SSL support supporting custom modes equivalent to ->allow, prefer, require, verify-full
  • feat: new default of require for SSL -> This requires no user interaction, and DB connections will simply start using SSL automatically
  • feat: SSL Settings UI for MongoDB that also pre-configures connection URLs with all required SSL settings

SSL setting UI for PostgreSQL (very similar for all other DBs):

image

Clean Coolify CA certificate UI:

image

Notes

  • Currently, connecting to any database via verify-full is only possible via its container name, the container IP is only supported with verify-ca this is because we can not add the containers IP to the certificate because the cert is generated before the container is running.
  • Currently, it is only possible to connect to the database using the public or internal URL, custom domain support will be added in the future via this feature request: Add custom Domain support to DBs #5102
  • Support for ClickhouseDB will be tracked here: Add SSL Support to ClickHouse DB #5101

Issues

- create ssl directory
- create a new certificate if one does not already exist
- add the certificates to the file store so that they are created as file mounts
- add SSL startup commands
- Replace RSA 4096 with ECDSA secp521r1 for stronger security (256-bit vs 112-bit)
- Faster certificate generation (3-4x speed improvement)
- 75% smaller key sizes (0.8KB vs 3.2KB) improves storage and transmission
- improve security by making certificates valid for only 90 days instead of 10 years
- add SubjectAltName
- remove unnecessary parameters
- use carbon immutable to make sure expiration date stays the same
- server_id is a foreign id
- server_id must be unique as each server can only have 1 CA cert
- resource_id must be unique as each resource can only have 1 SSL cert
- improve function parameters
- set default validity to 1 year as resources need to be manually restarted to use the new certificates
- use the CA cert to sign certificates
- use CA cert and key for SSL cert generation
- remove unused parameters
- add a few more echo with log output
- brief instructions and recommendations
- copy button to copy the CA file mount
- ability to display the CA certificate
- ability to save your own CA Cert or generate a new one
@albertorizzi
Copy link
Contributor

Will it also implement an SSL certificate for MongoDB? @peaklabs-dev

@peaklabs-dev
Copy link
Member Author

Yes it will be added for all DBs.

@peaklabs-dev peaklabs-dev added the 🏔️ Peaklabs A label for PRs that are ready for review/merge and have been created by Peaklabs. label Feb 10, 2025
@peaklabs-dev peaklabs-dev removed the 🏔️ Peaklabs A label for PRs that are ready for review/merge and have been created by Peaklabs. label Feb 11, 2025
@coollabsio coollabsio deleted a comment from coderabbitai bot Feb 11, 2025
@peaklabs-dev peaklabs-dev added the 🏔️ Peaklabs A label for PRs that are ready for review/merge and have been created by Peaklabs. label Feb 11, 2025
@peaklabs-dev peaklabs-dev marked this pull request as ready for review February 11, 2025 19:58
Copy link

coderabbitai bot commented Feb 11, 2025

📝 Walkthrough

Summary by CodeRabbit

  • New Features
    • Introduced comprehensive SSL certificate management across databases and server configurations.
    • Added new interface elements for enabling SSL, viewing certificate validity, and regenerating certificates.
    • Implemented automated tasks and notifications to ensure timely renewals.
  • Refactor
    • Streamlined backend workflows for generating, storing, and cleaning up SSL certificates.
  • Chores
    • Updated installation scripts and database schemas to support enhanced SSL functionality.

Walkthrough

The pull request integrates comprehensive SSL certificate management throughout the codebase. Multiple database start classes now conditionally create or remove SSL directories, generate certificates when needed using a new helper, and update Docker Compose configurations. Livewire components gain SSL configuration UI elements while various models are extended with polymorphic relationships to SSL certificates. New migrations, seeders, and a dedicated job manage certificate creation and renewal, along with updated resource deletion and volume naming logic to improve persistent storage handling.

Changes

Files Change Summary
app/Actions/Database/StartDragonfly.php, StartKeydb.php, StartMariadb.php, StartMongodb.php, StartMysql.php, StartPostgresql.php, StartRedis.php Added SSL handling: new private $ssl_certificate property, conditional logic in handle() methods, extraction of start command logic via buildStartCommand() (where applicable), and updated Docker Compose volumes to merge SSL mounts.
app/Livewire/Project/Database/…/General.php (Dragonfly, Keydb, Mariadb, Mongodb, Mysql, Postgresql, Redis) Integrated SSL configuration UI with new properties ($certificateValidUntil, $enable_ssl), methods instantSaveSSL() and regenerateSslCertificate(), and added validation rules/attributes.
app/Actions/Server/InstallDocker.php, app/Helpers/SslHelper.php, app/Jobs/RegenerateSslCertJob.php, app/Notifications/SslExpirationNotification.php Introduced centralized SSL support: new SslHelper for certificate generation, new model SslCertificate (with migrations), SSL regeneration job, and multi-channel renewal notifications.
app/Jobs/DeleteResourceJob.php, app/Models/LocalFileVolume.php, LocalPersistentVolume.php, app/Models/StandaloneClickhouse.php, …/Standalone*, app/Traits/HasNotificationSettings.php, Blade components (e.g. copy-button, modal-confirmation) Enhanced resource deletion cleanup, updated persistent volume encryption & naming (removal of legacy name-generation function), added sslCertificates() relationships in Standalone models, and refined notification settings/UI for improved user experience.
database/migrations/*, database/seeders/*, resources/views/…, scripts/install.sh Added migrations for SSL fields, new CA SSL Certificate seeder, inline database naming logic updates, and revisions in Blade components and install script directory structure to support SSL configurations.

Sequence Diagram(s)

sequenceDiagram
    participant DB as Database Service (StartX)
    participant FS as FileSystem
    participant SH as SslHelper

    DB->>DB: Check enable_ssl flag
    alt SSL enabled
        DB->>FS: Create SSL directory
        DB->>SH: Check for existing SSL certificate
        opt If certificate not found
            DB->>SH: Generate new SSL certificate
        end
        DB->>DB: Build start command with SSL parameters
    else SSL disabled
        DB->>FS: Remove SSL directory
        DB->>DB: Build standard start command
    end
Loading

Assessment against linked issues

Objective Addressed Explanation
Default volume mount remains intact when adding file/directory mounts (#5034)
Persistent storage remains consistent and data is not lost across restarts (#5099)
Consistent default volume naming to prevent new volume creation on each redeploy (#2376)

🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

‼️ IMPORTANT
Auto-reply has been disabled for this repository in the CodeRabbit settings. The CodeRabbit bot will not respond to your replies unless it is explicitly tagged.

  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR. (Beta)
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 24

🔭 Outside diff range comments (2)
app/Models/StandaloneDragonfly.php (1)

171-255: 🛠️ Refactor suggestion

Consider extracting common SSL URL generation logic.

The SSL URL generation logic is duplicated across database models. Consider extracting it into a trait or base class to promote DRY principles and ensure consistent SSL handling.

Create a new trait, e.g., HasSslSupport:

trait HasSslSupport
{
    public function sslCertificates()
    {
        return $this->morphMany(SslCertificate::class, 'resource');
    }

    protected function appendSslParameters(string $url): string
    {
        if ($this->enable_ssl && $this->ssl_mode === 'verify-ca') {
            $url .= '?cacert=' . config('ssl.ca_certificate_path', '/etc/ssl/certs/coolify-ca.crt');
        }
        return $url;
    }
}

Then use it in the database models:

class StandaloneDragonfly extends BaseModel
{
-    public function sslCertificates()
-    {
-        return $this->morphMany(SslCertificate::class, 'resource');
-    }
+    use HasSslSupport;

     protected function internalDbUrl(): Attribute
     {
         return new Attribute(
             get: function () {
                 $scheme = $this->enable_ssl ? 'rediss' : 'redis';
                 $port = $this->enable_ssl ? 6380 : 6379;
                 $url = "{$scheme}://:{$this->dragonfly_password}@{$this->uuid}:{$port}/0";
-                if ($this->enable_ssl && $this->ssl_mode === 'verify-ca') {
-                    $url .= '?cacert=/etc/ssl/certs/coolify-ca.crt';
-                }
+                return $this->appendSslParameters($url);
             }
         );
     }
}
app/Models/StandaloneMysql.php (1)

172-255: 🛠️ Refactor suggestion

Standardize SSL mode handling across database types.

While the MySQL-specific URL format is correct, consider standardizing the SSL mode handling:

  1. The SSL modes use different formats:

    • MySQL: 'VERIFY_CA', 'VERIFY_IDENTITY'
    • Redis/KeyDB/Dragonfly: 'verify-ca'
  2. The URL parameters use different formats:

    • MySQL: 'ssl-mode'
    • Redis/KeyDB/Dragonfly: 'cacert'

Create an enum for SSL modes and a helper for database-specific URL formatting:

enum SslMode: string
{
    case VERIFY_CA = 'VERIFY_CA';
    case VERIFY_IDENTITY = 'VERIFY_IDENTITY';
    case REQUIRED = 'REQUIRED';
}

trait HasSslSupport
{
    protected function appendSslParameters(string $url, string $dbType): string
    {
        if (!$this->enable_ssl) {
            return $url;
        }

        return match($dbType) {
            'mysql' => $this->appendMysqlSslParameters($url),
            'redis' => $this->appendRedisSslParameters($url),
            default => $url
        };
    }

    private function appendMysqlSslParameters(string $url): string
    {
        $url .= "?ssl-mode={$this->ssl_mode}";
        if (in_array($this->ssl_mode, [SslMode::VERIFY_CA->value, SslMode::VERIFY_IDENTITY->value])) {
            $url .= '&ssl-ca=' . config('ssl.ca_certificate_path');
        }
        return $url;
    }
}
🧹 Nitpick comments (45)
bootstrap/helpers/databases.php (1)

408-420: Consider adding port number validation.

While the port validation logic is correct, consider adding input validation for the port number to ensure it's within valid range (0-65535) and not in the reserved range.

 function isPublicPortAlreadyUsed(Server $server, int $port, ?string $id = null): bool
 {
+    if ($port < 0 || $port > 65535) {
+        throw new \InvalidArgumentException('Port number must be between 0 and 65535');
+    }
+    
+    // Optional: Check if port is in well-known port range (0-1023)
+    if ($port < 1024) {
+        throw new \InvalidArgumentException('Port number should not be in the reserved range (0-1023)');
+    }
+
     if ($id) {
         $foundDatabase = $server->databases()->where('public_port', $port)->where('is_public', true)->where('id', '!=', $id)->first();
     } else {
resources/views/components/forms/copy-button.blade.php (1)

4-4: Consider adding ARIA label for better accessibility.

The input field should have an aria-label to improve screen reader support.

-    <input type="text" value="{{ $text }}" readonly class="input">
+    <input type="text" value="{{ $text }}" readonly class="input" aria-label="Text to copy">
app/Livewire/Project/Database/Redis/General.php (1)

70-76: Consider using relationship methods for better maintainability.

Instead of using direct queries, consider defining and using an SSL certificate relationship on the database model. This would make the code more maintainable and follow Laravel's conventions.

-        $existingCert = SslCertificate::where('resource_type', $this->database->getMorphClass())
-            ->where('resource_id', $this->database->id)
-            ->first();
+        $existingCert = $this->database->sslCertificate;

         if ($existingCert) {
             $this->certificateValidUntil = $existingCert->valid_until;
         }

Add this relationship to your database model:

public function sslCertificate()
{
    return $this->morphOne(SslCertificate::class, 'resource');
}
app/Livewire/Project/Database/Mysql/General.php (1)

69-75: Add error handling for certificate retrieval.

While the certificate retrieval logic is correct, consider adding try-catch block to handle potential database errors gracefully.

     public function mount()
     {
         $this->db_url = $this->database->internal_db_url;
         $this->db_url_public = $this->database->external_db_url;
         $this->server = data_get($this->database, 'destination.server');
 
+        try {
             $existingCert = SslCertificate::where('resource_type', $this->database->getMorphClass())
                 ->where('resource_id', $this->database->id)
                 ->first();
 
             if ($existingCert) {
                 $this->certificateValidUntil = $existingCert->valid_until;
             }
+        } catch (Exception $e) {
+            handleError($e, $this);
+        }
     }
database/migrations/2025_01_30_125223_encrypt_local_file_volumes_fields.php (1)

48-61: Ensure decryption logic matches the encryption approach.

Similar to encryption, you may want a transaction or batch strategy during decryption. Logging errors is good, but partial decryption without rollback can result in inconsistent states.

app/Jobs/DeleteResourceJob.php (1)

81-84: Rename delete_configurations for clarity.

The comment “// rename to FileStorages” suggests that delete_configurations has outgrown its original purpose. Consider renaming the method to reflect its actual function (e.g., deleteFileStorages) to avoid confusion.

app/Notifications/SslExpirationNotification.php (2)

24-53: Potential collision in resource name array keys.

Storing URLs under $this->urls[$resource->name] can lead to overwriting if multiple resources share the same name. Consider using a stable unique identifier (e.g., UUID) as the array key instead of the resource’s name.


76-146: Reduce duplicated code across notification channels.

The code for constructing messages repeats similar steps (gathering resource names, building URLs). Consider extracting the shared logic to a helper method or a dedicated template to avoid duplication and simplify maintenance.

app/Livewire/Server/Advanced.php (2)

95-119: Long CA certificate validity
The CA certificate is set to 15 years (15 * 365). While it’s valid, consider using a shorter lifetime or making it configurable to align with typical SSL best practices.


121-135: Revisit file permissions
The logic of applying chmod -R 700 and then setting the file to 644 might be simplified for clarity. Also ensure that 9999:root is the intended ownership across all environments.

app/Helpers/SslHelper.php (1)

17-63: Key generation and SAN handling
Overall logic is correct. Using secp521r1 is secure. Consider verifying concurrency scenarios if multiple calls are made simultaneously.

app/Actions/Database/StartDragonfly.php (1)

37-55: Conditional SSL cleanup
Removes the SSL directory and associated certificates when SSL is disabled. Consider prompting for user confirmation if accidental toggling of SSL might cause data loss.

app/Actions/Database/StartMariadb.php (2)

21-21: New nullable property for tracking SSL certificate
Declaratively storing the SSL certificate reference is a clear approach. Consider a short docblock to further explain the property’s usage.


205-207: Ownership adjustment for SSL files
Chowning files to mysql:mysql is correct to ensure the server can access them securely. Consider also restricting file permissions to 600 or similar if needed.

app/Actions/Database/StartMysql.php (2)

169-180: MySQL custom config binding
Like in Mariadb, having a custom config file is beneficial. Remind users to avoid conflicting directives in SSL config.


206-208: Ownership for SSL files
Chowning the certificates to the MySQL user is correct to ensure read access. Consider layering file permissions to prevent overly permissive modes.

app/Actions/Database/StartKeydb.php (2)

82-84: Redefining container name and configuration directory
Reassigning these variables after SSL checks can help ensure they remain consistent. Just keep an eye on potential duplication if multiple reassignments happen in other methods.


201-203: Setting ownership to 999:999
KeyDB typically runs as UID 999. This is correct. Adding a more restrictive file permission (e.g., chmod 600) might be considered for additional protection.

app/Actions/Database/StartRedis.php (3)

22-22: Add a doc comment for the new $ssl_certificate property.
A short description outlining its role in SSL certificate management will help future maintainers.


33-35: Use a standardized logging approach if possible.
These echo statements are clear, but a structured logging mechanism would enhance debugging consistency across the codebase.


196-198: Hard-coded UID/GID might be incompatible with some Docker images.
Using chown -R 999:999 assumes a specific user and group. Consider referencing a named user or verifying the Redis container’s default UID/GID.

app/Actions/Database/StartPostgresql.php (4)

23-23: Document the $ssl_certificate property.
A brief doc comment explaining its purpose and lifecycle would be helpful for maintainers.


36-39: Echo-based messages.
Similar to other actions, consider implementing a structured logging approach for production-level observability.


179-188: Custom Postgres configuration.
Attaching a custom config file is useful. Keep in mind that it can override defaults, so comprehensive documentation or versioning is advised.


220-222: Hard-coded user ownership in container.
executeInDocker(... "chown {$this->database->postgres_user}:{$this->database->postgres_user}") assumes a specific username. Validate that this user matches the container’s default.

app/Actions/Database/StartMongodb.php (5)

21-21: Optional property documentation.
Adding a doc comment for $ssl_certificate helps clarify how SSL certs are tracked and reused.


37-39: Echo-based logging.
As with other classes, consider a standard logging system for uniformity.


166-176: Embedded MongoDB config.
Attaching mongod.conf is convenient, but obligates the team to manage potential mismatches with the default config.


208-241: Reevaluate MongoDB TLS modes.
Allowing invalid hostnames or connections without certificates can compromise security. Consider stricter modes if production demands strong encryption and validation.


251-253: Ownership by mongodb:mongodb.
Confirm this user and group exist in your chosen MongoDB image. Otherwise, permissions issues can arise.

resources/views/emails/ssl-certificate-renewed.blade.php (2)

22-24: Add security measures for URLs in email.

When including URLs in emails, consider:

  1. Adding URL validation
  2. Including a warning about phishing
  3. Adding URL expiration for security

16-16: Add more specific information about certificate expiration.

Instead of "approximately 14 more days", consider showing the exact expiration date of both old and new certificates to help users make informed decisions about redeployment timing.

app/Models/SslCertificate.php (1)

23-28: Add scope for finding expiring certificates.

Consider adding a scope to easily query certificates that are approaching expiration:

public function scopeExpiringSoon($query, $days = 14)
{
    return $query->where('valid_until', '<=', now()->addDays($days))
                 ->where('valid_until', '>', now());
}
database/migrations/2025_01_27_102616_add_ssl_fields_to_database_tables.php (2)

16-16: Standardize SSL mode naming conventions across databases.

The SSL modes use inconsistent naming conventions:

  • PostgreSQL: lowercase with hyphens (e.g., 'verify-ca')
  • MySQL: uppercase with underscores (e.g., 'VERIFY_CA')
  • MongoDB: lowercase with hyphens but missing 'verify-ca' option

Consider standardizing the naming convention across all databases for better maintainability.

Also applies to: 20-20, 36-36


22-24: Consider adding SSL mode for MariaDB.

MariaDB supports SSL modes similar to MySQL, but the migration only adds the enable_ssl field. Consider adding an ssl_mode field to provide granular SSL configuration options.

app/Jobs/RegenerateSslCertJob.php (1)

28-40: Optimize memory usage for large certificate sets.

The query loads all certificates into memory at once. Consider using a cursor for better memory efficiency with large datasets.

-        $certificates = $query->get();
-
-        if ($certificates->isEmpty()) {
-            return;
-        }
+        $query->cursor()->each(function ($certificate) {
app/Actions/Server/InstallDocker.php (1)

72-100: Add error handling for package installation failures.

The package installation commands should handle potential failures gracefully.

Consider wrapping critical installations in error handling:

-                    'command -v curl >/dev/null || apt install -y curl',
+                    'command -v curl >/dev/null || (apt install -y curl || (apt update && apt install -y curl))',
resources/views/livewire/project/database/dragonfly/general.blade.php (1)

52-85: Enhance SSL configuration UI with more guidance.

The SSL configuration UI is well-structured but could benefit from additional user guidance.

Consider adding:

  1. A helper text explaining the implications of enabling/disabling SSL
  2. A tooltip or info icon next to "SSL Configuration" explaining the feature
-            <h3>SSL Configuration</h3>
+            <div class="flex items-center gap-2">
+                <h3>SSL Configuration</h3>
+                <x-tooltip>
+                    SSL encryption secures database connections. Enable this for production environments.
+                </x-tooltip>
+            </div>
resources/views/livewire/project/database/redis/general.blade.php (1)

52-85: LGTM! Comprehensive SSL configuration UI.

The UI implementation includes:

  • Clear section header
  • Certificate validity status with color-coded warnings
  • Easy-to-use SSL toggle
  • Certificate regeneration modal with clear warnings

Consider adding a tooltip or helper text to explain the implications of enabling SSL.

Add helper text to the SSL checkbox:

-                <x-forms.checkbox id="database.enable_ssl" label="Enable SSL" wire:model.live="database.enable_ssl" instantSave="instantSaveSSL" />
+                <x-forms.checkbox id="database.enable_ssl" label="Enable SSL" wire:model.live="database.enable_ssl" instantSave="instantSaveSSL" 
+                    helper="Enabling SSL will secure database connections using SSL/TLS encryption. A restart is required after enabling or disabling SSL." />
resources/views/livewire/project/database/mariadb/general.blade.php (1)

69-101: Consider adding SSL mode selection for MariaDB.

While the SSL configuration UI is well-implemented, consider adding SSL mode selection similar to MongoDB, as MariaDB also supports different SSL modes (VERIFY_CA, VERIFY_IDENTITY).

app/Livewire/Project/Database/Dragonfly/General.php (1)

55-55: Add type hint for $certificateValidUntil.

The property should have a type hint for better type safety.

-public $certificateValidUntil = null;
+public ?Carbon $certificateValidUntil = null;

Don't forget to add the import:

use Carbon\Carbon;
app/Models/StandaloneKeydb.php (2)

245-252: Apply consistent SSL configuration handling.

For consistency with the internal URL changes, consider applying the same CA path configuration suggestion here.

-                        $url .= '?cacert=/etc/ssl/certs/coolify-ca.crt';
+                        $url .= '?cacert=' . config('ssl.ca_certificate_path', '/etc/ssl/certs/coolify-ca.crt');

226-236: Verify SSL port and consider making CA path configurable.

The implementation looks good but has two considerations:

  1. Verify that port 6380 is the correct SSL port for KeyDB.
  2. Consider making the CA certificate path configurable instead of hardcoding it.

Consider extracting the CA certificate path to a configuration value:

-                    $url .= '?cacert=/etc/ssl/certs/coolify-ca.crt';
+                    $url .= '?cacert=' . config('ssl.ca_certificate_path', '/etc/ssl/certs/coolify-ca.crt');
❌ Verification inconclusive

🌐 Web query:

What is the default SSL port for KeyDB?

💡 Result:

KeyDB does not have a separate default SSL/TLS port distinct from its standard TCP port. By default, KeyDB uses port 6379 for both non-encrypted and TLS-encrypted connections when configured[1][5][7]. However, enabling TLS requires explicit configuration:

  1. Default behavior:

    • Without TLS configuration, KeyDB listens on port 6379 for regular unencrypted TCP connections[1][5][7].
    • To enable TLS while retaining unencrypted traffic, you must configure a separate TLS-specific port (e.g., tls-port 6380)[2][5].
  2. TLS-only configuration:
    To force KeyDB to use port 6379 exclusively for TLS, you would:

    keydb-server --port 0 --tls-port 6379 --tls-cert-file server.crt --tls-key-file server.key

    This disables regular TCP and enables TLS on the default port[2][5].

  3. Cluster considerations:
    In clustered deployments, the cluster bus port always uses an offset of +10000 from the data port (e.g., 16379 if using 6379)[3]. This applies to both encrypted and unencrypted setups.

For security, always combine TLS with authentication (requirepass) and firewall rules restricting access to trusted clients[4][6].

Citations:


Action: Verify SSL Port Configuration and Externalize CA Certificate Path

  • The current implementation switches to port 6380 when SSL is enabled, even though by default KeyDB listens on port 6379 for both encrypted and unencrypted connections. Verify whether using a separate TLS port (6380) is intentional for your deployment.
  • Consider externalizing the CA certificate path (currently hardcoded as /etc/ssl/certs/coolify-ca.crt) to a configuration value to enhance flexibility.
app/Livewire/Project/Database/Postgresql/General.php (1)

122-154: Consider adding certificate expiration validation.

The method correctly handles certificate regeneration. Consider adding a check to validate if the current certificate is near expiration before regeneration.

 public function regenerateSslCertificate()
 {
     try {
         $existingCert = SslCertificate::where('resource_type', $this->database->getMorphClass())
             ->where('resource_id', $this->database->id)
             ->where('server_id', $this->server->id)
             ->first();

         if (! $existingCert) {
             $this->dispatch('error', 'No existing SSL certificate found for this database.');

             return;
         }

+        // Only regenerate if certificate is near expiration
+        if ($existingCert->valid_until && now()->diffInDays($existingCert->valid_until) > 30) {
+            $this->dispatch('error', 'Certificate is still valid for more than 30 days.');
+            return;
+        }

         $caCert = SslCertificate::where('server_id', $existingCert->server_id)->where('is_ca_certificate', true)->first();
app/Models/StandaloneRedis.php (1)

225-233: Consider extracting SSL configuration to a trait.

The method correctly handles SSL configuration for Redis URLs. Since this SSL configuration logic is similar across database models, consider extracting it to a shared trait.

trait HasSslConfiguration {
    protected function getSslScheme(string $defaultScheme): string {
        return $this->enable_ssl ? "{$defaultScheme}s" : $defaultScheme;
    }

    protected function getSslPort(int $defaultPort): int {
        return $this->enable_ssl ? $defaultPort + 1 : $defaultPort;
    }

    protected function appendSslParameters(string $url): string {
        if ($this->enable_ssl && $this->ssl_mode === 'verify-ca') {
            return $url . '?cacert=/etc/ssl/certs/coolify-ca.crt';
        }
        return $url;
    }
}
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b93d11f and f288852.

📒 Files selected for processing (54)
  • app/Actions/Database/StartDragonfly.php (4 hunks)
  • app/Actions/Database/StartKeydb.php (5 hunks)
  • app/Actions/Database/StartMariadb.php (5 hunks)
  • app/Actions/Database/StartMongodb.php (5 hunks)
  • app/Actions/Database/StartMysql.php (5 hunks)
  • app/Actions/Database/StartPostgresql.php (5 hunks)
  • app/Actions/Database/StartRedis.php (6 hunks)
  • app/Actions/Server/InstallDocker.php (2 hunks)
  • app/Console/Kernel.php (2 hunks)
  • app/Helpers/SslHelper.php (1 hunks)
  • app/Jobs/DeleteResourceJob.php (2 hunks)
  • app/Jobs/RegenerateSslCertJob.php (1 hunks)
  • app/Livewire/Project/Database/Dragonfly/General.php (6 hunks)
  • app/Livewire/Project/Database/Keydb/General.php (6 hunks)
  • app/Livewire/Project/Database/Mariadb/General.php (5 hunks)
  • app/Livewire/Project/Database/Mongodb/General.php (5 hunks)
  • app/Livewire/Project/Database/Mysql/General.php (5 hunks)
  • app/Livewire/Project/Database/Postgresql/General.php (5 hunks)
  • app/Livewire/Project/Database/Redis/General.php (5 hunks)
  • app/Livewire/Server/Advanced.php (2 hunks)
  • app/Models/LocalFileVolume.php (1 hunks)
  • app/Models/LocalPersistentVolume.php (0 hunks)
  • app/Models/SslCertificate.php (1 hunks)
  • app/Models/StandaloneClickhouse.php (1 hunks)
  • app/Models/StandaloneDragonfly.php (3 hunks)
  • app/Models/StandaloneKeydb.php (3 hunks)
  • app/Models/StandaloneMariadb.php (1 hunks)
  • app/Models/StandaloneMongodb.php (3 hunks)
  • app/Models/StandaloneMysql.php (3 hunks)
  • app/Models/StandalonePostgresql.php (4 hunks)
  • app/Models/StandaloneRedis.php (3 hunks)
  • app/Notifications/SslExpirationNotification.php (1 hunks)
  • app/Traits/HasNotificationSettings.php (2 hunks)
  • app/View/Components/Forms/Select.php (0 hunks)
  • bootstrap/helpers/databases.php (8 hunks)
  • database/migrations/2025_01_27_102616_add_ssl_fields_to_database_tables.php (1 hunks)
  • database/migrations/2025_01_27_153741_create_ssl_certificates_table.php (1 hunks)
  • database/migrations/2025_01_30_125223_encrypt_local_file_volumes_fields.php (1 hunks)
  • database/seeders/CaSslCertSeeder.php (1 hunks)
  • database/seeders/DatabaseSeeder.php (1 hunks)
  • database/seeders/ProductionSeeder.php (1 hunks)
  • resources/views/components/forms/copy-button.blade.php (1 hunks)
  • resources/views/components/modal-confirmation.blade.php (1 hunks)
  • resources/views/emails/ssl-certificate-renewed.blade.php (1 hunks)
  • resources/views/livewire/profile/index.blade.php (1 hunks)
  • resources/views/livewire/project/database/dragonfly/general.blade.php (1 hunks)
  • resources/views/livewire/project/database/keydb/general.blade.php (1 hunks)
  • resources/views/livewire/project/database/mariadb/general.blade.php (1 hunks)
  • resources/views/livewire/project/database/mongodb/general.blade.php (1 hunks)
  • resources/views/livewire/project/database/mysql/general.blade.php (1 hunks)
  • resources/views/livewire/project/database/postgresql/general.blade.php (1 hunks)
  • resources/views/livewire/project/database/redis/general.blade.php (1 hunks)
  • resources/views/livewire/server/advanced.blade.php (1 hunks)
  • scripts/install.sh (1 hunks)
💤 Files with no reviewable changes (2)
  • app/Models/LocalPersistentVolume.php
  • app/View/Components/Forms/Select.php
✅ Files skipped from review due to trivial changes (1)
  • scripts/install.sh
🔇 Additional comments (111)
bootstrap/helpers/databases.php (2)

19-180: Standardized database naming pattern looks good!

The inline UUID and name generation provides a consistent naming pattern across all database types. The format <type>-database-<uuid> ensures uniqueness and readability.


182-406: Robust backup management implementation!

The backup retention logic effectively handles:

  • Multiple retention criteria (amount, days, storage size)
  • Both local and S3 storage
  • Secure file path handling with proper escaping
  • Clean folder management
app/Models/StandaloneClickhouse.php (2)

166-169: LGTM! SSL certificate relationship properly implemented.

The sslCertificates() method correctly establishes a polymorphic relationship with the SslCertificate model, aligning with Laravel's best practices and the PR's objective of adding SSL support across database systems.


166-169: Verify consistent SSL implementation across database models.

Let's ensure the SSL certificate relationship is consistently implemented across all database models.

✅ Verification successful

🏁 Script executed:

#!/bin/bash
# Description: Check for consistent SSL certificate relationship implementation across database models
# Test: Search for sslCertificates() method in all database models
# Expect: Similar implementation in StandaloneDragonfly, StandaloneKeydb, StandaloneMariadb, StandaloneMongodb, StandaloneMysql, StandalonePostgresql, and StandaloneRedis

# Search for sslCertificates() method in all database models
ast-grep --pattern $'class Standalone$_ {
  $$$
  public function sslCertificates() {
    return $this->morphMany(SslCertificate::class, "resource");
  }
  $$$
}'

# Search for any SSL-related configuration or setup code
rg -A 5 "ssl|SSL" app/Models/Standalone*

Length of output: 11853


SSL Relationship Consistency Verified Across Models

The investigation confirms that the sslCertificates() method is consistently implemented in the SSL support-related models (StandaloneClickhouse, StandaloneDragonfly, StandaloneKeydb, StandaloneMariadb, StandaloneMongodb, StandaloneMysql, StandalonePostgresql, and StandaloneRedis) using:

public function sslCertificates() {
    return $this->morphMany(SslCertificate::class, 'resource');
}

This consistency ensures a uniform SSL certificate relationship across database models.

resources/views/livewire/profile/index.blade.php (1)

50-55: LGTM! Secure implementation of 2FA secret copying.

The implementation properly handles sensitive information by using decryption and maintaining visibility control.

resources/views/components/modal-confirmation.blade.php (1)

250-252: LGTM! Clean integration of copy functionality.

The copy-button component is well integrated into the confirmation flow while maintaining all security measures.

app/Livewire/Project/Database/Redis/General.php (2)

7-9: LGTM! SSL-related imports and property declaration.

The new imports and property declaration are well-structured and properly typed.

Also applies to: 35-35


49-49: LGTM! SSL validation configuration.

The validation rule and attribute for SSL configuration are properly defined.

Also applies to: 63-63

app/Livewire/Project/Database/Mysql/General.php (2)

7-9: LGTM! Well-structured SSL-related additions.

The new imports and property are correctly implemented to support SSL certificate management.

Also applies to: 26-27


42-43: LGTM! Comprehensive validation rules for SSL configuration.

The validation rules and attributes are well-defined. The SSL mode options align with MySQL's supported SSL modes.

Let's verify that these SSL modes are the complete set of valid options for MySQL:

Also applies to: 59-60

✅ Verification successful

🌐 Web query:

What are the valid SSL modes for MySQL database connections?

💡 Result:

MySQL supports five SSL modes for database connections, providing varying levels of security and encryption enforcement. These modes control whether encryption is used and how strictly server certificates are validated.

Valid SSL Modes

  1. DISABLED
    Establishes an unencrypted connection. Fails if the server requires SSL[1][2][10].

  2. PREFERRED (Default)
    Attempts encrypted connection first but falls back to unencrypted if:

    • Server doesn't support SSL
    • Client lacks SSL capabilities[1][2][10]
  3. REQUIRED
    Enforces encrypted connection. Fails if:

    • Server doesn't support SSL
    • Encryption can't be established[1][2][5][10]
  4. VERIFY_CA
    Requires encryption AND validates:

    • Server certificate is signed by trusted Certificate Authority (CA)
    • Certificate validity period[1][2][7][10]
  5. VERIFY_IDENTITY
    Includes all VERIFY_CA checks PLUS:

    • Server hostname matches certificate's Common Name (CN)
    • Most secure option for production environments[1][2][7][10]

Security Comparison Table

Mode Encryption CA Validation Hostname Verification
DISABLED
PREFERRED ✅ (if possible)
REQUIRED
VERIFY_CA
VERIFY_IDENTITY

Key Implementation Notes

  • Use REQUIRED or higher for compliance scenarios (HIPAA/GDPR)[4][9]
  • Azure MySQL enforces TLS 1.2+ by default starting Sept 2024[4]
  • Self-signed certificates require manual CA trust configuration[7]
  • Hostname verification (VERIFY_IDENTITY) requires proper DNS/SAN setup in certificates[7]

To check current connection security status:

\s  # In MySQL client shows "SSL: Cipher in use"

Citations:


SSL Mode Options Verification

Our investigation confirms that the validation rules are set up correctly. The supported SSL modes in the validation rule—PREFERRED, REQUIRED, VERIFY_CA, VERIFY_IDENTITY—are the appropriate choices when SSL is enabled. The missing DISABLED option is handled separately by the boolean setting (database.enable_ssl), which determines whether SSL is used at all.

  • When database.enable_ssl is false, the connection is non-SSL (i.e. "DISABLED").
  • When SSL is enabled, the allowed modes (PREFERRED, REQUIRED, VERIFY_CA, VERIFY_IDENTITY) match the valid MySQL options for encrypted connections.
app/Models/StandalonePostgresql.php (4)

262-266: Persistent storage relationship looks good

Your morphMany relationship to LocalPersistentVolume is correctly set up.


272-276: SSL certificates relationship is clearly defined

This morphMany relationship for multiple certificates appears correct and consistent with other parts of the code.


287-289: Scheduled backups relationship

Defining the morphMany relationship for scheduled backups is appropriate.


292-296: Environment variables ordering

Ordering environment variables by key enhances readability and consistency.

database/migrations/2025_01_30_125223_encrypt_local_file_volumes_fields.php (2)

17-19: Consider confirming text column limits for mount_path.

Changing mount_path to text without a defined limit is acceptable for most use cases. However, verify that your chosen column type satisfies any potential constraints (e.g., maximum allowed path length in an underlying filesystem or driver).


42-46: Potential mismatch in schema reversion.

When reverting fs_path to string, ensure that its length aligns with the original migration, preventing data truncation if the stored text previously exceeded the default string length. Confirm that this change won’t silently truncate data.

app/Jobs/DeleteResourceJob.php (2)

69-70: Double-check shared volume usage before deletion.

If these volumes are or could be shared by multiple resources, confirm that removing them here will not break other active resources. Implementing reference checks or warnings might be beneficial.


85-90: Thorough resource cleanup.

Deleting SSL certificates, backups, environment variables, and tags is comprehensive. However, ensure there are no other references to these items in the system. If any external or scheduled processes depend on them, consider adjusting the sequence or adding validation checks.

app/Livewire/Server/Advanced.php (5)

5-6: No immediate issues with new imports
The usage of SslHelper, RegenerateSslCertJob, and SslCertificate is straightforward.

Also applies to: 8-8


16-16: Well-defined properties for CA certificate management
No major concerns. These properties help manage CA certificate state within the UI.

Also applies to: 18-18, 20-20, 22-22


44-44: Loading CA certificate on mount
Automatically loading the CA certificate on mount ensures that the UI is up to date.


50-58: loadCaCertificate logic
Retrieves the CA certificate if it exists and updates the component state. Looks good.


60-64: Simple toggle method
No concerns with toggling the display of the certificate.

app/Helpers/SslHelper.php (4)

1-16: Class introduction
Defines SslHelper with default constants for organization name, country, and state. No immediate issues.


64-91: Certificate config building
The approach for building the config and subject alt names is correct. No immediate issues.


161-224: File storage logic
Implementation handles both .pem and separate .crt/.key usage. The approach looks good. Just confirm that chunked file deletion in lines 189-191 doesn't remove needed files in a race condition scenario.


226-232: Return of newly created certificate
Returning the SslCertificate is consistent, and the finally block with fclose($tempConfig) is good. No further issues.

app/Actions/Database/StartDragonfly.php (11)

5-6: No concerns with new imports
Usage of SslHelper and SslCertificate is consistent with the rest of the codebase.


21-21: New property for SSL certificate
Facilitates storing the certificate instance for further operations. Seems fine.


32-34: Added debug messages
Providing user feedback on directory creation steps is helpful.


56-79: SSL setup
Creates the SSL directory, retrieves the CA certificate, and generates a new one if missing. The workflow is straightforward.


81-84: No major concern
Reassigning container name and config directory. Double-check if duplication is intended.


88-88: Invoking new start command
This modular approach for building the start command is a good design.


138-139: Initializing volumes array
Using ??= for null coalescing assignment is fine and keeps the code concise.


141-144: Merging persistent directory volumes
Logic is appropriate, ensuring volumes are combined without overwriting.


160-172: CA certificate volume binding
The path /data/coolify/ssl/coolify-ca.crt is used as the source. Ensure it remains consistent across all environments.


185-187: Ownership for SSL files
Make sure user 999:999 is correct for the Dragonfly container.


194-209: TLS options for Dragonfly
Including --tls_ca_cert_file is a solid addition for secure connections. Implementation is correct.

app/Actions/Database/StartMariadb.php (9)

5-6: Imports for SSL Certificate Management
These imports cleanly introduce SSL-related helpers and models.


32-34: Directory creation logs
Adding console messages helps trace setup steps. No issues here.


37-79: SSL enable/disable logic
The core SSL logic is well-structured. A few considerations:

  • When disabling SSL, all certificate data is removed. Confirm that this is expected behavior, especially if users might re-enable SSL later and want the old certificate retained.
  • If there's a chance for concurrent or repeated calls, ensure certificate generation doesn’t cause race conditions.

Overall, the approach looks solid for straightforward enable/disable flows.


133-135: Volume creation condition
The conditional creation of volumes is straightforward.


137-143: Persistent storage merge
Merging persistent storage volumes into Docker Compose is done correctly.


146-148: File storage mapping
Mapping file storage volumes is nicely handled. Make sure the provided paths align with any updates from the certificate logic.


155-167: Appending CA certificate volume for SSL
Binding the CA certificate is good for secure trust. Verify that the container has the necessary read permissions.


169-180: Custom MariaDB config volume
Binding a custom configuration file is a neat extension point. Ensure user-supplied configs do not conflict with SSL directives (e.g., disabling SSL inadvertently).


186-194: mysqld SSL command overrides
Enabling --require-secure-transport=1 is an excellent security practice. This ensures all connections use SSL.

app/Actions/Database/StartMysql.php (9)

5-6: Imports for SSL certificate management
The added imports for SslHelper and SslCertificate are consistent with the broader SSL logic and keep the class self-contained.


21-21: New ssl_certificate property
Having a nullable certificate property is aligned with the dynamic enable/disable use case.


32-34: Directory creation logging
Providing feedback about directory creation is helpful for debugging deployment steps.


37-57: Toggle SSL removal vs. setup
This block mirrors the approach in StartMariadb, removing or creating certificates as needed. Double-check concurrency or repeated invocation.


133-133: Ensuring volumes key exists
Using the null coalescing assignment to ensure 'volumes' is defined is a clean approach.


136-139: Merging persistent volumes
Merging volumes to unify persistent storage is a standard pattern; looks good.


142-149: Mapping file storage volumes
Ensuring that persistent file volumes are appended properly is correct for MySQL data integrity.


155-167: Adding CA certificate volume
Attaching the CA certificate in a read-only manner ensures no accidental modifications.


187-195: mysqld SSL command
Using SSL flags with mysqld is consistent and sets a strict requirement for secure transport. Well done.

app/Actions/Database/StartKeydb.php (12)

5-6: Imports for SSL handling
Importing SslHelper and SslCertificate keeps SSL logic consistent with other database classes.


22-22: New nullable ssl_certificate property
Following the same pattern as the other classes. Straightforward approach to toggling SSL.


33-35: Directory creation logs
Clear communication of each setup step is helpful.


38-56: SSL removal logic
Removing the SSL directory and certificate references is consistent with the outlined approach.


57-80: SSL creation logic
Generating new certificates if none are present ensures no missing assets. Watch for concurrency or partial state if the environment is restarted unexpectedly.


91-91: Storing startup command
Capturing the start command in a variable is a neat organizational update.


141-141: Set volumes key if not present
Ensuring the 'volumes' array is always defined keeps merges safe.


144-147: Merging persistent volumes
Consistent with the pattern in other classes. Looks good.


151-156: Appending file volumes
Setting up KeyDB file volume attachments is straightforward here.


163-174: KeyDB config binding
Binding custom config is good. Confirm that user-supplied config doesn’t conflict with SSL if the user sets insecure options.


177-188: Binding CA certificate
Attaching the CA to /etc/keydb/certs matches the secure model. Setting it as read-only ensures no inadvertent edits.


270-300: New buildStartCommand method
This dedicated method helps keep the main flow cleaner and more readable. SSL arguments are appended appropriately if enabled. Nice improvement to code structure.

app/Actions/Database/StartRedis.php (4)

5-6: Good usage of SSL-related classes.
No concerns with these additional import statements.


38-56: Verify impact of removing SSL data.
Removing the SSL directory, certificates, and associated file storage is potentially destructive if any shared resources depend on them. Confirm no concurrency issues.


140-146: Confirm volume path consistency.
Merging persistent storage volumes and adding the CA certificate file is correct in principle. Ensure that names do not collide and /data/coolify/ssl/coolify-ca.crt is consistently available on the host.

Also applies to: 149-155, 162-174


285-298: Evaluate --tls-auth-clients optional for security.
Making client authentication optional can reduce security guarantees. Consider requiring client authentication in secured environments.

app/Actions/Database/StartPostgresql.php (6)

5-6: Proper addition of SSL imports.
No issues spotted here.


42-62: Removal of SSL data must be double-checked.
Ensure no other services or concurrent processes rely on these SSL directories or certificates.


143-149: Persistent volumes merge.
Using array_merge maintains existing volumes. Double-check that volume names do not collide with existing references.


153-158: File storage volumes.
Mapping file system paths with "$item->fs_path:$item->mount_path" is standard, but be mindful of collisions or overwrites if multiple volumes share similar paths.


167-175: Init scripts binding.
Ensure the init script contents are safe to run in production to avoid inadvertently introducing unsafe commands.


196-206: SSL command-line flags.
Before enabling SSL, ensure the mounted certificates (server.crt, server.key) exist and are valid. Consider verifying volumes pre-start.

app/Actions/Database/StartMongodb.php (5)

5-6: SSL imports introduced.
Consistent with the pattern in other database start classes.


42-61: SSL artifact removal strategy.
Deleting SSL assets clears them thoroughly but can be destructive if resources are shared. Confirm no concurrency issues.


154-160: File volumes merging approach.
Combining existing volumes is standard practice. Ensure mount paths do not override each other.


180-188: Automating init scripts.
Mounting docker-entrypoint-initdb.d directories is a clean way to seed initial data. Confirm script safety before production use.


190-201: Mounting CA certificate.
Ensure /data/coolify/ssl/coolify-ca.crt is reliably present on all servers. Otherwise, the container may fail to start.

database/seeders/DatabaseSeeder.php (1)

31-31: Verify CaSslCertSeeder dependencies.

The CaSslCertSeeder is placed at the end of the seeder array. Please verify that it doesn't have dependencies on other seeders that would require it to run earlier in the sequence.

✅ Verification successful

🏁 Script executed:

#!/bin/bash
# Description: Check for potential dependencies in CaSslCertSeeder
# Look for references to other seeder data in CaSslCertSeeder

rg -A 5 "class CaSslCertSeeder"

Length of output: 419


CaSslCertSeeder Dependency Verification Completed

The search revealed no explicit calls to other seeders (e.g., via Seeder::call) within the CaSslCertSeeder file. It only references the Server model to process existing server data. This indicates that there are no hidden dependencies on other seeder classes that require an earlier run.

Please ensure that any seeder responsible for inserting data into the Server table is executed before this seeder. Given that CaSslCertSeeder is now placed at the end of the seeder array, its dependencies appear to be correctly managed.

database/seeders/CaSslCertSeeder.php (1)

23-23: Review the CA certificate validity period.

A 15-year validity period for CA certificates is quite long. Consider reducing it to align with industry best practices (typically 5-10 years for root CAs).

❌ Incorrect review comment

🌐 Web query:

What is the recommended validity period for root CA certificates?

💡 Result:

The recommended validity period for root CA certificates depends on the PKI hierarchy design and security context, but generally falls within these ranges:

Enterprise PKI Design

  • Two-tier hierarchy: Root CA validity should be ≥10 years[1][3], typically double the intermediate CA's validity period[3].
    Example:

    • Intermediate CA: 5-10 years
    • Root CA: 10-20 years[1][3][4]
  • Key security: Offline root CAs with HSM-protected keys can safely use longer validity periods (15-25 years)[1][4].

Publicly Trusted CAs

  • Browser requirements: New roots must have 8-25 year validity[2].
  • Industry standards favor 10-20 year ranges for public roots[4].

Key Factors Influencing Validity Periods

Factor Recommendation
Cryptographic strength RSA 4096 allows ≤16 years[1]
Intermediate CA lifespan Root must outlive all intermediates[1][3]
Key protection Non-HSM roots should use shorter periods (≤1 month)[1]

For CRL validity in offline roots:

  • 6-12 months is ideal[1][5], balancing security and operational practicality.

Best practices emphasize planning root expiration around intermediate renewal cycles while accounting for cryptographic obsolescence risks[1][2][4]. Public CAs should adhere strictly to browser program limits (8-25 years)[2], while enterprises can extend beyond this if security controls justify it[1][4].

Citations:


CA Certificate Validity Period is Acceptable

Based on recent industry recommendations, a 15‑year validity period for a root CA—especially in an offline, HSM‑protected setting—is well within acceptable practice. The guidance for enterprise PKI designs generally suggests root CAs may have validity periods ranging from 10 to 20 years (or even 15–25 years when key security is ensured), which means the current setting is appropriate.

  • The current implementation (validityDays: 15 * 365) falls within these recommended ranges.
  • The review comment’s suggestion to reduce the period (to 5‑10 years) aligns more with recommendations for intermediates or online CAs rather than for a securely managed offline root CA.

Thus, no change is required unless there is additional context in your design that necessitates a shorter period.

Likely an incorrect or invalid review comment.

app/Traits/HasNotificationSettings.php (1)

7-7: LGTM! Well-structured notification settings.

The changes properly integrate SSL certificate renewal notifications while maintaining clean code organization and type safety.

Also applies to: 19-19

app/Models/LocalFileVolume.php (1)

10-14: LGTM! Good security practice.

Encrypting sensitive file paths and content using Laravel's built-in encryption is a good security practice.

app/Livewire/Project/Database/Mariadb/General.php (4)

26-26: LGTM! Property declaration for certificate validity tracking.

The property is correctly typed as nullable to handle cases where no certificate exists.


42-42: LGTM! SSL configuration validation rules and attributes.

The validation rules and attributes are properly defined for the SSL configuration.

Also applies to: 58-58


67-73: LGTM! Certificate validity check in mount method.

The mount method correctly retrieves and sets the certificate validity date.


155-187: LGTM! Comprehensive SSL certificate regeneration logic.

The regenerateSslCertificate method includes:

  • Proper error handling
  • CA certificate validation
  • Clear success/error messages
app/Livewire/Project/Database/Mongodb/General.php (1)

41-42: LGTM! MongoDB-specific SSL configuration rules.

The validation rules correctly specify the allowed SSL modes for MongoDB:

  • allow
  • prefer
  • require
  • verify-full
database/seeders/ProductionSeeder.php (1)

196-196: LGTM! SSL certificate seeder integration.

The CaSslCertSeeder is correctly placed after server and user setup, ensuring all required infrastructure exists before generating SSL certificates.

resources/views/livewire/server/advanced.blade.php (1)

41-129: LGTM! Well-structured SSL certificate management UI.

The implementation provides a comprehensive and user-friendly interface for managing CA SSL certificates with:

  • Clear modal confirmations for saving and regenerating certificates
  • Proper warnings about implications of certificate changes
  • Visual indicators for certificate validity status
  • Easy access to bind mount configuration
resources/views/livewire/project/database/mongodb/general.blade.php (1)

59-102: LGTM! Comprehensive SSL configuration UI for MongoDB.

The implementation provides a well-structured interface with:

  • Clear SSL configuration section
  • Certificate regeneration modal with proper warnings
  • Visual indicators for certificate validity status
  • MongoDB-specific SSL modes (allow, prefer, require, verify-full)
app/Livewire/Project/Database/Keydb/General.php (3)

58-61: LGTM!

The new properties for SSL management are correctly defined with appropriate validation.


78-84: LGTM!

The SSL certificate retrieval logic is correctly implemented in the mount method.


104-104: LGTM!

The enable_ssl property is correctly synchronized between the component and model.

Also applies to: 120-120

resources/views/livewire/project/database/mysql/general.blade.php (1)

69-110: LGTM!

The SSL configuration section is well-implemented with:

  • Clear enable/disable toggle
  • MySQL-specific SSL modes
  • Informative certificate validity display with expiration warnings
  • Certificate regeneration functionality
resources/views/livewire/project/database/postgresql/general.blade.php (1)

76-118: LGTM!

The SSL configuration section is well-implemented with:

  • Clear enable/disable toggle
  • PostgreSQL-specific SSL modes
  • Informative certificate validity display with expiration warnings
  • Certificate regeneration functionality
app/Models/StandaloneMariadb.php (1)

274-277: LGTM!

The sslCertificates relationship is correctly defined using Laravel's polymorphic relationship conventions.

app/Models/StandaloneKeydb.php (1)

171-174: LGTM! Well-structured relationship method.

The implementation correctly establishes a polymorphic relationship for managing SSL certificates, following Laravel's conventions.

app/Console/Kernel.php (1)

13-13: LGTM! Appropriate scheduling for SSL certificate regeneration.

The implementation correctly:

  1. Imports the new job class
  2. Schedules it to run twice daily in non-development environments

Also applies to: 88-88

app/Livewire/Project/Database/Postgresql/General.php (3)

28-28: LGTM!

The property is correctly defined to track SSL certificate expiration date.


55-56: LGTM!

The validation rules correctly enforce:

  • Boolean type for enable_ssl
  • String type with specific SSL modes for ssl_mode

110-120: LGTM!

The method correctly:

  • Saves SSL configuration properties
  • Implements error handling
  • Provides user feedback through dispatched messages
app/Models/StandaloneMongodb.php (3)

180-183: LGTM!

The method correctly establishes a polymorphic relationship for managing SSL certificates.


246-257: LGTM!

The method correctly:

  • Constructs MongoDB connection URL
  • Adds TLS parameters when SSL is enabled
  • Includes CA file path for verify-full mode

266-274: LGTM!

The method correctly mirrors the SSL configuration from internalDbUrl for external connections.

app/Models/StandaloneRedis.php (2)

167-170: LGTM!

The method correctly establishes a polymorphic relationship for managing SSL certificates, consistent with other database models.


245-252: LGTM!

The method correctly mirrors the SSL configuration from internalDbUrl for external connections.


<div class="relative" x-data="{ copied: false }">
<input type="text" value="{{ $text }}" readonly class="input">
<button @click="copied = true; navigator.clipboard.writeText('{{ $text }}'); setTimeout(() => copied = false, 1000)" class="absolute right-2 top-1/2 -translate-y-1/2 p-1.5 text-gray-400 hover:text-gray-300 transition-colors" title="Copy to clipboard">
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add error handling for clipboard operations.

The clipboard API can fail in certain environments. Consider adding error handling.

-    <button @click="copied = true; navigator.clipboard.writeText('{{ $text }}'); setTimeout(() => copied = false, 1000)" class="absolute right-2 top-1/2 -translate-y-1/2 p-1.5 text-gray-400 hover:text-gray-300 transition-colors" title="Copy to clipboard">
+    <button @click="navigator.clipboard.writeText('{{ $text }}').then(() => { copied = true; setTimeout(() => copied = false, 1000) }).catch(() => alert('Failed to copy to clipboard'))" class="absolute right-2 top-1/2 -translate-y-1/2 p-1.5 text-gray-400 hover:text-gray-300 transition-colors" title="Copy to clipboard">
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<button @click="copied = true; navigator.clipboard.writeText('{{ $text }}'); setTimeout(() => copied = false, 1000)" class="absolute right-2 top-1/2 -translate-y-1/2 p-1.5 text-gray-400 hover:text-gray-300 transition-colors" title="Copy to clipboard">
<button @click="navigator.clipboard.writeText('{{ $text }}').then(() => { copied = true; setTimeout(() => copied = false, 1000) }).catch(() => alert('Failed to copy to clipboard'))" class="absolute right-2 top-1/2 -translate-y-1/2 p-1.5 text-gray-400 hover:text-gray-300 transition-colors" title="Copy to clipboard">

Comment on lines +163 to +195
public function regenerateSslCertificate()
{
try {
$existingCert = SslCertificate::where('resource_type', $this->database->getMorphClass())
->where('resource_id', $this->database->id)
->where('server_id', $this->server->id)
->first();

if (! $existingCert) {
$this->dispatch('error', 'No existing SSL certificate found for this database.');

return;
}

$caCert = SslCertificate::where('server_id', $existingCert->server_id)->where('is_ca_certificate', true)->first();

SslHelper::generateSslCertificate(
commonName: $existingCert->commonName,
subjectAlternativeNames: $existingCert->subjectAlternativeNames ?? [],
resourceType: $existingCert->resource_type,
resourceId: $existingCert->resource_id,
serverId: $existingCert->server_id,
caCert: $caCert->ssl_certificate,
caKey: $caCert->ssl_private_key,
configurationDir: $existingCert->configuration_dir,
mountPath: $existingCert->mount_path,
);

$this->dispatch('success', 'SSL certificates regenerated. Restart database to apply changes.');
} catch (Exception $e) {
handleError($e, $this);
}
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Optimize certificate queries and add CA certificate validation.

The method could be optimized using relationship methods and should validate CA certificate existence.

     public function regenerateSslCertificate()
     {
         try {
-            $existingCert = SslCertificate::where('resource_type', $this->database->getMorphClass())
-                ->where('resource_id', $this->database->id)
-                ->where('server_id', $this->server->id)
-                ->first();
+            $existingCert = $this->database->sslCertificate()
+                ->where('server_id', $this->server->id)
+                ->first();

             if (! $existingCert) {
                 $this->dispatch('error', 'No existing SSL certificate found for this database.');
                 return;
             }

             $caCert = SslCertificate::where('server_id', $existingCert->server_id)
                 ->where('is_ca_certificate', true)
                 ->first();
+
+            if (! $caCert) {
+                $this->dispatch('error', 'No CA certificate found for this server.');
+                return;
+            }

             SslHelper::generateSslCertificate(
                 commonName: $existingCert->commonName,
                 subjectAlternativeNames: $existingCert->subjectAlternativeNames ?? [],
                 resourceType: $existingCert->resource_type,
                 resourceId: $existingCert->resource_id,
                 serverId: $existingCert->server_id,
                 caCert: $caCert->ssl_certificate,
                 caKey: $caCert->ssl_private_key,
                 configurationDir: $existingCert->configuration_dir,
                 mountPath: $existingCert->mount_path,
             );

             $this->dispatch('success', 'SSL certificates regenerated. Restart database to apply changes.');
         } catch (Exception $e) {
             handleError($e, $this);
         }
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
public function regenerateSslCertificate()
{
try {
$existingCert = SslCertificate::where('resource_type', $this->database->getMorphClass())
->where('resource_id', $this->database->id)
->where('server_id', $this->server->id)
->first();
if (! $existingCert) {
$this->dispatch('error', 'No existing SSL certificate found for this database.');
return;
}
$caCert = SslCertificate::where('server_id', $existingCert->server_id)->where('is_ca_certificate', true)->first();
SslHelper::generateSslCertificate(
commonName: $existingCert->commonName,
subjectAlternativeNames: $existingCert->subjectAlternativeNames ?? [],
resourceType: $existingCert->resource_type,
resourceId: $existingCert->resource_id,
serverId: $existingCert->server_id,
caCert: $caCert->ssl_certificate,
caKey: $caCert->ssl_private_key,
configurationDir: $existingCert->configuration_dir,
mountPath: $existingCert->mount_path,
);
$this->dispatch('success', 'SSL certificates regenerated. Restart database to apply changes.');
} catch (Exception $e) {
handleError($e, $this);
}
}
public function regenerateSslCertificate()
{
try {
$existingCert = $this->database->sslCertificate()
->where('server_id', $this->server->id)
->first();
if (! $existingCert) {
$this->dispatch('error', 'No existing SSL certificate found for this database.');
return;
}
$caCert = SslCertificate::where('server_id', $existingCert->server_id)
->where('is_ca_certificate', true)
->first();
if (! $caCert) {
$this->dispatch('error', 'No CA certificate found for this server.');
return;
}
SslHelper::generateSslCertificate(
commonName: $existingCert->commonName,
subjectAlternativeNames: $existingCert->subjectAlternativeNames ?? [],
resourceType: $existingCert->resource_type,
resourceId: $existingCert->resource_id,
serverId: $existingCert->server_id,
caCert: $caCert->ssl_certificate,
caKey: $caCert->ssl_private_key,
configurationDir: $existingCert->configuration_dir,
mountPath: $existingCert->mount_path,
);
$this->dispatch('success', 'SSL certificates regenerated. Restart database to apply changes.');
} catch (Exception $e) {
handleError($e, $this);
}
}

Comment on lines +152 to +161
public function instantSaveSSL()
{
try {
$this->database->enable_ssl = $this->database->enable_ssl;
$this->database->save();
$this->dispatch('success', 'SSL configuration updated.');
} catch (Exception $e) {
return handleError($e, $this);
}
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Fix redundant assignment and add validation in instantSaveSSL.

The current implementation has a redundant assignment and lacks validation.

     public function instantSaveSSL()
     {
         try {
-            $this->database->enable_ssl = $this->database->enable_ssl;
+            $this->validate(['database.enable_ssl' => 'boolean']);
             $this->database->save();
             $this->dispatch('success', 'SSL configuration updated.');
         } catch (Exception $e) {
             return handleError($e, $this);
         }
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
public function instantSaveSSL()
{
try {
$this->database->enable_ssl = $this->database->enable_ssl;
$this->database->save();
$this->dispatch('success', 'SSL configuration updated.');
} catch (Exception $e) {
return handleError($e, $this);
}
}
public function instantSaveSSL()
{
try {
$this->validate(['database.enable_ssl' => 'boolean']);
$this->database->save();
$this->dispatch('success', 'SSL configuration updated.');
} catch (Exception $e) {
return handleError($e, $this);
}
}

Comment on lines +146 to +156
public function instantSaveSSL()
{
try {
$this->database->enable_ssl = $this->database->enable_ssl;
$this->database->ssl_mode = $this->database->ssl_mode;
$this->database->save();
$this->dispatch('success', 'SSL configuration updated.');
} catch (Exception $e) {
return handleError($e, $this);
}
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Simplify and validate SSL configuration updates.

The current implementation of instantSaveSSL appears redundant and lacks validation.

     public function instantSaveSSL()
     {
         try {
-            $this->database->enable_ssl = $this->database->enable_ssl;
-            $this->database->ssl_mode = $this->database->ssl_mode;
+            $this->validate([
+                'database.enable_ssl' => 'boolean',
+                'database.ssl_mode' => 'nullable|string|in:PREFERRED,REQUIRED,VERIFY_CA,VERIFY_IDENTITY',
+            ]);
             $this->database->save();
             $this->dispatch('success', 'SSL configuration updated.');
         } catch (Exception $e) {
             return handleError($e, $this);
         }
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
public function instantSaveSSL()
{
try {
$this->database->enable_ssl = $this->database->enable_ssl;
$this->database->ssl_mode = $this->database->ssl_mode;
$this->database->save();
$this->dispatch('success', 'SSL configuration updated.');
} catch (Exception $e) {
return handleError($e, $this);
}
}
public function instantSaveSSL()
{
try {
$this->validate([
'database.enable_ssl' => 'boolean',
'database.ssl_mode' => 'nullable|string|in:PREFERRED,REQUIRED,VERIFY_CA,VERIFY_IDENTITY',
]);
$this->database->save();
$this->dispatch('success', 'SSL configuration updated.');
} catch (Exception $e) {
return handleError($e, $this);
}
}

Comment on lines +158 to +190
public function regenerateSslCertificate()
{
try {
$existingCert = SslCertificate::where('resource_type', $this->database->getMorphClass())
->where('resource_id', $this->database->id)
->where('server_id', $this->server->id)
->first();

if (! $existingCert) {
$this->dispatch('error', 'No existing SSL certificate found for this database.');

return;
}

$caCert = SslCertificate::where('server_id', $existingCert->server_id)->where('is_ca_certificate', true)->first();

SslHelper::generateSslCertificate(
commonName: $existingCert->common_name,
subjectAlternativeNames: $existingCert->subject_alternative_names ?? [],
resourceType: $existingCert->resource_type,
resourceId: $existingCert->resource_id,
serverId: $existingCert->server_id,
caCert: $caCert->ssl_certificate,
caKey: $caCert->ssl_private_key,
configurationDir: $existingCert->configuration_dir,
mountPath: $existingCert->mount_path,
);

$this->dispatch('success', 'SSL certificates have been regenerated. Please restart the database for changes to take effect.');
} catch (Exception $e) {
return handleError($e, $this);
}
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add validation for CA certificate existence.

The certificate regeneration logic should validate the CA certificate's existence before proceeding.

     public function regenerateSslCertificate()
     {
         try {
             $existingCert = SslCertificate::where('resource_type', $this->database->getMorphClass())
                 ->where('resource_id', $this->database->id)
                 ->where('server_id', $this->server->id)
                 ->first();
 
             if (! $existingCert) {
                 $this->dispatch('error', 'No existing SSL certificate found for this database.');
                 return;
             }
 
             $caCert = SslCertificate::where('server_id', $existingCert->server_id)
                 ->where('is_ca_certificate', true)
                 ->first();
 
+            if (! $caCert) {
+                $this->dispatch('error', 'No CA certificate found on the server.');
+                return;
+            }
+
+            if (! $caCert->ssl_certificate || ! $caCert->ssl_private_key) {
+                $this->dispatch('error', 'Invalid CA certificate: missing certificate or private key.');
+                return;
+            }
+
             SslHelper::generateSslCertificate(
                 commonName: $existingCert->common_name,
                 subjectAlternativeNames: $existingCert->subject_alternative_names ?? [],
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
public function regenerateSslCertificate()
{
try {
$existingCert = SslCertificate::where('resource_type', $this->database->getMorphClass())
->where('resource_id', $this->database->id)
->where('server_id', $this->server->id)
->first();
if (! $existingCert) {
$this->dispatch('error', 'No existing SSL certificate found for this database.');
return;
}
$caCert = SslCertificate::where('server_id', $existingCert->server_id)->where('is_ca_certificate', true)->first();
SslHelper::generateSslCertificate(
commonName: $existingCert->common_name,
subjectAlternativeNames: $existingCert->subject_alternative_names ?? [],
resourceType: $existingCert->resource_type,
resourceId: $existingCert->resource_id,
serverId: $existingCert->server_id,
caCert: $caCert->ssl_certificate,
caKey: $caCert->ssl_private_key,
configurationDir: $existingCert->configuration_dir,
mountPath: $existingCert->mount_path,
);
$this->dispatch('success', 'SSL certificates have been regenerated. Please restart the database for changes to take effect.');
} catch (Exception $e) {
return handleError($e, $this);
}
}
public function regenerateSslCertificate()
{
try {
$existingCert = SslCertificate::where('resource_type', $this->database->getMorphClass())
->where('resource_id', $this->database->id)
->where('server_id', $this->server->id)
->first();
if (! $existingCert) {
$this->dispatch('error', 'No existing SSL certificate found for this database.');
return;
}
$caCert = SslCertificate::where('server_id', $existingCert->server_id)
->where('is_ca_certificate', true)
->first();
if (! $caCert) {
$this->dispatch('error', 'No CA certificate found on the server.');
return;
}
if (! $caCert->ssl_certificate || ! $caCert->ssl_private_key) {
$this->dispatch('error', 'Invalid CA certificate: missing certificate or private key.');
return;
}
SslHelper::generateSslCertificate(
commonName: $existingCert->common_name,
subjectAlternativeNames: $existingCert->subject_alternative_names ?? [],
resourceType: $existingCert->resource_type,
resourceId: $existingCert->resource_id,
serverId: $existingCert->server_id,
caCert: $caCert->ssl_certificate,
caKey: $caCert->ssl_private_key,
configurationDir: $existingCert->configuration_dir,
mountPath: $existingCert->mount_path,
);
$this->dispatch('success', 'SSL certificates have been regenerated. Please restart the database for changes to take effect.');
} catch (Exception $e) {
return handleError($e, $this);
}
}

Comment on lines +52 to +85
<div class="flex flex-col gap-2">
<div class="flex items-center justify-between py-2">
<div class="flex items-center justify-between w-full">
<h3>SSL Configuration</h3>
@if($database->enable_ssl && $certificateValidUntil)
<x-modal-confirmation
title="Regenerate SSL Certificates"
buttonTitle="Regenerate SSL Certificates"
:actions="[
'The SSL certificate of this database will be regenerated.',
'You must restart the database after regenerating the certificate to start using the new certificate.'
]"
submitAction="regenerateSslCertificate"
:confirmWithText="false"
:confirmWithPassword="false"
/>
@endif
</div>
</div>
@if($database->enable_ssl && $certificateValidUntil)
<span class="text-sm">Valid until:
@if(now()->gt($certificateValidUntil))
<span class="text-red-500">{{ $certificateValidUntil->format('d.m.Y H:i:s') }} - Expired</span>
@elseif(now()->addDays(30)->gt($certificateValidUntil))
<span class="text-red-500">{{ $certificateValidUntil->format('d.m.Y H:i:s') }} - Expiring soon</span>
@else
<span>{{ $certificateValidUntil->format('d.m.Y H:i:s') }}</span>
@endif
</span>
@endif
<div class="flex flex-col gap-2">
<x-forms.checkbox id="enable_ssl" label="Enable SSL" wire:model.live="enable_ssl" instantSave="instantSaveSSL" />
</div>
</div>
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Extract SSL configuration into a reusable component.

The SSL configuration section is identical to the one in dragonfly/general.blade.php. Consider extracting it into a reusable component to maintain DRY principles.

Create a new component:

<!-- resources/views/components/database/ssl-configuration.blade.php -->
@props(['database', 'certificateValidUntil'])

<div class="flex flex-col gap-2">
    <div class="flex items-center justify-between py-2">
        <!-- ... rest of the SSL configuration ... -->
    </div>
</div>

Then use it in both templates:

-        <div class="flex flex-col gap-2">
-            <!-- ... SSL configuration ... -->
-        </div>
+        <x-database.ssl-configuration
+            :database="$database"
+            :certificateValidUntil="$certificateValidUntil"
+        />

Comment on lines +144 to +153
public function instantSaveSSL()
{
try {
$this->database->enable_ssl = $this->database->enable_ssl;
$this->database->save();
$this->dispatch('success', 'SSL configuration updated.');
} catch (Exception $e) {
return handleError($e, $this);
}
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Fix redundant assignment in instantSaveSSL method.

The assignment $this->database->enable_ssl = $this->database->enable_ssl is redundant as it assigns the value to itself.

Apply this diff to fix the redundant assignment:

-            $this->database->enable_ssl = $this->database->enable_ssl;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
public function instantSaveSSL()
{
try {
$this->database->enable_ssl = $this->database->enable_ssl;
$this->database->save();
$this->dispatch('success', 'SSL configuration updated.');
} catch (Exception $e) {
return handleError($e, $this);
}
}
public function instantSaveSSL()
{
try {
$this->database->save();
$this->dispatch('success', 'SSL configuration updated.');
} catch (Exception $e) {
return handleError($e, $this);
}
}

Comment on lines +147 to +157
public function instantSaveSSL()
{
try {
$this->database->enable_ssl = $this->database->enable_ssl;
$this->database->ssl_mode = $this->database->ssl_mode;
$this->database->save();
$this->dispatch('success', 'SSL configuration updated.');
} catch (Exception $e) {
return handleError($e, $this);
}
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Fix redundant assignments in instantSaveSSL method.

Similar to the MariaDB class, there are redundant self-assignments.

Apply this diff to fix the redundant assignments:

-            $this->database->enable_ssl = $this->database->enable_ssl;
-            $this->database->ssl_mode = $this->database->ssl_mode;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
public function instantSaveSSL()
{
try {
$this->database->enable_ssl = $this->database->enable_ssl;
$this->database->ssl_mode = $this->database->ssl_mode;
$this->database->save();
$this->dispatch('success', 'SSL configuration updated.');
} catch (Exception $e) {
return handleError($e, $this);
}
}
public function instantSaveSSL()
{
try {
- $this->database->enable_ssl = $this->database->enable_ssl;
- $this->database->ssl_mode = $this->database->ssl_mode;
$this->database->save();
$this->dispatch('success', 'SSL configuration updated.');
} catch (Exception $e) {
return handleError($e, $this);
}
}

Comment on lines +205 to +239
public function regenerateSslCertificate()
{
try {
$existingCert = SslCertificate::where('resource_type', $this->database->getMorphClass())
->where('resource_id', $this->database->id)
->where('server_id', $this->server->id)
->first();

if (! $existingCert) {
$this->dispatch('error', 'No existing SSL certificate found for this database.');

return;
}

$caCert = SslCertificate::where('server_id', $existingCert->server_id)
->where('is_ca_certificate', true)
->first();

SslHelper::generateSslCertificate(
commonName: $existingCert->commonName,
subjectAlternativeNames: $existingCert->subjectAlternativeNames ?? [],
resourceType: $existingCert->resource_type,
resourceId: $existingCert->resource_id,
serverId: $existingCert->server_id,
caCert: $caCert->ssl_certificate,
caKey: $caCert->ssl_private_key,
configurationDir: $existingCert->configuration_dir,
mountPath: $existingCert->mount_path,
);

$this->dispatch('success', 'SSL certificates regenerated. Restart database to apply changes.');
} catch (Exception $e) {
handleError($e, $this);
}
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add error handling for missing CA certificate.

The regenerateSslCertificate method should handle the case where no CA certificate is found.

 $caCert = SslCertificate::where('server_id', $existingCert->server_id)
     ->where('is_ca_certificate', true)
     ->first();

+if (! $caCert) {
+    $this->dispatch('error', 'No CA certificate found for this server.');
+    return;
+}
+
 SslHelper::generateSslCertificate(
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
public function regenerateSslCertificate()
{
try {
$existingCert = SslCertificate::where('resource_type', $this->database->getMorphClass())
->where('resource_id', $this->database->id)
->where('server_id', $this->server->id)
->first();
if (! $existingCert) {
$this->dispatch('error', 'No existing SSL certificate found for this database.');
return;
}
$caCert = SslCertificate::where('server_id', $existingCert->server_id)
->where('is_ca_certificate', true)
->first();
SslHelper::generateSslCertificate(
commonName: $existingCert->commonName,
subjectAlternativeNames: $existingCert->subjectAlternativeNames ?? [],
resourceType: $existingCert->resource_type,
resourceId: $existingCert->resource_id,
serverId: $existingCert->server_id,
caCert: $caCert->ssl_certificate,
caKey: $caCert->ssl_private_key,
configurationDir: $existingCert->configuration_dir,
mountPath: $existingCert->mount_path,
);
$this->dispatch('success', 'SSL certificates regenerated. Restart database to apply changes.');
} catch (Exception $e) {
handleError($e, $this);
}
}
public function regenerateSslCertificate()
{
try {
$existingCert = SslCertificate::where('resource_type', $this->database->getMorphClass())
->where('resource_id', $this->database->id)
->where('server_id', $this->server->id)
->first();
if (! $existingCert) {
$this->dispatch('error', 'No existing SSL certificate found for this database.');
return;
}
$caCert = SslCertificate::where('server_id', $existingCert->server_id)
->where('is_ca_certificate', true)
->first();
if (! $caCert) {
$this->dispatch('error', 'No CA certificate found for this server.');
return;
}
SslHelper::generateSslCertificate(
commonName: $existingCert->commonName,
subjectAlternativeNames: $existingCert->subjectAlternativeNames ?? [],
resourceType: $existingCert->resource_type,
resourceId: $existingCert->resource_id,
serverId: $existingCert->server_id,
caCert: $caCert->ssl_certificate,
caKey: $caCert->ssl_private_key,
configurationDir: $existingCert->configuration_dir,
mountPath: $existingCert->mount_path,
);
$this->dispatch('success', 'SSL certificates regenerated. Restart database to apply changes.');
} catch (Exception $e) {
handleError($e, $this);
}
}

Comment on lines +200 to +244
public function instantSaveSSL()
{
try {
$this->syncData(true);
$this->dispatch('success', 'SSL configuration updated.');
} catch (Exception $e) {
return handleError($e, $this);
}
}

public function regenerateSslCertificate()
{
try {
$existingCert = SslCertificate::where('resource_type', $this->database->getMorphClass())
->where('resource_id', $this->database->id)
->where('server_id', $this->server->id)
->first();

if (! $existingCert) {
$this->dispatch('error', 'No existing SSL certificate found for this database.');

return;
}

$caCert = SslCertificate::where('server_id', $existingCert->server_id)
->where('is_ca_certificate', true)
->first();

SslHelper::generateSslCertificate(
commonName: $existingCert->commonName,
subjectAlternativeNames: $existingCert->subjectAlternativeNames ?? [],
resourceType: $existingCert->resource_type,
resourceId: $existingCert->resource_id,
serverId: $existingCert->server_id,
caCert: $caCert->ssl_certificate,
caKey: $caCert->ssl_private_key,
configurationDir: $existingCert->configuration_dir,
mountPath: $existingCert->mount_path,
);

$this->dispatch('success', 'SSL certificates regenerated. Restart database to apply changes.');
} catch (Exception $e) {
handleError($e, $this);
}
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add validation for CA certificate existence.

The regenerateSslCertificate method should validate that the CA certificate exists before attempting to use it. Currently, it assumes $caCert exists and directly accesses its properties, which could lead to a null pointer exception.

Apply this diff to add validation:

 $caCert = SslCertificate::where('server_id', $existingCert->server_id)
     ->where('is_ca_certificate', true)
     ->first();

+if (!$caCert) {
+    $this->dispatch('error', 'No CA certificate found. Please ensure a CA certificate is configured for this server.');
+    return;
+}

 SslHelper::generateSslCertificate(
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
public function instantSaveSSL()
{
try {
$this->syncData(true);
$this->dispatch('success', 'SSL configuration updated.');
} catch (Exception $e) {
return handleError($e, $this);
}
}
public function regenerateSslCertificate()
{
try {
$existingCert = SslCertificate::where('resource_type', $this->database->getMorphClass())
->where('resource_id', $this->database->id)
->where('server_id', $this->server->id)
->first();
if (! $existingCert) {
$this->dispatch('error', 'No existing SSL certificate found for this database.');
return;
}
$caCert = SslCertificate::where('server_id', $existingCert->server_id)
->where('is_ca_certificate', true)
->first();
SslHelper::generateSslCertificate(
commonName: $existingCert->commonName,
subjectAlternativeNames: $existingCert->subjectAlternativeNames ?? [],
resourceType: $existingCert->resource_type,
resourceId: $existingCert->resource_id,
serverId: $existingCert->server_id,
caCert: $caCert->ssl_certificate,
caKey: $caCert->ssl_private_key,
configurationDir: $existingCert->configuration_dir,
mountPath: $existingCert->mount_path,
);
$this->dispatch('success', 'SSL certificates regenerated. Restart database to apply changes.');
} catch (Exception $e) {
handleError($e, $this);
}
}
public function regenerateSslCertificate()
{
try {
$existingCert = SslCertificate::where('resource_type', $this->database->getMorphClass())
->where('resource_id', $this->database->id)
->where('server_id', $this->server->id)
->first();
if (! $existingCert) {
$this->dispatch('error', 'No existing SSL certificate found for this database.');
return;
}
$caCert = SslCertificate::where('server_id', $existingCert->server_id)
->where('is_ca_certificate', true)
->first();
if (!$caCert) {
$this->dispatch('error', 'No CA certificate found. Please ensure a CA certificate is configured for this server.');
return;
}
SslHelper::generateSslCertificate(
commonName: $existingCert->commonName,
subjectAlternativeNames: $existingCert->subjectAlternativeNames ?? [],
resourceType: $existingCert->resource_type,
resourceId: $existingCert->resource_id,
serverId: $existingCert->server_id,
caCert: $caCert->ssl_certificate,
caKey: $caCert->ssl_private_key,
configurationDir: $existingCert->configuration_dir,
mountPath: $existingCert->mount_path,
);
$this->dispatch('success', 'SSL certificates regenerated. Restart database to apply changes.');
} catch (Exception $e) {
handleError($e, $this);
}
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
🏔️ Peaklabs A label for PRs that are ready for review/merge and have been created by Peaklabs.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants