目次


セキュアな IoT ソリューションの設計と構築, 第 3 回

IoT アプリケーションをセキュリティーで保護する

IoT データをセキュアに保管し、保管されたデータをセキュアな API を介して公開し、セキュアな API をモバイル・アプリや Web アプリから呼び出す

Comments

コンテンツシリーズ

このコンテンツは全3シリーズのパート#です: セキュアな IoT ソリューションの設計と構築, 第 3 回

このシリーズの続きに乞うご期待。

このコンテンツはシリーズの一部分です:セキュアな IoT ソリューションの設計と構築, 第 3 回

このシリーズの続きに乞うご期待。

クラウド・ベースの IoT アプリケーションにおけるセキュリティーの主な目標は、許可されてないユーザーがデバイスから送信される機密データや個人データにアクセスできないようにすることです。また、不正なコマンドがデバイスに送信されないようにするための対策も、IoT アプリケーションには必要になります。この全 3 回からなるシリーズの第 3 回では、IoT デバイスからのデータを扱う Web アプリとモバイル・アプリをセキュリティーで保護するためのさまざまな手法について説明します。シリーズの第 1 回第 2 回のそれぞれで、デバイスをセキュリティーで保護し、デバイスとネットワーク間にセキュアな通信を確立するさまざまな手法を詳しく説明しました。

図 1 に、この記事で説明する手法を示します。モバイル・クライアントと Web クライアントには、許可されたユーザーだけがアプリにアクセスすることを確実にするための固有のセキュリティー・メカニズムを設けてあります。アプリケーション・ロジックのコアは、Bluemix ベースのバックエンド・サービスに配置します。このアプリケーション・ロジックによって IBM Watson IoT Platform から (セキュアな Watson IoT Platform API を使用して) データをプルし、そのデータを分析し、デバイスの制御コマンドを送信します。

図 1. IoT デバイスのデータを扱う Web およびモバイル・アプリをセキュリティーで保護するための手法
IoT デバイスのデータを扱う Web およびモバイル・アプリをセキュリティーで保護するための手法の概要図
IoT デバイスのデータを扱う Web およびモバイル・アプリをセキュリティーで保護するための手法の概要図

この記事では、読者が Bluemix アプリケーションをどのようにして開発およびデプロイするのかを理解していることを前提とします。Bluemix をまだ利用したことがない場合は、このリンク先の developerWorks の「IBM Bluemix の基礎」学習パスに沿ってチュートリアルに取り組むことをお勧めします。この記事では、Web またはモバイル・アプリをどのようにして開発するのかをステップバイステップで説明することはしません。この記事での説明は、Web またはモバイル・アプリにセキュリティー機能をどのようにして追加するのかに絞ります。

アプリケーションで受信した IoT データをセキュリティーで保護する

この記事のために私たちが作成した IoT セキュリティー・デモ・サーバー・アプリケーションは、Watson IoT のイベント・データにサブスクライブし、Watson IoT から受信したデータを Cloudant データベースに維持します。データを維持する際は、保管するデータに含まれる機密性の高い属性の一部 (IoT デバイスからのペイロードなど) を暗号化します。ペイロードは JSON フォーマットですが、Cloudant データベース内では AES 暗号化フォーマットで保管され、権限を持つユーザーがデータを取得する際に暗号化解除されます。この暗号化と暗号化解除は、ユーザーには透過的に行われます。

Watson IoT Platform へのセキュアなアクセス

Bluemix 内でモノのインターネット・サービスを追加すると、ユーザーのアプリケーション用に以下に示すようなアクセス資格情報が生成されます。これらの資格情報によって、IBM Watson IoT Platform へのアクセスがユーザーに許可されます。

{ 
   "iotf-service":[ 
      { 
         "name":"your-iotf-service-name",
         "label":"iotf-service",
         "tags":[ 
            "internet_of_things",
            "Internet of Things",
            "ibm_created",
            "ibm_dedicated_public"
         ],
         "plan":"iotf-service-free",
         "credentials":{ 
            "iotCredentialsIdentifier":"your-credentials",
            "mqtt_host":"your-org-name.messaging.internetofthings.ibmcloud.com",
            "mqtt_u_port":1883,
            "mqtt_s_port":8883,
            "base_uri":"https://your-org-name.internetofthings.ibmcloud.com:443/api/v0001",
            "http_host":" your-org-name.internetofthings.ibmcloud.com",
            "org":" your-org-name ",
            "apiKey":"a-your-org-name-your-key",
            "apiToken":"your-token"
         }
      }
   ]
}

以下のコード・スニペットに示すように、Watson IoT Platform のイベントおよびステータス更新へのサブスクライブは簡単です。

var IotfClient = require("ibmiotf").IotfApplication;
 
var iotConfig = {
        "org" : org,
        "id" : "iotsecuritydemo",
        "auth-key" : apiKey,
        "auth-token" : apiToken
    };
 
var iotfClient = new IotfClient(iotConfig);
 
iotfClient.connect();
 
iotfClient.on("error", function (err) {
    console.log("IoTF client error: "+JSON.stringify(err, null, 4));
});
 
iotfClient.on("connect", function () {
    // Subscribe to status from all devices
    iotfClient.subscribeToDeviceStatus();
         
    // Subscribe to all events from all devices
        iotfClient.subscribeToDeviceEvents();
    });
 
