Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

NACaaS Remote Database #8038

Merged
merged 27 commits into from
Apr 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
92ba5cb
add remote database configuration to configurator UI
satkunas Mar 14, 2024
622e819
add remote database fields to assign database method
satkunas Mar 18, 2024
a7572fc
remove dup declaration
satkunas Apr 8, 2024
90f8bad
rename remote form fields, add remote_password. default remote_port
satkunas Apr 8, 2024
63c6fd2
use password input
satkunas Apr 9, 2024
6424fba
improve labels
satkunas Apr 9, 2024
6f537cc
Test a remote connection
jrouzierinverse Apr 18, 2024
c298e0a
do not perform secure installation with remotedb
satkunas Apr 18, 2024
1e665d5
remove Data::Dumper
jrouzierinverse Apr 19, 2024
1783093
Add support for MySQL
jrouzierinverse Apr 21, 2024
b63943d
Add back the username is secureDatabase
jrouzierinverse Apr 21, 2024
e693961
Add back username
jrouzierinverse Apr 21, 2024
3e63196
flatten remote, add boolean in payload
satkunas Apr 22, 2024
0945a73
adjust payloads
satkunas Apr 22, 2024
6f97d35
Use remote or local to connect to database
jrouzierinverse Apr 23, 2024
9116c11
rename root_username -> username, root_password -> password
jrouzierinverse Apr 23, 2024
c6434b5
Add username to test
jrouzierinverse Apr 23, 2024
9c3476d
Always send is_remote and remote
jrouzierinverse Apr 23, 2024
d8dbef7
Use the newer call the hanldes remote db setup
jrouzierinverse Apr 23, 2024
456a0df
Update version
jrouzierinverse Apr 23, 2024
e96d679
Update API
jrouzierinverse Apr 24, 2024
f91f041
Change for updated API
jrouzierinverse Apr 24, 2024
8aa172f
fix namespace
jrouzierinverse Apr 24, 2024
6d05183
update pfconfig and database_proxy settings
jrouzierinverse Apr 24, 2024
df871c0
Don't delete the database
jrouzierinverse Apr 24, 2024
c645646
Fix typo
jrouzierinverse Apr 25, 2024
9d43d9f
disable e2e unit until after next e2e PR is merged
satkunas Apr 25, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion debian/control
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ Depends: ${misc:Depends}, vlan,
# perl uncategorized modules
libapache-htpasswd-perl, libbit-vector-perl, libtext-csv-perl, libtext-csv-xs-perl,
libcgi-session-serialize-yaml-perl, libtimedate-perl, libapache-dbi-perl,
libdbd-mysql-perl, libfile-tail-perl, libnetwork-ipv4addr-perl,
libdbd-mysql-perl (>= 4.052), libfile-tail-perl, libnetwork-ipv4addr-perl,
libiptables-parse-perl, libiptables-chainmgr-perl, iptables (>= 1.4.0), iptables-netflow-dkms,
liblwp-useragent-determined-perl,
liblwp-protocol-connect-perl,
Expand Down
2 changes: 1 addition & 1 deletion html/pfappserver/lib/pfappserver/Form/Config/Pf.pm
Original file line number Diff line number Diff line change
Expand Up @@ -261,7 +261,7 @@ sub field_list {
type => 'PathUpload',
accessor => $old_name,
config_prefix => $doc_section->{ext},
upload_namespace => $name,
upload_namespace => 'pf',
};
};
}
Expand Down
250 changes: 238 additions & 12 deletions html/pfappserver/lib/pfappserver/Model/DB.pm
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ use pf::file_paths qw($install_dir);
use pf::error;
use pf::util;
use File::Slurp qw(read_dir);
use File::Temp qw(tempfile);

extends 'Catalyst::Model';

Expand All @@ -37,56 +38,169 @@ our $dbHandler;

sub assign {
my ( $self, $db, $user, $password ) = @_;
return $self->_assign($dbHandler, $db, $user, $password);
}

sub get_db_type {
my ($dbh) = @_;
my $data = $dbh->selectrow_arrayref("SELECT VERSION()");
if (!defined $data || !@$data) {
return
}

my $version = $data->[0];
($version, my $type) = split('-', $version);
$type //= "MySQL";
return ($version, $type);
}

sub assign_database {
my ($self, $args) = @_;
my $logger = get_logger();
my $db = delete $args->{database};
$args->{database} = '';
my ($dbh, undef, $user) = connect_to_database($args);
if (!$dbh) {
$logger->warn("$DBI::errstr");
return ( $STATUS::INTERNAL_SERVER_ERROR, [ "Error creating the user $user on database $db}"] );
}

my $status_msg;
return $self->_assign($dbh, $db, $args->{pf_username}, $args->{pf_password});
}

