diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5533981f..b39de9a2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -8,6 +8,7 @@ on: - '.test/**' - 'client/**' - 'server/**' + - 'dist/**' - 'Makefile.PL' permissions: @@ -75,15 +76,39 @@ jobs: done exit 1 - - name: Set up TLS certificates - run: sudo .test/tls-setup.sh + - name: Generate test credentials + run: | + DB_PW=$(openssl rand -base64 24) + ROOT_PW=$(openssl rand -base64 24) + echo "NICTOOL_DB_USER_PASSWORD=$DB_PW" >> "$GITHUB_ENV" + echo "DB_ROOT_PASSWORD=root" >> "$GITHUB_ENV" + echo "NICTOOL_DB_USER=nictool" >> "$GITHUB_ENV" + echo "NICTOOL_DB_NAME=nictool" >> "$GITHUB_ENV" + echo "DB_ENGINE=mysql" >> "$GITHUB_ENV" + echo "DB_HOSTNAME=localhost" >> "$GITHUB_ENV" + echo "ROOT_USER_EMAIL=ci@nictool.test" >> "$GITHUB_ENV" + echo "ROOT_USER_PASSWORD=$ROOT_PW" >> "$GITHUB_ENV" + echo "NICTOOL_CLIENT_DIR=$GITHUB_WORKSPACE/client" >> "$GITHUB_ENV" + echo "DB_SSL=1" >> "$GITHUB_ENV" + + - name: Allow Apache to traverse workspace path + run: | + # Ubuntu 21.04+ defaults home dirs to 750; www-data needs o+x to traverse + dir="$GITHUB_WORKSPACE" + while [ "$dir" != "/" ]; do + sudo chmod o+x "$dir" + dir="$(dirname "$dir")" + done + + - name: Set up NicTool configs, Apache, and TLS + run: sudo -E dist/setup/install-nictool.sh --nt-dir="$GITHUB_WORKSPACE" - name: Install Perl modules for Apache/mod_perl run: | - # Install modules not available as apt packages sudo cpanm --notest \ CryptX Crypt::Mac::HMAC Crypt::KeyDerivation \ Test::HTML::Lint Time::TAI64 DBD::MariaDB + - name: Install NicTool client and server run: | cd "$GITHUB_WORKSPACE/client" @@ -94,25 +119,16 @@ jobs: perl Makefile.PL sudo cpanm -n . - - name: Configure Apache for NicTool - run: | - client_conf="$RUNNER_TEMP/nictoolclient.conf" - server_conf="$RUNNER_TEMP/nictoolserver.conf" - - sed "s|/home/NicTool|$GITHUB_WORKSPACE|g" .test/nictoolclient.conf > "$client_conf" - sed "s|/home/NicTool|$GITHUB_WORKSPACE|g" .test/nictoolserver.conf > "$server_conf" - - sed \ - -e "s|/home/NicTool/.test/nictoolclient.conf|$client_conf|g" \ - -e "s|/home/NicTool/.test/nictoolserver.conf|$server_conf|g" \ - -e "s|/home/NicTool|$GITHUB_WORKSPACE|g" \ - .test/apache-ci.conf | sudo tee /etc/apache2/sites-enabled/nictool.conf > /dev/null - - name: Restart Apache run: sudo service apache2 restart || sudo cat /var/log/apache2/error.log - name: Create NicTool test database - run: perl .test/create_tables.pl + run: | + cd "$GITHUB_WORKSPACE/server/sql" + echo "" | perl create_tables.pl --environment + + - name: Create test user and test.cfg + run: perl dist/setup/setup-test-env.pl - name: Run client tests run: make -C "$GITHUB_WORKSPACE/client" test @@ -128,4 +144,4 @@ jobs: apache2 -version sudo cat /var/log/apache2/error.log || true cat /etc/apache2/apache2.conf || true - ls /usr/lib/apache2/modules/ || true \ No newline at end of file + ls /usr/lib/apache2/modules/ || true diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml new file mode 100644 index 00000000..0e705064 --- /dev/null +++ b/.github/workflows/docker.yml @@ -0,0 +1,134 @@ +name: Docker + +on: + pull_request: + types: [opened, synchronize, reopened] + paths: + - '.github/workflows/docker.yml' + - 'client/**' + - 'server/**' + - 'dist/**' + +permissions: + contents: read + +jobs: + build: + runs-on: ubuntu-22.04 + steps: + - uses: actions/checkout@v6 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Build Docker image + uses: docker/build-push-action@v6 + with: + context: . + file: dist/docker/Dockerfile + tags: docker-web:latest + cache-from: type=gha + cache-to: type=gha,mode=max + + docker-tests: + needs: build + runs-on: ubuntu-22.04 + steps: + - uses: actions/checkout@v6 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Load Docker image from cache + uses: docker/build-push-action@v6 + with: + context: . + file: dist/docker/Dockerfile + tags: docker-web:latest + load: true + cache-from: type=gha + + - name: Generate credentials + run: | + dist/docker/generate-env.sh + echo "NICTOOL_TEST_PASSWORD=$(sed -n 's/^ROOT_USER_PASSWORD=//p' dist/docker/.env)" >> "$GITHUB_ENV" + + - name: Start containers + working-directory: dist/docker + run: docker compose up -d --wait + timeout-minutes: 5 + + - name: Run server tests + run: docker compose -f dist/docker/docker-compose.yml exec -T web make -C /usr/local/nictool/server test + + - name: Run client tests + run: docker compose -f dist/docker/docker-compose.yml exec -T web make -C /usr/local/nictool/client test + + - name: Failure diagnostics + if: failure() + run: | + docker compose -f dist/docker/docker-compose.yml logs web || true + docker compose -f dist/docker/docker-compose.yml logs db || true + + e2e-tests: + needs: build + runs-on: ubuntu-22.04 + steps: + - uses: actions/checkout@v6 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Load Docker image from cache + uses: docker/build-push-action@v6 + with: + context: . + file: dist/docker/Dockerfile + tags: docker-web:latest + load: true + cache-from: type=gha + + - name: Generate credentials + run: | + dist/docker/generate-env.sh + echo "NICTOOL_TEST_PASSWORD=$(sed -n 's/^ROOT_USER_PASSWORD=//p' dist/docker/.env)" >> "$GITHUB_ENV" + + - name: Start containers + working-directory: dist/docker + run: docker compose up -d --wait + timeout-minutes: 5 + + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: 22 + cache: npm + cache-dependency-path: client/t/e2e/package-lock.json + + - name: Cache Playwright browsers + uses: actions/cache@v4 + id: playwright-cache + with: + path: ~/.cache/ms-playwright + key: playwright-${{ hashFiles('client/t/e2e/package-lock.json') }} + + - name: Install E2E dependencies + working-directory: client/t/e2e + run: | + npm ci + npx playwright install --with-deps chromium + + - name: Run E2E tests + working-directory: client/t/e2e + env: + NICTOOL_URL: http://localhost:8080 + NICTOOL_TEST_PASSWORD: ${{ env.NICTOOL_TEST_PASSWORD }} + DEBUG: pw:api + run: npx playwright test --reporter=list + + - name: Failure diagnostics + if: failure() + run: | + docker compose -f dist/docker/docker-compose.yml logs web || true + docker compose -f dist/docker/docker-compose.yml logs db || true + docker compose -f dist/docker/docker-compose.yml exec -T web cat /var/log/apache2/error.log 2>/dev/null || true diff --git a/.gitignore b/.gitignore index f2ef6fd8..b66cac80 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,6 @@ client/bin/do_build_test.sh server/bin/do_build_test.sh client/lib/nictoolclient.conf server/lib/nictoolserver.conf +dist/docker/.env +server/t/test.cfg +server/api/t/test.cfg diff --git a/.test/apache-ci.conf b/.test/apache-ci.conf deleted file mode 100644 index 0bfa35b0..00000000 --- a/.test/apache-ci.conf +++ /dev/null @@ -1,58 +0,0 @@ -PerlRequire /home/NicTool/.test/nictoolclient.conf - - - # force a https connection - ServerName dns.example.net - Redirect / https://localhost/ - - -#Listen 443 - - ServerName workflows.github.com - Alias /images/ "/home/NicTool/client/htdocs/images/" - DocumentRoot /home/NicTool/client/htdocs - DirectoryIndex index.cgi - SSLEngine on - SSLCertificateFile /etc/ssl/certs/server.crt - SSLCertificateKeyFile /etc/ssl/private/server.key - - - SetHandler perl-script - PerlResponseHandler ModPerl::Registry - PerlOptions +ParseHeaders - Options +ExecCGI - - - - AllowOverride None - Order allow,deny - Require all granted - - - - - PerlFreshRestart On - -PerlTaintCheck Off - -Listen 8082 - -PerlRequire /home/NicTool/.test/nictoolserver.conf - - - KeepAlive Off - - SetHandler perl-script - PerlResponseHandler NicToolServer - - - SetHandler perl-script - PerlResponseHandler Apache::SOAP - PerlSetVar dispatch_to "/home/NicTool/server, NicToolServer::SOAP" - - - AllowOverride None - Order allow,deny - Require all granted - - diff --git a/.test/apache-matt.conf b/.test/apache-matt.conf index 4ee7d4ce..0b4d3d13 100644 --- a/.test/apache-matt.conf +++ b/.test/apache-matt.conf @@ -1,7 +1,30 @@ -PerlRequire /Users/matt/git/nictool/client/lib/nictoolclient.conf +# Matt's local dev config — macOS with NicTool at /Users/matt/git/nictool +# +# Usage: +# export NICTOOL_DB_USER=nictool NICTOOL_DB_USER_PASSWORD= +# export NICTOOL_CLIENT_DIR=/Users/matt/git/nictool/client +# sudo cp .test/apache-matt.conf /etc/apache2/sites-enabled/nictool.conf +# sudo apachectl restart +# +# Or generate from the shared template instead: +# NT_DIR=/Users/matt/git/nictool dist/setup/install-nictool.sh + +PerlPassEnv DB_ENGINE +PerlPassEnv DB_HOSTNAME +PerlPassEnv DB_PORT +PerlPassEnv DB_SSL +PerlPassEnv NICTOOL_DB_NAME +PerlPassEnv NICTOOL_DB_USER +PerlPassEnv NICTOOL_DB_USER_PASSWORD +PerlPassEnv NICTOOL_CLIENT_DIR +PerlPassEnv NICTOOL_SERVER_HOST +PerlPassEnv NICTOOL_SERVER_PORT +PerlPassEnv NICTOOL_SERVER_PROTOCOL +PerlPassEnv NICTOOL_DATA_PROTOCOL + +PerlRequire "/Users/matt/git/nictool/client/lib/nictoolclient.conf" - # force a https connection ServerName localhost.simerson.net Redirect / https://localhost.simerson.net/ @@ -10,7 +33,7 @@ Listen 443 ServerName localhost.simerson.net Alias /images/ "/Users/matt/git/nictool/client/htdocs/images/" - DocumentRoot /Users/matt/git/nictool/client/htdocs + DocumentRoot "/Users/matt/git/nictool/client/htdocs" DirectoryIndex index.cgi SSLEngine on SSLCertificateFile /etc/ssl/certs/server.crt @@ -25,10 +48,9 @@ Listen 443 AllowOverride None - Order allow,deny - Allow from all + Require all granted - + PerlFreshRestart On @@ -37,7 +59,7 @@ PerlTaintCheck Off Listen 8082 -PerlRequire /Users/matt/git/nictool/server/lib/nictoolserver.conf +PerlRequire "/Users/matt/git/nictool/server/lib/nictoolserver.conf" KeepAlive Off @@ -50,4 +72,8 @@ PerlRequire /Users/matt/git/nictool/server/lib/nictoolserver.conf PerlResponseHandler Apache::SOAP PerlSetVar dispatch_to "/Users/matt/git/nictool/server, NicToolServer::SOAP" + + AllowOverride None + Require all granted + diff --git a/.test/create_tables.pl b/.test/create_tables.pl deleted file mode 100755 index 3f61f54d..00000000 --- a/.test/create_tables.pl +++ /dev/null @@ -1,140 +0,0 @@ -#!/usr/bin/perl - -use strict; -use Crypt::KeyDerivation; -use DBI; -use English; -$|++; - -my $db_name = 'nictool'; -my $db_user = 'nictool'; -my $db_pass = 'lootcin!mysql'; -my $db_host = 'localhost'; -my $db_root_pw = $ENV{MYSQL_ROOT_PASSWORD} // 'root'; - -my $dbh = get_dbh(); - -my $nt_root_email = 'workflows@github.com'; -my $salt = _get_salt(16); -my $pass_hash = unpack( "H*", Crypt::KeyDerivation::pbkdf2( $db_pass, $salt, 5000, 'SHA512' ) ); - -print qq{\n -Beginning table creation. -------------------------- -DATABASE DSN: mysql://$db_user\@$db_host/$db_name -host: $db_host -db : $db_name -user: $db_user - *** the DSN info must match the settings in nictoolserver.conf! *** - -NICTOOL LOGIN: https://$db_host/index.cgi -user : nictest -salt : $salt -pass : encrypted as: $pass_hash -email: $nt_root_email -------------------------- -}; - -# Create database and initial privileges -$dbh->do("DROP DATABASE IF EXISTS $db_name"); -$dbh->do("CREATE DATABASE $db_name"); - -my %user_hosts = map { $_ => 1 } ( $db_host, '127.0.0.1', '::1' ); - -for my $host ( sort keys %user_hosts ) { - my $db_user_host = "'$db_user'\@'$host'"; - $dbh->do("DROP USER IF EXISTS $db_user_host"); - - my $created_user = eval { - $dbh->do("CREATE USER $db_user_host IDENTIFIED WITH mysql_native_password BY '$db_pass'"); - 1; - }; - - if ( !$created_user ) { - $dbh->do("CREATE USER $db_user_host IDENTIFIED BY '$db_pass'"); - } - - $dbh->do("GRANT ALL PRIVILEGES ON $db_name.* TO $db_user_host"); -} - -$dbh->do("USE $db_name"); - -my @sql_files = get_sql_files(); -foreach my $sql (@sql_files) { - open( my $fh, '<', $sql ) or die "failed to open $sql for read: $!"; - print "\nopened $sql\n"; - my $q_string = join( ' ', grep {/^[^#]/} grep {/[\S]/} <$fh> ); - foreach my $q ( split( ';', $q_string ) ) { # split string into queries - next if $q !~ /[\S]/; # skip blank entries - print "$q;"; # show the query - $dbh->do($q) or die $DBI::errstr; # run it! - } - close $fh; - print "\n"; -} - -$dbh->do("INSERT INTO `nt_group` VALUES (2,1,'test_group', 0)"); -$dbh->do( - "INSERT INTO `nt_group_log` VALUES - (2,1,1,'added',1487651312,2,1,'test_group')" -); -$dbh->do( - "INSERT INTO `nt_group_subgroups` VALUES - (1,2,1000);" -); -$dbh->do( - "INSERT INTO `nt_perm` VALUES - (2,2,NULL,NULL,NULL,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,'1,2,3',0);" -); -$dbh->do( - "INSERT INTO `nt_user` VALUES - (1,1,'Root','User','root','$pass_hash','$salt','$nt_root_email',NULL,0), - (2,2,'TestFirst','TestLast','nictest','09afe1013ec0a14793df1317a8e5f28f5ee84cc9758f50a19cb6154857e07ffe',']]l7./*4,8]wvBbo','test\@example.com',NULL,0)" -); -$dbh->disconnect; -print "\n"; - -sub get_dbh { - print " -######################################################################### - Administrator DSN (database connection settings) -#########################################################################\n"; - - my $dbh = DBI->connect( - "dbi:mysql:host=$db_host", - 'root', - $db_root_pw, - { mysql_ssl => 1, - ChopBlanks => 1, - } - ) or die $DBI::errstr; - - return $dbh; -} - -sub get_sql_files { - my @r; - opendir( DIR, 'server/sql' ) || die "unable to open dir: $!\n"; - foreach my $file ( sort readdir(DIR) ) { - next if /^\./; - next if -d "server/sql/$file"; - next if $file !~ /\.sql$/; - push @r, 'server/sql/' . $file; - } - close DIR; - if ( scalar @r < 8 ) { - die "didn't find *.sql files. Are you running this in the sql dir?\n"; - } - return @r; -} - -sub _get_salt { - my $self = shift; - my $length = shift || 16; - my $chars = join( '', map chr, 40 .. 126 ); # ASCII 40-126 - my $salt; - for ( 0 .. ( $length - 1 ) ) { - $salt .= substr( $chars, rand( ( length $chars ) - 1 ), 1 ); - } - return $salt; -} diff --git a/.test/freshtest.sh b/.test/freshtest.sh index 6da58ff9..53487be4 100755 --- a/.test/freshtest.sh +++ b/.test/freshtest.sh @@ -1,6 +1,9 @@ #!/bin/sh -perl .test/create_tables.pl \ +cd server/sql \ + && echo "" | perl create_tables.pl --environment \ + && cd ../.. \ + && perl dist/setup/setup-test-env.pl \ && cd client \ && perl Makefile.PL \ && sudo make install \ diff --git a/.test/install-nictool.sh b/.test/install-nictool.sh deleted file mode 100755 index ef27a1b2..00000000 --- a/.test/install-nictool.sh +++ /dev/null @@ -1,13 +0,0 @@ -#!/bin/sh - -NT_INSTALL_DIR=${NT_INSTALL_DIR:="/usr/local/nictool"} - -if [ ! -d "$NT_INSTALL_DIR" ]; then - mkdir -p "$NT_INSTALL_DIR" || exit -fi - -cp .test/nictoolserver.conf server/lib/nictoolserver.conf -cp .test/nictoolclient.conf client/lib/nictoolclient.conf - -cp -r server "$NT_INSTALL_DIR/" -cp -r client "$NT_INSTALL_DIR/" diff --git a/.test/nictoolclient.conf b/.test/nictoolclient.conf deleted file mode 100644 index f9643cab..00000000 --- a/.test/nictoolclient.conf +++ /dev/null @@ -1,48 +0,0 @@ -#!/usr/bin/perl - -use strict; - -use CGI(); - -BEGIN { - $NicToolClient::app_dir = '/home/NicTool/client'; - $NicToolClient::app_title = 'NicTool'; - $NicToolClient::image_dir = 'images'; - $NicToolClient::generic_error_message = qq(If you get this error, please contact the system administrator.); - $NicToolClient::show_help_links = 1; - $NicToolClient::edit_after_new_zone = 1; - $NicToolClient::include_subgroups_checked = 1; - $NicToolClient::exact_match_checked = 0; - - $NicToolClient::template_dir = "$NicToolClient::app_dir/templates"; - $NicToolClient::login_template = "$NicToolClient::template_dir/login.html"; - $NicToolClient::setup_error_template = "$NicToolClient::template_dir/setup_error.html"; - $NicToolClient::frameset_template = "$NicToolClient::template_dir/frameset.html"; - $NicToolClient::start_html_template = "$NicToolClient::template_dir/start_html.html"; - $NicToolClient::end_html_template = "$NicToolClient::template_dir/end_html.html"; - $NicToolClient::body_frame_start_template = "$NicToolClient::template_dir/body_frame_start.html"; - - $NicToolClient::page_length = 50; - - $NicToolClient::default_zone_ttl = '86400'; - $NicToolClient::default_zone_mailaddr = 'hostmaster.ZONE.TLD.'; - $NicToolClient::default_zone_refresh= '16384'; # RFC 1912 range (20 min to 12 hours) - $NicToolClient::default_zone_retry = '900'; # RFC 1912 range (180-900 sec) - $NicToolClient::default_zone_expire = '1048576'; # RFC 1912 range (14 - 28 days) - $NicToolClient::default_zone_minimum = '2560'; # RFC 2308 range (1 - 3 hours) - - $NicToolClient::default_zone_record_ttl = '86400'; - $NicToolClient::default_nameserver_ttl = '86400'; - - $NicToolServerAPI::server_host = "localhost"; - $NicToolServerAPI::server_port = "8082"; - $NicToolServerAPI::transfer_protocol = 'http', - $NicToolServerAPI::data_protocol = "soap"; - $NicToolServerAPI::debug_soap_setup = 0; - $NicToolServerAPI::debug_soap_request = 0; - $NicToolServerAPI::debug_soap_response = 0; -} - -use NicToolClient; - -1; diff --git a/.test/nictoolserver.conf b/.test/nictoolserver.conf deleted file mode 100644 index 830e7a65..00000000 --- a/.test/nictoolserver.conf +++ /dev/null @@ -1,38 +0,0 @@ -#!/usr/bin/perl - -use Apache::DBI(); -use Apache::SOAP; -use DBIx::Simple; -use XML::Parser; -use SOAP::Lite; - -use strict; - -use NicToolServer; -use NicToolServer::SOAP; -use NicToolServer::Client::SOAP; -use NicToolServer::Client; -use NicToolServer::Session; -use NicToolServer::Response; -use NicToolServer::Permission; -use NicToolServer::Zone; -use NicToolServer::Zone::Sanity; -use NicToolServer::Zone::Record; -use NicToolServer::Zone::Record::Sanity; -use NicToolServer::Group; -use NicToolServer::Group::Sanity; -use NicToolServer::User; -use NicToolServer::User::Sanity; -use NicToolServer::Nameserver; -use NicToolServer::Nameserver::Sanity; - -BEGIN { - # Database configuration - $NicToolServer::dsn = "DBI:mysql:database=nictool;host=localhost;port=3306;mysql_ssl=1;"; - $NicToolServer::db_user = 'nictool'; - $NicToolServer::db_pass = 'lootcin!mysql'; - - Apache::DBI->connect_on_init($NicToolServer::dsn, $NicToolServer::db_user, $NicToolServer::db_pass); -} - -1; diff --git a/client/lib/nictoolclient.conf.dist b/client/lib/nictoolclient.conf.dist index cf36aa38..647a69fb 100644 --- a/client/lib/nictoolclient.conf.dist +++ b/client/lib/nictoolclient.conf.dist @@ -3,12 +3,12 @@ # NicTool v2.00-rc1 Copyright 2001 Damon Edwards, Abe Shelton & Greg Schueler # NicTool v2.01 Copyright 2004 The Network People, Inc. # -# NicTool is free software; you can redistribute it and/or modify it under -# the terms of the Affero General Public License as published by Affero, +# NicTool is free software; you can redistribute it and/or modify it under +# the terms of the Affero General Public License as published by Affero, # Inc.; either version 1 of the License, or any later version. # -# NicTool is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +# NicTool is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY # or FITNESS FOR A PARTICULAR PURPOSE. See the Affero GPL for details. # # You should have received a copy of the Affero General Public License @@ -21,14 +21,15 @@ use strict; use CGI(); BEGIN { - $NicToolClient::app_dir = '/usr/local/nictool/client'; + $NicToolClient::app_dir = $ENV{NICTOOL_CLIENT_DIR} || '/usr/local/nictool/client'; #Interface options - $NicToolClient::app_title = 'NicTool'; + $NicToolClient::app_title = 'NicTool'; - $NicToolClient::image_dir = 'images'; + $NicToolClient::image_dir = 'images'; - $NicToolClient::generic_error_message = qq(If you continue to get this error, please contact the system administrator, or your corporate contact.); + $NicToolClient::generic_error_message = + qq(If you continue to get this error, please contact the system administrator, or your corporate contact.); #show the "help" links $NicToolClient::show_help_links = 1; @@ -42,33 +43,35 @@ BEGIN { #is the "exact match" checkbox automatically checked? $NicToolClient::exact_match_checked = 0; - $NicToolClient::template_dir = "$NicToolClient::app_dir/templates"; - $NicToolClient::login_template = "$NicToolClient::template_dir/login.html"; - $NicToolClient::setup_error_template = "$NicToolClient::template_dir/setup_error.html"; - $NicToolClient::frameset_template = "$NicToolClient::template_dir/frameset.html"; - $NicToolClient::start_html_template = "$NicToolClient::template_dir/start_html.html"; - $NicToolClient::end_html_template = "$NicToolClient::template_dir/end_html.html"; - $NicToolClient::body_frame_start_template = "$NicToolClient::template_dir/body_frame_start.html"; + $NicToolClient::template_dir = "$NicToolClient::app_dir/templates"; + $NicToolClient::login_template = "$NicToolClient::template_dir/login.html"; + $NicToolClient::setup_error_template = "$NicToolClient::template_dir/setup_error.html"; + $NicToolClient::frameset_template = "$NicToolClient::template_dir/frameset.html"; + $NicToolClient::start_html_template = "$NicToolClient::template_dir/start_html.html"; + $NicToolClient::end_html_template = "$NicToolClient::template_dir/end_html.html"; + $NicToolClient::body_frame_start_template = + "$NicToolClient::template_dir/body_frame_start.html"; $NicToolClient::page_length = 50; #default values for zones/nameservers - $NicToolClient::default_zone_ttl = '86400'; + $NicToolClient::default_zone_ttl = '86400'; $NicToolClient::default_zone_mailaddr = 'hostmaster.ZONE.TLD.'; - $NicToolClient::default_zone_refresh= '16384'; # RFC 1912 range (20 min to 12 hours) - $NicToolClient::default_zone_retry = '900'; # RFC 1912 range (180-900 sec) - $NicToolClient::default_zone_expire = '1048576'; # RFC 1912 range (14 - 28 days) - $NicToolClient::default_zone_minimum = '2560'; # RFC 2308 range (1 - 3 hours) + $NicToolClient::default_zone_refresh = '16384'; # RFC 1912 range (20 min to 12 hours) + $NicToolClient::default_zone_retry = '900'; # RFC 1912 range (180-900 sec) + $NicToolClient::default_zone_expire = '1048576'; # RFC 1912 range (14 - 28 days) + $NicToolClient::default_zone_minimum = '2560'; # RFC 2308 range (1 - 3 hours) $NicToolClient::default_zone_record_ttl = '86400'; $NicToolClient::default_nameserver_ttl = '86400'; #NicToolServer connection settings - $NicToolServerAPI::server_host = "127.0.0.1"; - $NicToolServerAPI::server_port = "8082"; - $NicToolServerAPI::transfer_protocol = 'http', - $NicToolServerAPI::data_protocol = "soap"; # 'soap' or 'xml_rpc' - $NicToolServerAPI::debug_soap_setup = 0; # debug soap calls + $NicToolServerAPI::server_host = $ENV{NICTOOL_SERVER_HOST} || "127.0.0.1"; + $NicToolServerAPI::server_port = $ENV{NICTOOL_SERVER_PORT} || "8082"; + $NicToolServerAPI::transfer_protocol = $ENV{NICTOOL_SERVER_PROTOCOL} || 'http', + $NicToolServerAPI::data_protocol = + $ENV{NICTOOL_DATA_PROTOCOL} || "soap"; # 'soap' or 'xml_rpc' + $NicToolServerAPI::debug_soap_setup = 0; # debug soap calls $NicToolServerAPI::debug_soap_request = 0; $NicToolServerAPI::debug_soap_response = 0; } diff --git a/client/t/e2e/helpers.ts b/client/t/e2e/helpers.ts index 5a05a220..48f739aa 100644 --- a/client/t/e2e/helpers.ts +++ b/client/t/e2e/helpers.ts @@ -5,8 +5,8 @@ import type { Page, Frame } from '@playwright/test'; // Constants // --------------------------------------------------------------------------- export const BASE = process.env.NICTOOL_URL || 'https://localhost:8443'; -export const USERNAME = 'root'; -export const PASSWORD = 'nictool'; +export const USERNAME = process.env.NICTOOL_TEST_USER || 'root'; +export const PASSWORD = process.env.NICTOOL_TEST_PASSWORD || 'nictool'; export const GROUP_DEFAULTS = [ 'user_create=1', 'user_delete=1', 'user_write=1', diff --git a/dist/docker/.env.example b/dist/docker/.env.example new file mode 100644 index 00000000..67c19bff --- /dev/null +++ b/dist/docker/.env.example @@ -0,0 +1,9 @@ +# Copy to .env and fill in values, or run: ./generate-env.sh +DB_ROOT_PASSWORD= +NICTOOL_DB_NAME=nictool +NICTOOL_DB_USER=nictool +NICTOOL_DB_USER_PASSWORD= +ROOT_USER_EMAIL=admin@example.com +ROOT_USER_PASSWORD= +# Set DB_SSL=1 to enable SSL for database connections +#DB_SSL= diff --git a/dist/docker/Dockerfile b/dist/docker/Dockerfile new file mode 100644 index 00000000..d730a27c --- /dev/null +++ b/dist/docker/Dockerfile @@ -0,0 +1,84 @@ +FROM debian:bookworm + +ENV DEBIAN_FRONTEND=noninteractive LANG=en_US.UTF-8 LC_ALL=C.UTF-8 + +RUN apt-get -q update && apt-get install -qy \ + perl \ + cpanminus \ + build-essential \ + apache2 \ + libapache2-mod-perl2 \ + libapache2-mod-perl2-dev \ + libxml2 \ + libssl-dev \ + libmariadb-dev \ + expat \ + libexpat-dev \ + gettext \ + bind9utils \ + mariadb-client \ + openssl \ + libdbix-simple-perl \ + libdbd-mysql-perl \ + libapache-dbi-perl \ + libnet-ip-perl \ + libxml-libxml-perl \ + libxml-parser-perl \ + libdigest-hmac-perl \ + libjson-perl \ + librpc-xml-perl \ + libsoap-lite-perl \ + libmodule-build-perl \ + libmime-base32-perl \ + libmime-base64-perl \ + libbind-confparser-perl \ + libcrypt-openssl-rsa-perl \ + libcrypt-openssl-dsa-perl \ + libnet-dns-perl \ + libyaml-perl \ + libwww-perl \ + liburi-perl \ + libmime-tools-perl \ + libmailtools-perl \ + libfile-sharedir-perl \ + libdbi-perl \ + libcgi-pm-perl \ + libtest-pod-perl \ + libtest-output-perl \ + libtest-simple-perl \ + && apt-get clean \ + && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* + +# Copy only Makefile.PL files first for dependency caching +COPY server/Makefile.PL /usr/local/nictool/server/Makefile.PL +COPY client/Makefile.PL /usr/local/nictool/client/Makefile.PL + +# Install Perl dependencies (cached unless Makefile.PL changes) +RUN cd /usr/local/nictool/server && perl Makefile.PL && cpanm -n . \ + && cd /usr/local/nictool/client && perl Makefile.PL && cpanm -n . + +# Additional Perl modules not in Makefile.PL +RUN cpanm --notest \ + CryptX Crypt::Mac::HMAC Crypt::KeyDerivation \ + Test::HTML::Lint Time::TAI64 DBD::MariaDB \ + Net::LDAP Test::Output + +# Set up Apache modules +RUN a2dismod mpm_event && a2enmod mpm_prefork && a2enmod ssl \ + && rm -rf /etc/apache2/sites-enabled/* /etc/apache2/sites-available/* + +# Now copy the full source (only this layer invalidates on code changes) +COPY server/ /usr/local/nictool/server/ +COPY client/ /usr/local/nictool/client/ +COPY dist/ /usr/local/nictool/dist/ + +# Install the actual NicTool modules (deps are cached above) +RUN cd /usr/local/nictool/server && perl Makefile.PL && make install \ + && cd /usr/local/nictool/client && perl Makefile.PL && make install + +COPY dist/docker/entrypoint.sh /entrypoint.sh +RUN chmod +x /entrypoint.sh + +EXPOSE 80 443 + +CMD ["/entrypoint.sh"] diff --git a/dist/docker/debian/web/Dockerfile b/dist/docker/debian/web/Dockerfile deleted file mode 100644 index 2857108f..00000000 --- a/dist/docker/debian/web/Dockerfile +++ /dev/null @@ -1,61 +0,0 @@ -FROM debian -MAINTAINER Gerhard - -ENV DEBIAN_FRONTEND=noninteractive LANG=en_US.UTF-8 LC_ALL=C.UTF-8 LANGUAGE=en_US.UTF-8 - -# take care of OS stuff -RUN apt-get -q update && apt-get install -qy --force-yes \ - perl \ - cpanminus \ - build-essential \ - apache2 \ - libapache2-mod-perl2 \ - libapache2-mod-perl2-dev \ - libxml2 \ - libssl-dev \ - libmariadb-dev \ - expat \ - libexpat-dev \ - gettext \ - git \ - bind9utils \ - mariadb-client \ - && apt-get clean \ - && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* - -# clone the NicTool repo -RUN git clone https://github.com/nictool/NicTool.git /usr/local/nictool - -# install Perl dependencies -RUN cd /usr/local/nictool/server \ - && perl Makefile.PL \ - && cpanm -n . \ - && cd /usr/local/nictool/client \ - && perl Makefile.PL \ - && cpanm -n . - -# set up/install any additional Perl dependencies -RUN cd /usr/local/nictool/server \ - && perl bin/nt_install_deps.pl; \ - cd /usr/local/nictool/client \ - && perl bin/install_deps.pl - -# install Perl module for LDAP authentication -RUN cpanm Net::LDAP - -# set up apache -RUN rm -rf /etc/apache2/sites-enabled/* \ - && rm -rf /etc/apache2/sites-available/* - -# ensure exposed -RUN mkdir -p /etc/nictool/ && mkdir -p /var/lib/nictool/ - -RUN a2enmod ssl && a2dismod mpm_event && a2enmod mpm_prefork - -ADD startup.sh /startup.sh - -EXPOSE 80 443 - -VOLUME /etc/nictool/ /var/lib/nictool/ - -CMD /bin/bash /startup.sh diff --git a/dist/docker/debian/web/conf/nictool.conf b/dist/docker/debian/web/conf/nictool.conf deleted file mode 100644 index 0e6751bf..00000000 --- a/dist/docker/debian/web/conf/nictool.conf +++ /dev/null @@ -1,50 +0,0 @@ -PerlRequire /usr/local/nictool/client/lib/nictoolclient.conf - - - # force a https connection - ServerName dns.example.net # change me - Redirect / https://dns.example.net/ # change me - - - - ServerName dns.example.net # change me - Alias /images/ "/usr/local/nictool/client/htdocs/images/" - DocumentRoot /usr/local/nictool/client/htdocs - DirectoryIndex index.cgi - SSLEngine on - SSLCertificateFile /etc/ssl/certs/server.crt - SSLCertificateKeyFile /etc/ssl/private/server.key - - - SetHandler perl-script - PerlResponseHandler ModPerl::Registry - PerlOptions +ParseHeaders - Options +ExecCGI - - - - Require all granted - - - - - PerlFreshRestart On - -PerlTaintCheck Off - -Listen 8082 - -PerlRequire /usr/local/nictool/server/lib/nictoolserver.conf - - - KeepAlive Off - - SetHandler perl-script - PerlResponseHandler NicToolServer - - - SetHandler perl-script - PerlResponseHandler Apache::SOAP - PerlSetVar dispatch_to "/usr/local/nictool/server, NicToolServer::SOAP" - - diff --git a/dist/docker/debian/web/conf/nictoolclient.conf b/dist/docker/debian/web/conf/nictoolclient.conf deleted file mode 100644 index cf36aa38..00000000 --- a/dist/docker/debian/web/conf/nictoolclient.conf +++ /dev/null @@ -1,79 +0,0 @@ -#!/usr/bin/perl -# -# NicTool v2.00-rc1 Copyright 2001 Damon Edwards, Abe Shelton & Greg Schueler -# NicTool v2.01 Copyright 2004 The Network People, Inc. -# -# NicTool is free software; you can redistribute it and/or modify it under -# the terms of the Affero General Public License as published by Affero, -# Inc.; either version 1 of the License, or any later version. -# -# NicTool is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY -# or FITNESS FOR A PARTICULAR PURPOSE. See the Affero GPL for details. -# -# You should have received a copy of the Affero General Public License -# along with this program; if not, write to Affero Inc., 521 Third St, -# Suite 225, San Francisco, CA 94107, USA -# - -use strict; - -use CGI(); - -BEGIN { - $NicToolClient::app_dir = '/usr/local/nictool/client'; - - #Interface options - $NicToolClient::app_title = 'NicTool'; - - $NicToolClient::image_dir = 'images'; - - $NicToolClient::generic_error_message = qq(If you continue to get this error, please contact the system administrator, or your corporate contact.); - - #show the "help" links - $NicToolClient::show_help_links = 1; - - #go to detail view after creating a new zone - $NicToolClient::edit_after_new_zone = 1; - - #is the "include subgroups" checkbox automatically checked? - $NicToolClient::include_subgroups_checked = 1; - - #is the "exact match" checkbox automatically checked? - $NicToolClient::exact_match_checked = 0; - - $NicToolClient::template_dir = "$NicToolClient::app_dir/templates"; - $NicToolClient::login_template = "$NicToolClient::template_dir/login.html"; - $NicToolClient::setup_error_template = "$NicToolClient::template_dir/setup_error.html"; - $NicToolClient::frameset_template = "$NicToolClient::template_dir/frameset.html"; - $NicToolClient::start_html_template = "$NicToolClient::template_dir/start_html.html"; - $NicToolClient::end_html_template = "$NicToolClient::template_dir/end_html.html"; - $NicToolClient::body_frame_start_template = "$NicToolClient::template_dir/body_frame_start.html"; - - $NicToolClient::page_length = 50; - - #default values for zones/nameservers - $NicToolClient::default_zone_ttl = '86400'; - $NicToolClient::default_zone_mailaddr = 'hostmaster.ZONE.TLD.'; - $NicToolClient::default_zone_refresh= '16384'; # RFC 1912 range (20 min to 12 hours) - $NicToolClient::default_zone_retry = '900'; # RFC 1912 range (180-900 sec) - $NicToolClient::default_zone_expire = '1048576'; # RFC 1912 range (14 - 28 days) - $NicToolClient::default_zone_minimum = '2560'; # RFC 2308 range (1 - 3 hours) - - $NicToolClient::default_zone_record_ttl = '86400'; - $NicToolClient::default_nameserver_ttl = '86400'; - - #NicToolServer connection settings - $NicToolServerAPI::server_host = "127.0.0.1"; - $NicToolServerAPI::server_port = "8082"; - $NicToolServerAPI::transfer_protocol = 'http', - $NicToolServerAPI::data_protocol = "soap"; # 'soap' or 'xml_rpc' - $NicToolServerAPI::debug_soap_setup = 0; # debug soap calls - $NicToolServerAPI::debug_soap_request = 0; - $NicToolServerAPI::debug_soap_response = 0; -} - -use lib "$NicToolClient::app_dir/lib"; -use NicToolClient; - -1; diff --git a/dist/docker/debian/web/conf/nictoolserver.conf b/dist/docker/debian/web/conf/nictoolserver.conf deleted file mode 100644 index 654011a5..00000000 --- a/dist/docker/debian/web/conf/nictoolserver.conf +++ /dev/null @@ -1,51 +0,0 @@ -#!/usr/bin/perl - -use Apache::DBI(); -use Apache::SOAP; -use DBIx::Simple; -use XML::Parser; -use SOAP::Lite; - -use strict; -use NicToolServer; -use NicToolServer::SOAP; -use NicToolServer::Client::SOAP; -use NicToolServer::Client; -use NicToolServer::Session; -use NicToolServer::Response; -use NicToolServer::Permission; -use NicToolServer::Zone; -use NicToolServer::Zone::Sanity; -use NicToolServer::Zone::Record; -use NicToolServer::Zone::Record::Sanity; -use NicToolServer::Group; -use NicToolServer::Group::Sanity; -use NicToolServer::User; -use NicToolServer::User::Sanity; -use NicToolServer::Nameserver; -use NicToolServer::Nameserver::Sanity; - -BEGIN { - # Database configuration - $NicToolServer::dsn = "DBI:mysql:database=nictool;host=127.0.0.1;port=3306"; - $NicToolServer::db_user = 'nictool'; - $NicToolServer::db_pass = 'lootcin205'; - - # LDAP configuration - # $NicToolServer::ldap_servers = 'ldap1.example.com,ldap2.example.com'; # Comma-separated list - # $NicToolServer::ldap_starttls = 0; # Defaults to 0 - # $NicToolServer::ldap_basedn = 'ou=Nictool users,dc=example,dc=com'; # Search base - # $NicToolServer::ldap_user_mapping = 'uid'; # Defaults to 'uid' - - # If ldap_filter is set, NicTool will perform a subtree search (scope: sub) for user under ldap_basedn, - # otherwise it will guesstimate the dn at basedn level (ala scope: one) - # $NicToolServer::ldap_filter = '(&(objectClass=*)(uid=*))'; - - # If anonymous search for the user_mapping attribute is not allowed. Only needed if filter is defined - # $NicToolServer::ldap_binddn = 'cn=Admin,dc=example,dc=com'; - # $NicToolServer::ldap_bindpw = 'the_admin_password'; - - Apache::DBI->connect_on_init($NicToolServer::dsn, $NicToolServer::db_user, $NicToolServer::db_pass); -} - -1; diff --git a/dist/docker/debian/web/conf/nt_vars b/dist/docker/debian/web/conf/nt_vars deleted file mode 100644 index b7dce82b..00000000 --- a/dist/docker/debian/web/conf/nt_vars +++ /dev/null @@ -1,31 +0,0 @@ -DB_ENGINE="mysql" -DB_HOSTNAME="changeme" -DB_ROOT_PASSWORD="changeme" -NICTOOL_DB_NAME="changeme" -NICTOOL_DB_USER="changeme" -NICTOOL_DB_USER_PASSWORD="changeme" -ROOT_USER_EMAIL="changeme" -ROOT_USER_PASSWORD="changeme" -CERT_CN="changeme" -CERT_COUNTRY="XX" -CERT_STATE="changeme" -CERT_LOCALITY="changeme" -CERT_ORG="changeme" -CERT_OU="changeme" -CERT_EMAIL="changeme" - -export DB_ENGINE -export DB_HOSTNAME -export DB_ROOT_PASSWORD -export NICTOOL_DB_NAME -export NICTOOL_DB_USER -export NICTOOL_DB_USER_PASSWORD -export ROOT_USER_EMAIL -export ROOT_USER_PASSWORD -export CERT_CN -export CERT_COUNTRY -export CERT_STATE -export CERT_LOCALITY -export CERT_ORG -export CERT_OU -export CERT_EMAIL diff --git a/dist/docker/debian/web/startup.sh b/dist/docker/debian/web/startup.sh deleted file mode 100755 index cf6b3be8..00000000 --- a/dist/docker/debian/web/startup.sh +++ /dev/null @@ -1,91 +0,0 @@ -#!/bin/bash -# Version 1.0.0 -# Author Gerhard - -CONF_DIR="/etc/nictool" - -echo "" -echo -e "***\n*** Ensure the required configfiles are distributed in the container\n***\n" - -if [ ! -f "${CONF_DIR}/nt_vars" ]; then - cp -v /usr/local/nictool/dist/docker/debian/web/conf/nt_vars ${CONF_DIR}/nt_vars -fi - - -if [ ! -f "${CONF_DIR}/nictoolclient.conf" ]; then - cp -v /usr/local/nictool/client/lib/nictoolclient.conf.dist ${CONF_DIR}/nictoolclient.conf -fi -if [ ! -L "/usr/local/nictool/client/lib/nictoolclient.conf" ]; then - ln -v -s -T ${CONF_DIR}/nictoolclient.conf /usr/local/nictool/client/lib/nictoolclient.conf -fi - - -if [ ! -f "${CONF_DIR}/nictoolserver.conf" ]; then - cp -v /usr/local/nictool/server/lib/nictoolserver.conf.dist ${CONF_DIR}/nictoolserver.conf -fi -if [ ! -L "/usr/local/nictool/server/lib/nictoolserver.conf" ]; then - ln -v -s -T ${CONF_DIR}/nictoolserver.conf /usr/local/nictool/server/lib/nictoolserver.conf -fi - - -if [ ! -f "${CONF_DIR}/nictool.conf" ]; then - cp -v /usr/local/nictool/dist/docker/debian/web/conf/nictool.conf ${CONF_DIR}/nictool.conf -fi -if [ ! -L "/etc/apache2/sites-enabled/nictool.conf" ]; then - ln -v -s -T ${CONF_DIR}/nictool.conf /etc/apache2/sites-enabled/nictool.conf -fi - - -echo "" -echo -e "***\n*** Load the nt_vars environment variables\n***\n" -. ${CONF_DIR}/nt_vars - - -echo "" -echo -e "***\n*** Check the Database ...\n***\n" -# Test database connection -SQL_OUT=$(mysql -h ${DB_HOSTNAME} -u ${NICTOOL_DB_USER} --password=${NICTOOL_DB_USER_PASSWORD} --connect-timeout=10 -e "SELECT option_value FROM ${NICTOOL_DB_NAME}.nt_options WHERE option_name='db_version';" 2>&1 | head -n 1 ) -if [[ "$?" -eq "0" ]]; then - if [[ "${SQL_OUT}" == "option_value" ]]; then - echo "OK - Database accessible, table schema present." - else - echo "WARN - Database check was not successful. Database empty?" - SQLPING_ROOT_OUT=$(mysqladmin ping -h ${DB_HOSTNAME} -u root --password=${DB_ROOT_PASSWORD} --connect-timeout=10 2>&1 1>/dev/null) - if [[ "$?" -eq "0" ]] && [[ "${SQLPING_ROOT_OUT}" == "" ]]; then - echo "INIT - Initialise the database ..." - cd /usr/local/nictool/server/sql; ./create_tables.pl --environment - else - echo "ERR - Database connection failed. (RC=$?, root)" - echo "" - echo "${SQLPING_ROOT_OUT}" - exit 1 - fi - fi -else - echo "ERR - Database connection failed. (RC=$?)" - echo "" - echo "${SQL_OUT}" - exit 1 -fi - - -echo "" -echo -e "***\n*** Checking the Certificate files\n***\n" -if [ ! -f "${CONF_DIR}/server.crt" ]; then - echo "INIT - Create TLS Key and CSR ..." - openssl req -x509 -nodes -days 2190 -newkey rsa:2048 \ - -keyout ${CONF_DIR}/server.key -out ${CONF_DIR}/server.crt \ - -subj "/C=$CERT_COUNTRY/ST=$CERT_STATE/L=$CERT_LOCALITY/O=$CERT_ORG/OU=$CERT_OU/CN=$CERT_CN/emailAddress=$CERT_EMAIL"; -fi -if [ ! -L "/etc/ssl/private/server.key" ]; then - ln -v -s -T ${CONF_DIR}/server.key /etc/ssl/private/server.key -fi -if [ ! -L "/etc/ssl/certs/server.crt" ]; then - ln -v -s -T ${CONF_DIR}/server.crt /etc/ssl/certs/server.crt -fi - - -echo "" -echo -e "***\n*** Start the Apache webserver\n***\n" -. /etc/apache2/envvars && exec /usr/sbin/apache2 -DFOREGROUND - diff --git a/dist/docker/docker-compose.yml b/dist/docker/docker-compose.yml new file mode 100644 index 00000000..855ff835 --- /dev/null +++ b/dist/docker/docker-compose.yml @@ -0,0 +1,36 @@ +services: + db: + image: mariadb:11 + environment: + MYSQL_ROOT_PASSWORD: ${DB_ROOT_PASSWORD} + MYSQL_ROOT_HOST: '%' + MYSQL_DATABASE: ${NICTOOL_DB_NAME:-nictool} + MYSQL_USER: ${NICTOOL_DB_USER:-nictool} + MYSQL_PASSWORD: ${NICTOOL_DB_USER_PASSWORD} + healthcheck: + test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"] + interval: 5s + timeout: 5s + retries: 10 + + web: + image: docker-web:latest + build: + context: ../.. + dockerfile: dist/docker/Dockerfile + ports: + - "8080:80" + - "8443:443" + depends_on: + db: + condition: service_healthy + env_file: .env + environment: + DB_ENGINE: mysql + DB_HOSTNAME: db + healthcheck: + test: ["CMD", "curl", "-sf", "http://localhost/index.cgi"] + interval: 5s + timeout: 5s + retries: 30 + start_period: 30s diff --git a/dist/docker/entrypoint.sh b/dist/docker/entrypoint.sh new file mode 100755 index 00000000..55d3775c --- /dev/null +++ b/dist/docker/entrypoint.sh @@ -0,0 +1,36 @@ +#!/bin/bash +set -e + +NT_DIR="${NT_DIR:-/usr/local/nictool}" + +echo "==> Running NicTool setup" +"$NT_DIR/dist/setup/install-nictool.sh" --nt-dir="$NT_DIR" + +echo "==> Waiting for database" +for attempt in $(seq 1 30); do + if mysqladmin ping -h "${DB_HOSTNAME:-127.0.0.1}" -u "${NICTOOL_DB_USER}" --password="${NICTOOL_DB_USER_PASSWORD}" --silent 2>/dev/null; then + echo " Database is ready." + break + fi + if [ "$attempt" -eq 30 ]; then + echo "ERROR: Database not available after 30 attempts" >&2 + exit 1 + fi + sleep 2 +done + +echo "==> Checking database schema" +SQL_OUT=$(mysql -h "${DB_HOSTNAME:-127.0.0.1}" -u "${NICTOOL_DB_USER}" --password="${NICTOOL_DB_USER_PASSWORD}" \ + -e "SELECT option_value FROM ${NICTOOL_DB_NAME:-nictool}.nt_options WHERE option_name='db_version';" 2>&1 | head -n 1) || true + +if [ "$SQL_OUT" != "option_value" ]; then + echo "==> Initializing database schema" + cd "$NT_DIR/server/sql" || exit 1 + echo "" | perl create_tables.pl --environment +fi + +echo "==> Setting up test environment" +perl "$NT_DIR/dist/setup/setup-test-env.pl" + +echo "==> Starting Apache" +. /etc/apache2/envvars && exec /usr/sbin/apache2 -DFOREGROUND diff --git a/dist/docker/generate-env.sh b/dist/docker/generate-env.sh new file mode 100755 index 00000000..6ad1acf1 --- /dev/null +++ b/dist/docker/generate-env.sh @@ -0,0 +1,24 @@ +#!/bin/bash +set -e + +ENV_FILE="$(cd "$(dirname "$0")" && pwd)/.env" + +if [ -f "$ENV_FILE" ]; then + echo ".env already exists, not overwriting." >&2 + exit 0 +fi + +DB_ROOT_PW=$(openssl rand -base64 24) +NT_DB_PW=$(openssl rand -base64 24) +NT_ROOT_PW=$(openssl rand -base64 24) + +cat > "$ENV_FILE" < + ServerName localhost + Alias /images/ "%%NT_DIR%%/client/htdocs/images/" + DocumentRoot "%%NT_DIR%%/client/htdocs" + DirectoryIndex index.cgi + + + SetHandler perl-script + PerlResponseHandler ModPerl::Registry + PerlOptions +ParseHeaders + Options +ExecCGI + + + + AllowOverride None + Require all granted + + + + + ServerName localhost + Alias /images/ "%%NT_DIR%%/client/htdocs/images/" + DocumentRoot "%%NT_DIR%%/client/htdocs" + DirectoryIndex index.cgi + SSLEngine on + SSLCertificateFile /etc/ssl/certs/server.crt + SSLCertificateKeyFile /etc/ssl/private/server.key + + + SetHandler perl-script + PerlResponseHandler ModPerl::Registry + PerlOptions +ParseHeaders + Options +ExecCGI + + + + AllowOverride None + Require all granted + + + + + PerlFreshRestart On + +PerlTaintCheck Off + +Listen 8082 + +PerlRequire "%%NT_DIR%%/server/lib/nictoolserver.conf" + + + KeepAlive Off + + SetHandler perl-script + PerlResponseHandler NicToolServer + + + SetHandler perl-script + PerlResponseHandler Apache::SOAP + PerlSetVar dispatch_to "%%NT_DIR%%/server, NicToolServer::SOAP" + + + AllowOverride None + Require all granted + + diff --git a/dist/setup/install-nictool.sh b/dist/setup/install-nictool.sh new file mode 100755 index 00000000..73816b24 --- /dev/null +++ b/dist/setup/install-nictool.sh @@ -0,0 +1,56 @@ +#!/bin/sh +set -e + +# Parse --nt-dir flag or use NT_DIR env var +NT_DIR="${NT_DIR:-/usr/local/nictool}" +for arg in "$@"; do + case "$arg" in + --nt-dir=*) NT_DIR="${arg#--nt-dir=}" ;; + esac +done + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" + +echo "==> Installing NicTool configs (NT_DIR=$NT_DIR)" + +# Copy .dist → .conf if .conf doesn't exist yet +for conf in server/lib/nictoolserver client/lib/nictoolclient; do + src="$NT_DIR/${conf}.conf.dist" + dst="$NT_DIR/${conf}.conf" + if [ ! -f "$dst" ]; then + echo " cp $src -> $dst" + cp "$src" "$dst" + fi +done + +# mod_perl requires prefork MPM (not event/worker) +echo "==> Configuring Apache MPM" +if command -v a2dismod >/dev/null 2>&1; then + a2dismod mpm_event 2>/dev/null || true + a2dismod mpm_worker 2>/dev/null || true + a2enmod mpm_prefork 2>/dev/null || true +fi + +# Generate Apache config from template +echo "==> Generating Apache config" +sed "s|%%NT_DIR%%|${NT_DIR}|g" "$SCRIPT_DIR/apache.conf.in" \ + > "/etc/apache2/sites-enabled/nictool.conf" + +# Export NicTool env vars into Apache's envvars so mod_perl can see them +echo "==> Injecting NicTool env vars into Apache envvars" +ENVVARS="/etc/apache2/envvars" +if [ -f "$ENVVARS" ]; then + for var in DB_ENGINE DB_HOSTNAME DB_PORT DB_SSL \ + NICTOOL_DB_NAME NICTOOL_DB_USER NICTOOL_DB_USER_PASSWORD \ + NICTOOL_CLIENT_DIR NICTOOL_SERVER_HOST NICTOOL_SERVER_PORT \ + NICTOOL_SERVER_PROTOCOL NICTOOL_DATA_PROTOCOL; do + val=$(eval echo "\${$var:-}") + if [ -n "$val" ]; then + echo "export ${var}='${val}'" >> "$ENVVARS" + fi + done +fi + +# Set up TLS certificates +echo "==> Setting up TLS" +"$SCRIPT_DIR/tls-setup.sh" diff --git a/dist/setup/setup-test-env.pl b/dist/setup/setup-test-env.pl new file mode 100644 index 00000000..5f94acd5 --- /dev/null +++ b/dist/setup/setup-test-env.pl @@ -0,0 +1,129 @@ +#!/usr/bin/perl +# +# Creates the test user/group and generates test.cfg files for the test suite. +# Reads all credentials from environment variables. +# + +use strict; +use warnings; +use Crypt::KeyDerivation; +use DBI; + +my $db_engine = $ENV{DB_ENGINE} || 'mysql'; +my $db_host = $ENV{DB_HOSTNAME} || '127.0.0.1'; +my $db_name = $ENV{NICTOOL_DB_NAME} || 'nictool'; +my $db_user = $ENV{NICTOOL_DB_USER} or die "Set NICTOOL_DB_USER\n"; +my $db_pass = $ENV{NICTOOL_DB_USER_PASSWORD} or die "Set NICTOOL_DB_USER_PASSWORD\n"; + +# Generate a random password for the test user +my $test_pass = _random_password(20); +my $salt = _get_salt(16); +my $pass_hash = unpack( "H*", Crypt::KeyDerivation::pbkdf2( $test_pass, $salt, 5000, 'SHA512' ) ); + +my $dsn = "DBI:$db_engine:database=$db_name;host=$db_host;port=3306"; +$dsn .= ";mysql_ssl=1" if $ENV{DB_SSL}; +my %opts = ( RaiseError => 1 ); +$opts{mysql_ssl} = 1 if $ENV{DB_SSL}; +my $dbh = DBI->connect( $dsn, $db_user, $db_pass, \%opts ) + or die "Cannot connect to $dsn: $DBI::errstr\n"; + +# Check if test group already exists +my ($group_exists) = $dbh->selectrow_array("SELECT COUNT(*) FROM nt_group WHERE nt_group_id = 2"); + +unless ($group_exists) { + print "Creating test group and user...\n"; + $dbh->do("INSERT INTO nt_group VALUES (2,1,'test_group', 0)"); + $dbh->do("INSERT INTO nt_group_log VALUES (2,1,1,'added',UNIX_TIMESTAMP(),2,1,'test_group')"); + $dbh->do("INSERT INTO nt_group_subgroups VALUES (1,2,1000)"); + $dbh->do( + "INSERT INTO nt_perm VALUES (2,2,NULL,NULL,NULL,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,'1,2,3',0)" + ); +} + +# Check if test user already exists +my ($user_exists) = + $dbh->selectrow_array("SELECT COUNT(*) FROM nt_user WHERE username = 'nictest'"); + +if ($user_exists) { + $dbh->do( "UPDATE nt_user SET password = ?, pass_salt = ? WHERE username = 'nictest'", + undef, $pass_hash, $salt ); + print "Updated test user 'nictest' password.\n"; +} +else { + $dbh->do( + "INSERT INTO nt_user VALUES (2,2,'TestFirst','TestLast','nictest',?,?,'test\@example.com',NULL,0)", + undef, $pass_hash, $salt + ); + print "Created test user 'nictest'.\n"; +} + +$dbh->disconnect; + +# Determine project root (two levels up from this script: dist/setup/ -> project root) +my $script_dir = $0; +$script_dir =~ s|/[^/]+$||; +my $project_root = "$script_dir/../.."; + +# Write server/t/test.cfg +write_test_cfg( "$project_root/server/t/test.cfg", < 'api/lib', # in the root dir +lib => '../api/lib', # in the test dir + +# change the following as needed +server_host => 'localhost', +server_port => 8082, +data_protocol => 'soap', # can be 'soap' or 'xml_rpc' +username => 'nictest\@test_group', +password => '$test_pass', + +# for database tests. Set the same as in nictoolserver.conf +dsn => '$dsn', +db_user => '$db_user', +db_pass => '$db_pass', + +} +EOF + +# Write server/api/t/test.cfg +write_test_cfg( "$project_root/server/api/t/test.cfg", < 'localhost', +server_port => 8082, +data_protocol => 'soap', # can be 'soap' or 'xml_rpc' +username => 'nictest\@test_group', +password => '$test_pass', +} +# the username and password required is a nictool user, typically +# the one automatically created when you run ./create_tables.pl +EOF + +print "Generated test.cfg files.\n"; + +sub write_test_cfg { + my ( $path, $content ) = @_; + open( my $fh, '>', $path ) or die "Cannot write $path: $!\n"; + print $fh $content; + close $fh; + print " wrote $path\n"; +} + +sub _random_password { + my $length = shift || 20; + my @chars = ( 'A' .. 'Z', 'a' .. 'z', '0' .. '9' ); + my $pass = ''; + $pass .= $chars[ rand @chars ] for 1 .. $length; + return $pass; +} + +sub _get_salt { + my $length = shift || 16; + my $chars = join( '', map chr, 40 .. 126 ); + my $salt = ''; + $salt .= substr( $chars, rand( length($chars) - 1 ), 1 ) for 0 .. ( $length - 1 ); + return $salt; +} diff --git a/.test/tls-setup.sh b/dist/setup/tls-setup.sh similarity index 100% rename from .test/tls-setup.sh rename to dist/setup/tls-setup.sh diff --git a/server/api/t/test.cfg b/server/api/t/test.cfg deleted file mode 100644 index 98737e2f..00000000 --- a/server/api/t/test.cfg +++ /dev/null @@ -1,10 +0,0 @@ -# edit the following values -{ -server_host => 'localhost', -server_port => 8082, -data_protocol => 'soap', # can be 'soap' or 'xml_rpc' -username => 'nictest@test_group', -password => 'lootcin234', -} -# the username and password required is a nictool user, typically -# the one automatically created when you run ./create_tables.pl diff --git a/server/bin/nt_powerdns.pl b/server/bin/nt_powerdns.pl index 822ca197..8d9604f8 100755 --- a/server/bin/nt_powerdns.pl +++ b/server/bin/nt_powerdns.pl @@ -38,7 +38,7 @@ my $db_name = $ENV{NT_PDNS_DB_NAME} // 'nictool'; my $db_host = $ENV{NT_PDNS_DB_HOST} // '127.0.0.1'; my $db_user = $ENV{NT_PDNS_DB_USER} // 'nictool'; -my $db_pass = $ENV{NT_PDNS_DB_PASS} // 'lootcin!mysql'; +my $db_pass = $ENV{NT_PDNS_DB_PASS} or die "Set NT_PDNS_DB_PASS\n"; my $dsn = "DBI:mysql:database=$db_name;host=$db_host;mysql_ssl=1"; my $dbh = DBI->connect( $dsn, $db_user, $db_pass ) @@ -92,6 +92,7 @@ +{ response => [@res], expire => $default_ttl + time }; } elsif ( $qtype eq 'ANY' ) { + # PDNS commonly asks ANY for lookups that were originally NS/A/etc. # Include both explicit RR data and nameserver table data. @res = ( &get_records(@arr), &get_ns(@arr) ); @@ -100,6 +101,7 @@ +{ response => [@res], expire => $default_ttl + time }; } elsif ( $qtype eq 'NS' ) { + # Return zone apex NS from nt_zone_nameserver and any explicit NS RRs. # This helps modern PDNS clients that ask NS in multiple contexts. @res = ( &get_ns(@arr), &get_records(@arr) ); @@ -140,7 +142,7 @@ sub get_axfr { my ( $type, $zoneid ) = @_; my $t = ''; - my $sql = qq[ + my $sql = qq[ SELECT z.nt_zone_id, z.zone, t.name AS type, r.name, r.ttl, r.address, r.weight, r.priority, r.other FROM nt_zone z @@ -154,7 +156,7 @@ sub get_axfr { print STDERR "\t" . $sql . "\n" if $warnsql; my $sth = $dbh->prepare($sql); my @result; - if ( $sth->execute($zoneid) ) { + if ( $sth->execute($zoneid) ) { my @rows; while ( $t = $sth->fetchrow_hashref ) { my @content = rr_content_fields($t); @@ -163,8 +165,7 @@ sub get_axfr { "DATA", $t->{name} =~ /\.$/ ? $t->{name} : $t->{name} . "." . $t->{zone}, - 'IN', $t->{type}, $t->{ttl}, ( $use_zone_id ? $t->{'nt_zone_id'} : 1 ), - @content + 'IN', $t->{type}, $t->{ttl}, ( $use_zone_id ? $t->{'nt_zone_id'} : 1 ), @content ]; push @rows, $t; } @@ -215,7 +216,7 @@ sub get_records { "; print STDERR "\t" . $zone_lookup_sql . "\n" if $warnsql; - my $zsth = $dbh->prepare($zone_lookup_sql); + my $zsth = $dbh->prepare($zone_lookup_sql); my @zone_lookup_params = ( $main::nt_nameserver_id, @zone_names ); return () if !$zsth->execute(@zone_lookup_params); @@ -233,7 +234,7 @@ sub get_records { return () if !@zone_name_pairs; my $pair_placeholders = join( ',', ('(?,?)') x @zone_name_pairs ); - my @pair_params = map { @$_ } @zone_name_pairs; + my @pair_params = map {@$_} @zone_name_pairs; my @query_params = (@pair_params); my $type_clause = ''; @@ -270,8 +271,7 @@ sub get_records { push @result, [ "DATA", $qname, $qclass, $t->{type}, $t->{ttl}, - ( $use_zone_id ? $t->{'nt_zone_id'} : 1 ), - @content + ( $use_zone_id ? $t->{'nt_zone_id'} : 1 ), @content ]; push @rows, $t; $main::seenrecid{ $t->{'nt_zone_record_id'} } = 1; @@ -313,7 +313,7 @@ sub get_ns { my @order; my $t = ''; - my $sql = " + my $sql = " SELECT z.nt_zone_id, ns.ttl, ns.name, ns.address FROM nt_zone z @@ -349,7 +349,7 @@ sub get_soa { my @order; my $t = ''; - my $sql = " + my $sql = " SELECT ns.name, z.* FROM nt_zone z INNER JOIN nt_zone_nameserver zns ON z.nt_zone_id=zns.nt_zone_id diff --git a/server/lib/nictoolserver.conf.dist b/server/lib/nictoolserver.conf.dist index f6c1827f..fd8810ab 100644 --- a/server/lib/nictoolserver.conf.dist +++ b/server/lib/nictoolserver.conf.dist @@ -26,10 +26,23 @@ use NicToolServer::Nameserver; use NicToolServer::Nameserver::Sanity; BEGIN { - # Database configuration - $NicToolServer::dsn = "DBI:mysql:database=nictool;host=127.0.0.1;port=3306"; - $NicToolServer::db_user = 'nictool'; - $NicToolServer::db_pass = 'lootcin205'; + # Database configuration — credentials are required env vars + my $db_engine = $ENV{DB_ENGINE} || 'mysql'; + my $db_host = $ENV{DB_HOSTNAME} || '127.0.0.1'; + my $db_name = $ENV{NICTOOL_DB_NAME} || 'nictool'; + + my $db_user = $ENV{NICTOOL_DB_USER} + or die "Set NICTOOL_DB_USER (the database user for NicTool)\n"; + my $db_pass = $ENV{NICTOOL_DB_USER_PASSWORD} + or die "Set NICTOOL_DB_USER_PASSWORD (the database password for NicTool)\n"; + + my $db_port = $ENV{DB_PORT} || '3306'; + my $dsn = "DBI:$db_engine:database=$db_name;host=$db_host;port=$db_port"; + $dsn .= ";mysql_ssl=1" if $ENV{DB_SSL}; + + $NicToolServer::dsn = $dsn; + $NicToolServer::db_user = $db_user; + $NicToolServer::db_pass = $db_pass; # LDAP configuration # $NicToolServer::ldap_servers = 'ldap1.example.com,ldap2.example.com'; # Comma-separated list @@ -46,7 +59,8 @@ BEGIN { # $NicToolServer::ldap_binddn = 'cn=Admin,dc=example,dc=com'; # $NicToolServer::ldap_bindpw = 'the_admin_password'; - Apache::DBI->connect_on_init($NicToolServer::dsn, $NicToolServer::db_user, $NicToolServer::db_pass); + Apache::DBI->connect_on_init( $NicToolServer::dsn, $NicToolServer::db_user, + $NicToolServer::db_pass ); } 1; diff --git a/server/sql/create_tables.pl b/server/sql/create_tables.pl index ff12aa5c..ebcbf12a 100755 --- a/server/sql/create_tables.pl +++ b/server/sql/create_tables.pl @@ -155,16 +155,22 @@ $dbh->do("DROP DATABASE IF EXISTS $db"); $dbh->do("CREATE DATABASE $db"); -# remote sessions will never be recognized as 'db_user'@'db_hostname' as MySQL does -# a reverse lookup of the initiating host's IP address and uses that as the -# connection string, eg 'db_user'@'x.x.x.x' -if ( $db_host eq 'localhost' || $db_host eq '127.0.0.1' || $db_host eq '::1' ) { - $dbh->do("GRANT ALL PRIVILEGES ON $db.* TO $db_user\@$db_host IDENTIFIED BY '$db_pass'"); -} -else { - $dbh->do("GRANT ALL PRIVILEGES ON $db.* TO $db_user\@'%' IDENTIFIED BY '$db_pass'"); +# Create the NicTool database user. MySQL 8.0+ removed GRANT ... IDENTIFIED BY, +# so we CREATE USER first, then GRANT separately. +my $user_host = + ( $db_host eq 'localhost' || $db_host eq '127.0.0.1' || $db_host eq '::1' ) ? $db_host : '%'; + +$dbh->do("DROP USER IF EXISTS '$db_user'\@'$user_host'"); + +# Try mysql_native_password first (MySQL 8.0+), fall back for MariaDB +my $created = $dbh->do( + "CREATE USER '$db_user'\@'$user_host' IDENTIFIED WITH mysql_native_password BY '$db_pass'"); +if ( !$created ) { + $dbh->do("CREATE USER '$db_user'\@'$user_host' IDENTIFIED BY '$db_pass'"); } +$dbh->do("GRANT ALL PRIVILEGES ON $db.* TO '$db_user'\@'$user_host'"); + $dbh->do("USE $db"); my @sql_files = get_sql_files(); @@ -249,8 +255,9 @@ sub get_dbh { print "\n"; return if $test_run; - my $dbh = - DBI->connect( "dbi:$db_engine:host=$db_host", "root", $db_root_pw, { ChopBlanks => 1, } ) + my %opts = ( ChopBlanks => 1 ); + $opts{mysql_ssl} = 1 if $ENV{DB_SSL}; + my $dbh = DBI->connect( "dbi:$db_engine:host=$db_host", "root", $db_root_pw, \%opts ) or die $DBI::errstr; return ( $dbh, $db_host, $db_engine ); diff --git a/server/sql/dump_schema.pl b/server/sql/dump_schema.pl index a4e3b1d5..1c47fee7 100755 --- a/server/sql/dump_schema.pl +++ b/server/sql/dump_schema.pl @@ -9,7 +9,7 @@ my $db = 'nictool'; my $db_dsn = "DBI:mysql:database=$db;host=$db_host"; my $db_user = 'nictool'; -my $db_pass = 'lootcin205'; +my $db_pass = $ENV{NICTOOL_DB_USER_PASSWORD} or die "Set NICTOOL_DB_USER_PASSWORD\n"; my $root_pw = ''; my $dbh = DBI->connect( $db_dsn, 'root', $root_pw ); diff --git a/server/t/test.cfg b/server/t/test.cfg deleted file mode 100644 index f6c87043..00000000 --- a/server/t/test.cfg +++ /dev/null @@ -1,20 +0,0 @@ -{ - -# this specifies the location of the NicTool api client lib -# if it is not installed. (you never ran 'make install' ) -lib => 'api/lib', # in the root dir -lib => '../api/lib', # in the test dir - -# change the following as needed -server_host => 'localhost', -server_port => 8082, -data_protocol => 'soap', # can be 'soap' or 'xml_rpc' -username => 'nictest@test_group', -password => 'lootcin234', - -# for database tests. Set the same as in nictoolserver.conf -dsn => 'DBI:mysql:database=nictool;host=localhost;port=3306;mysql_ssl=1', -db_user => 'nictool', -db_pass => 'lootcin!mysql', - -}