001package com.streamconverter.config; 002 003import java.io.IOException; 004import java.io.InputStream; 005import java.util.Properties; 006import org.slf4j.Logger; 007import org.slf4j.LoggerFactory; 008 009/** 010 * 本番環境向けセキュリティ設定管理クラス 011 * 012 * <p>このクラスは、本番環境での安全な運用を支援するため、 セキュリティ関連の設定値を一元管理します。 013 * 014 * <p>主な機能: 015 * 016 * <ul> 017 * <li>XML外部エンティティ攻撃防止設定の管理 018 * <li>XPathインジェクション防止設定の管理 019 * <li>パストラバーサル攻撃防止設定の管理 020 * <li>入力値検証設定の管理 021 * <li>監査ログ設定の管理 022 * </ul> 023 * 024 * @since 1.0.0 025 */ 026public class SecurityConfigurationManager { 027 028 private static final Logger logger = LoggerFactory.getLogger(SecurityConfigurationManager.class); 029 private static final Logger securityLogger = 030 LoggerFactory.getLogger("com.streamConverter.security"); 031 032 private static SecurityConfigurationManager instance; 033 private final Properties securityConfig; 034 private final String activeProfile; 035 036 private SecurityConfigurationManager() { 037 this.activeProfile = determineActiveProfile(); 038 this.securityConfig = loadSecurityConfiguration(); 039 logSecurityConfigurationStatus(); 040 } 041 042 /** 043 * SecurityConfigurationManagerのシングルトンインスタンスを取得します 044 * 045 * @return SecurityConfigurationManagerインスタンス 046 */ 047 public static synchronized SecurityConfigurationManager getInstance() { 048 if (instance == null) { 049 instance = new SecurityConfigurationManager(); 050 } 051 return instance; 052 } 053 054 /** 055 * アクティブなプロファイルを取得します 056 * 057 * @return アクティブプロファイル名 058 */ 059 public String getActiveProfile() { 060 return activeProfile; 061 } 062 063 /** 064 * 本番環境かどうかを判定します 065 * 066 * @return 本番環境の場合true、そうでない場合false 067 */ 068 public boolean isProductionEnvironment() { 069 return "prod".equalsIgnoreCase(activeProfile) || "production".equalsIgnoreCase(activeProfile); 070 } 071 072 // =========================================== 073 // XML Security Settings 074 // =========================================== 075 076 /** 077 * XML外部エンティティの無効化設定を取得します 078 * 079 * @return XML外部エンティティを無効化する場合true 080 */ 081 public boolean isXmlExternalEntitiesDisabled() { 082 return getBooleanProperty("security.xml.disable-external-entities", true); 083 } 084 085 /** 086 * XMLドキュメントタイプ宣言の無効化設定を取得します 087 * 088 * @return ドキュメントタイプ宣言を無効化する場合true 089 */ 090 public boolean isXmlDoctypeDeclarationsDisabled() { 091 return getBooleanProperty("security.xml.disable-doctype-declarations", true); 092 } 093 094 /** 095 * 外部DTD読み込みの無効化設定を取得します 096 * 097 * @return 外部DTD読み込みを無効化する場合true 098 */ 099 public boolean isLoadExternalDtdDisabled() { 100 return getBooleanProperty("security.xml.load-external-dtd", false); 101 } 102 103 // =========================================== 104 // TreePath Security Settings 105 // =========================================== 106 107 /** 108 * XPath検証の有効化設定を取得します 109 * 110 * @return XPath検証を有効化する場合true 111 */ 112 public boolean isXPathValidationEnabled() { 113 return getBooleanProperty("security.xpath.validation.enabled", true); 114 } 115 116 /** 117 * XPath厳格モードの設定を取得します 118 * 119 * @return XPath厳格モードが有効な場合true 120 */ 121 public boolean isXPathStrictModeEnabled() { 122 return getBooleanProperty("security.xpath.strict-mode", true); 123 } 124 125 // =========================================== 126 // Path Traversal Prevention 127 // =========================================== 128 129 /** 130 * パストラバーサル防止の設定を取得します 131 * 132 * @return パストラバーサル防止が有効な場合true 133 */ 134 public boolean isPathTraversalPreventionEnabled() { 135 return getBooleanProperty("security.path-traversal.prevention", true); 136 } 137 138 /** 139 * 親ディレクトリ参照許可の設定を取得します 140 * 141 * @return 親ディレクトリ参照を許可する場合true 142 */ 143 public boolean isParentReferencesAllowed() { 144 return getBooleanProperty("security.path-traversal.allow-parent-references", false); 145 } 146 147 /** 148 * ファイルアクセスのワークスペース制限設定を取得します 149 * 150 * @return ワークスペースへの制限が有効な場合true 151 */ 152 public boolean isFileAccessRestrictedToWorkspace() { 153 return getBooleanProperty("security.file-access.restrict-to-workspace", true); 154 } 155 156 // =========================================== 157 // Input Validation Settings 158 // =========================================== 159 160 /** 161 * 最大ファイルサイズ制限を取得します 162 * 163 * @return 最大ファイルサイズ(バイト) 164 */ 165 public long getMaxFileSize() { 166 String maxSizeStr = getStringProperty("security.input.max-file-size", "100MB"); 167 return parseFileSize(maxSizeStr); 168 } 169 170 /** 171 * 許可されるファイル拡張子リストを取得します 172 * 173 * @return 許可される拡張子の配列 174 */ 175 public String[] getAllowedFileExtensions() { 176 String extensions = 177 getStringProperty("security.input.allowed-file-extensions", ".json,.xml,.csv,.txt"); 178 return extensions.split(","); 179 } 180 181 /** 182 * 文字エンコーディング検証の有効化設定を取得します 183 * 184 * @return エンコーディング検証が有効な場合true 185 */ 186 public boolean isEncodingValidationEnabled() { 187 return getBooleanProperty("security.input.validate-encoding", true); 188 } 189 190 // =========================================== 191 // Audit and Monitoring Settings 192 // =========================================== 193 194 /** 195 * 監査ログの有効化設定を取得します 196 * 197 * @return 監査ログが有効な場合true 198 */ 199 public boolean isAuditEnabled() { 200 return getBooleanProperty("audit.enabled", isProductionEnvironment()); 201 } 202 203 /** 204 * セキュリティログの有効化設定を取得します 205 * 206 * @return セキュリティログが有効な場合true 207 */ 208 public boolean isSecurityLoggingEnabled() { 209 return getBooleanProperty("logging.security.enabled", isProductionEnvironment()); 210 } 211 212 /** 213 * パフォーマンス監視の有効化設定を取得します 214 * 215 * @return パフォーマンス監視が有効な場合true 216 */ 217 public boolean isPerformanceMonitoringEnabled() { 218 return getBooleanProperty("monitoring.enabled", true); 219 } 220 221 // =========================================== 222 // Private Helper Methods 223 // =========================================== 224 225 private String determineActiveProfile() { 226 // 環境変数から取得 227 String profile = System.getenv("STREAMCONVERTER_PROFILE"); 228 if (profile != null && !profile.trim().isEmpty()) { 229 return profile.trim(); 230 } 231 232 // システムプロパティから取得 233 profile = System.getProperty("streamconverter.profiles.active"); 234 if (profile != null && !profile.trim().isEmpty()) { 235 return profile.trim(); 236 } 237 238 // デフォルトは development 239 return "dev"; 240 } 241 242 private Properties loadSecurityConfiguration() { 243 Properties props = new Properties(); 244 245 // デフォルト設定を読み込み 246 loadPropertiesFile(props, "application.properties"); 247 248 // プロファイル固有の設定を読み込み(存在する場合) 249 if (activeProfile != null && !activeProfile.isEmpty()) { 250 loadPropertiesFile(props, "application-" + activeProfile + ".properties"); 251 } 252 253 return props; 254 } 255 256 private void loadPropertiesFile(Properties props, String filename) { 257 try (InputStream input = getClass().getClassLoader().getResourceAsStream(filename)) { 258 if (input != null) { 259 props.load(input); 260 logger.debug("Loaded security configuration from: {}", filename); 261 } else { 262 logger.debug("Configuration file not found: {}", filename); 263 } 264 } catch (IOException e) { 265 logger.warn("Failed to load configuration file: {}", filename, e); 266 } 267 } 268 269 private boolean getBooleanProperty(String key, boolean defaultValue) { 270 String value = securityConfig.getProperty(key); 271 if (value != null) { 272 return Boolean.parseBoolean(value.trim()); 273 } 274 return defaultValue; 275 } 276 277 private String getStringProperty(String key, String defaultValue) { 278 return securityConfig.getProperty(key, defaultValue); 279 } 280 281 private long parseFileSize(String sizeStr) { 282 if (sizeStr == null || sizeStr.trim().isEmpty()) { 283 return 100 * 1024 * 1024; // Default 100MB 284 } 285 286 sizeStr = sizeStr.trim().toUpperCase(); 287 long multiplier = 1; 288 289 if (sizeStr.endsWith("KB")) { 290 multiplier = 1024; 291 sizeStr = sizeStr.substring(0, sizeStr.length() - 2); 292 } else if (sizeStr.endsWith("MB")) { 293 multiplier = 1024 * 1024; 294 sizeStr = sizeStr.substring(0, sizeStr.length() - 2); 295 } else if (sizeStr.endsWith("GB")) { 296 multiplier = 1024 * 1024 * 1024; 297 sizeStr = sizeStr.substring(0, sizeStr.length() - 2); 298 } 299 300 try { 301 return Long.parseLong(sizeStr.trim()) * multiplier; 302 } catch (NumberFormatException e) { 303 logger.warn("Invalid file size format: {}, using default 100MB", sizeStr); 304 return 100 * 1024 * 1024; // Default 100MB 305 } 306 } 307 308 private void logSecurityConfigurationStatus() { 309 if (isSecurityLoggingEnabled()) { 310 securityLogger.info( 311 "Security Configuration Manager initialized for profile: {}", activeProfile); 312 securityLogger.info("Production environment: {}", isProductionEnvironment()); 313 securityLogger.info("XML external entities disabled: {}", isXmlExternalEntitiesDisabled()); 314 securityLogger.info("TreePath validation enabled: {}", isXPathValidationEnabled()); 315 securityLogger.info( 316 "Path traversal prevention enabled: {}", isPathTraversalPreventionEnabled()); 317 securityLogger.info("Audit logging enabled: {}", isAuditEnabled()); 318 } 319 } 320}