LP2017941 Generate Redis passwords at build time
authorBill Erickson <berickxx@gmail.com>
Mon, 1 May 2023 15:01:51 +0000 (11:01 -0400)
committerBill Erickson <berickxx@gmail.com>
Fri, 12 May 2023 14:52:43 +0000 (10:52 -0400)
Generate passwords as UUIDs at build time.  Apply a password the the
Redis 'default' account for security.

Make Perl %connections cache per-Client, not global.

Signed-off-by: Bill Erickson <berickxx@gmail.com>
.gitignore
README
bin/opensrf-perl.pl.in
configure.ac
examples/redis-accounts.example.txt [deleted file]
examples/redis-accounts.example.txt.in [new file with mode: 0644]
src/perl/lib/OpenSRF/Transport/Redis/Client.pm

index 181b1d1..7203d9b 100644 (file)
@@ -10,6 +10,7 @@ config.sub
 configure
 depcomp
 doc/dokuwiki-doc-stubber.pl
+examples/redis-accounts.example.txt
 examples/math_bench.pl
 examples/math_client.py
 examples/multisession-test.pl
diff --git a/README b/README
index f3e7d9e..e7fbf6e 100644 (file)
--- a/README
+++ b/README
@@ -226,7 +226,7 @@ save ""
 +
 3. Restart the Redis server to make the changes take effect:
 +
-.Starting ejabberd
+.Starting Redis
 [source, bash]
 ---------------------------------------------------------------------------
 systemctl start redis-server
@@ -288,14 +288,25 @@ cp redis-accounts.example.txt redis-accounts.txt
 
 Creating Redis Accounts
 --------------------------------------
-
 Before starting services, it's necessary to create Redis accounts.
 Issue the following command as the *opensrf* Linux account:
-
++
 [source, bash]
 ---------------------------------------------------------------------------
 osrf_control --reset-message-bus
 ---------------------------------------------------------------------------
++
+Accessing the Redis Command Line
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+The script which creates Redis OpenSRF accounts also applies a password
+to the 'default' Redis account for security.  To access the Redis
+command line with full privileges, use the password for the 'default'
+user from the SYSCONFDIR/redis-accounts.txt file.  For example:
++
+[source,bash]
+---------------------------------------------------------------------------
+REDISCLI_AUTH=f0d2ebcc-5a52-49e4-a910-a515144b4141 redis-cli
+---------------------------------------------------------------------------
 
 Starting and stopping OpenSRF services
 --------------------------------------
