ログインフォレンジックを使用してみる

はじめに

Salesforceのバージョンアップではセキュリティ関連の強化も継続して行われています。今回のブログではSpring'16でリリースされた「ログインフォレンジック」を紹介します。

ログインフォレンジックとは?

ヘルプを参照すると以下のように説明されています。

企業の重大な懸念であるなりすまし詐欺は後を絶ちません。1 日 (1 時間でも) あたりの組織へのログイン数に基づいてセキュリティ担当者が特定のユーザアカウントが侵害されているかどうかを判断するのは困難です。

ログインフォレンジックを使用すると、不審なログインアクティビティを特定しやすくなります。この機能では、次のような主要なユーザアクセスデータが提供されます。

・指定した期間のユーザあたりの平均ログイン数

平均ログイン数を超えるユーザ

営業時間外にログインしたユーザ

不審な IP 範囲を使用してログインしたユーザ

従来から提供されるログイン履歴と似ていますが、これまでシステム管理者がログイン履歴をダウンロードし、独自に集計しなければ確認できなかった統計値がログインフォレンジックを有効にすることで取得できるようになる点がポイントでしょう。また、カスタムのアプリケーションからAPIを介してログインする際に、ログインイベントのHTTPヘッダーを追加し拡張できる点も重要です。

なお、ログインフォレンジックを有効にすることで取得できるログインイベントおよび統計値は、APIオブジェクトとして提供され、LoginEvent、PlatformEventMetrics という名前のAPIオブジェクトを介して取得します。

APIオブジェクトとして提供されるため、Salesforce標準のユーザインターフェース(ビューやレポートなど)で参照できない点は注意しましょう。

ログインフォレンジックの有効化

設定 > 監視 > イベント監視 からログインフォレンジックを有効化します。

サンプルアプリ開発前の準備

サンプルアプリではLoginEvent/PlatformEventMetricsを取得しCSV出力を行ってみます。

1. WSDLの取得

設定 > ビルド > 開発 > API からEnterprise WSDLを生成し、WSDLを保存します。

2. Force WSC(Force.com Web Service Connector)をダウンロード

http://mvnrepository.com/artifact/com.force.api/force-wsc から最新版のWSCをダウンロードします。

3. スタブの生成

以下のコマンドでスタブを生成します。

java -classpath wsc-XX.jar com.sforce.ws.tools.wsdlc enterprise.wsdl enterprise.jar

4. Apache Commons CSVをダウンロード

CSV出力を簡単に行うために今回はApache Commons CSVを使用します。

http://commons.apache.org/proper/commons-csv/ から最新版をダウンロードします。

LoginEventの取得

LoginEventを取得しCSV出力を行うサンプルコードです。

APIオブジェクトと言っても特別なコーディングは必要なく、標準/カスタムオブジェクトと同様にクエリを発行してデータを取得することができます。

package app;

import java.io.FileWriter;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.List;
import org.apache.commons.csv.CSVFormat;
import org.apache.commons.csv.CSVPrinter;
import org.apache.commons.csv.QuoteMode;

import com.sforce.soap.enterprise.Connector;
import com.sforce.soap.enterprise.EnterpriseConnection;
import com.sforce.soap.enterprise.QueryResult;
import com.sforce.soap.enterprise.sobject.LoginEvent;
import com.sforce.soap.enterprise.sobject.SObject;
import com.sforce.ws.ConnectionException;
import com.sforce.ws.ConnectorConfig;

public class LoginEventApp {
	
	static final String USERNAME = "hoge@hoge.com";
	static final String PASSWORD = "password";
	static EnterpriseConnection connection;
	static final String SOQL = 
			"SELECT "
			+ "AdditionalInfo, "
			+ "ApiType, "
			+ "ApiVersion, "
			+ "Application, "
			+ "AuthServiceId, "
			+ "Browser, "
			+ "ClientVersion, "
			+ "EventDate, "
			+ "LoginGeoId, "
			+ "LoginHistoryId, "
			+ "LoginType, "
			+ "LoginUrl, "
			+ "Platform, "
			+ "SourceIp, "
			+ "Status, "
			+ "UserId, "
			+ "Username "
			+ "FROM "
			+ "LoginEvent";
	
	public static void main(String[] args) {
		ConnectorConfig config = new ConnectorConfig();
		config.setUsername(USERNAME);
		config.setPassword(PASSWORD);
		try{
			connection = Connector.newConnection(config);
			List<LoginEvent> loginEvents = queryLoginEvent();
			printCsv(loginEvents);
		}catch(ConnectionException e){
			e.printStackTrace();
		}
	}