iotfClient.on("deviceEvent", function (deviceType, deviceId, eventType, format, payload) {
    // Handle events from devices
      console.log("Device event from:"+deviceType+", "+deviceId+" of event "+eventType);
     
      insertEventToDB(deviceType, deviceId, eventType, format, payload);
});
 
iotfClient.on("deviceStatus", function (deviceType, deviceId, payload, topic) {
    // Handle status updates from devices
      console.log("Device status from:"+deviceType+", "+deviceId);
     
      insertStatusToDB(deviceType, deviceId, payload, topic);
});

上記のコードにより、IoT イベントを受信するたびに、そのイベントとイベントに関連付けられたペイロードが Cloudant データベースに挿入されるようになります。

Cloudant データベース内のデータを暗号化する

専用 Cloudant のインスタンスでは、データベース内に保管中のデータ属性の暗号化がサポートされますが、Bluemix 固有の Cloudant サービスの場合、データ属性の暗号化はまだサポートされていません。ただし、カスタム暗号化手法を実装することによって、機密性の高いデバイス・データのセキュリティーを確保できます。

データを保管および取得する際の暗号化と暗号解除は、以下のコード・スニペットに示すように、業界で標準的な AES-256 アルゴリズムに基づいて実装できます。

var crypto = require('crypto');
var algorithm = 'aes-256-ctr';
var cryptoKey = 'your-key';
 
function encrypt(text){
  var cipher = crypto.createCipher(algorithm, cryptoKey);
  var crypted = cipher.update(text,'utf8','hex');
  crypted += cipher.final('hex');
  return crypted;
}
  
function decrypt(text){
  var decipher = crypto.createDecipher(algorithm, cryptoKey);
  var dec = decipher.update(text,'hex','utf8');
  dec += decipher.final('utf8');
  return dec;
}

特に大量のデータを処理しなければならない場合をはじめ、データの暗号化はパフォーマンスに負担をかけます。したがって、適切な手法を検討して実装する際は、この側面を考慮する必要があります。

Cloudant データベース内に保管されているデータにセキュアにアクセスする

Cloudant データベース内に保管されている IoT データにアクセスするには、以下のいずれかの方法を使用できます。

  • Cloudant API キーを共有する場合は、サード・パーティー・アプリケーションを使用します。
  • Bluemix のセキュリティー・サービスで保護された、独自のカスタム API を作成します。

Cloudant API キーを使用したサード・パーティー・アクセス

Bluemix 内で Cloudant DB サービスを追加すると、ユーザーのプログラム用に以下に示すようなアクセス資格情報が生成されます。プログラムからこれらの資格情報を使用することで、Cloudant データベースにアクセスすることができます。

{
  "cloudantNoSQLDB": [
    {
      "name": "Your name",
      "label": "cloudantNoSQLDB",
      "plan": "Shared",
      "credentials": {
        "username": "Your user id",
        "password": "Your password",
        "host": "Your host id",
        "port": 443,
        "url": "https://username:password@.cloudant.com"
      }
    }
  ]
}

資格情報のユーザー名とパスワードを使用して、以下のような Cloudant API キーを生成できます。

POST _api/v2/api_keys

生成された API キーは通常のユーザー・アカウントと同じように、読み取り/書き込み/管理アクセス権限を付与するなどして使用できます。

POST https://<ユーザー名:パスワード>.cloudant.com/_api/v2/api_keys

レスポンスは以下のような内容になります。

"password": "YPNCaIX1sJRX5upaL3eqvTfi",
"ok": true,
"key": "blentfortedsionstrindigl"

API キーには特定のデータベースに対するアクセス権限を付与する必要があります。データベースにアクセスするには、以下に示す PUT リクエストを使用します。

https://<ユーザー名>.cloudant.com/_api/v2/db/<データベース>/_security),

API キーは、Cloudant ダッシュボード (以下の図を参照) から生成することもできます。

API キーを生成するために使用できる Cloudant ダッシュボード画面のスクリーンショット
API キーを生成するために使用できる Cloudant ダッシュボード画面のスクリーンショット

API キーとパスワードのペアは、他のあらゆるユーザー・アカウントと同じように扱われます。API キーを呼び出しに渡すことで、データベースを API キーと共有し、API キーにそのデータベースに対するアクセス権限を割り当てることができます。ユーザー・アカウントを使用するのが適切でない状況では、API キーとパスワードのペアを使用してください。そのような状況に該当するのは、例えばソース・コードに資格情報が含まれている場合や、プログラムによってそれぞれに異なるアクセス権限を割り当てた複数のアカウントを作成しなければならない場合などです。

API レスポンスには、以下のプロパティーが格納されます。

  • ok - リクエストが成功すると、このプロパティーが返されます。
  • error - リクエストが失敗すると、エラー・コードを含んだこのプロパティーが返されます。
  • key - API キーを返します。
  • password - API パスワードを返します。

カスタム API を使用したサード・パーティー・アクセス

Cloudant データベースに直接アクセスできるようにすると、サード・パーティーとの統合が脆弱になり、保守するにもコストがかかってしまいます。推奨されるアーキテクチャーは、カスタム API を使用して、データベースにカプセル化層を重ねることです。カスタム API はデータベース固有の詳細を隠し、特定のインターフェース・コントラクトによってデバイス情報を返します。

