diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..57b08f6 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +*.terraform* +*.tfstate* +gcp.yaml +outputs.tf \ No newline at end of file diff --git a/AWS_Terraform_Template/example/existing_resource.tfvars b/AWS_Terraform_Template/example/existing_resource.tfvars new file mode 100644 index 0000000..ed469a2 --- /dev/null +++ b/AWS_Terraform_Template/example/existing_resource.tfvars @@ -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 diff --git a/AWS_Terraform_Template/get_activation_token.sh b/AWS_Terraform_Template/get_activation_token.sh new file mode 100644 index 0000000..3d73313 --- /dev/null +++ b/AWS_Terraform_Template/get_activation_token.sh @@ -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!" diff --git a/AWS_Terraform_Template/main.tf b/AWS_Terraform_Template/main.tf new file mode 100644 index 0000000..7d6701e --- /dev/null +++ b/AWS_Terraform_Template/main.tf @@ -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 +} diff --git a/AWS_Terraform_Template/provider.tf b/AWS_Terraform_Template/provider.tf new file mode 100644 index 0000000..679223b --- /dev/null +++ b/AWS_Terraform_Template/provider.tf @@ -0,0 +1,5 @@ +provider "aws" { + region = var.aws_region +} + + diff --git a/AWS_Terraform_Template/readme.md b/AWS_Terraform_Template/readme.md new file mode 100644 index 0000000..07fe79a --- /dev/null +++ b/AWS_Terraform_Template/readme.md @@ -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 + +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= +terraform apply -var-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: diff --git a/AWS_Terraform_Template/variables.tf b/AWS_Terraform_Template/variables.tf new file mode 100644 index 0000000..f2036cd --- /dev/null +++ b/AWS_Terraform_Template/variables.tf @@ -0,0 +1,136 @@ +variable "aws_region" { + type = string + description = <+=;,?*@&. It should not begin with an underscore (_) or end with a period (.) or hyphen (-). Must match the regular expression: ^[a-z]([-a-z0-9]*[a-z0-9])? +EOT + validation { + condition = length(var.scanner_name) >= 1 && length(var.scanner_name) <= 63 + error_message = "Scanner name must be between 1 and 63 characters long." + } +} + +variable "scanner_instance_type" { + type = string + description = </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!" diff --git a/Azure_Terraform_Template/main.tf b/Azure_Terraform_Template/main.tf new file mode 100644 index 0000000..84cbc30 --- /dev/null +++ b/Azure_Terraform_Template/main.tf @@ -0,0 +1,281 @@ +locals { + is_rg_new = contains(["new", ""], var.virtual_resource_group_new_or_existing) ? 1 : 0 + is_vnet_new = contains(["new", ""], var.virtual_network_new_or_existing) ? 1 : 0 + is_public_ip = var.assign_public_ip ? 1 : 0 + image_uri = contains(["global_marketplace"], var.image_resource) ? 1 : 0 + resource_group_name = local.is_rg_new == 1 ? length(var.resource_group_name) > 0 ? "${var.resource_group_name}" : "${var.scanner_name}-vrgt-${random_integer.random_num.result}" : var.resource_group_name + v_net_name = local.is_vnet_new == 1 ? var.new_virtual_network.name : var.virtual_network_name + subnet_name = local.is_vnet_new == 1 ? var.new_subnet.name : var.virtual_subnet_name + resource_group = local.is_rg_new == 1 ? azurerm_resource_group.example[0].id : data.azurerm_resource_group.existing_rg[0].id + v_net = local.is_vnet_new == 1 ? azurerm_virtual_network.example[0].id : data.azurerm_virtual_network.existing_vnet[0].id + subnet = local.is_vnet_new == 1 ? azurerm_subnet.example[0].id : data.azurerm_subnet.existing_subnet[0].id + boot_diag_uri = var.boot_diagnostics ? data.azurerm_storage_account.existing-acc[0].primary_blob_endpoint : null + security_group = var.is_default_sg ? azurerm_network_security_group.example[0].id : data.azurerm_network_security_group.existing[0].id + security_group_name = !var.is_default_sg ? var.security_group_name : "" + 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 +} + +resource "random_integer" "random_num" { + min = 1000 + max = 9999 +} + +resource "azurerm_resource_group" "example" { + count = local.is_rg_new + name = local.resource_group_name + location = var.location +} + +data "azurerm_resource_group" "existing_rg" { + count = local.is_rg_new == 0 ? 1 : 0 + name = local.resource_group_name +} + +resource "azurerm_virtual_network" "example" { + count = local.is_vnet_new + name = local.v_net_name + location = var.location + resource_group_name = local.resource_group_name + address_space = var.new_virtual_network.address_space + depends_on = [ + azurerm_resource_group.example + ] +} + +data "azurerm_virtual_network" "existing_vnet" { + count = local.is_vnet_new == 0 ? 1 : 0 + name = local.v_net_name + resource_group_name = local.resource_group_name + depends_on = [ + data.azurerm_resource_group.existing_rg + ] +} + +resource "azurerm_subnet" "example" { + count = local.is_vnet_new + name = local.subnet_name + resource_group_name = local.resource_group_name + virtual_network_name = local.v_net_name + address_prefixes = var.new_subnet.address_prefixes + depends_on = [ + azurerm_virtual_network.example + ] +} + +data "azurerm_subnet" "existing_subnet" { + count = local.is_vnet_new == 0 ? 1 : 0 + name = local.subnet_name + virtual_network_name = local.v_net_name + resource_group_name = local.resource_group_name + depends_on = [ + data.azurerm_virtual_network.existing_vnet + ] +} + +data "azurerm_storage_account" "existing-acc" { + count = var.boot_diagnostics ? 1 : 0 + name = var.storage_account_name + resource_group_name = "saregressionRG" +} + +resource "azurerm_network_interface" "example" { + count = var.vm_count + name = "${var.network_interface_name}${count.index}" + location = var.location + resource_group_name = local.resource_group_name + + #This IP configuration for the IPv4 address + ip_configuration { + name = "v4config" + private_ip_address_version = "IPv4" + primary = "true" + subnet_id = local.subnet + private_ip_address_allocation = "Dynamic" + public_ip_address_id = var.assign_public_ip ? azurerm_public_ip.IPv4_Public_IP[count.index].id : null + } + + #This IP configuration for the IPv6 address + dynamic "ip_configuration" { + for_each = var.assign_public_ip && var.assign_ipv6_public_ip ? [1] : [] + content { + name = "v6config" + private_ip_address_version = "IPv6" + subnet_id = local.subnet + private_ip_address_allocation = "Dynamic" + public_ip_address_id = azurerm_public_ip.IPv6_Public_IP[count.index].id + } + } + +} + +#Create a v4 Public IP +resource "azurerm_public_ip" "IPv4_Public_IP" { + count = var.assign_public_ip ? var.vm_count : 0 + name = "new_ipv4-${count.index}" + resource_group_name = local.resource_group_name + location = var.location + sku = "Standard" + allocation_method = "Static" + ip_version = "IPv4" + depends_on = [local.resource_group] +} + +#Create a v6 Public IP +resource "azurerm_public_ip" "IPv6_Public_IP" { + count = var.assign_public_ip && var.assign_ipv6_public_ip ? var.vm_count : 0 + name = "ipv6-${count.index}" + resource_group_name = local.resource_group_name + location = var.location + sku = "Standard" + allocation_method = "Static" + ip_version = "IPv6" + depends_on = [local.resource_group] +} + +resource "azurerm_network_security_group" "example" { + count = var.is_default_sg ? 1 : 0 + name = "default-sg" + location = var.location + resource_group_name = local.resource_group_name + + // Allow outbound traffic on port 443 for ipv4 + security_rule { + name = "AllowHTTPSOutbound" + description = "Allow all outbound traffic on port 443" + protocol = "Tcp" + source_port_range = "443" + source_address_prefix = "*" + destination_port_range = "443" + destination_address_prefix = "*" + access = "Allow" + priority = 1002 + direction = "Outbound" + } + // Allow outbound traffic on port 443 for ipv6 + security_rule { + name = "AllowHTTPSOutbound" + description = "Allow all outbound traffic on port 443" + protocol = "Tcp" + source_port_range = "443" + source_address_prefix = "*" + destination_port_range = "443" + destination_address_prefix = "*" + access = "Allow" + priority = 1002 + direction = "Outbound" + } + // Conditionally allow inbound traffic if a proxy port is defined for ipv4 + dynamic "security_rule" { + for_each = local.is_proxy_defined ? [1] : [] + content { + name = "AllowProxyInbound" + description = "Allow inbound traffic on proxy port" + protocol = "Tcp" + source_port_range = local.proxy_port + source_address_prefix = var.proxy_cidr_block + destination_port_range = local.proxy_port + destination_address_prefix = var.proxy_cidr_block + access = "Allow" + priority = 1003 + direction = "Inbound" + } + } + // Conditionally allow inbound traffic if a proxy port is defined for ipv6 + dynamic "security_rule" { + for_each = local.is_proxy_defined ? [1] : [] + content { + name = "AllowProxyInbound" + description = "Allow inbound traffic on proxy port" + protocol = "Tcp" + source_port_range = local.proxy_port + source_address_prefix = var.proxy_ipv6_cidr_blocks + destination_port_range = local.proxy_port + destination_address_prefix = var.proxy_ipv6_cidr_blocks + access = "Allow" + priority = 1003 + direction = "Inbound" + } + } + depends_on = [ + azurerm_resource_group.example + ] +} + +data "azurerm_network_security_group" "existing" { + count = var.is_default_sg ? 0 : 1 + name = local.security_group_name + resource_group_name = local.resource_group_name +} + +resource "azurerm_network_interface_security_group_association" "example" { + count = var.vm_count + network_interface_id = azurerm_network_interface.example[count.index].id + network_security_group_id = local.security_group +} + +resource "azurerm_linux_virtual_machine" "scanner_vm" { + count = var.vm_count + name = "${var.scanner_name}-${count.index}" + location = var.location + resource_group_name = local.resource_group_name + size = var.scanner_vm_size + admin_username = "${var.vm_username}${count.index}" + admin_password = "${var.vm_password}${count.index}" + disable_password_authentication = false + network_interface_ids = [ + azurerm_network_interface.example[count.index].id + ] + os_disk { + caching = "ReadWrite" + storage_account_type = var.os_disk_type + } + dynamic "source_image_reference" { + for_each = local.image_uri == 1 ? [1] : [] + content { + publisher = "qualysguard" + offer = "qualys-virtual-scanner" + sku = "qvsa" + version = "latest" + } + } + dynamic "plan" { + for_each = local.image_uri == 1 ? [1] : [] + content { + name = "qvsa" + product = "qualys-virtual-scanner" + publisher = "qualysguard" + } + } + dynamic "boot_diagnostics" { + for_each = var.boot_diagnostics ? [1] : [] + content { + storage_account_uri = local.boot_diag_uri + } + } + source_image_id = local.image_uri != 1 ? var.image_resource : null + depends_on = [ + azurerm_network_interface.example, + azurerm_resource_group.example + ] + user_data = base64encode(local.templates[count.index]) +} + +# Null resource to start the VM +resource "null_resource" "start_vm" { + count = var.start_vm ? var.vm_count : 0 + provisioner "local-exec" { + command = "az vm start --resource-group ${local.resource_group_name} --name ${var.scanner_name}-${count.index}" + } + depends_on = [azurerm_linux_virtual_machine.scanner_vm] +} + +# Null resource to stop the VM +resource "null_resource" "stop_vm" { + count = var.stop_vm ? var.vm_count : 0 + provisioner "local-exec" { + command = "az vm deallocate --resource-group ${local.resource_group_name} --name ${var.scanner_name}-${count.index}" + } + depends_on = [azurerm_linux_virtual_machine.scanner_vm] +} diff --git a/Azure_Terraform_Template/provider.tf b/Azure_Terraform_Template/provider.tf new file mode 100644 index 0000000..ab91b24 --- /dev/null +++ b/Azure_Terraform_Template/provider.tf @@ -0,0 +1,3 @@ +provider "azurerm" { + features {} +} diff --git a/Azure_Terraform_Template/readme.md b/Azure_Terraform_Template/readme.md new file mode 100644 index 0000000..655e38e --- /dev/null +++ b/Azure_Terraform_Template/readme.md @@ -0,0 +1,72 @@ +# Deploy Qualys Virtual Scanner Appliance on Azure Cloud + +## GCP Template Files + +- [main.tf](./main.tf) +- [provider.tf](./provider.tf) +- [variables.tf](./variables.tf) +- [version.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 Azure Authentication + +```shell +export QUALYSGUARD_LOGIN="your_qualysguard_username" +export QUALYSGUARD_PASSWORD="your__qualysguard_password" +export azure_subscription_id="your_azure_subscription_id" +export azure_client_id="your_azure_client_id" +export azure_client_secret="your_azure_client_secret" +export azure_tenant_id="your_azure_tenant_id" +``` + +Execute the following script: + +path_to_get_activation_token_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= +terraform apply -var-file= +``` + +### Parameters + +| Parameter | Description | Details | +| -------------------------------------- | ----------------------------------------------------- | ------- | +| `location` | Location identifier | Location identifier for deployment (example: `eastus`). [Learn more](https://azure.microsoft.com/en-in/global-infrastructure/locations) | +| `vm_count` | Number of scanner VMs to create | N/A | +| `scanner_name` | Virtual Scanner Name | VM name on Azure 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])?`. | +| `start_vm` | True/False | True will execute an Azure CLI command to start the scanner VMs. | +| `stop_vm` | True/False | True will execute an Azure CLI command to stop the scanner VMs. | +| `network_interface_name` | Name of the network interface | Provide a custom name for the network interface resource for the scanner VM. | +| `virtual_network_new_or_existing` | New or Existing Virtual Network | `"new"` or "" for a new Vnet, `"existing"` for using an existing Vnet. | +| `virtual_resource_group_new_or_existing`| New or Existing Resource Group | `"new"` or "" for a new resource group, `"existing"` for using an existing resource group. | +| `resource_group_name` | Resource group name | Custom name for a new or existing resource group. For `virtual_resource_group_new_or_existing="existing"`, provide the existing resource group name. | +| `virtual_network_name` | Existing virtual network name | Used with `virtual_network_new_or_existing="existing"`. Provide the existing virtual network name. | +| `storage_account_name` | Storage account name | Name of existing storage account. | +| `new_virtual_network` | Name and address space of new virtual network | Used with virtual_network_new_or_existing="new" or "".Custom name and address space. Defaults to `"default_vnet"` and `'10.0.0.0/24'` for IPv4, `'fd00:db8:deca::/64'` for IPv6 if not provided. | +| `new_subnet` | Name and address prefix of new subnet |Used with virtual_network_new_or_existing="new" or "".Custom name and address prefix. Defaults to `"default_subnet"` and `'10.0.0.0/24'` for IPv4, `'fd00:db8:deca::/64'` for IPv6 if not provided. | +| `os_disk_type` | OS disk type | One of the following: `Premium_LRS`, `StandardSSD_LRS`, `Standard_LRS`. Premium Disk is recommended but only available with selected VM sizes. [Learn more](https://docs.microsoft.com/en-us/azure/virtual-machines/windows/disks-types) | +| `assign_public_ip` | True/False | Assign IPv4 public IP to scanner VMs. Default is `false`. | +| `assign_ipv6_public_ip` | True/False | Assign IPv6 public IP to scanner VMs. Default is `false`. | +| `image_resource` | Image for scanner VM | Provide image URI stored locally or use `'global_marketplace'` for the latest marketplace image. | +| `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` | Valid proxy (optional) | The proxy server address, if applicable. | +| `proxy_cidr_block` | Valid cidr range |Valid cidr range for security/firewall rules.Default value is "0.0.0.0/0" | +| `proxy_ipv6_cidr_blocks` | Valid IPv6 cidr range |Valid IPv6 cidr range for security/firewall rules.Default value is "::/0" | +| `qualysguard_url` | QualysGuard URL | The URL for accessing QualysGuard. | diff --git a/Azure_Terraform_Template/variables.tf b/Azure_Terraform_Template/variables.tf new file mode 100644 index 0000000..bb594c0 --- /dev/null +++ b/Azure_Terraform_Template/variables.tf @@ -0,0 +1,207 @@ +variable "location" { + type = string + description = "Azure region for resource deployment (e.g., 'eastus'). Refer to the Azure locations documentation: https://azure.microsoft.com/en-in/global-infrastructure/locations/" +} + +variable "scanner_name" { + type = string + description = "Defines the name of the scanner VM. Must be 1-63 characters long, consisting of alphanumeric characters, underscores, periods, or hyphens. Special characters are not allowed.\nMore info: https://docs.microsoft.com/en-us/azure/azure-resource-manager/management/resource-name-rules#microsoftcompute." + + validation { + condition = length(var.scanner_name) >= 1 && length(var.scanner_name) <= 64 + error_message = "Scanner name must be between 1 and 63 characters." + } +} + +variable "scanner_vm_size" { + type = string + default = "Standard_A2_v2" + description = "Size of the Scanner VM. Refer to the Azure VM sizes documentation: https://docs.microsoft.com/en-us/azure/virtual-machines/linux/sizes" +} + +variable "image_resource" { + type = string + description = </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}.json" + 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 "{" >> ${user_data} + echo "\"PERSCODE\":\"${perscode}\"," >> ${user_data} + else + echo "Unable to fetch Activation code for VM ${i}" + exit 1 + fi + + if [ -n "${proxy_url}" ]; then + echo "\"PROXY_URL\":\"${proxy_url}\"" >> ${user_data} + echo "}" >> ${user_data} + else + sed -i 's/,//g' ${user_data} + echo "}" >> ${user_data} + fi +done +echo "Userdata file generation completed successfully!" diff --git a/GCP_Terraform_Template/main.tf b/GCP_Terraform_Template/main.tf new file mode 100644 index 0000000..64775d8 --- /dev/null +++ b/GCP_Terraform_Template/main.tf @@ -0,0 +1,164 @@ + +# Local values to determine the configuration +locals { + is_new_network = contains(["new", ""], var.virtual_network_new_or_existing) ? 1 : 0 + is_new_subnet = contains(["new", ""], var.virtual_subnet_new_or_existing) ? 1 : 0 + vnet_name = contains(["new", ""], var.virtual_network_new_or_existing) ? length(var.virtual_network_name) > 0 ? "${var.virtual_network_name}" : "${var.scanner_name}-vnet-${random_integer.random_num.result}" : var.virtual_network_name + vsubnet_name = contains(["new", ""], var.virtual_subnet_new_or_existing) ? length(var.virtual_subnet_name) > 0 ? "${var.virtual_subnet_name}" : "${var.scanner_name}-vsubnet-${random_integer.random_num.result}" : var.virtual_subnet_name + isGlobalMktImage = (var.image_uri == "") || (var.image_uri == "global_marketplace") + selected_image = local.isGlobalMktImage ? data.google_compute_image.global_marketplace_image[0].self_link : var.image_uri + network = contains(["new", ""], var.virtual_network_new_or_existing) ? google_compute_network.example[0].self_link : data.google_compute_network.existing_network[0].self_link + subnetwork = contains(["new", ""], var.virtual_subnet_new_or_existing) ? google_compute_subnetwork.example[0].self_link : data.google_compute_subnetwork.existing_subnetwork[0].self_link + templates = [for i in range(var.vm_count) : file("./userdata/userdata_${i}.json")] + proxy_port = regex(":(\\d+)$", var.proxy_url)[0] + is_proxy_defined = length(var.proxy_url) > 0 + ipv4_range = var.assign_public_ip ? ["0.0.0.0/0"] : [] + ipv6_range = var.assign_public_ip && var.assign_ipv6_ip ? ["::/0"] : [] + destination_ranges = concat(local.ipv4_range, local.ipv6_range) +} + +resource "random_integer" "random_num" { + min = 1000 + max = 9999 +} + +resource "google_compute_network" "example" { + count = local.is_new_network + name = local.vnet_name + auto_create_subnetworks = false +} + +data "google_compute_network" "existing_network" { + count = local.is_new_network == 0 ? 1 : 0 + name = var.virtual_network_name +} + +resource "google_compute_subnetwork" "example" { + count = local.is_new_subnet + name = local.vsubnet_name + ip_cidr_range = var.subnet_cidr + network = google_compute_network.example[0].self_link + region = var.scanner_region + stack_type = var.assign_ipv6_ip ? "IPV4_IPV6" : "IPV4_ONLY" + ipv6_access_type = var.assign_ipv6_ip ? "EXTERNAL" : null + +} + +data "google_compute_subnetwork" "existing_subnetwork" { + count = local.is_new_subnet == 0 ? 1 : 0 + name = var.virtual_subnet_name + region = var.scanner_region +} + +# Fetch the global marketplace image +data "google_compute_image" "global_marketplace_image" { + count = local.isGlobalMktImage ? 1 : 0 + project = "qualys-gcp-security" + family = "qvsa" +} + +resource "google_compute_firewall" "allow_https_outbound" { + count = var.default_firewall_rule ? 1 : 0 + name = "allow-https-outbound" + description = "Allow outbound traffic on port 443" + + network = var.virtual_network_name + + allow { + protocol = "tcp" + ports = ["443"] + } + + direction = "EGRESS" + priority = 1002 + destination_ranges = ["0.0.0.0/0"] + source_ranges = ["0.0.0.0/0"] + depends_on = [local.network] +} + +resource "google_compute_firewall" "allow_https_outbound_ipv6" { + count = var.default_firewall_rule ? 1 : 0 + name = "allow-https-outbound-ipv6" + description = "Allow outbound traffic on port 443 for IPv6" + + network = var.virtual_network_name + + allow { + protocol = "tcp" + ports = ["443"] + } + + direction = "EGRESS" + priority = 1003 + destination_ranges = ["::/0"] + source_ranges = ["::/0"] + depends_on = [local.network] +} + +resource "google_compute_firewall" "allow_proxy_inbound" { + count = local.is_proxy_defined ? 1 : 0 + name = "allow-proxy-inbound" + description = "Allow inbound traffic on proxy port" + network = var.virtual_network_name + allow { + protocol = "tcp" + ports = [local.proxy_port] + } + + direction = "INGRESS" + priority = 1003 + destination_ranges = [var.proxy_cidr_block] + source_ranges = [var.proxy_cidr_block] + depends_on = [local.network] +} + +resource "google_compute_firewall" "allow_proxy_inbound_ipv6" { + count = local.is_proxy_defined ? 1 : 0 + name = "allow-https-inbound-ipv6" + description = "Allow inbound traffic on proxy port" + network = var.virtual_network_name + allow { + protocol = "tcp" + ports = [local.proxy_port] + } + + direction = "INGRESS" + priority = 1003 + destination_ranges = [var.proxy_ipv6_cidr_blocks] + source_ranges = [var.proxy_ipv6_cidr_blocks] + depends_on = [local.network] +} + +# Create the VM instance +resource "google_compute_instance" "vm_instance" { + count = var.vm_count + name = "${var.scanner_name}-${count.index}" + machine_type = var.scanner_machine_type + zone = var.zone + + boot_disk { + initialize_params { + image = local.selected_image + } + } + + network_interface { + network = local.network + subnetwork = local.subnetwork + stack_type = var.assign_public_ip ? var.stack_type : null + dynamic "access_config" { + for_each = var.assign_public_ip ? [1] : [] + content { + network_tier = var.network_tier + } + } + dynamic "ipv6_access_config" { + for_each = (var.assign_public_ip && var.assign_ipv6_ip) ? [1] : [] + content { + network_tier = "PREMIUM" + } + } + } + metadata = jsondecode(local.templates[count.index]) + desired_status = var.desired_status +} diff --git a/GCP_Terraform_Template/provider.tf b/GCP_Terraform_Template/provider.tf new file mode 100644 index 0000000..77f4a57 --- /dev/null +++ b/GCP_Terraform_Template/provider.tf @@ -0,0 +1,6 @@ +provider "google" { + project = var.project_name + zone = var.zone + credentials = var.key_file_path + region = var.scanner_region +} diff --git a/GCP_Terraform_Template/readme.md b/GCP_Terraform_Template/readme.md new file mode 100644 index 0000000..f99370a --- /dev/null +++ b/GCP_Terraform_Template/readme.md @@ -0,0 +1,70 @@ +# Deploy Qualys Virtual Scanner Appliance on Google Cloud + +## GCP Template Files + +- [main.tf](./main.tf) +- [provider.tf](./provider.tf) +- [variables.tf](./variables.tf) +- [version.tf](./version.tf) + +## Deploy using Terraform + +### Pre-requisites + +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 Authentication + +```shell +export QUALYSGUARD_LOGIN="your_qualysguard_username" +export QUALYSGUARD_PASSWORD="your__qualysguard_password" +``` + +Execute the following script: + +./get_activation_token.sh + +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= +terraform apply -var-file= +``` + +### Parameters + +| Parameter | Input Value | Description | +| ---------------------------------- | ------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `scanner_region` | Region Identifier | Region identifier for deployment (e.g., `us-west1`). [Learn more](https://cloud.google.com/compute/docs/regions-zones#:~:text=A%20zone%20is%20a%20deployment,is%20us%2Dcentral1%2Da%20.) | +| `zone` | Zone Identifier | Zone identifier for deployment (e.g., `us-west1-b`). [Learn more](https://cloud.google.com/compute/docs/regions-zones#:~:text=A%20zone%20is%20a%20deployment,is%20us%2Dcentral1%2Da%20.) | +| `scanner_name` | Scanner VM name | VM name on GCP can be 1-63 characters long and may contain alphanumerics, underscores, periods, and hyphens, and should match the regex `^[a-z]([-a-z0-9]*[a-z0-9])?`. [Learn more](https://cloud.google.com/compute/docs/naming-resources) | +| `scanner_machine_type` | Scanner Machine Type | Any from the mentioned series (e.g., `e2-medium`). Available types: `E2 Series`, `N1 Series`, `N2 Series`, `N2D Series`, `C2 Series`, `C3 Series`, `M1 Series`, `M2 Series`, `A1 Series`, `F1 Series`, `G1 Series`. [Learn more](https://docs.microsoft.com/en-us/azure/virtual-machines/linux/sizes) | +| `project_name` | Existing Project Name | Project name on GCP can be 1-63 characters long and may contain alphanumerics, underscores, periods, and hyphens, and should match the regex `^[a-z]([-a-z0-9]*[a-z0-9])?`. [Learn more](https://cloud.google.com/resource-manager/docs/creating-managing-projects) | +| `family_name` | | Family Name for image. [Learn more](https://cloud.google.com/compute/docs/images/image-families-best-practices) | +| `key_file_path` | Example:`test_key.json` | Key file path for service account. [Learn more](https://cloud.google.com/iam/docs/keys-create-delete#:~:text=You%20can%20use%20service%20account,%2Dprivate%2Dkey.json%20.) | +| `vm_count` | Number of VMs (Scanners) to create | | +| `virtual_network_new_or_existing` | New or Existing Virtual Network | Virtual network options for scanner VM. To create a new virtual network, provide `"new"` or `""`. To link the scanner with an existing virtual network, provide `"existing"` keyword. | +| `virtual_subnet_new_or_existing` | New or Existing Subnet | Virtual subnet options for scanner VM. To create a new subnet, provide `"new"` or `""`. To link the scanner with an existing subnet, provide `"existing"` keyword. | +| `image_uri` | Scanner image | Image options for scanner VM. Use the image URI extracted from Google Cloud Storage (GCS) bucket for a local image, or provide `"global_marketplace"` keyword to use the latest marketplace image. | +| `virtual_network_name` | Name of the virtual network instance | This parameter can be used to provide a custom name when creating a new virtual network for the scanner VM during deployment. If not provided, Terraform will generate a name combining `"scannerName"` and a random string. For existing network, use name of existing network. | +| `virtual_subnet_name` | Name of the virtual subnet instance | This parameter can be used to provide a custom name when creating a new virtual subnet for the scanner VM during deployment. If not provided, Terraform will generate a name combining `"scannerName"` and a random string. For existing subnet, use name of existing subnet. | +| `default_firewall_rule` | `true`/`false` | This parameter specifies whether to use the default firewall rule (`true` for yes, `false` for no). | +| `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_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. | +| `stack_type` | `IPV4_ONLY`/`IPV4_IPV6` | IP stack type for network interface.For assign_public_ip=true, set stack_type=`IPV4_ONLY` or `IPV4_IPV6`.Default value is `IPV4_ONLY` | +| `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` | Valid proxy (optional) | The proxy server address, if applicable. | +| `proxy_cidr_block` | Valid CIDR range | Valid CIDR range for security/firewall rules. Default value is "0.0.0.0/0". | +| `proxy_ipv6_cidr_blocks` | Valid IPv6 CIDR range | Valid IPv6 CIDR range for security/firewall rules. Default value is "::/0". | +| `desired_status` | `RUNNING`/`TERMINATED` | Desired status of the instance. | +| `network_tier` | `Premium`/`Standard` | Network tier for the VM instances. | +| `qualysguard_url` | QualysGuard URL | The URL for accessing QualysGuard. | diff --git a/GCP_Terraform_Template/variables.tf b/GCP_Terraform_Template/variables.tf new file mode 100644 index 0000000..c349b4c --- /dev/null +++ b/GCP_Terraform_Template/variables.tf @@ -0,0 +1,142 @@ +variable "zone" { + type = string + description = "Specifies the deployment zone (e.g., us-west1-b).\nMore info: https://cloud.google.com/compute/docs/regions-zones." +} + +variable "scanner_name" { + type = string + description = "Defines the name of the scanner VM. Must be 1-63 characters long, consisting of alphanumeric characters, underscores, periods, or hyphens. Special characters are not allowed.\nMore info: https://docs.microsoft.com/en-us/azure/azure-resource-manager/management/resource-name-rules#microsoftcompute." + + validation { + condition = length(var.scanner_name) >= 1 && length(var.scanner_name) <= 64 + error_message = "Scanner name must be between 1 and 63 characters." + } +} + +variable "scanner_machine_type" { + type = string + description = "Specifies the machine type for the scanner VM.\nMore info: https://cloud.google.com/compute/docs/general-purpose-machines." + default = "e2-medium" +} + +variable "project_name" { + type = string + description = "Defines the Google Cloud project name.\nMore info: https://cloud.google.com/resource-manager/docs/creating-managing-projects." +} + +variable "family_name" { + type = string + description = "Specifies the image family name for the scanner VM.\nMore info: https://cloud.google.com/compute/docs/images/image-families-best-practices." +} + +variable "scanner_region" { + type = string + description = "Specifies the region for deployment (e.g., us-west1).\nMore info: https://cloud.google.com/compute/docs/regions-zones." +} + +variable "key_file_path" { + type = string + description = "Specifies the file path for the service account key.\nMore info: https://cloud.google.com/iam/docs/keys-create-delete." +} + +variable "virtual_network_new_or_existing" { + type = string + description = "Defines whether to create a new virtual network or use an existing one. Provide 'new' for a new network or 'existing' for existing network." +} + +variable "virtual_subnet_new_or_existing" { + type = string + description = "Defines whether to create a new subnet or use an existing one. Provide 'new' for a new subnet or 'existing' for an existing subnet." +} + +variable "virtual_network_name" { + type = string + description = "Specifies the name of the virtual network." + default = "default-virtual-network" +} + +variable "virtual_subnet_name" { + type = string + description = "Specifies the name of the virtual subnet." + default = "default-subnet" +} + +variable "subnet_cidr" { + type = string + description = "Defines the CIDR range for the subnet. Default is 10.0.0.0/24." + default = "10.0.0.0/24" +} + +variable "image_uri" { + type = string + description = "Specifies the image URI for the scanner VM. Provide either the local image URI or 'global_marketplace'." +} + +variable "qualysguard_url" { + type = string + description = "Specifies the Qualys Guard URL." +} + +variable "friendly_name" { + type = string + description = "Defines a user-friendly name for the scanner to be created on qweb." +} + +variable "proxy_url" { + type = string + description = "Specifies the proxy URL for network communication." + default = "" +} + +variable "vm_count" { + type = number + description = "Defines the number of scanner VMs to create." + default = 1 +} + +variable "assign_public_ip" { + type = bool + description = "Determines whether to assign a public IPv4 address to the VM." + default = false +} + +variable "assign_ipv6_ip" { + type = bool + description = "Determines whether to assign an IPv6 address to the VM." + default = false +} + +variable "default_firewall_rule" { + description = "Determines whether to use the default firewall rules or an existing rule." + type = bool + default = true +} + +variable "desired_status" { + description = "Specifies the desired operational status of the VM instances (e.g., RUNNING or TERMINATED)." + type = string + default = "RUNNING" +} + +variable "network_tier" { + description = "Specifies the network tier for the VM instances (e.g., PREMIUM or STANDARD)." + type = string + default = "STANDARD" +} + +variable "stack_type" { + description = "Specifies the IP stack type for the VM instances (e.g., IPV4_ONLY or IPV4_IPV6)." + type = string + default = "IPV4_ONLY" +} + +variable "proxy_cidr_block" { + description = "CIDR block for the proxy" + type = string + default = "0.0.0.0/0" +} +variable "proxy_ipv6_cidr_blocks" { + description = "IPV6 CIDR block for the proxy" + type = string + default = "::/0" +} diff --git a/GCP_Terraform_Template/version.tf b/GCP_Terraform_Template/version.tf new file mode 100644 index 0000000..cf8dc21 --- /dev/null +++ b/GCP_Terraform_Template/version.tf @@ -0,0 +1,9 @@ +terraform { + required_providers { + google = { + source = "hashicorp/google" + version = "~> 5.0" + } + } + required_version = ">= 1.0.0" +}