📜 ⬆️ ⬇️

Amazon CloudFront + Custom Origins

For about a year, Amazon has added Custom Origins support for its CloudFront service and in my opinion this is very good, because I have long been eyeing different CDNs.

Honestly, it is difficult to find now a CDN for a small project or for a start, when traffic according to calculations should not exceed 50 - 100 GB per month. They are either very expensive, or work only with those sites that generate a lot of traffic and almost all work on a prepaid basis, i.e. You do not pay for actually used traffic, but for some amount that you may not work out.

Amazon CloudFront stands out from the competition in this respect. The fee here is charged only for actually used traffic and is rather small, depending on the region, it averages $ 0.15 per GB of traffic. But before you had to use CloudFront together with the S3 service, which increased the cost, now you can use your own server as the origin server.

I would like to tell you in detail about how I connected, paid for and added support for CloudFront in my project.
')

Payment


First I want to talk about how I pay for the service and what difficulties I encountered at this stage.

So, I myself am from Ukraine, but I think that for all or many CIS countries this information will be relevant. The only form of payment offered to me on Amazon is to pay with a credit card. Those. no electronic money like WebMoney is accepted. Therefore, I had to go to the bank and open a credit card. I chose Aval, although most likely the bank does not matter much, but the credit card itself matters. I discovered the dollar Visa Classic, and when I opened it I asked a bank employee if I could pay with this card for purchases on the Internet on foreign websites. Opening the card took 10 business days.

Now about the problems that I encountered:
1. Activation of the card - here I confess I have a little protupil. I put the money on the card through the bank's cash desk, but it turns out that the card is activated when you do something with it through an ATM (with money already on it), even a balance check will do. In any case, it is worth checking with the bank employee how exactly the card is activated in their bank.

2. CVV - Amazon, which surprised me a lot, does not request the CVV code of the card. When registering, you need to enter only the card number, the date until which it is valid and the name of the card holder. As it turned out, Amazon is working on some scheme where this CVV is not required, but you need, at the time of payment or forever, to disable protection of your card by CVV code. This can be done by telephone through the call-center of the bank or by writing an application in the branch where you opened the card.

In principle, this is all the problems that I have with the payment.

Registration and keys


The registration process can be started from the CloudFront page. Registration is very simple, at one of the registration steps you will need to enter information about the card and if it passes validation, the account will be registered. Together with your CloudFront account, an account will be automatically created for the S3 service, even if we want to use Custom Origins.

After registration, you need to generate the keys that you need to communicate with the API and to form secure URLs to your files. To do this, go to the Security Credentials section of your account. Here you will find 3 tabs:
1. Access Keys - data from this tab will be needed to form the authorization header when querying the API;

2. X.509 certificates - keys for API requests through SOAP;

3. Key Pairs - keys to form secure URLs.

Standard headers when communicating with REST API



All requests to the REST API should contain the following headers:
1. x-amz-date - the date of the request. The date must be in one of the formats described in the RFC 2616 specification section “Date / Time Formats”;

2. Content-Type - the type of the request body, usually “application / xml”;

3. Content-Length - the length of the request body;

4. Authorization - the authorization header has the following structure: “AWS aws_secret_key_id: signature”, where:
4.1 AWS - constant string, followed by a whitespace;
4.2 aws_secret_key_id - can be found on the Security Credentials page of your account in the tab “Access Keys”;
4.3 signature is a control signature generated using the sha1 hashing algorithm, based on the request date you specified in the x-amz-date header and the secret key that can be found on the page where aws_secret_key_id is located. Below is the PHP code that generates signature:
  1. $signature = base64_encode ( hash_hmac ( 'sha1' , $requestDate , $awsSecretKey , true ) ) ;

Distribution



In order to be able to distribute files through Amazon CloudFront, you must create a Distribution.
On one account, you can create a maximum of 100 Distributions, the number of files in one Distribution is unlimited.
Distribution are of 2 types:
1. Download - to distribute files via the HTTP and HTTPS protocols (HTTPS is a bit more expensive);

2. Stream - to distribute video and audio files via the RTMP protocol. I want to immediately disappoint, Distribution of the Stream view does not work with Custom Origins , only with S3 as the origin server.

