【AWS TIPS】在 CloudFront 提供私有內容 by qrtt1 | CodeData
top

【AWS TIPS】在 CloudFront 提供私有內容

分享:

CloudFront 是 AWS 提供的 Content Delivery Network(簡稱 CDN)服務,最簡易的使用方法為指定一個 S3 bucket(或多個 bucket)做為它的檔案來源。CloudFront 會替你建立一個新的 domain name,透過這個新的 domain name 取用 S3 Object 就是使用 CDN 服務,它會依最近的 CDN 服務主機提供您檔案。許多人用它來放置靜態的圖檔、網頁資料,多數的需求是放置可公開觀看的檔案。

相對於 S3 放上私有的檔案依然能簡單利用 AWS SDK,取得經過簽署(signed)的網址限制使用者可以觀看的期限,CloudFront 目前在官方 Java SDK 內並沒有直接的功能製作限制性的網址。即使在官方文件內,也是使用第三方套件 jets3 內的 CloudFrontService 類別進行網址簽署的示範。並且對於各種事情準備事項散落於文件各處,讓想要瞭解這個功能的讀者有較大的阻礙。這篇文章的誕生就是為了讓需要使用這個功能的開發者,有較為集中的概念進行施做。

準備工作

  1. 開發者一枚:您必需有 AWS 帳號,並知道如何使用 S3 與建立 CloudFront。(此文程式碼部分僅針對 Java Developer,但設定上是不分語言的)
  2. 建立一個實作練習用的 CloudFront Distribution
  3. 設定 Distribution 內的 Origins
    1. 建立並指定 Origin Access Identity
    2. 啟用 Restrict Bucket Access(設定 S3 Bucket 只能透過 CloudFront 存取)
    3. 啟用 Restrict View Access(設定 CloudFront 網址都需要簽署後使用)
  4. 產生 CloudFront KeyPair

設定 Distribution

下圖為一個新的 Distribution,它僅只用預設的設定建立一個 Download 用的 Distribution:

aws-tips-cloudfont-1

在這個例子,我們僅有一個檔案來源(origins)。您可以看到它的 Origin Access Identity 是空的。
接著我們會建立新的 Origin Access Identity 並指定給這個 Origin。

點選唯一的一個 Origin 編輯它:

aws-tips-cloudfont-2

勾選 Restrict Bucket Access 會出現更多選項:

  1. 勾選 Create a New Identity(官方手冊建立,只要有一組 Origin Identity 共用即可。)
  2. 勾選 Update Bucket Policy(這個選項會更新 S3 Bucket 增加對於 Origin Identity 的授權,無論你的 S3 Object 權限為何,只要是 Origin Identity 都能存取)

aws-tips-cloudfont-3

當完成上述動作後,可以至 S3 Bucket 上看看是否多出 Policy 設定:

aws-tips-cloudfont-4

Restrict View Access

在 Distribution 設定內,還有 Behavior 需要變更 View Access,建議這個步驟會讓整個 Distribution 都必需要經過網址簽署的動作才能使用。

aws-tips-cloudfont-5

CloudFront KeyPair

要建立 CloudFront 專用的 KeyPair 手冊上給的是一個網址,它在實際的 AWS Console 合併在帳號選單內。點選 Security Credentials

aws-tips-cloudfont-6

進入 Security Credentials 設定頁後,展開 CloudFront Key Pairs,按下 Create New Key Pair 產生新的 Key Pair:
aws-tips-cloudfont-7

接著下載 Private Key File:
aws-tips-cloudfont-8

驗證設定

完成上述的設定後,我們可以做一些簡單的測試來驗證上述的設定都確實到位:

  1. 上傳一個文件至 origin 的 S3 Bucket,設成 public read。結果是無法直接以 S3 URL 觀看。(真的嗎!?)
  2. 使用 CloudFront URL 無法直接觀看
    aws-tips-cloudfont-9
  3. 利用 openssl 指令製作簽署過的 URL

Signed URL

Signed URL 的製作步驟為:

  1. 撰寫 policy(也就是這個 URL 存取的限制條件)
  2. 對 policy 內容進行簽署的動作
  3. 產生 Signed URL

policy

你可以在官方手冊找到 policy 的寫法,基本上它是個 JSON 格式(不能有空白與換行符號):

{"Statement":[{"Resource":"http://d1dppt1amzlzf7.cloudfront.net/sample.png","Condition":{"DateLessThan":{"AWS:EpochTime":1376289018}}}]}

將上述內容存檔於 my.policy.json 透過 openssl 指令與先前下載的 private key 進行簽署(參考官方手冊而來):

