Understanding InSpec Profile Inheritance

Engineers sometimes use the phrase DRYDon’t Repeat Yourself. It’s commonly used when writing or reviewing code that is repetitive. The usual solution is to take the repetitious code and turn it into something that can be reused.

It’s really easy to fall into the “repeat yourself” trap, even with a tool like InSpec. InSpec Profiles contain some number of controls and tests that describe the compliance status of a host or system. It’s likely that all hosts in a fleet need to be tested against the same compliance rules. But what if one host has a legitimate reason to fail a particular test? Or what if a test for a group of hosts is considered a lower severity failure than a different group of hosts? Don’t fork/copy that profile just yet!

Using Profile Inheritance

In addition to its own controls, an InSpec profile can bring in the controls from another InSpec profile. Additionally, when inheriting the controls of another profile, a profile can skip or even modify those included controls.

When a profile includes controls from another profile, it is usually referred to as a “meta profile” or a “profile overlay.” Those of us with Chef background sometimes call it a “wrapper profile,” a loving reference to the “wrapper cookbook” pattern so many of our Chef community members are fond of.

Defining the Dependencies

Before a profile can use controls from another profile, the to-be-included profile needs to be specified in the including profile’s inspec.yml file in the depends section. For each profile to be included, a location for the profile from where to be fetched and a name for the profile should be included. For example:

depends:
 - name: linux-baseline
   url: https://github.com/dev-sec/linux-baseline/archive/master.tar.gz
 - name: ssh-baseline
   url: https://github.com/dev-sec/ssh-baseline/archive/master.tar.gz

Once defined in the inspec.yml, controls from the included profiles can be used! Let’s look at some examples.

Including All Controls from a Profile

With the include_controls command in a profile, all controls from the named profile will be executed every time the including profile is executed.

In the example above, every time my-app-profile is executed, all the controls from my-baseline are also executed. Therefore, the following controls would be executed:

  • myapp-1
  • myapp-2
  • myapp-3
  • baseline-1
  • baseline-2

This is a great reminder that having a good naming convention for your controls is helpful to avoid confusion when
including controls from other profiles!

Skipping a Control from a Profile

What if one of the controls from the included profile does not apply to your environment? Luckily, it is not necessary to maintain a slightly-modified copy of the included profile just to delete a control. The skip_control command tells InSpec to not run a particular control.

In the above example, all controls from my-app-profile and my-baseline profile will be executed every time my-app-profile is executed except for control baseline-2 from the my-baseline profile.

Modifying a Control

Let’s say a particular control from an included profile should still be run, but the impact isn’t appropriate? Perhaps the test should still run, but if it fails, it should be treated as low severity instead of high severity?

When a control is included, it can also be modified!

In the above example, all controls from my-baseline are executed along with all the controls from the including profile, my-app-profile. However, should control baseline-1 fail, it will be raised with an impact of 0.5 instead of the originally-intended impact of 1.0.

Selectively Including Controls from a Profile

If there are only a handful of controls that should be executed from an included profile, it’s not necessary to skip all the unneeded controls, or worse, copy/paste those controls bit-for-bit into your profile. Instead, use the require_controls command.

Whenever my-app-profile is executed, in addition to its own controls, it will run only the controls specified in the require_controls block. In the case, the following controls would be executed:

  • myapp-1
  • myapp-2
  • myapp-3
  • baseline-2
  • baseline-4

Controls baseline-1, baseline-3, and baseline-5 would not be run, just as if they were manually skipped. This method of including specific controls ensures only the controls specified are executed; if new controls are added to a later version of my-baseline, they would not be run.

And, just the way its possible to modify controls when using include_controls, controls can be modified as well.

As with the prior example, only baseline-2 and baseline-4 are executed, but if baseline-2 fails, it will report with an impact of 0.5 instead of the originally-intended 1.0 impact.

An Example Profile

I have published a real InSpec profile that uses the above tips and tricks. Look at the controls/controls_from_other_profiles.rb control file for similar examples to those I’ve provided above in this post.

Let’s go reuse!

Despite its somewhat-scary name, profile inheritance isn’t scary at all! In fact, it will help keep profile sprawl to a minimum and help promote profile reuse and creation of reusable profiles.

Do you have more questions? Don’t be shy! Swing by the #inspec channel of the Chef Community Slack and let us know how we can help.

Author Adam Leff

Adam Leff is the Technical Community Advocate for InSpec at Chef. Prior to joining Chef, Adam held engineering and management positions at WebMD. He lives in Northern VA with his family and can frequently be found at local DevOps meetups in the DC area.