production.log

株式会社リブセンスでエンジニアをやっている星直史のブログです。

【ServerlessFramework】AWS LambdaとCognitoで作るセキュアなS3へのオブジェクト格納

概要

ユーザーがアップロードした画像データをS3に保存するケースにおいて Serverless Frameworkを使用して、AWS API Gateway 経由しLambdaで処理をするときに、 Cognitoで認証したユーザーのIAMをSTSを使用してS3にPUTするときの説明です。

f:id:watasihasitujidesu:20170417083912p:plain

今日は上記の図の四角で囲った部分の話をします。

serverless.yml

provider:
  name: aws
  runtime: nodejs6.10
  stage: dev 
  region: us-west-2
#  iamRoleStatements:
#    - Effect: "Allow"
#      Action:
#        - "s3:PutObject"
#      Resource:
#        - "arn:aws:s3:::yukashita-image-uploads/original-files/${self:provider.stage}/*"
functions:
  auth:
    handler: auth.auth
  upload:
    handler: handler.upload
    events:
      - http:
          path: upload
          method: post
          authorizer: auth
    response:
      headers:
        Content-Type: "'application/json'"
      template: $input.path('$')
resources:
  Resources:
    UploadBucket:
      Type: AWS::S3::Bucket
      Properties:
        BucketName: "bucket"

iamRoleStatementsをコメントアウトしていますが、Lambda自体にIAM(認可)の必要はありません。 というのも、認証されたCognitoIdentity UserのIAMで操作をするので、 必要な権限はCognitoのIAMに設定していきます。(設定自体はCognitoのIAM設定で説明します。)

Lambda

'use strict';

const aws = require("aws-sdk");

var cognitoidentityserviceprovider = new aws.CognitoIdentityServiceProvider({
    apiVersion: '2016-04-18',
    region: 'us-west-2'
});

module.exports.upload = (event, context, callback) => {
  var AWS = require('aws-sdk');
  AWS.config.region = 'us-west-2';
  
  console.log(AWS.config.credentials) // Enviroment Credentialとなる。つまり、Lambdaで設定されているCredential

  var options = { 
    params: {
      apiVersion: '2006-03-01',
      Bucket: "bucket"
    }   
  };  
  var bucket = new AWS.S3(options);

  var idToken = event.headers.Authorization; // 認証されたCognitoIdentity UserのidTokenを取得
  
  // AWS IAM STSでCognitoAuthUserに紐づいたクレデンシャルを取得。
  // CognitoIdentityCredentialsは内部的にSTSを返してくれる。
  AWS.config.credentials = new AWS.CognitoIdentityCredentials({
    region: "us-west-2",
    IdentityPoolId : 'us-west-2:your-identity--pool-id',
    RoleArn: "arn:aws:iam::123456789:role/Cognito_your_poolAuth_Role", // AuthRoleであることに注意
    Logins : { 
      'cognito-idp.us-west-2.amazonaws.com/us-west-2_ABCDEFG' : idToken // idToken != accessTokenであること
    }   
  }); 

  // AWS.config.credentialsを書き換えただけでは反映はされない
  // この時点では、AWS.config.credentials.needsRefresh == trueとなる
  
  // AWS.config.credentials.getを呼ぶと、未反映の場合はAWS.config.credentials.refreshを呼び出し、反映させる
  AWS.config.credentials.get(function(err) {
    // この時点で、AWS.config.credentials.needsRefresh == falseとなる
    // Cognito Credentialとなる。
    console.log(AWS.config.credentials)
    if (err) console.log(err);

    var params = { 
      Key: ["bucket", AWS.config.credentials.identityId, "object_name.jpg"].join("/"), // AWS.config.credentials.identityIdで認証をされたCognitoIdentity Userしか触れない領域を確保する。
      ContentType: "image/jpg",
      Body: event.body
    };
    bucket.putObject(params, function(err, data) {
      if (err) {
        console.log("Error", err);
      } else {
        console.log("Success", err);
      }
    });
  });
  callback(null, event);
};

ちょっと長いのですが、注意点はコードコメントにしています。 かいつまんでポイントを説明します。

  1. Lambda起動直後はAWS.config.credentialsEnviroment Credentialとなる。つまり、Lambdaで設定されているCredentialです。この時点ではLambdaにはなんの権限がないので、AWSリソースにアクセスしようとした場合、AccessDenyになります。
  2. AWS.CognitoIdentityCredentialsでCognitoIdentity Userのクレデンシャルを取得します。また、このときに渡すTokenはidTokenです。accessTokenではありません。公式ドキュメント
  3. AWS.config.credentials = new AWS.CognitoIdentityCredentialsとしただけでは未反映の状態です。AWS.config.credentials.getで反映させる必要があります。
  4. AWS.config.credentials.get後のAWS.config.credentialsCognitoCredentialとなります。これでCognitoで設定したIAMの権限に切り替わります。

最後にCognitoのIAM設定を確認します。

CognitoのIAM設定

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "mobileanalytics:PutEvents",
                "cognito-sync:*",
                "cognito-identity:*"
            ],
            "Resource": [
                "*"
            ]
        },
        {
            "Effect": "Allow",
            "Action": [
                "s3:PutObject"
            ],
            "Resource": [
                "arn:aws:s3:::bucket/${cognito-identity.amazonaws.com:sub}/*"
            ]
        }
    ]
}

Resourceで設定する内容は、LambdaのS3.putObjectの際に指定するKeyの許可について指定します。 ${cognito-identity.amazonaws.com:sub}を指定していますが、これはCognito Identity poolのIdentityIDです。
設定するIAM 変数名がものすごくわかりにくいのですが、Cognito User pool の Userに割り当てられるsubではありません。*1

