Improving the Secured Computer-based Testing Environment for R on CentOS 7 using Rprofile and Renviron

Editor's Note: The contents and improvements shown within this post were made possible by the understanding and encouragement of the Computer-based Testing Facility (CBTF) staff. Please see the acknowledgements section at the end.

Intro

Last year, the Department of Statistics established a collaborative relationship with the College of Engineering at the University of Illinois' Computer-based Testing Facility (CBTF). Through the collaboration, we were able to use the CBTF to assess students on a secured computational environment. For more details on the original onboarding work to establish an offline secure R and RStudio configuration for CentOS 7, please see last year's post R and RStudio in a Secured Environment on CentOS 7.

Through this undertaking, a pedagogical shift in testing was possible. STAT faculty and students were able to test programming knowledge in an analyst environment instead of using in-class written exams and take-home exams. Understandably, this produced better outcomes for both parties since the environment students used to complete course work was available to them during the examination and each student was being held accountable for their understanding of the material. (Note: Take home exams are notorious for "discussions" among students.)

Following the initial pilot program in Spring 2018, we've seen adoption of the CBTF in courses outside of STAT 385. In particular, the following STAT courses have started to use the CBTF for assessment purposes:

  • STAT 440: Data Management (Fall 18)
  • STAT 430: Topics in Applied Statistics - Data Science Programming Methods (Spring 2019)
  • STAT 430: Topics in Applied Statistics - Data Science Discoveries (Spring 2019)
  • STAT 432: Introduction to Statistical Learning (Fall 2019)

With the background and progress update done, there is always "one more" item that lurks around the corner. Unfortunately, that item is a bit of a bummer: the initial configuration was problematic.

Environment Woes

As more students began using the R and RStudio configuration, they were doing so on a foreign environment that didn't align with their expectations. Students always attempted to install packages with install.packages() prior to accessing them using either library() or require(). In addition, the crippled environment displayed a notably distracting error on startup since it showed a bogus secure HTTPS warning that had a copious amount of red text.

CBTF Environment Initialization Spring 2018

How could this be resolved?

Startup Procedures for R and RStudio.

The secure HTTPS warning problem begins when students launch RStudio. So, it would be useful to understand the underlying startup procedure between both R and RStudio. Luckily, Thomas Pedersen decided to chart out R's startup procedures outlined in the ?Startup help documentation:

Visual Representation of _R_'s Startup Procedure by Thomas Pedersen

In short, there are two important files in play during R's startup:

  1. .Renviron that set environmental variables
  2. .Rprofile that executes R code

The modifications we seek to implement will rely heavily on both of these files.

RStudio's startup procedures begins immediately after the R session, including startup, is finished. Their procedure revolves around establishing "hooks" into the R session that allow different features of its IDE to function. Unfortunately, there is no way to automatically override all of the established hooks and documentation for each override is scattered on https://support.rstudio.com as no official manual for the Desktop version exists.

Modifying R and RStudio startup with .Rprofile and .Renviron

Under the previous secure environment configuration, only the .Rprofile file was. It's use case was to set the CRAN repository to a local folder using the repo option in options(). As a result, all install.packages() requests were redirected and, thus, a time out was prevented when the HTTPS connection to CRAN could not be established due to a lack of internet access.

To begin, the logic being used for the .Rprofile needs to be reworked. Instead of being free floating, the logic will be contained within the .First() function that is ran first. This prevents any side effects from the startup script cluttering the global environment.

From there, an empty CRAN directory will be created in the user's home directory if not present. This handles the condition of the home directory being wiped on log out or after 72 hours.

In addition, the start up procedure will now alert users that package installation is disabled and a suite of packages have been pre-installed. As a side benefit, there will be a shim set up to intercept the install.package() command -- if run during an R GUI or console session -- that re-displays the package disabled message.

.Rprofile (in full)