	private static List<LoginEvent> queryLoginEvent() {
		List<LoginEvent> loginEvents = new ArrayList<LoginEvent>();
		try {
			Boolean done = false;
			QueryResult qr = connection.query(SOQL);
			if(qr.getSize() > 0){
				while(!done){
					for(SObject sobj : qr.getRecords()){
						loginEvents.add((LoginEvent)sobj);
					}
					if(qr.isDone()){
						done = true;
					}else{
						qr = connection.queryMore(qr.getQueryLocator());
					}
				}
			}
		} catch (ConnectionException e) {
			e.printStackTrace();
		}
		return loginEvents;
	}
	
	private static void printCsv(List<LoginEvent> loginEvents){
		CSVFormat format = CSVFormat.RFC4180.withDelimiter(',').withQuoteMode(QuoteMode.ALL);
		CSVPrinter printer = null;
		try{
			Path path = Paths.get("test.csv");
			printer = new CSVPrinter(new FileWriter(path.toString()), format);
			for(LoginEvent le: loginEvents){
				printer.print(le.getAdditionalInfo());
				printer.print(le.getApiType());
				printer.print(le.getApiVersion());
				printer.print(le.getApplication());
				printer.print(le.getAuthServiceId());
				printer.print(le.getBrowser());
				printer.print(le.getClientVersion());
				printer.print(new SimpleDateFormat("yyyy/MM/dd HH:mm:dd.ss").format(le.getEventDate().getTime()));
				printer.print(le.getLoginGeoId());
				printer.print(le.getLoginHistoryId());
				printer.print(le.getLoginType());
				printer.print(le.getLoginUrl());
				printer.print(le.getSourceIp());
				printer.print(le.getPlatform());
				printer.print(le.getStatus());
				printer.print(le.getUser());
				printer.print(le.getUsername());
				printer.println();
			}
			printer.flush();
		}catch(Exception e){
			e.printStackTrace();
		}finally {
			if(printer != null){
				try{
					printer.close();
				}catch(Exception e){
					e.printStackTrace();
				}
			}
		}
	}

}

CSV出力した内容です。こちらの情報はログイン履歴で取得できる情報と似ています。

取得できる情報の詳細はこちら参考にしてください。

PlatformEventMetricsの取得

LoginEventの取得サンプルと同様にPlatformEventMeticsも取得してCSV出力を行ってみましょう。

と思ったのですが、いざ開発を進めてみるとWSDLにPlatformEventMetricsが含まれていないです。試している環境はDeveloper Editionですし、生成したWSDLのバージョンは間違いなくAPIバージョン36.0としており原因が全くわからなかったので、セールスフォース・ドットコム社に問い合わせしてみました!

【サポートからの回答】

ログインフォレンジックに関連する、オブジェクトですが、現状はLoginEventのみのリリースとなっており、PlatformeEventMetricsに関しましてはまだ正式リリースとはなっていないことが確認できました。

ううむ、リリースノートが間違っていたのですね。。

サンプルアプリは実行できませんでしたが、具体的には以下の統計値が取得できるようです。

統計値種別(API参照名) 表示ラベル
NumLogins ログイン回数
NumDistinctLogins 個別のログイン回数
NumLoginsByUser ユーザ別のログイン回数
NumDistinctIps 個別のIP数
NumDistinctIpsByUser ユーザ別の個別のIP数
NumDistinctUsersByIP IP別の個別のユーザ数
NumDistinctBrowsersByUser ユーザ別の個別のブラウザ数
NumDistinctUsersByBrowser ブラウザ別の個別のユーザ数
NumDistinctUsersByApplication アプリケーション別の個別のユーザ数
NumDistinctApplicationsByUser ユーザ別の個別のアプリケーション数
NumDistinctUsersByLoginUrl ログインURL別の個別のユーザ数
NumDistinctLoginUrlsByUser ユーザ別の個別のログインURL数
NumDistinctUsersByPlatform プラットフォーム別の個別のユーザ数
NumDistinctPlatformsByUser ユーザ別の個別のプラットフォーム数

詳細はPlatformEventMetricsのリファレンスをご確認ください。

おわりに

ログインフォレンジックを有効にし、統計値の取得ができるところが最も重要な内容だっただけに、残念な結果となってしまいました。正式にリリースされましたら、別途本記事をフォローしたいと思います。