$db = $dbHandler->quote_identifier($db);
sub _assign {
my ($self, $dbh, $db, $user, $password ) = @_;
my $logger = get_logger();

my $status_msg;
my ($version, $type) = get_db_type($dbh);
$db = $dbh->quote_identifier($db);

# Create global PF user
foreach my $host ("'%'","localhost") {
my $sql_query = "GRANT SELECT,INSERT,UPDATE,DELETE,EXECUTE,LOCK TABLES,CREATE TEMPORARY TABLES ON $db.* TO ?\@${host} IDENTIFIED BY ?";
$dbHandler->do($sql_query, undef, $user, $password);
my $sql_query = "DROP USER IF EXISTS ?\@${host}";
$dbh->do($sql_query, undef, $user);
if ( $DBI::errstr ) {
$status_msg = "Error creating the user $user on database $db";
$logger->warn("$DBI::errstr");
return ( $STATUS::INTERNAL_SERVER_ERROR, $status_msg );
}
$sql_query = "GRANT DROP ON $db.radius_nas TO ?\@${host} IDENTIFIED BY ?";
$dbHandler->do($sql_query, undef, $user, $password);

$sql_query = "CREATE USER ?\@${host} IDENTIFIED BY ?";
$dbh->do($sql_query, undef, $user, $password);
if ( $DBI::errstr ) {
$status_msg = "Error creating the user $user on database $db";
$logger->warn("$DBI::errstr");
return ( $STATUS::INTERNAL_SERVER_ERROR, $status_msg );
}
$sql_query = "GRANT SELECT ON mysql.proc TO ?\@${host} IDENTIFIED BY ?";
$dbHandler->do($sql_query, undef, $user, $password);

$sql_query = "GRANT DROP,SELECT,INSERT,UPDATE,DELETE,EXECUTE,LOCK TABLES,CREATE TEMPORARY TABLES ON $db.* TO ?\@${host}";
$dbh->do($sql_query, undef, $user);
if ( $DBI::errstr ) {
$status_msg = "Error creating the user $user on database $db";
$logger->warn("$DBI::errstr");
return ( $STATUS::INTERNAL_SERVER_ERROR, $status_msg );
}
$sql_query = "GRANT BINLOG ADMIN ON *.* TO ?\@${host} IDENTIFIED BY ?";
$dbHandler->do($sql_query, undef, $user, $password);

$sql_query = "GRANT CREATE,DROP ON $db.radius_nas TO ?\@${host}";
$dbh->do($sql_query, undef, $user);
if ( $DBI::errstr ) {
$status_msg = "Error granting BINLOG ADMIN for user $user on database";
$status_msg = "Error creating the user $user on database $db";
$logger->warn("$DBI::errstr");
return ( $STATUS::INTERNAL_SERVER_ERROR, $status_msg );
}

if ($type ne 'MySQL') {
$sql_query = "GRANT SELECT ON mysql.proc TO ?\@${host}";
$dbh->do($sql_query, undef, $user);
if ( $DBI::errstr ) {
$status_msg = "Error creating the user $user on database $db";
$logger->warn("$DBI::errstr");
return ( $STATUS::INTERNAL_SERVER_ERROR, $status_msg );
}
}


$sql_query = $type eq 'MySQL' ? "GRANT BINLOG_ADMIN ON *.* TO ?\@${host}" : "GRANT BINLOG ADMIN ON *.* TO ?\@${host}";
$dbh->do($sql_query, undef, $user);
if ( $DBI::errstr ) {
$status_msg = "Error granting BINLOG ADMIN for user $user on database $db";
$logger->warn("$DBI::errstr");
return ( $STATUS::INTERNAL_SERVER_ERROR, $status_msg );
}
}
# Apply the new privileges
$dbHandler->do("FLUSH PRIVILEGES");
$dbh->do("FLUSH PRIVILEGES");
if ( $DBI::errstr ) {
$status_msg = ["Error creating the user [_1] on database [_2]",$user,$db];
$logger->warn("$DBI::errstr");
return ( $STATUS::INTERNAL_SERVER_ERROR, $status_msg );
}

$status_msg = ["Successfully created the user [_1] on database [_2]",$user,$db];

# return original status message
return ( $STATUS::OK, $status_msg );
}