IoT セキュリティー・デモ・サーバー・アプリケーション内の最初のカスタム API では、以下の HTTP リクエストを使ってデバイスのリストにアクセスできるようになっています。

http://<アプリケーション・ルート>/iotf/devices

例えば、IoT セキュリティー・デモ・サーバー・アプリケーションの Cloudant データベース内に保管されているデータの送信元デバイスのリストにアクセスするには、以下のリクエストを使用します。

http://iotsecuritydemo.mybluemix.net/iotf/devices

IoT セキュリティー・デモ・サーバー・アプリケーション内の 2 番目のカスタム API では、以下の HTTP リクエストを使って特定のデバイスのデータにアクセスできます。

http://<アプリケーション・ルート>/iotf/devices/<デバイス>

例えば、IoT セキュリティー・デモ・サーバー・アプリケーション内で使用するデバイス「j.patra」での最後の 50 件のイベントとそれぞれのイベントのペイロードを表示するには、以下のリクエストを使用します。

http://iotsecuritydemo.mybluemix.net/iotf/devices/j.patra

さらに、以下のようにリクエストに追加パラメーターを指定することで、特定の数のイベントをリクエストすることもできます。

http://<アプリケーション・ルート>/iotf/devices/<デバイス>?count=<>

例えば、IoT セキュリティー・デモ・サーバー・アプリケーション内で使用するデバイス「j.patra」の最後の 5 件のイベントとそれぞれのペイロードを表示するには、以下のリクエストを使用します。

http://iotsecuritydemo.mybluemix.net/iotf/devices/j.patra?count=5

Mobile Client Access を利用して API をセキュリティーで保護する

データをセキュリティーで保護し、カスタム API を介して公開した後は、次のステップとして、これらのカスタム API を認証と許可によるセキュリティーで保護します。Web アプリやモバイル・アプリが API を呼び出す際は、必要な認証資格情報を提示しなければならないようにします。また、これらのアプリには、それぞれがアクセス権限を持つ特定のデバイスのデータにだけ、アクセスが許可されるようにします。カスタム API をセキュリティーで保護する手段として、この記事では IBM Bluemix 上に用意されている Mobile Client Access サービスを利用します。

この記事で取り上げるカスタム API は、Node.js を使用して開発されています。API エンドポイントを保護するために、Node.js 内の API ハンドラー・メソッドは、passport 認証ミドルウェアを使用して増補されています。passport 認証ミドルウェアは、Mobile Client Access ストラテジー・オブジェクトを使って初期化された Node.js passport インスタンスです。したがって、認証要求は Mobile Client Access Server SDK によって代行受信されて処理されます。

API セキュリティーを実装する

IoT セキュティー・デモ・サーバー・アプリケーションのコードから抜粋した以下のスニペットに、Mobile Client Access と passport 認証ミドルウェアを使用してカスタム API エンドポイント (/iotf/devices) を保護する方法が説明されています。

// --------- Protecting mobile back end with Mobile Client Access -----------
 
// Load passport (http://passportjs.org)
var passportMCA = require('passport');
 
// Get the MCA passport strategy to use
var MCABackendStrategy = require('bms-mca-token-validation-strategy').MCABackendStrategy;
 
// Tell passport to use the MCA strategy
passportMCA.use(new MCABackendStrategy());
 
// Tell application to use passport
app.use(passportMCA.initialize());
 
// The serialize-deserialize functions save the user object in the session
passportMCA.serializeUser(function(user, done) { 
    done(null, user); 
}); 
passportMCA.deserializeUser(function(obj, done) { 
    done(null, obj); 
}); 
 
var iotRouteBase = '/iotf';
 
// This is a protected API
app.get(iotRouteBase + '/devices', 
        passportMCA.authenticate('mca-backend-strategy', {session: true}), 
        function(req, res) {
            // Check for the user group and filter out the devices that the user does not have access to
            executeIfAllowed(req.user, '', 'reader', 
                function(error, userGroup) {
                    if(error) {
                        res.status(400).json(error);
                    }
                    else {
                        getDeviceList(req.user, userGroup, 
                            function(error, result) {
                                if(error) {
                                    res.status(400).json(error);
                                }
                                else {
                                    res.status(200).send(result);
                                }
                          });
                     }
                });
        }
);

user および userGroup オブジェクトというカスタム認証・許可オブジェクトによって、Cloudant データベース内に保管されたデータに対するロール・ベースのセキュアなアクセスを提供します。これらの user および userGroup オブジェクトを使用するために、この記事ではカスタム認証プロバイダーを使用します。

カスタム ID プロバイダーを実装する

カスタム API を使用する際のベスト・プラクティスは、デバイス・データへのアクセスに対してだけでなく、デバイスへのコマンド送信に対しても、より厳重にユーザー許可を制御することです。そのためには、Cloudant 内に useruserGroup のそれぞれを対象としたカスタム・ドキュメントのセットを作成します。

Cloudant 内のこのカスタム・ドキュメントのセットを、カスタム認証プロバイダー (IoT セキュリティー・デモ・サーバー・アプリケーションの一部として実装) のロール・ベースの認証に使用します。

user ドキュメントの例は次のとおりです。

