diff --git a/README.md b/README.md index 89efea4..8c9ec19 100644 --- a/README.md +++ b/README.md @@ -207,6 +207,7 @@ The following inputs can be used as `step.with` keys | `tf_state_file_name_append` | String | Appends a string to the tf-state-file. Setting this to `unique` will generate `tf-state-aws-unique`. (Can co-exist with `tf_state_file_name`) | | `tf_state_bucket` | String | AWS S3 bucket name to use for Terraform state. See [note](#s3-buckets-naming) | | `tf_state_bucket_destroy` | Boolean | Force purge and deletion of S3 bucket defined. Only evaluated when `tf_stack_destroy` is also `true`, so it is safe to leave this enabled when standing up your stack. Defaults to `false`. | +| `tf_state_bucket_prevent_destroy` | Boolean | Prevent Terraform from destroying the S3 state bucket. Set to `true` to enable lifecycle prevent_destroy. Defaults to `true`. | | `tf_targets` | List | A list of targets to create before the full stack creation. | | `ansible_skip` | Boolean | Skip Ansible execution after Terraform excecution. Default is `false`.| | `ansible_ssh_to_private_ip` | Boolean | Make Ansible connect to the private IP of the instance. Only usefull if using a hosted runner in the same network. Default is `false`. | @@ -243,6 +244,7 @@ The following inputs can be used as `step.with` keys | `aws_ec2_user_data_file` | String | Relative path in the repo for a user provided script to be executed with Terraform EC2 Instance creation. See [this note](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/user-data.html#user-data-shell-scripts). Make sure the add the executable flag to the file. | | `aws_ec2_user_data_replace_on_change`| Boolean | If `aws_ec2_user_data_file` file changes, instance will stop and start. Hence public IP will change. This will destroy and recreate the instance. Defaults to `true`. | | `aws_ec2_additional_tags` | JSON | Add additional tags to the terraform [default tags](https://www.hashicorp.com/blog/default-tags-in-the-terraform-aws-provider), any tags put here will be added to ec2 provisioned resources.| +| `aws_ec2_prevent_destroy` | Boolean | Prevent Terraform from destroying the EC2 instance. Set to `true` to enable lifecycle prevent_destroy. Defaults to `true`. |

@@ -261,6 +263,7 @@ The following inputs can be used as `step.with` keys | `aws_vpc_single_nat_gateway` | Boolean | Toggles only one NAT gateway for all of the public subnets. Defaults to `false`. | | `aws_vpc_external_nat_ip_ids` | String | **Existing** comma separated list of IP IDs if reusing. (ElasticIPs). | | `aws_vpc_additional_tags` | JSON | Add additional tags to the terraform [default tags](https://www.hashicorp.com/blog/default-tags-in-the-terraform-aws-provider), any tags put here will be added to vpc provisioned resources.| +| `aws_vpc_prevent_destroy` | Boolean | Prevent Terraform from destroying the VPC. Set to `true` to enable lifecycle prevent_destroy. Defaults to `true`. |

@@ -276,6 +279,7 @@ The following inputs can be used as `step.with` keys | `aws_r53_create_root_cert` | Boolean | Generates and manage the root cert for the application. **See note**. Default is `false`. | | `aws_r53_create_sub_cert` | Boolean | Generates and manage the sub-domain certificate for the application. **See note**. Default is `false`. | | `aws_r53_additional_tags` | JSON | Add additional tags to the terraform [default tags](https://www.hashicorp.com/blog/default-tags-in-the-terraform-aws-provider), any tags put here will be added to R53 provisioned resources.| +| `aws_r53_cert_prevent_destroy` | Boolean | Prevent Terraform from destroying Route53 certificates. Set to `true` to enable lifecycle prevent_destroy. Defaults to `true`. |

@@ -292,6 +296,7 @@ The following inputs can be used as `step.with` keys | `aws_elb_access_log_bucket_name` | String | S3 bucket name to store the ELB access logs. Defaults to `${aws_resource_identifier}-logs` (or `-lg `depending of length). **Bucket will be deleted if stack is destroyed.** | | `aws_elb_access_log_expire` | String | Delete the access logs after this amount of days. Defaults to `90`. Set to `0` in order to disable this policy. | | `aws_elb_additional_tags` | JSON | Add additional tags to the terraform [default tags](https://www.hashicorp.com/blog/default-tags-in-the-terraform-aws-provider), any tags put here will be added to elb provisioned resources.| +| `aws_elb_prevent_destroy` | Boolean | Prevent Terraform from destroying the load balancer. Set to `true` to enable lifecycle prevent_destroy. Defaults to `true`. |

@@ -334,6 +339,7 @@ The following inputs can be used as `step.with` keys | `aws_efs_mount_target` | String | Directory path in efs to mount directory to. Default is `/`. | | `aws_efs_ec2_mount_point` | String | The `aws_efs_ec2_mount_point` input represents the folder path within the EC2 instance to the data directory. Default is `/user/ubuntu//data`. Additionally, this value is loaded into the docker-compose `.env` file as `HOST_DIR`. | | `aws_efs_additional_tags` | JSON | Add additional tags to the terraform [default tags](https://www.hashicorp.com/blog/default-tags-in-the-terraform-aws-provider), any tags put here will be added to efs provisioned resources.| +| `aws_efs_prevent_destroy` | Boolean | Prevent Terraform from destroying the EFS file system. Set to `true` to enable lifecycle prevent_destroy. Defaults to `true`. |

@@ -365,8 +371,9 @@ The following inputs can be used as `step.with` keys | `aws_rds_db_cloudwatch_logs_exports`| String | Set of log types to enable for exporting to CloudWatch logs. Defaults to `postgresql`. Options are MySQL and MariaDB: `audit,error,general,slowquery`. PostgreSQL: `postgresql,upgrade`. MSSQL: `agent,error`. Oracle: `alert,audit,listener,trace`. | | `aws_rds_db_multi_az` | Boolean| Specifies if the RDS instance is multi-AZ. Defaults to `false`. | | `aws_rds_db_maintenance_window` | String | The window to perform maintenance in. Eg: `Mon:00:00-Mon:03:00` | -| `aws_rds_db_apply_immediately` | Boolean | Specifies whether any database modifications are applied immediately, or during the next maintenance window. Defaults to `false`.| +| `aws_rds_db_apply_immediately` | Boolean | Specifies whether any database modifications are applied immediately, or during the next maintenance window. Defaults to `false`.| | `aws_rds_db_additional_tags` | JSON | Add additional tags to the terraform [default tags](https://www.hashicorp.com/blog/default-tags-in-the-terraform-aws-provider), any tags put here will be added to RDS provisioned resources.| +| `aws_rds_db_prevent_destroy` | Boolean | Prevent Terraform from destroying the RDS database. Set to `true` to enable lifecycle prevent_destroy. Defaults to `true`. **Important: This protects your database from accidental deletion.** |

@@ -463,6 +470,61 @@ Option 2, you have access to the `aws_efs_fs_id` attributes, which will mount an If you set `aws_efs_create_mount_target` and `aws_efs_create_ha`, mount targets will be created for all of the availability zones of the region. +## Protecting Resources with Prevent Destroy + +To protect critical infrastructure from accidental deletion, the `prevent_destroy` lifecycle flag is **enabled by default** on various AWS resources. When enabled, Terraform will refuse to destroy the resource, providing an extra layer of safety for your production infrastructure. + +### Available Prevent Destroy Flags + +The following resources have `prevent_destroy` enabled by default (all default to `true`): + +- **`tf_state_bucket_prevent_destroy`** - Protects the S3 bucket containing Terraform state +- **`aws_ec2_prevent_destroy`** - Protects the EC2 instance +- **`aws_vpc_prevent_destroy`** - Protects the VPC infrastructure +- **`aws_r53_cert_prevent_destroy`** - Protects Route53 certificates +- **`aws_elb_prevent_destroy`** - Protects the Elastic Load Balancer +- **`aws_efs_prevent_destroy`** - Protects the EFS file system +- **`aws_rds_db_prevent_destroy`** - Protects the RDS database + +### Example Usage - Disabling Protection + +Since protection is enabled by default, you typically only need to explicitly set these flags if you want to **disable** protection: + +```yaml +name: Deploy without resource protection +on: + push: + branches: [ develop ] + +jobs: + EC2-Deploy: + runs-on: ubuntu-latest + steps: + - id: deploy + uses: bitovi/github-actions-deploy-docker-to-ec2@v1.0.1 + with: + aws_access_key_id: ${{ secrets.AWS_ACCESS_KEY_ID }} + aws_secret_access_key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + aws_default_region: us-east-1 + + # Disable protection for development environment + aws_ec2_prevent_destroy: false + aws_vpc_prevent_destroy: false + + # Keep database protected even in dev + aws_rds_db_enable: true + aws_rds_db_prevent_destroy: true # Explicitly keep enabled +``` + +### Important Notes + +- **All resources are protected by default** with `prevent_destroy` set to `true` +- This provides a safety net against accidental infrastructure deletion +- When `prevent_destroy` is enabled, you must manually disable it before destroying the stack +- To destroy a protected resource: set the flag to `false`, apply the changes, then run the destroy operation +- For production environments, it's recommended to keep these protections enabled +- The Terraform state bucket protection is independent of `tf_state_bucket_destroy` + ## Adding external RDS Database If `aws_rds_db_enable` is set to `true`, this action will deploy a Postgres RDS database. diff --git a/action.yaml b/action.yaml index 0a47f90..36f180b 100644 --- a/action.yaml +++ b/action.yaml @@ -51,6 +51,10 @@ inputs: tf_state_bucket_destroy: description: 'Force purge and deletion of S3 bucket defined. Only evaluated when `tf_stack_destroy` is also true, so it is safe to leave this enabled when standing up your stack.' required: false + tf_state_bucket_prevent_destroy: + description: 'Prevent Terraform from destroying the S3 state bucket. Set to true to enable lifecycle prevent_destroy.' + required: false + default: true tf_targets: # targets description: 'A list of targets to create before the full stack creation.' required: false @@ -129,6 +133,10 @@ inputs: aws_ec2_additional_tags: description: 'A JSON object of additional tags that will be included on created resources. Example: `{"key1": "value1", "key2": "value2"}`' required: false + aws_ec2_prevent_destroy: + description: 'Prevent Terraform from destroying the EC2 instance. Set to true to enable lifecycle prevent_destroy.' + required: false + default: true # AWS VPC Inputs aws_vpc_create: @@ -167,6 +175,10 @@ inputs: aws_vpc_additional_tags: description: 'A JSON object of additional tags that will be included on created resources. Example: `{"key1": "value1", "key2": "value2"}`' required: false + aws_vpc_prevent_destroy: + description: 'Prevent Terraform from destroying the VPC. Set to true to enable lifecycle prevent_destroy.' + required: false + default: true # AWS Route53 Domains abd Certificates aws_r53_enable: @@ -196,6 +208,10 @@ inputs: aws_r53_additional_tags: description: 'A JSON object of additional tags that will be included on created resources. Example: `{"key1": "value1", "key2": "value2"}`' required: false + aws_r53_cert_prevent_destroy: + description: 'Prevent Terraform from destroying Route53 certificates. Set to true to enable lifecycle prevent_destroy.' + required: false + default: true # AWS ELB aws_elb_create: @@ -230,6 +246,10 @@ inputs: aws_elb_additional_tags: description: 'A JSON object of additional tags that will be included on created resources. Example: `{"key1": "value1", "key2": "value2"}`' required: false + aws_elb_prevent_destroy: + description: 'Prevent Terraform from destroying the load balancer. Set to true to enable lifecycle prevent_destroy.' + required: false + default: true # Docker docker_install: @@ -322,6 +342,10 @@ inputs: aws_efs_additional_tags: description: 'A list of strings that will be added to created resources' required: false + aws_efs_prevent_destroy: + description: 'Prevent Terraform from destroying the EFS file system. Set to true to enable lifecycle prevent_destroy.' + required: false + default: true # AWS RDS aws_rds_db_enable: # aws_enable_postgres @@ -405,6 +429,10 @@ inputs: aws_rds_db_additional_tags: description: 'A JSON object of additional tags that will be included on created resources. Example: `{"key1": "value1", "key2": "value2"}`' required: false + aws_rds_db_prevent_destroy: + description: 'Prevent Terraform from destroying the RDS database. Set to true to enable lifecycle prevent_destroy.' + required: false + default: true # RDS Proxy aws_db_proxy_name: @@ -629,6 +657,7 @@ runs: tf_state_file_name_append: ${{ inputs.tf_state_file_name_append }} tf_state_bucket: ${{ inputs.tf_state_bucket }} tf_state_bucket_destroy: ${{ inputs.tf_state_bucket_destroy }} + tf_state_bucket_prevent_destroy: ${{ inputs.tf_state_bucket_prevent_destroy }} tf_targets: ${{ inputs.tf_targets || inputs.targets }} ansible_skip: ${{ inputs.ansible_skip }} ansible_ssh_to_private_ip: ${{ inputs.ansible_ssh_to_private_ip }} @@ -657,6 +686,7 @@ runs: aws_ec2_user_data_file: ${{ inputs.aws_ec2_user_data_file }} aws_ec2_user_data_replace_on_change: ${{ inputs.aws_ec2_user_data_replace_on_change }} aws_ec2_additional_tags: ${{ inputs.aws_ec2_additional_tags }} + aws_ec2_prevent_destroy: ${{ inputs.aws_ec2_prevent_destroy }} ## AWS VPC aws_vpc_create: ${{ inputs.aws_vpc_create }} @@ -671,6 +701,7 @@ runs: aws_vpc_single_nat_gateway: ${{ inputs.aws_vpc_single_nat_gateway }} aws_vpc_external_nat_ip_ids: ${{ inputs.aws_vpc_external_nat_ip_ids }} aws_vpc_additional_tags: ${{ inputs.aws_vpc_additional_tags }} + aws_vpc_prevent_destroy: ${{ inputs.aws_vpc_prevent_destroy }} # AWS Route53 Domains abd Certificates aws_r53_enable: ${{ inputs.aws_r53_enable }} @@ -682,6 +713,7 @@ runs: aws_r53_create_root_cert: ${{ inputs.aws_r53_create_root_cert || inputs.create_root_cert }} aws_r53_create_sub_cert: ${{ inputs.aws_r53_create_sub_cert || inputs.create_sub_cert }} aws_r53_additional_tags: ${{ inputs.aws_r53_additional_tags }} + aws_r53_cert_prevent_destroy: ${{ inputs.aws_r53_cert_prevent_destroy }} # AWS ELB aws_elb_create: ${{ inputs.aws_elb_create }} @@ -694,6 +726,7 @@ runs: aws_elb_access_log_bucket_name: ${{ inputs.aws_elb_access_log_bucket_name }} aws_elb_access_log_expire: ${{ inputs.aws_elb_access_log_expire }} aws_elb_additional_tags: ${{ inputs.aws_elb_additional_tags }} + aws_elb_prevent_destroy: ${{ inputs.aws_elb_prevent_destroy }} # Docker docker_install: ${{ inputs.docker_install }} @@ -725,6 +758,7 @@ runs: aws_efs_enable_backup_policy: ${{ inputs.aws_efs_enable_backup_policy || inputs.aws_enable_efs_backup_policy }} aws_efs_transition_to_inactive: ${{ inputs.aws_efs_transition_to_inactive }} aws_efs_additional_tags: ${{ inputs.aws_efs_additional_tags }} + aws_efs_prevent_destroy: ${{ inputs.aws_efs_prevent_destroy }} # AWS RDS aws_rds_db_enable: ${{ inputs.aws_rds_db_enable || inputs.aws_enable_postgres }} @@ -754,6 +788,7 @@ runs: aws_rds_db_maintenance_window: ${{ inputs.aws_rds_db_maintenance_window }} aws_rds_db_apply_immediately: ${{ inputs.aws_rds_db_apply_immediately }} aws_rds_db_additional_tags: ${{ inputs.aws_rds_db_additional_tags }} + aws_rds_db_prevent_destroy: ${{ inputs.aws_rds_db_prevent_destroy }} # DB Proxy aws_db_proxy_name : ${{ inputs.aws_db_proxy_name }}