Secure a WordPress deployment on an ECS instance

Updated at:

Deploy WordPress on an ECS instance with encryption, credential rotation, patch management, and least-privilege access to defend against common threats.

Note

This example uses Terraform to deploy WordPress on an ECS instance. For other deployment methods, see Build a WordPress website. Terraform is an open source resource orchestration tool available as a managed service in Resource Orchestration Service (ROS). You can create Terraform templates and stacks to orchestrate Alibaba Cloud, Amazon Web Services (AWS), and Microsoft Azure resources. See Terraform.

Security design principles

  • Do not store long-term credentials, such as AccessKey IDs, AccessKey secrets, database usernames, and passwords, in resources.

  • Rotate credentials regularly to mitigate the impact of credential leaks.

  • Fix known OS vulnerabilities promptly to meet security patch baselines.

  • Back up critical data regularly to enable restoration after unexpected data loss.

Architecture

The following figure shows the security architecture for deploying WordPress on an ECS instance.

image

Deploying WordPress requires an ECS instance and an ApsaraDB RDS for MySQL instance. You can configure the following security settings:

  • ECS instance

    • Add security group rules to manage inbound and outbound traffic.

      A security group is a virtual firewall that controls inbound and outbound traffic of ECS instances. See Overview.

    • Use automatic snapshot policies to periodically back up disk data.

      Regular snapshots enable rapid data restoration after accidental operations or ransomware attacks. See Automatic snapshot policy.

    • Obtain access credentials through RAM-based authorization.

      Attach an instance RAM role to the ECS instance so it can use STS temporary credentials to call other Alibaba Cloud APIs. This eliminates the need to store an AccessKey pair in plaintext and enables fine-grained access control. See Instance RAM roles.

    • Use Key Management Service (KMS) to encrypt data.

      Encrypt disks attached to ECS instances with KMS keys to protect stored data from unauthorized access. See Disk encryption.

    • Periodically refresh the ApsaraDB RDS secret.

      Cloud Assistant runs scheduled commands on the ECS instance to refresh and retrieve the ApsaraDB RDS secret, keeping database credentials up to date.

    • Automatically fix security vulnerabilities with patch management.

      OOS Patch Manager automatically installs security and software patches on ECS instances to meet compliance requirements and prevent attacks. See Overview of Patch Manager.

  • ApsaraDB RDS for MySQL

    • Rotate the ApsaraDB RDS secret regularly.

      Secret leaks, such as database passwords, SSH keys, and AccessKey pairs, are a major threat to data security. Secrets Manager allows you to configure dynamic ApsaraDB RDS secrets with automatic periodic rotation. See Overview of Secrets Manager.

      Note

      In dual-account mode, Secrets Manager references one account as ACSCurrent and another as ACSPrevious. During rotation, the ACSPrevious account password is reset to a new random value, and the two versions are swapped. By default, the application receives ACSCurrent. If the application accesses Secrets Manager within the rotation period, such as 15 days, it obtains the rotated secret. See Rotate generic secrets.

    • Use KMS to encrypt data.

      Cloud disk encryption is free for ApsaraDB RDS for MySQL. It encrypts data on each disk at the block storage level, so data remains protected even if leaked. See Disk encryption.

Deployment process and security measures

The following steps show how to configure security settings with sample Terraform templates. The templates are interdependent and cannot run separately. Download the complete Terraform configuration file (Securely Deploy WordPress) and run it to create all resources, deploy WordPress, and configure security settings.

Step 1: Create a least-privilege security group

This Terraform template creates a security group that allows only inbound SSH on port 22 from specific IP addresses. Open additional ports as needed. For example, to allow MySQL connections, add an inbound rule for port 3306.

Important

Port 80 is temporarily disabled to prevent unexpected access during deployment. After WordPress is deployed, manually open port 80 in the security group.

# Create a virtual private cloud (VPC).
resource "alicloud_vpc" "default" {
	cidr_block = "172.16.0.0/16"
	vpc_name = "${var.name}-vpc"
}

# Create a security group.
resource "alicloud_security_group" "default" {
	name = "${var.name}-sg"
	vpc_id = alicloud_vpc.default.id
	inner_access_policy = "Drop"
}

