この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
ども、大瀧です。
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の構成
EndorseはSORACOMのグループ単位で設定します。ユーザーコンソールとAPIで設定することができますが、今回はSORACOM APIをSORACOM SDK for RubyのCLIから設定します。グループ設定のSoracomEndorse
ネームスペースにパラメータとして以下をkey
とvalue
のハッシュで指定します。
$ 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
にデコード内容が表示されます。
header.kid
が検証用公開鍵のファイル名、payload.soracom-endorse-claim.*
にSIM関連の情報が見えますね。このあとの検証ロジックでそれぞれ参照します。
AWSの構成
Webサービスの簡単なサンプルとして、API Gateway + Lambda(Node.js)で実装してみました。以下のような処理をLambdaで実装します。
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]を以下のように設定しました。
これで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の一時キーが返ってきました!
AssumeRole
APIのレスポンスは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に対応していくことを期待します。
脚注
- そのほか、ユーザーデータとしてデバイスから任意の情報を追加することもできます ↩