Compliance Suite Remediation Guide

The Fugue Compliance Suite is a library of validations that enforce compliance and regulatory standards. When the Compliance Suite has been applied to a local composition or the Conductor, it checks your infrastructure to see if it adheres to the standards you selected. If a composition is not compliant, it will not compile, and the Ludwig compiler returns a validation error stating which compliance control was violated.

This guide discusses five common compliance violations and how to remediate them. You can review remediation basics first, or jump ahead to the five compliance errors:

You can download these examples from Github. Each example includes a noncompliant composition and one or more compliant versions.

Remediation Basics

You can remediate Compliance Suite validation errors and bring your composition into compliance by editing the Ludwig in it. The error message produced by the compiler tells you three important things:

  1. What went wrong
  2. Which control was violated
  3. Which lines of your code violated it

For example, the error message below says the validation failed because CIS 1-16 requires IAM policies to be attached only to groups or roles. It lists lines 17-20 of the composition, where the IAM user noncompliantAlice has a directly attached managed policy.

In this case, we’d need to remove or comment out line 19 and add noncompliantAlice to an IAM group elsewhere in the composition.

ludwig (validation error):
  "NoncompliantIAMUser.lw" (line 17, column 20, in binding noncompliantAlice):
  Validation failed:

    17| noncompliantAlice: IAM.User.new {
    18|   userName: "noncompliantAlice",
    19|   managedPolicies: [myManagedPolicy]
    20| }

  IAM policies must be attached only to groups or roles.

  - CIS_1-16

  (from Fugue.AWS.IAM.Compliance.noPoliciesAttachedToUsers)

Advanced users can find the validation function in the Fugue Standard Library and determine the exact Ludwig types that trigger the error message. In this case, you would look for the noPoliciesAttachedToUsers function in /opt/fugue/lib/Fugue/AWS/IAM/Compliance.lw on macOS and Linux and C:\Program Files\Fugue\lib\Fugue\AWS\IAM\Compliance.lw on Windows.

Note

If you want to learn more about remediating this error, see the full walkthrough at How to Write a Validation: IAM Users and the CIS Benchmark.

Common Compliance Violations and How to Fix Them

In this section, we explain five common Compliance Suite validation errors and how to remediate them. Each one has two sample compositions: a noncompliant version and a remediated version.

We’ve trimmed the Ludwig lines from the error messages because we include them in the example code.

Open S3 Buckets

Error Message

The S3 bucket used to store CloudTrail logs must not publicly accessible.

- CIS_2-3

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

Fugue.AWS.CloudTrail.Compliance.s3targetprivate

Compliance Standards

  • GDPR_30-(1)
  • HIPAA_§164.308(a)(1)(ii)(D); HIPAA_§164.308(a)(6)(i); HIPAA_§164.312(b); HIPAA_§164.312(c)(2), HIPAA_§164.308(a)(4), HIPAA_§164.310(a)(2)(iii), HIPAA_§164.310(b), HIPAA_§164.312(a)(1), HIPAA_§164.312(a)(2)(i), HIPAA_§164.312(a)(2)(ii)
  • CIS 2.3

Resources

S3 bucket, CloudTrail logs

Explanation

This error message indicates that the access control list for the S3 bucket used to store CloudTrail logs is configured incorrectly. Publicly accessible CloudTrail logs are a security concern because they can expose sensitive configuration and activity details.

The S3.BucketCannedACL sum type includes canned ACLs with different grantees and permissions. If the CloudTrail target bucket ACL is set to any of the following constructors, validation will fail:

Solution

You can manually remediate the violation by changing the ACL to one of these acceptable constructors:

All four constructors grant bucket access only to the bucket owner or the Log Delivery group.

Sample Noncompliant Composition

This code excerpt shows the target bucket configuration, which uses the canned ACL S3.PublicRead:

bucket-ct: S3.Bucket.new {
  name: name ++ "-cloudtrail",
  region: region,
  acl: S3.PublicRead,
  policy: Typed.Policy.toString(bucket-ct-policy),
  loggingConfiguration:
    S3.LoggingConfiguration {
      targetGrants: [],
      targetPrefix: "cloudtrail/",
      targetBucket: bucket
    }
}

(Download full composition)