# Add an inbound rule to the security group.
resource "alicloud_security_group_rule" "default" {
	type = "ingress"
	ip_protocol = "tcp"
	nic_type = "intranet"
	policy = "accept"
	# port_range = "22/1024"
	port_range = "22/22"
	cidr_ip = "<Specific IP address that is allowed for access>"
	security_group_id = alicloud_security_group.default.id
}

// Close port 80 to prevent unexpected access during WordPress deployment.
// resource "alicloud_security_group_rule" "web" {
// 	type = "ingress"
// 	ip_protocol = "tcp"
// 	nic_type = "intranet"
// 	policy = "accept"
// 	# port_range = "22/1024"
// 	port_range = "80/80"
// 	cidr_ip = "A.B.C.D/0"
// 	security_group_id = alicloud_security_group.default.id
// }

Step 2: Create an ECS instance

Create an automatic snapshot policy

This Terraform template creates an automatic snapshot policy. Snapshots are taken at 23:00 every Wednesday and 00:00–03:00 every Thursday, and retained for 14 days. Adjust the schedule as needed.

# Create an automatic snapshot policy.
resource "alicloud_ecs_auto_snapshot_policy" "default" {
	repeat_weekdays = ["3"]
	time_points = ["0", "1", "2", "3", "23"]
	retention_days = 14
}

Create an instance RAM role

This Terraform template creates a RAM policy named wordpress and attaches it to a RAM role named wordpress, allowing the ECS instance to assume the role for specified KMS operations.

# Configure a RAM policy.
resource "alicloud_ram_policy" "wordpress" {
	policy_name = "wordpress"
	policy_document = <<EOT
{
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "kms:GetSecretValue"
            ],
            "Resource": "${alicloud_kms_secret.wordpress_db_passwd.arn}"
        },
        {
            "Effect": "Allow",
            "Action": [
                "kms:DescribeSecret",
                "kms:ListSecretVersionIds"
            ],
            "Resource": "*"
        }
    ],
    "Version": "1"
}
EOT
}

# Create a RAM role.
resource "alicloud_ram_role" "wordpress" {
	name = "wordpress"
	document    = <<EOF
  {
    "Statement": [
      {
        "Action": "sts:AssumeRole",
        "Effect": "Allow",
        "Principal": {
          "Service": [
            "apigateway.aliyuncs.com", 
            "ecs.aliyuncs.com"
          ]
        }
      }
    ],
    "Version": "1"
  }
  EOF
}

# Attach the RAM policy to the RAM role.
resource "alicloud_ram_role_policy_attachment" "wordpress" {
	policy_name = alicloud_ram_policy.wordpress.id
	policy_type = "Custom"
	role_name = alicloud_ram_role.wordpress.name
}

Create an ECS instance with disk encryption, a RAM role, and an automatic snapshot policy

This Terraform template performs the following operations:

  • Create an ECS instance with the specified zone, instance type, image, security group, vSwitch, and network billing method.

  • Enable system disk encryption.

  • Associate an automatic snapshot policy with the system disk for periodic backups.

  • Assign the RAM role to the ECS instance so it can call API operations without an AccessKey pair.

resource "alicloud_instance" "default" {
	availability_zone = data.alicloud_zones.default.zones.0.id
	instance_type = data.alicloud_instance_types.default.instance_types.0.id
	image_id = data.alicloud_images.default.images.0.id
	security_groups = [alicloud_security_group.default.id]
	vswitch_id = alicloud_vswitch.default.id
	instance_name = "${var.name}-instance"
	internet_charge_type = "PayByTraffic"
	internet_max_bandwidth_out = 5
	system_disk_category = "cloud_essd"
	// key_name = "YOUR SSH PUBKEY NAME"
	role_name = alicloud_ram_role.wordpress.name
	system_disk_auto_snapshot_policy_id = "${alicloud_ecs_auto_snapshot_policy.default.id}"
	system_disk_encrypted = true
}

Step 3: Create a high-availability ApsaraDB RDS instance

Create an ApsaraDB RDS for MySQL instance

This Terraform template creates an ApsaraDB RDS for MySQL instance named tf-wordpress with cloud disk encryption enabled. The instance is accessible only within the VPC, preventing risks from public endpoint exposure.

