miniconda環境にlocalstack専用のenvironmentを作成する

localstack の実行環境を作成するにあたり、既存のPythonの環境とは分離しておきたいので、miniconda で localstack 専用の environment を作成して、ここにインストールした記録。

environment の作成

$ conda create -n localstack
Fetching package metadata .........
Solving package specifications: 
Package plan for installation in environment /Users/<username>/miniconda3/envs/localstack:

Proceed ([y]/n)? y

environment を activate

$ . activate localstack
(localstack) $

localstack をインストール。

(localstack) $ pip install localstack

localstack を起動

(localstack) $ localstack start

筆者の環境では以下のようなエラーが出て起動に失敗する状態だったため、つぎ示すようなパッチを当てた。

(localstack) $ localstack start
Starting local dev environment. CTRL-C to quit.
Error starting infrastructure: 'zipimport.zipimporter' object has no attribute 'path'
Traceback (most recent call last):
  File "/Users/<username>/miniconda3/envs/localstack/bin/localstack", line 79, in <module>
    infra.start_infra()
  File "/Users/<username>/miniconda3/envs/localstack/lib/python3.6/site-packages/localstack/services/infra.py", line 362, in start_infra
    raise e
  File "/Users/<username>/miniconda3/envs/localstack/lib/python3.6/site-packages/localstack/services/infra.py", line 308, in start_infra
    load_plugins()
  File "/Users/<username>/miniconda3/envs/localstack/lib/python3.6/site-packages/localstack/services/infra.py", line 102, in load_plugins
    file_path = '%s/%s/plugins.py' % (module[0].path, module[1])
AttributeError: 'zipimport.zipimporter' object has no attribute 'path'

object has no attribute 'path' の対策:

--- envs/localstack/lib/python3.6/site-packages/localstack/services/infra.py    2017-07-20 19:57:18.000000000 +0900
+++ envs/localstack/lib/python3.6/site-packages/localstack/services/infra.py    2017-07-20 20:22:30.000000000 +0900
@@ -96,7 +96,8 @@
         if six.PY3 and not isinstance(module, tuple):
             file_path = '%s/%s/plugins.py' % (module.module_finder.path, module.name)
         elif six.PY3 or isinstance(module[0], pkgutil.ImpImporter):
-            file_path = '%s/%s/plugins.py' % (module[0].path, module[1])
+            if hasattr(module[0], 'path'):
+                file_path = '%s/%s/plugins.py' % (module[0].path, module[1])
         if file_path and file_path not in loaded_files:
             load_plugin_from_path(file_path)
             loaded_files.append(file_path)

最初は jar ファイルのダウンロードが行われるため、立ち上がるまでかなり時間がかかるがじっと待つ。

(localstack) $ localstack start
Starting local dev environment. CTRL-C to quit.
Starting mock ES service (http port 4578)...
Starting local Elasticsearch (http port 4571)...
Starting mock S3 (http port 4572)...
Starting mock SNS (http port 4575)...
Starting mock SQS (http port 4576)...
Starting mock SES (http port 4579)...
Starting mock API Gateway (http port 4567)...
Starting mock DynamoDB (http port 4569)...
Starting mock DynamoDB Streams service (http port 4570)...
Starting mock Firehose service (http port 4573)...
Starting mock Lambda service (http port 4574)...
Starting mock Kinesis (http port 4568)...
Starting mock Redshift (http port 4577)...
Starting mock Route53 (http port 4580)...
Starting mock CloudFormation (http port 4581)...
Starting mock CloudWatch (http port 4582)...
WARNING:infra.py:Service "elasticsearch" not yet available, retrying...
WARNING:infra.py:Service "elasticsearch" not yet available, retrying...
WARNING:infra.py:Service "elasticsearch" not yet available, retrying...
Ready.

初期状態では、DynamoDB は -inMemory オプションがついた状態で起動され、localstack を停止するとデータも消滅する。
データを保存するためには、環境変数 DATA_DIR をセットしておく。
この環境変数をセットすると Kinesis と Elasticsearch のデータ保存場所もこのディレクトリの中に変更される。

