s3

AWS-SDK-Goを使って、ユーザーが投稿したファイルをS3から削除してみる

はじめに S3でユーザーが投稿した画像を管理している場合、ユーザーがアプリを退会した際にユーザーに関する情報、S3からもユーザーのファイルを削除する必要性があります。 S3上のプレフィックスは userID/ となっており、全体のキーはbucket/userID/fileName.jpg となっています。   問題点 AWSの制約上、bucket/userID以下にファイルが1つでもあると、 いきなりbucket/userIDを削除することはできません。 加えて、リストを取得するListObjectsでは1度に1000個のキーか取得することができず、 オブジェクトの削除を行うDeleteObjectも1度に1000個のキーまでしか指定できません。 もしユーザーが1000個以上のファイルをアップロードしている場合、 一筋縄ではいかないため工夫する必要性があります。   コード   解説 ユーザーが投稿したファイルを全て取得する [crayon-5b4efebc7abed081727378/] 最初のリクエストをする際に、Delimiter: aws.String(userID + "/"),をつけてリクエストをすることで、レスポンスにIsTruncatedが含まれています。 IsTruncatedがtrueだと全てを取得できず、まだ残りのオブジェクトがある状態 falseだと全てのオブジェクトを取得できた意味をします   [crayon-5b4efebc7abfc650664418/] 最初のリクエストでIsTruncatedがtrueの場合、 NextMarkerがレスポンスに含まれているのでこれを次のリクエストに含めておきます。 ループの中でこれらを繰り返しておき、 IsTruncatedがfalseになった時点で終了。この関数では[]stringを返します。   オブジェクトを削除する [crayon-5b4efebc7ac00770244772/] 関数全体↑   [crayon-5b4efebc7ac06464543061/] getAllObjectで取得したキーが入った配列の長さが1000以上だった場合、 先程述べた様に、1度のリクエストで削除できるキーが最大1000のため配列を分割する必要性があります。   [crayon-5b4efebc7ac09567350148/] func chunk()では1000要素以上の配列に入ったキーから1つの配列が1000個未満のキーとなるように複数の配列に分割して[][]stringを返します。   [crayon-5b4efebc7ac0c431815965/] この分割された[][]stringをfor-rangeでループさせ、画像を削除していきます。   [crayon-5b4efebc7ac0f990530124/] bucket/userID 以下のファイルが0になったら、bucket/userIDを削除します   使い方 ちなみに画像数が5,000枚レベルになると、マシンのスペックにもよりますがAPIサーバーからアプリへレスポンスを返すまでに1分程度、かかってしまいとても使えたものではありません。 なので、今回はDBからユーザーの情報を削除する部分はGoroutineで制御し、 このS3から画像を削除する部分はレスポンスを返した後に実行されるようにしてみました。 [crayon-5b4efebc7ac11572762518/] DBからユーザー情報を削除する部分はerrgroup(goroutine)で制御を行って、 エラー処理ができるように。全ての処理が終わったあとに、   [crayon-5b4efebc7ac14760639263/] WaitGroupをインクリメントしておき、wg.Wait()をせずにそのままreturn nilでレスポンスを返します。 ただ、これを行うとS3から画像を削除する部分でエラーを起こるとユーザーへ通知できなくなってしまうので、Slackへ通知するようにしてます。 (そして手作業で該当ユーザーのファイルを削除していく…)   おわり Go言語に自信が全く無い素人のコードを晒して大変恐縮していますが もし間違いや、こうした方がいいのご指摘がありましたらぜひコメントしていただけると本当に幸いです。

S3をCloudFrontから配信&LambdaとWAFを組み合わせて利便性とセキュリティを確保してみる