このIAM変数を利用すると、s3://bucket/Cognito Identity poolのIdentityID/オブジェクトにしかアクセスできなくなります。 言い換えると、他のユーザーの情報にアクセスが不可能になります。 また、同様にDynamoDB Finegrain アクセスも同じ概念となります。

解決できなかったこと

今回はCognitoのIAMをAWSマネジメントコンソールから手で書き換えるしかありませんでした。 理由はserverless.ymlでIAMのresourceを作成できないからです。 具体的に言うと、IAM > Roles > Trust relationship の Principal > Federatedをcognito-identity.amazonaws.comに設定できません。

そのため、ここだけは手動でやらなくてはならないという悲しい状況でした。

Cognitoは設定値が紛らわしく、どれを使えば良いの?といった感じです。 今回出てきたものは、下記の通りです。

  • idToken != accessToken
  • Cognito Identity pool の IdentityID != Cognito User pool の User sub

まとめ

一言でまとめると、 Lambdaの処理中にCognito Identity で設定したIAMを使う だけだと思います。
serveless.ymlでIAMを定義できなかったり、AWS.congif.credentialsがrefreshしないと反映されなかったり、そもそもCognitoの設定値が紛らわしかったりで、散々ハマりましたが、 おかげで各サービスの理解が深まりました。

*1:ここでめちゃくちゃハマりました。

Serverless Framework 1.10.2でAWS IAM変数(${cognito-identity.amazonaws.com:sub})を使用する場合の設定

概要

標題の通りですが、Serverless Framework 1.10.2において、serverless.ymlにAWS IAM変数(${cognito-identity.amazonaws.com:sub}など)を使用する場合、
フレームワークの挙動として${文字列}の値を設定した変数に置き換えようとする動きをするので、その挙動を変更し、IAM変数を使えるようにする方法を書きます。
※今回はcognito-identity.amazonaws.com:subに限定して書きますので、汎用的ではありません。

元のコード

provider:
  name: aws 
  runtime: nodejs6.10
  stage: dev 
  region: us-west-2
  iamRoleStatements:
    - Effect: "Allow"
      Action:
        - "s3:PutObject"
      Resource: "arn:aws:s3:::mybucket/${self:provider.stage}/${cognito-identity.amazonaws.com:sub}/*"

このような感じで、自分のcognito identityに紐づいたs3 bucketしか触らせないようにするIAM Roleを記述し、 sls deploy -vした場合、

  Serverless Error ---------------------------------------
 
     Invalid variable reference syntax for variable cognito-identity.amazonaws.com.
     You can only reference env vars, options, & files. You
     can check our docs for more info.

こんな感じで怒られます。 そんな変数ねーよって感じですね。

そこで、ここの処理をどうしているのか追っていったところ
variableSyntaxという名の、それっぽものがありました。

さらに調べると、providerブロックの直下にvariableSyntaxを記述すると、記述した値でオーバーライドされとのことでした。 正直正規表現で全てのパターンを網羅するの気合いがなかったので、cognitoに限定した正規表現を作りました。

修正前: variableSyntax: '\\${([ :a-zA-Z0-9._,\\-\\/\\(\\)]+?)}'
修正後: variableSyntax: "\\${([^cognito*+][ :a-zA-Z0-9._,\\-\\/\\(\\)]+?)}"

単純ですね。cognito*+だった場合は対象から除外しているだけです。

修正後のコード

provider:
  name: aws 
  variableSyntax: "\\${([^cognito*+][ :a-zA-Z0-9._,\\-\\/\\(\\)]+?)}"
  runtime: nodejs6.10
  stage: dev 
  region: us-west-2
  iamRoleStatements:
    - Effect: "Allow"
      Action:
        - "s3:PutObject"
      Resource: "arn:aws:s3:::mybucket/${self:provider.stage}/${cognito-identity.amazonaws.com:sub}/*"

これで、deployし、AWS マネジメントコンソールのIAM > Roles > Review Policyで確認しましょう。

f:id:watasihasitujidesu:20170412113554p:plain

意図した通りの挙動になっていますね。

ファイル内の改行を置換するコマンド

2ファイル間で重複する / しない 行を出力する方法
こちらの操作をするときに、セットで、ファイル内の改行を置換するコマンドを調べることがあるので、
メモ程度に残しておく。

$ cat hogehoge.text
1
2
3
4
5

上記のファイルを1,2,3,4,5と出力したい場合のコマンド

cat hogehoge.text | sed -e :loop -e 'N; $!b loop' -e 's/\n/ /g'

2ファイル間で重複する / しない 行を出力する方法

タイトルの通り、何気に結構使う処理だけど都度調べているからメモとして残す。

a.text と b.textが以下の内容の時、1と 2,3,4,5を出力したい場合

$ cat a.text
1
2
3
4
5

$ cat b.text
2
3
4
5

$ sort {a,b}.text | uniq -u # ユニークな行を出力
$ sort {a,b}.text | uniq -d # 重複行を出力

@t_wada さんの「Mac の開発環境構築を自動化する (2015 年初旬編)」をAnsible ベストプラクティスに則り書き換えてみた

概要

t-wada.hatenablog.jp

Ansibleでmacの環境構築する際、id:t-wada さんの上記の記事を参考したのですが、 Ansible Best Practicesに沿っていなかったので、書き直してみました。

Ansibleを動かすまで