qty:Downloads qrtt1$ cat my.policy.json | openssl sha1 -sign pk-APKAIRCVNJDOPXY4KBLQ.pem | openssl base64 | tr '+=/' '-_~'
dJ6~BDRpPMicu9ZefcGJZv0FQna2yG7Sr5-8~q3ebu3jFS9dNrOhiXOh5NYCkFCu
Qrfuc1Crw6Jcftr0BcHcRP0HJtuzYbGyBSxWNtgKjyCh3-aLCjmjo-FOgiwHeH1M
7r1elKe8kltlNM6vRqa7-Ueo2b2v9B7nJTPZzQojzNW-EmFqSKN7Lp5Xe8JIt7I6
hyVQeWsUTL8TKVut~VaJecRirYcbmiRc2jU3bAOCXWjNF0shFOByDrOzhwOMgxN4
NlKXTk3xC4vjH-lX-TDlqVc0IBe8PtttAIwuq-0MozJCHLQYXlFI5wgd5fk0dajT
QVwaqtxI31R5m-IAoO6X0w__

產生 Signed URL

Signed URL 即為原始的 URL 加上 3 個參數:

  1. Expires 即為在 policy 內填的 AWS:EpochTime
  2. Signature 即為透過上一步指令產生的「簽章」(需合併成一行)
  3. Key-Pair-Id 即為產生 CloudFront Key Pair 時由系統指定的 ID(會它透過此 ID 知道你是用哪一組 Key 簽署的,才能進行簽章驗證)

所以新的 URL 為下列區段的結合:

http://d1dppt1amzlzf7.cloudfront.net/sample.png
?Expires=1376289018
&Signature=dJ6~BDRpPMicu9ZefcGJZv0FQna2yG7Sr5-8~q3ebu3jFS9dNrOhiXOh5NYCkFCuQrfuc1Crw6Jcftr0BcHcRP0HJtuzYbGyBSxWNtgKjyCh3-aLCjmjo-FOgiwHeH1M7r1elKe8kltlNM6vRqa7-Ueo2b2v9B7nJTPZzQojzNW-EmFqSKN7Lp5Xe8JIt7I6hyVQeWsUTL8TKVut~VaJecRirYcbmiRc2jU3bAOCXWjNF0shFOByDrOzhwOMgxN4NlKXTk3xC4vjH-lX-TDlqVc0IBe8PtttAIwuq-0MozJCHLQYXlFI5wgd5fk0dajTQVwaqtxI31R5m-IAoO6X0w__
&Key-Pair-Id=APKAIRCVNJDOPXY4KBLQ

Access Denied

aws-tips-cloudfont-10

當您自行使用 openssl 指令產生 signature 並組合 signed url 後,試著開啟網頁有可能遇到 Access Denied 的情況。

以我實驗時遇到的狀況來說可能是:

  1. 準備工作沒有確實完成:需重新檢查各環節該設的內容是否都做了,像是 S3 Policy 是否更新並接受 Origin Identity 存取。
  2. Signed URL 參數 typo 或誤植。例如:填錯 distribution 的 domain。
  3. 限制條件與您的環境不符合:我曾在 2013/7/30 試著執行 2013/7/14 到期的 signed url,這顯然條件不符合要求無法使用。
  4. policy 未剔除空白與換行字元

在此我們特別說明以 openssl 指令製作 signature 時會遇到的情況,首先來看看手冊:

aws-tips-cloudfont-11

您可能覺得疑惑,不就是上一段的做法一模一樣的內容嗎?不過容易出錯的細節為成 policy file 建立。
若是在 linux 下,我們可以很容易地利用 cat 指令建立檔案(利用 > 導向,寫完內容後換行並按下 Ctrl+D 送出 EOF):

cat > my.policy.json
{"Statement":[{"Resource":"http://d1dppt1amzlzf7.cloudfront.net/sample.png","Condition":{"DateLessThan":{"AWS:EpochTime":1375366198}}}]}

接著你用這個 my.policy.json 產生 signature 卻遇上了 Access Denied。這是因為最後按下的 Enter 產生一個換行符號。
利用 od 指令可以看出最後一個字元為 nl:

qty:CloudFrontLab qrtt1$ od -a my.policy.json 
0000000    {   "   S   t   a   t   e   m   e   n   t   "   :   [   {   "
0000020    R   e   s   o   u   r   c   e   "   :   "   h   t   t   p   :
0000040    /   /   d   1   d   p   p   t   1   a   m   z   l   z   f   7
0000060    .   c   l   o   u   d   f   r   o   n   t   .   n   e   t   /
0000100    s   a   m   p   l   e   .   p   n   g   "   ,   "   C   o   n
0000120    d   i   t   i   o   n   "   :   {   "   D   a   t   e   L   e
0000140    s   s   T   h   a   n   "   :   {   "   A   W   S   :   E   p
0000160    o   c   h   T   i   m   e   "   :   1   3   7   5   3   6   6
0000200    1   9   8   }   }   }   ]   }  nl                            
0000211

未剔除換行符號前的 signature 為:

