Building trust and moving fast in a regulated industry: automated compliance with InSpec

Mehdi El Gueddari
GSK Tech
Published in
14 min readSep 10, 2019

At GSK, we take our responsibilities towards ethics and compliance very seriously, and we have robust policies and compliance processes covering all our operations. In our tech organisation, the recent move towards automated compliance is an example of how we are bringing in new technologies to improve performance whilst maintaining GSK’s values (Patient focus, Respect, Integrity, Transparency) and expectations (Courage, Accountability, Development, Teamwork).

In the wider software community, regulations are often seen as a blocker to speed and innovation. It’s easy to see why. The regulatory landscape is vast, it’s ever-changing and it varies widely between countries and jurisdictions. The financial cost of implementing compliance can be crippling. But the ultimate cost of non-compliance can be terminal.

The challenges of regulatory compliance are being felt across all industries across the globe. For example, GDPR and other global data protection and privacy laws apply to software across all industries. Regulatory compliance is no longer the preserve of regulated industries like pharma or finance — it’s becoming an essential part of building software in any industry.

Our team at GSK comes from diverse backgrounds: a few of us have already worked in regulated industries, most of us haven’t. But one thing we have in common is our shared belief in the DevOps culture and practices. In previous roles we’ve all experienced how embracing DevOps can dramatically speed up software delivery while increasing quality.

So we’ve dived into the world of regulatory compliance to try and see how DevOps could work here. We’re seeking the freedom to innovate and iterate fast on solving our users’ problems, while baking-in regulatory compliance, security and other aspects of quality.

The regulations imposed on pharmaceutical companies can present challenges and act as perceived barriers to innovation. But we believe a lot of this cost and friction can be minimised by the adoption of the DevOps culture and practices.

Here are a few elements of the DevOps culture that we’re found particularly relevant to regulatory compliance:

  • Automate what can be automated.
    Not all regulatory compliance can be automated. But a lot of it can. Automation reduces the amount of manual work required to check for compliance and eliminates human error.
    Automation also allows teams to detect and resolve compliance issues as soon as they happen instead of waiting for weeks or months for a compliance review or audit to highlight them. This not only dramatically reduces the cost and effort of implementing compliance but also enables always-on compliance, which builds trust with internal stakeholders.
  • Shift-left on compliance
    The concept of shifting left on security has rapidly gained momentum in the software industry. This trend involves infosec teams moving from doing security reviews themselves to helping engineering teams build security into the design of their application and build security testing into the application’s automated test suite (shifting security left within the software development lifecycle).
    The same concept can be applied for regulatory compliance. Compliance teams can collaborate with engineering teams to bake compliance in the design of software application and help automate compliance checks within the application’s automated test suite.
    Building a platform that takes care of many compliance issues for them can be a great way to dramatically reduce the regulatory burden on individual engineering teams. The US government had some success doing this. Cloud platform providers can also help by doing a lot of the compliance heavy lifting for you. Here’s how Google Cloud helps meet HIPAA-compliance requirements for example.
    Another way to help engineering teams is to provide them with pre-approved automated test suites they can use to verify the compliance of their application from the day they start building. The example of automated TLS compliance below is a great example of this type of test.
  • Continuous improvement and rugged compliance
    Just like security is never “done”, regulatory compliance is never “done”. Any change can trigger the need to comply with another regulation. And regulations themselves are ever-changing.
    Dealing with an evolving regulatory landscape requires putting in place the right culture and mindset. A mindset of continuous improvement, where engineering teams take ownership of continually improving and expending the scope of their automated regulatory checks. A rugged approach to compliance. And a generative culture where communication flows freely within the company and teams collaborate to achieve a common goal, share the risks and are empowered to make changes.
  • Start small, share and expand
    The idea of automating regulatory compliance can feel overwhelming at first. There’s no magic bullet, no box you can tick to automate it away. Tooling is only a part of the problem — usually the easiest one. The key — and usually the hardest — part is fostering and spreading the right culture across the company.
    This is a common situation faced by teams attempting a DevOps transformation. The latest State of DevOps Report highlighted that successful DevOps transformations tend to start from grassroot efforts that are then shared to other teams and eventually spread throughout the entire company. Rather than a big-bang approach that tries to address all compliance issues at once, start small with something that provides immediate value and demonstrates success. Then grow from there.

