Dependency Checking Your Ruby Application
Checking your application’s dependencies for known vulnerabilities is a critical, relatively low effort step you should take to secure your application, which you may have read about in another recent article: What is SCA?
Compared to the wealth of tools used for dependency checking in, for example JavaScript, there’s not nearly as much information out there about dependency checks for Ruby. To help remedy that, today I’m going to explore options for checking a Ruby application’s dependencies in a simple Jenkins pipeline. Both tools we’ll be looking at analyze a project’s Gemfile.lock, generated by bundler after a `bundle install` run, checking against some database of vulnerabilities.
For some initial setup, I’m going to be using a Jenkins server running on top of a Kubernetes cluster, as featured in a previous blog. The important detail for this exercise is that we define a Ruby Docker image in a container template, which we’ll use to install and analyze dependencies:
podTemplate([
containers: [
containerTemplate(name: 'ruby', image: 'ruby:2.6.3', command: 'cat', ttyEnabled: true, runAsUser: '0'),
],
volumes: [persistentVolumeClaim(mountPath: '/root', claimName: 'ruby-pipeline-jenkins-agent', readOnly: false)]
]) {
node (POD_LABEL) {
/*
* rest of pipeline here
*/
}
}
The underlying persistent volumes and jenkins agent are configured through helm charts, which you can read more about in the aforementioned post.
Next, we need to check out a codebase, and use this ruby container to install dependencies and the bundler-audit tool:
stage('Checkout code') {
checkout([
$class: 'GitSCM',
branches: [[name: '*/master']],
doGenerateSubmoduleConfigurations: false,
extensions: [],
submoduleCfg: [],
userRemoteConfigs: [[
credentialsId: 'git-<project>-ssh-token',
url: '[email protected]:org/project/your-repo.git'
]]
])
}
stage('Install Dependencies') {
container('ruby') {
sh 'gem install bundler:2.0.2 \
&& bundle install \
&& gem install bundler-audit'
}
}
Finally, reusing this container, it’s time to run bundler-audit. The codebase, checked out outside of this container, is still accessible due to shared volumes.
container('ruby') {
Stage ('Dependency Check') {
status = sh(script: 'bundle-audit check --update', returnStatus: true)
println "Dependency check status: ${status}"
}
}
After a few minutes and a run of the pipeline, we should have some results. The results should look something like the following Jenkins’ console log output:
bundle-audit check --update
Updating ruby-advisory-db ...
From https://github.com/rubysec/ruby-advisory-db
* branch master -> FETCH_HEAD
Already up to date.
Updated ruby-advisory-db
ruby-advisory-db: 430 advisories
Name: actionview
Version: 5.2.3
Advisory: CVE-2020-5267
Criticality: Unknown
URL: https://groups.google.com/forum/#!topic/rubyonrails-security/55reWMM_Pg8
Title: Possible XSS vulnerability in ActionView
Solution: upgrade to ~> 5.2.4, >= 5.2.4.2, >= 6.0.2.2
Vulnerabilities found!
This is a bit less than ideal, and can’t seem to be configured to output any differently. It won’t be as simple, but we can configure Jenkins to use the OWASP dependency check CLI.
To do this, we’ll add the Dependency Check Plugin to your Jenkins instance, and configure it in Global Tool Configuration.
The ‘name’ field will be ‘dependency-check’, and we’ll install version 5.3.0 automatically from dl.bintray.com. It will also require a jdk installation, so make sure that is also configured in global tools.
This plugin will run OWASP Dependency Check, which in turn will actually utilize the bundler-audit installation, and incorporate the results into its detailed generated reports.
With the dependency check plugin configured, we can now modify the dependency checking stage to use this tool.
container('ruby') {
Stage ('Dependency Check') {
def openjdk = tool 'openjdk'
env.JAVA_HOME = "${openjdk}"
def dependencyCheck = tool 'dependency-check'
sh "${dependencyCheck}/bin/dependency-check.sh --scan Gemfile.lock --format ALL --out . --data /root/owasp-cve-db"
archiveArtifacts "dependency-check-report.**"
}
}
The pipeline will now generate html, csv, json, and xml reports and archive them. You can hone the run arguments to just the output types needed, either html for easy viewing, or one of the others to integrate with other tools you may be using to aggregate or visualize results, such as SonarQube.
Hopefully, this example has illustrated how relatively simple setting up dependency checking for a Ruby application can be.