variable "db_engine" {
	default = "MySQL"
}

variable "db_engine_version" {
	default = "8.0"
}

variable "db_charge_type" {
	default = "PostPaid"
}

variable "db_category" {
	default = "HighAvailability"
}

variable "db_storage_type" {
	default = "cloud_essd"
}

data "alicloud_db_instance_classes" "tf" {
	zone_id                  = data.alicloud_zones.default.zones.0.id
	engine                   = "${var.db_engine}"
	engine_version           = "${var.db_engine_version}"
	category                 = "${var.db_category}"
	db_instance_storage_type = "${var.db_storage_type}"
	instance_charge_type     = "${var.db_charge_type}"
}

resource "alicloud_db_instance" "tf-wordpress" {
	engine                   = "${var.db_engine}"
	engine_version           = "${var.db_engine_version}"
	instance_type            = data.alicloud_db_instance_classes.tf.instance_classes.0.instance_class
	instance_storage         = data.alicloud_db_instance_classes.tf.instance_classes.0.storage_range.min
	instance_charge_type     = "Postpaid"
	instance_name            = "tf-wordpress"
	vswitch_id               = alicloud_vswitch.default.id
	monitoring_period        = "60"
	db_instance_storage_type = "${var.db_storage_type}"
	security_group_ids       = [alicloud_security_group.db.id]
    // The encryption_key can be additionally specified to encrypt data by using other keys.
    // Specify role_arn to use the default customer master key (CMK) for RDS disk encryption.
	role_arn                 = alicloud_ram_role.rds.arn
}

// Create a default RDS RAM role to eliminate the need for manual authorization in the console.
resource "alicloud_ram_role" "rds" {
	name = "AliyunRDSInstanceEncryptionDefaultRole"
	document    = <<EOF
{
  "Statement": [
    {
      "Action": "sts:AssumeRole",
      "Effect": "Allow",
      "Principal": {
	"Service": [
	  "rds.aliyuncs.com"
	]
      }
    }
  ],
  "Version": "1"
}
EOF
}

resource "alicloud_ram_role_policy_attachment" "rds" {
	policy_name = "AliyunRDSInstanceEncryptionRolePolicy"
	policy_type = "System"
	role_name = alicloud_ram_role.rds.name
}

Create a database and database accounts

This Terraform template creates a database and database accounts on the ApsaraDB RDS for MySQL instance.

  • Create a database named wordpress with a randomly generated password.

  • Create two accounts, wordpress and wordpress_backup, with identical permissions. Secrets Manager uses both accounts to rotate the ApsaraDB RDS secret without service interruption.

# Configure settings for a random password.
resource "random_string" db_passwd {
	length = 16
	special = false
}

# Create a database.
resource "alicloud_db_database" "default" {
  instance_id = alicloud_db_instance.tf-wordpress.id
  name        = "wordpress"
}

# Create the first database account and set the password to the preceding random password.
resource "alicloud_rds_account" "default" {
	db_instance_id   = alicloud_db_instance.tf-wordpress.id
	account_name     = "wordpress"
	account_password = resource.random_string.db_passwd.result
}

# Grant the account the read and write permissions on the database named wordpress.
resource "alicloud_db_account_privilege" "privilege" {
	instance_id  = alicloud_db_instance.tf-wordpress.id
	account_name = alicloud_rds_account.default.account_name
	privilege    = "ReadWrite"
	db_names     = alicloud_db_database.default.*.name
}

# Create the second database account and set the password to the preceding random password.
resource "alicloud_rds_account" "backup" {
	db_instance_id   = alicloud_db_instance.tf-wordpress.id
	account_name     = "wordpress_backup"
	account_password = resource.random_string.db_passwd.result
}

# Grant the account the read and write permissions on the database named wordpress.
resource "alicloud_db_account_privilege" "privilege-backup" {
	instance_id  = alicloud_db_instance.tf-wordpress.id
	account_name = alicloud_rds_account.backup.account_name
	privilege    = "ReadWrite"
	db_names     = alicloud_db_database.default.*.name
}

Configure automatic ApsaraDB RDS secret rotation

This Terraform template uses KMS Secrets Manager to manage the ApsaraDB RDS secret in dual-account mode with automatic rotation every 15 days.