Based on the title of the article and clause 2, I will consider only Download Distribution. I will immediately give an example of an XML request for creating a new Download Distribution:
<?xml version = "1.0" encoding = "UTF-8" ?> <DistributionConfig xmlns = "http://cloudfront.amazonaws.com/doc/2010-11-01/" > <CustomOrigin > <DNSName > www.example.com </DNSName > <HTTPPort > 80 </HTTPPort > <OriginProtocolPolicy > http-only </OriginProtocolPolicy > </CustomOrigin > <CallerReference > your unique caller reference </CallerReference > <CNAME > mysite.example.com </CNAME > <Comment > My comments </Comment > <Enabled > true </Enabled > <TrustedSigners > <Self /> </TrustedSigners > <Logging > <Bucket > mylogs.s3.amazonaws.com </Bucket > <Prefix > myprefix/ </Prefix > </Logging > </DistributionConfig >
  1. <?xml version = "1.0" encoding = "UTF-8" ?> <DistributionConfig xmlns = "http://cloudfront.amazonaws.com/doc/2010-11-01/" > <CustomOrigin > <DNSName > www.example.com </DNSName > <HTTPPort > 80 </HTTPPort > <OriginProtocolPolicy > http-only </OriginProtocolPolicy > </CustomOrigin > <CallerReference > your unique caller reference </CallerReference > <CNAME > mysite.example.com </CNAME > <Comment > My comments </Comment > <Enabled > true </Enabled > <TrustedSigners > <Self /> </TrustedSigners > <Logging > <Bucket > mylogs.s3.amazonaws.com </Bucket > <Prefix > myprefix/ </Prefix > </Logging > </DistributionConfig >
  2. <?xml version = "1.0" encoding = "UTF-8" ?> <DistributionConfig xmlns = "http://cloudfront.amazonaws.com/doc/2010-11-01/" > <CustomOrigin > <DNSName > www.example.com </DNSName > <HTTPPort > 80 </HTTPPort > <OriginProtocolPolicy > http-only </OriginProtocolPolicy > </CustomOrigin > <CallerReference > your unique caller reference </CallerReference > <CNAME > mysite.example.com </CNAME > <Comment > My comments </Comment > <Enabled > true </Enabled > <TrustedSigners > <Self /> </TrustedSigners > <Logging > <Bucket > mylogs.s3.amazonaws.com </Bucket > <Prefix > myprefix/ </Prefix > </Logging > </DistributionConfig >
  3. <?xml version = "1.0" encoding = "UTF-8" ?> <DistributionConfig xmlns = "http://cloudfront.amazonaws.com/doc/2010-11-01/" > <CustomOrigin > <DNSName > www.example.com </DNSName > <HTTPPort > 80 </HTTPPort > <OriginProtocolPolicy > http-only </OriginProtocolPolicy > </CustomOrigin > <CallerReference > your unique caller reference </CallerReference > <CNAME > mysite.example.com </CNAME > <Comment > My comments </Comment > <Enabled > true </Enabled > <TrustedSigners > <Self /> </TrustedSigners > <Logging > <Bucket > mylogs.s3.amazonaws.com </Bucket > <Prefix > myprefix/ </Prefix > </Logging > </DistributionConfig >
  4. <?xml version = "1.0" encoding = "UTF-8" ?> <DistributionConfig xmlns = "http://cloudfront.amazonaws.com/doc/2010-11-01/" > <CustomOrigin > <DNSName > www.example.com </DNSName > <HTTPPort > 80 </HTTPPort > <OriginProtocolPolicy > http-only </OriginProtocolPolicy > </CustomOrigin > <CallerReference > your unique caller reference </CallerReference > <CNAME > mysite.example.com </CNAME > <Comment > My comments </Comment > <Enabled > true </Enabled > <TrustedSigners > <Self /> </TrustedSigners > <Logging > <Bucket > mylogs.s3.amazonaws.com </Bucket > <Prefix > myprefix/ </Prefix > </Logging > </DistributionConfig >
  5. <?xml version = "1.0" encoding = "UTF-8" ?> <DistributionConfig xmlns = "http://cloudfront.amazonaws.com/doc/2010-11-01/" > <CustomOrigin > <DNSName > www.example.com </DNSName > <HTTPPort > 80 </HTTPPort > <OriginProtocolPolicy > http-only </OriginProtocolPolicy > </CustomOrigin > <CallerReference > your unique caller reference </CallerReference > <CNAME > mysite.example.com </CNAME > <Comment > My comments </Comment > <Enabled > true </Enabled > <TrustedSigners > <Self /> </TrustedSigners > <Logging > <Bucket > mylogs.s3.amazonaws.com </Bucket > <Prefix > myprefix/ </Prefix > </Logging > </DistributionConfig >
  6. <?xml version = "1.0" encoding = "UTF-8" ?> <DistributionConfig xmlns = "http://cloudfront.amazonaws.com/doc/2010-11-01/" > <CustomOrigin > <DNSName > www.example.com </DNSName > <HTTPPort > 80 </HTTPPort > <OriginProtocolPolicy > http-only </OriginProtocolPolicy > </CustomOrigin > <CallerReference > your unique caller reference </CallerReference > <CNAME > mysite.example.com </CNAME > <Comment > My comments </Comment > <Enabled > true </Enabled > <TrustedSigners > <Self /> </TrustedSigners > <Logging > <Bucket > mylogs.s3.amazonaws.com </Bucket > <Prefix > myprefix/ </Prefix > </Logging > </DistributionConfig >
  7. <?xml version = "1.0" encoding = "UTF-8" ?> <DistributionConfig xmlns = "http://cloudfront.amazonaws.com/doc/2010-11-01/" > <CustomOrigin > <DNSName > www.example.com </DNSName > <HTTPPort > 80 </HTTPPort > <OriginProtocolPolicy > http-only </OriginProtocolPolicy > </CustomOrigin > <CallerReference > your unique caller reference </CallerReference > <CNAME > mysite.example.com </CNAME > <Comment > My comments </Comment > <Enabled > true </Enabled > <TrustedSigners > <Self /> </TrustedSigners > <Logging > <Bucket > mylogs.s3.amazonaws.com </Bucket > <Prefix > myprefix/ </Prefix > </Logging > </DistributionConfig >
  8. <?xml version = "1.0" encoding = "UTF-8" ?> <DistributionConfig xmlns = "http://cloudfront.amazonaws.com/doc/2010-11-01/" > <CustomOrigin > <DNSName > www.example.com </DNSName > <HTTPPort > 80 </HTTPPort > <OriginProtocolPolicy > http-only </OriginProtocolPolicy > </CustomOrigin > <CallerReference > your unique caller reference </CallerReference > <CNAME > mysite.example.com </CNAME > <Comment > My comments </Comment > <Enabled > true </Enabled > <TrustedSigners > <Self /> </TrustedSigners > <Logging > <Bucket > mylogs.s3.amazonaws.com </Bucket > <Prefix > myprefix/ </Prefix > </Logging > </DistributionConfig >
  9. <?xml version = "1.0" encoding = "UTF-8" ?> <DistributionConfig xmlns = "http://cloudfront.amazonaws.com/doc/2010-11-01/" > <CustomOrigin > <DNSName > www.example.com </DNSName > <HTTPPort > 80 </HTTPPort > <OriginProtocolPolicy > http-only </OriginProtocolPolicy > </CustomOrigin > <CallerReference > your unique caller reference </CallerReference > <CNAME > mysite.example.com </CNAME > <Comment > My comments </Comment > <Enabled > true </Enabled > <TrustedSigners > <Self /> </TrustedSigners > <Logging > <Bucket > mylogs.s3.amazonaws.com </Bucket > <Prefix > myprefix/ </Prefix > </Logging > </DistributionConfig >
  10. <?xml version = "1.0" encoding = "UTF-8" ?> <DistributionConfig xmlns = "http://cloudfront.amazonaws.com/doc/2010-11-01/" > <CustomOrigin > <DNSName > www.example.com </DNSName > <HTTPPort > 80 </HTTPPort > <OriginProtocolPolicy > http-only </OriginProtocolPolicy > </CustomOrigin > <CallerReference > your unique caller reference </CallerReference > <CNAME > mysite.example.com </CNAME > <Comment > My comments </Comment > <Enabled > true </Enabled > <TrustedSigners > <Self /> </TrustedSigners > <Logging > <Bucket > mylogs.s3.amazonaws.com </Bucket > <Prefix > myprefix/ </Prefix > </Logging > </DistributionConfig >
  11. <?xml version = "1.0" encoding = "UTF-8" ?> <DistributionConfig xmlns = "http://cloudfront.amazonaws.com/doc/2010-11-01/" > <CustomOrigin > <DNSName > www.example.com </DNSName > <HTTPPort > 80 </HTTPPort > <OriginProtocolPolicy > http-only </OriginProtocolPolicy > </CustomOrigin > <CallerReference > your unique caller reference </CallerReference > <CNAME > mysite.example.com </CNAME > <Comment > My comments </Comment > <Enabled > true </Enabled > <TrustedSigners > <Self /> </TrustedSigners > <Logging > <Bucket > mylogs.s3.amazonaws.com </Bucket > <Prefix > myprefix/ </Prefix > </Logging > </DistributionConfig >
  12. <?xml version = "1.0" encoding = "UTF-8" ?> <DistributionConfig xmlns = "http://cloudfront.amazonaws.com/doc/2010-11-01/" > <CustomOrigin > <DNSName > www.example.com </DNSName > <HTTPPort > 80 </HTTPPort > <OriginProtocolPolicy > http-only </OriginProtocolPolicy > </CustomOrigin > <CallerReference > your unique caller reference </CallerReference > <CNAME > mysite.example.com </CNAME > <Comment > My comments </Comment > <Enabled > true </Enabled > <TrustedSigners > <Self /> </TrustedSigners > <Logging > <Bucket > mylogs.s3.amazonaws.com </Bucket > <Prefix > myprefix/ </Prefix > </Logging > </DistributionConfig >
  13. <?xml version = "1.0" encoding = "UTF-8" ?> <DistributionConfig xmlns = "http://cloudfront.amazonaws.com/doc/2010-11-01/" > <CustomOrigin > <DNSName > www.example.com </DNSName > <HTTPPort > 80 </HTTPPort > <OriginProtocolPolicy > http-only </OriginProtocolPolicy > </CustomOrigin > <CallerReference > your unique caller reference </CallerReference > <CNAME > mysite.example.com </CNAME > <Comment > My comments </Comment > <Enabled > true </Enabled > <TrustedSigners > <Self /> </TrustedSigners > <Logging > <Bucket > mylogs.s3.amazonaws.com </Bucket > <Prefix > myprefix/ </Prefix > </Logging > </DistributionConfig >
  14. <?xml version = "1.0" encoding = "UTF-8" ?> <DistributionConfig xmlns = "http://cloudfront.amazonaws.com/doc/2010-11-01/" > <CustomOrigin > <DNSName > www.example.com </DNSName > <HTTPPort > 80 </HTTPPort > <OriginProtocolPolicy > http-only </OriginProtocolPolicy > </CustomOrigin > <CallerReference > your unique caller reference </CallerReference > <CNAME > mysite.example.com </CNAME > <Comment > My comments </Comment > <Enabled > true </Enabled > <TrustedSigners > <Self /> </TrustedSigners > <Logging > <Bucket > mylogs.s3.amazonaws.com </Bucket > <Prefix > myprefix/ </Prefix > </Logging > </DistributionConfig >
  15. <?xml version = "1.0" encoding = "UTF-8" ?> <DistributionConfig xmlns = "http://cloudfront.amazonaws.com/doc/2010-11-01/" > <CustomOrigin > <DNSName > www.example.com </DNSName > <HTTPPort > 80 </HTTPPort > <OriginProtocolPolicy > http-only </OriginProtocolPolicy > </CustomOrigin > <CallerReference > your unique caller reference </CallerReference > <CNAME > mysite.example.com </CNAME > <Comment > My comments </Comment > <Enabled > true </Enabled > <TrustedSigners > <Self /> </TrustedSigners > <Logging > <Bucket > mylogs.s3.amazonaws.com </Bucket > <Prefix > myprefix/ </Prefix > </Logging > </DistributionConfig >
  16. <?xml version = "1.0" encoding = "UTF-8" ?> <DistributionConfig xmlns = "http://cloudfront.amazonaws.com/doc/2010-11-01/" > <CustomOrigin > <DNSName > www.example.com </DNSName > <HTTPPort > 80 </HTTPPort > <OriginProtocolPolicy > http-only </OriginProtocolPolicy > </CustomOrigin > <CallerReference > your unique caller reference </CallerReference > <CNAME > mysite.example.com </CNAME > <Comment > My comments </Comment > <Enabled > true </Enabled > <TrustedSigners > <Self /> </TrustedSigners > <Logging > <Bucket > mylogs.s3.amazonaws.com </Bucket > <Prefix > myprefix/ </Prefix > </Logging > </DistributionConfig >
  17. <?xml version = "1.0" encoding = "UTF-8" ?> <DistributionConfig xmlns = "http://cloudfront.amazonaws.com/doc/2010-11-01/" > <CustomOrigin > <DNSName > www.example.com </DNSName > <HTTPPort > 80 </HTTPPort > <OriginProtocolPolicy > http-only </OriginProtocolPolicy > </CustomOrigin > <CallerReference > your unique caller reference </CallerReference > <CNAME > mysite.example.com </CNAME > <Comment > My comments </Comment > <Enabled > true </Enabled > <TrustedSigners > <Self /> </TrustedSigners > <Logging > <Bucket > mylogs.s3.amazonaws.com </Bucket > <Prefix > myprefix/ </Prefix > </Logging > </DistributionConfig >

