DEV Community

Cover image for Securing Your AWS Terraform Deployments: 5 Authentication Methods
yadanaresh
yadanaresh

Posted on

Securing Your AWS Terraform Deployments: 5 Authentication Methods

Introduction

In the rapidly evolving landscape of cloud infrastructure management, securing access to AWS resources is a foundational concern. One critical aspect is the secure authentication of Terraform, a popular infrastructure-as-code tool, to your AWS environment. In this blog post, we will delve into five distinct authentication methods, providing comprehensive explanations and practical demonstrations for each. By the end, you'll be well-equipped to make informed decisions aboåut the most suitable authentication approach for your specific use case.

Prerequisites

Before diving into the practical demonstrations, it's essential to ensure that the following prerequisites are satisfied:

Procure Bastion Server:

  • Acquire a Bastion server with SSH port open.
  • Ensure that HTTP and HTTPS ports are open to facilitate necessary communications.

Install Terraform on Bastion Server:

  • Terraform should be installed on the Bastion server, serving as the central point for infrastructure provisioning.

Reference: https://developer.hashicorp.com/terraform/tutorials/aws-get-started/install-cli

Install AWS CLI on Bastion Server:

  • AWS CLI is a crucial component for several authentication methods. Install it on the Bastion server to facilitate secure interactions with AWS services.

Reference: https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html

Note: The following scenarios and practical demonstrations are explained using RHEL OS. Ensure that your Bastion server is running RHEL, and adapt commands accordingly based on the specific Linux distribution you are using.

Scenario 1: Direct Values in Code

Explanation:
The most straightforward yet insecure method involves hardcoding AWS secret and access keys directly into Terraform code or configuration files. While this approach might be convenient for testing or training purposes, it poses a significant security risk in a production environment. Anyone with access to the codebase can retrieve sensitive information, making it a less-than-ideal choice for real-world scenarios.

Embedding AWS secret and access keys directly into Terraform code is generally considered a bad practice due to security concerns. When secret keys are exposed in Terraform code, it poses a significant security risk as anyone with access to the Terraform code can potentially access sensitive AWS resources, make unauthorized changes, or compromise the security of your infrastructure. It's crucial to follow best practices for handling secrets to avoid these risks.

  1. Configuration File:
    Terraform configurations are often stored in files with a .tf extension. These files contain the infrastructure code and configuration details. In some cases, AWS secret and access keys might be directly embedded in these files.

  2. Usage in Resources:
    The variables are then used in the AWS resource definitions within the Terraform code. For example:

    provider "aws" {
      access_key = "AKIATDG3VPEUQ2HE5NEY"
      secret_key = "bU2BBXqBhXeDvtsgta41mmUzbX5eV+IJCxGIkk/0"
      region     = "us-west-2"
    }
    
    resource "aws_instance" "senario1" {
      ami           = "ami-0c55b159cbfafe1f0"
      instance_type = "t2.micro"
    }
    
  3. Exposing Secrets:

    Unfortunately, when secrets are directly embedded like this, they become visible to anyone who has access to the Terraform code. This includes version control systems, shared repositories, and collaborators on the project.

  4. Security Implications:

    Exposing AWS secret and access keys in Terraform code is a security vulnerability. If the Terraform code is inadvertently shared or exposed, unauthorized individuals may gain access to AWS resources, leading to potential data breaches, unauthorized usage, or even financial losses.

  provider "aws" {
      access_key = "AKIATDG3VPEUQ2HE5NEY"
      secret_key = "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"
      region     = "us-west-2"
    }

    resource "aws_instance" "senario1" {
      ami           = "ami-0c55b159cbfafe1f0"
      instance_type = "t2.micro"
    }
Enter fullscreen mode Exit fullscreen mode

commands to execute
terraform init
terraform plan
terraform apply

Scenario 2: Environment Variables

Explanation:
Storing AWS credentials as environment variables in Terraform improves security over direct code embedding but introduces risks. While protecting against code exposure, environment variables are susceptible to command history leaks. Manual export before Terraform execution and limited access control granularity make them less suitable for certain production scenarios. Organizations should consider advanced secrets management solutions, automate secure variable configuration, and regularly rotate credentials to enhance security. In production, combining environment variables with AWS IAM roles or dedicated key management services provides a more robust and secure approach.

Exporting AWS Secret Keys as Environment Variables:

In this demonstration, you are likely using commands in the terminal to export AWS secret keys as environment variables. This is a more secure approach than embedding the keys directly in Terraform code, as it separates sensitive information from the infrastructure configuration.

    export AWS_ACCESS_KEY_ID="AKIATDG3VPEUQ2HE5NEY"
    export AWS_SECRET_ACCESS_KEY="bU2BBXqBhXeDvtsgta41mmUzbX5eV+IJCxGIkk/0"
Enter fullscreen mode Exit fullscreen mode

Terraform Configuration:

In your Terraform code, you then reference these environment variables instead of directly embedding the secret keys.

 provider "aws" {
  region = "us-east-1" #always region is us east
}
resource "aws_instance" "senario2" {
  ami           = "ami-023c11a32b0207432" # Specify ami details
  instance_type = "t2.micro"
}
Enter fullscreen mode Exit fullscreen mode

Security Breach in Bash History:

The vulnerability arises from the fact that commands entered in a terminal, including the export commands for AWS secret keys, are often logged in the bash history file. If someone gains access to the history file, they can see the exported secret keys.

For example, if a user runs history or looks into the .bash_history file, they can find:

  history |grep -i export
   22  export AWS_ACCESS_KEY_ID="AKIAZ3QDJN4PLRT7OUGC"
   23  export AWS_SECRET_ACCESS_KEY="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"
Enter fullscreen mode Exit fullscreen mode

This creates a potential security risk, as anyone with access to the system could view these commands and extract sensitive information.

Mitigation:

To mitigate this risk, you should take steps to secure the bash history file. You can configure the shell not to log certain commands or use tools that help manage and encrypt the history file.

To mask sensitive commands in Bash history in Linux, you can use the HISTCONTROL environment variable with the value ignorespace or ignoreboth. This prevents commands preceded by a space from being saved to the history. Here's how you can set it:

Copy code
echo "HISTCONTROL=ignorespace" >> ~/.bashrc
source ~/.bashrc
Now, when you precede a command with a space, it won't be stored in the Bash history. For example:

[root@ip-172-31-33-196 AWS-Terra]#  export AWS_ACCESS_KEY_ID="AKIAZ3QDJN4PLRT7OUGC"
[root@ip-172-31-33-196 AWS-Terra]# 
[root@ip-172-31-33-196 AWS-Terra]#  export AWS_SECRET_ACCESS_KEY="IGFhfJaQ7ZGyt1ZGSJPfuchhNREiJDcdiaTzWdoY"
[root@ip-172-31-33-196 AWS-Terra]# 
[root@ip-172-31-33-196 AWS-Terra]# history |grep -i export
   22  export AWS_ACCESS_KEY_ID="AKIAZ3QDJN4PLRT7OUGC"
   23  export AWS_SECRET_ACCESS_KEY="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"
   50  history |grep -i export
   51  history |grep -i export
Enter fullscreen mode Exit fullscreen mode

Practical Demonstration:

commands

terraform init
----------------
Initializing the backend...

