【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 類別進行網址簽署的示範。並且對於各種事情準備事項散落於文件各處,讓想要瞭解這個功能的讀者有較大的阻礙。這篇文章的誕生就是為了讓需要使用這個功能的開發者,有較為集中的概念進行施做。 準備工作
設定 Distribution下圖為一個新的 Distribution,它僅只用預設的設定建立一個 Download 用的 Distribution: 在這個例子,我們僅有一個檔案來源(origins)。您可以看到它的 Origin Access Identity 是空的。 點選唯一的一個 Origin 編輯它: 勾選 Restrict Bucket Access 會出現更多選項:
當完成上述動作後,可以至 S3 Bucket 上看看是否多出 Policy 設定: Restrict View Access在 Distribution 設定內,還有 Behavior 需要變更 View Access,建議這個步驟會讓整個 Distribution 都必需要經過網址簽署的動作才能使用。 CloudFront KeyPair要建立 CloudFront 專用的 KeyPair 手冊上給的是一個網址,它在實際的 AWS Console 合併在帳號選單內。點選 Security Credentials 進入 Security Credentials 設定頁後,展開 CloudFront Key Pairs,按下 Create New Key Pair 產生新的 Key Pair: 驗證設定完成上述的設定後,我們可以做一些簡單的測試來驗證上述的設定都確實到位:
Signed URLSigned 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 URLSigned URL 即為原始的 URL 加上 3 個參數:
所以新的 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當您自行使用 openssl 指令產生 signature 並組合 signed url 後,試著開啟網頁有可能遇到 Access Denied 的情況。 以我實驗時遇到的狀況來說可能是:
在此我們特別說明以 openssl 指令製作 signature 時會遇到的情況,首先來看看手冊: 您可能覺得疑惑,不就是上一段的做法一模一樣的內容嗎?不過容易出錯的細節為成 policy file 建立。 cat > my.policy.json {"Statement":[{"Resource":"http://d1dppt1amzlzf7.cloudfront.net/sample.png","Condition":{"DateLessThan":{"AWS:EpochTime":1375366198}}}]} 接著你用這個 my.policy.json 產生 signature 卻遇上了 Access Denied。這是因為最後按下的 Enter 產生一個換行符號。 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。 大致上的作法是:
我們另外實作一組單純對 download distribution 做 signature 的工具 CloudFrontSignTool。 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) 讓我們轉換格式了。 |