Request URL : cloudfront.amazonaws.com/2010 –11–01 / distribution
Request Method : POST

I will describe in more detail what each of the tags means:
1. CustomOrigin is the area in which the parameters of your origin server are set:
1.1 DNSName - server origin domain;
1.2 HTTPPort - port for access to origin server via HTTP protocol;
1.3 OriginProtocolPolicy - Amazon can request files from your origin server in two ways: http-only — always via the HTTP protocol; match-viewer — via the protocol that the end user used to request the file, but only HTTP or HTTPS.
2. CallerReference - a unique identifier for the request, it can be both numeric and alphabetic, the main thing that would be unique;
3. CNAME - for each Distribution, Amazon creates a subdomain for the cloudfront.net domain. You can hang one or several CNAMEs on this domain;
4. Comment - a comment for Distribution;
5. Enabled - sets the Distribution active or not;
6. TrustedSigners - Distributions are also divided according to the type of access to them into public and private. Everyone has access to files in public Distributions, and to access private files, you need to create a secure URL in which you can specify the expiration date, access via IP and more. To create a private Distribution, you need to specify this section;
7. Logging - section for setting query logging parameters:
7.1 Bucket - to save logs, Amazon uses the S3 service, so if you want to use them, you will have to pay extra for S3. This parameter sets S3 Bucket, in which logs will be stored;
7.2 Prefix - log prefix, I understand that this is something like a directory for logs.

