From 02f7aa37e4e013b70572c239ea6a2c6f4f6465b4 Mon Sep 17 00:00:00 2001 From: Iskren Hadzhinedev Date: Fri, 29 Mar 2024 17:10:37 +0200 Subject: [PATCH 1/2] Add ixp-manager:setup-wizard command for easier installation --- app/Console/Commands/SetupWizard.php | 212 +++++++++++++++++++++++++++ 1 file changed, 212 insertions(+) create mode 100644 app/Console/Commands/SetupWizard.php diff --git a/app/Console/Commands/SetupWizard.php b/app/Console/Commands/SetupWizard.php new file mode 100644 index 000000000..3804a54f4 --- /dev/null +++ b/app/Console/Commands/SetupWizard.php @@ -0,0 +1,212 @@ + + * @package IXP\Console\Commands + * @copyright Copyright (C) 2009 - 2024 Internet Neutral Exchange Association Company Limited By Guarantee + * @license http://www.gnu.org/licenses/gpl-2.0.html GNU GPL V2.0 + */ +class SetupWizard extends Command +{ + /** + * The name and signature of the console command. + * + * @var string + */ + protected $signature = 'ixp-manager:setup-wizard' + . ' {--N|name= : The name of the admin user}' + . ' {--U|username= : The username of the admin user}' + . ' {--E|email= : The email of the admin user}' + . ' {--A|asn= : The ASN of your IXP}' + . ' {--I|infrastructure= : The name of your primary infrastructure}' + . ' {--C|company-name= : The name of your company}'; + + /** + * The console command description. + * @var string + */ + protected $description = "Run initial setup for IXP Manager"; + + /** + * Execute the console command. + * + * @return int + * + * @throws + */ + public function handle(): int + { + if (Customer::count() > 0) { + $this->error('IXP Manager has already been setup. Exiting.'); + return 1; + } + + $this->info('Starting the setup wizard...'); + $data = $this->populateData(); + + $passhash = password_hash($data['password'], PASSWORD_BCRYPT, ['cost' => 10]); + + try { + DB::transaction(function () use ($data, $passhash) { + + Infrastructure::create([ + 'name' => $data['infrastructure'], + 'shortname' => $data['infrastructure'], + 'isPrimary' => 1, + 'created_at' => Carbon::now(), + 'updated_at' => Carbon::now(), + ]); + + $billingDetail = CompanyBillingDetail::create([ + 'billingContatName' => $data['name'], + 'billingEmail' => config('identity.billing_email', $data['email']), + 'invoiceMethod' => CompanyBillingDetail::INVOICE_METHOD_EMAIL, + 'billingFrequency' => CompanyBillingDetail::BILLING_FREQUENCY_NOBILLING, + 'created_at' => Carbon::now(), + 'updated_at' => Carbon::now(), + ]); + + $registrationDetail = CompanyRegisteredDetail::create([ + 'registeredName' => $data['company_name'], + 'created_at' => Carbon::now(), + 'updated_at' => Carbon::now(), + ]); + + $cust = Customer::create([ + 'name' => $data['company_name'], + 'type' => 3, + 'shortname' => $data['company_name'], + 'autsys' => $data['asn'], + 'maxprefixes' => 100, + 'peeringemail' => $data['email'], + 'peeringpolicy' => 'mandatory', + 'nocphone' => config('identity.support_phone', '+1 555 555 5555'), + 'noc24hphone' => config('identity.support_phone', '+1 555 555 5555'), + 'nocemail' => config('identity.support_email', $data['email']), + 'nochours' => config('identity.support_hours', '24/7'), + 'nocwww' => config('app.url', 'http://example.com'), + 'corpwww' => config('identity.corporate_url', 'http://example.com'), + 'datejoin' => Carbon::now(), + 'status' => 1, + 'activepeeringmatrix' => 1, + 'company_registered_detail_id' => $registrationDetail->id, + 'company_billing_details_id' => $billingDetail->id, + 'abbreviatedName' => $data['company_name'], + 'isReseller' => 0, + 'created_at' => Carbon::now(), + 'updated_at' => Carbon::now(), + ]); + + $cust->contacts()->create([ + 'name' => $data['name'], + 'email' => $data['email'], + 'created_at' => Carbon::now(), + 'updated_at' => Carbon::now(), + ]); + + $user = new User; + $user->name = $data['name']; + $user->username = $data['username']; + $user->password = $passhash; + $user->email = $data['email']; + $user->privs = User::AUTH_SUPERUSER; + $user->disabled = 0; + $user->creator = 'IXP Manager setup wizard'; + $user->created_at = Carbon::now(); + $user->updated_at = Carbon::now(); + + $user->save(); + $user->customer()->associate($cust); + $user->customers()->attach($cust->id); + $user->currentCustomerToUser()->update(['privs' => User::AUTH_SUPERUSER]); + }); + } + catch (\Exception $e) { + $this->error('A database error occurred while setting up IXP Manager:' . $e->getMessage()); + return 2; + } + + return 0; + } + + protected function populateData(): array + { + + $data = [ + "asn" => $this->option('asn') ?? $this->ask('Enter the ASN of your IXP'), + "company_name" => $this->option('company-name') ?? $this->ask('Enter the name of your company'), + "infrastructure" => $this->option('infrastructure') ?? $this->ask('Enter the name of your primary infrastructure'), + "name" => $this->option('name') ?? $this->ask('Enter the full name(s) of the admin user'), + "username" => $this->option('username') ?? $this->ask('Enter the username of the admin user'), + "email" => $this->option('email') ?? $this->ask('Enter the email of the admin user'), + "password" => $this->secret('Enter the password of the admin user'), + ]; + if ($data['password'] !== $this->secret('Confirm the password of the admin user')) { + $this->error('Passwords do not match. Exiting.'); + exit(1); + } + + $passwordRules = Password::min(8) + ->mixedCase() + ->numbers() + ->symbols() + ->uncompromised(); + $validator = \Validator::make($data, [ + 'asn' => 'required|integer|between:1,4294967295', + 'company_name' => 'required|string', + 'infrastructure' => 'required|string', + 'name' => 'required|string', + 'username' => 'required|string', + 'email' => 'required|email', + 'password' => ['required', 'string', $passwordRules], + ]); + + if ($validator->fails()) { + $this->error('The following errors occurred:'); + foreach ($validator->errors()->all() as $error) { + $this->error("\t" . $error); + } + exit(2); + } + return $data; + + } +} \ No newline at end of file From 6e65ab32231a11e912fdbb4752b56498d572ae4d Mon Sep 17 00:00:00 2001 From: Iskren Hadzhinedev Date: Fri, 29 Mar 2024 17:57:57 +0200 Subject: [PATCH 2/2] Use OS/SAPI envvar for providing the password non-interactively --- app/Console/Commands/SetupWizard.php | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/app/Console/Commands/SetupWizard.php b/app/Console/Commands/SetupWizard.php index 3804a54f4..e5e7b7b6b 100644 --- a/app/Console/Commands/SetupWizard.php +++ b/app/Console/Commands/SetupWizard.php @@ -170,6 +170,12 @@ public function handle(): int protected function populateData(): array { + /* Do not use laravel's `env()` because it reads the .env file. + * Instead explicitly require the variable to be set from the OS or the SAPI to avoid someone accidentally leaving it set in the .env file. + */ + $envPassword = getenv('IXP_SETUP_ADMIN_PASSWORD'); + putenv('IXP_SETUP_ADMIN_PASSWORD'); // Unset the variable as soon as we read it to reduce the risk of it leaking. + $data = [ "asn" => $this->option('asn') ?? $this->ask('Enter the ASN of your IXP'), "company_name" => $this->option('company-name') ?? $this->ask('Enter the name of your company'), @@ -177,9 +183,9 @@ protected function populateData(): array "name" => $this->option('name') ?? $this->ask('Enter the full name(s) of the admin user'), "username" => $this->option('username') ?? $this->ask('Enter the username of the admin user'), "email" => $this->option('email') ?? $this->ask('Enter the email of the admin user'), - "password" => $this->secret('Enter the password of the admin user'), + "password" => $envPassword === false ? $this->secret('Enter the password of the admin user') : $envPassword, ]; - if ($data['password'] !== $this->secret('Confirm the password of the admin user')) { + if ($envPassword === false && $data['password'] !== $this->secret('Confirm the password of the admin user')) { $this->error('Passwords do not match. Exiting.'); exit(1); }