How to Use the Fugue Compliance Suite, Part 2: Design-Time

Note

To see the full list of Compliance Suite validations and the compliance standards they map to, install the Fugue Client Tools and view the Ludwig modules locally:

  • /opt/fugue/lib on macOS and Linux
  • C:\Program Files\Fugue\lib\ on Windows

Overview

Welcome to Part 2 of How to Use the Fugue Compliance Suite.

In Part 1, we demonstrated how to use the Fugue Compliance Suite to enforce security and regulatory standards at runtime.

Here in Part 2, we’ll go over using the Compliance Suite at design-time. We’ll also show how to customize a compliance library for your organization’s needs.

Part 1 is not a prerequisite for this exercise, but we encourage you to review it if you’re interested in runtime validations.

Download

You can download the source code for this example from our Github repo:

If you executed init during the Quick Setup, download the files to the directory where you ran the command.

Get editor plug-ins here.

Let’s Go!

Use Case: Applying the NIST.Account Module at Design-Time

Step 1: Select Compliance Standard

Let’s say you are drafting a composition and you know you’ll eventually need it to pass the Fugue.Compliance.NIST.Account standard. You don’t want to wait until you’re done writing it to test it! You need a short feedback loop – if the infrastructure you are declaring violates Fugue.Compliance.NIST.Account, you’d prefer to know very quickly. This is a good use case for design-time validations.

Step 2: Copy and Customize Library (Optional)

We’ll apply the Fugue.Compliance.NIST.Account module without changes, so we don’t need to pick and choose our validations (but don’t worry, we’ll demonstrate that a little later). We don’t need to make a copy of the compliance module either because we are not uploading it anywhere. For now, let’s continue to Step 3.

Step 3: Apply Validations

Account-level standards are to be applied at design-time. To validate a composition at design-time, we need to import the compliance library into the composition.

Let’s start with a very simple example. This composition only has two lines of code. We’ll call it NISTAccount.lw:

composition

import Fugue.Compliance.NIST.Account

(Download NISTAccount.lw here.)

By importing the Compliance Suite library with import Fugue.Compliance.NIST.Account, we ensure the composition is tested against the library every time it is compiled.

As you can see, the composition doesn’t declare any infrastructure yet. Let’s test our design-time validation by compiling it with lwc, the Ludwig compiler:

lwc NISTAccount.lw

We can see right away that our composition has a problem. It is not compliant with several NIST policies:

ludwig (validation error):
  Validation failed:

  CloudTrail must be enabled in all regions and CloudTrail log file validation must be enabled.

  - NIST-800-53_AC-2g;NIST-800-53_AC-2 (7)(b);
  - NIST-800-53_AC-2g;NIST-800-53_AC-6 (9)
  - NIST-800-53_AC-6 (9);NIST-800-53_AU-3;NIST-800-53_SI-4 (20)

  (from Fugue.AWS.CloudTrail.Compliance.allRegionsLoggingEnabled)

The error message explains why the validation failed, indicates which policies were violated, and lists the name of the validation function – in this case, Fugue.AWS.CloudTrail.Compliance.allRegionsLoggingEnabled. To make our composition compliant, we’ll need to enable CloudTrail and also turn on log file validation.

Step 4: Resolve Errors and Reapply (if applicable)

Let’s add some statements to our composition to enable CloudTrail. We’ll add a CloudTrail.Trail and an S3.Bucket, and we’ll also set the trail’s enableLogFileValidation argument to True.

composition

import Fugue.Compliance.NIST.Account

import Fugue.AWS as AWS
import Fugue.AWS.CloudTrail as CloudTrail
import Fugue.AWS.S3 as S3

bucket: S3.Bucket.new {
  name: "example-bucket",
  region: AWS.Us-east-1,
}

trail: CloudTrail.Trail.new {
  name: "trail",
  includeGlobalServiceEvents: True,
  isMultiRegionTrail: True,
  targetBucket: bucket,
  region: AWS.Us-east-1,
  enableLogging: True,
  enableLogFileValidation: True,
}

(Download NISTAccountPartial.lw here.)

If we compile the updated composition lwc NISTAccountPartial.lw, we have some new validation errors:

ludwig (validation error):
  "NISTAccount.lw" (line 9, column 9):
  Validation failed:

     9| bucket: S3.Bucket.new {
    10|   name: "example-bucket",
    11|   region: AWS.Us-east-1,
    12| }

  Server side encryption policy is required for S3 buckets.

  - NIST-800-53_SC-13

  (from Fugue.AWS.S3.Compliance.bucketRequireServerSideEncryption)

ludwig (validation error):
  "NISTAccount.lw" (line 14, column 8):
  Validation failed:

    14| trail: CloudTrail.Trail.new {
    15|   name: "trail",
    16|   includeGlobalServiceEvents: True,
    17|   isMultiRegionTrail: True,
    18|   targetBucket: bucket,
    19|   region: AWS.Us-east-1,
    20|   enableLogging: True,
    21|   enableLogFileValidation: True,
    22| }

  CloudTrail trails must be integrated with CloudWatch logs.

  - NIST-800-53_AC-2g

  (from Fugue.AWS.CloudTrail.Compliance.logsToCloudWatch)

Now we know we need a server-side encryption policy for our S3.Bucket (NIST-800-53_SC-13) and a CloudWatch.Logs.LogGroup for our CloudTrail.Trail (NIST-800-53_AC-2g).

We use a typed IAM policy to enable server-side encryption. We also reference an externally-managed KMS.Key that will be used to encrypt log data and an externally-managed IAM.Role for the CloudWatchLogsRole:

composition

import Fugue.Compliance.NIST.Account

import Fugue.AWS.KMS as KMS
import Fugue.AWS as AWS
import Fugue.AWS.IAM.Typed.Policy as Policy
import Fugue.AWS.IAM.Typed.Condition as Condition
import Fugue.AWS.IAM.Typed as Typed
import Fugue.AWS.CloudTrail as CloudTrail
import Fugue.AWS.CloudWatch.Logs as Logs
import Fugue.AWS.S3 as S3
import Fugue.AWS.IAM as IAM

bucket: S3.Bucket.new {
  name: "example-bucket",
  region: AWS.Us-east-1,
  policy: Policy.toString (Policy.new{
    version: Policy.V2012-10-17,
    statements: [
      Policy.deny {
        principals: [ Typed.Wildcard.wildcard ],
        actions:    [ "s3:PutObject" ],
        resources:  [ "arn:aws:s3:::example-bucket/*" ],
        conditions: [
          Condition.null("s3:x-amz-server-side-encryption"),
          Condition.stringNotEquals("s3:x-amz-server-side-encryption", "AES256")
        ]
      }
    ]
  })
}

trail: CloudTrail.Trail.new {
  name: "trail",
  includeGlobalServiceEvents: True,
  isMultiRegionTrail: True,
  targetBucket: bucket,
  region: AWS.Us-east-1,
  enableLogging: True,
  enableLogFileValidation: True,
  logGroup: logGroup,
  cloudWatchLogsRole: IAM.Role.external("CloudWatchLogsRole")
}

logGroup: Logs.LogGroup.new {
  name: "loggroup",
  retentionInDays: 30,
  encryptionKey: KMS.Key.external("86d67225-5385-412c-9a4e-cec4823f341f", AWS.Us-east-1),
  region: AWS.Us-east-1
}

(Download NISTAccountFixed.lw here.)

Now if we try lwc NISTAccountFixed.lw, we’ll see that the composition passes validation and successfully compiles, returning without output.

Use Case: Excluding or Including Specific Validations From a Library

Step 1: Select Compliance Standard

In some cases, it may be unnecessary or undesirable to adopt every validation in a compliance standard. Perhaps you don’t use a particular AWS region, so you want to remove the CIS validation that requires CloudTrail to be enabled in all regions. On the other hand, your infrastructure might not need to adhere to HIPAA, but some of HIPAA’s regulations might still align with your company’s policies. In this case, you may want to collect the most relevant validations in a single library.

So, if you want to customize the Compliance Suite, you can do so easily by making a copy of one of the libraries and adding or removing just the validations you want.

Let’s start by looking at the Fugue.Compliance.CIS library.