Immediately after creating the Distribution, it is in InProgress status, but you can start working with it only after it changes its status to Active. It usually takes 10–15 minutes. To check the status of Distribution, you can use the request to the API for information about Distribution.

Request URL : cloudfront.amazonaws.com/2010 –11–01 / distribution / distribution_id
Request Method : GET

distribution_id - Amazon returns in response to a successful request to create a Distribution.

Features file returns


So we got to the most important thing - sending out our files through the CDN. There are several features:
1. Amazon CloudFront does not transmit to the origin server any URL parameters that were transmitted when a file was requested from CDN.T. e. if the end user requested the file example_sub_domain.cloudfront.net/image_1.jpg?param=value , then when you request this file from the origin server you will not get “param = value”, BUT in the logs will be the full URL;

2. Updating the file in Distribution. The file may be requested in the following cases:
2.1 If it is not on the CloudFront server;
2.2 If the file is spooled. The expiration date can be controlled using the headers: cache-control, expires and pragma. By default, the file is cached for 24 hours;
2.3 If the file was deleted from Distribution using the Invalidation query;
Please note that when an end user requests a file, Amazon does not check the date of file modification as other CDNs do. If you want the file to be automatically picked up by the Amazon server, then you need to specify the version of the file or the modification date in the file name.

3. Protecting files on the origin side - if you use your own server as the origin server (that is, just the case that I describe in the article), then Amazon does not protect the files on your origin server from unauthorized access, this task lies entirely with the owner of origin. But at the same time, file protection must be implemented in such a way that Amazon itself has access to these files via HTTP or HTTPS. At the same time, Amazon, when requesting a file, does not send any data on which its requests could be distinguished from others. Based on this, there is only one option for protecting member files on your origin server - IP protection. I protect member files using .htaccess as follows:

  1. Order Deny , Allow
  2. Deny from all


