PAMレコードのリンク済み認証情報
PAMリソースレコードでアクセス可能な各フィールドタイプの説明
必要なSDKバージョン: 17.1.1以上
このドキュメントでは、PAMリソースレコードのリンク済み認証情報を操作する際に利用できるプロパティメソッドの一覧と、高度な活用事例を紹介しています。
すべてのプロパティメソッド
for (KeeperRecordLink link : record.getLinks()) {
// 基本プロパティ
String targetUid = link.getRecordUid();
String linkPath = link.getPath(); // 例: "pamUser", "ai_settings", "jit_settings"
String rawData = link.getData(); // Base64エンコードされたデータ
// ユーザー権限メソッド
boolean isAdmin = link.isAdminUser();
boolean isLaunchCredential = link.isLaunchCredential();
// 権限メソッド
boolean allowsRotation = link.allowsRotation();
boolean allowsConnections = link.allowsConnections();
boolean allowsPortForwards = link.allowsPortForwards();
boolean allowsSessionRecording = link.allowsSessionRecording();
boolean allowsTypescriptRecording = link.allowsTypescriptRecording();
boolean allowsRemoteBrowserIsolation = link.allowsRemoteBrowserIsolation();
// 設定関連メソッド
boolean rotatesOnTermination = link.rotatesOnTermination();
Integer dataVersion = link.getLinkDataVersion();
// データ解析メソッド
boolean hasReadableData = link.hasReadableData();
boolean hasEncryptedData = link.hasEncryptedData();
boolean mightBeEncrypted = link.mightBeEncrypted();
System.out.println("Link Analysis for " + targetUid + ":");
System.out.println(" Path: " + linkPath);
System.out.println(" Admin: " + isAdmin);
System.out.println(" Launch Credential: " + isLaunchCredential);
System.out.println(" Allows Rotation: " + allowsRotation);
System.out.println(" Allows Connections: " + allowsConnections);
System.out.println(" Has Encrypted Data: " + hasEncryptedData);
}高度な活用事例
高度なデータアクセスパターン
暗号化データの処理
public void handleEncryptedLinkData(KeeperRecord record) {
for (KeeperRecordLink link : record.getLinks()) {
if (link.getData() != null) {
System.out.println("\nAnalyzing link data for: " + link.getRecordUid());
System.out.println(" Path: " + (link.getPath() != null ? link.getPath() : "null"));
// 暗号化状態を確認
boolean mightBeEncrypted = link.mightBeEncrypted();
boolean hasEncryptedData = link.hasEncryptedData();
boolean hasReadableData = link.hasReadableData();
System.out.println(" Encryption Analysis:");
System.out.println(" mightBeEncrypted(): " + mightBeEncrypted);
System.out.println(" hasEncryptedData(): " + hasEncryptedData);
System.out.println(" hasReadableData(): " + hasReadableData);
try {
if (hasEncryptedData || mightBeEncrypted) {
// 方法1: getDecryptedDataを使用して暗号化コンテンツを復号
String decryptedData = link.getDecryptedData(record.getRecordKey());
System.out.println(" Decrypted Data: " + decryptedData);
// 方法2: getLinkDataを使用して構造化データにアクセス
Map<String, Object> linkData = link.getLinkData(record.getRecordKey());
if (linkData != null) {
System.out.println(" Structured Data:");
for (Map.Entry<String, Object> entry : linkData.entrySet()) {
System.out.println(" " + entry.getKey() + ": " + entry.getValue());
}
}
} else {
// プレーンなBase64データ
String plainData = link.getDecodedData();
System.out.println(" Plain Data: " + plainData);
}
} catch (Exception e) {
System.out.println(" Error processing data: " + e.getMessage());
System.out.println(" Raw Base64: " + link.getData());
}
}
}
}設定データ関連メソッドのデモ
public void demonstrateSettingsDataMethods(SecretsManagerOptions options) throws Exception {
QueryOptions queryOptions = new QueryOptions(
Collections.emptyList(),
Collections.emptyList(),
true // リンクと設定データを取得
);
KeeperSecrets secrets = SecretsManager.getSecrets2(options, queryOptions);
System.out.println("Settings Data Methods Demo:");
boolean foundSettings = false;
for (KeeperRecord record : secrets.getRecords()) {
if (record.getLinks() != null && !record.getLinks().isEmpty()) {
System.out.println("\nRecord: " + record.getTitle() + " (" + record.getType() + ")");
for (KeeperRecordLink link : record.getLinks()) {
String linkPath = link.getPath();
System.out.println(" Link to " + link.getRecordUid() + " (path: " + linkPath + ")");
// AI設定メソッドをテスト
if ("ai_settings".equals(linkPath)) {
foundSettings = true;
System.out.println(" Testing getAiSettingsData():");
try {
Map<String, Object> aiSettings = link.getAiSettingsData(record.getRecordKey());
if (aiSettings != null) {
System.out.println(" AI Settings found:");
for (Map.Entry<String, Object> entry : aiSettings.entrySet()) {
System.out.println(" " + entry.getKey() + ": " + entry.getValue());
}
} else {
System.out.println(" AI Settings data could not be parsed");
}
} catch (Exception e) {
System.out.println(" Error parsing AI settings: " + e.getMessage());
}
}
// JIT設定メソッドをテスト
if ("jit_settings".equals(linkPath)) {
foundSettings = true;
System.out.println(" Testing getJitSettingsData():");
try {
Map<String, Object> jitSettings = link.getJitSettingsData(record.getRecordKey());
if (jitSettings != null) {
System.out.println(" JIT Settings found:");
for (Map.Entry<String, Object> entry : jitSettings.entrySet()) {
System.out.println(" " + entry.getKey() + ": " + entry.getValue());
}
} else {
System.out.println(" JIT Settings data could not be parsed");
}
} catch (Exception e) {
System.out.println(" Error parsing JIT settings: " + e.getMessage());
}
}
// 一般的な getSettingsForPath メソッドをテスト
if (linkPath != null && linkPath.endsWith("_settings")) {
foundSettings = true;
System.out.println(" Testing getSettingsForPath(\"" + linkPath + "\"):");
try {
Map<String, Object> settings = link.getSettingsForPath(linkPath, record.getRecordKey());
if (settings != null) {
System.out.println(" Settings found for path '" + linkPath + "':");
for (Map.Entry<String, Object> entry : settings.entrySet()) {
System.out.println(" " + entry.getKey() + ": " + entry.getValue());
}
} else {
System.out.println(" Settings data could not be parsed");
}
} catch (Exception e) {
System.out.println(" Error parsing settings: " + e.getMessage());
}
}
}
}
}
if (!foundSettings) {
System.out.println(" No settings paths found in current records.");
System.out.println(" Settings paths to look for: ai_settings, jit_settings, *_settings");
}
}複雑な関係性の分析 (DAG解析)
public class ComprehensiveDagAnalyzer {
public static void analyzeLinkingPatterns(SecretsManagerOptions options) throws Exception {
QueryOptions queryOptions = new QueryOptions(
Collections.emptyList(),
Collections.emptyList(),
true // リンクを取得
);
KeeperSecrets secrets = SecretsManager.getSecrets2(options, queryOptions);
// 関係マップを構築
Map<String, List<String>> relationshipMap = new HashMap<>();
Map<String, String> recordTitles = new HashMap<>();
Map<String, String> recordTypes = new HashMap<>();
int totalLinks = 0;
for (KeeperRecord record : secrets.getRecords()) {
recordTitles.put(record.getRecordUid(), record.getTitle());
recordTypes.put(record.getRecordUid(), record.getType());
List<KeeperRecordLink> links = record.getLinks();
if (links != null && !links.isEmpty()) {
List<String> targets = new ArrayList<>();
for (KeeperRecordLink link : links) {
targets.add(link.getRecordUid());
totalLinks++;
}
relationshipMap.put(record.getRecordUid(), targets);
}
}
System.out.println("DAG Analysis Report:");
System.out.println("===================");
System.out.println("Total records: " + secrets.getRecords().size());
System.out.println("Records with outgoing links: " + relationshipMap.size());
System.out.println("Total links: " + totalLinks);
// 参照対象 (被リンク) になっているレコードを抽出
Set<String> targets = new HashSet<>();
for (List<String> linkTargets : relationshipMap.values()) {
targets.addAll(linkTargets);
}
System.out.println("Records that are link targets: " + targets.size());
// 詳細な関係パターンを表示
if (!relationshipMap.isEmpty()) {
System.out.println("\nDetailed Relationship Patterns:");
for (Map.Entry<String, List<String>> entry : relationshipMap.entrySet()) {
String sourceTitle = recordTitles.get(entry.getKey());
String sourceType = recordTypes.get(entry.getKey());
System.out.println(" " + sourceTitle + " [" + sourceType + "] → " + entry.getValue().size() + " record(s)");
for (String targetUid : entry.getValue()) {
String targetTitle = recordTitles.get(targetUid);
String targetType = recordTypes.get(targetUid);
System.out.println(" → " + (targetTitle != null ? targetTitle : "Unknown record: " + targetUid) +
" [" + (targetType != null ? targetType : "Unknown") + "]");
}
}
} else {
System.out.println(" No links found in current records.");
}
}
}高度なPAMユーザー管理
public static List<UserStatus> getPamMachineUsers(String pamMachineUid, InMemoryStorage storage) {
List<UserStatus> users = new ArrayList<>();
try {
QueryOptions queryOptions = new QueryOptions(
Collections.emptyList(),
Collections.emptyList(),
true // リンクを取得
);
SecretsManagerOptions options = new SecretsManagerOptions(storage);
KeeperSecrets secrets = SecretsManager.getSecrets2(options, queryOptions);
// PAMマシンレコードを検索
KeeperRecord pamMachine = null;
for (KeeperRecord record : secrets.getRecords()) {
if (record.getRecordUid().equals(pamMachineUid) && "pamMachine".equals(record.getType())) {
pamMachine = record;
break;
}
}
if (pamMachine != null && pamMachine.getLinks() != null) {
System.out.println("PAM Machine: " + pamMachine.getTitle());
System.out.println("このPAMマシンに関連付けられたユーザー数: " + pamMachine.getLinks().size());
for (KeeperRecordLink link : pamMachine.getLinks()) {
// リンクされたユーザーレコードを検索
for (KeeperRecord record : secrets.getRecords()) {
if (record.getRecordUid().equals(link.getRecordUid()) && "pamUser".equals(record.getType())) {
// SDKユーティリティメソッドで管理者ステータスを確認
boolean isAdmin = link.isAdminUser();
boolean isLaunchCredential = link.isLaunchCredential();
users.add(new UserStatus(record.getTitle(), record.getRecordUid(), isAdmin, isLaunchCredential));
System.out.println(" " + record.getTitle() + " - [" + (isAdmin ? "IS ADMIN" : "IS NOT ADMIN") + "] [" +
(isLaunchCredential ? "IS LAUNCH CREDENTIAL" : "IS NOT LAUNCH CREDENTIAL") + "]");
break;
}
}
}
// PAM構成を検証(テストロジックに基づく)
long adminUsersCount = users.stream().filter(UserStatus::isAdmin).count();
long launchCredentialUsersCount = users.stream().filter(UserStatus::isLaunchCredential).count();
System.out.println("Summary:");
System.out.println(" Admin users: " + adminUsersCount);
System.out.println(" Launch credential users: " + launchCredentialUsersCount);
if (adminUsersCount > 1) {
System.out.println("Warning: 複数の管理者ユーザーが検出されました(通常は1人であるべきです)");
}
if (launchCredentialUsersCount > 1) {
System.out.println("Warning: 複数の起動用認証情報が検出されました(通常は1人であるべきです)");
}
}
} catch (Exception e) {
System.err.println("PAMマシンユーザーの取得中にエラーが発生しました: " + e.getMessage());
}
return users;
}
public static class UserStatus {
private final String userName;
private final String userUid;
private final boolean isAdmin;
private final boolean isLaunchCredential;
public UserStatus(String userName, String userUid, boolean isAdmin, boolean isLaunchCredential) {
this.userName = userName;
this.userUid = userUid;
this.isAdmin = isAdmin;
this.isLaunchCredential = isLaunchCredential;
}
public String getUserName() { return userName; }
public String getUserUid() { return userUid; }
public boolean isAdmin() { return isAdmin; }
public boolean isLaunchCredential() { return isLaunchCredential; }
@Override
public String toString() {
return userName + " (" + userUid + ") - [" +
(isAdmin ? "IS ADMIN" : "IS NOT ADMIN") + "] [" +
(isLaunchCredential ? "IS LAUNCH CREDENTIAL" : "IS NOT LAUNCH CREDENTIAL") + "]";
}
}包括的なリンク済みレコードデータ分析
public static void analyzeLinkDataStructure(SecretsManagerOptions options) throws Exception {
QueryOptions queryOptions = new QueryOptions(
Collections.emptyList(),
Collections.emptyList(),
true
);
KeeperSecrets secrets = SecretsManager.getSecrets2(options, queryOptions);
System.out.println("Link Data Structure Analysis:");
for (KeeperRecord record : secrets.getRecords()) {
if (record.getLinks() != null && !record.getLinks().isEmpty()) {
System.out.println("\nRecord: " + record.getTitle() + " (" + record.getType() + ")");
System.out.println("UID: " + record.getRecordUid());
for (int i = 0; i < record.getLinks().size(); i++) {
KeeperRecordLink link = record.getLinks().get(i);
System.out.println(" Link " + (i + 1) + ":");
System.out.println(" Target UID: " + link.getRecordUid());
System.out.println(" Path: " + (link.getPath() != null ? link.getPath() : "null"));
if (link.getData() != null) {
try {
if (link.hasEncryptedData() || link.mightBeEncrypted()) {
String decodedData = link.getDecryptedData(record.getRecordKey());
System.out.println(" Decoded Data: " + decodedData);
} else {
String decodedData = link.getDecodedData();
System.out.println(" Decoded Data: " + decodedData);
}
} catch (Exception e) {
System.out.println(" Raw Base64: " + link.getData());
System.out.println(" Decode Error: " + e.getMessage());
}
} else {
System.out.println(" Data: null");
}
}
}
}
}ユーティリティメソッドの完全な例
public static void demonstrateLinkUtilityMethods(SecretsManagerOptions options) throws Exception {
QueryOptions queryOptions = new QueryOptions(
Collections.emptyList(),
Collections.emptyList(),
true
);
KeeperSecrets secrets = SecretsManager.getSecrets2(options, queryOptions);
System.out.println("Link Utility Methods Demo:");
for (KeeperRecord record : secrets.getRecords()) {
if (record.getLinks() != null && !record.getLinks().isEmpty()) {
System.out.println("\nRecord: " + record.getTitle() + " (" + record.getType() + ")");
for (int i = 0; i < record.getLinks().size(); i++) {
KeeperRecordLink link = record.getLinks().get(i);
System.out.println(" Link " + (i + 1) + " to " + link.getRecordUid() + ":");
System.out.println(" Path: " + (link.getPath() != null ? link.getPath() : "null"));
// ユーザー関連ユーティリティ
if (link.isAdminUser()) {
System.out.println(" Admin user");
}
if (link.isLaunchCredential()) {
System.out.println(" Launch credential");
}
// 権限関連ユーティリティ
if (link.allowsRotation()) {
System.out.println(" Allows rotation");
}
if (link.allowsConnections()) {
System.out.println(" Allows connections");
}
if (link.allowsPortForwards()) {
System.out.println(" Allows port forwards");
}
if (link.allowsSessionRecording()) {
System.out.println(" Allows session recording");
}
if (link.allowsTypescriptRecording()) {
System.out.println(" Allows typescript recording");
}
if (link.allowsRemoteBrowserIsolation()) {
System.out.println(" Allows remote browser isolation");
}
// 設定関連ユーティリティ
if (link.rotatesOnTermination()) {
System.out.println(" Rotates on termination");
}
Integer version = link.getLinkDataVersion();
if (version != null) {
System.out.println(" Data version: " + version);
}
if (link.hasReadableData()) {
System.out.println(" Has readable JSON data");
} else if (link.getData() != null) {
System.out.println(" Has encrypted/binary data");
} else {
System.out.println(" No data");
}
}
}
}
}メソッド完全リファレンス
getRecordUid()
String
対象レコードのUID
getPath()
String
リンクメタデータのタイプ
getData()
String
Base64エンコードされた生データ
isAdminUser()
boolean
ユーザーが管理者権限を持つか
isLaunchCredential()
boolean
起動用認証情報かどうか
allowsRotation()
boolean
パスワードローテーションが許可されているか
allowsConnections()
boolean
接続が許可されているか
allowsPortForwards()
boolean
ポートフォワーディングが許可されているか
allowsSessionRecording()
boolean
セッション録画が有効か
allowsTypescriptRecording()
boolean
Typescript録画が有効か
allowsRemoteBrowserIsolation()
boolean
リモートブラウザ分離が許可されているか
rotatesOnTermination()
boolean
セッション終了時にパスワードがローテーションされるか
getLinkDataVersion()
Integer
データ形式のバージョン番号
hasReadableData()
boolean
データが可読なJSON形式か
hasEncryptedData()
boolean
データが暗号化されているか
mightBeEncrypted()
boolean
データが暗号化されている可能性があるか (ヒューリスティック検出)
getDecryptedData(byte[])
String
レコードキーを使ってデータを復号
getDecodedData()
String
暗号化なしでBase64デコード
getLinkData(byte[])
Map<String, Object>
汎用的な暗号化データへのアクセス
getAiSettingsData(byte[])
Map<String, Object>
AI設定専用のアクセス
getJitSettingsData(byte[])
Map<String, Object>
JIT設定専用のアクセス
getSettingsForPath(String, byte[])
Map<String, Object>
パス指定による汎用的な設定アクセス
インフラ管理のためのDAG概念
有向非巡回グラフ (Directed Acyclic Graphs) の理解
GraphSyncは有向非巡回グラフ (DAG) 構造を実装しています。
有向 (DIRECTED): リンクには方向があります (A → BはB → Aと同じではありません)
非巡回 (ACYCLIC): 循環参照は存在しません (A → B → C → Aは許可されません)
グラフ (GRAPH): レコード (ノード) がリンク (エッジ) で接続されています
利点
✅ 依存関係の追跡 (「このサーバーはこのデータベースを必要とする」)
✅ 関連する認証情報の整理
✅ インフラの関係性を把握
✅ セキュリティ境界の維持
パフォーマンス最適化
効率的な処理戦略
public class OptimizedGraphOperations {
// 戦略1: 選択的な取得
public void processSpecificRecords(List<String> recordUids, SecretsManagerOptions options) throws Exception {
// 特定のレコードのみリンク付きで取得し、帯域幅を削減
QueryOptions filtered = new QueryOptions(
recordUids, // このレコードだけ
Collections.emptyList(),
true
);
KeeperSecrets secrets = SecretsManager.getSecrets2(options, filtered);
// 必要なものだけを処理
}
// 戦略2: 複数の操作に備えたキャッシュ
private Map<String, KeeperRecord> recordCache = new HashMap<>();
private Map<String, List<KeeperRecord>> typeCache = new HashMap<>();
public void initializeCache(SecretsManagerOptions options) throws Exception {
QueryOptions queryOptions = new QueryOptions(null, null, true);
KeeperSecrets secrets = SecretsManager.getSecrets2(options, queryOptions);
// 効率的な検索構造を構築
for (KeeperRecord record : secrets.getRecords()) {
recordCache.put(record.getRecordUid(), record);
typeCache.computeIfAbsent(record.getType(), k -> new ArrayList<>()).add(record);
}
}
public List<KeeperRecord> getPamMachines() {
return typeCache.getOrDefault("pamMachine", new ArrayList<>());
}
public KeeperRecord getRecord(String uid) {
return recordCache.get(uid);
}
}エラーハンドリングのベストプラクティス
public void robustLinkProcessing(SecretsManagerOptions options) {
try {
QueryOptions queryOptions = new QueryOptions(null, null, true);
KeeperSecrets secrets = SecretsManager.getSecrets2(options, queryOptions);
for (KeeperRecord record : secrets.getRecords()) {
try {
List<KeeperRecordLink> links = record.getLinks();
// nullリンクへの対応(requestLinks=false の場合)
if (links == null) {
System.err.println("Warning: Links not available for " + record.getTitle() +
" - ensure requestLinks=true in QueryOptions");
continue;
}
// 各リンクを安全に処理
for (KeeperRecordLink link : links) {
try {
// 暗号化データへ安全にアクセス
if (link.hasEncryptedData()) {
Map<String, Object> data = link.getLinkData(record.getRecordKey());
if (data != null) {
// 復号と処理に成功
System.out.println("Processed encrypted data for " + link.getRecordUid());
} else {
System.out.println("Could not decrypt data for link to " + link.getRecordUid());
}
}
// リンクのプロパティへ安全にアクセス
boolean isAdmin = link.isAdminUser();
boolean allowsRotation = link.allowsRotation();
// ... 他のプロパティ
} catch (Exception linkError) {
System.err.println("Error processing link to " + link.getRecordUid() + ": " + linkError.getMessage());
}
}
} catch (Exception recordError) {
System.err.println("Error processing record " + record.getRecordUid() + ": " + recordError.getMessage());
}
}
} catch (Exception e) {
System.err.println("Failed to retrieve records with links: " + e.getMessage());
e.printStackTrace();
}
}実装上の重要な注意点
重要な理解事項
リンクとファイルの違い:
UpdateOptionsのlinksToRemoveパラメータはレコードリンクではなくファイルを削除しますNullと空の違い:
requestLinks=falseの場合、linksフィールドはnull。requestLinks=trueでもリンクが存在しない場合は空リストになりますパフォーマンスへの影響: リンクをリクエストするとレスポンスサイズと処理時間が大幅に増加します
暗号化: リンクデータは暗号化されている可能性があり、復号にはレコードキーが必要です
セキュリティ上の考慮事項
キー管理: リンクデータを復号する際は、常にソースレコードのキーを使用すること
アクセス制御: リンクのプロパティは、どの操作が許可されているかを示します
検証: 操作を行う前に必ずリンクプロパティを確認すること
テスト実装からのベストプラクティス
必要なときのみリンクをリクエスト: パフォーマンスのために
requestLinks=trueは慎重に使用する可能な限りレコードをフィルタ:
recordsFilterを利用してデータ取得を制限する結果をキャッシュ: 同じデータに対して複数の操作を行う場合はルックアップマップを構築する
エラーを適切に処理: リンクデータの復号やアクセスは失敗する可能性がある
要件を検証: リンクプロパティが期待どおりの権限を持っているか確認する
構成をテスト: PAMのセットアップで管理者や起動用認証情報の数が正しいことを検証する
Kotlin対応
// Kotlin実装(構文を強化)
fun analyzeAdvancedGraphRelationships(options: SecretsManagerOptions) {
val queryOptions = QueryOptions(
recordsFilter = emptyList(),
foldersFilter = emptyList(),
requestLinks = true
)
val secrets = getSecrets2(options, queryOptions)
secrets.records.forEach { record ->
record.links?.forEach { link ->
println("${record.title} → ${link.recordUid}")
// Kotlinの簡潔な構文を使った高度なプロパティチェック
when {
link.isAdminUser() -> println(" Admin privileges")
link.isLaunchCredential() -> println(" Launch credential")
link.allowsRotation() -> println(" Rotation allowed")
link.allowsConnections() -> println(" Connections allowed")
link.allowsSessionRecording() -> println(" Recording enabled")
}
// セーフコールを用いた設定データアクセス
when (link.path) {
"ai_settings" -> {
link.getAiSettingsData(record.recordKey)?.let { aiData ->
println(" AI: ${aiData["aiEnabled"]} - Model: ${aiData["aiModel"]}")
}
}
"jit_settings" -> {
link.getJitSettingsData(record.recordKey)?.let { jitData ->
println(" JIT: ${jitData["enabled"]} - TTL: ${jitData["ttl"]}s")
}
}
}
// 汎用的な暗号化データアクセス
link.getLinkData(record.recordKey)?.let { data ->
data.forEach { (key, value) ->
println(" $key: $value")
}
}
}
}
}最終更新