resource "alicloud_kms_secret" "wordpress_db_passwd" {
	secret_name			= "wordpress_db_passwd"
	description			= "from terraform"
	secret_data			= jsonencode({
		Accounts = [
			{
				AccountName = alicloud_rds_account.default.account_name
				AccountPassword = resource.random_string.db_passwd.result
			},
			{
				AccountName = alicloud_rds_account.backup.account_name
				AccountPassword = resource.random_string.db_passwd.result
			}
		]
	})
	version_id			= "000000000001"
	force_delete_without_recovery	= true
	rotation_interval		= "15d"
	secret_type                     = "Rds"
	enable_automatic_rotation       = true
	extended_config                 = jsonencode({
		"SecretSubType" = "DoubleUsers",
		"DBInstanceId" = alicloud_db_instance.tf-wordpress.id
	})
}

Step 4: Deploy WordPress and configure scheduled key refresh

Deploy WordPress

This Terraform template deploys WordPress by performing the following operations:

  • Install dependencies, such as PHP, httpd, aliyun-cli, and jq.

  • Download and install WordPress to the /var/www/html/wordpress/ directory.

  • Configure wp-config.php to use environment variable files and file locks for secure database credential retrieval.

  • Configure httpd to block access to hidden files (filenames starting with .), such as .WORDPRESS_DB_PASSWORD_FILE, preventing exposure of sensitive data.

Important

After this step, open port 80 in the security group to allow WordPress access. Enter http://<Public IP address of your ECS instance> in a browser to open the WordPress installation page and follow the on-screen instructions.