{
  "_id": "joypatra@gmail.com",
  "recordType": "user",
  "userName": "joypatra",
  "password": "encrypted-password",
  "displayName": "Joy Patra",
  "attributes": {
    "Language": "English",
    "Country": "India",
    "emailAddress": "joypatra@gmail.com",
    "userGroup": "groupAdmin"
  }
}

userGroup ドキュメントの例は次のとおりです。

{
  "_id": "groupViewers",
  "recordType": "userGroup",
  "roleAdmin": "false",
  "roleReader": "true",
  "roleWriter": "false",
  "displayName": "Device administrator group",
  "devices": [“j.patra”, “m.patra”]
}

この記事で開発したカスタム ID プロバイダーは、Cloudant 内にある上記の user ドキュメントと userGroup ドキュメントを使用します。IBM Bluemix 上の Node.js バックエンド・サーバー上で稼働する、このカスタム ID プロバイダーには、Mobile Client Access サービスによってユーザーを認証するために必要となる 2 つの RESTful API が用意されています。Mobile Client Access サービスは、これらの API を任意のカスタム認証プロバイダー内で呼び出すように設計されています。

これら 2 つの API は、Mobile Client Access サービスによって規定されている startAuthorizationhandleChallengeAnswer です。Mobile Client Access サービスがこれの RESTful API に期待するフォーマットは次のとおりです。

POST <base_url>/apps/<tenant_id>/<realm_name>/<request_type>

コード説明
base_urlカスタム ID プロバイダー Web アプリのベース URL を指定します。ベース URL は、Mobile Client Access ダッシュボードに登録されている URL です。
tenant_idテナントの固有 ID を指定します。Mobile Client Access がこの API を呼び出すとき、Mobile Client Access では、常に IBM Bluemix アプリケーションの GIUD を提供します。
realm_nameMobile Client Access ダッシュボードに定義されているカスタム・レルム名を指定します。
request_type次のいずれかの要求タイプを指定します。
  • startAuthorization: 認証プロセスの最初のステップを指定します。カスタム ID プロバイダーは、「チャレンジ」、「成功」、「失敗」のいずれかの状態で応答します。
  • handleChallengeAnswer: モバイル・クライアントからの認証チャレンジ応答を処理します。
これらの要求タイプとデータ・フォーマットについて詳しくは、このリンク先の IBM Bluemix 資料を参照してください。

上記の 2 つの必須メソッドを実装するのは、この記事の一環として実装されているカスタム認証プロバイダーです。以下のコード・スニペットに、Cloudant データベースに対するユーザー検証を示します。

