From 174e0131f77c8fc9dec3c2f009a872184fec4c9a Mon Sep 17 00:00:00 2001 From: peaklabs-dev <122374094+peaklabs-dev@users.noreply.github.com> Date: Sat, 3 Oct 2026 21:54:48 +0200 Subject: [PATCH 1/2] feat(proxy): add hashing options for HTTP basic authentication --- app/Enums/HttpBasicAuthHashAlgorithm.php | 9 + .../Api/ApplicationsController.php | 49 ++++- app/Livewire/Project/Application/General.php | 30 ++- app/Models/Application.php | 16 ++ app/Models/Server.php | 6 + .../ApplicationConfigurationSnapshot.php | 4 + bootstrap/helpers/api.php | 5 + bootstrap/helpers/docker.php | 29 ++- database/factories/ApplicationFactory.php | 3 + ...uth_hash_options_to_applications_table.php | 30 +++ openapi.json | 202 ++++++++++++++++++ openapi.yaml | 155 ++++++++++++++ .../project/application/general.blade.php | 39 ++++ .../Api/ApplicationSettingsApiTest.php | 29 +++ .../ApplicationHttpBasicAuthUiTest.php | 70 ++++++ .../Proxy/CaddyBasicAuthLabelsTest.php | 52 +++++ .../ApplicationConfigurationSnapshotTest.php | 16 ++ .../ApplicationHttpBasicAuthHashingTest.php | 45 ++++ 18 files changed, 778 insertions(+), 11 deletions(-) create mode 100644 app/Enums/HttpBasicAuthHashAlgorithm.php create mode 100644 database/migrations/2026_10_02_170101_add_http_basic_auth_hash_options_to_applications_table.php create mode 100644 tests/v4/Browser/ApplicationHttpBasicAuthHashingTest.php diff --git a/app/Enums/HttpBasicAuthHashAlgorithm.php b/app/Enums/HttpBasicAuthHashAlgorithm.php new file mode 100644 index 0000000000..c6340c0a1f --- /dev/null +++ b/app/Enums/HttpBasicAuthHashAlgorithm.php @@ -0,0 +1,9 @@ + ['type' => 'boolean', 'description' => 'HTTP Basic Authentication enabled.'], 'http_basic_auth_username' => ['type' => 'string', 'nullable' => true, 'description' => 'Username for HTTP Basic Authentication'], 'http_basic_auth_password' => ['type' => 'string', 'nullable' => true, 'description' => 'Password for HTTP Basic Authentication'], + 'http_basic_auth_hash_algorithm' => ['type' => 'string', 'enum' => ['bcrypt', 'argon2id'], 'default' => 'bcrypt', 'description' => 'Hash algorithm for the HTTP Basic Authentication password. Argon2id needs the Caddy proxy, version 2.11 or newer.'], + 'http_basic_auth_bcrypt_cost' => ['type' => 'integer', 'minimum' => 4, 'maximum' => 14, 'default' => 10, 'description' => 'Bcrypt cost for the HTTP Basic Authentication password.'], + 'http_basic_auth_argon2id_memory_cost' => ['type' => 'integer', 'minimum' => 8192, 'maximum' => 262144, 'default' => 65536, 'description' => 'Argon2id memory cost in KiB for the HTTP Basic Authentication password.'], + 'http_basic_auth_argon2id_time_cost' => ['type' => 'integer', 'minimum' => 1, 'maximum' => 12, 'default' => 4, 'description' => 'Argon2id time cost (iterations) for the HTTP Basic Authentication password.'], 'connect_to_docker_network' => ['type' => 'boolean', 'description' => 'The flag to connect the service to the predefined Docker network.'], 'force_domain_override' => ['type' => 'boolean', 'description' => 'Force domain usage even if conflicts are detected. Default is false.'], 'autogenerate_domain' => ['type' => 'boolean', 'default' => true, 'description' => 'If true and domains is empty, auto-generate a domain using the server\'s wildcard domain or sslip.io fallback. Default: true.'], @@ -620,6 +625,10 @@ class ApplicationsController extends Controller 'is_http_basic_auth_enabled' => ['type' => 'boolean', 'description' => 'HTTP Basic Authentication enabled.'], 'http_basic_auth_username' => ['type' => 'string', 'nullable' => true, 'description' => 'Username for HTTP Basic Authentication'], 'http_basic_auth_password' => ['type' => 'string', 'nullable' => true, 'description' => 'Password for HTTP Basic Authentication'], + 'http_basic_auth_hash_algorithm' => ['type' => 'string', 'enum' => ['bcrypt', 'argon2id'], 'default' => 'bcrypt', 'description' => 'Hash algorithm for the HTTP Basic Authentication password. Argon2id needs the Caddy proxy, version 2.11 or newer.'], + 'http_basic_auth_bcrypt_cost' => ['type' => 'integer', 'minimum' => 4, 'maximum' => 14, 'default' => 10, 'description' => 'Bcrypt cost for the HTTP Basic Authentication password.'], + 'http_basic_auth_argon2id_memory_cost' => ['type' => 'integer', 'minimum' => 8192, 'maximum' => 262144, 'default' => 65536, 'description' => 'Argon2id memory cost in KiB for the HTTP Basic Authentication password.'], + 'http_basic_auth_argon2id_time_cost' => ['type' => 'integer', 'minimum' => 1, 'maximum' => 12, 'default' => 4, 'description' => 'Argon2id time cost (iterations) for the HTTP Basic Authentication password.'], 'connect_to_docker_network' => ['type' => 'boolean', 'description' => 'The flag to connect the service to the predefined Docker network.'], 'force_domain_override' => ['type' => 'boolean', 'description' => 'Force domain usage even if conflicts are detected. Default is false.'], 'autogenerate_domain' => ['type' => 'boolean', 'default' => true, 'description' => 'If true and domains is empty, auto-generate a domain using the server\'s wildcard domain or sslip.io fallback. Default: true.'], @@ -815,6 +824,10 @@ class ApplicationsController extends Controller 'is_http_basic_auth_enabled' => ['type' => 'boolean', 'description' => 'HTTP Basic Authentication enabled.'], 'http_basic_auth_username' => ['type' => 'string', 'nullable' => true, 'description' => 'Username for HTTP Basic Authentication'], 'http_basic_auth_password' => ['type' => 'string', 'nullable' => true, 'description' => 'Password for HTTP Basic Authentication'], + 'http_basic_auth_hash_algorithm' => ['type' => 'string', 'enum' => ['bcrypt', 'argon2id'], 'default' => 'bcrypt', 'description' => 'Hash algorithm for the HTTP Basic Authentication password. Argon2id needs the Caddy proxy, version 2.11 or newer.'], + 'http_basic_auth_bcrypt_cost' => ['type' => 'integer', 'minimum' => 4, 'maximum' => 14, 'default' => 10, 'description' => 'Bcrypt cost for the HTTP Basic Authentication password.'], + 'http_basic_auth_argon2id_memory_cost' => ['type' => 'integer', 'minimum' => 8192, 'maximum' => 262144, 'default' => 65536, 'description' => 'Argon2id memory cost in KiB for the HTTP Basic Authentication password.'], + 'http_basic_auth_argon2id_time_cost' => ['type' => 'integer', 'minimum' => 1, 'maximum' => 12, 'default' => 4, 'description' => 'Argon2id time cost (iterations) for the HTTP Basic Authentication password.'], 'connect_to_docker_network' => ['type' => 'boolean', 'description' => 'The flag to connect the service to the predefined Docker network.'], 'force_domain_override' => ['type' => 'boolean', 'description' => 'Force domain usage even if conflicts are detected. Default is false.'], 'autogenerate_domain' => ['type' => 'boolean', 'default' => true, 'description' => 'If true and domains is empty, auto-generate a domain using the server\'s wildcard domain or sslip.io fallback. Default: true.'], @@ -981,6 +994,10 @@ class ApplicationsController extends Controller 'is_http_basic_auth_enabled' => ['type' => 'boolean', 'description' => 'HTTP Basic Authentication enabled.'], 'http_basic_auth_username' => ['type' => 'string', 'nullable' => true, 'description' => 'Username for HTTP Basic Authentication'], 'http_basic_auth_password' => ['type' => 'string', 'nullable' => true, 'description' => 'Password for HTTP Basic Authentication'], + 'http_basic_auth_hash_algorithm' => ['type' => 'string', 'enum' => ['bcrypt', 'argon2id'], 'default' => 'bcrypt', 'description' => 'Hash algorithm for the HTTP Basic Authentication password. Argon2id needs the Caddy proxy, version 2.11 or newer.'], + 'http_basic_auth_bcrypt_cost' => ['type' => 'integer', 'minimum' => 4, 'maximum' => 14, 'default' => 10, 'description' => 'Bcrypt cost for the HTTP Basic Authentication password.'], + 'http_basic_auth_argon2id_memory_cost' => ['type' => 'integer', 'minimum' => 8192, 'maximum' => 262144, 'default' => 65536, 'description' => 'Argon2id memory cost in KiB for the HTTP Basic Authentication password.'], + 'http_basic_auth_argon2id_time_cost' => ['type' => 'integer', 'minimum' => 1, 'maximum' => 12, 'default' => 4, 'description' => 'Argon2id time cost (iterations) for the HTTP Basic Authentication password.'], 'connect_to_docker_network' => ['type' => 'boolean', 'description' => 'The flag to connect the service to the predefined Docker network.'], 'force_domain_override' => ['type' => 'boolean', 'description' => 'Force domain usage even if conflicts are detected. Default is false.'], 'autogenerate_domain' => ['type' => 'boolean', 'default' => true, 'description' => 'If true and domains is empty, auto-generate a domain using the server\'s wildcard domain or sslip.io fallback. Default: true.'], @@ -1143,6 +1160,10 @@ class ApplicationsController extends Controller 'is_http_basic_auth_enabled' => ['type' => 'boolean', 'description' => 'HTTP Basic Authentication enabled.'], 'http_basic_auth_username' => ['type' => 'string', 'nullable' => true, 'description' => 'Username for HTTP Basic Authentication'], 'http_basic_auth_password' => ['type' => 'string', 'nullable' => true, 'description' => 'Password for HTTP Basic Authentication'], + 'http_basic_auth_hash_algorithm' => ['type' => 'string', 'enum' => ['bcrypt', 'argon2id'], 'default' => 'bcrypt', 'description' => 'Hash algorithm for the HTTP Basic Authentication password. Argon2id needs the Caddy proxy, version 2.11 or newer.'], + 'http_basic_auth_bcrypt_cost' => ['type' => 'integer', 'minimum' => 4, 'maximum' => 14, 'default' => 10, 'description' => 'Bcrypt cost for the HTTP Basic Authentication password.'], + 'http_basic_auth_argon2id_memory_cost' => ['type' => 'integer', 'minimum' => 8192, 'maximum' => 262144, 'default' => 65536, 'description' => 'Argon2id memory cost in KiB for the HTTP Basic Authentication password.'], + 'http_basic_auth_argon2id_time_cost' => ['type' => 'integer', 'minimum' => 1, 'maximum' => 12, 'default' => 4, 'description' => 'Argon2id time cost (iterations) for the HTTP Basic Authentication password.'], 'connect_to_docker_network' => ['type' => 'boolean', 'description' => 'The flag to connect the service to the predefined Docker network.'], 'force_domain_override' => ['type' => 'boolean', 'description' => 'Force domain usage even if conflicts are detected. Default is false.'], 'autogenerate_domain' => ['type' => 'boolean', 'default' => true, 'description' => 'If true and domains is empty, auto-generate a domain using the server\'s wildcard domain or sslip.io fallback. Default: true.'], @@ -1228,7 +1249,7 @@ class ApplicationsController extends Controller if ($return instanceof JsonResponse) { return $return; } - $allowedFields = ['project_uuid', 'environment_name', 'environment_uuid', 'server_uuid', 'destination_uuid', 'type', 'name', 'description', 'is_static', 'is_spa', 'is_auto_deploy_enabled', 'is_force_https_enabled', 'is_preview_deployments_enabled', 'domains', 'noindex_domains', 'git_repository', 'git_branch', 'git_commit_sha', 'private_key_uuid', 'docker_registry_image_name', 'docker_registry_image_tag', 'build_pack', 'install_command', 'build_command', 'start_command', 'ports_exposes', 'ports_mappings', 'custom_network_aliases', 'base_directory', 'publish_directory', 'health_check_enabled', 'health_check_type', 'health_check_command', 'health_check_path', 'health_check_port', 'health_check_host', 'health_check_method', 'health_check_return_code', 'health_check_scheme', 'health_check_response_text', 'health_check_interval', 'health_check_timeout', 'health_check_retries', 'health_check_start_period', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'custom_labels', 'custom_docker_run_options', 'post_deployment_command', 'post_deployment_command_container', 'pre_deployment_command', 'pre_deployment_command_container', 'manual_webhook_secret_github', 'manual_webhook_secret_gitlab', 'manual_webhook_secret_bitbucket', 'manual_webhook_secret_gitea', 'redirect', 'github_app_uuid', 'instant_deploy', 'dockerfile', 'dockerfile_location', 'docker_compose_location', 'docker_compose_raw', 'docker_compose_custom_start_command', 'docker_compose_custom_build_command', 'docker_compose_domains', 'watch_paths', 'use_build_server', 'use_build_secrets', 'static_image', 'custom_nginx_configuration', 'is_http_basic_auth_enabled', 'http_basic_auth_username', 'http_basic_auth_password', 'connect_to_docker_network', 'force_domain_override', 'autogenerate_domain', 'is_container_label_escape_enabled', 'tags', 'is_preserve_repository_enabled', 'preview_url_template', 'max_restart_count', ...self::APPLICATION_SETTING_FIELDS]; + $allowedFields = ['project_uuid', 'environment_name', 'environment_uuid', 'server_uuid', 'destination_uuid', 'type', 'name', 'description', 'is_static', 'is_spa', 'is_auto_deploy_enabled', 'is_force_https_enabled', 'is_preview_deployments_enabled', 'domains', 'noindex_domains', 'git_repository', 'git_branch', 'git_commit_sha', 'private_key_uuid', 'docker_registry_image_name', 'docker_registry_image_tag', 'build_pack', 'install_command', 'build_command', 'start_command', 'ports_exposes', 'ports_mappings', 'custom_network_aliases', 'base_directory', 'publish_directory', 'health_check_enabled', 'health_check_type', 'health_check_command', 'health_check_path', 'health_check_port', 'health_check_host', 'health_check_method', 'health_check_return_code', 'health_check_scheme', 'health_check_response_text', 'health_check_interval', 'health_check_timeout', 'health_check_retries', 'health_check_start_period', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'custom_labels', 'custom_docker_run_options', 'post_deployment_command', 'post_deployment_command_container', 'pre_deployment_command', 'pre_deployment_command_container', 'manual_webhook_secret_github', 'manual_webhook_secret_gitlab', 'manual_webhook_secret_bitbucket', 'manual_webhook_secret_gitea', 'redirect', 'github_app_uuid', 'instant_deploy', 'dockerfile', 'dockerfile_location', 'docker_compose_location', 'docker_compose_raw', 'docker_compose_custom_start_command', 'docker_compose_custom_build_command', 'docker_compose_domains', 'watch_paths', 'use_build_server', 'use_build_secrets', 'static_image', 'custom_nginx_configuration', 'is_http_basic_auth_enabled', 'http_basic_auth_username', 'http_basic_auth_password', 'http_basic_auth_hash_algorithm', 'http_basic_auth_bcrypt_cost', 'http_basic_auth_argon2id_memory_cost', 'http_basic_auth_argon2id_time_cost', 'connect_to_docker_network', 'force_domain_override', 'autogenerate_domain', 'is_container_label_escape_enabled', 'tags', 'is_preserve_repository_enabled', 'preview_url_template', 'max_restart_count', ...self::APPLICATION_SETTING_FIELDS]; $validator = customApiValidator($request->all(), [ 'name' => 'string|max:255', @@ -1240,7 +1261,9 @@ class ApplicationsController extends Controller 'destination_uuid' => 'string', 'is_http_basic_auth_enabled' => 'boolean', 'http_basic_auth_username' => 'string|nullable', - 'http_basic_auth_password' => 'string|nullable', + // bcrypt ignores everything after 72 bytes. Labels for Traefik always use bcrypt and are also generated on + // Caddy servers when "Labels for all supported proxies" is selected, so that the proxy can be switched easily. + 'http_basic_auth_password' => 'string|nullable|max:72', 'autogenerate_domain' => 'boolean', 'tags' => 'array|nullable', 'tags.*' => 'string|min:2', @@ -3106,6 +3129,10 @@ class ApplicationsController extends Controller 'watch_paths' => ['type' => 'string', 'description' => 'The watch paths.'], 'use_build_server' => ['type' => 'boolean', 'nullable' => true, 'description' => 'Use build server.'], 'use_build_secrets' => ['type' => 'boolean', 'description' => 'Use Docker Build Secrets for build-time environment variables.'], + 'http_basic_auth_hash_algorithm' => ['type' => 'string', 'enum' => ['bcrypt', 'argon2id'], 'description' => 'Hash algorithm for the HTTP Basic Authentication password. Argon2id needs the Caddy proxy, version 2.11 or newer.'], + 'http_basic_auth_bcrypt_cost' => ['type' => 'integer', 'minimum' => 4, 'maximum' => 14, 'description' => 'Bcrypt cost for the HTTP Basic Authentication password.'], + 'http_basic_auth_argon2id_memory_cost' => ['type' => 'integer', 'minimum' => 8192, 'maximum' => 262144, 'description' => 'Argon2id memory cost in KiB for the HTTP Basic Authentication password.'], + 'http_basic_auth_argon2id_time_cost' => ['type' => 'integer', 'minimum' => 1, 'maximum' => 12, 'description' => 'Argon2id time cost (iterations) for the HTTP Basic Authentication password.'], 'is_git_submodules_enabled' => ['type' => 'boolean', 'description' => 'Clone Git submodules.'], 'is_git_lfs_enabled' => ['type' => 'boolean', 'description' => 'Enable Git LFS.'], 'is_git_shallow_clone_enabled' => ['type' => 'boolean', 'description' => 'Use a shallow Git clone.'], @@ -3226,7 +3253,7 @@ class ApplicationsController extends Controller $dnsCleanup = app(ManagedDnsRecordCleanup::class); $previousDnsHostnames = $dnsCleanup->hostnamesOf($application); $server = $application->destination->server; - $allowedFields = ['name', 'description', 'is_static', 'is_spa', 'is_auto_deploy_enabled', 'is_force_https_enabled', 'is_preview_deployments_enabled', 'domains', 'noindex_domains', 'git_repository', 'git_branch', 'git_commit_sha', 'docker_registry_image_name', 'docker_registry_image_tag', 'build_pack', 'static_image', 'install_command', 'build_command', 'start_command', 'ports_exposes', 'ports_mappings', 'custom_network_aliases', 'base_directory', 'publish_directory', 'health_check_enabled', 'health_check_type', 'health_check_command', 'health_check_path', 'health_check_port', 'health_check_host', 'health_check_method', 'health_check_return_code', 'health_check_scheme', 'health_check_response_text', 'health_check_interval', 'health_check_timeout', 'health_check_retries', 'health_check_start_period', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'custom_labels', 'custom_docker_run_options', 'post_deployment_command', 'post_deployment_command_container', 'pre_deployment_command', 'pre_deployment_command_container', 'watch_paths', 'manual_webhook_secret_github', 'manual_webhook_secret_gitlab', 'manual_webhook_secret_bitbucket', 'manual_webhook_secret_gitea', 'dockerfile_location', 'dockerfile_target_build', 'docker_compose_location', 'docker_compose_custom_start_command', 'docker_compose_custom_build_command', 'docker_compose_domains', 'redirect', 'instant_deploy', 'use_build_server', 'use_build_secrets', 'custom_nginx_configuration', 'is_http_basic_auth_enabled', 'http_basic_auth_username', 'http_basic_auth_password', 'connect_to_docker_network', 'force_domain_override', 'is_container_label_escape_enabled', 'is_preserve_repository_enabled', 'preview_url_template', 'max_restart_count', ...self::APPLICATION_SETTING_FIELDS]; + $allowedFields = ['name', 'description', 'is_static', 'is_spa', 'is_auto_deploy_enabled', 'is_force_https_enabled', 'is_preview_deployments_enabled', 'domains', 'noindex_domains', 'git_repository', 'git_branch', 'git_commit_sha', 'docker_registry_image_name', 'docker_registry_image_tag', 'build_pack', 'static_image', 'install_command', 'build_command', 'start_command', 'ports_exposes', 'ports_mappings', 'custom_network_aliases', 'base_directory', 'publish_directory', 'health_check_enabled', 'health_check_type', 'health_check_command', 'health_check_path', 'health_check_port', 'health_check_host', 'health_check_method', 'health_check_return_code', 'health_check_scheme', 'health_check_response_text', 'health_check_interval', 'health_check_timeout', 'health_check_retries', 'health_check_start_period', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'custom_labels', 'custom_docker_run_options', 'post_deployment_command', 'post_deployment_command_container', 'pre_deployment_command', 'pre_deployment_command_container', 'watch_paths', 'manual_webhook_secret_github', 'manual_webhook_secret_gitlab', 'manual_webhook_secret_bitbucket', 'manual_webhook_secret_gitea', 'dockerfile_location', 'dockerfile_target_build', 'docker_compose_location', 'docker_compose_custom_start_command', 'docker_compose_custom_build_command', 'docker_compose_domains', 'redirect', 'instant_deploy', 'use_build_server', 'use_build_secrets', 'custom_nginx_configuration', 'is_http_basic_auth_enabled', 'http_basic_auth_username', 'http_basic_auth_password', 'http_basic_auth_hash_algorithm', 'http_basic_auth_bcrypt_cost', 'http_basic_auth_argon2id_memory_cost', 'http_basic_auth_argon2id_time_cost', 'connect_to_docker_network', 'force_domain_override', 'is_container_label_escape_enabled', 'is_preserve_repository_enabled', 'preview_url_template', 'max_restart_count', ...self::APPLICATION_SETTING_FIELDS]; $validationRules = [ 'name' => 'string|max:255', @@ -3241,7 +3268,9 @@ class ApplicationsController extends Controller 'is_http_basic_auth_enabled' => 'boolean|nullable', 'is_preview_deployments_enabled' => 'boolean|nullable', 'http_basic_auth_username' => 'string', - 'http_basic_auth_password' => 'string', + // bcrypt ignores everything after 72 bytes. Labels for Traefik always use bcrypt and are also generated on + // Caddy servers when "Labels for all supported proxies" is selected, so that the proxy can be switched easily. + 'http_basic_auth_password' => 'string|max:72', 'include_source_commit_in_build' => 'boolean', 'ports_exposes' => 'nullable|string|regex:/^(\d+)(,\d+)*$/', ]; @@ -3322,7 +3351,8 @@ class ApplicationsController extends Controller $requestHasHttpBasicAuth = $request->has('is_http_basic_auth_enabled') || $request->has('http_basic_auth_username') - || $request->has('http_basic_auth_password'); + || $request->has('http_basic_auth_password') + || $request->hasAny(['http_basic_auth_hash_algorithm', 'http_basic_auth_bcrypt_cost', 'http_basic_auth_argon2id_memory_cost', 'http_basic_auth_argon2id_time_cost']); if ($request->has('is_http_basic_auth_enabled') && $request->is_http_basic_auth_enabled === true) { if (blank($application->http_basic_auth_username) || blank($application->http_basic_auth_password)) { @@ -4867,6 +4897,15 @@ class ApplicationsController extends Controller { $teamId = getTeamIdFromToken(); + if ($request->input('http_basic_auth_hash_algorithm') === HttpBasicAuthHashAlgorithm::ARGON2ID->value && ! $server->caddySupportsArgon2idBasicAuth()) { + return response()->json([ + 'message' => 'Validation failed.', + 'errors' => [ + 'http_basic_auth_hash_algorithm' => 'Argon2id needs a server that runs the Caddy proxy, version 2.11 or newer.', + ], + ], 422); + } + // Validate ports_mappings if ($request->has('ports_mappings')) { $ports = []; diff --git a/app/Livewire/Project/Application/General.php b/app/Livewire/Project/Application/General.php index 5d272b36fa..0e13b97501 100644 --- a/app/Livewire/Project/Application/General.php +++ b/app/Livewire/Project/Application/General.php @@ -2,6 +2,7 @@ namespace App\Livewire\Project\Application; +use App\Enums\HttpBasicAuthHashAlgorithm; use App\Enums\StaticImageTypes; use App\Jobs\ApplicationDeploymentJob; use App\Livewire\Project\Service\Storage; @@ -118,6 +119,14 @@ class General extends Component public ?string $httpBasicAuthPassword = null; + public string $httpBasicAuthHashAlgorithm = HttpBasicAuthHashAlgorithm::BCRYPT->value; + + public int $httpBasicAuthBcryptCost = 10; + + public int $httpBasicAuthArgon2idMemoryCost = 65536; + + public int $httpBasicAuthArgon2idTimeCost = 4; + public ?string $watchPaths = null; public string $redirect; @@ -193,7 +202,13 @@ class General extends Component 'isPreserveRepositoryEnabled' => 'boolean|required', 'isHttpBasicAuthEnabled' => 'boolean|required', 'httpBasicAuthUsername' => 'string|nullable', - 'httpBasicAuthPassword' => 'string|nullable', + // bcrypt ignores everything after 72 bytes. Labels for Traefik always use bcrypt and are also generated on + // Caddy servers when "Labels for all supported proxies" is selected, so that the proxy can be switched easily. + 'httpBasicAuthPassword' => 'string|nullable|max:72', + 'httpBasicAuthHashAlgorithm' => ['required', Rule::enum(HttpBasicAuthHashAlgorithm::class)], + 'httpBasicAuthBcryptCost' => 'required|integer|min:4|max:14', + 'httpBasicAuthArgon2idMemoryCost' => 'required|integer|min:8192|max:262144', + 'httpBasicAuthArgon2idTimeCost' => 'required|integer|min:1|max:12', 'watchPaths' => 'nullable', 'redirect' => 'string|required', ]; @@ -367,6 +382,9 @@ class General extends Component if ($toModel) { $this->validate(); $this->validateChangedDockerComposeRaw(); + if ($this->httpBasicAuthHashAlgorithm === HttpBasicAuthHashAlgorithm::ARGON2ID->value && ! $this->application->destination->server->caddySupportsArgon2idBasicAuth()) { + throw new Exception('Argon2id needs a server that runs the Caddy proxy, version 2.11 or newer.'); + } // Application properties $this->application->name = $this->name; @@ -407,6 +425,10 @@ class General extends Component $this->application->is_http_basic_auth_enabled = $this->isHttpBasicAuthEnabled; $this->application->http_basic_auth_username = $this->httpBasicAuthUsername; $this->application->http_basic_auth_password = $this->httpBasicAuthPassword; + $this->application->http_basic_auth_hash_algorithm = $this->httpBasicAuthHashAlgorithm; + $this->application->http_basic_auth_bcrypt_cost = $this->httpBasicAuthBcryptCost; + $this->application->http_basic_auth_argon2id_memory_cost = $this->httpBasicAuthArgon2idMemoryCost; + $this->application->http_basic_auth_argon2id_time_cost = $this->httpBasicAuthArgon2idTimeCost; $this->application->watch_paths = $this->watchPaths; $this->application->redirect = $this->redirect; @@ -460,6 +482,10 @@ class General extends Component $this->httpBasicAuthPassword = auth()->user()->can('update', $this->application) ? $this->application->http_basic_auth_password : null; + $this->httpBasicAuthHashAlgorithm = ($this->application->usesArgon2idBasicAuth() ? HttpBasicAuthHashAlgorithm::ARGON2ID : HttpBasicAuthHashAlgorithm::BCRYPT)->value; + $this->httpBasicAuthBcryptCost = $this->application->http_basic_auth_bcrypt_cost; + $this->httpBasicAuthArgon2idMemoryCost = $this->application->http_basic_auth_argon2id_memory_cost; + $this->httpBasicAuthArgon2idTimeCost = $this->application->http_basic_auth_argon2id_time_cost; $this->watchPaths = $this->application->watch_paths; $this->redirect = $this->application->redirect; @@ -497,7 +523,7 @@ class General extends Component if ($oldIsSpa !== $this->isSpa) { $this->generateNginxConfiguration($this->isSpa ? 'spa' : 'static'); } - if ($oldIsHttpBasicAuthEnabled !== $this->isHttpBasicAuthEnabled) { + if ($oldIsHttpBasicAuthEnabled !== $this->isHttpBasicAuthEnabled || $this->application->isDirty(['http_basic_auth_hash_algorithm', 'http_basic_auth_bcrypt_cost', 'http_basic_auth_argon2id_memory_cost', 'http_basic_auth_argon2id_time_cost'])) { $this->application->save(); } diff --git a/app/Models/Application.php b/app/Models/Application.php index 277b7ec246..1c3c416bb1 100644 --- a/app/Models/Application.php +++ b/app/Models/Application.php @@ -4,6 +4,7 @@ namespace App\Models; use App\Enums\ApplicationDeploymentStatus; use App\Enums\BuildPackTypes; +use App\Enums\HttpBasicAuthHashAlgorithm; use App\Enums\ProxyTypes; use App\Exceptions\DeploymentException; use App\Services\ConfigurationGenerator; @@ -125,6 +126,10 @@ use Symfony\Component\Yaml\Yaml; 'is_http_basic_auth_enabled' => ['type' => 'boolean', 'description' => 'HTTP Basic Authentication enabled.'], 'http_basic_auth_username' => ['type' => 'string', 'nullable' => true, 'description' => 'Username for HTTP Basic Authentication'], 'http_basic_auth_password' => ['type' => 'string', 'nullable' => true, 'description' => 'Password for HTTP Basic Authentication'], + 'http_basic_auth_hash_algorithm' => ['type' => 'string', 'enum' => ['bcrypt', 'argon2id'], 'description' => 'Hash algorithm for the HTTP Basic Authentication password. Argon2id needs the Caddy proxy, version 2.11 or newer.'], + 'http_basic_auth_bcrypt_cost' => ['type' => 'integer', 'minimum' => 4, 'maximum' => 14, 'description' => 'Bcrypt cost for the HTTP Basic Authentication password.'], + 'http_basic_auth_argon2id_memory_cost' => ['type' => 'integer', 'minimum' => 8192, 'maximum' => 262144, 'description' => 'Argon2id memory cost in KiB for the HTTP Basic Authentication password.'], + 'http_basic_auth_argon2id_time_cost' => ['type' => 'integer', 'minimum' => 1, 'maximum' => 12, 'description' => 'Argon2id time cost (iterations) for the HTTP Basic Authentication password.'], new OA\Property(property: 'settings', ref: '#/components/schemas/ApplicationSetting'), ] )] @@ -220,6 +225,10 @@ class Application extends BaseModel 'is_http_basic_auth_enabled', 'http_basic_auth_username', 'http_basic_auth_password', + 'http_basic_auth_hash_algorithm', + 'http_basic_auth_bcrypt_cost', + 'http_basic_auth_argon2id_memory_cost', + 'http_basic_auth_argon2id_time_cost', 'connect_to_docker_network', 'force_domain_override', 'is_container_label_escape_enabled', @@ -268,6 +277,7 @@ class Application extends BaseModel { return [ 'http_basic_auth_password' => 'encrypted', + 'http_basic_auth_hash_algorithm' => HttpBasicAuthHashAlgorithm::class, 'manual_webhook_secret_github' => 'encrypted', 'manual_webhook_secret_gitlab' => 'encrypted', 'manual_webhook_secret_bitbucket' => 'encrypted', @@ -657,6 +667,12 @@ class Application extends BaseModel return $this->source instanceof GithubApp; } + public function usesArgon2idBasicAuth(): bool + { + return $this->http_basic_auth_hash_algorithm === HttpBasicAuthHashAlgorithm::ARGON2ID + && $this->destination->server->caddySupportsArgon2idBasicAuth(); + } + public function isForceHttpsEnabled() { return data_get($this, 'settings.is_force_https_enabled', false); diff --git a/app/Models/Server.php b/app/Models/Server.php index 88f3ecc15a..2fc5a99e3e 100644 --- a/app/Models/Server.php +++ b/app/Models/Server.php @@ -1244,6 +1244,12 @@ $siteAddress { return $this->caddyRunsCurrentVersion(); } + public function caddySupportsArgon2idBasicAuth(): bool + { + return $this->caddyRunsCurrentVersion() + && self::caddyDockerProxyImageVersion($this->configuredCaddyProxyImage()) >= [2, 11]; + } + public function isServerApiEnabled(): bool { return $this->settings->is_sentinel_enabled; diff --git a/app/Services/DeploymentConfiguration/ApplicationConfigurationSnapshot.php b/app/Services/DeploymentConfiguration/ApplicationConfigurationSnapshot.php index b974e03db3..6a38840c5f 100644 --- a/app/Services/DeploymentConfiguration/ApplicationConfigurationSnapshot.php +++ b/app/Services/DeploymentConfiguration/ApplicationConfigurationSnapshot.php @@ -208,6 +208,10 @@ class ApplicationConfigurationSnapshot $this->item('is_http_basic_auth_enabled', 'HTTP basic auth', $this->application->is_http_basic_auth_enabled, 'redeploy'), $this->item('http_basic_auth_username', 'HTTP basic auth username', $this->application->http_basic_auth_username, 'redeploy'), $this->item('http_basic_auth_password', 'HTTP basic auth password', $this->application->http_basic_auth_password, 'redeploy', sensitive: true), + $this->item('http_basic_auth_hash_algorithm', 'HTTP basic auth hash algorithm', $this->application->http_basic_auth_hash_algorithm?->value, 'redeploy'), + $this->item('http_basic_auth_bcrypt_cost', 'HTTP basic auth bcrypt cost factor', $this->application->http_basic_auth_bcrypt_cost, 'redeploy'), + $this->item('http_basic_auth_argon2id_memory_cost', 'HTTP basic auth Argon2id memory (KiB)', $this->application->http_basic_auth_argon2id_memory_cost, 'redeploy'), + $this->item('http_basic_auth_argon2id_time_cost', 'HTTP basic auth Argon2id iterations', $this->application->http_basic_auth_argon2id_time_cost, 'redeploy'), ]; } diff --git a/bootstrap/helpers/api.php b/bootstrap/helpers/api.php index ee89f52c6f..46501331e9 100644 --- a/bootstrap/helpers/api.php +++ b/bootstrap/helpers/api.php @@ -2,6 +2,7 @@ use App\Actions\Shared\MigrateResourceToDestination; use App\Enums\BuildPackTypes; +use App\Enums\HttpBasicAuthHashAlgorithm; use App\Enums\RedirectTypes; use App\Enums\StaticImageTypes; use App\Models\Application; @@ -276,6 +277,10 @@ function sharedDataApplications(?Application $application = null): array 'docker_compose_custom_build_command' => ValidationPatterns::shellSafeCommandRules(), 'is_container_label_escape_enabled' => 'boolean', 'is_preserve_repository_enabled' => 'boolean', + 'http_basic_auth_hash_algorithm' => Rule::enum(HttpBasicAuthHashAlgorithm::class), + 'http_basic_auth_bcrypt_cost' => 'integer|min:4|max:14', + 'http_basic_auth_argon2id_memory_cost' => 'integer|min:8192|max:262144', + 'http_basic_auth_argon2id_time_cost' => 'integer|min:1|max:12', ]; } diff --git a/bootstrap/helpers/docker.php b/bootstrap/helpers/docker.php index b44714a8b0..464bad84cc 100644 --- a/bootstrap/helpers/docker.php +++ b/bootstrap/helpers/docker.php @@ -766,7 +766,7 @@ function isNoindexDomain(string $domain, ?Collection $noindex_domains): bool ->contains(ValidationPatterns::normalizeApplicationDomainUrl($domain)); } -function fqdnLabelsForCaddy(string $network, string $uuid, Collection $domains, bool $is_force_https_enabled = false, $onlyPort = null, ?Collection $serviceLabels = null, ?bool $is_gzip_enabled = true, ?bool $is_stripprefix_enabled = true, ?string $service_name = null, ?string $image = null, string $redirect_direction = 'both', ?string $predefinedPort = null, bool $is_http_basic_auth_enabled = false, ?string $http_basic_auth_username = null, ?string $http_basic_auth_password = null, ?Collection $noindex_domains = null, bool $is_traffic_analytics_enabled = false, array $domainPortOverrides = [], bool $supports_log_append = false, bool $supports_basic_auth_directive = false) +function fqdnLabelsForCaddy(string $network, string $uuid, Collection $domains, bool $is_force_https_enabled = false, $onlyPort = null, ?Collection $serviceLabels = null, ?bool $is_gzip_enabled = true, ?bool $is_stripprefix_enabled = true, ?string $service_name = null, ?string $image = null, string $redirect_direction = 'both', ?string $predefinedPort = null, bool $is_http_basic_auth_enabled = false, ?string $http_basic_auth_username = null, ?string $http_basic_auth_password = null, ?Collection $noindex_domains = null, bool $is_traffic_analytics_enabled = false, array $domainPortOverrides = [], bool $supports_log_append = false, bool $supports_basic_auth_directive = false, ?int $http_basic_auth_bcrypt_cost = null, array $http_basic_auth_argon2id_options = []) { $labels = collect([]); if ($serviceLabels) { @@ -777,7 +777,9 @@ function fqdnLabelsForCaddy(string $network, string $uuid, Collection $domains, $is_http_basic_auth_enabled = $is_http_basic_auth_enabled && $http_basic_auth_username !== null && $http_basic_auth_password !== null; if ($is_http_basic_auth_enabled) { - $hashedPassword = password_hash($http_basic_auth_password, PASSWORD_BCRYPT, ['cost' => 10]); + $hashedPassword = $http_basic_auth_argon2id_options + ? password_hash($http_basic_auth_password, PASSWORD_ARGON2ID, $http_basic_auth_argon2id_options) + : password_hash($http_basic_auth_password, PASSWORD_BCRYPT, ['cost' => $http_basic_auth_bcrypt_cost ?? 10]); } $trafficAppKey = caddyTrafficAppKey($uuid, $service_name); @@ -834,6 +836,9 @@ function fqdnLabelsForCaddy(string $network, string $uuid, Collection $domains, if ($is_http_basic_auth_enabled) { // Caddy 2.8 renamed basicauth to basic_auth; see Server::caddySupportsBasicAuthDirective(). $basicAuthDirective = $supports_basic_auth_directive ? 'basic_auth' : 'basicauth'; + if ($http_basic_auth_argon2id_options) { + $labels->push("caddy_{$loop}.{$basicAuthDirective}=argon2id"); + } $labels->push("caddy_{$loop}.{$basicAuthDirective}.{$http_basic_auth_username}=\"{$hashedPassword}\""); } if ($is_traffic_analytics_enabled) { @@ -907,7 +912,7 @@ function dockerComposeServicePorts(?string $compose, ?string $serviceName): arra ->unique()->values()->all(); } -function fqdnLabelsForTraefik(string $uuid, Collection $domains, bool $is_force_https_enabled = false, $onlyPort = null, ?Collection $serviceLabels = null, ?bool $is_gzip_enabled = true, ?bool $is_stripprefix_enabled = true, ?string $service_name = null, bool $generate_unique_uuid = false, ?string $image = null, string $redirect_direction = 'both', bool $is_http_basic_auth_enabled = false, ?string $http_basic_auth_username = null, ?string $http_basic_auth_password = null, ?Collection $noindex_domains = null, bool $escape_redirect_replacement_for_compose = true, array $domainPortOverrides = []) +function fqdnLabelsForTraefik(string $uuid, Collection $domains, bool $is_force_https_enabled = false, $onlyPort = null, ?Collection $serviceLabels = null, ?bool $is_gzip_enabled = true, ?bool $is_stripprefix_enabled = true, ?string $service_name = null, bool $generate_unique_uuid = false, ?string $image = null, string $redirect_direction = 'both', bool $is_http_basic_auth_enabled = false, ?string $http_basic_auth_username = null, ?string $http_basic_auth_password = null, ?Collection $noindex_domains = null, bool $escape_redirect_replacement_for_compose = true, array $domainPortOverrides = [], ?int $http_basic_auth_bcrypt_cost = null) { $labels = collect([]); $labels->push('traefik.enable=true'); @@ -919,7 +924,7 @@ function fqdnLabelsForTraefik(string $uuid, Collection $domains, bool $is_force_ $is_http_basic_auth_enabled = $is_http_basic_auth_enabled && $http_basic_auth_username !== null && $http_basic_auth_password !== null; $http_basic_auth_label = "http-basic-auth-{$uuid}"; if ($is_http_basic_auth_enabled) { - $hashedPassword = password_hash($http_basic_auth_password, PASSWORD_BCRYPT, ['cost' => 10]); + $hashedPassword = password_hash($http_basic_auth_password, PASSWORD_BCRYPT, ['cost' => $http_basic_auth_bcrypt_cost ?? 10]); } if ($is_http_basic_auth_enabled) { @@ -1186,6 +1191,10 @@ function generateLabelsApplication(Application $application, ?ApplicationPreview $appUuid = $appUuid.'-pr-'.$pull_request_id; } $labels = collect([]); + $httpBasicAuthArgon2idOptions = $application->usesArgon2idBasicAuth() ? [ + 'memory_cost' => $application->http_basic_auth_argon2id_memory_cost, + 'time_cost' => $application->http_basic_auth_argon2id_time_cost, + ] : []; if ($pull_request_id === 0) { if ($application->fqdn) { $domains = str(data_get($application, 'fqdn'))->explode(','); @@ -1205,6 +1214,7 @@ function generateLabelsApplication(Application $application, ?ApplicationPreview is_http_basic_auth_enabled: $application->is_http_basic_auth_enabled, http_basic_auth_username: $application->http_basic_auth_username, http_basic_auth_password: $application->http_basic_auth_password, + http_basic_auth_bcrypt_cost: $application->http_basic_auth_bcrypt_cost, noindex_domains: $noindexDomains, domainPortOverrides: $application->domain_port_overrides ?? [], )); @@ -1222,6 +1232,8 @@ function generateLabelsApplication(Application $application, ?ApplicationPreview is_http_basic_auth_enabled: $application->is_http_basic_auth_enabled, http_basic_auth_username: $application->http_basic_auth_username, http_basic_auth_password: $application->http_basic_auth_password, + http_basic_auth_bcrypt_cost: $application->http_basic_auth_bcrypt_cost, + http_basic_auth_argon2id_options: $httpBasicAuthArgon2idOptions, noindex_domains: $noindexDomains, is_traffic_analytics_enabled: $application->destination->server->isTrafficAnalyticsEnabled(), supports_log_append: $application->destination->server->caddySupportsLogAppend(), @@ -1242,6 +1254,7 @@ function generateLabelsApplication(Application $application, ?ApplicationPreview is_http_basic_auth_enabled: $application->is_http_basic_auth_enabled, http_basic_auth_username: $application->http_basic_auth_username, http_basic_auth_password: $application->http_basic_auth_password, + http_basic_auth_bcrypt_cost: $application->http_basic_auth_bcrypt_cost, noindex_domains: $noindexDomains, escape_redirect_replacement_for_compose: false, domainPortOverrides: $application->domain_port_overrides ?? [], @@ -1258,6 +1271,8 @@ function generateLabelsApplication(Application $application, ?ApplicationPreview is_http_basic_auth_enabled: $application->is_http_basic_auth_enabled, http_basic_auth_username: $application->http_basic_auth_username, http_basic_auth_password: $application->http_basic_auth_password, + http_basic_auth_bcrypt_cost: $application->http_basic_auth_bcrypt_cost, + http_basic_auth_argon2id_options: $httpBasicAuthArgon2idOptions, noindex_domains: $noindexDomains, is_traffic_analytics_enabled: $application->destination->server->isTrafficAnalyticsEnabled(), supports_log_append: $application->destination->server->caddySupportsLogAppend(), @@ -1288,6 +1303,7 @@ function generateLabelsApplication(Application $application, ?ApplicationPreview is_http_basic_auth_enabled: $application->is_http_basic_auth_enabled, http_basic_auth_username: $application->http_basic_auth_username, http_basic_auth_password: $application->http_basic_auth_password, + http_basic_auth_bcrypt_cost: $application->http_basic_auth_bcrypt_cost, noindex_domains: $noindexDomains, escape_redirect_replacement_for_compose: false, domainPortOverrides: $preview->domain_port_overrides ?? [], @@ -1305,6 +1321,8 @@ function generateLabelsApplication(Application $application, ?ApplicationPreview is_http_basic_auth_enabled: $application->is_http_basic_auth_enabled, http_basic_auth_username: $application->http_basic_auth_username, http_basic_auth_password: $application->http_basic_auth_password, + http_basic_auth_bcrypt_cost: $application->http_basic_auth_bcrypt_cost, + http_basic_auth_argon2id_options: $httpBasicAuthArgon2idOptions, noindex_domains: $noindexDomains, is_traffic_analytics_enabled: $application->destination->server->isTrafficAnalyticsEnabled(), supports_log_append: $application->destination->server->caddySupportsLogAppend(), @@ -1324,6 +1342,7 @@ function generateLabelsApplication(Application $application, ?ApplicationPreview is_http_basic_auth_enabled: $application->is_http_basic_auth_enabled, http_basic_auth_username: $application->http_basic_auth_username, http_basic_auth_password: $application->http_basic_auth_password, + http_basic_auth_bcrypt_cost: $application->http_basic_auth_bcrypt_cost, noindex_domains: $noindexDomains, escape_redirect_replacement_for_compose: false, domainPortOverrides: $preview->domain_port_overrides ?? [], @@ -1339,6 +1358,8 @@ function generateLabelsApplication(Application $application, ?ApplicationPreview is_http_basic_auth_enabled: $application->is_http_basic_auth_enabled, http_basic_auth_username: $application->http_basic_auth_username, http_basic_auth_password: $application->http_basic_auth_password, + http_basic_auth_bcrypt_cost: $application->http_basic_auth_bcrypt_cost, + http_basic_auth_argon2id_options: $httpBasicAuthArgon2idOptions, noindex_domains: $noindexDomains, is_traffic_analytics_enabled: $application->destination->server->isTrafficAnalyticsEnabled(), supports_log_append: $application->destination->server->caddySupportsLogAppend(), diff --git a/database/factories/ApplicationFactory.php b/database/factories/ApplicationFactory.php index 188d32954b..ecbae8862f 100644 --- a/database/factories/ApplicationFactory.php +++ b/database/factories/ApplicationFactory.php @@ -21,6 +21,9 @@ class ApplicationFactory extends Factory 'ports_exposes' => '3000', 'environment_id' => 1, 'destination_id' => 1, + 'http_basic_auth_bcrypt_cost' => 10, + 'http_basic_auth_argon2id_memory_cost' => 65536, + 'http_basic_auth_argon2id_time_cost' => 4, ]; } } diff --git a/database/migrations/2026_10_02_170101_add_http_basic_auth_hash_options_to_applications_table.php b/database/migrations/2026_10_02_170101_add_http_basic_auth_hash_options_to_applications_table.php new file mode 100644 index 0000000000..17212a63a6 --- /dev/null +++ b/database/migrations/2026_10_02_170101_add_http_basic_auth_hash_options_to_applications_table.php @@ -0,0 +1,30 @@ +string('http_basic_auth_hash_algorithm')->default('bcrypt'); + $table->unsignedTinyInteger('http_basic_auth_bcrypt_cost')->default(10); + $table->unsignedInteger('http_basic_auth_argon2id_memory_cost')->default(65536); + $table->unsignedTinyInteger('http_basic_auth_argon2id_time_cost')->default(4); + }); + } + + public function down(): void + { + Schema::table('applications', function (Blueprint $table) { + $table->dropColumn([ + 'http_basic_auth_hash_algorithm', + 'http_basic_auth_bcrypt_cost', + 'http_basic_auth_argon2id_memory_cost', + 'http_basic_auth_argon2id_time_cost', + ]); + }); + } +}; diff --git a/openapi.json b/openapi.json index 313fefa63a..8c4eb62e7f 100644 --- a/openapi.json +++ b/openapi.json @@ -979,6 +979,36 @@ "nullable": true, "description": "Password for HTTP Basic Authentication" }, + "http_basic_auth_hash_algorithm": { + "type": "string", + "enum": [ + "bcrypt", + "argon2id" + ], + "default": "bcrypt", + "description": "Hash algorithm for the HTTP Basic Authentication password. Argon2id needs the Caddy proxy, version 2.11 or newer." + }, + "http_basic_auth_bcrypt_cost": { + "type": "integer", + "minimum": 4, + "maximum": 14, + "default": 10, + "description": "Bcrypt cost for the HTTP Basic Authentication password." + }, + "http_basic_auth_argon2id_memory_cost": { + "type": "integer", + "minimum": 8192, + "maximum": 262144, + "default": 65536, + "description": "Argon2id memory cost in KiB for the HTTP Basic Authentication password." + }, + "http_basic_auth_argon2id_time_cost": { + "type": "integer", + "minimum": 1, + "maximum": 12, + "default": 4, + "description": "Argon2id time cost (iterations) for the HTTP Basic Authentication password." + }, "connect_to_docker_network": { "type": "boolean", "description": "The flag to connect the service to the predefined Docker network." @@ -1570,6 +1600,36 @@ "nullable": true, "description": "Password for HTTP Basic Authentication" }, + "http_basic_auth_hash_algorithm": { + "type": "string", + "enum": [ + "bcrypt", + "argon2id" + ], + "default": "bcrypt", + "description": "Hash algorithm for the HTTP Basic Authentication password. Argon2id needs the Caddy proxy, version 2.11 or newer." + }, + "http_basic_auth_bcrypt_cost": { + "type": "integer", + "minimum": 4, + "maximum": 14, + "default": 10, + "description": "Bcrypt cost for the HTTP Basic Authentication password." + }, + "http_basic_auth_argon2id_memory_cost": { + "type": "integer", + "minimum": 8192, + "maximum": 262144, + "default": 65536, + "description": "Argon2id memory cost in KiB for the HTTP Basic Authentication password." + }, + "http_basic_auth_argon2id_time_cost": { + "type": "integer", + "minimum": 1, + "maximum": 12, + "default": 4, + "description": "Argon2id time cost (iterations) for the HTTP Basic Authentication password." + }, "connect_to_docker_network": { "type": "boolean", "description": "The flag to connect the service to the predefined Docker network." @@ -2161,6 +2221,36 @@ "nullable": true, "description": "Password for HTTP Basic Authentication" }, + "http_basic_auth_hash_algorithm": { + "type": "string", + "enum": [ + "bcrypt", + "argon2id" + ], + "default": "bcrypt", + "description": "Hash algorithm for the HTTP Basic Authentication password. Argon2id needs the Caddy proxy, version 2.11 or newer." + }, + "http_basic_auth_bcrypt_cost": { + "type": "integer", + "minimum": 4, + "maximum": 14, + "default": 10, + "description": "Bcrypt cost for the HTTP Basic Authentication password." + }, + "http_basic_auth_argon2id_memory_cost": { + "type": "integer", + "minimum": 8192, + "maximum": 262144, + "default": 65536, + "description": "Argon2id memory cost in KiB for the HTTP Basic Authentication password." + }, + "http_basic_auth_argon2id_time_cost": { + "type": "integer", + "minimum": 1, + "maximum": 12, + "default": 4, + "description": "Argon2id time cost (iterations) for the HTTP Basic Authentication password." + }, "connect_to_docker_network": { "type": "boolean", "description": "The flag to connect the service to the predefined Docker network." @@ -2647,6 +2737,36 @@ "nullable": true, "description": "Password for HTTP Basic Authentication" }, + "http_basic_auth_hash_algorithm": { + "type": "string", + "enum": [ + "bcrypt", + "argon2id" + ], + "default": "bcrypt", + "description": "Hash algorithm for the HTTP Basic Authentication password. Argon2id needs the Caddy proxy, version 2.11 or newer." + }, + "http_basic_auth_bcrypt_cost": { + "type": "integer", + "minimum": 4, + "maximum": 14, + "default": 10, + "description": "Bcrypt cost for the HTTP Basic Authentication password." + }, + "http_basic_auth_argon2id_memory_cost": { + "type": "integer", + "minimum": 8192, + "maximum": 262144, + "default": 65536, + "description": "Argon2id memory cost in KiB for the HTTP Basic Authentication password." + }, + "http_basic_auth_argon2id_time_cost": { + "type": "integer", + "minimum": 1, + "maximum": 12, + "default": 4, + "description": "Argon2id time cost (iterations) for the HTTP Basic Authentication password." + }, "connect_to_docker_network": { "type": "boolean", "description": "The flag to connect the service to the predefined Docker network." @@ -3113,6 +3233,36 @@ "nullable": true, "description": "Password for HTTP Basic Authentication" }, + "http_basic_auth_hash_algorithm": { + "type": "string", + "enum": [ + "bcrypt", + "argon2id" + ], + "default": "bcrypt", + "description": "Hash algorithm for the HTTP Basic Authentication password. Argon2id needs the Caddy proxy, version 2.11 or newer." + }, + "http_basic_auth_bcrypt_cost": { + "type": "integer", + "minimum": 4, + "maximum": 14, + "default": 10, + "description": "Bcrypt cost for the HTTP Basic Authentication password." + }, + "http_basic_auth_argon2id_memory_cost": { + "type": "integer", + "minimum": 8192, + "maximum": 262144, + "default": 65536, + "description": "Argon2id memory cost in KiB for the HTTP Basic Authentication password." + }, + "http_basic_auth_argon2id_time_cost": { + "type": "integer", + "minimum": 1, + "maximum": 12, + "default": 4, + "description": "Argon2id time cost (iterations) for the HTTP Basic Authentication password." + }, "connect_to_docker_network": { "type": "boolean", "description": "The flag to connect the service to the predefined Docker network." @@ -3702,6 +3852,32 @@ "type": "boolean", "description": "Use Docker Build Secrets for build-time environment variables." }, + "http_basic_auth_hash_algorithm": { + "type": "string", + "enum": [ + "bcrypt", + "argon2id" + ], + "description": "Hash algorithm for the HTTP Basic Authentication password. Argon2id needs the Caddy proxy, version 2.11 or newer." + }, + "http_basic_auth_bcrypt_cost": { + "type": "integer", + "minimum": 4, + "maximum": 14, + "description": "Bcrypt cost for the HTTP Basic Authentication password." + }, + "http_basic_auth_argon2id_memory_cost": { + "type": "integer", + "minimum": 8192, + "maximum": 262144, + "description": "Argon2id memory cost in KiB for the HTTP Basic Authentication password." + }, + "http_basic_auth_argon2id_time_cost": { + "type": "integer", + "minimum": 1, + "maximum": 12, + "description": "Argon2id time cost (iterations) for the HTTP Basic Authentication password." + }, "is_git_submodules_enabled": { "type": "boolean", "description": "Clone Git submodules." @@ -23504,6 +23680,32 @@ "nullable": true, "description": "Password for HTTP Basic Authentication" }, + "http_basic_auth_hash_algorithm": { + "type": "string", + "enum": [ + "bcrypt", + "argon2id" + ], + "description": "Hash algorithm for the HTTP Basic Authentication password. Argon2id needs the Caddy proxy, version 2.11 or newer." + }, + "http_basic_auth_bcrypt_cost": { + "type": "integer", + "minimum": 4, + "maximum": 14, + "description": "Bcrypt cost for the HTTP Basic Authentication password." + }, + "http_basic_auth_argon2id_memory_cost": { + "type": "integer", + "minimum": 8192, + "maximum": 262144, + "description": "Argon2id memory cost in KiB for the HTTP Basic Authentication password." + }, + "http_basic_auth_argon2id_time_cost": { + "type": "integer", + "minimum": 1, + "maximum": 12, + "description": "Argon2id time cost (iterations) for the HTTP Basic Authentication password." + }, "settings": { "$ref": "#\/components\/schemas\/ApplicationSetting" } diff --git a/openapi.yaml b/openapi.yaml index dc2b3aebf9..cfc334c844 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -645,6 +645,29 @@ paths: type: string nullable: true description: 'Password for HTTP Basic Authentication' + http_basic_auth_hash_algorithm: + type: string + enum: [bcrypt, argon2id] + default: bcrypt + description: 'Hash algorithm for the HTTP Basic Authentication password. Argon2id needs the Caddy proxy, version 2.11 or newer.' + http_basic_auth_bcrypt_cost: + type: integer + minimum: 4 + maximum: 14 + default: 10 + description: 'Bcrypt cost for the HTTP Basic Authentication password.' + http_basic_auth_argon2id_memory_cost: + type: integer + minimum: 8192 + maximum: 262144 + default: 65536 + description: 'Argon2id memory cost in KiB for the HTTP Basic Authentication password.' + http_basic_auth_argon2id_time_cost: + type: integer + minimum: 1 + maximum: 12 + default: 4 + description: 'Argon2id time cost (iterations) for the HTTP Basic Authentication password.' connect_to_docker_network: type: boolean description: 'The flag to connect the service to the predefined Docker network.' @@ -1031,6 +1054,29 @@ paths: type: string nullable: true description: 'Password for HTTP Basic Authentication' + http_basic_auth_hash_algorithm: + type: string + enum: [bcrypt, argon2id] + default: bcrypt + description: 'Hash algorithm for the HTTP Basic Authentication password. Argon2id needs the Caddy proxy, version 2.11 or newer.' + http_basic_auth_bcrypt_cost: + type: integer + minimum: 4 + maximum: 14 + default: 10 + description: 'Bcrypt cost for the HTTP Basic Authentication password.' + http_basic_auth_argon2id_memory_cost: + type: integer + minimum: 8192 + maximum: 262144 + default: 65536 + description: 'Argon2id memory cost in KiB for the HTTP Basic Authentication password.' + http_basic_auth_argon2id_time_cost: + type: integer + minimum: 1 + maximum: 12 + default: 4 + description: 'Argon2id time cost (iterations) for the HTTP Basic Authentication password.' connect_to_docker_network: type: boolean description: 'The flag to connect the service to the predefined Docker network.' @@ -1417,6 +1463,29 @@ paths: type: string nullable: true description: 'Password for HTTP Basic Authentication' + http_basic_auth_hash_algorithm: + type: string + enum: [bcrypt, argon2id] + default: bcrypt + description: 'Hash algorithm for the HTTP Basic Authentication password. Argon2id needs the Caddy proxy, version 2.11 or newer.' + http_basic_auth_bcrypt_cost: + type: integer + minimum: 4 + maximum: 14 + default: 10 + description: 'Bcrypt cost for the HTTP Basic Authentication password.' + http_basic_auth_argon2id_memory_cost: + type: integer + minimum: 8192 + maximum: 262144 + default: 65536 + description: 'Argon2id memory cost in KiB for the HTTP Basic Authentication password.' + http_basic_auth_argon2id_time_cost: + type: integer + minimum: 1 + maximum: 12 + default: 4 + description: 'Argon2id time cost (iterations) for the HTTP Basic Authentication password.' connect_to_docker_network: type: boolean description: 'The flag to connect the service to the predefined Docker network.' @@ -1744,6 +1813,29 @@ paths: type: string nullable: true description: 'Password for HTTP Basic Authentication' + http_basic_auth_hash_algorithm: + type: string + enum: [bcrypt, argon2id] + default: bcrypt + description: 'Hash algorithm for the HTTP Basic Authentication password. Argon2id needs the Caddy proxy, version 2.11 or newer.' + http_basic_auth_bcrypt_cost: + type: integer + minimum: 4 + maximum: 14 + default: 10 + description: 'Bcrypt cost for the HTTP Basic Authentication password.' + http_basic_auth_argon2id_memory_cost: + type: integer + minimum: 8192 + maximum: 262144 + default: 65536 + description: 'Argon2id memory cost in KiB for the HTTP Basic Authentication password.' + http_basic_auth_argon2id_time_cost: + type: integer + minimum: 1 + maximum: 12 + default: 4 + description: 'Argon2id time cost (iterations) for the HTTP Basic Authentication password.' connect_to_docker_network: type: boolean description: 'The flag to connect the service to the predefined Docker network.' @@ -2057,6 +2149,29 @@ paths: type: string nullable: true description: 'Password for HTTP Basic Authentication' + http_basic_auth_hash_algorithm: + type: string + enum: [bcrypt, argon2id] + default: bcrypt + description: 'Hash algorithm for the HTTP Basic Authentication password. Argon2id needs the Caddy proxy, version 2.11 or newer.' + http_basic_auth_bcrypt_cost: + type: integer + minimum: 4 + maximum: 14 + default: 10 + description: 'Bcrypt cost for the HTTP Basic Authentication password.' + http_basic_auth_argon2id_memory_cost: + type: integer + minimum: 8192 + maximum: 262144 + default: 65536 + description: 'Argon2id memory cost in KiB for the HTTP Basic Authentication password.' + http_basic_auth_argon2id_time_cost: + type: integer + minimum: 1 + maximum: 12 + default: 4 + description: 'Argon2id time cost (iterations) for the HTTP Basic Authentication password.' connect_to_docker_network: type: boolean description: 'The flag to connect the service to the predefined Docker network.' @@ -2429,6 +2544,25 @@ paths: use_build_secrets: type: boolean description: 'Use Docker Build Secrets for build-time environment variables.' + http_basic_auth_hash_algorithm: + type: string + enum: [bcrypt, argon2id] + description: 'Hash algorithm for the HTTP Basic Authentication password. Argon2id needs the Caddy proxy, version 2.11 or newer.' + http_basic_auth_bcrypt_cost: + type: integer + minimum: 4 + maximum: 14 + description: 'Bcrypt cost for the HTTP Basic Authentication password.' + http_basic_auth_argon2id_memory_cost: + type: integer + minimum: 8192 + maximum: 262144 + description: 'Argon2id memory cost in KiB for the HTTP Basic Authentication password.' + http_basic_auth_argon2id_time_cost: + type: integer + minimum: 1 + maximum: 12 + description: 'Argon2id time cost (iterations) for the HTTP Basic Authentication password.' is_git_submodules_enabled: type: boolean description: 'Clone Git submodules.' @@ -15009,6 +15143,27 @@ components: type: string nullable: true description: 'Password for HTTP Basic Authentication' + http_basic_auth_hash_algorithm: + type: string + enum: + - bcrypt + - argon2id + description: 'Hash algorithm for the HTTP Basic Authentication password. Argon2id needs the Caddy proxy, version 2.11 or newer.' + http_basic_auth_bcrypt_cost: + type: integer + minimum: 4 + maximum: 14 + description: 'Bcrypt cost for the HTTP Basic Authentication password.' + http_basic_auth_argon2id_memory_cost: + type: integer + minimum: 8192 + maximum: 262144 + description: 'Argon2id memory cost in KiB for the HTTP Basic Authentication password.' + http_basic_auth_argon2id_time_cost: + type: integer + minimum: 1 + maximum: 12 + description: 'Argon2id time cost (iterations) for the HTTP Basic Authentication password.' settings: $ref: '#/components/schemas/ApplicationSetting' type: object diff --git a/resources/views/livewire/project/application/general.blade.php b/resources/views/livewire/project/application/general.blade.php index 1df2bedfb2..45e03854a3 100644 --- a/resources/views/livewire/project/application/general.blade.php +++ b/resources/views/livewire/project/application/general.blade.php @@ -1,3 +1,4 @@ +@php use App\Enums\HttpBasicAuthHashAlgorithm; use App\Enums\ProxyTypes; @endphp
+ @php + $server = $application->destination->server; + $supportsArgon2id = $server->caddySupportsArgon2idBasicAuth(); + $usesArgon2id = $httpBasicAuthHashAlgorithm === HttpBasicAuthHashAlgorithm::ARGON2ID->value; + @endphp +