conda 環境では、activate および deactivate を実行したときに、environment の etc/conda/activate.d/*.sh あるいは etc/conda/deactivate.d/*.sh が自動的に source されるので、自動的にこの環境変数をセットするようにフックを作成する。

ここでは、environment の data を DATA_DIR に指定している。

(localstack) $ cd $CONDA_PREFIX
(localstack) $ mkdir data
(localstack) $ mkdir -p etc/conda/activate.d
(localstack) $ mkdir -p etc/conda/deactivate.d
(localstack) $ echo 'export DATA_DIR=$CONDA_PREFIX/data' >etc/conda/activate.d/env_vars.sh
(localstack) $ echo 'unset DATA_DIR' >etc/conda/deactivate.d/env_vars.sh

いったん deactivate して、再度 activate すれば、環境変数 DATA_DIR がセットされているはずである。

(localstack) $ . deactivate
$ . activate localstack
(localstack) $ echo $DATA_DIR
/Users/<username>/miniconda3/envs/localstack/data

この状態で localstack を起動すれば、DynamoDB のデータも localstack の停止後も消えずに残るようになる。

続きを読む

AWSからS3のセキュリティについて警告メールが来た時の対処方法

AWSからS3の警告メールが来たら

S3バケットのポリシーに関するTIPSです。
SORACOMの松井さんに教えていただいて大変助かったので情報共有として記事にします。

いきなりメールが来る

AWSからある日こんなメールが届きました。どうも特定の条件の人に一斉にメール送ってるようです。

Subject: Securing Amazon S3 Buckets [AWS Account: ******]

Hello,

We’re writing to remind you that one or more of your Amazon S3 bucket access control lists (ACLs) are currently configured to allow access from any user on the Internet. The list of buckets with this configuration is below.

By default, S3 bucket ACLs allow only the account owner to read contents from the bucket; however, these ACLs can be configured to permit world access. While there are reasons to configure buckets with world read access, including public websites or publicly downloadable content, recently, there have been public disclosures by third parties of S3 bucket contents that were inadvertently configured to allow world read access but were not intended to be publicly available.

We encourage you to promptly review your S3 buckets and their contents to ensure that you are not inadvertently making objects available to users that you don’t intend. Bucket ACLs can be reviewed in the AWS Management Console (http://console.aws.amazon.com ), or using the AWS CLI tools. ACLs permitting access to either “All Users” or “Any Authenticated AWS User” (which includes any AWS account) are effectively granting world access to the related content.

For more information on configuring your bucket ACLs, please visit: https://docs.aws.amazon.com/AmazonS3/latest/dev/S3_ACLs_UsingACLs.html

For additional assistance reviewing your bucket ACLs, please visit http://aws.amazon.com/support to create a case with AWS Developer Support.

Your list of buckets configured to allow access from anyone on the Internet are:

バケットの名前

要約すると「おめー、S3のバケットを世界中から見られるように設定してるけどまずいんじゃね?ちゃんと制限しろよ」ということのようです。

とはいえ

指摘されたバケットはいずれもstatic web hostingで使っているバケットです。その性質上、世界中から見えないとあまり意味がないです。
公式ドキュメントでも以下のように「該当バケット以下のすべてのオブジェクトに対するGetObjectメソッドを、全てのアクセスに対して許可しろ」と書いてあります。

http://docs.aws.amazon.com/ja_jp/AmazonS3/latest/dev/WebsiteAccessPermissionsReqd.html

これは困った・・。

そこに救いの手が

Facebookで相談したところ、元・中の人の松井さんが教えてくれました。

Bucket policy でアクセス元IPアドレスを 0.0.0.0/1 と 128.0.0.0/1 からのみ許可するというライフハック

これはどういうことかというと

  • 0.0.0.0/0を開放するとこれは全開放になっちゃうのでこれまでと変わらないから多分また警告が来る
  • ネットマスクを最小値の1として0.0.0.0/1とするとこれは0.0.0.1~127.255.255.255.254のレンジになる
  • 追加で128.0.0.0/1を許可すると、これが128.0.0.1~255.255.255.254になる
  • 結局、2つ合わせると0.0.0.0/0とするのとほぼ等価になる(厳密にいうとブロードキャストアドレスの分があるけど、そんなアドレスからのアクセスはないだろう)

ということになります。ネットマスクの計算については割愛します。

というわけで

以下のように書き換えたら無事終了。

{
    "Version": "2008-10-17",
    "Statement": [
        {
            "Sid": "PublicReadForGetBucketObjects",
            "Effect": "Allow",
            "Principal": {
                "AWS": "*"
            },
            "Action": "s3:GetObject",
            "Resource": "バケットのARN/*",
            "Condition": {
                "IpAddress": {
                    "aws:SourceIp": [
                        "0.0.0.0/1",
                        "128.0.0.0/1"
                    ]
                }
            }
        }
    ]
}

将来的なことを考えるとv6アドレスも足すべきでしょうけどとりあえずはこれでよさそうです。

しかし、そもそもstatic web hostingがONになってる人は対象から外すとかしてくれればいいんですけどねぇ・・。

あと、これで本当に警告が来なくなるのかは未検証です(しばらく待ってみないとわからないので)のでご了承ください。
また届いたら記事をアップデートします。

続きを読む

AWS LambdaでImageMagickによる画像への文字埋め込みで好きなフォントを使う方法

LambdaでImageMagickを用いて好きなフォントを使う方法

方法は単純。
TrueTypeFontファイルへのパスをfontに渡してあげるだけ。
なのでデプロイパッケージに好きなフォントのttfファイルを含めてあげればできる。
下にサンプルソース。
(imagemagickのコマンドを使用する、こっちのほうが個人的には楽だった)

const exec = require('child_process').exec;
const aws = require('aws-sdk');
const s3 = new aws.S3({region: "ap-northeast-1"});
const fs = require ('fs');
const uuid = require('uuid');

exports.handler = (event, context, callback) => {
  const dir = `/tmp/${uuid.v4()}`; // 書き込み可能なのは/tmp以下だけ、かつ名前重複でエラー起きるので重複できないようにする。
  const options = [
    "-size 300x300", // 画像のサイズ。変更元をxc:whiteにしているため必要
    "-font ipam.ttf", // TrueTypeFontファイルへのパス。好きなフォントのTTFを使えばよろし。
    "-pointsize 24", // 文字サイズ
    '-fill "#000000"', // 文字色
    "-gravity center", // 文字の基準位置
    '-annotate +0+0 "テスト"' // 文字。基準位置(もしくは左上)からの位置を調整できる。
  ];
  exec(`mkdir ${dir} && convert ${options.join(" ")} xc:white ${dir}/image.png`, (err, stdout, stderr) => {
    if (err) {
      callback(stderr.split("n"));
      return;
    }
    s3.putObject({
      Bucket: "",
      Key: "",
      Body: fs.createReadStream(`${dir}/image.png`),
      ContentEncoded: 'base64',
      ContentType: 'image/png'
    }, (ex) => {
      if (ex) {
        callback(ex);
        return;
      }
      callback(null, "success");
    });
  });
};

続きを読む

AWS Lambda と Fastly instant purge を利用して S3 コンテンツの更新を即時反映する

Fastly CDN の特徴のひとつに、Instant purge(コンテンツの即時削除)があります。通常、CDN では更新の多いコンテンツはキャッシュすることができませんが、Fastly では Purge を即座に行うことが可能であるため、更新の多いコンテンツでもキャッシュすることが可能です。
ただし、コンテンツが変更されるたびに Instant purge を手動で行うのは理想的な運用とはいえません。ここでは AWS Lambda を利用して、S3 でのファイル更新を検知して、自動的に Instant purge を実行する方法を紹介します。

Lambda の設定

  1. Services から Lambda を選択し、Create a Lambda function をクリックします
  2. blueprint が表示されますが、ここでは Blank Function を選択します
  3. トリガーの設定画面が表示されるので、S3 を選択し、Bucket にコンテンツが配置される S3 バケットを選択し、Event type を PUT とします
    Screen Shot 0029-07-18 at 21.06.54.png
  4. Name / Description を入力し、Runtime はデフォルトの Node.js を選択します。
    Screen Shot 0029-07-18 at 21.11.26.png
  5. Code は下記のように設定します。hostname を Instant purge を行うドメインに変更してください。
    下記のコードでは、アップデートされたパスを key に格納し、options にドメイン、ポート番号、method(purge のため、PURGE) と共に設定し、http.request で Instant purge のリクエストを送信しています。
console.log('Loading event');
var aws = require('aws-sdk');
var s3 = new aws.S3({apiVersion: '2006-03-01'});
var http = require('http');

exports.handler = function(event, context) {
   console.log('Received event:');
   console.log(JSON.stringify(event, null, '  '));
   // Get the object from the event and show its content type
   const bucket = event.Records[0].s3.bucket.name;
   const key = event.Records[0].s3.object.key;
   s3.getObject({Bucket:bucket, Key:key},
      function(err,data) {
        if (err) {
           console.log('error getting object ' + key + ' from bucket ' + bucket + 
               '. Make sure they exist and your bucket is in the same region as this function.');
           context.done('error','error getting file'+err);
        }
        else {
           console.log('CONTENT TYPE:' + data.ContentType + ' bucket: ' + bucket + ' path: ' + key);
           context.done(null,'');
        }
      }
   );

  var options = {
    hostname: 'www.example.com',
    port: 80,
    path: '/' + key,
    method: 'PURGE',
  };

  callback = function(response) {
  var str = '';

    //another chunk of data has been recieved, so append it to `str`
    response.on('data', function (chunk) {
      str += chunk;
    });

    //the whole response has been recieved, so we just print it out here
    response.on('end', function () {
      console.log(options.hostname + ':' + options.port + options.path + ' method:' + options.method);
      console.log(str);
    });
  }

  http.request(options, callback).end();
};

6 . Role は Choose an existing role を選択し、Existing role を選択します。
Screen Shot 0029-07-18 at 21.14.43.png

以上で設定は完了です。

動作確認

ここでは index.html ファイルを更新し、パージによりキャッシュ MISS となることを確認します。
1. curl コマンドでキャッシュヒットになることを確認します。

$ curl -svo /dev/null http://www.example.com/lambda
...
< Age: 3
...
< X-Cache: MISS, HIT
< X-Cache-Hits: 0, 1

2 . s3 上の lambda ファイルを更新します
3. curl コマンドでキャッシュミスになることを確認します。

$ curl -svo /dev/null http://www.example.com/lambda
...
< Age: 0
...
< X-Cache: MISS, MISS
< X-Cache-Hits: 0, 0

4 . CloudWatch のログでも下記のように PURGE リクエストが送信されていることが確認できます。
Screen_Shot_0029-07-18_at_22_40_08.png

続きを読む

TerraformでAWSの構成を読み取るぞ(Terraform import)

前書き

Terraform import については、Qiitaでお2人も記載してくれていますので、参照ください。

tyasuさん:terraform importの使い方メモ
kt_higaさん:terraform importを試してみた

同じスタンスで記載してもしょうがないので、勉強している最中に違う観点でいくつかTerraform import について記載します。

環境

Windows にTerraform(2017/7/18時点:v0.9.11)をインストール
AWSには、EC2,ELB,RDS,S3 など以前手作業で作った環境があります。

準備

まずは、最低限Terraformの利用には、access_key, secret_keyが必要です。
こちらを参照してキーを確認しましょう。
Windowsの作業フォルダにファイルを作成して、確認したキーを記載します。
※ ファイル名は、<任意の名前>.tf としてください。
ここでは、c:work フォルダを作業フォルダとします。

provider "aws" {                                            ・・・・決め打ちです。
    region      = "ap-northeast-1"                          ・・・・リージョンを記載(ap-notheast-1:日本リージョン)
    access_key  = "XXXXXXXXXXXXXXXXX"                       ・・・・access_keyを登録
    secret_key  = "YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY" ・・・・secret_keyを登録
}

※ ほとんどが決め打ちです。
   今回は、AWS環境のため"aws" となります。

インポート!

叩くコマンドは、簡単!

AWSのマネジメントコンソールから、インスタンスID(赤枠部分)を確認して、コマンドのオプションに指定します。
C:> cd work
C:work> terraform import aws_instance.<任意の名前> <インスタンスID>

題.png

ここでは、こんな感じでコマンドを叩いてみました。
もともとの環境では、タグ名を「batchserver01」という名前を付けていましたが、ここではわざと「batch01」に変更しています。
タグ名と合わせても勿論OKです。

import02.png

無事、terraform.tfstate ファイルが作成されました。

※ <> 部分は、修正しています。

{
    "version": 3,
    "terraform_version": "0.9.11",
    "serial": 0,
    "lineage": "XXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX",
    "modules": [
        {
            "path": [
                "root"
            ],
            "outputs": {},
            "resources": {
                "aws_instance.batch01": {
                    "type": "aws_instance",
                    "depends_on": [],
                    "primary": {
                        "id": "<INSTANCE_ID>",
                        "attributes": {
                            "ami": "<AMI_ID>",
                            "associate_public_ip_address": "false",
                            "availability_zone": "<AvailabilityZone_ID>",
                            "disable_api_termination": "false",
                            "ebs_block_device.#": "0",
                            "ebs_optimized": "false",
                            "ephemeral_block_device.#": "0",
                            "iam_instance_profile": "",
                            "id": "<INSTANCE ID>",
                            "instance_state": "running",
                            "instance_type": "<INSTANCE_TYPE>",
                            "ipv6_addresses.#": "0",
                            "key_name": "<KEY_NAME>",
                            "monitoring": "false",
                            "network_interface.#": "0",
                            "network_interface_id": "<NW_INTERFACE_ID>",
                            "primary_network_interface_id": "<NW_INTERFACE_ID>",
                            "private_dns": "<DNS_NAME>",
                            "private_ip": "<PRIVATE_IP>",
                            "public_dns": "",
                            "public_ip": "",
                            "root_block_device.#": "1",
                            "root_block_device.0.delete_on_termination": "false",
                            "root_block_device.0.iops": "100",
                            "root_block_device.0.volume_size": "10",
                            "root_block_device.0.volume_type": "gp2",
                            "security_groups.#": "0",
                            "source_dest_check": "true",
                            "subnet_id": "<SUBNET_ID>",
                            "tags.%": "1",
                            "tags.Name": "batchserver01",
                            "tenancy": "default",
                            "volume_tags.%": "0",
                            "vpc_security_group_ids.#": "1",
                            "vpc_security_group_ids.2148762998": "<SECURITY_GROUP_ID>"
                        },
                        "meta": {
                            "schema_version": "1"
                        },
                        "tainted": false
                    },
                    "deposed": [],
                    "provider": "aws"
                }
            },
            "depends_on": []
        }
    ]
}

ここから本題

★ EC2を1台しかimportしていないので、もう1台importしたらどうなるんだ?

import01.png

別に問題なくコマンド終了。
先ほど作成されたterraform.tfstate は、terraform.tfstate.backup にCOPYされ、terraform.tfstate に2台目が追記されました。
(省略)

★ S3をimport しても大丈夫だよね?

C:work> terraform import aws_s3_bucket.<任意の名前> <バケット名>

わざとバケット名を間違えて見ました。

import03.png

★ ALBをimport !!

C:work> terraform import aws_alb.<任意の名前> <ARN>

import04.png

結果、terraform.tfstate ファイルにはどんどんimport すればしただけ、追記されていきました。
どんどん下に追記されていくわけではなく、DataSource(aws_s3_bucket, aws_instance, aws_albなど)の名前で順に並んでいくようです。

参考サイト

対応しているDataSource をはじめ、オプション確認にはやっぱりここは外せません。

本家HashiCorp:AWS Provider

続きを読む