We’re still early in our DevOps and automated compliance journey. We’ve made the first steps but have a long way to go to reach the ideals above. We’ll be using this blog to document our journey.

Let’s look at how automated compliance works in practice with a real-world example.

Automated compliance in practice with InSpec

When looking at automating a large, complex system, starting at the edge of the system is a good way to get going and quickly demonstrate value. When it comes to automated compliance, automating infrastructure compliance is a good place to start. A tool like InSpec, an open-source tool created by Chef, can make it really easy to get started.

What is infrastructure compliance?

For example:

  • Is all data encrypted in transit? This can be verified by checking that all web apps mandate the use of HTTPS.
  • Is user data stored in the correct geographical location? This can be verified by checking that cloud storage instances have been created in the expected regions.
  • Is access to production data restricted to those who have a genuine need to access it? This can be verified by checking the permissions granted to specific user groups in your Identity and Access Management system (IAM).

What is InSpec?

InSpec is a cross-platform command-line test runner that runs automated tests written in Ruby. It’s open-source and is actively maintained and evolving under the guidance of Chef.

InSpec comes with an extensive and continually expanded library of helper resources and functions that make it really easy to write infrastructure compliance checks, regardless of where your infrastructure is located (bare metal, AWS, GCP or Azure).

For example, writing a test checking that a web app supports the TLS 1.2 protocol but rejects the older TLS 1.0 and 1.1 protocols is as easy as creating an .rb file with:

… and executing the test with the inspec command-line interface.

Why InSpec?

Since InSpec is just a test runner that runs automated tests, why not write those tests as part of your existing automated test suite? Here’s why:

  • Write less code.
    Writing infrastructure compliance checks like the TLS version check above is non-trivial. InSpec comes with an extensive library of helper resources and functions dedicated to test infrastructure configuration and compliance. This library keeps being improved and expanded by the open-source community. That’s a lot of code you won’t have to write and maintain.
    You might not even have to write your tests! A growing library of ready-made open-source compliance tests is being made available through the DevSec Project. Here is for example an InSpec test suite for a Postgres instance. Or one for a Linux box.
  • Check compliance across the entire application portfolio.
    InSpec is cross-platform and can check for infra compliance on bare metal, AWS, GCP and Azure. Teams can share their InSpec compliance tests with other teams, regardless of the tech stack of their respective application. And compliance teams can provide pre-approved InSpec tests that all engineering teams can run against their app.
  • Prove compliance to auditors.
    InSpec comes with a wide range of reporting formats, including HTML for manual verification and JSON or JUnit for feeding into reporting platforms.
  • Help transition from manual to automated compliance tests.
    InSpec comes with a command-line interface that makes it easy to run test suites on an ad-hoc basis. It can be a great way for teams to start moving from manual compliance checks to automated tests even if they’re not yet in a position to introduce a continuous integration pipeline.
  • Easy to integrate in Continuous Integration (CI) pipelines.
    InSpec is published as both a Ruby package and a Docker image, making it easy to integrate into most CI pipelines. Test results can be written in the JUnit format, which many CI platforms would understand.

InSpec in practice — checking for TLS compliance

Our first foray into automated compliance started with a low-hanging fruit, yet high-impact test: checking the TLS compliance of our web apps. We wanted to assert that a web app:

  • supports HTTPS.
  • support TLS 1.2, rejects older TLS versions and rejects SSL.
  • supports strong ciphers like AES GCM and rejects insecure ones like RC4.

And we wanted to implement it in a way that made it easy for engineering teams to use in their CI pipelines to check the compliance of their apps but also made it easy for our audit and compliance team to run on an ad-hoc basis against any web app. InSpec made it trivial.

A step-by-step guide on writing your first compliance test with InSpec:

1. Install InSpec or run it via Docker

You can install InSpec on Windows, macOS or Linux. On a Mac:

brew cask install chef/chef/inspec

Alternatively, you can run InSpec via its Docker image:

# Example: display the list of available InSpec commands.
docker run -it --rm --volume $(pwd):/share chef/inspec:4.16 help \
--chef-license=accept-silent
# Add the --no-enable-telemetry command-line param if you prefer not
# to send usage information back to Chef.

What’s happening in that Docker command?

The best way to understand this Docker command is to look at the Dockerfile for the InSpec image:

ENTRYPOINT [“inspec”]
Makes InSpec run by default. This is why we can directly pass the inspec command we want to run (help in the example above) and why we need to specify the -it Docker option to interact with inspec running in the image.

