Problems with csrf token when running pure container rstudio image

Greetings - I am trying to get an interactive application working for the lates Rocker rstudio singularity container. The container starts cleanly but when I click the ‘Connect to Rstudio’ button, it launches a new tab with a sign on error. Checking output.log, I see an error like this:

2022-01-11T21:52:16.353468Z [rserver] ERROR Failed to validate sign-in with invalid CSRF form; LOGGED FROM: bool rstudio::server::auth::common::validateSignIn(const rstudio::core::http::Request&, rstudio::core::http::Response*) src/cpp/server/auth/ServerAuthCommon.cpp:132

Taking a look with the Firefox inspector at the connect button, I see that the token seems to be getting passed:

<input id="csrfToken" type="hidden" name="csrf-token" value="1b6b3890-e329-4eb6-8ea2-c096a65bcf64">

Here are (what I think are) the key files.
submit.yml.erb :

---
batch_connect:
  template: "basic"
  conn_params:
    - csrf_token

view.html.erb:

<script>
  var hostButton=$( "a.btn.btn-primary.btn-sm.fas.fa-terminal" );
  var hostname=hostButton.text();
  hostButton.replaceWith(hostname);
  document.cookie = "csrf-token=<%= csrf_token %>; path=/rnode/<%= host %>/<%= port %>; secure";`
</script>
<form action="/rnode/<%= host %>/<%= port %>/auth-do-sign-in" method="post" target="_blank">
  <input type="hidden" name="username" value="<%= ENV["USER"] %>"/>
  <input type="hidden" name="password" value="<%= password %>"/>
  <input type="hidden" name="staySignedIn" value="1"/>
  <input type="hidden" name="appUri" value=""/>
  <input id="csrfToken" type="hidden" name="csrf-token" value="<%= csrf_token %>"/>
  <button class="btn btn-primary" type="submit">
    <i class="fab fa-r-project"></i> Connect to RStudio Server
  </button>
</form>

before.sh.erb:

# Export the module function if it exists
[[ $(type -t module) == "function" ]] && export -f module

# Find available port to run server on
port=$(find_port ${host})

# Define a password and export it for RStudio authentication
password="$(create_passwd 16)"
export RSTUDIO_PASSWORD="${password}"

# For v1.4
<%-
  require 'securerandom'
  csrftoken=SecureRandom.uuid
-%>
export csrf_token="<%= csrftoken %>"

script.sh.erb:

#!/usr/bin/env bash

# Load the required environment
setup_env () {
  # Additional environment which could be moved into a module
  # Change these to suit
  workdir="$HOME/rstudio-server"
  export RSTUDIO_SERVER_IMAGE="$HOME/singularity-images/rstudio-server.sif"
  export SINGULARITY_BINDPATH="$workdir/run:/run,$workdir/var/lib/rstudio-server:/var/lib/rstudio-server,$workdir/database.conf:/etc/rstudio/database.conf,$workdir/home:/home/rstudio,/sys/fs/cgroup"
#  export PATH="$PATH:/usr/lib/rstudio-server/bin"
#  export SINGULARITYENV_PATH="$PATH"
  # In Singularity 3.5.x it became necessary to explicitly pass LD_LIBRARY_PATH
  # to the singularity process
  export SINGULARITYENV_LD_LIBRARY_PATH="$LD_LIBRARY_PATH"
}
setup_env

#
# Set up bind directories
#
mkdir -p -m 700 ${workdir}/run ${workdir}/var/lib/rstudio-server ${workdir}/home
cat > ${workdir}/database.conf <<END
provider=sqlite
directory=/var/lib/rstudio-server
END

#
# Start RStudio Server
#

# PAM auth helper used by RStudio
export RSTUDIO_AUTH="${PWD}/bin/auth"

# Let's use a consistent personal library location
export R_LIBS_USER=${workdir}/home/R/rocker-rstudio/4.1 # image specific 

# Generate an `rsession` wrapper script
export RSESSION_WRAPPER_FILE="${PWD}/rsession.sh"
(
umask 077
sed 's/^ \{2\}//' > "${RSESSION_WRAPPER_FILE}" << EOL
  #!/usr/bin/env bash
  export OMP_NUM_THREADS=${SLURM_JOB_CPUS_PER_NODE} # SLURM specific

  # Launch the original command
  echo "Launching rsession..."
  exec rsession "\${@}"
EOL
)
chmod 700 "${RSESSION_WRAPPER_FILE}"

# Set working directory to home directory
cd "${HOME}"

export myTMPDIR="$TMPDIR"
mkdir -p "$myTMPDIR/rstudio-server"

# Make myTMPDIR writeable
chmod -R 777 $myTMPDIR  
chmod -R 777 $TMPDIR

python -c 'from uuid import uuid4; print(uuid4())' > "$myTMPDIR/rstudio-server/secure-cookie-key"
chmod 0600 "$myTMPDIR/rstudio-server/secure-cookie-key"
export SECURE_COOKIE_KEY="${myTMPDIR}/rstudio-server/secure-cookie-key"

set -x
# Launch the RStudio Server
echo "Starting up rserver..."

singularity run -B "$myTMPDIR:/tmp,$myTMPDIR" "$RSTUDIO_SERVER_IMAGE" \
 rserver --www-port "${port}" \
         --auth-none 0 \
         --auth-pam-helper-path "${RSTUDIO_AUTH}" \
         --auth-minimum-user-id=500 \
         --auth-encrypt-password 0 \
         --rsession-path "${RSESSION_WRAPPER_FILE}" \
         --secure-cookie-key-file "${SECURE_COOKIE_KEY}" \
         --server-user $USER

echo 'Singularity as exited...'

And finally, connection.yml, for this example, looked like this:

csrf_token: 1b6b3890-e329-4eb6-8ea2-c096a65bcf64
host: c4-n12
port: 9051
password: 6wde4T0irIaOKy92

So, I am struggling to understand how this token is supposed to be used and why rstudio server considers this one an invalid CSRF form. Any pointers would be greatly appreciated.

Hi and thanks for posting!

Unfortunately we are not able to replicate this at this time but we are in the process of upgrading our systems to be able to troubleshoot this problem.

I’m going to replicate the issue ASAP and will make an update when we can more definitively understand what Rstudio is doing during the authentication with those varying pieces of the form.

In short, we are having the same questions and are actively investigating them and will keep you posted.

1 Like

Hi,

What does your template/bin/auth file look like? Older versions of that file only accept 1 argument
but since rstudio-server-1.3 it is called with 3 arguments. We still only care about the first argument.

Hello and thanks for your reply. The auth file:

#!/usr/bin/env bash

# Confirm username is supplied
if [[ $# -ne 1 ]]; then
  echo "Usage: auth USERNAME"
  exit 1
fi
USERNAME="${1}"

# Confirm password environment variable exists
if [[ -z ${RSTUDIO_PASSWORD} ]]; then
  echo "The environment variable RSTUDIO_PASSWORD is not set"
  exit 1
fi

# Read in the password from user
read -s -p "Password: " PASSWORD
echo ""

if [[ ${USERNAME} == ${USER} && ${PASSWORD} == ${RSTUDIO_PASSWORD} ]]; then
  echo "Successful authentication"
  exit 0
else
  echo "Invalid authentication"
  exit 1
fi

Update -

I replaced the -ne with -lt in the first if block of the auth file. I still see the token errors after that. I did just turn off authentication altogether with the --auth-none 1 rserver option and the page opens and rstudio session, I do still see the CSRF errors in output.log:

2022-01-14T22:16:58.348008Z [rserver] ERROR Failed to validate sign-in with invalid CSRF form; LOGGED FROM: bool rstudio::server::auth::common::validateSignIn(const rstudio::core::http::Request&, rstudio::core::http::Response*) src/cpp/server/auth/ServerAuthCommon.cpp:132
Launching rsession...
2022-01-14T22:20:40.650624Z [rserver] ERROR Failed to validate sign-in with invalid CSRF form; LOGGED FROM: bool rstudio::server::auth::common::validateSignIn(const rstudio::core::http::Request&, rstudio::core::http::Response*) src/cpp/server/auth/ServerAuthCommon.cpp:132