この記事は、AWS Lambda Advent Calendar 2017 の5日目となります。 今回は S3 にある画像ファイルを CloudFront から配信をしつつ、 CloudFront のアクセスログを Lambda で解析を行って、WAF の IPリストへ追加を行って、セキュリティの担保を行ってみようと思います。   S3 を CloudFront から配信する理由 前提条件に、弊社の S3 バケットにはユーザー個人が撮影した画像がアップロードされます。 なのでセキュリティには気を置いてしっかり運用していく必要性があります。 AWS SDK を使用すれば S3 から署名付きURLが吐き出され、これにアクセスすれば画像を取得できます。が、この署名付きURLには有効期限が設定されており、アプリから使うにはちょっと面倒です。 S3 を CloudFront から配信をすると、WAF が使えるようになるので固定URLで配信しつつ、セキュリティも担保できます。   構成 ①ユーザーが CloudFront にアクセスを行う ②リクエストヘッダー等を WAF へ送る ③WAF にあるルールと照合し、結果を返す ④③がOKなら S3 から画像を持ってくる(NG なら 403 を返す) ⑤アクセスログを S3 へ保存する ⑥⑤のタイミングで、Lambda が発火する ⑦アクセスログに HTTPステータスコードが 200 以外のものがあったら、IPを WAF の IPリストに追加する Lambda 関連のアドベントカレンダーなのに、Lambda の部分を特筆するところがねぇ…   CloudFront ディストリビューションの作成 オリジンに、対象の S3 バケットを。 ログの設定も何となく分かると思います。 S3 は CloudFront からのみアクセスできるようにしておきます。 ブルートフォースアタック等で、キー(/user_id/hoge_id/file_name_.jpg みたいな)がバレないように、こうします。CloudFront + WAF を組み合わせればキーがバレる前にブラックリストに入れて、アクセスを拒否できます。   WAF web ACL の作成 AWS resource to associate には先程作成した CloudFront のディストリビューションを設定します。 ルールの作成部分では IP address で作成する。 文字列判定を使えば特定の文字列をヘッダーに載せるとアクセスできるなんて仕組みもできます。   こんな感じです。 最初に IPアドレス判定を行ってから、文字列判定のルールでヘッダー認証をします。順番が大事です。今回はなんちゃってヘッダー認証があるので、デフォルトアクションは全て 403 を返すようにしています。   Lambda スクリプトを作る 言語は好きなものを選んでください。 今回僕が書いて運用しているコードを貼っておきます。 IP_SET_ID には WAF -> IP addresses の自分で作成した空のルールにアクセスすると、URLにある /ipsets/<ここ> を指定します。…

Ansibleのs3モジュールを使ってディレクトリごとアップロードする

梅雨の季節がやって参りました。 Ansibleのs3モジュールを使ってディレクトリごとアップロードする方法を覚え書きとして。   認証情報を環境変数に入れておく Ansibleで使うAWSの認証情報はPlaybookに書き込むか 環境変数を使うかで選べます。 万が一のことを考えて環境変数で管理したほうが良いと思います。 [crayon-5b4efebc7b29f112788386/]   ファイルのアップロード [crayon-5b4efebc7b2b7859191249/]   ディレクトリごとアップロード(Ansible 2.3以降) [crayon-5b4efebc7b2ba935378532/] key_prefixがないとバケット直下にファイルが展開されちゃうので key_prefixを設定しておきます。 こうすると BUCKET_NAME/source/<sourcecodeの中身> ってなる。   boto required for this module Ansibleを実行するホストでAWSの操作をする場合、 botoとかいうPythonのモジュールをインストールします。 加えてdelegate_to: 127.0.0.1をタスクに追加しないとリモート先で実行されてしまって エラーが消えないのでご注意を。 逆にリモート先で行うならリモート先で実行します。 [crayon-5b4efebc7b2bd643299442/]   それでも解決できない場合 引数にansible_python_interpreterをつけて実行する ansible-playbook -i production/hosts api.yml --extra-vars "ansible_python_interpreter=`(which python)" --user root --ask-pass hostsファイルにansible_python_interpreterをつける [crayon-5b4efebc7b2bf838809399/]