qty:CloudFrontLab qrtt1$ cat my.policy.json | openssl sha1 -sign pk-APKAIRCVNJDOPXY4KBLQ.pem | openssl base64 | tr '+=/' '-_~'
iVbxvAEtpsBpxwGsQ0zSriVbItQrBB7NR3D24PsmF1ML1YwbPOIxuMNqOtASBzxx
CIBDlZl8C7T42wUy3ZSF-mBIBipNJuVivUi8~WM3OgLn3B3jAkiHoY3f0lyTQ12w
z14esXP46mAYnBuOrwcYuV8ysv026JyKMsRI1g5Y1cb2qMa-E6j-k7VyJSKR7fjo
L5Qn1jO3vvXbOEPIBgXM7ltaODeJC-vMrBioq7crCvlQJSwiehauXVxmFBcW~6P~
4yOhA1OU4RASPhASMPV16F865iRGI2S452DBuw99OPTkvOINIKZoNKIsa~iwsMn8
BLGd1vm8Es~lZ0cvNBcP3A__

現在我們去掉換行符號(請用您覺得最適當的工具即可):

原始檔案長度為 137:

qty:CloudFrontLab qrtt1$ ls -l|grep my.policy.json
-rw-r--r--  1 qrtt1  staff   137  8  1 22:48 my.policy.json

只有前 136 個字元是需要的,利用 dd 建立一個沒有換行符號的 policy:

qty:CloudFrontLab qrtt1$ dd if=my.policy.json of=my.policy.without.nl.json bs=136 count=1
1+0 records in
1+0 records out
136 bytes transferred in 0.000032 secs (4225373 bytes/sec)
qty:CloudFrontLab qrtt1$ ls -l|grep json
-rw-r--r--  1 qrtt1  staff   137  8  1 22:48 my.policy.json
-rw-r--r--  1 qrtt1  staff   136  8  1 22:52 my.policy.without.nl.json

正確的 signature 應為:

qty:CloudFrontLab qrtt1$ cat my.policy.without.nl.json | openssl sha1 -sign pk-APKAIRCVNJDOPXY4KBLQ.pem | openssl base64 | tr '+=/' '-_~'
KHCYk411NJg5w8XBuz4h8v-euf6jB11-Kh9PezAR7xaD7SywrEgoYPA~Tg5crtrg
xbJk7tf61mTY3funSBI5ZHYX1r6FxTCiCGtVblJVukh2ajqEF8jGcQuP876V5bOF
qLATcXYAznck2gK92RZomW1AukjZwD-N4L7JehGkDwvtyqUfPlaGAZJLOChgQ3Oi
8YcTm6OiOYxTxD0Y-AxVh9e1YtjRcw58cIASfQceosvHQLea2ic1c8o4MIVCqyj3
1OYE4W-hmZVT9kHUQX71tr2iEh49uOOJpdtoa7ataH0NLVfuft5J~gnllmHw1IV0
Kj8lKH-lMI1D1LVtUWWoJg__

產生 Signed URL by Java

官方手冊推薦的方式是使用 jets3 內的 CloudFrontService,依 手冊的範例 能簡單正確地產生出 Signed URL。

大致上的作法是:

  1. 將 CloudFront KeyPair 的 private key 用 openssl 進行格式轉換(這是為了配合 jets3 使用的格式)
  2. 依範例填入適當的參數
    1. domain
    2. keyId
    3. privte key data
    4. expire time in seconds

我們另外實作一組單純對 download distribution 做 signature 的工具 CloudFrontSignTool
除了陽春了點,它用的法大致如同使用 jets3 一點,並且我們的實作使用原先的 private key 格式:

String domain = "[your_cloudfront_domain]";
String keyId = "[the id of your cloudfront keypair]";

/* private key file which downloaded from aws console without any format convertion */
String keyPath = "[the private key of your cloudfront keypair]";

DistributionConfiguration configuration = new DistributionConfiguration(keyId, keyPath, domain);
CloudFrontSignedUrlGenerator generator = new CloudFrontSignedUrlGenerator();

/* make the signed url */
String signedUrl = generator.signCannedPolicy(configuration, "some.resource.mp4", 60 * 1000);

CloudFrontSignTool 目前公開放在 github 上,若有任何需要 fork 擴充或直接使用。

補充 2013/08/13:

在官方手冊內的 Java Sample 仍提到需要先用 openssl 工具做 PEM to DER 的轉換。jets3 現在已經有提供 EncryptionUtil.convertRsaPemToDer(java.io.InputStream is) 讓我們轉換格式了。

後續 >> 【AWS TIPS】帳單的管理

分享:
按讚!加入 CodeData Facebook 粉絲群

留言

留言請先。還沒帳號註冊也可以使用FacebookGoogle+登錄留言

關於作者

目前在一家網路應用軟體公司擔任開發工作,對多媒體處理與雲端應用充滿興趣,工作之餘亦常整理開發經驗分享於網路或於社群活動時進行分享。

熱門論壇文章

熱門技術文章