resource "alicloud_ecs_command" "default" {
	name = "tf-command"
	command_content = base64encode(<<EOT
set -e
echo hello world > /tmp/hello.txt

wget https://aliyuncli.alicdn.com/aliyun-cli-linux-latest-amd64.tgz
tar xzvf aliyun-cli-linux-latest-amd64.tgz
mv aliyun /usr/local/bin/aliyun
rm -rf aliyun-cli-linux-latest-amd64.tgz

dnf install -y httpd 
dnf install -y httpd-tools php php-cli php-json php-gd php-mbstring php-pdo php-xml php-mysqlnd php-pecl-zip wget
dnf install -y jq

cd /var/www/html
wget https://wordpress.org/latest.tar.gz
tar xvzf latest.tar.gz
rm -rf latest.tar.gz

cat > /etc/httpd/conf.d/wordpress.conf << EOF
<VirtualHost *:80>
ServerAdmin root@localhost
DocumentRoot /var/www/html/wordpress
<Directory "/var/www/html/wordpress">
Options Indexes FollowSymLinks
AllowOverride all
Require all granted
</Directory>
ErrorLog /var/log/httpd/wordpress_error.log
CustomLog /var/log/httpd/wordpress_access.log common
</VirtualHost>
EOF

cat > /var/www/html/wordpress/.WORDPRESS_DB_USER_FILE << "EOF"
wordpress
EOF

cat > /var/www/html/wordpress/.WORDPRESS_DB_PASSWORD_FILE << "EOF"
${resource.random_string.db_passwd.result}
EOF

cat > /var/www/html/wordpress/.WORDPRESS_DB_HOST_FILE << "EOF"
${alicloud_db_instance.tf-wordpress.connection_string}
EOF

# disable any access to the hidden files
cat > /var/www/html/wordpress/.htaccess << "EOF"
<FilesMatch "^\.">
Order allow,deny
Deny from all
</FilesMatch>
EOF

for KEY in AUTH_KEY SECURE_AUTH_KEY LOGGED_IN_KEY NONCE_KEY AUTH_SALT SECURE_AUTH_SALT LOGGED_IN_SALT NONCE_SALT; do
  cat > /var/www/html/wordpress/.WORDPRESS_$${KEY}_FILE << EOF
$(openssl rand -base64 48)
EOF
done

touch /var/www/html/wordpress/.env.lock

cat > /var/www/html/wordpress/wp-config.php << "EOF"
<?php
/**
 * The base configuration for WordPress
 *
 * The wp-config.php creation script uses this file during the installation.
 * You don't have to use the web site, you can copy this file to "wp-config.php"
 * and fill in the values.
 *
 * This file contains the following configurations:
 *
 * * Database settings
 * * Secret keys
 * * Database table prefix
 * * ABSPATH
 *
 * This has been slightly modified (to read environment variables) for use in Docker.
 *
 * @link https://wordpress.org/documentation/article/editing-wp-config-php/
 *
 * @package WordPress
 */

// IMPORTANT: this file needs to stay in-sync with https://github.com/WordPress/WordPress/blob/master/wp-config-sample.php
// (it gets parsed by the upstream wizard in https://github.com/WordPress/WordPress/blob/f27cb65e1ef25d11b535695a660e7282b98eb742/wp-admin/setup-config.php#L356-L392)

// a helper function to lookup "env_FILE", "env", then fallback
if (!function_exists('getenv_docker')) {
	// https://github.com/docker-library/wordpress/issues/588 (WP-CLI will load this file 2x)
	function getenv_docker($env, $default) {
		$fileName = __DIR__ . '/.' . $env . '_FILE';

		if (file_exists($fileName)) {
			$lockfile = __DIR__ . '/' . '.env.lock';
			$fp = fopen($lockfile, 'r+');
			if (flock($fp, LOCK_SH)) {
				$env = rtrim(file_get_contents($fileName), "\r\n");
				flock($fp, LOCK_UN);
			}
			return $env;
		}

		if (($val = getenv($env)) !== false) {
			return $val;
		}
		else {
			return $default;
		}
	}
}

// ** Database settings - You can get this info from your web host ** //
/** The name of the database for WordPress */
define( 'DB_NAME', getenv_docker('WORDPRESS_DB_NAME', 'wordpress') );

/** Database username */
define( 'DB_USER', getenv_docker('WORDPRESS_DB_USER', 'example username') );

/** Database password */
define( 'DB_PASSWORD', getenv_docker('WORDPRESS_DB_PASSWORD', 'example password') );

/**
 * Docker image fallback values above are sourced from the official WordPress installation wizard:
 * https://github.com/WordPress/WordPress/blob/1356f6537220ffdc32b9dad2a6cdbe2d010b7a88/wp-admin/setup-config.php#L224-L238
 * (However, using "example username" and "example password" in your database is strongly discouraged.  Please use strong, random credentials!)
 */

/** Database hostname */
define( 'DB_HOST', getenv_docker('WORDPRESS_DB_HOST', 'mysql') );

/** Database charset to use in creating database tables. */
define( 'DB_CHARSET', getenv_docker('WORDPRESS_DB_CHARSET', 'utf8') );

/** The database collate type. Don't change this if in doubt. */
define( 'DB_COLLATE', getenv_docker('WORDPRESS_DB_COLLATE', '') );

/**#@+
 * Authentication unique keys and salts.
 *
 * Change these to different unique phrases!  You can generate these using
 * the {@link https://api.wordpress.org/secret-key/1.1/salt/ WordPress.org secret-key service}.
 *
 * You can change these at any point in time to invalidate all existing cookies.
 * This will force all users to have to log in again.
 *
 * @since 2.6.0
 */
define( 'AUTH_KEY',         getenv_docker('WORDPRESS_AUTH_KEY',         'put your unique phrase here') );
define( 'SECURE_AUTH_KEY',  getenv_docker('WORDPRESS_SECURE_AUTH_KEY',  'put your unique phrase here') );
define( 'LOGGED_IN_KEY',    getenv_docker('WORDPRESS_LOGGED_IN_KEY',    'put your unique phrase here') );
define( 'NONCE_KEY',        getenv_docker('WORDPRESS_NONCE_KEY',        'put your unique phrase here') );
define( 'AUTH_SALT',        getenv_docker('WORDPRESS_AUTH_SALT',        'put your unique phrase here') );
define( 'SECURE_AUTH_SALT', getenv_docker('WORDPRESS_SECURE_AUTH_SALT', 'put your unique phrase here') );
define( 'LOGGED_IN_SALT',   getenv_docker('WORDPRESS_LOGGED_IN_SALT',   'put your unique phrase here') );
define( 'NONCE_SALT',       getenv_docker('WORDPRESS_NONCE_SALT',       'put your unique phrase here') );
// (See also https://wordpress.stackexchange.com/a/152905/199287)

/**#@-*/

/**
 * WordPress database table prefix.
 *
 * You can have multiple installations in one database if you give each
 * a unique prefix. Only numbers, letters, and underscores please!
 */
$table_prefix = getenv_docker('WORDPRESS_TABLE_PREFIX', 'wp_');

/**
 * For developers: WordPress debugging mode.
 *
 * Change this to true to enable the display of notices during development.
 * It is strongly recommended that plugin and theme developers use WP_DEBUG
 * in their development environments.
 *
 * For information on other constants that can be used for debugging,
 * visit the documentation.
 *
 * @link https://wordpress.org/documentation/article/debugging-in-wordpress/
 */
define( 'WP_DEBUG', !!getenv_docker('WORDPRESS_DEBUG', '') );

/* Add any custom values between this line and the "stop editing" line. */
// define('WP_HOME','https://sampledomain.com/');
// define('WP_SITEURL','https://sampledomain.com/');
/** SSL */
// define('FORCE_SSL_ADMIN', true);

// If we're behind a proxy server and using HTTPS, we need to alert WordPress of that fact
// see also https://wordpress.org/support/article/administration-over-ssl/#using-a-reverse-proxy
// if (isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && strpos($_SERVER['HTTP_X_FORWARDED_PROTO'], 'https') !== false) {
// 	$_SERVER['HTTPS'] = 'on';
// }
// (we include this by default because reverse proxying is extremely common in container environments)

// Enable SSL/TLS between Wordpress and MYSQL database
// Define('MYSQL_CLIENT_FLAGS', MYSQLI_CLIENT_SSL);//This activates SSL mode
// Define('MYSQL_SSL_CA', '/usr/src/wordpress/CERTNAME.pem');

/* That's all, stop editing!  Happy publishing. */

/** Absolute path to the WordPress directory. */
if ( !  defined( 'ABSPATH' ) ) {
	define( 'ABSPATH', __DIR__ . '/' );
}

/** Sets up WordPress vars and included files. */
require_once ABSPATH . 'wp-settings.php';
EOF

chown -Rf apache:apache ./wordpress/
chmod -Rf 775 ./wordpress/

systemctl enable --now httpd
systemctl restart httpd
		EOT
	)
	type = "RunShellScript"
	timeout = 60 * 10
}