Note: For the sake of clarity we refer to the CIS AWS Foundations Benchmark generically as a “standard.” This enables us to reference compliance standards more broadly (NIST, HIPAA, etc.) and simplify the terminology when we’re talking about functionality that may apply to the CIS Benchmark or other compliance standards.

Step 2: Copy and Customize Library (Optional)

Before we do anything else, we make a copy of Fugue.Compliance.CIS so we can modify it safely. We’ll name the file CustomComplianceSuite.lw:

cp /opt/fugue/lib/Fugue/Compliance/CIS.lw CustomComplianceSuite.lw

On Windows:

C:\Program Files\Fugue\lib\Fugue\Compliance\CIS.lw CustomComplianceSuite.lw

Go ahead and open CustomComplianceSuite.lw in your favorite text editor. In the Ludwig file, you’ll find some more detailed information that will be useful in customizing your library.

At the top is a description of the library, followed by a list of validations in it that require additional parameters, followed by explicit exports, internal details, an import list, and finally the validation references.

Each section of validation references is organized by service. Let’s look at the Validations for Fugue.AWS.EC2.Compliance section:

################################################################################
# Validations for Fugue.AWS.EC2.Compliance
################################################################################

import Fugue.AWS.EC2.Compliance

validate Fugue.AWS.EC2.Compliance.iamRolesUsedAWSResourceAccess {
  references: ["CIS_1-21"],
}

validate Fugue.AWS.EC2.Compliance.vpcLoggingEnabled {
  references: ["CIS_2-9"],
}

validate Fugue.AWS.EC2.Compliance.noIngressFromAnywhereToPort22 {
  references: ["CIS_4-1"],
}

validate Fugue.AWS.EC2.Compliance.noIngressFromAnywhereToPort3389 {
  references: ["CIS_4-2"],
}

Here we have another import line and a series of validation registrations. The top-level standards, like Fugue.Compliance.CIS, are essentially just directories of validations. The actual validation functions generally live elsewhere, in the module for the appropriate service. Here, that would be Fugue.AWS.EC2.Compliance.

Fugue.Compliance.CIS neatly packages together all the CIS validations from different services and registers the validations with the validate keyword, which signals the compiler to “turn on” each validation.

So even though the validations are part of the Fugue Standard Library, nothing happens until you import Fugue.Compliance.CIS, which then imports the validations and activates them.

Take a look at the last one:

validate Fugue.AWS.EC2.Compliance.noIngressFromAnywhereToPort3389 {
  references: ["CIS_4-2"],
}

Each registration has the field references, which lists each compliance standard that the validation checks. This can be customized and is just for your benefit. For example, you could change CIS_4-2 above to AcmeCorpPolicy1.

Now that we’ve gone over how the compliance standards are structured, let’s use the Fugue.AWS.EC2.Compliance validations in Fugue.Compliance.CIS as a starting point for our custom compliance library.

You can comment out validations that are not applicable by prepending a # to each line. We’re going to focus on just CIS_4-1 and CIS_4-2, which ensure no security groups allow ingress from 0.0.0.0/0 to port 22 or port 3389 respectively. (See the AWS CIS Foundations Benchmark for details.) So we will comment the other validations out, being careful not to delete the import line.

Here’s what CustomComplianceSuite.lw looks like now:

################################################################################
# Validations for Fugue.AWS.EC2.Compliance
################################################################################

import Fugue.AWS.EC2.Compliance

# validate Fugue.AWS.EC2.Compliance.iamRolesUsedAWSResourceAccess {
#   references: ["CIS_1-21"],
# }

# validate Fugue.AWS.EC2.Compliance.vpcLoggingEnabled {
#   references: ["CIS_2-9"],
# }

validate Fugue.AWS.EC2.Compliance.noIngressFromAnywhereToPort22 {
  references: ["CIS_4-1"],
}

validate Fugue.AWS.EC2.Compliance.noIngressFromAnywhereToPort3389 {
  references: ["CIS_4-2"],
}

(Download CustomComplianceSuite.lw here.)

Step 3: Apply Validations

You can apply any validation at design-time or runtime (except account-level modules), so use whatever method you like best. Remember that you can apply a validation at design-time by importing it into the composition and then compiling, and you can apply it at runtime by uploading it to the Conductor and running a composition.

