Skip to content

Commit

Permalink
Terraform templates v1.0.0 for AWS, GCP & Azure
Browse files Browse the repository at this point in the history
  • Loading branch information
sakshibi committed Sep 6, 2024
0 parents commit 1009082
Show file tree
Hide file tree
Showing 26 changed files with 1,767 additions and 0 deletions.
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
*.terraform*
*.tfstate*
gcp.yaml
outputs.tf
26 changes: 26 additions & 0 deletions AWS_Terraform_Template/example/existing_resource.tfvars
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# Example: Creation of a virtual scanner using existing resources, a marketplace AMI, a security group, ignore AMI changes set to true.

# General Configuration
aws_region = "us-east-1"
availability_zone = "us-east-1b"
scanner_instance_type = "t2.micro"
scanner_name = "terraform-test"
ignore_ami_changes = true
vm_count = 2 # Number of VMs to create
instance_state = "running"

# Network and Security Configuration
vpc_name = "existing_vpc_name"
virtual_subnet_name = "existing_subnet_name"
default_security_group = false
security_group = "existing_security_group_name"

# IP Address Configuration
assign_public_ip = true
assign_ipv6_public_ip = false

# Marketplace Image and QualysGuard Configuration
ami = "global_marketplace"
friendly_name = "qvsa" # Friendly name for the scanner to be created in qweb
qualysguard_url = "qualysguard.qualys.com" # URL for QualysGuard platform
proxy_url = "user:password@proxy.example.com:8080" # Proxy URL for the scanner
80 changes: 80 additions & 0 deletions AWS_Terraform_Template/get_activation_token.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
#!/bin/bash

# Check if required environment variables are set
for var in QUALYSGUARD_LOGIN QUALYSGUARD_PASSWORD AWS_ACCESS_KEY_ID AWS_DEFAULT_REGION AWS_SECRET_ACCESS_KEY; do
[ -z "${!var}" ] && echo "Error: $var is missing." && exit 1
done

# File with the configuration
config_file=${1}

if [ ! -f "${config_file}" ]; then
echo "Error: Configuration file '${config_file}' not found."
exit 1
fi

