【新機能】 #SORACOM EndorseによるSIMカードとAWSの認証連携

2016.01.27

ども、大瀧です。

SORACOM Conference 2016 "Connected."でSORACOMの新サービスがいくつか発表されました。そのうちの一つであるSORACOM Endorseは、SORACOM Air専用のトークン発行サービスです。 Endorseの発行するトークンはSIMカードと対応付けることができるため、トークンに対応する外部Webサービスに送ることで、SIMカードと外部Webサービスの認証連携を構成することができます。今回は、おなじみAWS APIとの認証連携の例をご紹介します。

概要

SORACOM Endorseは、SORACOM所有の秘密鍵を用いたJWT(JSON Web Token)形式の電子署名済みトークンを発行します。(JWTについては都元のブログ記事が詳しいので、JWTの説明はここでは割愛します。)署名対象となるJSONには、検証用公開鍵ファイル名とIMSIなどAir SIMの情報 *1が含まれるので、検証するWebサービスでは公開鍵による署名自体の正当性だけでなく、SIMの正当性、ユーザー独自情報による正当性の検証も組み込む拡張性があります。今回はWebサービスのサンプルとしてAmazon API Gateway + AWS LambdaでSIMの正当性の検証を実装してみました。

endorse-aws01

Endorseの構成

EndorseはSORACOMのグループ単位で設定します。ユーザーコンソールとAPIで設定することができますが、今回はSORACOM APIをSORACOM SDK for RubyのCLIから設定します。グループ設定のSoracomEndorseネームスペースにパラメータとして以下をkeyvalueのハッシュで指定します。

$ export SORACOM_EMAIL=YOUR_SORACOM_EMAIL
$ export SORACOM_PASSWORD=YOUR_SORACOM_PASSWORD
$ soracom auth
$ soracom group update_configuration \
  --group-id=XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX \
  --namespace=SoracomEndorse \
  --params='[{"key":"parametersToEndorse","value":{"imsi":true,"imei":true,"msisdn":false,"userdata":true}},{"key":"enabled","value":true},{"key":"tokenTimeoutSeconds","value":600}]'
{
  "operatorId": "OP1234567890",
  "groupId": "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX",
  "createdAt": 1453179246724,
  "lastModifiedAt": 1453282193396,
  "configuration": {
    "SoracomEndorse": {
      "parametersToEndorse": {
        "imsi": true,
        "imei": true,
        "msisdn": false,
        "userdata": true
      },
      "enabled": true,
      "tokenTimeoutSeconds": 600
    }
  },
  "tags": {
    "name": "OtakiTest"
  },
  "createdTime": 1453179246724,
  "lastModifiedTime": 1453282193396
}
$

これでグループ設定はOKです。利用したいSIMをグループに加え、SIMを差したマシン(SIMを差したWi-Fiルータ経由などでも可)で動作確認してみます。Endorseのエンドポイントendorse.soracom.ioにHTTPSでGETリクエストを送り、{"token":<トークン文字列>}というJSONが返ってくればOKです。

$ curl https://endorse.soracom.io
{"token":"eyJraWQiOiJ2MS1mMmZlYTA2MGI5M2Y1MTBiZmI3MjJmMmNkNGIzNzc0ZS14NTA5LnBlbSIsImFsZyI6IlJTMjU2In0.eyJpc3MiOiJodHRwczovL3NvcmFjb20uaW8iLCJhdWQiOiJzb3JhY29tLWVuZG9yc2UtYXVkaWVuY2UiLCJleHAiOjE0NTM4MjA1ODAsImp0aSI6Il9wbmxhR3g5RGlCNFBFUXF5aW1XSUEiLCJpYXQiOjE0NTM4MjA0MDAsIm5iZiI6MTQ1MzgyMDM0MCwic3ViIjoic29yYWNvbS1lbmRvcnNlIiwic29yYWNvbS1lbmRvcnNlLWNsYWltIjp7Imltc2kiOiI0NDAxMDMxNDQ4NDM3OTgiLCJpbWVpIjoiMzU1MjMwMDQzMzkzNDY1In19.Uv4RXiyD2MXTJesrcKiZSHCfhLJKAt_ug6SrCSsM4PfHM9kbrbCxieZZuS2ATi2ynisW-25-QysZQScQO12JpDinXk6RdTNW6kM2YdTzH3KcWiLt-nlXYwemGdnrRtSBCVcL10tep6Jemla-XeO-mJYT5XLjFWN8aX4sSYi_xunZcg7LIvuPqQIqQT8SahRPbWO31EVTEsiHnelLWNpkQUAehIYb3rU3wiSPzxJ-ZqEybA3VlxFwxruqYgmQTztGZSQugNtfmU_69cwV-gK9SxY79zO7LsLsPjwIL2MloAFYGci5Wg1ealJoBpq51j9KRUnNGe_3E8wzYw914gSPGA"}$