We’ll apply the validations at design-time for this example. CISSecurityGroup.lw is a straightforward composition that declares an EC2.SecurityGroup, an EC2.IpPermission that opens SSH up to the world, and an EC2.IpPermission that opens RDP up to the world:

composition

import Fugue.AWS as AWS
import Fugue.AWS.EC2 as EC2
import CustomComplianceSuite

exampleSecurityGroup: EC2.SecurityGroup.new {
  description: "Allow traffic from the Internet",
  vpc: exampleVpc,
  ipPermissions: [sshFromAll, rdpFromAll]
}

sshFromAll: EC2.IpPermission.ssh(EC2.IpPermission.Target.all)
rdpFromAll: EC2.IpPermission.rdp(EC2.IpPermission.Target.all)

exampleVpc: EC2.Vpc.new {
  cidrBlock: "10.0.0.0/16",
  region: AWS.Us-west-2,
}

(Download CISSecurityGroup.lw here.)

We’ve imported the CustomComplianceSuite library, so when we compile the composition, we get a couple of errors:

lwc CISSecurityGroup.lw
ludwig (validation error):
  "CISSecurityGroup.lw" (line 7, column 23):
  Validation failed:

     7| exampleSecurityGroup: EC2.SecurityGroup.new {
     8|   description: "Allow traffic from the Internet",
     9|   vpc: exampleVpc,
    10|   ipPermissions: [sshFromAll, rdpFromAll]
    11| }

  Security groups must not allow ingress from 0.0.0.0/0 to port 3389. (port is 3389)

  - CIS_4-2

  (from Fugue.AWS.EC2.Compliance.noIngressFromAnywhereToPort3389)

ludwig (validation error):
  "CISSecurityGroup.lw" (line 7, column 23):
  Validation failed:

     7| exampleSecurityGroup: EC2.SecurityGroup.new {
     8|   description: "Allow traffic from the Internet",
     9|   vpc: exampleVpc,
    10|   ipPermissions: [sshFromAll, rdpFromAll]
    11| }

  Security groups must not allow ingress from 0.0.0.0/0 to port 22. (port is 22)

  - CIS_4-1

  (from Fugue.AWS.EC2.Compliance.noIngressFromAnywhereToPort22)

Which means our custom compliance library is working!

Step 4: Resolve Errors and Reapply (if applicable)

Let’s bring the composition into compliance by making some changes. We’ll replace the wide-open security group rules with ones that only allow SSH and RDP access from a specific IP address:

composition

import Fugue.AWS as AWS
import Fugue.AWS.EC2 as EC2
import CustomComplianceSuite

exampleSecurityGroup: EC2.SecurityGroup.new {
  description: "Allow traffic from my IP",
  vpc: exampleVpc,
  ipPermissions: [sshFromMyIp, rdpFromMyIp]
}

myIpAddress: "73.73.73.73/32"

sshFromMyIp: EC2.IpPermission.new {
  ipProtocol: "tcp",
  fromPort: 22,
  toPort: 22,
  target: EC2.IpPermission.Target.ipRanges [myIpAddress]
}

rdpFromMyIp: EC2.IpPermission.new {
  ipProtocol: "tcp",
  fromPort: 3389,
  toPort: 3389,
  target: EC2.IpPermission.Target.ipRanges [myIpAddress]
}

exampleVpc: EC2.Vpc.new {
  cidrBlock: "10.0.0.0/16",
  region: AWS.Us-west-2,
}

(Download CISSecurityGroupFixed.lw here.)

Once again, let’s try to run the composition and see if it passes validation:

lwc CISSecurityGroupFixed.lw

And the composition is compliant! It passes validation this time. Congratulations! Now you can continue to refine your compliance library as you like.

Note

If you want a quick way to see whether your local compositions will pass a compliance standard, you can compile each with the compiler’s –validation-modules option to test them without importing them into the composition. It’s a good practice to import them, but this works in a pinch:

lwc Composition.lw --validation-modules Fugue.Compliance.NIST

If you want a quick way to see whether your running compositions will pass a standard without actually creating a process, you can upload the validations as usual with fugue policy validation-add and then execute fugue run with the --dry-run option:

fugue run --dry-run Composition.lw

Next Steps

And as always, reach out to support@fugue.co with any questions.