こちらは、t_wadaさんの記事のままです。

sudo xcodebuild -license
xcode-select --install
ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
brew doctor
brew update
brew install python
brew install ansible

どこを直したのか

twada/macbook-provisioning

hoshinaoshi/macbook-provisioning

本家とforkしたリポジトリを比較しながら、修正した点を挙げていきます。 階層構造も修正しています。

【本家】

.
├── LICENSE
├── README.md
├── hosts
├── localhost.yml

【修正後】

.
├── LICENSE
├── README.md
├── ansible.cfg
├── group_vars
├── hosts
├── localhost.yml
├── roles
│   ├── homebrew
│   │   ├── defaults
│   │   ├── files
│   │   ├── handlers
│   │   ├── meta
│   │   ├── tasks
│   │   │   └── main.yml
│   │   ├── templates
│   │   └── vars
│   │       └── main.yml
│   ├── homebrew-cask
│   │   ├── defaults
│   │   ├── files
│   │   ├── handlers
│   │   ├── meta
│   │   ├── tasks
│   │   │   └── main.yml
│   │   ├── templates
│   │   └── vars
│   │       └── main.yml
│   ├── oh-my-zsh
│   │   ├── defaults
│   │   ├── files
│   │   ├── handlers
│   │   ├── meta
│   │   ├── tasks
│   │   │   └── main.yml
│   │   ├── templates
│   │   └── vars
│   └── ricty
│       ├── defaults
│       ├── files
│       ├── handlers
│       │   └── main.yml
│       ├── meta
│       ├── tasks
│       │   └── main.yml
│       ├── templates
│       └── vars
└── site.yml

*1

site.ymlを作成し、localhost.ymlをinclude

ベストプラクティスではsite.ymlをmaster playbookとします。 また、プロビジョニング対象の役割(production, stagingなど)ごとに実行するroleをまとめたplaybookも作ります。 今回はlocalhost.ymlのみですね。*2

これらのファイルはroleをincludeするだけに留め、ここに細かいタスクを書いていくことはしません。(後述)

ロールの割り振り

元々のlocalhost.ymlのタスクは大きく4つの処理をしています。

  • homebrew
  • homebrew-cask
  • oh-my-zsh
  • ricty

今回はローカル環境のみが対象ですが、複数のプロビジョニング対象があった場合に、個別に差し込めるようにするため、 これらのタスクをrolesディレクトリ配下にそれぞれの役割ごとに分割します。 directory-layout

loclhost.ymlに列挙された変数を各ロールに振り分ける。

loclhost.ymlにhomebrew_taps, homebrew_packages, homebrew_cask_packagesの3つが定義されていましたが、 これらはhomebrewとcaskで使用するので、

  • ./roles/homebrew/vars/main.yml
  • .roles/homebrew-cask/vars/main.yml

に記述します。

./roles/hogehoge/vars/main.ymlに変数を記述すると
./roles/hogehoge/tasks/main.ymlの実行時に自動で変数を読み込み、タスク内で使用することができます。

localhost.ymlに記述されているhandlersはricty用なので、ricty/handlers/main.ymlに移動

localhost.ymlに記述されているhandlerは、よく見るとRicty用の処理であるため、Rictyロールのhandlerとして定義します。 こちらは、varsと同様にhandlerも同role内のhandlerディレクトリに記述していれば、notifyをした際に呼び出されます。

- name: run fc-cache
  shell: fc-cache -vf

localhost.ymlでtasksとして記述された内容をroleに任せる。

これらのファイルはロールをインクルードするだけに留め、ここに細かいタスクを書いていくことはしません。(後述)

ここまでの書き換えで、各ロールにvars, tasks, handlerを分けることができたので、 localhost.ymlではロールをインクルードするだけにします。

---
- name: Setup my MacBook
  hosts: localhost
  connection: local
  gather_facts: no 
  roles:
    - { role: homebrew,      tags: [ homebrew ] }
    - { role: homebrew-cask, tags: [ homebrew-cask ] }
    - { role: oh-my-zsh,     tags: [ oh-my-zsh ] }
    - { role: ricty,         tags: [ ricty ] }

だいぶスッキリしましたね。

また、tagを切っておくと、指定したタグだけ実行 / 指定したタグをスキップなどができるので、追加しておきます。 ansible-playbook site.yml --tags "homebrew,ricty" ansible-playbook site.yml --skip-tags "oh-my-zsh"

playbook実行時の引数を極力減らせるように設定

HOMEBREW_CASK_OPTS="--appdir=/Applications" ansible-playbook -i hosts -vv localhost.yml

実行時にhostsの設定をしたり、HOMEBREW_CASK_OPTSを設定したりするのはめんどくさいので、

ansible.cfgを作成し、デフォルトの動作を設定をします。

[defaults]
hostfile = ./hosts

環境変数については、homebrew-caskのタスクの一部として実行するようにします。

- name: HOMEBREW_CASK_OPTS設定
  shell: export HOMEBREW_CASK_OPTS="--appdir=/Applications"
...後続の処理がずらずら〜

ここまでの設定で、実行時のコマンドが HOMEBREW_CASK_OPTS="--appdir=/Applications" ansible-playbook -i hosts -vv localhost.ymlansible-playbook -vv site.yml となりました。 シンプル!

まとめ

以上でAnsible ベストプラクティスを適用した、Mac の開発環境構築を自動化する (2015 年初旬編)です。

"mac ansible"などで検索すると、localhost.ymlにtasksがばーーーーーっと書かれたplaybookをよく目にする印象があります。