sub connect_to_database {
my ($args) = @_;
my $fh;
$args->{database} //= "mysql";
$args->{hostname} ||= "localhost";
if ($args->{remote}{ca_cert}) {
($fh, my $filename) = tempfile();
print $fh $args->{remote}{ca_cert};
$fh->flush();
$fh->close();
$args->{remote}{ca_file} = $filename;
}

my ($connect_str, $user, $password) = make_connection_str($args);
my $dbh = DBI->connect($connect_str, $user, $password);
return $dbh, $args->{database}, $user;
}

sub test_connection {
my ($self, $args) = @_;
my $logger = get_logger();
my ($dbh, $db, $user) = connect_to_database($args);
if ( !$dbh ) {
my $status_msg = ["Error in connection to the database [_1] with user [_2]",$db,$user];
$logger->warn("$DBI::errstr");
return ( $STATUS::INTERNAL_SERVER_ERROR, $status_msg );
}

my $status_msg = ["Successfully connected to the database [_1] with user [_2]",$db,$user];
return ( $STATUS::OK, $status_msg );
}

sub make_connection_str {
my ($args) = @_;
my $dsn = "DBI:mysql:dbname=$args->{database}";
if (!$args->{is_remote}) {
return (
"$dsn;mysql_socket=/var/lib/mysql/mysql.sock",
$args->{username},
$args->{password},
);
}

my $remote = $args->{remote};
$dsn .= ";host=$remote->{hostname}";
my $port = $remote->{port} // '3306';
$dsn .= ";port=$port";
if ($remote->{encryption} eq "tls") {
$dsn .= ";mysql_ssl=1;mysql_ssl_ca_file=$remote->{ca_file}";
}

return (
$dsn,
$remote->{username},
$remote->{password},
);
}

=head2 connect

=cut
Expand Down Expand Up @@ -132,6 +246,118 @@ sub create {
return ( $STATUS::OK, $status_msg );
}

sub make_mysql_command {
my ($args) = @_;
if ($args->{is_remote}) {
return make_remote_mysql_command($args);
}

my $user = quotemeta ($args->{username});
my $password = quotemeta ($args->{password});
my $db = quotemeta ($args->{database});
my $mysql_cmd = "/usr/bin/mysql --socket=/var/lib/mysql/mysql.sock -u $user -p$password $db";
return ($mysql_cmd, undef, "-p$password");
}

sub make_remote_mysql_command {
my ($args) = @_;
my $remote = $args->{remote};
my $mysql_cmd = "/usr/bin/mysql";
if ($remote->{encryption} eq 'tls') {
$mysql_cmd .= " --ssl";
}

if ($remote->{port}) {
$mysql_cmd .= " -P". quotemeta($remote->{port});
}

my $fh = make_file_for_cert($remote);
if ($remote->{ca_file}) {
$mysql_cmd .= " --ssl-ca=". $remote->{ca_file};
}

my $host = quotemeta ($remote->{hostname});
my $user = quotemeta ($remote->{username});
my $password = quotemeta ($remote->{password});
my $db = quotemeta ($args->{database});
$mysql_cmd .= " -h$host -u$user -p$password $db";
return ($mysql_cmd, $fh, "-p$password");
}

sub make_file_for_cert {
my ($remote_args) = @_;
if (!$remote_args->{ca_cert}) {
return undef;
}

my ($fh, $filename) = tempfile();
print $fh $remote_args->{ca_cert};
$fh->flush();
$fh->close();
$remote_args->{ca_file} = $filename;
return $fh;
}

sub apply_schema {
my ( $self, $args) = @_;
my $logger = get_logger();

my ( $status_msg, $result );
my ($mysql_cmd, $fh, $log_strip) = make_mysql_command($args);
my $cmd = "$mysql_cmd < $install_dir/db/pf-schema.sql";
my $db = $args->{database};
eval { $result = pf_run($cmd, (accepted_exit_status => [ 0 ]), log_strip => $log_strip) };
if ( $@ || !defined($result) ) {
$status_msg = ["Error applying the schema to the database [_1]", $db ];
$logger->warn("$@: $result");
return ( $STATUS::INTERNAL_SERVER_ERROR, $status_msg );
}
my @custom_schemas = read_dir( "$install_dir/db/custom", prefix => 1, err_mode => 'quiet' ) ;
@custom_schemas = sort @custom_schemas;
foreach my $custom_schema (@custom_schemas) {
my $cmd = "$mysql_cmd < $custom_schema";
eval { $result = pf_run($cmd, (accepted_exit_status => [ 0 ]), log_strip => $log_strip) };
if ( $@ || !defined($result) ) {
$status_msg = ["Error applying the custom schema $custom_schema to the database [_1]", $db ];
$logger->warn("$@: $result");
return ( $STATUS::INTERNAL_SERVER_ERROR, $status_msg );
}
}

$status_msg = ["Successfully applied the schema to the database [_1]", $db];
# return original status message
return ( $STATUS::OK, $status_msg );
}