.First = function() {
    ## Local CRAN ----

    ## Initialize an empty local CRAN repository.
    ## Note: In this setup, we are not placing package
    ## sources or binaries into this location.
    local_user_cran = file.path(Sys.getenv("HOME"), "cran")
    dir.create(local_user_cran, showWarnings = FALSE)

    ## Set the location of packages to the empty local CRAN.
    ## Ensure that the appropriate RStudio Secure warning is handled.
    options(repos = c("CRAN" = paste0("file://", local_user_cran)))

    # Exit if a human isn't present (e.g. run from an Rscript / R CMD)
    if (!interactive()) {
        return()
    }

    ## CBTF Help Messages ----

    ## Hard coded help documentation location
    ## TODO: Update to where docs are on the CBTF website!!

    cbtf_help_url = "https://cbtf.engr.illinois.edu/home.html"

    ## Open the URL for students to view help documentation
    help_cbtf = function(url = cbtf_help_url) {
        message("Opening help documentation at: ", url)
        utils::browseURL(url)
    }

    ## Place the help function into the global environment
    ## TODO: Enable when documentation is present
    # base::assign("help_cbtf", help_cbtf, envir = globalenv())

    ## Provide startup messages in red text to indicate the status of
    ## package installation and environment specific documentation

    cbtf_disabled_cran_msg = function() {
        message("Note: ")
        message(strwrap("Installing packages from CRAN is disabled."))
        message(strwrap("All required R packages have already been installed.\n"))
    }

    cbtf_documentation_msg = function() {
        message(
            strwrap(
                paste0("For help with R and RStudio in the CBTF, please consult",
                       " the help guide by using the `help_cbtf()` function.")
                , prefix = "\n"
            )
        )
    }

    cbtf_welcome_msg = function() {
        cbtf_disabled_cran_msg()
        # TODO: Enable when help docs are on the CBTF website!
        # cbtf_documentation_msg()
    }

    ## Disable install.packages ----

    ## Override an R function in a package.
    shim_pkg_func = function(name, pkgname, value) {

        ## Ensure package is loaded.
        ## If the package is not on the search path, we cannot modify it!
        pkg_env_name = paste0("package:", pkgname)
        if (!pkg_env_name %in% search()) {
            # Load the library
            library(pkgname, character.only = TRUE)
        }

        # Retrieve package environment
        env = as.environment(pkg_env_name)
        #pkg_ns_env = asNamespace(pkgname)

        # Unlock environment where the function/variable is found.
        base::unlockBinding(name, env)

        # Make the assignment into the environment with the new value
        base::assign(name, value, envir = env)

        # Close the environment
        base::lockBinding(name, env)
    }

    ## Provide an alternative install.packages(...) routine
    install_packages_shim = function(...) { cbtf_disabled_cran_msg() }

    ## Setup a shim
    ##
    ## Note: RStudio will overwrite the shim due to the initialization procedure.
    ## Only valid in _R_ terminal sessions or R GUI.
    shim_pkg_func("install.packages", "utils", install_packages_shim)

    ## Display the welcome bumper
    cbtf_welcome_msg()
}

Now, the attention switches to the .Renviron file to disable specific features of the RStudio IDE that require internet access. The environment variables are found on two separate help documentation pages: Secure Package Downloads for R and Disabling RStudio Features. Note: RStudio Server Pro configuration has a different set of variables.

.Renviron (in full)

## Environment variables used here are primarily for RStudio Desktop
## RStudio Server Pro configuration variables can be found here:
## https://docs.rstudio.com/ide/server-pro/latest/r-sessions.html

## Disable the secure download warning
## c.f. https://support.rstudio.com/hc/en-us/articles/206827897-Secure-Package-Downloads-for-R
## Ticket: https://github.com/rstudio/rstudio/issues/4461
RSTUDIO_DISABLE_SECURE_DOWNLOAD_WARNING="1"

## Disabling R Studio features
## c.f. https://support.rstudio.com/hc/en-us/articles/210990438-Disabling-RStudio-Features

## Disables RStudio Calling Home for Updates
RSTUDIO_DISABLE_CHECK_FOR_UPDATES="1"

## Disables RStudio's package panel but NOT installing packages...
## This requires crippling the CRAN repo and inserting a shim for install.packages
## Note: Not ideal if students use it to load packages via checkbox.
# RSTUDIO_DISABLE_PACKAGES="1"

## Disables publishing Rmds or Shiny apps to RPubs/shinyapps.io
RSTUDIO_DISABLE_EXTERNAL_PUBLISH="1"

Fin

The offline experience for both R and RStudio should be greatly improved. For our implementation, students are now presented with:

CBTF Startup Procedure

In the future, we hope that RStudio will provide the ability to set a startup hook.

Acknowledgements

  • Patrick Bailey
    • supporting and assistance with deployment of software on CBTF machines.
  • David Mussulman
    • CBTF introduction, pedgagoy discussions, and feedback.
  • Carleen Sacris
    • attending meetings regarding accommodations and providing support for exam takers.
  • Craig Zilles and Matthew West
    • establishing the CBTF and handling operations.
  • All the proctors!

Side-effects

During the process of writing this post, we hoped to contribute elsewhere in the R community. As a result, the following was created:

comments powered by Disqus