Initializing provider plugins...
- Finding latest version of hashicorp/aws...
- Installing hashicorp/aws v5.29.0...
- Installed hashicorp/aws v5.29.0 (signed by HashiCorp)
Enter fullscreen mode Exit fullscreen mode
terraform plan
---------------
`Plan: 1 to add, 0 to change, 0 to destroy.

─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────

Note: You didn't use the -out option to save this plan, so Terraform can't guarantee to take exactly these actions if you run "terraform apply" now.

Enter fullscreen mode Exit fullscreen mode
terraform apply
----------------
Plan: 1 to add, 0 to change, 0 to destroy.

Do you want to perform these actions?
  Terraform will perform the actions described above.
  Only 'yes' will be accepted to approve.

  Enter a value: yes

aws_instance.senario2: Creating...
aws_instance.senario2: Still creating... [10s elapsed]
aws_instance.senario2: Still creating... [20s elapsed]
aws_instance.senario2: Still creating... [30s elapsed]
aws_instance.senario2: Creation complete after 31s [id=i-005cb70b463c82be3]

Apply complete! Resources: 1 added, 0 changed, 0 destroyed.
Enter fullscreen mode Exit fullscreen mode

Scenario 3: AWS CLI with Config Files

Explanation:
Leveraging the AWS CLI for AWS credential management offers an improved security approach by storing access and secret keys in a dedicated file (~/.aws/secure-credentials). This method introduces an additional layer of protection, addressing concerns related to embedding credentials directly in Terraform code or using environment variables. The confidential file ensures that sensitive information is isolated and encrypted, enhancing security measures. However, it is imperative to enforce strict access controls to the AWS CLI and associated credential files. Limiting access to authorized teams or individuals helps prevent unauthorized use and ensures that only trusted personnel can interact with AWS resources. In summary, integrating the AWS CLI with secure credential storage practices is a robust security strategy, combining convenience with heightened protection against credential exposure and misuse.

Practical Demonstration:

let's walk through the steps to configure the AWS CLI, store credentials in a secure file, and integrate them into Terraform for enhanced security.

Before we test scenario 3, let's unset the environment variable using the following commands.

[root@ip-xxx-xx-xx-xxx AWS-Terra]# unset AWS_ACCESS_KEY_ID
[root@ip-xxx-xx-xx-xxx AWS-Terra]# unset AWS_SECRET_ACCESS_KEY
[root@ip-xxx-xx-xx-xxx AWS-Terra]# echo $AWS_SECRET_ACCESS_KEY

[root@ip-xxx-xx-xx-xxx AWS-Terra]# echo $AWS_ACCESS_KEY_ID

[root@ip-xxx-xx-xx-xxx AWS-Terra]# 
Enter fullscreen mode Exit fullscreen mode

1. Configure AWS CLI:

aws configure
Enter fullscreen mode Exit fullscreen mode

Follow the prompts to enter your AWS access key, secret key, region, and preferred output format. This information will be stored in the AWS CLI configuration file (~/.aws/credentials).

2. Store Credentials in a Secure File:

Create a dedicated file to store your AWS credentials securely. For example:

echo "[default]" > ~/.aws/credentials
echo "aws_access_key_id = YOUR_ACCESS_KEY" >> ~/.aws/credentials
echo "aws_secret_access_key = YOUR_SECRET_KEY" >> ~/.aws/credentials
Enter fullscreen mode Exit fullscreen mode

Secure the file's permissions to make it readable only by the owner:

chmod 600 /AWS-Terra/.aws/credentials
Enter fullscreen mode Exit fullscreen mode

3. Configure Terraform to Use AWS CLI Credentials:

In your Terraform configuration, set the AWS provider block to use the AWS CLI credentials file:

provider "aws" {
  shared_credentials_files = ["/AWS-Terra/.aws/credentials"]
  region                 = "us-east-1"
}
Enter fullscreen mode Exit fullscreen mode

4. Enhanced Security:

By default, only authorized users should have access to the AWS CLI configuration and credentials. Ensure that access to the AWS CLI is restricted to authorized teams or individuals.

provider "aws" {
  shared_credentials_files = ["/AWS-Terra/.aws/credentials"]
  region = "us-east-1" #always region is us east
}
resource "aws_instance" "senario3" {
  ami           = "ami-023c11a32b0207432" # Specify ami details
  instance_type = "t2.micro"
}
Enter fullscreen mode Exit fullscreen mode

commands

terraform plan
--------------
aws_instance.senario2: Refreshing state... [id=i-005cb70b463c82be3]

Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
  + create
  - destroy

Terraform will perform the following actions:

  # aws_instance.senario2 will be destroyed
  # (because aws_instance.senario2 is not in configura 


Plan: 1 to add, 0 to change, 1 to destroy.

───────────────────────────────────────────────────────────────────────────────────────────

Note: You didn't use the -out option to save this plan, so Terraform can't guarantee to take exactly these actions if you run "terraform apply" now.


terraform apply
----------------
aws_instance.senario2: Refreshing state... [id=i-005cb70b463c82be3]

Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
  + create
  - destroy

Terraform will perform the following actions:

  # aws_instance.senario2 will be destroyed
Plan: 1 to add, 0 to change, 1 to destroy.

Do you want to perform these actions?
  Terraform will perform the actions described above.
  Only 'yes' will be accepted to approve.

  Enter a value: yes

aws_instance.senario2: Destroying... [id=i-005cb70b463c82be3]
aws_instance.senario3: Creating...
aws_instance.senario2: Still destroying... [id=i-005cb70b463c82be3, 10s elapsed]
aws_instance.senario3: Still creating... [10s elapsed]
aws_instance.senario2: Still destroying... [id=i-005cb70b463c82be3, 20s elapsed]
aws_instance.senario3: Still creating... [20s elapsed]
aws_instance.senario2: Destruction complete after 30s
aws_instance.senario3: Still creating... [30s elapsed]
aws_instance.senario3: Still creating... [40s elapsed]
aws_instance.senario3: Still creating... [50s elapsed]
aws_instance.senario3: Creation complete after 52s [id=i-0e214b3b0fb488a6d]

Apply complete! Resources: 1 added, 0 changed, 1 destroyed.
Enter fullscreen mode Exit fullscreen mode

Scenario 4: EC2 Role-Based Authentication

Explanation:
Securing AWS infrastructure through IAM roles and their attachment to EC2 instances is a best practice that enhances security. This approach minimizes security breaches by providing temporary and scoped permissions, ensuring resources are accessed based on the instance's role. IAM roles offer dynamic and flexible access control, reducing the risk associated with long-lived credentials. In practical terms, setting up EC2 role-based authentication involves creating roles with specific permissions and attaching them to instances. This not only simplifies credential management but also strengthens security measures, aligning with the principle of least privilege. Leveraging IAM roles showcases AWS's robust security model and is essential for securing cloud-based environments effectively.

IAM Role Permissions for EC2 Bastion Instance Creation:

IAM Console:

  • Navigate to the IAM Console in the AWS Management Console.
  • Select "Roles" and click "Create role."
  • Choose "AWS service" as the trusted entity and EC2 as the use case.

Permissions:

  • In the "Permissions" step, attach the following policies to the role:
  • AmazonEC2FullAccess: Provides comprehensive EC2 permissions.
  • AmazonSSMFullAccess: Allows interaction with EC2 instances using AWS Systems Manager.

Note:

  • It's essential to follow the principle of least privilege. If you only need to create and manage EC2 instances, the above policies are generally sufficient. However, if you plan to perform additional tasks (e.g., creating VPCs, modifying security groups), you may need to attach additional policies accordingly.

Create Role:

  • Complete the role creation process and make note of the Role ARN, which you will use when launching your EC2 instance.

Instance Launch Configuration:

  • Launch a new EC2 instance or select an existing one.
  • In the instance configuration, choose the IAM role created earlier in the "Configure Instance Details" section.

Verify Role-Based Authentication:

  • Connect to the EC2 instance using SSH or any other preferred method.
  • Confirm the role-based authentication by checking the AWS CLI configuration on the instance using aws configure list.

Terraform code

provider "aws" {
  region = "us-east-1"  # Set your desired AWS region
}

resource "aws_iam_role" "ec2_role" {
  name = "ec2_role"
  assume_role_policy = <<EOF
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Service": "ec2.amazonaws.com"
      },
      "Action": "sts:AssumeRole"
    }
  ]
}
EOF
}

resource "aws_iam_instance_profile" "ec2_instance_profile" {
  name = "ec2_instance_profile"
  role = aws_iam_role.ec2_role.name
}

resource "aws_iam_role_policy_attachment" "ec2_full_access" {
  policy_arn = "arn:aws:iam::aws:policy/AmazonEC2FullAccess"
  role       = aws_iam_role.ec2_role.name
}

resource "aws_instance" "Bastion_ec2_role" {
  ami           = "ami-023c11a32b0207432"
  instance_type = "t2.micro"
  key_name      = "terraform-infra"

  iam_instance_profile = aws_iam_instance_profile.ec2_instance_profile.name

  tags = {
    Name = "bastion-role"
  }
}

Enter fullscreen mode Exit fullscreen mode

Image description

Image description

Image description

terraform init
----------------
Initializing the backend...

Initializing provider plugins...
- Finding latest version of hashicorp/aws...
- Installing hashicorp/aws v5.29.0...
- Installed hashicorp/aws v5.29.0 (signed by HashiCorp)

Terraform has created a lock file .terraform.lock.hcl to record the provider
selections it made above. Include this file in your version control repository
so that Terraform can guarantee to make the same selections by default when
you run "terraform init" in the future.

Terraform has been successfully initialized!

terraform plan
----------------
Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:

  # aws_instance.senario4 will be created
  + resource "aws_instance" "senario4" {

Plan: 1 to add, 0 to change, 0 to destroy.

─────────────────────────────────────────────────────────────────────────────────────────

Note: You didn't use the -out option to save this plan, so Terraform can't guarantee to take exactly these actions if you run "terraform apply" now.

terraform apply
-----------------
Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:

  # aws_instance.senario4 will be created
  + resource "aws_instance" "senario4" {
      + ami                                  = "ami-023c11a32b0207432"

Plan: 1 to add, 0 to change, 0 to destroy.

Do you want to perform these actions?
  Terraform will perform the actions described above.
  Only 'yes' will be accepted to approve.

  Enter a value: yes

aws_instance.senario4: Creating...

aws_instance.senario4: Still creating... [4m30s elapsed]
aws_instance.senario4: Creation complete after 4m33s [id=i-01c26968753921b24]

Apply complete! Resources: 1 added, 0 changed, 0 destroyed.

Enter fullscreen mode Exit fullscreen mode

After creating the Bastion EC2 instance with the 'ec2_role,' which has the 'AmazonEC2FullAccess' permission, I logged into the Bastion server. Subsequently, I executed a Terraform script to create an additional EC2 instance without passing any AWS access and secret keys explicitly. The EC2 instance was successfully created based on the IAM role policy associated with 'ec2_role.' This demonstrates Scenario 4, where there is no need to provide AWS access and secret keys explicitly; EC2 instances can be launched securely using IAM roles.

Scenario 5: HashiCorp Vault Integration

Explanation:
Integrating Terraform with HashiCorp Vault represents the apex of contemporary cloud security practices. This synergy centralizes the administration of sensitive information, specifically AWS secret and access keys, ensuring their secure storage. The dynamic retrieval of these credentials during Terraform execution adds an extra layer of security. Exploring the nuances of Vault integration reveals its robust security features, establishing it as a reliable safeguard for critical information. If you harbor a keen interest in securing AWS secrets, integrating them with HashiCorp Vault emerges as a preferred and effective method, offering a comprehensive approach to managing and protecting sensitive data in cloud environments.

Taking a hands-on approach

creating HashiCorp vault secrets

installation of vault on Linux server


sudo yum install -y yum-utils
sudo yum-config-manager --add-repo https://rpm.releases.hashicorp.com/RHEL/hashicorp.repo
sudo yum -y install vault

Enter fullscreen mode Exit fullscreen mode

commands

[-31-36-42 ~]# vault login
Token (will be hidden):
Success! You are now authenticated. The token information displayed below is already stored in the token helper. You do NOT need to run "vault login" again. Future Vault requests will automatically use this token.

Key Value


token hvs.IZnSVaRnskiqOgLbkKVgzJgY
token_accessor w1ggJYnsUZwl8f3on9h2A5Vj
token_duration ∞
token_renewable false
token_policies ["root"]
identity_policies []
policies ["root"]

commands

[31-36-42 ec2-with-vault]# vault kv put secret/aws-creds access_key=AKIAUUSZO22P4DSSDUCWHVPKssf
------Secret Path ----
secret/data/aws-creds

------ Metadata -------
Key Value


created_time 2023-12-03T22:50:07.362873124Z
custom_metadata
deletion_time n/a
destroyed false
version 3
[root@ip-172-31-36-42 ec2-with-vault]# vault kv put secret/aws-sec secret_key=3wWrTb9qUm+OewewebBB44ElkqbO6fNxE82pWKGLIheJM
---- Secret Path -----
secret/data/aws-sec

--------- Metadata --------
Key Value


created_time 2023-12-03T22:50:47.626808901Z
custom_metadata
deletion_time n/a
destroyed false
version 1

provider.tf

provider "vault" {
  address = "https://127.0.0.1:8200"
  skip_tls_verify = true
  token = "xxxxxxxxxxxxxxxxxxx"
}

Enter fullscreen mode Exit fullscreen mode

vault-secrect.tf

data "vault_generic_secret" "aws-creds" {
    path = "secret/data/aws-creds"
}
data "vault_generic_secret" "aws-sec" {
   path = "secret/data/aws-sec"
}
Enter fullscreen mode Exit fullscreen mode
 provider "aws" {
  region = "us-east-1" #always region is us east
}
resource "aws_instance" "ec2-with-vault" {
  ami           = "ami-023c11a32b0207432"
  instance_type = "t2.micro"
  key_name      = "terraform-infra"

  # Other instance configurations...

  tags = {
    Name = "ec2-with-vault"
  }
}
Enter fullscreen mode Exit fullscreen mode

Conclusion

Choosing the right authentication method for your Terraform deployments is pivotal in maintaining a robust and secure AWS environment. By understanding the strengths and weaknesses of each scenario and following the practical demonstrations, you can make informed decisions aligned with your organization's security requirements. Stay tuned for the upcoming sections, where we will provide detailed step-by-step instructions and insights for each authentication method.

Top comments (0)