"Use SORACOM Air to access SORACOM Endorse server."というレスポンスが返る場合は、Endorseの設定に不備があるか、SIMを経由せずにEndorseのエンドポイントにアクセスしています。それぞれ確認しましょう。

トークンは、JWTに対応する各言語のライブラリでデコードできますが、Webアプリケーションとして提供されるJSON Web Tokens - jwt.ioがお手軽です。画面左側のEncodedのフォームにトークンをペーストすると、右側のDecodedにデコード内容が表示されます。

endorse-aws03

header.kidが検証用公開鍵のファイル名、payload.soracom-endorse-claim.*にSIM関連の情報が見えますね。このあとの検証ロジックでそれぞれ参照します。

AWSの構成

Webサービスの簡単なサンプルとして、API Gateway + Lambda(Node.js)で実装してみました。以下のような処理をLambdaで実装します。

endorse-aws02

IAMロールの設定

ユーザーにレスポンスとして与える一時キーに対応するIAMロールとLambda実行用のロールを用意します。今回は検証用なので、1つのロールにまとめて定義しました。以下のインラインポリシー2つを紐付けるlambda_exec_role_assume_role_myselfロールを追加しました。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "logs:*"
            ],
            "Resource": "arn:aws:logs:*:*:*"
        },
        {
            "Effect": "Allow",
            "Action": [
                "s3:GetObject",
                "s3:PutObject"
            ],
            "Resource": [
                "arn:aws:s3:::*"
            ]
        }
    ]
}
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "Stmt1453184220000",
            "Effect": "Allow",
            "Action": [
                "sts:AssumeRole"
            ],
            "Resource": [
                "*"
            ]
        }
    ]
}

また、作成したロールの[信頼関係]タブで以下のポリシーを設定します。

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Service": "lambda.amazonaws.com",
        "AWS": "arn:aws:iam::<AWSアカウントID>:root"
      },
      "Action": "sts:AssumeRole"
    }
  ]
}

これでOKです。

API Gatewayの設定

今回はAPI GatewayをLambdaを呼び出すディスパッチャとして利用するだけなので、特別な設定は特にしません。/リソースのGETメソッドに定義、後述のLambda関数をIntegration Typeに設定しました。トークンを渡すクエリストリングsoracom_endorse_tokenがLambdaのイベントオブジェクトのtokenメンバーとして渡るよう、[Method Request] - [URL Query String Parameters]と[Method Integration] - [Mapping Templates]を以下のように設定しました。

endorse-aws04

endorse-aws05

これでOKです。デプロイしておきます。

Lambda関数の定義

今回はNode.jsで実装してみました。

var request = require('request');
var aws = require('aws-sdk');
var jwt = require('jsonwebtoken');
var soracom = require('soracom');

// SORACOMの認証情報
var sora_email    = 'YOUR_SORACOM_EMAIL';
var sora_password = 'YOUR_SORACOM_PASSWORD';
// SORACOM Endorseの検証用公開キーストア
var sora_keystore = 'https://s3-ap-northeast-1.amazonaws.com/soracom-public-keys/';

// 一時クレデンシャルの対象となるAWS IAMロールのARN
var role_arn = 'arn:aws:iam::123456789012:role/ROLE_NAME';

