We use Terraform for all our deployment automation needs. Thanks to it’s fantastic extensibility, if there isn’t a provider available to do what we need, it’s very easy to create one.
Terraform, for those who haven’t used it before, lets you declaratively specify the resources that you want to deploy and then maintains a state of what has and hasn’t been deployed. For deployed resources, it tracks the characteristics or attributes, and updates accordingly with updates.
As an example, say we wanted to create a new S3 bucket in our AWS account we might define something like;
resource "aws_s3_bucket" "my-bucket-for-secret-things" {
bucket = "my-bucket-for-secret-things"
acl = "public-read"
tags = {
Name = "Sensitive Storage Bucket"
Environment = "Production"
}
}
Now, you might see straight away that there is a potential issue here - the bucket has an ACL allowing public-read
. Checking the AWS Documentation on canned responses, shows us that public read has the following permissions;
Owner gets FULL_CONTROL. The AllUsers group (see Who Is a Grantee?) gets READ access.
We can see this easily with a single resource in our file, but imagine this was just one resource among thousands in a project; this could very easily go unnoticed and end up in production with a wide open bucket.
Enter TFSEC
tfsec
is a static analysis tool for Terraform created by liamg. It will tell you if there are security issues in your Terraform. Installation is simple;
Mac
tfsec
is available for install with homebrew
brew install tfsec
Windows
tfsec
is available for install with Chocolatey
choco install tfsec
Linux/everything else
Alternatively, you can install using go get
go get -u github.com/tfsec/tfsec/cmd/tfsec
We can use tfsec
to test our Terraform by simply running the command against the folder with our terraform in it.
.
|_ tf/
|_ main.tf
Assuming that the above resource for the S3 bucket is in main.tf
, we can run
tfsec tf
This will give me a list of problems categorised into severities.
3 potential problems detected:
Problem 1
[AWS001][WARNING] Resource 'aws_s3_bucket.my-bucket-for-secret-things' has an ACL which allows public read access.
/tmp/tfsec/main.tf:5
2 |
3 | resource "aws_s3_bucket" "my-bucket-for-secret-things" {
4 | bucket = "my-bucket-for-secret-things"
5 | acl = "public-read"
6 |
7 | tags = {
8 | Name = "Sensitive Storage Bucket"
See https://github.com/tfsec/tfsec/wiki/AWS001 for more information.
Problem 2
[AWS002][ERROR] Resource 'aws_s3_bucket.my-bucket-for-secret-things' does not have logging enabled.
/tmp/tfsec/main.tf:3-11
1 |
2 |
3 | resource "aws_s3_bucket" "my-bucket-for-secret-things" {
4 | bucket = "my-bucket-for-secret-things"
5 | acl = "public-read"
6 |
7 | tags = {
8 | Name = "Sensitive Storage Bucket"
9 | Environment = "Production"
10 | }
11 | }
12 |
See https://github.com/tfsec/tfsec/wiki/AWS002 for more information.
Problem 3
[AWS017][ERROR] Resource 'aws_s3_bucket.my-bucket-for-secret-things' defines an un-encrypted S3 bucket (missing server_side_encryption_configuration block).
/tmp/tfsec/main.tf:3-11
1 |
2 |
3 | resource "aws_s3_bucket" "my-bucket-for-secret-things" {
4 | bucket = "my-bucket-for-secret-things"
5 | acl = "public-read"
6 |
7 | tags = {
8 | Name = "Sensitive Storage Bucket"
9 | Environment = "Production"
10 | }
11 | }
12 |
See https://github.com/tfsec/tfsec/wiki/AWS017 for more information.
We can see that it has identified that we have a public acl specified, this is a warning to draw our attention to the fact and allow us to make a judgement call.
The other two errors tell us that we don’t have “at rest” encryption and we could do with access logging. All in all very useful since I’d forgotten them.
But there is more
One of the benefits of tfsec
is the ability for it to process default variable values and report on them as well. For example if we had a variable for the acl.
variable "bucket-acl" {
description = "The ACL for S3 buckets"
default = "public-read"
}
then we update the main.tf
file to use this variable rather than the hard coded value;
resource "aws_s3_bucket" "my-bucket-for-secret-things" {
bucket = "my-bucket-for-secret-things"
acl = var.bucket-acl
tags = {
Name = "Sensitive Storage Bucket"
Environment = "Production"
}
}
Our folder structure now looks more like this;
.
|_ tf/
|_ main.tf
|_ variables.tf
Now when we run tfsec
against the tf
folder it gives us a slightly different error;
Problem 1
[AWS001][] Resource 'aws_s3_bucket.my-bucket-for-secret-things' has an ACL which allows public read access.
/tmp/tfsec/main.tf:5
2 |
3 | resource "aws_s3_bucket" "my-bucket-for-secret-things" {
4 | bucket = "my-bucket-for-secret-things"
5 | acl = var.bucket-acl [string] "public-read"
6 |
7 | tags = {
8 | Name = "Sensitive Storage Bucket"
See https://github.com/tfsec/tfsec/wiki/AWS001 for more information.
The default value as been evaluated from the variables.tf
file and seen that without explicitly overriding the value would have the same net effect as hard coding it to public-read
False Positive
Assuming that you did intend to ignore this error for this resource, that is fine, it can be done by adding an ignore declaration to the resource;
resource "aws_s3_bucket" "my-bucket-for-secret-things" {
bucket = "my-bucket-for-secret-things"
#tfsec:ignore:AWS001
acl = var.bucket-acl
tags = {
Name = "Sensitive Storage Bucket"
Environment = "Production"
}
}
More information
Check out the github project for more information
A note on Terraform
If it sounds like something you want to learn more about then check out Terraform: From Beginner to Master
by Kevin Holditch