so that Amazon would have access to my files, I added the following rule to .htaccess
  1. # Amazon CloudFront
  2. Allow from 216.137.60.0/ 23


I took the mask 216.137.60.0/23 right here .

Public URL


Assume that the file is available on your origin server at:

  1. origin.example.com/images/image_1.jpg


Then in order to give it through Amazon CloudFront you need to generate the following URL:

  1. example_sub_domain.cloudfront.net/images/image_1.jpg


where example_sub_domain.cloudfront.net is the domain of that Distribution, which corresponds to origin-origin.example.com.

Private URL


Private URLs are divided into two types:
1. Sanned - you can specify only the expiration date of the URL;
2. Custom - you can specify the period when the URl will be valid, as well as one or more IP addresses from which the URL can be used.

I only needed the date until which the URL would be valid, so I will give an example to generate the Canned URL:

  1. function getSignedUrl ( $ url )
  2. {
  3. // Prepare expire date
  4. $ expireDate = time ( ) + SECURE_URL_TIMEOUT ;
  5. // Read Cloudfront Private Key Pair
  6. $ fp = fopen ( CLOUD_FRONT_KEY_PAIR_PATH , "r" ) ;
  7. $ privateKey = fread ( $ fp , 8192 ) ;
  8. fclose ( $ fp ) ;
  9. // Create the private key
  10. $ privateKey = openssl_get_privatekey ( $ privateKey ) ;
  11. if ( ! $ privateKey ) {
  12. return false ;
  13. }
  14. // Prepare json policy
  15. $ json = '{"Statement": [{"Resource": "' . $ url . '", "Condition": {"DateLessThan": {"AWS: EpochTime":' . $ expireDate . '}}}]} " ;
  16. // Sign the policy with the private key
  17. if ( ! openssl_sign ( $ json , $ signature , $ privateKey , OPENSSL_ALGO_SHA1 ) ) {
  18. return false ;
  19. }
  20. // Create url safe signed policy
  21. $ signature = str_replace ( array ( '+' , '=' , '/' ) , array ( '-' , '_' , '~' ) , base64_encode ( $ signature ) ;
  22. // Construct the URL
  23. return $ url
  24. . '? Expires =' . $ expireDate
  25. . '& Signature =' . $ signature
  26. . '& Key-Pair-Id =' . CLOUD_FRONT_KEY_PAIR_ID ;
  27. }


The function uses the following parameters and constants:
1. $ url is the source URL of the form http://example_sub_domain.cloudfront.net/images/image_1.jpg;
2. SECURE_URL_TIMEOUT - timeout for the URL in seconds;
3. CLOUD_FRONT_KEY_PAIR_PATH - the path to your private key, which can be generated on the Security Credentials page in the Key Pairs tab;
4. CLOUD_FRONT_KEY_PAIR_ID - the identifier of the private key, which can be found in the same place where the key itself.

The output of this function will be a private URL, with the help of which the end user, in the allotted timeout, will be able to access the content, which is located in the private Distribution.

useful links


1. Amazon CloudFront - the main page of the service;
2. Developer Guide - a detailed description of the service CloudFront;
3. API Reference - REST API documentation for the CloudFront service;
4. AWS SDK for PHP - PHP library, which contains classes for working with all services of Amazon. A very useful thing;
5. Test code is a small code that I wrote during the testing process.

PS


That's basically all I wanted to write. The article does not claim to be a complete guide on using custom origin servers for CloudFront, but I hope it will be useful to those who do not need S3. Thank you all for your attention.

Source: https://habr.com/ru/post/115911/


All Articles