console.log('Loading event');
exports.handler = function(event, context) {
  // トークンをデコード
  var decoded = jwt.decode(event.token, {complete: true});
  // デコードしたペイロードからIMSIを取得
  var imsi = decoded.payload['soracom-endorse-claim'].imsi;
  // ヘッダに含まれるキー名からキーストアのURLを生成し、取得
  request(sora_keystore + decoded.header.kid, function (err, response, body){
    var pubkey = body;
    try {
      // トークンの検証が成功すればtryブロックを継続
      jwt.verify(event.token, pubkey);

      // SORACOM APIにアクセス
      var sora_obj = new soracom({ email: sora_email, password: sora_password });
      sora_obj.post('/auth', function(err, res, auth) {
        if (!err) {
          sora_obj.defaults(auth);
          sora_obj.get('/subscribers', function(err, res, body) {
            // SIM一覧から、IMSIを照合
            var result = body.some(function(subscriber) {
              return subscriber.imsi == imsi;
            })
            if(result) {
              // IMSIの照合が成功したら、AWSの一時キーを取得
              var sts = new aws.STS();
              var params = {
                RoleArn: role_arn,
                RoleSessionName: 'nominator-temp'
              };
              sts.assumeRole(params, function (err, data) {
                if (!err) {
                  // 一時キーを含むレスポンスを返送
                  context.succeed(data);
                } else {
                  console.log(err, err.stack);
                  context.fail('ERROR : Failed to get credential.');
                }
              });
            }else {
              // IMSI照合に失敗した場合
              context.fail('ERROR : Not authorized your device.');
            };
          });
        }
      });
    } catch(err) {
      // トークンの検証に失敗した場合
      console.log(err, err.stack);
      context.fail('ERROR : Failed to verify web token.');
    }
  });
};
  • 17〜26行目 : JWTのデコード、検証を行っています。Node.jsのJWTライブラリは扱いが非常に楽でシンプルに実装できた印象です。検証用公開鍵のURLは公開鍵ファイル名を除き固定です。
  • 28〜37行目 : SORACOM APIのSIM一覧をコールし、デコードしたトークンに含まれるISMIと照合しています。こちらの記事と同様の処理です。
  • 45行目でAWSの一時キーを発行、48行目で返しています。

動作確認

では、Endorseが発行したトークンをクエリストリングに含め、作成したWebサービス(API Gatewayのエンドポイント)にリクエストしてみます。

$ curl https://1234567890.execute-api.ap-northeast-1.amazonaws.com/prod/?soracom_endorse_token=XXXXXXXXXXXXXXXXXXXXXXX
{"ResponseMetadata":{"RequestId":"fd9d903a-c444-11e5-8cf2-d5f82273d3d6"},"Credentials":{"AccessKeyId":"ASIAXXXXXXXXXXXX","SecretAccessKey":"XXXXXXXXXXXXXXXXXXXXXXXX","SessionToken":"XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX","Expiration":"2016-01-26T16:53:49.000Z"},"AssumedRoleUser":{"AssumedRoleId":"AROAJTSYXEA5LVZ6B6N5S:nominator-temp","Arn":"arn:aws:sts::XXXXXXXXXXXX:assumed-role/lambda_exec_role_assume_role_myself/nominator-temp"}}
$

見慣れた方には見慣れている、AWSの一時キーが返ってきました!

AssumeRoleAPIのレスポンスはAWS CLIのローカルキャッシュのJSONと互換性がありそうなことに気づいたので、AWS CLIの設定ファイルにプロファイルを設定し、以下のようにローカルキャッシュにリダイレクトすることで事前の権限設定なしでAWS CLIを使えることを確認しました。

~/.aws/credentials

  :
[endorse]
role_arn = arn:aws:iam::XXXXXXXXXXXX:role/lambda_exec_role_assume_role_myself
source_profile = default
$ curl https://1234567890.execute-api.ap-northeast-1.amazonaws.com/prod/?soracom_endorse_token=XXXXXXXXXXXXXXXXXXXXXXX \
  > ~/.aws/cli/cache/endorse--arn_aws_iam__XXXXXXXXXXXX_role-lambda_exec_role_assume_role_myself.json
$ aws logs describe-log-groups \
  --profile endorse \
  --region ap-northeast-1
{
    "logGroups": [
        {
            "arn": "arn:aws:logs:ap-northeast-1:XXXXXXXXXXXX:log-group:/aws/lambda/Nominator:*",
            "creationTime": 1453184914154,
            "metricFilterCount": 0,
            "logGroupName": "/aws/lambda/Nominator",
            "storedBytes": 24480
        }
    ]
}
$

いい感じです。

まとめ

SORACOMの新しい認証サービスEndorseとAWS APIを連携させる例を紹介しました。もちろんAWS以外の任意のWebサービスでトークンのデコード/検証ができますので、様々なWebサービスがEndorseに対応していくことを期待します。

脚注

  1. そのほか、ユーザーデータとしてデバイスから任意の情報を追加することもできます