=head2 create_database

=cut

sub create_database {
my ( $self, $args ) = @_;
my $db = $args->{database};
my $logger = get_logger();
my ( $status_msg, $result );
my ($dbh, undef, $user) = connect_to_database({%$args, database => ''});
if (!$dbh) {
$status_msg = ["Error in creating the database [_1]",$db];
$logger->warn($DBI::errstr);
return ( $STATUS::INTERNAL_SERVER_ERROR, $status_msg );
}

my $db_quoted = $dbh->quote_identifier($db);
$result = $dbh->do("CREATE DATABASE $db_quoted DEFAULT CHARACTER SET = 'utf8mb4'");
if ( !$result ) {
$status_msg = ["Error in creating the database [_1]", $db];
$logger->warn($DBI::errstr);
return ( $STATUS::INTERNAL_SERVER_ERROR, $status_msg );
}

$status_msg = ["Successfully created the database [_1]", $db];
# return original status message
return ( $STATUS::OK, $status_msg );
}

=head2 secureInstallation

Intended to integrate the "/usr/bin/mysql_secure_installation" steps
Expand Down
11 changes: 5 additions & 6 deletions html/pfappserver/root/src/utils/regex.js
Original file line number Diff line number Diff line change
@@ -1,25 +1,24 @@
export const reAscii = value => /^([\x20-\x7E]+)$/.test(value)

export const reAlphaNumeric = value => /^[a-zA-Z0-9]*$/.test(value)

export const reAlphaNumericHyphenUnderscoreDot = value => /^[a-zA-Z0-9-_.]*$/.test(value)

export const reCommonName = value => /^([A-Z]+|[A-Z]+[0-9A-Z_:]*[0-9A-Z]+)$/i.test(value)

export const reDomain = value => /^((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9*]+\.)+[a-zA-Z]{2,}))$/.test(value)

export const reEmail = value => /(^$|^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$)/.test(value)

export const reDomain = value => /^((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9*]+\.)+[a-zA-Z]{2,}))$/.test(value)
export const reFilename = value => /^[^\\/?%*:|"<>]+$/.test(value)

export const reIpv4 = value => /^(([0-9]{1,3}.){3,3}[0-9]{1,3})$/i.test(value)

export const reIpv6 = value => /^(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))$/i.test(value)

export const reFilename = value => /^[^\\/?%*:|"<>]+$/.test(value)

export const reMac = value => /^([0-9a-fA-F]{2}[-:]?){5,}([0-9a-fA-F]){2}$/.test(value)

export const reNumeric = value => /^-?[0-9]*$/.test(value)

// eslint-disable-next-line no-useless-escape
export const reStaticRoute = value => /^(?:[0-9]{1,3}\.){3}[0-9]{1,3}\/?(\d+)?\s+?(via\s+(?:[0-9]{1,3}\.){3}[0-9]{1,3}\s+?)?dev\s+[a-z,0-9\.]+$/i.test(value)


export const reAscii = value => /^([\x20-\x7E]+)$/.test(value)
10 changes: 9 additions & 1 deletion html/pfappserver/root/src/utils/yup.js
Original file line number Diff line number Diff line change
Expand Up @@ -366,6 +366,14 @@ yup.addMethod(yup.string, 'isFQDN', function (message) {
})
})

yup.addMethod(yup.string, 'isHostname', function (message) {
return this.test({
name: 'isFQDN',
message: message || i18n.t('Invalid hostname.'),
test: value => ['', null, undefined].includes(value) || reIpv4(value) || isFQDN(value)
})
})

yup.addMethod(yup.string, 'isIpv4', function (message) {
return this.test({
name: 'isIpv4',
Expand Down Expand Up @@ -412,7 +420,7 @@ yup.addMethod(yup.string, 'isPort', function (message) {
return this.test({
name: 'isPort',
message: message || i18n.t('Invalid port.'),
test: value => ['', null, undefined].includes(value) || (+value === parseFloat(value) && +value >= 1 && +value <= 65535)
test: value => ['', null, undefined].includes(value) || (+value === parseInt(value) && +value >= 1 && +value <= 65535)
})
})

Expand Down
Loading
Loading