Sample Remediated Composition

We replaced S3.PublicRead with S3.Private in the bucket-ct declaration:

bucket-ct: S3.Bucket.new {
  name: name ++ "-cloudtrail",
  region: region,
  acl: S3.Private,
  policy: Typed.Policy.toString(bucket-ct-policy),
  loggingConfiguration:
    S3.LoggingConfiguration {
      targetGrants: [],
      targetPrefix: "cloudtrail/",
      targetBucket: bucket
    }
}

(Download full composition)

S3 Buckets Not Encrypted

Error Message

Server side encryption policy is required for S3 buckets.

  - NIST-800-53_SC-13

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

Fugue.AWS.S3.Compliance.bucketRequireServerSideEncryption

Compliance Standards

  • NIST-800-53_SC-13
  • HIPAA_§164.306(a); HIPAA_§164.312(a)(2)(iv); HIPAA_§164.312(c)(1)
  • GDPR_25-(2); GDPR_32-(1)(a)

Resources

S3 bucket, IAM policy

Explanation

This error message indicates that an S3 bucket lacks an IAM policy configured for server-side encryption. Certain compliance standards require data to be encrypted at rest. Unencrypted data poses a security risk because it compromises confidentiality and integrity, but encrypting an S3 bucket’s objects can help limit the damage if the bucket is ever breached.

The bucket policy should deny any s3:PutObject request unless it has the s3:x-amz-server-side-encryption header and the header’s value is AES256 – which means if an object isn’t encrypted, the upload will fail. See the AWS documentation for an example IAM policy.

Solution

To remediate this error, you have a few options.

  1. You can automatically generate a bucket policy for server-side encryption with the Fugue.AWS.S3.Bucket.Policy.SSE.s3 function. See Automatically Generate a Bucket Policy.
  2. You can write your own typed bucket policy for server-side encryption with Fugue.AWS.IAM.Typed.Policy.new and apply it to the bucket. See Write a Custom Bucket Policy.

Sample Noncompliant Composition

bucket0: S3.Bucket.new {
  name: bucketName,
  region: AWS.Us-east-1,
  acl: S3.AuthenticatedRead,
}

(Download full composition)

Sample Remediated Compositions

Automatically Generate a Bucket Policy

The simplest method is to use the Fugue.AWS.S3.Bucket.Policy.SSE.s3 function to generate a policy configured for server-side encryption. Here we’ve added the policy argument in S3.Bucket.new and set it to S3.Bucket.Policy.SSE.s3(bucketName):

bucket0: S3.Bucket.new {
  name: bucketName,
  region: AWS.Us-east-1,
  acl: S3.AuthenticatedRead,
  policy: S3.Bucket.Policy.SSE.s3(bucketName)
}

The generated policy cannot be customized. If you want to make changes, update the composition to use a typed or JSON policy instead.

(Download full composition)

Write a Custom Bucket Policy

Alternatively, you can include a typed bucket policy in your composition that meets the requirements for server-side encryption:

policyDocument: Typed.Policy.new {
  version: Typed.Policy.V2012-10-17,
  id: "PutObjPolicy",
  statements: [
    Typed.Policy.deny {
      principals: [ Typed.Wildcard.wildcard ],
      actions: ["s3:PutObject"],
      resources: ["arn:aws:s3:::" ++ bucketName ++ "/*"],
      conditions: [
        Typed.Condition.stringNotEquals("s3:x-amz-server-side-encryption", "AES256"),
        Typed.Condition.null("s3:x-amz-server-side-encryption")
      ]
    }
  ]
}

You must also attach the policy to the bucket with the policy argument:

bucket0: S3.Bucket.new {
  name: bucketName,
  region: AWS.Us-east-1,
  acl: S3.AuthenticatedRead,
  policy: Typed.Policy.toString(policyDocument)
}

(Download full composition)

This method enables you to make changes to the bucket policy, such as adding further restrictions.

The AWS documentation has a similar policy formatted as JSON.

EBS Volume Not Encrypted

Error Message

Elastic Block Store requires encryption.

- GDPR_25-(2)
- GDPR_32-(1)(a)

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

Fugue.AWS.EC2.Compliance.requireEncryptionForEBSVolume