index 9103b8c..8e0115b 100755 (executable)
@@ -621,16 +621,47 @@ sub do_reset_message_bus {
 
     my $routers = $conf->bootstrap->routers;
 
-    # TODO pull logins for all clients in the conf, including
-    # gateway and router.
-    for my $router (@{$conf->bootstrap->routers}) {
+    my $bus_pass = `grep 'ACL SETUSER default on >' $opt_bus_accounts | cut -d'>' -f2`;
+
+    chomp($bus_pass);
+
+    die "No password for Redis 'default' account found in $opt_bus_accounts\n"
+        unless $bus_pass;
 
+    # Redis prefers the password be passed via ENV.
+    $ENV{REDISCLI_AUTH} = $bus_pass;
+
+    # Apply the bus accounts to all of our domains.
+    for my $router (@{$conf->bootstrap->routers}) {
         my $domain = ref $router ? $router->{domain} : $router;
         my $port = $conf->bootstrap->port;
 
+        # The first time this script runs after installing / rebooting Redis,
+        # the 'default' account will have no password.  Subsequent logins
+        # will use the password defined in our redis-accounts file.  See if
+        # we can figure where we are...
+        my $login = `echo "exit" | redis-cli -h $domain -p $port 2>&1`;
+
+        if ($login =~ /AUTH failed/) {
+            # Login failed.  Clear the password.
+            delete $ENV{REDISCLI_AUTH};
+        } else {
+            # Multiple OpenSRF domains may run on the same Redis instance.
+            # If so, make sure subsequent runs on the same redis instance 
+            # use the just-applied password.  In this case, our $login
+            # var above will be empty, becuase Redis will think we are
+            # trying to login with no authentication, and will later fail
+            # when we try to perform actions that are not allowed.
+            $ENV{REDISCLI_AUTH} = $bus_pass;
+        }
+
         msg("Resetting bus accounts for domain $domain");
 
-        system("cat $opt_bus_accounts | redis-cli -h $domain -p $port > /dev/null");
+        # Grep out some noise.  Avoid piping to /dev/null so we can 
+        # see failures.
+        my $command = "redis-cli -h $domain -p $port | grep -v OK | grep -v ^1";
+
+        system("cat $opt_bus_accounts | $command");
     }
 }
 
index 9bd31b4..542ea75 100644 (file)
@@ -53,6 +53,21 @@ AC_SUBST([PID_DIR])
 AC_SUBST(prefix)
 AC_SUBST(bindir)
 
+OPENSRF_BUS_PASS=$(cat /proc/sys/kernel/random/uuid)
+GATEWAY_BUS_PASS=$(cat /proc/sys/kernel/random/uuid)
+ROUTER_BUS_PASS=$(cat /proc/sys/kernel/random/uuid)
+DEFAULT_BUS_PASS=$(cat /proc/sys/kernel/random/uuid)
+
+AC_DEFINE_UNQUOTED([OPENSRF_BUS_PASS], ["$OPENSRF_BUS_PASS"], [opensrf bus password])
+AC_DEFINE_UNQUOTED([GATEWAY_BUS_PASS], ["$GATEWAY_BUS_PASS"], [gateway bus password])
+AC_DEFINE_UNQUOTED([ROUTER_BUS_PASS], ["$ROUTER_BUS_PASS"], [router bus password])
+AC_DEFINE_UNQUOTED([DEFAULT_BUS_PASS], ["$DEFAULT_BUS_PASS"], [admin bus password])
+
+AC_SUBST([OPENSRF_BUS_PASS])
+AC_SUBST([GATEWAY_BUS_PASS])
+AC_SUBST([ROUTER_BUS_PASS])
+AC_SUBST([DEFAULT_BUS_PASS])
+
 #-------------------------------
 # Installation options
 #-------------------------------
@@ -321,6 +336,7 @@ if test "x$OSRF_INSTALL_CORE" = "xtrue"; then
        #------------------------------------
 
        AC_CONFIG_FILES([doc/dokuwiki-doc-stubber.pl
+                        examples/redis-accounts.example.txt
                         examples/math_bench.pl
                         examples/multisession-test.pl
                         src/c-apps/Makefile
diff --git a/examples/redis-accounts.example.txt b/examples/redis-accounts.example.txt
deleted file mode 100644 (file)
index e2ceeaa..0000000
+++ /dev/null
@@ -1,34 +0,0 @@
-
-SET comment "opensrf clients can perform all opensrf-level actions"
-SET COMMENT "opensrf accounts send requets to opensrf:router:* queues"
-SET COMMENT "opensrf accounts send replies to opensrf:client:* queues"
-SET COMMENT "opensrf accounts lpop requests from their opensrf:servivce: queue."
-SET COMMENT "TODO: separate Listener vs Drone accounts to prevent Drones / standalone clients from accessing opensrf:service:*"
-
-ACL SETUSER opensrf reset
-ACL SETUSER opensrf on >password
-ACL SETUSER opensrf -@all +lpop +blpop +rpush +del ~opensrf:router:* ~opensrf:service:* ~opensrf:client:*
-
-SET comment "routers lpop requests from their own opensrf:router:* queues"
-SET comment "routers send requests to opensrf:service:* queues"
-SET comment "routers send replies to opensrf:client:* queues"
-
-ACL SETUSER router reset
-ACL SETUSER router on >password
-ACL SETUSER router -@all +lpop +blpop +rpush +del ~opensrf:router:* ~opensrf:service:* ~opensrf:client:*
-
-SET comment "gateway accounts send request to opensrf:router:* queues"
-SET comment "gateway accounts send subsequent, stateful requests to opensrf:client:* queues"
-
-ACL SETUSER gateway reset
-ACL SETUSER gateway on >password
-ACL SETUSER gateway -@all +lpop +blpop +rpush +del ~opensrf:router:* ~opensrf:client:*
-
-SET comment "admin can do anything"
-
-ACL SETUSER admin reset
-ACL SETUSER admin on >password
-ACL SETUSER admin +@all ~*
-
-DEL comment
-
diff --git a/examples/redis-accounts.example.txt.in b/examples/redis-accounts.example.txt.in
new file mode 100644 (file)
index 0000000..4d5a6e9
--- /dev/null
@@ -0,0 +1,31 @@
+
+SET comment "opensrf clients can perform all opensrf-level actions"
+SET COMMENT "opensrf accounts send requets to opensrf:router:* queues"
+SET COMMENT "opensrf accounts send replies to opensrf:client:* queues"
+SET COMMENT "opensrf accounts lpop requests from their opensrf:servivce: queue."
+SET COMMENT "TODO: separate Listener vs Drone accounts to prevent Drones / standalone clients from accessing opensrf:service:*"
+
+ACL SETUSER opensrf reset
+ACL SETUSER opensrf on >@OPENSRF_BUS_PASS@
+ACL SETUSER opensrf -@all +lpop +blpop +rpush +del ~opensrf:router:* ~opensrf:service:* ~opensrf:client:*
+
+SET comment "routers lpop requests from their own opensrf:router:* queues"
+SET comment "routers send requests to opensrf:service:* queues"
+SET comment "routers send replies to opensrf:client:* queues"
+
+ACL SETUSER router reset
+ACL SETUSER router on >@ROUTER_BUS_PASS@
+ACL SETUSER router -@all +lpop +blpop +rpush +del ~opensrf:router:* ~opensrf:service:* ~opensrf:client:*
+
+SET comment "gateway accounts send request to opensrf:router:* queues"
+SET comment "gateway accounts send subsequent, stateful requests to opensrf:client:* queues"
+
+ACL SETUSER gateway reset
+ACL SETUSER gateway on >@GATEWAY_BUS_PASS@
+ACL SETUSER gateway -@all +lpop +blpop +rpush +del ~opensrf:router:* ~opensrf:client:*
+
+SET comment "default can do anything"
+SET comment "set default password last so our logged-in account does not break mid-script"
+
+ACL SETUSER default resetpass
+ACL SETUSER default on >@DEFAULT_BUS_PASS@
index 53ac681..7220083 100644 (file)
@@ -9,9 +9,6 @@ use OpenSRF::Transport;
 use OpenSRF::Transport::Redis::Message;
 use OpenSRF::Transport::Redis::BusConnection;
 
-# Map of bus domain names to bus connections.
-my %connections;
-
 # There will only be one Client per process, but each client may
 # have multiple connections.
 my $_singleton;
@@ -22,7 +19,10 @@ sub new {
 
     return $_singleton if $_singleton && !$force;
 
-    my $self = {service => $service};
+    my $self = {
+        service => $service,
+        connections => {},
+    };
 
     bless($self, $class);
 
@@ -70,7 +70,7 @@ sub add_connection {
     );
 
     $connection->set_address();
-    $connections{$domain} = $connection;
+    $self->{connections}->{$domain} = $connection;
     
     $connection->connect;
 
@@ -80,7 +80,7 @@ sub add_connection {
 sub get_connection {
     my ($self, $domain) = @_;
 
-    my $con = $connections{$domain};
+    my $con = $self->{connections}->{$domain};
 
     return $con if $con;
 
@@ -115,18 +115,18 @@ sub primary_domain {
 
 sub primary_connection {
     my $self = shift;
-    return $connections{$self->primary_domain};
+    return $self->{connections}->{$self->primary_domain};
 }
 
 sub disconnect {
     my ($self, $domain) = @_;
 
-    for my $domain (keys %connections) {
-        my $con = $connections{$domain};
+    for my $domain (keys %{$self->{connections}}) {
+        my $con = $self->{connections}->{$domain};
         $con->disconnect($self->primary_domain eq $domain);
-        delete $connections{$domain};
     }
 
+    $self->{connections} = {};
     $_singleton = undef;
 }