Providing secure access to content in Cloud CDN through Terraform
To configure secure access to content in Cloud CDN:
- Prepare your cloud.
- Delegate your domain to Cloud DNS.
- Create an infrastructure.
- Publish the webiste on the web server.
- Test secure access to files.
If you no longer need the resources you created, delete them.
Prepare your cloud
Sign up for Yandex Cloud and create a billing account:
- Go to the management console
and log in to Yandex Cloud or create an account if you do not have one yet. - On the Yandex Cloud Billing
page, make sure you have a billing account linked and it has theACTIVE
orTRIAL_ACTIVE
status. If you do not have a billing account, create one.
If you have an active billing account, you can go to the cloud page
Learn more about clouds and folders.
Required paid resources
The cost of infrastructure support for setting up secure access to Cloud CDN content includes:
- Fee for using a public IP address (see Yandex Virtual Private Cloud pricing).
- Fee for VM computing resources and disks (see Yandex Compute Cloud pricing).
- Fee for using a public DNS zone and public DNS requests (see Yandex Cloud DNS pricing).
- Fee for data storage in Object Storage, operations with data, and outgoing traffic (see Object Storage pricing).
- Fee for outgoing traffic from CDN servers (see Cloud CDN pricing).
Delegate your domain to Cloud DNS
Delegate your domain to Cloud DNS. To do this, in your domain registrar's account, specify the addresses of these DNS servers in your domain settings: ns1.yandexcloud.net
and ns2.yandexcloud.net
.
Create an infrastructure
With Terraform
Terraform is distributed under the Business Source License
For more information about the provider resources, see the documentation on the Terraform
To create an infrastructure using Terraform:
-
Install Terraform, get the authentication credentials, and specify the source for installing the Yandex Cloud provider (see Configure a provider, step 1).
-
Prepare files with the infrastructure description:
Ready-made configurationManually-
Clone the repository with configuration files.
git clone https://github.com/yandex-cloud-examples/yc-cdn-protected-access
-
Go to the directory with the repository. Make sure it contains the following files:
yc-cdn-secure-token.tf
: New infrastructure configuration.yc-cdn-secure-token.auto.tfvars
: User data file.
-
Prepare files for uploading to the bucket:
-
Save any image in JPEG format to the
content.jpg
file. -
Create a file named
index.html
:<html> <body> </body> </html>
-
-
Create a folder for configuration files.
-
In the folder, create:
-
yc-cdn-secure-token.tf
configuration fileyc-cdn-secure-token.tf
# Declaring variables variable "folder_id" { type = string } variable "domain_name" { type = string } variable "subdomain_name" { type = string } variable "bucket_name" { type = string } variable "cdn_cname" { type = string } variable "secure_key" { type = string } variable "ssh_key_path" { type = string } variable "index_file_path" { type = string } variable "content_file_path" { type = string } locals { sa_name = "my-service-account" network_name = "webserver-network" subnet_name = "webserver-subnet-ru-central1-b" sg_name = "webserver-sg" vm_name = "mywebserver" domain_zone_name = "my-domain-zone" cert_name = "mymanagedcert" origin_gp_name = "my-origin-group" } # Configuring a provider terraform { required_providers { yandex = { source = "yandex-cloud/yandex" } } required_version = ">= 0.13" } provider "yandex" { folder_id = var.folder_id } # Creating a service account resource "yandex_iam_service_account" "ig-sa" { name = local.sa_name } # Assigning roles to a service account resource "yandex_resourcemanager_folder_iam_member" "storage-editor" { folder_id = var.folder_id role = "storage.editor" member = "serviceAccount:${yandex_iam_service_account.ig-sa.id}" } # Creating a static access key for CA resource "yandex_iam_service_account_static_access_key" "sa-static-key" { service_account_id = "${yandex_iam_service_account.ig-sa.id}" } # Create network resource "yandex_vpc_network" "webserver-network" { name = local.network_name } # Create subnet resource "yandex_vpc_subnet" "webserver-subnet-b" { name = local.subnet_name zone = "ru-central1-b" network_id = "${yandex_vpc_network.webserver-network.id}" v4_cidr_blocks = ["192.168.1.0/24"] } # Creating a security group resource "yandex_vpc_security_group" "webserver-sg" { name = local.sg_name network_id = "${yandex_vpc_network.webserver-network.id}" ingress { protocol = "TCP" description = "http" v4_cidr_blocks = ["0.0.0.0/0"] port = 80 } ingress { protocol = "TCP" description = "https" v4_cidr_blocks = ["0.0.0.0/0"] port = 443 } ingress { protocol = "TCP" description = "ssh" v4_cidr_blocks = ["0.0.0.0/0"] port = 22 } egress { protocol = "ANY" description = "any" v4_cidr_blocks = ["0.0.0.0/0"] from_port = 0 to_port = 65535 } } # Creating a boot disk for the VM resource "yandex_compute_disk" "boot-disk" { type = "network-ssd" zone = "ru-central1-b" size = "20" image_id = "fd8jtn9i7e9ha5q25niu" } # Creating a VM instance resource "yandex_compute_instance" "mywebserver" { name = local.vm_name platform_id = "standard-v2" zone = "ru-central1-b" resources { cores = "2" memory = "2" } boot_disk { disk_id = yandex_compute_disk.boot-disk.id } network_interface { subnet_id = "${yandex_vpc_subnet.webserver-subnet-b.id}" nat = true security_group_ids = ["${yandex_vpc_security_group.webserver-sg.id}"] } metadata = { user-data = "#cloud-config\nusers:\n - name: yc-user\n groups: sudo\n shell: /bin/bash\n sudo: 'ALL=(ALL) NOPASSWD:ALL'\n ssh-authorized-keys:\n - ${file("${var.ssh_key_path}")}" } } # Creating a public DNS zone resource "yandex_dns_zone" "my-domain-zone" { name = local.domain_zone_name zone = "${var.domain_name}." public = true } # Creating a type A resource record for the web server resource "yandex_dns_recordset" "rsА1" { zone_id = yandex_dns_zone.my-domain-zone.id name = "${yandex_dns_zone.my-domain-zone.zone}" type = "A" ttl = 600 data = ["${yandex_compute_instance.mywebserver.network_interface.0.nat_ip_address}"] } # Adding a Let's Encrypt certificate resource "yandex_cm_certificate" "le-certificate" { name = local.cert_name domains = [var.domain_name,"${var.subdomain_name}.${var.domain_name}"] managed { challenge_type = "DNS_CNAME" challenge_count = 2 } } # Creating CNAME records for domain validation when issuing a certificate resource "yandex_dns_recordset" "validation-record" { count = yandex_cm_certificate.le-certificate.managed[0].challenge_count zone_id = yandex_dns_zone.my-domain-zone.id name = yandex_cm_certificate.le-certificate.challenges[count.index].dns_name type = yandex_cm_certificate.le-certificate.challenges[count.index].dns_type ttl = 600 data = [yandex_cm_certificate.le-certificate.challenges[count.index].dns_value] } # Waiting for domain validation and the issue of a Let's Encrypt certificate data "yandex_cm_certificate" "example-com" { depends_on = [yandex_dns_recordset.validation-record] certificate_id = yandex_cm_certificate.le-certificate.id wait_validation = true } # Creating a bucket resource "yandex_storage_bucket" "cdn-source" { access_key = yandex_iam_service_account_static_access_key.sa-static-key.access_key secret_key = yandex_iam_service_account_static_access_key.sa-static-key.secret_key bucket = var.bucket_name max_size = 1073741824 anonymous_access_flags { read = true list = true } website { index_document = "index.html" } } # Uploading the website home page to the bucket resource "yandex_storage_object" "index-object" { access_key = yandex_iam_service_account_static_access_key.sa-static-key.access_key secret_key = yandex_iam_service_account_static_access_key.sa-static-key.secret_key bucket = "${yandex_storage_bucket.cdn-source.bucket}" key = var.index_file_path source = var.index_file_path } # Uploading a test file to the bucket resource "yandex_storage_object" "content-object" { access_key = yandex_iam_service_account_static_access_key.sa-static-key.access_key secret_key = yandex_iam_service_account_static_access_key.sa-static-key.secret_key bucket = "${yandex_storage_bucket.cdn-source.bucket}" key = var.content_file_path source = var.content_file_path } # Creating a CDN origin group resource "yandex_cdn_origin_group" "my-origin-group" { name = local.origin_gp_name origin { source = "${var.bucket_name}.website.yandexcloud.net" } } # Creating a CDN resource resource "yandex_cdn_resource" "my-resource" { cname = "${var.subdomain_name}.${var.domain_name}" active = true origin_protocol = "match" origin_group_id = "${yandex_cdn_origin_group.my-origin-group.id}" ssl_certificate { type = "certificate_manager" certificate_manager_id = "${data.yandex_cm_certificate.example-com.id}" } options { custom_host_header = "${var.bucket_name}.website.yandexcloud.net" secure_key = "${var.secure_key}" enable_ip_url_signing = true } } # Creating a CNAME record for the CDN resource resource "yandex_dns_recordset" "cdn-cname" { zone_id = yandex_dns_zone.my-domain-zone.id name = "${yandex_cdn_resource.my-resource.cname}." type = "CNAME" ttl = 600 data = [var.cdn_cname] }
-
The
yc-cdn-secure-token.auto.tfvars
user data file:yc-cdn-secure-token.auto.tfvars
folder_id = "<folder_ID>" ssh_key_path = "<path_to_public_SSH_key_file>" index_file_path = "<name_of_website_homepage_file>" content_file_path = "<name_of_file_with_content_to_upload_to_bucket>" domain_name = "<domain_name>" subdomain_name = "<CDN_resource_subdomain_prefix>" bucket_name = "<bucket_name>" cdn_cname = "<CDN_provider_domain_name_value>" secure_key = "<secret_key>"
-
For more information about the parameters of resources used in Terraform, see the provider documentation:
- Service account: yandex_iam_service_account
. - Service account role: yandex_resourcemanager_folder_iam_member
. - Static access key: yandex_iam_service_account_static_access_key
. - Network: yandex_vpc_network
. - Subnets: yandex_vpc_subnet
. - Security group: yandex_vpc_security_group
. - VM disk: yandex_compute_disk
. - VM instance: yandex_compute_instance
. - DNS zone: yandex_dns_zone
. - DNS resource record: yandex_dns_recordset
. - TLS certificate: yandex_cm_certificate
. - Bucket: yandex_storage_bucket
. - Object: yandex_storage_object
. - Origin group: yandex_cdn_origin_group
. - CDN resource: yandex_cdn_resource
.
-
-
In the
yc-cdn-secure-token.auto.tfvars
file, set the following user-defined properties:folder_id
: Folder ID.ssh_key_path
: Path to the file with a public SSH key to authenticate the user on the VM. For more information, see Creating an SSH key pair.index_file_path
: Path to the website homepage file.content_file_path
: Path to the file with content for upload to the bucket.domain_name
: Your domain name, e.g.,example.com
.subdomain_name
: Prefix of subdomain for the CDN resource, e.g.,cdn
.bucket_name
: Bucket name consistent with the naming conventions.cdn_cname
: Domain name of the Cloud CDN provider for the folder's CDN resources.secure_key
: Secret key that is a string of 6 to 32 characters. It is required to restrict access to a resource using secure tokens.
-
Create resources:
-
In the terminal, change to the folder where you edited the configuration file.
-
Make sure the configuration file is correct using the command:
terraform validate
If the configuration is correct, the following message is returned:
Success! The configuration is valid.
-
Run the command:
terraform plan
The terminal will display a list of resources with parameters. No changes are made at this step. If the configuration contains errors, Terraform will point them out.
-
Apply the configuration changes:
terraform apply
-
Confirm the changes: type
yes
in the terminal and press Enter.
-
It may take up to 15 minutes for the new settings of the existing resource to apply to CDN servers. After that, we recommend purging the resource cache.
The content on the new CDN resource will be accessible only via signed links.
Publish the webiste on the web server
Next, you will create and publish on your web server a website that will generate signed links to content hosted on the secure CDN resource. For data transfer security, you will copy the previously created TLS certificate to the same web server and enable SSL encryption.
Download the certificate from Certificate Manager
To use the TLS certificate created in Certificate Manager in your web server configuration, download the certificate chain and private key to the current directory:
-
Learn the ID of the previously created TLS certificate:
yc certificate-manager certificate list
Result:
+----------------------+---------------+-----------------------------+---------------------+---------+--------+ | ID | NAME | DOMAINS | NOT AFTER | TYPE | STATUS | +----------------------+---------------+-----------------------------+---------------------+---------+--------+ | fpq90lobsh0l******** | mymanagedcert | cdn.example.com,example.com | 2024-03-22 16:42:53 | MANAGED | ISSUED | +----------------------+---------------+-----------------------------+---------------------+---------+--------+
For more information about the
yc certificate-manager certificate list
command, see the CLI reference. -
Download the key and certificate by specifying the ID you got in the previous step:
yc certificate-manager certificate content \ --id <certificate_ID> \ --chain ./certificate_full_chain.pem \ --key ./private_key.pem
For more information about the
yc certificate-manager certificate content
command, see the CLI reference.
Configure the web server
-
Copy the certificates and private key thus obtained to the VM hosting the web server:
scp ./certificate_full_chain.pem yc-user@<VM_IP_address>:certificate_full_chain.pem \ && scp ./private_key.pem yc-user@<VM_IP_address>:private_key.pem
Where
<VM_IP_address>
is the public IP address of the previously created VM with a web server.You can find the IP address of your VM in the management console
on the VM page under Network or using theyc compute instance get mywebserver
CLI command.If this is your first time connecting to the VM, you will see an unknown host warning:
The authenticity of host '51.250.**.*** (51.250.**.***)' can't be established. ED25519 key fingerprint is SHA256:PpcKdcT09gjU045pkEIwIU8lAXXLpwJ6bKC********. This key is not known by any other names Are you sure you want to continue connecting (yes/no/[fingerprint])?
Type
yes
in the terminal and press Enter. -
Connect to the VM with the web server:
ssh yc-user@<VM_IP_address>
-
Create a directory for the certificate and move the copied files there:
sudo mkdir /etc/ssl-certificates sudo mv certificate_full_chain.pem /etc/ssl-certificates/ sudo mv private_key.pem /etc/ssl-certificates/
-
Create a directory for your website files and grant the required permissions for it to the
www-data
user:sudo mkdir -p /var/www/<domain_name>/public_html sudo chown www-data:www-data /var/www/<domain_name>/public_html
Where
<domain_name>
is the domain name of your website, e.g.,example.com
. -
Configure a virtual host for your website:
-
Create a virtual host configuration file:
sudo nano /etc/apache2/sites-available/mywebsite.conf
-
Add the following configuration into the file:
<VirtualHost *:443> ServerName <domain_name> ServerAdmin webmaster@localhost DocumentRoot /var/www/<domain_name>/public_html ErrorLog ${APACHE_LOG_DIR}/error.log CustomLog ${APACHE_LOG_DIR}/access.log combined SSLEngine on SSLCertificateFile /etc/ssl-certificates/certificate_full_chain.pem SSLCertificateChainFile /etc/ssl-certificates/certificate_full_chain.pem SSLCertificateKeyFile /etc/ssl-certificates/private_key.pem </VirtualHost>
Where
<domain_name>
is the domain name of your website, e.g.,example.com
. -
Activate the virtual host you created:
sudo a2ensite mywebsite
Result:
Enabling site mywebsite. To activate the new configuration, you need to run: systemctl reload apache2
-
Enable
ssl
for the web server:sudo a2enmod ssl
Result:
Considering dependency setenvif for ssl: Module setenvif already enabled Considering dependency mime for ssl: Module mime already enabled Considering dependency socache_shmcb for ssl: Enabling module socache_shmcb. Enabling module ssl. See /usr/share/doc/apache2/README.Debian.gz on how to configure SSL and create self-signed certificates. To activate the new configuration, you need to run: systemctl restart apache2
-
Restart the web server:
sudo systemctl reload apache2
-
Create a website
-
Create the home page file for the website:
sudo nano /var/www/<domain_name>/public_html/index.php
Where
<domain_name>
is the domain name of your website, e.g.,example.com
. -
Add the following code into the
index.php
file you created:<!DOCTYPE html> <html> <head> <title>Secure token generator website</title> <meta charset="utf-8" /> </head> <body> <h2>Secure link generator</h2> <p>Below, a signed link to the secure CDN resource has been generated. The link is valid for five minutes. The content at this link is available only to the user the link was generated for by the website (verified by IP address).</p> <br> <?php $secret = '<secret_key>'; $ip = trim(getUserIpAddr()); $domain_name = '<domain_name>'; $path = '<object_key>'; $expires = time() + 300; $link = "$expires$path$ip $secret"; $md5 = md5($link, true); $md5 = base64_encode($md5); $md5 = strtr($md5, '+/', '-_'); $md5 = str_replace('=', '', $md5); $url = '<a href="https://'.$domain_name.$path.'?md5='.$md5.'&expires='.$expires.'" target="_blank">Signed link to file</a>'; echo "<p>Your IP address: <b>".$ip."</b></p><p>If you are using a VPN, you link may not work. For the signed link generator to work properly, disable your VPN.</p>"; echo "<br><br>"; echo $url; function getUserIpAddr() { if (!empty($_SERVER['HTTP_CLIENT_IP'])) { $addr = $_SERVER['HTTP_CLIENT_IP']; } elseif (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) { $addr = $_SERVER['HTTP_X_FORWARDED_FOR']; } else { $addr = $_SERVER['REMOTE_ADDR']; } return $addr; } ?> </body> </html>
Where:
$secret
: Secret key created when configuring the CDN resource.$domain_name
: Domain name of the created CDN resource, e.g.,cdn.example.com
.$path
: Key of the object in the source bucket, e.g.,/content.jpg
. It must contain/
.
The website will generate a signed link to access this object via the CDN resource.
Test secure access to files
To test the generator of signed links to the secure CDN resource:
-
In your browser, go to the website you created, e.g.,
https://example.com
. -
Click the link that was generated.
If everything works as it should, you will see the image hosted on the secure CDN resource.
Note
An active VPN may interfere with the signed link generator's operation. For the website to work correctly, disable your VPN.
-
Open the generated link on another device that uses another IP address to access the internet, e.g., a smartphone.
Access to content will be denied.
-
Try opening the link on the first device after the five-minute timeout expires.
Access to content will be denied.
You have configured secure access to your content.
When generating links, you can also specify a trusted IP address, e.g., the one used for internet access in your corporate network. Thus you will restrict access to your content from outside your company’s network infrastructure.
How to delete the resources you created
To stop paying for the resources you created:
-
Open the
yc-cdn-secure-token.tf
configuration file and delete the description of the new infrastructure from it. -
Apply the changes:
-
In the terminal, change to the folder where you edited the configuration file.
-
Make sure the configuration file is correct using the command:
terraform validate
If the configuration is correct, the following message is returned:
Success! The configuration is valid.
-
Run the command:
terraform plan
The terminal will display a list of resources with parameters. No changes are made at this step. If the configuration contains errors, Terraform will point them out.
-
Apply the configuration changes:
terraform apply
-
Confirm the changes: type
yes
in the terminal and press Enter.
-