複数のプロビジョニング対象が存在する場合は、ベストプラクティスが有効だと思いますが、 構築する対象がローカル環境のみで、処理(や、ロール)が少ないのであれば、1ファイルにゴリゴリ書いても良いのではないかと感じました。

ただ、実際は、開発スタートをするまでにrbenvの設定をしたり、ミドルウェアの設定をしたりなんだりすると、localhost.ymlが肥大化してしまうのではないかと考えられるため、早め早めにロールだけでも分けて記述するのが落とし所かと思います。

こちらにコードを置いておきます。
https://github.com/hoshinaoshi/macbook-provisioning

今回Ansibleを初めて触ってみたのですが、ymlで書くのが地味に楽でした。
Ansibleの処理も「Ansible {{やりたいコマンド}}」で検索 => 1つ目の記事ではい理解〜というような感じでした。
自分は、初めての技術に触れるときは、初心者用の書籍を購入して、一読するのですが、Ansibleにおいては下記の書籍を買いました。

*1:中身が空のディレクトリは削除しても良いかも

*2:通常は複数のサーバーに対して行うので、若干無理矢理感がありますね

Rubyで使われるコロンの意味を調べてみた

概要

Rubyを初めて触ったときに、(当時の自分が触っていた)C#JAVAではコロンが使われておらず、 これはどのような意味なのかがよく話からたかったので、まとめてみました。

:symbol
"symbol"

こちらの違いについてまとめます。

Rubyにおけるコロンの意味とは?

Rubyにおけるコロンは、シンボルといいます。

:symbol 一見文字列と同種に見えるが、内部的には数値として扱われます。 そのため、比較や検索などの速度面が文字列と比べると高速になります。

  • ハッシュのキー
  • メソッドの引数として渡すクラス名、メソッド名、変数名、定数名

などについては、はシンボルを使用したほうが良いでしょう。

リファレンスでは

リファレンスを引用します。

Rubyの内部実装では、メソッド名や変数名、定数名、クラス名など の'名前'を整数で管理しています。これは名前を直接文字列として処理するよりも 速度面で有利だからです。そしてその整数をRubyのコード上で表現したものがシンボルです。 シンボルは、ソース上では文字列のように見え、内部では整数として扱われる、両者を仲立ちするような存在です。 名前を管理するという役割上、シンボルと文字列は一対一に対応します。 また、文字列と違い、immutable (変更不可)であり、同値ならば必ず同一です。

文字列と見せかけて、内部の実装では整数として扱っている。といったところでしょうか。

検証してみた

> Rubyの内部実装では、メソッド名や変数名、定数名、クラス名など の'名前'を整数で管理しています。

つまり、メソッド名や変数名、定数名、クラス名を定義した瞬間にシンボルができるという意味です。 実際にやってみましょう。

class SymbolTest
end

symbol_var = 0

SYMLBOL_CONSTANT = 0

def symbol_method
end

p Symbol.all_symbols.include?(:SymbolTest)
=> true

p Symbol.all_symbols.include?(:symbol_method)
=> true

p Symbol.all_symbols.include?(:symbol_var)
=> true

p Symbol.all_symbols.include?(:SYMLBOL_CONSTANT)
=> true

見事に全てtrueを返しましたね。

> これは名前を直接文字列として処理するよりも 速度面で有利だからです。

文字列と数値なので、そりゃ高速になるだろうと反射的に思いましたが、こちらも検証してみます。

benchmarkというモジュールを使用して計測してみます。 実際のやり方はこちらを参考にしました。 Ruby でベンチマークを取る方法 - Qiita

require 'benchmark'

Benchmark.bm 10 do |r| 
  str = "0123456789"
  str_hash = { "0123456789" => 1 }
  r.report "String" do
    9999999.times { str_hash[str] }
  end 

  sym = :"0123456789"
  sym_hash = { "0123456789" =>  1 }
  r.report "Symbol" do
    9999999.times { sym_hash[sym] }
  end 
end

                 user     system      total        real
String       1.190000   0.000000   1.190000 (  1.190577)
Symbol       0.810000   0.000000   0.810000 (  0.815450)

文字列に比べて、シンボルは30%前後早くなっていますね。 すごいぞシンボル。

> 名前を管理するという役割上、シンボルと文字列は一対一に対応します。 また、文字列と違い、immutable (変更不可)であり、同値ならば必ず同一です。

同じ名前のシンボルであれば、いくつ生成してもオブジェクトIDが1つという意味ですかね。

hoge1 = "hoge"
hoge2 = "hoge"
puts hoge1.equal?(hoge2)
=> false

puts hoge1.object_id
=> 70281989413340

puts hoge2.object_id
=> 70281989361520

sym1 = :hoge
sym2 = :hoge
puts sym1.equal?(sym2)
=> true

puts sym1.object_id
=> 539048

puts sym2.object_id
=> 539048

こちらもリファレンス通りですね。

まとめ

冒頭でも書きましたが、Rubyにおけるコロンは、"シンボル"と呼びます。 一見文字列と同じように見えますが、内部的には整数と同様に扱われます。 そのため

  • 文字列と比較すると処理が高速
  • 同じ値であれば、生成されるオブジェクトは1つ。

また、クラス、メソッド、変数、定数を定義すると、その名前のシンボルも生成されます。

文字列とシンボルの使い分けとしては、

  • ハッシュのキー
  • メソッドの引数として渡すクラス名、メソッド名、変数名、定数名

などは、文字列ではなく、シンボルを使用したほうが良いでしょう。