app.post('/apps/:tenantId/:realmName/startAuthorization', jsonParser, function(req, res){
	var tenantId = req.params.tenantId;
	var realmName = req.params.realmName;
	var headers = req.body.headers;

	var response = { status: "failure" };

	var validTenant = isValidTenant(tenantId);
	var validRealm = isValidRealm(realmName);
	
	if(validTenant && validRealm) {
		response = {
				status: "challenge",
				challenge: { text: "Enter username and password" }
			};
		console.log("Responding with challenge.");
		res.status(200).json(response);
	}
	else {
		response.failure = { text: "Wrong tenant id or realm name." };
		console.log("Responding with failure.”);
		res.status(400).json(response);
	}
});

app.post('/apps/:tenantId/:realmName/handleChallengeAnswer', jsonParser, function(req, res){
	var tenantId = req.params.tenantId;
	var realmName = req.params.realmName;
	var challengeAnswer = req.body.challengeAnswer;

	var username = req.body.challengeAnswer["username"];
	var password = req.body.challengeAnswer["password"];

	var response = { status: "failure" };

	var validTenant = isValidTenant(tenantId);
	var validRealm = isValidRealm(realmName);
	
	if(validTenant && validRealm) {
		// Retrieve custom user document from Cloudant db
		db.get(username, function(error, userIdentity) {
			var response = { status: "failure" };
			
			if(! error) {
				if(encrypt(password) == userIdentity.password) {
					response.status = "success";
					response.userIdentity = userIdentity;
				}
				else
					error = new Error("Username or password does not exist or does not match.");
			}

			response.failure = error;

			if(response.status == "success") {
				console.log("Responding with success.");
				res.status(200).json(response);
			}
			else {
				console.log("Responding with failure.");
				res.status(400).json(response);
			}
		});
	}
	else {
		response.failure = { text: "Wrong tenant id or realm name." };
		console.log("Responding with failure.");		    
            res.status(400).json(response);
	}
});

カスタム ID プロバイダーについては、クライアントに送信するカスタム認証チャレンジを開発する必要があります。認証チャレンジは、以下のコード・スニペットに示す JSON オブジェクトです。以下のように、challenge 属性には独自のカスタム・フィールドを挿入できます。クライアント・サイドのアプリケーションは、これらのカスタム・フィールドを受け取って、必要に応じて使用するかどうかを選択できます。

{
    status: "challenge",
    challenge: {
        message: "Enter username and password",
        customField_retriesLeft: 2,
        customField_minUsernameLength: 8
    }
}

フロントエンドのアプリケーションは、チャレンジに対する応答を以下のフォーマットを使用して送信します。応答には、カスタム属性を埋め込むことができます。サーバー・サイドのアプリケーションはこれらのカスタム属性を受け取って、必要に応じて使用するかどうかを選択できます。

{
    username: "joypatra@gmail.com",
    password: "your-password",
    otherCustomField: "1234"
}

この記事でサーバー・サイドに実装したカスタム ID プロバイダーは、Cloudant データベース内のカスタム user ドキュメントと userGroup ドキュメントを使用して、応答に含まれている資格情報を検証します。

ユーザー検証が成功すると、サーバー・サイドのカスタム ID プロバイダーが Cloudant 内の user ドキュメントを Mobile Client Access サービスに返します。Mobile Client Access サービスは、その user ドキュメントに含まれる特定の属性をコピーして、セッションの user オブジェクトを作成します。以下のコード・スニペットに、Mobile Client Access サービスによって作成される user オブジェクトの例を記載します。

{
    "id": "joypatra",
    "authBy": "iotsecuritydemoRealm",
    "displayName": "Joy Patra",
    "attributes": {
	"userGroup":"groupAdmin",
	"emailAddress":"joypatra@gmail.com",
	"Language":"English",
	"Country":"India"
    }
}

また、Mobile Client Access サービスはセッション user オブジェクトに、user ドキュメントに含まれる attributes 属性を「そのまま」コピーします。したがって、この attributes 属性に、アプリケーション固有のすべてのカスタム属性を含めることができます。

セキュアな API にアクセスする

クライアント・アプリケーションが保護されたカスタム API にアクセスするには、次のいずれかの手法を使用できます。

  • バックエンド間の統合ストラテジー
  • API ベースの統合ストラテジー

表 1 で、この 2 つの手法を比較します。

表 1. 保護されたカスタム API にクライアント・アプリケーションがアクセスするための 2 つの手法
バックエンド間の統合ストラテジーAPI ベースの統合ストラテジー
この手法を成功させるためには、ターゲットのバックエンド・アプリケーションが「秘密」鍵の値をフロントエンド・アプリケーションと共有する必要があります。フロントエンド・アプリケーションからの要求ヘッダーにこの「秘密」鍵が含まれていれば、バックエンド・アプリケーション API は要求を受け入れて、それ以上の認証は行いません。フロントエンド・アプリケーションに「秘密」を知らせる必要はありません。フロントエンド・アプリケーションはユーザー資格情報をバックエンド・アプリケーション API に渡し、バックエンド・アプリケーションがそのユーザー資格情報の認証と許可を行います。
ユーザー固有の認証と許可を行う責任は、フロントエンド・アプリケーションにあります。フロントエンドが正しい「秘密」の値を渡せば、バックエンドはそれだけでフロントエンドを信頼します。フロントエンド・アプリケーションは認証も許可も行いません。
この統合は極めて単純です。Mobile Client Access のカスタム認証プロバイダーの場合、フロントエンド・アプリケーションが、Mobile Client Access からのチャレンジを処理するカスタム認証リスナーを実装する必要があります。
この記事を作成している時点で、明示的な「ログアウト」メソッドはありません。この記事を作成している時点で、明示的な「ログアウト」メソッドはありません。

バックエンド間の統合ストラテジーを実装する

Mobile Client Access のバックエンド間の統合ストラテジー内では、データに対する必要なアクセス権限を取得するために、セキュリティー資格情報 (secret フィールド) をHTTP リクエストのヘッダーに含めて渡します。以下に、API を呼び出すためのサンプル・コードを記載します。

var oauthSDK = require('bms-mca-oauth-sdk');
var request = require('request');
 
var mca_options = {
        cacheSize: 100,
        appId: "Your app id",                
        clientId: "Your client id",            
        secret: "Secret",
        serverUrl: "https://imf-authserver.ng.bluemix.net/imf-authserver"
    };
 
app.get('/devices', ensureAuthenticated, function(req, res) {
    var user_email = req.user._json.emailAddress;
    console.log("req.user - " + user_email);
    oauthSDK.getAuthorizationHeader(mca_options).then(function(authHeader){
        req.headers.Authorization = authHeader;
        request({
            url: 'http://iotsecuritydemo.mybluemix.net/iotf/devices', //URL to hit
            method: 'GET', //Specify the method, a POST is recommended
            headers: { //We can define headers too
                'Authorization': authHeader
            }
        }, function(error, response, body){
            if(error) {
                console.log(error);
            } else {
                var html = "";
                html += "<p>IOTF Devices:</p>"
                html += "<pre>" + body + "</pre>";
                res.send(html);
            }
        });
    });
});

API ベースの統合ストラテジーを実装する

API ベースの統合ストラテジー内では、フロントエンド・アプリケーションがユーザー固有の必須資格情報を渡した場合にのみ、バックエンドへのアクセスが許可されます。Facebook または Google 認証の場合、Mobile Client Access Client SDK がフロントエンドにある Facebook または Google の認証ライブラリーとのやり取りを処理します。カスタム認証プロバイダーの場合は、フロントエンド・アプリケーションが認証リスナーを実装する必要があります。

例えば、Mobile Client Access Client SDK を Cordova 用に初期化するには、以下のようにします。

BMSClient.initialize(applicationRoute, applicationGUID);

applicationRouteapplicationGUID は、Bluemix 上でホストされているバックエンド・アプリケーションの Bluemix アプリケーション・ルートと Bluemimx アプリケーション GUID です。

カスタム認証リスナーを実装するには、このリンク先の Mobile Client Access Bluemix 資料に記載されているカスタム認証リスナーの実装例 (Cordova の場合) を使用してください。

実装したカスタム認証リスナーを使用するには、その前に、そのリスナーを BMSClient に登録する必要があります。また、保護されたバックエンド API に対してリクエストを送信する前に、カスタム認証リスナーを呼び出さなければなりません。それには、以下のコードを使用します。

BMSClient.registerAuthenticationListener(realmName, customAuthenticationListener);

realmName は、Bluemix 内の Mobile Client Access ダッシュボード内でカスタム認証プロバイダーを構成する際に指定した名前です。

IoT クライアント・アプリケーションをセキュリティーで保護する

IoT クライアント・アプリケーションをセキュリティーで保護するには、Web アプリとモバイル・アプリにそれぞれ別個の認証を使用するという方法、あるいは Web アプリとモバイル・アプリの両方を対象とした統合認証を使用するという方法があります。

開発者は、開発中のアプリケーションのタイプに応じて、いずれかの方法を選択できます。Bluemix クラウド・プラットフォームは、プラットフォーム、データ、アプリケーションの各レベルでセキュリティーを提供します。

選択肢 1: Web とモバイルにそれぞれ別個の認証を使用する

別個の認証を使用する場合、Web アプリのユーザー認証には Bluemix SSO サービス (Cloud Directory ベース) を利用し、モバイル・アプリのユーザー認証には MobileFirst Client Access サービス (カスタム認証プロバイダー・ベース) を利用します。ただし、Web アプリとモバイル・アプリは両方とも、デバイス・データに対するユーザー許可には同じセキュアなアプリケーション層を使用します。図 2 を参照してください。

図 2. 別個の認証と同じセキュアなアプリケーション層
別個の認証と同じセキュアなアプリケーション層の概要図
別個の認証と同じセキュアなアプリケーション層の概要図

選択肢 2: モバイルおよび Web アプリに統合認証を使用する

図 3 に、Mobile Client Access サービスに基づく統合認証・許可を示します。統合認証の手法を使用する場合、モバイル・アプリと Web アプリが同じ Cloudant ベースのカスタムに認証・許可プロバイダーを使用してデバイス・データを保護し、デバイスにコマンドを送信できるユーザーを制限します。

図 3. 同じ認証と同じセキュアなアプリケーション層
同じ認証と同じセキュアなアプリケーション層の概要図
同じ認証と同じセキュアなアプリケーション層の概要図

Web アプリをセキュリティーで保護する

IoT SSO および許可をデモンストレーションするためのサンプル Web アプリ内では、「選択肢 1: Web とモバイルにそれぞれ別個の認証を使用する」で説明したように、Web アプリのユーザー認証をサポートするために IBM Cloud Directory を使用しています。この選択肢の場合、サンプル Web アプリがバックエンド API を呼び出すために必要な (ログイン ユーザー資格情報にマッピングされた) 資格情報を維持しなければなりません (この構成を避けるために、モバイル・アプリと Web アプリの両方に同じ資格情報を使用する場合は、選択肢 2 の「モバイルおよび Web アプリに統合認証を使用する」手法を使用できます)。

IBM Cloud Directory を使用した認証

IoT デバイス・データへのアクセスを提供する Web アプリは、アプリケーション・ユーザーのユーザー名/パスワードの組み合わせによるセキュリティーで保護する必要があります。デモ Web アプリ内では、バックエンド・ユーザー・レジストリー (IBM Cloud Directory など) を使用して、すべてのユーザー情報を保管します。さらに、このアプリケーションには Bluemix SSO を利用してシングル・サインオン (SSO) 機能も追加しています。

SSO サービスでは、ユーザーの資格情報を保管できる ID ソースとして、以下の 3 つのユーザー・レジストリーをサポートしています。

  • SAML Enterprise: SAML トークンの交換によって認証を行うユーザー・レジストリー。
  • Cloud Directory: IBM クラウド内でホストされるユーザー・レジストリー。この記事での例では、このユーザー・レジストリーを使用しています。
  • ソーシャル ID ソース: Google、Facebook、LinkedIn のそれぞれによって保守されるユーザー・レジストリー。

アプリケーション開発者が SSO 機能をアプリケーションに組み込むには、その前に、管理者がサービス・インスタンスを作成して ID ソースを追加する必要があります。SSO 機能を (IBM Cloud Directory を利用して) Web アプリに追加するには、以下の手順に従います。

  1. Bluemix 内に SSO サービスをセットアップし、新しい IBM クラウド・ディレクトリーを作成します。CSV ファイル内にユーザーを作成し、そのファイルをクラウド・ディレクトリーにアップロードします。以下の図を参照してください。 SSO を設定する画面のスクリーンショット
    SSO を設定する画面のスクリーンショット
  2. Node.js passport パッケージpassport-idaas-openidconnect を併せて使用して、Bluemix 環境にデプロイされている Node.js アプリケーションから SSO サービスを呼び出します。
    var express = require('express');
    var cookieParser = require('cookie-parser');
    var session = require('express-session');
    var passport = require('passport');
     
    var app = express();
     
    app.use(logger('dev'));
    app.use(cookieParser());
    app.use(session({
        secret : 'iotfCloud123456789',
        saveUninitialized : true,
        resave : true
    }));
    app.use(passport.initialize());
    app.use(passport.session());
     
    passport.serializeUser(function(user, done) {
        done(null, user);
    });
    passport.deserializeUser(function(obj, done) {
        done(null, obj);
    });
     
    var services = JSON.parse(process.env.VCAP_SERVICES || "{}");
    var ssoConfig = services.SingleSignOn[0];
    var client_id = ssoConfig.credentials.clientId;
    var client_secret = ssoConfig.credentials.secret;
    var authorization_url = ssoConfig.credentials.authorizationEndpointUrl;
    var token_url = ssoConfig.credentials.tokenEndpointUrl;
    var issuer_id = ssoConfig.credentials.issuerIdentifier;
    var callback_url = "https://IOTSSOAyan.mybluemix.net/auth/sso/callback";
     
    var OpenIDConnectStrategy = require('passport-idaas-openidconnect').IDaaSOIDCStrategy;
    var Strategy = new OpenIDConnectStrategy({
        authorizationURL : authorization_url,
        tokenURL : token_url,
        clientID : client_id,
        scope : 'openid',
        response_type : 'code',
        clientSecret : client_secret,
        callbackURL : callback_url,
        skipUserProfile : 'false',
        issuer : issuer_id
    }, function(iss, sub, profile, accessToken, refreshToken, params, done) {
        process.nextTick(function() {
            profile.accessToken = accessToken;
            profile.refreshToken = refreshToken;
            done(null, profile);
        })
    });
  3. ensureAuthenticated() 関数を使用して、各呼び出し内でログインを検証します。
    function ensureAuthenticated(req, res, next) {
        if (!req.isAuthenticated()) {
            req.session.originalUrl = req.originalUrl;
            res.redirect('/login');
        } else {
            return next();
        }
    }

モバイル・アプリをセキュリティーで保護する

Web アプリをセキュリティーで保護するだけでなく、IBM Bluemix ではモバイル・アプリのセキュリティーを確保するためのサービスも提供しています。これらのサービスのうち特に重要なのは、モバイル・デバイスからアプリケーションへのアクセスを制御する Mobile Client Access サービスです。

以下の図に、Mobile Client Access を利用したセキュアなモバイル・アプリが IoT データにアクセスする際のフロー (ユーザー資格情報をカスタム Cloudant データベースから検証するシナリオの場合のフロー) を示します。その下に、図に示されている各ステップを説明します。

  1. モバイル・デバイスが、モバイル・アプリケーション・バックエンドとのセッションを初期化するために、Mobile Client Access Client SDK を使用してモバイル・アプリケーション・バックエンドの Bluemix アプリケーション・ルートと Bluemix アプリケーション GUID を提供します。
  2. モバイル・デバイスが、モバイル・アプリケーション・バックエンド上の保護されたエンドポイント (セキュリティー・デモ・サーバー・アプリケーション内では、/iotf/devices) を呼び出します。エンドポイントを保護するために、サーバー・サイドで Node.js passport 認証が挿入されます。この認証では、Mobile Client Access Server SDK の Mobile Client Access ストラテジーが使用されます。
  3. Mobile Client Access Server SDK はエンドポイントに対する要求をインターセプトし、このセッションに既存の Mobile Client Access OAuth トークンがあるかどうかを検証します。
  4. 既存のトークンが有効な場合、Mobile Client Access Server SDK がエンドポイント・ハンドラーの呼び出しを許可します。そうでなければ、応答で HTTP 401 を返します。
  5. Mobile Client Access Client SDK は、Mobile Client Access Server SDK から返される HTTP 401 応答をインターセプトする方法を把握しているため、Client SDK が認証フローをトリガーします。
  6. 認証フローでは、Mobile Client Access サービスで認証プロバイダーとして Facebook、Google、またはカスタム認証プロバイダーのどれが構成されているのかを踏まえて、認証結果を Mobile Client Access Server SDK に返します。
  7. Mobile Client Access Server SDK に対する認証フローが成功すると、保護されたエンドポイント (セキュリティー・デモ・サーバー・アプリケーション内では、/iotf/devices) のエンドポイント・ハンドラーが呼び出されます。

Mobile Client Access Client SDK を初期化する (Android の場合)

SDK を初期化するには、コンテキスト、アプリケーション GUID、およびアプリケーション・ルートの 3 つのパラメーターを initialize() メソッドに渡す必要があります (以下のコード・スニペットを参照)。

アプリケーション・ルートとアプリケーション GUID は、Bluemix アプリケーション・ダッシュボードの最上部にある「Mobile Options (モバイル・オプション)」セクション内で確認できます。

try {
    // Initialize SDK with IBM Bluemix application ID and route
    //
    // You can find your Application Route and Application GUID in the 
    // Mobile Options section on top of your Bluemix application dashboard
 
            BMSClient.getInstance().initialize(
                    getApplicationContext(),
                    DeviceIoTDemoApplication.APPLICATION_ROUTE,
                    DeviceIoTDemoApplication.APPLICATION_ID);
     } catch (MalformedURLException e) {
            Log.e(TAG, "Error initializing Bluemix Mobile Service Client: "+e);
     }

セキュアな API にリクエストを送信する

Mobile Client Access Client SDK の初期化が完了した後、保護された API にリクエストを送信できます。

http://<アプリケーション・ルート>/iotf/devices

サンプル Android アプリは、Mobile Client Access Client SDK を使用してインストルメント化されています。上記のエンドポイントにモバイル・アプリからリクエストを送信するには、BMSClient インスタンスの初期化の後に以下のコードを追加します。

Request request = new Request(                      
           BMSClient.getInstance().getBluemixAppRoute()+"/iotf/devices", 
           Request.GET);
 
// BMSResponseListener is a custom class that has implemented ResponseListener
request.send(OAuthLoginActivity.this, new BMSResponseListener() {
    @Override
    public void onSuccess(final Response response) {
        super.onSuccess(response);
        runOnUiThread(
            new Runnable() {
               @Override
               public void run() {
                   Log.i(TAG, "Success Response=" +            
                     response.getResponseText());
                   // Execute tasks related to your Android UI
               }
            }
        );
    }
});

サンプル・モバイル Android クライアント

この記事のために開発したモバイル Android クライアント・サンプル・アプリは、最初にログイン画面でユーザー名、パスワード、そしてサーバーの接続先の情報を収集した後、この記事で前に説明した デバイス API を呼び出します。サーバー・サイドのアプリケーションが認証チャレンジで応答すると、クライアントはログイン画面で収集したユーザー名とパスワードを提供します。

セキュリティー・デモ・サーバー・アプリケーションでは、この Android アプリがユーザー名とパスワードをローカル Android ストレージに保存します。ただし、実際の本番アプリ内では、これとは異なる動作が必要になるはずです。

認証が成功すると、デバイス API はこのユーザーに表示が許可されているデバイスのリストを返します。これらのデバイスのそれぞれが、アプリ UI の別個のタブに表示されます。各タブに表示される内容は、対応するデバイスの最後のイベントです。そのイベントのタイム・スタンプとセンサーのペイロード・データが表示されます。

Android アプリは、カスタム認証リスナーを実装しています。この記事のモバイル・アプリは、ユーザー名とパスワードを収集してから、そのユーザー名とパスワードを使用して CustomAuthenticationListener クラスをインスタンス化しますが、以下のコードにコメントで注記しているように、本番の実装では Mobile Client Access Client SDK から認証チャレンジを受け取ってからユーザー名とパスワードを取得することを推奨します。

さらに、コードの onAuthenticationChallengeReceived() メソッド内で発生した例外をキャッチして、AuthenticationContext にエラーが発生したことを報告する必要があります。発生した例外をキャッチしなければ、Mobile Client Access Client API が永遠に「waiting-for-credentials」状態のままになります。これについても、以下のコードにコメントで注記しています。

public class CustomAuthenticationListener implements AuthenticationListener {
    private final String TAG = getClass().getSimpleName();

    private Context context;
    private String username = "";
    private String password = "";

    public CustomAuthenticationListener(Context context, String username, String password) {
        this.context = context;
        this.username = username;
        this.password = password;
    }

    @Override
    public void onAuthenticationChallengeReceived (
                        AuthenticationContext authContext,
                        JSONObject challenge, 
                        Context context) {

        // This is where developer would show a login screen, 
        // collect credentials and invoke
        // authContext.submitAuthenticationChallengeAnswer() API

        JSONObject challengeResponse = new JSONObject();
        try {
            challengeResponse.put("username", username);
            challengeResponse.put("password", password);
           
            authContext.submitAuthenticationChallengeAnswer(
                                            challengeResponse);
        } catch (JSONException e){

        // In case there was a failure in collecting credentials 
        // you need to report it back to the AuthenticationContext.
        // Otherwise Mobile Client Access client SDK will remain in a 
        // waiting-for-credentials state forever

            authContext.submitAuthenticationFailure(null);
        }
    }

アプリのセキュリティーの脆弱性をスキャンして監視する

IBM Bluemix プラットフォームには、アプリをスキャンまたは監視するために利用できるサービスも用意されています。例えば、IBM Application Security on Cloud サービスを利用すると、アプリにセキュリティーの抜け穴がないことを確実化できます。開発者はこれらのサービスを利用して、Web およびモバイル・アプリをデプロイする前に脆弱性をスキャンし、セキュリティー上の問題を識別して、推奨されるフィックスを実装することができます。さらに、Mobile Client Access サービスに用意されているダッシュボードを使用して、モバイル・アプリの使用状況、そしてデバイス内でのセキュリティー・イベント (認証の成功または失敗など) を監視および分析できます。

まとめ

IoTデバイスから取り込んだ機密データを分析するアプリケーションや、これらの IoT デバイス上の機能を制御するアプリケーションでは、セキュリティーを焦点領域の 1 つにしなければなりません。この記事では、IBM Bluemix プラットフォーム上で実行される IoT アプリケーション層のセキュリティーに注目し、IBM Bluemix のセキュリティー・サービスを利用して Web アプリとモバイル・アプリに共通のユーザー認証・許可をどのようにして実装するのか、機密データを Cloudant データベースに安全に保管するにはどのようにするのかを説明しました。

謝辞

この記事のレビューと提案で多大な協力をしてくれた Bluemix ソリューション・アーキテクトの Subrata Saha に感謝の言葉を贈ります。


ダウンロード可能なリソース


関連トピック


コメント

コメントを登録するにはサインインあるいは登録してください。

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=60
Zone=セキュリティ
ArticleID=1039190
ArticleTitle=セキュアな IoT ソリューションの設計と構築, 第 3 回: IoT アプリケーションをセキュリティーで保護する
publish-date=11032016