WORKDIR /share
Makes /share the current directory in the image. By specifying --volume $(pwd):/share, we’re mounting the current local directory as the /share directory in the Docker image, making our local test files available to the InSpec command running in the Docker image.

In addition, --rm cleans up the container and removes its filesystem after running, ensuring that you always start with a clean image, and --chef-license=accept-silent stops InSpec from prompting you to accept its license at every run.

2. Create an InSpec Profile

An InSpec test suite is called a Profile. Each InSpec profile can be packaged, distributed and executed independently from any other profile.

It’s common practice to create separate InSpec profiles for different compliance aspects. For example, checking the configuration of a Postgres instance and that of an Apache install should be implemented in two separate profiles. This allows engineering teams to pick-and-mix the compliance profiles that are relevant for their particular application.

In its simplest form, an InSpec profile is comprised of just two files laid out as:

The inspec.yml YAML file contains metadata describing the test suite while the .rb Ruby source file contains the tests. This is an example of a minimal inspec.yml file:

3. Write The Tests

To write a test from scratch, browse InSpec’s list of resources. For example, here is the doc for InSpec’s ssl resource, which we’ll use for our TLS compliance checks.

The documentation of InSpec resources is decent and usually includes example usage, which makes it easy to get started. But it can be incomplete. The doc for the ssl resource for example makes it look like it can only be used to test web apps running locally. It’s only when reading the InSpec source code that you’ll discover that you can provide a parameter named host to run the test against any web app and not just local apps.

This is why it’s a good idea to always start by checking if the DevSec project already has an InSpec profile you could reuse. Here is their SSL profile for example.

In our case, we’ll write our TLS compliance from scratch to show how easy InSpec makes it to turn compliance rules into automated tests.

In your controls/tests.rb file:

Follow the InSpec style guide when implementing an InSpec profile. The guide is short and sweet. Following it ensures that your tests will work well with the InSpec tooling and will be easy to maintain by anyone familiar with InSpec.

4. Verify

InSpec comes with a linter that verifies the syntax, structure and metadata of your profile. It flags any issue that may cause your tests to not function as expected. Before running or publishing an InSpec profile, always run it through the InSpec linter first:

# If you installed InSpec locally
inspec check profile/
# If you're running InSpec via Docker
docker run -it --rm --volume $(pwd):/share chef/inspec:4.16 check profile/ \
--chef-license=accept-silent

5. Run the tests

Execute the tests by running InSpec’s exec command against the folder containing your InSpec profile source code. Since InSpec profiles are written in Ruby, there is no compilation or packaging step required.

# If you installed InSpec locally
inspec exec profile/
# If you're running InSpec via Docker
docker run -it --rm --volume $(pwd):/share chef/inspec:4.16 exec profile/ \
--chef-license=accept-silent

By default, InSpec outputs a human-friendly textual output showing a summary of the test run:

6. Make your tests configurable with Inputs

In our test suite above, we’ve hardcoded the test to run against github.com. Since we want to create an InSpec profile that can be reused by teams across the company, we want to let users specify their own list of web hosts to the run the test against.

InSpec enables this with its Inputs feature. Inputs allow you to parameterize tests by passing parameters either as a command-line arguments to the InSpec CLI or via a YAML file. Here, we’ll use a YAML file to let users of our InSpec profile provide a list of web hosts to tests.

In an input.yml file (placed outside of your profile folder. The input file shouldn’t be included in your InSpec profile as it’s meant to be provided at runtime by the user of the InSpec profile):

Each property in the input file can be of any of the built-in Ruby data type. In our case, our host_pairs property is an array of objects (or array of hashes in Ruby parlance) to let users specify a list of web hosts to test.

We can now read this input data in our test code using the input() method that InSpec provides. Replace the code in your controls/tests.rb file with:

Finally, although not mandatory, it’s best practice to declare the type of input data your tests require in your inspec.yml file like so:

Declaring the input data required by your tests in inspec.yml will make it easier for users to discover how to use your InSpec profile.

To run the InSpec test suite against the list of hosts specified in the input.yml, provide the path of your input.yml file via the --input-file command-line parameter:

# This assumes that you're placed the input.yml file in the current directory# If you installed InSpec locally
inspec exec profile/ --input-file ./input.yml
# If you're running InSpec via Docker.
docker run -it --rm --volume $(pwd):/share chef/inspec:4.16 exec profile/ \
--input-file ./input.yml \
--chef-license=accept-silent