こちらの本はRubyについて、今でもリファレンス的に使用している本なので、オススメします。
※今回のシンボルについては、ここまで詳細には書かれていませんが、全範囲を網羅的に書かれています。

homebrewでinstallしたcurlがbrew cask install時のTLS1.2ではOSX標準のcurlが邪魔をしてうまく反映されなかった時の対処法

概要

homebrewでinstallしたTLS 1.2 接続できるcurlbrew installでTLS1.2接続しなければならないパッケージのinstall時に、OSX標準のcurlを見に行ってしまい、うまくinstallできなかったので、その対処方法を書きます。

具体的には、brew cask install sourcetreeをした際に、curl https://downloads.atlassian.com/software/sourcetree/SourceTree_2.3.2.zipを叩くのですが、 そこでssl handshake failedとなり失敗していました。

手順

homebrewで最新のcurlをinstallしOpenSSLを使用するように変更

確認に使用したコマンドなども含めつつ説明していきます。

まずは、curlでTLS1.2で接続していないことを確認します。

$ curl --dump-header - https://www.example.com --tlsv1.2 --verbose 
*   Trying nnn.nnn.nnn.nnn...
* Connected to www.example.com (nnn.nnn.nnn.nnn) port 443 (#0)
* SSL peer handshake failed, the server most likely requires a client certificate to connect
* Closing connection 0
curl: (35) SSL peer handshake failed, the server most likely requires a client certificate to connect

次に、最新のOpenSSLでTLS1.2が使用できることを確認します。

$ openssl s_client -connect www.example.com:443 -tls1_2
CONNECTED(00000003)
...

既存のcurlにOpenSSLが使われていないことを確認します。

$ which curl
/usr/bin/curl
$ curl --version
curl 7.43.0 (x86_64-apple-darwin14.0) libcurl/7.43.0 SecureTransport zlib/1.2.5
Protocols: dict file ftp ftps gopher http https imap imaps ldap ldaps pop3 pop3s rtsp smb smbs smtp smtps telnet tftp 
Features: AsynchDNS IPv6 Largefile GSS-API Kerberos SPNEGO NTLM NTLM_WB SSL libz UnixSockets

上記の通りOpenSSLが含まれていません。

homebrewで改めてcurlをinstallします。

$ brew install --with-openssl curl
$ brew link curl --force

OpenSSLが使用できているか確認します。

$ which curl
/usr/local/bin/curl

$ curl --version
curl 7.51.0 (x86_64-apple-darwin14.5.0) libcurl/7.51.0 **OpenSSL/1.0.2j** zlib/1.2.5
Protocols: dict file ftp ftps gopher http https imap imaps ldap ldaps pop3 pop3s rtsp smb smbs smtp smtps telnet tftp 
Features: IPv6 Largefile NTLM NTLM_WB SSL libz TLS-SRP UnixSockets 

OpenSSL/1.0.2jが適用されていることが確認できました。

念のために、TLS1.2で接続できるかも確認してみます。

$ curl --dump-header - https://www.google.com --tlsv1.2 --verbose
* Rebuilt URL to: https://www.google.com/
*   Trying 216.58.200.196...
* TCP_NODELAY set
* Connected to www.google.com (216.58.200.196) port 443 (#0)
...
...
* Curl_http_done: called premature == 0
* Connection #0 to host www.google.com left intact

問題なくTLS1.2接続できていますね。

シンボリックリンクを張る

この時点で、brew cask install sourcetreeを実行してもssl handshake failedになります。 前回の記事同様パスの影響を受けているのではないかと考えましたので、 brew cask installで見ているcurlシンボリックリンクで無理やりbrew installしたcurlを参照させます。

$ sudo mv /usr/bin/curl /usr/bin/curl_back
$ sudo ln -s /usr/local/bin/curl /usr/bin/curl
$ brew cask install sourcetree
==> Caveats
Cask sourcetree installs files under "/usr/local". The presence of such
files can cause warnings when running "brew doctor", which is considered
to be a bug in Homebrew-Cask.

==> Downloading https://downloads.atlassian.com/software/sourcetree/SourceTree_2.3.2.zip
Already downloaded: /Users/user_name/Library/Caches/Homebrew/Cask/sourcetree--2.3.2.zip
==> Verifying checksum for Cask sourcetree
==> Moving App 'SourceTree.app' to '/Applications/SourceTree.app'
==> Symlinking Binary 'stree' to '/usr/local/bin/stree'
🍺  sourcetree was successfully installed!

うまくいきました。 シンボリックリンクで無理やり変更しているので、ワークアラウンド感がハンパないですね。 うまいやり方があれば是非教えていただきたいです。

homebrewでinstlalしたOpenSSLが反映されず、OSX標準のOpenSSLが使用されてしまう場合の対処方法

概要

OSX標準のOpenSSLが古く、tls1.2で通信できず、curlが失敗する事案が発生してしまいました。 対処方法を調べても、

`brew update
brew upgrade openssl
brew link openssl --force

で治りました^^

という記事が多かったのですが、上記の対応でもopenssl versionが古いままだったので、その対処方法を書きます。

手順

手順は下記の通りです。

  • homebrewで最新のOpenSSLをinstallし、リンクを張る
  • OpenSSLのパスを確認
  • 環境変数の設定

homebrewで最新のOpenSSLをinstall

この記事にたどり着いた人は、既に実行済みかつ、5,000万回は同じことを見ていると思いますが、 念のため記載しておきます。

$ which openssl #opensslのパスを確認 /usr/bin/openssl となってるはず
$ openssl version #バージョンの確認
$ brew update #brewのアップデート
$ brew list #opensslがインストールされているか確認
$ brew info openssl #最新版の情報取得
$ brew upgrade openssl #アップグレード
$ brew link openssl

OpenSSLのパスを確認

だいたいの記事では、これで治りました報告が多かったのですが、自分の場合は、 この時点でopenssl versionをやっても古いままでした。

今一度、パスを確認してみると

$ openssl version
OpenSSL 0.9.8zg 14 July 2015

$ which openssl
/usr/bin/openssl

$ brew info openssl
openssl: stable 1.0.2j (bottled) [keg-only]
ずらずら〜〜
/usr/local/Cellar/openssl/1.0.2j (1,695 files, 12M)

OSX 標準のOpenSSLのパスが/usr/bin/opensslだったのに対して homebrewでinstallしたOpenSSLが/usr/local/Cellar/openssl/1.0.2jなのが分かります。

環境変数の設定

これまでの結果から、読み込まれる順番が問題なのではないか?と推測し、 $PATHの設定を疑いました。

$ echo "export PATH=/usr/local/Cellar/openssl/1.0.2j/bin:$PATH" >> ~/.bash_profile
$ source ~/.bash_profile
$ which openssl
/usr/local/Cellar/openssl/1.0.2j/bin/openssl
$ openssl version
OpenSSL 1.0.2j  26 Sep 2016

予想通りでした。 brew link opensslだけでは正常に反映されておらず、基本的なパスなどの確認を怠っていたがために小一時間ハマってしまいました。

AWSアカウント開設直後 & EC2インスタンス立ち上げ直後に最低限行うべきこと

概要

以前からクラウド破産クラウド破産と耳に入ってはいたが、個人で利用しているAWSアカウントはほぼノーガードでした。
また、ピクスタの来期AWS予算を策定していく中で、こんな金額がいきなり身に降りかかってきたら生命保険に入ることを考えてしまうだろうと思ったため、
個人のアカウントも最低限セキュリティ対策をすることにしました。

やったこと(IAM & Billing)

  • MFA導入
  • IAM設定
    • グループ、ユーザー、IAMユーザーに対してのMFA設定
  • コストエクスプローラーのの有効化
  • アカウント設定
    • 秘密の質問など
  • CloudTrailの有効化
  • Trusted Advisor チェックの実施

ここまで箇条書きにしてみたものの、クラスメソッドさんの記事を参考にしました。

個人で使用する分には、IAMグループでAdministratorAccessを設定するのは、やむなしかと思ったのですが、 組織で使用する場合には、適切にグループの権限設定をした方が吉だと思いました。

やったこと(EC2)

アカウント全般もさることながら、EC2インスタンスもAmazonLinuxのAMIをまんま使用していたので、
稼働中のインスタンスに対してもセキュリティ向上のために下記を参考に諸々実施しました。

大きくは、

  • ec2-userの削除
  • sshdのLISTENポート変更
  • git-secretsの使用

がセキュリティ上、有用ってところでしょうか。

特に自分の場合は、個人で開発を進めているとアクセスキーとシークレットキーを誤ってcommitしてしまう事案が多発しているので、
git-secretsは入れておくべきだと感じました。

まとめ

実際にやってみて感じたのは、項目は多少多いものの、設定自体は簡単でした。 今更ながらですが、デフォルト設定のままだと不正利用された時に何も言えなくなるので、最初に締めべきとこは締めた方が良いと思います。

Re:dashのインストールから定期実行までの手順と使用感のまとめ

前回までの記事で

と、Webサービスのデータ解析をする上で必要なデータソースから取り込む手順を書きました。
今回は、それを定期的に実行する設定の手順を書きます。

Re:dashの設定

  1. クエリ画面にアクセス
  2. Refresh Scheduleのリンクを押下
  3. 実行スケジュールを設定。 上記の設定で決まった時間、決まった間隔で実行することが可能です。

また、Re:dashはクエリで取得するデータはページにアクセスする度に取得するわけではなく、内部に保存することで高速に情報を取得するようになっています。
取得するのに時間がかかるクエリは、深夜に実行しておくのが賢い使い方かもしれません。

f:id:watasihasitujidesu:20160815165803p:plain

Googleスプレッドシートの設定

  1. メニュバー > アドオン > Google Analytics > Schedule reportsを押下
  2. Enable reports to run automatically. にチェック
  3. 実行する間隔と時間を設定

f:id:watasihasitujidesu:20160815165828p:plain

f:id:watasihasitujidesu:20160815165842p:plain

以上で定期実行の設定は終わりです。
GoogleスプレッドシートとRe:dashの設定は、多少互いの実行時間を気にする必要はありますが、
ここまでできれば、自動でデータが取得できるので、Re:dashでダッシュボードを設定したり、
良い感じにグラフを組み合わせることで、データ可視化 & 共有プラットフォームの構築が可能になります。

まとめ

個人的には、
これまでRailsActiveRecordでデータ取得して〜
Viewでhtml書いて〜
Google Chartsでグラフ描画して〜
あ、そうだそうだ、絞り込みも追加して〜

と、自分でゴリゴリ書いていくのではなく、閲覧系の機能はRe:dashにお任せして、
更新系機能やRe:dashで実現しきれない部分だけは自分で作っていた方が良いと思いました。

また、難しいと感じる点は2点あります。

1点目は
GoogleスプレッドシートSQLの出力イメージをRe:dashで使えるように考えるは難しいのではないかと感じています。
特に、非エンジニアに運用を展開するときは、そこを厚めにサポートしなければ組織根付いていかないのかなと思っています。

2点目は
ダッシュボードに属させるクエリの管理です。
ここのカテゴライズを考えておかなければ、結局「あのデータどこに行ったっけ?エンジニアに聞いたろ」
ということが発生してしまうので、エンジニアの手間があまり軽減しない可能性があると思いました。

Re:dashがここまでデータ抽出 => 可視化を簡単に出来るとは思いませんでした。
難しいと感じる点で書いた2点、すなわち組織への展開と運用方法の確立さえうまくいけば、
エンジニアやディレクターの工数軽減できるサービスだと思います。

GoogleAnalyticsからGoogleスプレッドシートに自動でデータを取得する

前回の記事では、Googleスプレッドシートの値をRe:dashに取り込む方法を紹介しました。 今回は、GoogleAnalyticsのデータをGoogleスプレッドシートに取り込む手順を紹介しようと思います。

1. Googleスプレッドシートのアドオンを取得する。 GoogleスプレッドシートのアドオンにGoogleAnalyticsががあるので、それを取得します。 f:id:watasihasitujidesu:20160815123805p:plain f:id:watasihasitujidesu:20160815123833p:plain

2. メニューバー > アドオン > GoogleAnalytics > Create new reportで取得情報を設定する

Create new reportを選択すると右側に情報入力欄が表示されます。 そこの、 1) Name Your Report(これは好きな名前でOK) 2) Select Account Information - Account - Property - View (Profile) を設定してください。 上記の設定は、情報取得元になります。

入力が完了したらCreate Reportボタンを押下します。 f:id:watasihasitujidesu:20160815123904p:plain f:id:watasihasitujidesu:20160815123915p:plain

3. 取得したい情報を入力する。 2の手順が完了するとReport Configurationというシートが追加されます。 そのシートの下記項目を埋めていきます。

  • Metrics(縦軸)
  • Dimensions(横軸)

今回は、下記の内容を記入して実行してみます。

項目名 入力値
Metrics ga:sessions
Dimensions ga:date,ga:medium
Sort -ga:sessions

上記は直近7日間を日ごとに、参照元別のセッション数を出すための切り口を設定しています。

4. 実行する メニューバー > アドオン > GoogleAnalytics > Run reportsで値を取得します。

f:id:watasihasitujidesu:20160815123937p:plain

取得した値を元にRe:dashに適用できるようにピボットテーブルなどで加工すると、Google AnalyticsのデータをRe:dashで表示できるようになります。

Googleスプレッドシートの値をRe:dashに取り込む

Re:dashではDataSourceにGoogleスプレッドシートを指定することができます。 Googleスプレッドシートからデータを取り込む場合、 GoogleAPIConsoleからAPI Keyを発行するなど、手順が多かったので、今回の記事でまとめようと思います。

1. GoogleAPIConsoleにアクセス

2. 新規プロジェクト作成 Re:dash用のプロジェクトを作成します。 f:id:watasihasitujidesu:20160814130146p:plain

3. API Keyを作成

  • 画面左のサイドナビより認証情報をクリック f:id:watasihasitujidesu:20160814130147p:plain

  • 認証情報を作成 > サービス アカウントキーを選択 f:id:watasihasitujidesu:20160814130145p:plain

  • サービスアカウント種別を新しいサービスアカウントを選択し、各項目を入力。キーのタイプはJSONを選択 f:id:watasihasitujidesu:20160814130148p:plain

  • 画面左のサイドナビよりダッシュボードをクリック > APIを有効にするをクリック f:id:watasihasitujidesu:20160814130142p:plain

  • 検索フォームに"Drive"と入力すると出てくるGoogle Drive APIを選択 f:id:watasihasitujidesu:20160814130143p:plain

  • 有効にする をクリック f:id:watasihasitujidesu:20160814130144p:plain

これで、Googleスプレッドシートを操作するAPIキーの取得と、APIの有効化が終わりました。

4. Googleスプレッドシートで、APIと連携

  • API Keyを作成する過程で、秘密鍵jsonとしてDLできたはずです。このjsonファイルにemailが記載されているので、それをコピーしておきます。 
  • Googleスプレッドシートの共有設定で、先ほどコピーしたemailと共有する。

f:id:watasihasitujidesu:20160814131317p:plain

5. Re:dashでDataSourceを追加 Re:dash側でデータソースを追加します。
typeはGoogleSpreadSheetを選択し、API Keyを取得する際にDLしてきたjsonをファイル選択して、作成します。

f:id:watasihasitujidesu:20160814131333p:plain

6. クエリ作成 クエリ作成は少し、注意する必要があります。
取り込みたいGoogleスプレッドシートのURLが
https://docs.google.com/spreadsheets/d/10fdasFMDSjige4dfsaOMGF0asfmm1/edit#gid=2008635114
というURLで、左から2番目のシートを取り込みたい場合のクエリは

10fdasFMDSjige4dfsaOMGF0asfmm1|1
となります。
注意することは2つあります。
1. スプレッドシートのURLとシートの番号は|で区切る
2. シートの番号は左から0,1,2...となる。
10fdasFMDSjige4dfsaOMGF0asfmm1|1

f:id:watasihasitujidesu:20160814132025p:plain

この方法を参考に、自身のクエリを作り、実行すると、GoogleスプレッドシートのデータをRe:dashに取り込むことができます

Re:dashでデータソースとしてMysqlを追加する

saitou.hatenablog.com 前回の記事でRe:dashを使えるようにしましたので、今回はDataSourceとしてMySQLを追加してみます。

1. ログインする 初期ユーザーのログインidとpassは下記の通りです。 ID: admin PASS: admin

2. 画面右上のDBマークをクリックする

f:id:watasihasitujidesu:20160814123142p:plain

3. New DataSourceをクリック

f:id:watasihasitujidesu:20160814123151p:plain

4. 項目を埋めていく

f:id:watasihasitujidesu:20160814123204p:plain

5. DBが作成されたことを確認

f:id:watasihasitujidesu:20160814123155p:plain

6. クエリを実行してみる http://作成したインスタンスのIP/queries/new 上記にアクセスし、DataSourceを作成したDBを選択すると、 画面左側にスキーマ情報が表示されました。 スキーマが表示されれば接続自体できていますので、クエリを作成-実行してみてください。

f:id:watasihasitujidesu:20160814123206p:plain

Re:dashはスキーマを確認できるだけではなく、クエリの整形や、 グラフのpngダウンロード, 数値のCSV, Excelダウンロードまでサポートしています。

また、作成したグラフは範囲を指定することで、特定の日付に絞り込んだりすることができるので、 いちから自分で画面を作るより、だいぶ楽です。

データ可視化, 共有プラットフォームとして人気のRe:dashをAWS EC2で試してみるまでの手順

うちの会社では

ということが数多くあります。

正直Railsで実装するとなると、viewまで書かなければならなくしんどいのと、 GAからデータ取得し、スプレッドシートに貼り付けという作業をdailyでやり続けるめんどくささがありました。

これらの作業を自動で取得からグラフ作成できる良いツールはないかと探していたところ、 Re:dashと言われるデータ可視化, 共有プラットフォームツールがあるということで、試してみました。

今回はAWS EC2を使用してログインするところまでの手順を書こうと思います。

Re:dashとは

Re:dashとは、DataSource*1*2にクエリを投げ、返って来た結果を簡単にグラフ化、共有、ダッシュボードに登録することで、
データ可視化や共有プラットフォームとして機能することが期待できるツールです。 また、クエリは定期実行*3させたり、クエリをForkして使いまわせたりできます。 データやグラフについても、CSV, ExcelでDLや、iframeでhtmlに組み込み、png画像としてDLできたりします。

f:id:watasihasitujidesu:20160814120841p:plain

AWS EC2で試す手順

  1. AMIを使用する。 AWSで試すのであれば、AMIを使用するのが、最速です。 リージョンが東京*4であればami-b30ec9d2というAMIIDですぐに環境を立ち上げることができます。

f:id:watasihasitujidesu:20160814120845p:plain

  1. セキュリティグループの設定 Re:dashの操作方法は2種類あります。
  2. Webからアクセスして操作する方法
  3. ssh接続して操作する方法

なので、セキュリティグループで22(ssh), 80(http), 443(https)を開けておく必要があります。 また、AMIのOSはubuntuですので、ssh接続する際のユーザーはubuntuを指定する必要があります*5
ssh -i ~/.ssh/public.key ubuntu@ip-adress

  1. ログインしてみる
  2. で作成したインスタンスのIPにアクセスしてみましょう。
    アクセスするとIDとPASSを入力するフォームがあるので、
    初期ユーザの
    ID: admin
    PASS: admin
    でログインできるはずです。

AWS EC2のAMIを使用すれば、少しの時間でRe:dashを使えるようになります。
AWS以外にも

  • Google Compute Engine
  • Docker Compose
  • Heroku
  • apt-get などサポートしているので、初期導入までは比較的容易に済みそうです。

*1:MySQL, PostgreSQL, TDなど

*2:Google SpreadSheetも含まれるのでDataSourceと表記しました

*3:毎分, 時, 週, 月など

*4:ap-northeast-1

*5:普段ubuntuを触らないので、ssh接続するのに小一時間くらいハマりました

Qtがないとcapybara-webkitがbundle installできないので対処する方法

概要

Qtがないとcapybara-webkitをinstallできないって毎回怒られるんだけど、
毎回やりかた忘れちゃうのでメモ

手順

Qtのインストール

# qt4
$ wget https://download.qt.io/official_releases/qt/4.8/4.8.7/qt-everywhere-opensource-src-4.8.7.tar.gz
$ tar xzvf qt-everywhere-opensource-src-4.8.7.tar.gz
$ rm qt-everywhere-opensource-src-4.8.7.tar.gz
$ cd qt-everywhere-opensource-src-4.8.7
$ ./configure -opensource -nomake examples -nomake tests
$ sudo gmake -j2 # 2並列で1時間くらいかかる
$ sudo gmake install
$ sudo ln -s /usr/local/Trolltech/Qt-4.8.7/bin/qmake /usr/bin/qmake

# qt5
$ more /etc/yum.repos.d/cent6.repo
[CentOS6riken]
name=Extra Packages for Enterprise Linux 6 - $basearch
baseurl=ftp://ftp.riken.jp/Linux/centos/6/os/x86_64/
#mirrorlist=https://mirrors.fedoraproject.org/metalink?repo=epel-6&arch=$basearch
failovermethod=priority
enabled=1
gpgcheck=0
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-EPEL-6
$ sudo yum install qt5-qtwebkit-devel
$ sudo ln -s /usr/lib64/qt5/bin/qmake /usr/bin/qmake

Xvfbのインストール

Qtをインストールした後にbundle installをするとHeadless::Exception: Xvfb not found on your systemと怒られるので、Xvfbをインストールする。

sudo yum install -y libXcomposite.x86_64 xorg-x11-server-Xvfb.x86_64