resource "alicloud_ecs_invocation" "default" {
	instance_id = [alicloud_instance.default.id]
	command_id = alicloud_ecs_command.default.id
	timeouts {
		create = "30m"
	}
}

Periodically refresh the ApsaraDB RDS secret

This Terraform template schedules the ECS instance to periodically refresh the ApsaraDB RDS secret for improved access security.

  1. Create a command named tf-secret-rotation that retrieves the latest secret from KMS and updates the local credential files.

  2. Run the command every 60 minutes via Cloud Assistant to refresh the secret.

Security benefits:

  • Temporary credentials from the metadata service replace long-term AccessKey pairs, reducing the risk of key leaks.

  • Regular secret updates limit the time window for attackers to exploit compromised credentials.

  • Combined with properly configured security group rules, regular credential rotation improves overall system security against unauthorized access.

For higher security, this template works with Secrets Manager to automatically rotate the ApsaraDB RDS secret. For manual rotation, run the tf-secret-rotation command after calling the RotateSecret operation.

resource "alicloud_ecs_command" "secret_rotation" {
	name = "tf-secret-rotation"
	command_content = base64encode(<<EOT
#!/bin/bash

cat > /tmp/secret_rotation.sh << "EOF"
#!/bin/bash

echo "$(date --rfc-3339=seconds): Updating the secret" >> /var/log/secret_rotation.log
token=$(curl -X PUT "http://100.100.100.200/latest/api/token" -H "X-aliyun-ecs-metadata-token-ttl-seconds:60")
region_id=$(curl -H "X-aliyun-ecs-metadata-token: $token" http://100.100.100.200/latest/meta-data/region-id)
aliyun --mode EcsRamRole --ram-role-name wordpress --region $region_id kms GetSecretValue --SecretName wordpress_db_passwd | 
		jq '.SecretData|fromjson.AccountName' |tr -d '"' > /var/www/html/wordpress/.WORDPRESS_DB_USER_FILE
aliyun --mode EcsRamRole --ram-role-name wordpress --region $region_id kms GetSecretValue --SecretName wordpress_db_passwd | 
		jq '.SecretData|fromjson|.AccountPassword'|tr -d '"' > /var/www/html/wordpress/.WORDPRESS_DB_PASSWORD_FILE
EOF

flock /var/www/html/wordpress/.env.lock -c "/bin/bash /tmp/secret_rotation.sh"
EOT
	)
	type = "RunShellScript"
	timeout = 30
}

resource "alicloud_ecs_invocation" "secret_rotation" {
	instance_id = [alicloud_instance.default.id]
	command_id = alicloud_ecs_command.secret_rotation.id
	timeouts {
		create = "30s"
	}
	repeat_mode = "Period"
	frequency = "rate(60m)"
}

Step 5: Configure patch baselines and automatic vulnerability fixing

This Terraform template applies the following default patch installation policy:

  • Install patches every seven days in the early morning.

  • Enable automatic patch installation without instance restart.

  • Create disk snapshots before patch installation, retained for seven days.

data "alicloud_regions" "current_region_ds" {
	current = true
}

# Create a RAM role.
resource "alicloud_ram_role" "oos" {
	name = "oos"
	document    = <<EOF
{
  "Statement": [
    {
      "Action": "sts:AssumeRole",
      "Effect": "Allow",
      "Principal": {
        "Service": [
          "oos.aliyuncs.com"
        ]
      }
    }
  ],
  "Version": "1"
}
EOF
}

# Create a RAM policy.
resource "alicloud_ram_policy" "oos" {
	policy_name = "oos"
	policy_document = <<EOT
{
  "Version": "1",
  "Statement": [
    {
      "Action": [
        "ecs:CreateSnapshot",
        "ecs:DescribeCloudAssistantStatus",
        "ecs:DescribeDisks",
        "ecs:DescribeInstances",
        "ecs:DescribeInvocationResults",
        "ecs:DescribeInvocations",
        "ecs:DescribeManagedInstances",
        "ecs:DescribeSnapshots",
        "ecs:RebootInstance",
        "ecs:RunCommand"
      ],
      "Resource": "*",
      "Effect": "Allow"
    },
    {
      "Action": [
        "ecd:CreateSnapshot",
        "ecd:DescribeCloudAssistantStatus",
        "ecd:DescribeDesktops",
        "ecd:DescribeInvocations",
        "ecd:DescribeSnapshots",
        "ecd:RebootDesktops",
        "ecd:RunCommand"
      ],
      "Resource": "*",
      "Effect": "Allow"
    },
    {
      "Action": [
        "oos:ListInstancePatchStates",
        "oos:StartExecution"
      ],
      "Resource": "*",
      "Effect": "Allow"
    }
  ]
}
EOT
}

# Attach the RAM policy to the RAM role.
resource "alicloud_ram_role_policy_attachment" "oos" {
	policy_name = alicloud_ram_policy.oos.id
	policy_type = "Custom"
	role_name = alicloud_ram_role.oos.name
}

# Execute the OOS template.
resource "alicloud_oos_execution" "patch" {
	template_name = "ACS-ECS-ScheduleApplyPatchBaseline"
	description   = "Auto patch software to satify the baseline"
	parameters    = jsonencode({
		regionId: "${data.alicloud_regions.current_region_ds.regions.0.id}",
		resourceType: "ALIYUN::ECS::Instance",
		targets: {
			ResourceIds: ["${alicloud_instance.default.id}"],
			RegionId: "${data.alicloud_regions.current_region_ds.regions.0.id}",
			Type: "ResourceIds",
		},
		timerTrigger: {
			expression: "0 0 1 */7 * *",
			type: "cron",
			timeZone: "Asia/Shanghai",
			endDate: "2099-04-04T04:00:00Z",
		},
		action: "install",
		whetherCreateSnapshot: true,
		retentionDays: 7,
		rebootIfNeed: false,
		OOSAssumeRole: "${alicloud_ram_role.oos.name}",
	})
}

Summary

Security defense requires in-depth protection across multiple dimensions, such as data security, network security, identity and access control, operating system security, and application security. See Data security, Network security, and Identity and access control.