We can now see our tests being run against all the hosts specified in the input.yml file:

NOTE: If you run InSpec’s check command against your new InSpec profile, you’ll see that it now outputs a warning:

profile/controls/tests.rb:3: Control tls has no tests defined

This is a limitation of the InSpec linter. By wrapping our tests with a while loop iterating through the list of hosts we want to test, we’re making the execution of these tests dynamic, preventing the InSpec linter from being able to discover them. This limitation is documented in the InSpec style guide.

7. Share with other teams

Now that you have your InSpec profile working, you can share it with other teams. InSpec makes this fairly easy. Two common options would be publishing to a git repo or publishing the InSpec profile as a compressed file.

Users of your InSpec profile can provide the input for the tests (in our example, the list of hosts they want to test) as a local input.yml file and point the InSpec CLI to your published InSpec profile to execute it.

Publish to a git repo
Push your InSpec profile to a git repo, making sure that your inspec.yml file and the controls folder containing your tests are at the root of the repo. Other teams can now execute your InSpec profile by pointing the inspec CLI to to the git repo:

# Via SSH
inspec exec git@github.com:company-name/inspec-tls.git --input-file ./input.yml
# Via HTTPS
inspec exec https://github.com/company-name/inspec-tls.git --input-file ./input.yml
# For private repos
inspec exec https://API_TOKEN@github.com/company-name/inspec-tls.git --input-file ./input.yml

Publish as a compressed file
Compress the folder containing your InSpec profile into a tarball or a zip file and upload it to any file host that can serve files over HTTP (such as Google Cloud Storage, Azure Blob Storage or Amazon S3 ). Other teams can now execute your InSpec profile by pointing the inspec CLI to to its URL:

inspec exec https://webserver/inspec-tls.tar.gz --input-file ./input.yml# If Basic Auth is required
inspec exec https://username:password@webserver/inspec-tls.tar.gz --input-file ./input.yml

Other options
The doc for the InSpec exec command includes the list of all the possible publication options, which include Chef Automate and Chef Supermarket.

8. Integrate in a CI pipeline

While InSpec’s CLI and human-friendly output make it easy for teams to run compliance tests on an ad-hoc basis, integrating InSpec tests in a CI pipeline allows teams to move to always-on compliance where every change is immediately and automatically tested for compliance. This is what enables teams to move fast with confidence.

InSpec is cross-platform and is published as both a Ruby package and a Docker image, so integration into most CI pipelines is straightforward.

When running on a CI server, output the test results in a file in structured format like JUnit XML or JSON. This allows the test results to be recorded in the build artifacts for audit and reporting. You should also output the test results in a human-friendly format to the standard output for inclusion in the build output.

InSpec makes this simple with its --reporter parameter that lets you specify a list of test output formats and locations:

# Output the test results both in a human-friendly format 
# to the standard output ('cli') and in the JUnit XML
# format to the file artifacts/testresults.xml ('junit').
inspec exec profile/ \
--input-file ./input.yml \
--reporter cli junit:artifacts/testresults.xml

When run in Jenkins for example, the JUnit output allows Jenkins to pickup the test results and display them in its Test tab:

Jenkins test tab showing the results of InSpec tests.

Conclusion

Despite its simplicity, the TLS Compliance InSpec profile above is an example of real-world automated compliance. With this, we’ve explored:

  • The rapidly growing library of InSpec resources that makes it possible to write automated compliance checks on many aspects of your GCP, Azure, AWS and on-prem infrastructure.
  • The DecSec project that publishes a growing list of open-source and ready-made InSpec profiles allowing you to check the configuration of the most common OSes and stacks.
  • How the InSpec CLI and human-friendly test output makes it easy to run ad-hoc compliance checks.
  • How its frictionless integration with CI pipelines makes always-on compliance possible.
  • How its cross-platform nature, input feature and publication options make it possible to share InSpec profiles with other teams and spread the DevOps and automated compliance ethos.

We’re still very early in our automated compliance journey but our experience with InSpec so far has been great. InSpec also supports more advanced setups via features like Profile Dependencies that should make scaling to larger test suites possible.

--

--

Mehdi El Gueddari
GSK Tech

Principal Engineer at GSK. Loves distributed systems, beautiful prose, period properties and the Irish weather.