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}