# Read the file and export variables
while IFS='=' read -r key value; do
# Skip lines that are comments or empty
if [[ "${key}" =~ ^\s*# ]] || [[ -z "${key}" ]]; then
continue
fi

# Remove leading/trailing spaces from key and value
key=$(echo "${key}" | sed "s/ //g")
value=$(echo "${value}" | sed "s/ //g")

# Replace spaces around '=' and handle the value in quotes
if [[ "${value}" == \"*\" ]]; then
value=$(echo "${value}" | sed -e 's/.*"\([^"]*\)".*/\1/')
fi

# Remove inline comments
value=$(echo "${value}" | sed 's/^[^"]*"\([^"]*\)".*/\1/')

# Skip if the value is empty after processing
if [[ -z "${value}" ]]; then
continue
fi
# Export variable
export "${key}=${value}"
done < "${config_file}"

user_prefix="${friendly_name}-$(date +%s)" # Creating user_prefix with same timestamp

# Get the count of existing userdata files
mkdir -p userdata # This would be no-op if userdata dir is already present
existing_count=$(ls userdata/userdata_*.txt 2>/dev/null | wc -l)

# Check if VM count matches the existing userdata files count
if [ "${existing_count}" -eq "${vm_count}" ]; then
echo "VM count matches the number of userdata files. Skipping further execution."
exit 0
fi

# Loop starting from the next available index
for ((i = existing_count; i < vm_count; i++)); do
sleep 3
qvsa_name="${user_prefix}-${i}"
USER_DATA="userdata/userdata_${i}.txt"

perscode=$(curl --insecure -s \
--request POST --url "https://${qualysguard_url}/api/2.0/fo/appliance/" \
--user "${QUALYSGUARD_LOGIN}:${QUALYSGUARD_PASSWORD}" \
--header "X-Requested-With: Curl" \
--data "action=create&echo_request=1&name=${qvsa_name}" | \
grep "ACTIVATION_CODE" | cut -d">" -f2 | cut -d"<" -f1)

if [ $? -eq 0 ] && [ -n "${perscode}" ]; then
echo "PERSCODE=${perscode}" >> ${USER_DATA}
else
echo "Unable to fetch Activation code"
exit 1
fi

if [ -n "${proxy_url}" ]; then
echo "PROXY_URL=${proxy_url}" >> ${USER_DATA}
fi

done
echo "Userdata file generation completed successfully!"
111 changes: 111 additions & 0 deletions AWS_Terraform_Template/main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
locals {
isGlobalMktImage = contains(["global_marketplace"], var.ami) ? 1 : 0
vpc_name = var.vpc_name
subnet_name = var.virtual_subnet_name
selected_ami = local.isGlobalMktImage == 1 ? data.aws_ami.global_marketplace_ami[0].id : var.ami
security_group = var.default_security_group ? aws_security_group.default_security_group[0].id : data.aws_security_group.existing[0].id
security_group_name = var.default_security_group ? "default_security_group" : var.security_group
templates = [for i in range(var.vm_count) : file("./userdata/userdata_${i}.txt")]
proxy_port = regex(":(\\d+)$", var.proxy_url)[0]
is_proxy_defined = length(var.proxy_url) > 0
}

data "aws_vpc" "existing_vpc" {
filter {
name = "tag:Name"
values = [local.vpc_name]
}
}

data "aws_subnet" "existing_subnet" {
filter {
name = "tag:Name"
values = [local.subnet_name]
}
}

resource "aws_security_group" "default_security_group" {
count = var.default_security_group ? 1 : 0
name = local.security_group_name
vpc_id = data.aws_vpc.existing_vpc.id
# Ingress rules: Allow inbound traffic only if a proxy is defined
dynamic "ingress" {
for_each = local.is_proxy_defined ? [1] : []
content {
from_port = tonumber(local.proxy_port)
to_port = tonumber(local.proxy_port)
protocol = "tcp"
cidr_blocks = [var.proxy_cidr_block]
ipv6_cidr_blocks = [var.proxy_ipv6_cidr_blocks]
}
}
# Egress rules: Allow only HTTPS (port 443)
egress {
from_port = 443
to_port = 443
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
ipv6_cidr_blocks = ["::/0"]

}
tags = {
Name = local.security_group_name
}
}

data "aws_security_group" "existing" {
count = var.default_security_group ? 0 : 1
filter {
name = "tag:Name"
values = [local.security_group_name]
}
}

data "aws_ami" "global_marketplace_ami" {
count = local.isGlobalMktImage == 1 ? 1 : 0
most_recent = true
owners = ["aws-marketplace"]
filter {
name = "name"
values = ["qVSA-AWS.x86*"]
}
}

resource "aws_instance" "vm_instance_ignore_ami_change" {
count = var.ignore_ami_changes ? var.vm_count : 0
ami = local.selected_ami
instance_type = var.scanner_instance_type
subnet_id = data.aws_subnet.existing_subnet.id
monitoring = true
ipv6_address_count = var.assign_ipv6_public_ip ? 1 : 0
associate_public_ip_address = var.assign_public_ip ? true : false
vpc_security_group_ids = [local.security_group]
lifecycle {
ignore_changes = [ami, ]
}
tags = {
Name = "${var.scanner_name}-${count.index}"
}
user_data = base64encode(local.templates[count.index])
}

resource "aws_instance" "vm_instance" {
count = var.ignore_ami_changes ? 0 : var.vm_count
ami = local.selected_ami
instance_type = var.scanner_instance_type
subnet_id = data.aws_subnet.existing_subnet.id
monitoring = true
ipv6_address_count = var.assign_ipv6_public_ip ? 1 : 0
associate_public_ip_address = var.assign_public_ip ? true : false
vpc_security_group_ids = [local.security_group]
tags = {
Name = "${var.scanner_name}-${count.index}"
}
user_data = base64encode(local.templates[count.index])
}

resource "aws_ec2_instance_state" "instance_status" {
count = var.vm_count
instance_id = var.ignore_ami_changes ? aws_instance.vm_instance_ignore_ami_change[count.index].id : aws_instance.vm_instance[count.index].id
state = var.instance_state
}
5 changes: 5 additions & 0 deletions AWS_Terraform_Template/provider.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
provider "aws" {
region = var.aws_region
}


71 changes: 71 additions & 0 deletions AWS_Terraform_Template/readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
# Deploy Qualys Virtual Scanner Appliance on AWS Cloud

## AWS Template Files

- `main.tf`
- `provider.tf`
- `variables.tf`
- `version.tf`

## Deploy using Terraform

### Pre-requisite

1. **For Windows Users**: Install Windows Subsystem for Linux (WSL). For detailed installation steps, refer to the [official documentation](https://learn.microsoft.com/en-us/windows/wsl/install).
2. **Terraform Installation**: Install Terraform by following the instructions provided in the [official terraform documentation](https://developer.hashicorp.com/terraform/install).

### STEP 1

#### Export Environment Variables for QualysGuard and AWS Authentication

```shell
export QUALYSGUARD_LOGIN="your_qualysguard_username"
export QUALYSGUARD_PASSWORD="your__qualysguard_password"
export AWS_ACCESS_KEY_ID="your_AWS_ACCESS_KEY_ID"
export AWS_SECRET_ACCESS_KEY="your_AWS_SECRET_ACCESS_KEY"
export AWS_DEFAULT_REGION="your_AWS_DEFAULT_REGION"
```

Execute the following script:

./get_activation_token.sh <PATH_TO_TFVARS_FILE>

Example:
./get_activation_token.sh example/existing_resource.tfvars

### STEP 2

#### See parameter file examples in the 'example' directory

```shell
terraform init
terraform plan -var-file=<PATH_TO_TFVARS_FILE>
terraform apply -var-file=<PATH_TO_TFVARS_FILE>
```

### Parameters

| Parameter | Input Value | Description |
| ----------------------- | --------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `aws_region` | Region Identifier | Region identifier for deployment (e.g., `us-east-1`). [Learn more](https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/Concepts.RegionsAndAvailabilityZones.html) |
| `availability_zone` | Availability Zones | Zone identifier for deployment (e.g., `us-east-1b`). [Learn more](https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/Concepts.RegionsAndAvailabilityZones.html#Concepts.RegionsAndAvailabilityZones.AvailabilityZones) |
| `scanner_instance_type` | Instance Type | Any from mentioned series (e.g., `t2.micro`) [Learn more](https://aws.amazon.com/ec2/instance-types/) |
| `scanner_name` | Virtual Scanner Name | VM name on AWS can be 1-63 characters long and may contain alphanumerics, underscores, periods, and hyphens but cannot contain special characters. It should match the regex `^[a-z]([-a-z0-9]*[a-z0-9])?`. |
| `vm_count` | Number of scanner VMs to create | N/A |
| `instance_state` |`running`/`stopped` | Desired state of Scanner VM's |
| `vpc_name` | VPC Name | Name of existing VPC. |
| `virtual_subnet_name` | Subnet Name | Name of existing subnet. |
| `default_security_group` | true/false |Attatch default Security Group to scanner VMs. Default value is true. |
| `security_group` | Security group name |Existing Security group name. Provide default_security_group = false while using existing security group. |
| `ami` | `global_marketplace` or AMI ID | AMI for the scanner VM. Provide the AMI ID stored in the region or `"global_marketplace"` to use the latest marketplace image. |
| `ignore_ami_changes` | `true`/`false` | For `ignore_ami_changes=true`, Terraform will ignore any new AMI ID updates. This allows you to update the configurations of the existing VM(s) that were created with the previous AMI. For `ignore_ami_changes=false`, Terraform will detect a new AMI ID, triggering the creation of new VM(s) with the updated AMI and desired variables(during terraform plan/apply). |
| `friendly_name` | Friendly name for scanners | Assign a friendly name to each scanner created on QWeb. The friendly_name will be a combination of a user-defined name (up to 19 characters) and a 13-character string consisting of the current Unix timestamp and the VM's vm_count index. Since QWeb has a 32-character limit for the friendly_name, the user-defined portion can be up to 19 characters. Example: qvsa-1234567890-0 |
| `proxy_url` | Optional Variable | Valid proxy, if applicable. |
| `proxy_cidr_block` | Valid cidr range |Valid proxy cidr range for security/firewall rules.Default value is "0.0.0.0/0" |
| `proxy_ipv6_cidr_blocks` | Valid IPv6 cidr range |Valid proxy IPv6 cidr range for security/firewall rules.Default value is "::/0" |
| `assign_public_ip` | true/false | This parameter specifies whether to assign a public IPv4 address to the scanner VM (`true` for yes, `false` for no).Default value is false. |
| `assign_ipv6_public_ip` | true/false | This parameter specifies whether to assign a public IPv6 address to the scanner VM (`true` for yes, `false` for no). Default value is false. |

### Note

There is an open bug which doesn't prevent IPv6 assignment even though `ipv6_address_count=0` is set: <https://github.com/hashicorp/terraform-provider-aws/issues/20025>
Loading

0 comments on commit 1009082

Please sign in to comment.