Compliance Standards

  • NIST‌-800-53_SC‌-13
  • HIPAA_§164.306(a)
  • HIPAA_§164.312(a)(2)(iv)
  • HIPAA_§164.312(c)(1)
  • GDPR_25-(2)
  • GDPR_32-(1)(a)

Resources

EBS volume, KMS key

Explanation

This error message indicates that an EBS volume does not have encryption enabled. EBS volumes can be encrypted at rest and in transit. As with S3 buckets, unencrypted EBS volumes pose a security risk, and compliance standards commonly require encryption.

The error is triggered when the encrypted argument in Fugue.AWS.EC2.Volume.new has been set to False or has not been specified at all.

Solution

To remediate this compliance violation, you can set encrypted: True in the Fugue.AWS.EC2.Volume.new declaration in your composition. The default KMS key for EBS is used to encrypt the volume. To encrypt the volume using a specific KMS key, you can set the kmsKey argument.

Sample Noncompliant Composition

#########################
### EBS VOLUME
#########################
asgVolume: EC2.Volume.new {
  size: 1,
  availabilityZone: AWS.A,
  volumeType: EC2.Standard,
}

(Download full composition)

Sample Remediated Composition

Encrypt With Default Key

To encrypt the volume with the default KMS key for EBS:

#########################
### EBS VOLUME
#########################
asgVolume: EC2.Volume.new {
  size: 1,
  availabilityZone: AWS.A,
  volumeType: EC2.Standard,
  encrypted: True
}

(Download full composition)

Encrypt With Specific Key

To encrypt the volume with a specific KMS key:

#########################
### EBS VOLUME
#########################
asgVolume: EC2.Volume.new {
  size: 1,
  availabilityZone: AWS.A,
  volumeType: EC2.Standard,
  encrypted: True,
  kmsKey: myKey
}

myKey: KMS.Key.external("c5641f34-2c46-1234-1234-123412341234", AWS.Us-west-2)

(Download full composition)

IAM Instance Roles Not Used

Error Message

IAM instance roles must be used for AWS resource access from instances.

- CIS_1-19

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

Fugue.AWS.EC2.Compliance.iamRolesUsedAWSResourceAccess

Compliance Standards

  • CIS_1-19
  • HIPAA_§164.308(a)(3)(i)
  • HIPAA_§164.312(a)(1)

Services

IAM roles, instance profiles

Explanation

This error message indicates that an EC2 instance doesn’t have an instance profile associated with it, or that it does but the instance profile doesn’t have an IAM role (i.e., the roles argument consists of an empty list, []).

Under CIS, all EC2 instances should be associated with a role via an instance profile. Instance profiles can provide temporary credentials to applications running on the instance, which lessens the chance of human error due to credential mismanagement or even hard-coding creds into applications.

Solution

Declare an IAM role for the instance profile and specify it in the roles argument of the Fugue.AWS.IAM.InstanceProfile.new declaration.

Sample Noncompliant Composition

exampleInstanceProfile: IAM.InstanceProfile.new {
  instanceProfileName: "myProfile",
  roles: [],
}

(Download full composition)

Sample Remediated Composition

We’ve declared three more resources using three more functions:

exampleInstanceProfile: IAM.InstanceProfile.new {
  instanceProfileName: "myProfile",
  roles: [myRole],
}

myRole: IAM.Role.new {
  roleName: "my-role",
  rolePolicies: [myPolicy],
  assumeRolePolicyDocument: Typed.AssumeRolePolicy.toString(Typed.AssumeRolePolicy.ec2)
}

myPolicy: IAM.Policy.new {
  policyName: "my-policy",
  policyDocument: Typed.Policy.toString(policyDocument)
}

policyDocument: Typed.Policy.new {
  version: Typed.Policy.V2012-10-17,
  statements: [
    Typed.Policy.allow {
      actions: ["s3:ListBucket"],
      resources: ["arn:aws:s3:::*"]
    }
  ]
}

(Download full composition)

VPC Flow Logs Not Enabled

Error Message

VPC Flow Logs must be enabled for packet "Rejects" for VPCs.

- GDPR_30-(1)

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

Fugue.AWS.EC2.Compliance.vpcLoggingEnabled

Compliance Standards

  • CIS_2-9
  • NIST-800-53_AC-4;NIST-800-53_SC-7a;NIST-800-53_SI-4a.2
  • HIPAA_§164.308(a)(1)(ii)(D);HIPAA_§164.308(a)(6)(i)
  • HIPAA_§164.312(b),HIPAA_§164.308(a)(4)(ii)(B), HIPAA_§164.310(a)(1)
  • HIPAA_§164.312(a)(1),HIPAA_§164.312(b),HIPAA_§164.312(c)
  • HIPAA_§164.312(e), HIPAA_§164.310(b)
  • GDPR_30-(1)

Resources

EC2 VPC, CloudWatch Logs

Explanation

This error message indicates that the composition declares a VPC but not a VPC flow log. CIS, NIST, HIPAA, and GDPR require all VPCs to have a flow log, which facilitates network monitoring. Disabled flow logs are a security concern because crucial details about anomalous traffic or security threats could go unnoticed.

Solution

You can create a VPC flow log with the Fugue.AWS.EC2.FlowLog.new function. If you want multiple VPCs to share the same log group, you can use the Fugue.AWS.EC2.FlowLog.batch function.

You’ll also need to declare a log group with the Fugue.AWS.CloudWatch.Logs.LogGroup.new function.

Sample Noncompliant Composition

#########################
# NETWORKS
#########################

helloWorldVpc: EC2.Vpc.new {
  cidrBlock: "10.0.0.0/16",
  tags: [helloWorldTag],
  region: AWS.Us-west-2,
}

helloWorldTag: AWS.tag("Application", "Hello World")

(Download full composition)

Sample Remediated Composition

Single VPC

We’ve added a helloWorldFlowLog flog low through the Fugue.AWS.EC2.FlowLog.new function.

The flow log uses an external IAM role, which requires an ARN, so we’ve also added the accountId binding as a convenient way to set the AWS account ID.

We’ve also declared a logGroup via Fugue.AWS.CloudWatch.Logs.LogGroup.new.

This example uses EC2.Reject for the flow log’s trafficType argument, but EC2.All is also acceptable.

accountId: '123456789012'

#########################
# NETWORKS
#########################

helloWorldVpc: EC2.Vpc.new {
  cidrBlock: "10.0.0.0/16",
  tags: [helloWorldTag],
  region: AWS.Us-west-2,
}

helloWorldTag: AWS.tag("Application", "Hello World")

#########################
# VPC Flow Log
#########################

helloWorldFlowLog: EC2.FlowLog.new {
  resource: EC2.FlowLog.Resource.vpc(helloWorldVpc),
  logGroup: logGroup,
  role: IAM.Role.external("arn:aws:iam::" ++ accountId ++ ":role/aws-service-role/events.amazonaws.com/AWSServiceRoleForCloudWatchEvents"),
  trafficType: EC2.Reject
}

logGroup: Logs.LogGroup.new {
  name: "VpcFlowLogGroup",
  region: AWS.Us-west-2
}

(Download full composition)

Multiple VPCs, Single Log Group

Using the Fugue.AWS.EC2.FlowLog.batch function, we’ve configured both VPCs to log to the same logGroup. We’ve used the Fugue.AWS.EC2.FlowLog.Resource.vpc function to create a FlowLogResource constructor for each VPC. And we’ve added an accountId binding and logGroup just like we did for the single VPC composition.

accountId: '123456789012'

#########################
# NETWORKS
#########################

helloWorldVpc: EC2.Vpc.new {
  cidrBlock: "10.0.0.0/16",
  tags: [helloWorldTag],
  region: AWS.Us-west-2,
}

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

helloWorldTag: AWS.tag("Application", "Hello World")

#########################
# VPC Flow Log
#########################

flow-logs: EC2.FlowLog.batch {
  resources: [
    EC2.FlowLog.Resource.vpc(helloWorldVpc),
    EC2.FlowLog.Resource.vpc(secondVpc)
  ],
  logGroup: logGroup,
  role: IAM.Role.external("arn:aws:iam::" ++ accountId ++ ":role/aws-service-role/events.amazonaws.com/AWSServiceRoleForCloudWatchEvents"),
  trafficType: EC2.Reject
}

(Download full composition)