BYOKMapProperties.java

/*
 * GovWay - A customizable API Gateway 
 * https://govway.org
 * 
 * Copyright (c) 2005-2024 Link.it srl (https://link.it). 
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 3, as published by
 * the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 */
package org.openspcoop2.pdd.core.byok;

import java.util.HashMap;
import java.util.Map;
import java.util.Properties;

import org.apache.commons.lang.StringUtils;
import org.openspcoop2.utils.UtilsException;
import org.openspcoop2.utils.UtilsRuntimeException;
import org.openspcoop2.utils.certificate.byok.BYOKInstance;
import org.openspcoop2.utils.certificate.byok.BYOKManager;
import org.openspcoop2.utils.certificate.byok.BYOKMode;
import org.openspcoop2.utils.certificate.byok.BYOKRequestParams;
import org.openspcoop2.utils.properties.MapProperties;
import org.slf4j.Logger;

/**
 * BYOKMapProperties
 *
 * @author Poli Andrea (apoli@link.it)
 * @author $Author$
 * @version $Rev$, $Date$
 */

public class BYOKMapProperties extends MapProperties {

	public static final String FILE_NAME = "govway.secrets.properties";
	
	private static final String PROP_KSM_PREFIX = "ksm.";
	private static final String ENV_KSM_PREFIX = MapProperties.ENV_PREFIX+PROP_KSM_PREFIX;
	private static final String JAVA_KSM_PREFIX = MapProperties.JAVA_PREFIX+PROP_KSM_PREFIX;
	
	private static final String PROP_SECURITY_PREFIX = "security.";
	private static final String ENV_SECURITY_PREFIX = MapProperties.ENV_PREFIX+PROP_SECURITY_PREFIX;
	private static final String JAVA_SECURITY_PREFIX = MapProperties.JAVA_PREFIX+PROP_SECURITY_PREFIX;
	
	// default true
	private static final String PROP_WRAPPED_PREFIX = "wrapped."; 
	private static final String ENV_WRAPPED_PREFIX = MapProperties.ENV_PREFIX+PROP_WRAPPED_PREFIX;
	private static final String JAVA_WRAPPED_PREFIX = MapProperties.JAVA_PREFIX+PROP_WRAPPED_PREFIX;
	
	// default false
	private static final String PROP_UNWRAP_AFTER_GOVWAY_STARTUP_PREFIX = "govway-run-only.unwrap-after-startup."; 
	private static final String ENV_UNWRAP_AFTER_GOVWAY_STARTUP_PREFIX = MapProperties.ENV_PREFIX+PROP_UNWRAP_AFTER_GOVWAY_STARTUP_PREFIX;
	private static final String JAVA_UNWRAP_AFTER_GOVWAY_STARTUP_PREFIX = MapProperties.JAVA_PREFIX+PROP_UNWRAP_AFTER_GOVWAY_STARTUP_PREFIX;
	
	private static final String UNWRAP_DEFAULT_MODE = "unwrap.default.mode";
	private static final String UNWRAP_DEFAULT_ID = "unwrap.default.id";
	private static final String UNWRAP_MODE_SECURITY = "security"; 
	private static final String UNWRAP_MODE_KSM = "ksm"; 
	
	// ksm.<id>.param.<paramName>=<paramValue>
	private static final String KSM_PREFIX = "ksm.";
	private static final String KSM_PARAM_PREFIX = ".param.";
	private static String getKsmParamPrefixPropertyName(String ksmId) {
		return KSM_PREFIX+ksmId+KSM_PARAM_PREFIX;
	}
		
	private static final String ERROR_SUFFIX_UNKNOW = "' unknown";
	
	private static final String ERROR_DEFAULT_MODE_NOT_FOUND = ") an unwrap mode has not been defined (security/ksm); specifying the mode is mandatory if a default unwrap mode is not defined.";
	
	private String defaultUnwrapId = null;
	private Boolean defaultUnwrapModeSecurity = null;
	
	private boolean useSecurityEngine = false;
	private String securityPolicy;
	private String securityRemotePolicy;
	
	private Map<String, DriverBYOK> mapDriverSecurity = new HashMap<>();
	
	private Map<String, Map<String, String>> mapKsmInput = new HashMap<>();
	
	private Map<String, Object> dynamicMap = null;
	private boolean checkJmxPrefixOperazioneNonRiuscita = false;
	
	private boolean existsUnwrapPropertiesAfterGovWayStartup = false;
	public boolean isExistsUnwrapPropertiesAfterGovWayStartup() {
		return this.existsUnwrapPropertiesAfterGovWayStartup;
	}
	private boolean isGovWayStarted = false; // usato per caricare variabili che devono essere trattate solo sui nodi run
	public boolean isGovWayStarted() {
		return this.isGovWayStarted;
	}
	public void setGovWayStarted(boolean isGovWayStarted) {
		this.isGovWayStarted = isGovWayStarted;
	}

	/** Copia Statica */
	private static BYOKMapProperties secretsProperties = null;
	
	/**private BYOKMapProperties(Logger log, boolean throwNotFound,
			boolean useSecurityEngine,
			Map<String, Object> dynamicMapParam, boolean checkJmxPrefixOperazioneNonRiuscita) throws UtilsException {
		super(log, FILE_NAME, throwNotFound);
		init(useSecurityEngine,
				dynamicMapParam, checkJmxPrefixOperazioneNonRiuscita);
	}*/
	private BYOKMapProperties(Logger log, String fileName, boolean throwNotFound, 
			boolean useSecurityEngine,
			Map<String, Object> dynamicMapParam, boolean checkJmxPrefixOperazioneNonRiuscita) throws UtilsException {
		super(log, fileName, throwNotFound);
		init(useSecurityEngine,
				dynamicMapParam, checkJmxPrefixOperazioneNonRiuscita);
	}
	private void init(boolean useSecurityEngine,
			Map<String, Object> dynamicMapParam, boolean checkJmxPrefixOperazioneNonRiuscita) {
		String securityPolicyB = BYOKManager.getSecurityEngineGovWayPolicy();
		String securityRemotePolicyB = BYOKManager.getSecurityRemoteEngineGovWayPolicy();
		if(securityPolicyB!=null && StringUtils.isNotEmpty(securityPolicyB)) {
			this.securityPolicy = securityPolicyB;
		}
		if(securityRemotePolicyB!=null && StringUtils.isNotEmpty(securityRemotePolicyB)) {
			this.securityRemotePolicy = securityRemotePolicyB;
		}
		this.useSecurityEngine = useSecurityEngine;
		this.dynamicMap = dynamicMapParam;
		this.checkJmxPrefixOperazioneNonRiuscita = checkJmxPrefixOperazioneNonRiuscita;
	}
	
	/**
	 * Il Metodo si occupa di inizializzare il propertiesReader 
	 *
	 * 
	 */
	public static boolean initialize(Logger log, String fileName, boolean throwNotFound,  
			boolean useSecurityEngine,
			Map<String, Object> dynamicMapParam, boolean checkJmxPrefixOperazioneNonRiuscita){

		try {
			BYOKMapProperties.secretsProperties = new BYOKMapProperties(log, fileName, throwNotFound, 
					useSecurityEngine, 
					dynamicMapParam, checkJmxPrefixOperazioneNonRiuscita);	
		    return true;
		}
		catch(Exception e) {
		    return false;
		}
	}
    
	/**
	 * Ritorna l'istanza di questa classe
	 *
	 * @return Istanza di BYOKMapProperties
	 * 
	 */
	public static BYOKMapProperties getInstance(){
		// spotbugs warning 'SING_SINGLETON_GETTER_NOT_SYNCHRONIZED': l'istanza viene creata allo startup
		if (BYOKMapProperties.secretsProperties == null) {
			synchronized (BYOKMapProperties.class) {
				if (BYOKMapProperties.secretsProperties == null) {
					return null;
				}
			}
		}
		return BYOKMapProperties.secretsProperties;
	}

	@Override
	public void loadPropertyInEnvironment(String key) throws UtilsException {
		
		this.loadDefaultUnwrap();
		
		if(key.startsWith(ENV_PREFIX) && key.length()>ENV_PREFIX.length() &&
				!key.startsWith(ENV_KSM_PREFIX) &&
				!key.startsWith(ENV_SECURITY_PREFIX) &&
				!key.startsWith(ENV_WRAPPED_PREFIX) &&
				!key.startsWith(ENV_UNWRAP_AFTER_GOVWAY_STARTUP_PREFIX)) {
			loadEnvPropertyInEnvironment(key);
		}
		else if(key.startsWith(JAVA_PREFIX) && key.length()>JAVA_PREFIX.length() &&
				!key.startsWith(JAVA_KSM_PREFIX) &&
				!key.startsWith(JAVA_SECURITY_PREFIX) &&
				!key.startsWith(JAVA_WRAPPED_PREFIX) &&
				!key.startsWith(JAVA_UNWRAP_AFTER_GOVWAY_STARTUP_PREFIX)) {
			loadJavaPropertyInEnvironment(key);
		}
	}
	public void loadEnvPropertyInEnvironment(String key) throws UtilsException {
		String envKey = key.substring(ENV_PREFIX.length());
		String value = this.reader.getValue_convertEnvProperties(key);
		
		if(this.isUnwrapAfterGovWayStartup(false,envKey)){
			this.existsUnwrapPropertiesAfterGovWayStartup = true;
			if(!this.isGovWayStarted) {
				return; // verrĂ  effettuato l'unwrap dopo che GovWay ha completato lo startup
			}
		}
		
		if(this.isWrapped(false,envKey)) {
			processWrappedValue(key, envKey, value, 
					false); // envProperty
		}
		else {
			setEnvProperty(envKey, value);
		}
	}
	public void loadJavaPropertyInEnvironment(String key) throws UtilsException {
		String envKey = key.substring(JAVA_PREFIX.length());
		String value = this.reader.getValue_convertEnvProperties(key);
		
		if(this.isUnwrapAfterGovWayStartup(true,envKey)) {
			this.existsUnwrapPropertiesAfterGovWayStartup = true;
			if(!this.isGovWayStarted) {
				return; // verrĂ  effettuato l'unwrap dopo che GovWay ha completato lo startup
			}
		}
		
		if(this.isWrapped(true,envKey)) {
			processWrappedValue(key, envKey, value, 
					true); // envProperty
		}
		else {
			setJavaProperty(envKey, value);
		}
	}
	private void processWrappedValue(String key, String envKey, String value, boolean javaProperty) throws UtilsException {
		String plainValue = null;
		String securityId = this.getUnwrapId(javaProperty, envKey);
		try {
			if(this.isWrappedBySecurity(javaProperty, envKey)) {
				String p = this.securityRemotePolicy!=null ? this.securityRemotePolicy : this.securityPolicy;
				if(!this.useSecurityEngine && securityId!=null && securityId.equals(p)) {
					// securityNonInizializzato, skip
					return;
				}
				plainValue = getDriverBYOK(securityId).unwrapAsString(value, securityId, true);
			}
			else {
				plainValue = unwrapByKsmId(value, securityId);
			}
		}catch(Exception e) {
			throw new UtilsException("["+key+"] "+e.getMessage(),e);
		}
		if(plainValue==null) {
			throw new UtilsException("("+key+") unwrapped value null");
		}
		else {
			if(javaProperty) {
				setJavaProperty(envKey, plainValue);
			}
			else{
				setEnvProperty(envKey, plainValue);
			}
		}
	}
	
	private void loadDefaultUnwrap() throws UtilsException {
		String tmp = this.reader.getValue_convertEnvProperties(UNWRAP_DEFAULT_ID);
		if(tmp!=null) {
			this.defaultUnwrapId = tmp.trim();
			if(StringUtils.isEmpty(this.defaultUnwrapId)) {
				this.defaultUnwrapId = null;
			}
		}
		
		if(this.defaultUnwrapId!=null) {
			tmp = this.reader.getValue_convertEnvProperties(UNWRAP_DEFAULT_MODE);
			if(tmp!=null && StringUtils.isNotEmpty(tmp.trim())) {
				if(UNWRAP_MODE_SECURITY.equals(tmp.trim())) {
					this.defaultUnwrapModeSecurity = true; 
				}
				else if(UNWRAP_MODE_KSM.equals(tmp.trim())) {
					this.defaultUnwrapModeSecurity = false; 
				}
				else {
					throw new UtilsException(UNWRAP_DEFAULT_MODE +" '"+tmp.trim()+ERROR_SUFFIX_UNKNOW);
				}
			}
			else {
				this.defaultUnwrapModeSecurity = true; // default by security id
			}
		}
	}
	
	private boolean isWrapped(boolean javaProperty, String key) throws UtilsException {
		if(this.reader==null) {
			return false; // non inizializzato
		}
		String prefix = javaProperty ? JAVA_WRAPPED_PREFIX : ENV_WRAPPED_PREFIX;
		String tmp = this.reader.getValue_convertEnvProperties(prefix+key);
		if(tmp!=null && StringUtils.isNotEmpty(tmp.trim())) {
			if("true".equals(tmp.trim())) {
				return true;
			}
			else if("false".equals(tmp.trim())) {
				return false;
			}
			else {
				throw new UtilsException(prefix+key+" with value '"+tmp.trim()+ERROR_SUFFIX_UNKNOW);
			}
		}
		else {
			return true; // default
		}
	}
	
	private boolean isUnwrapAfterGovWayStartup(boolean javaProperty, String key) throws UtilsException {
		if(this.reader==null) {
			return false; // non inizializzato
		}
		String prefix = javaProperty ? JAVA_UNWRAP_AFTER_GOVWAY_STARTUP_PREFIX : ENV_UNWRAP_AFTER_GOVWAY_STARTUP_PREFIX;
		String tmp = this.reader.getValue_convertEnvProperties(prefix+key);
		if(tmp!=null && StringUtils.isNotEmpty(tmp.trim())) {
			if("true".equals(tmp.trim())) {
				return true;
			}
			else if("false".equals(tmp.trim())) {
				return false;
			}
			else {
				throw new UtilsException(prefix+key+" with value '"+tmp.trim()+ERROR_SUFFIX_UNKNOW);
			}
		}
		else {
			return false; // default
		}
	}
	
	@Override
	protected boolean obfuscate(boolean java, String key) {
		try {
			return isWrapped(java, key);
		}catch(Exception e) {
			return true;
		}
	}
	
	private boolean isWrappedBySecurity(boolean javaProperty, String key) throws UtilsException {
		if(this.reader==null) {
			return false; // non inizializzato
		}
		String pSecurityName = javaProperty ? JAVA_SECURITY_PREFIX : ENV_SECURITY_PREFIX;
		String tmp = this.reader.getValue_convertEnvProperties(pSecurityName+key);
		if(tmp!=null && StringUtils.isNotEmpty(tmp.trim())) {
			return true;
		}
		else {
			String pKsmName = javaProperty ? JAVA_KSM_PREFIX : ENV_KSM_PREFIX;
			tmp = this.reader.getValue_convertEnvProperties(pKsmName+key);
			if(tmp!=null && StringUtils.isNotEmpty(tmp.trim())) {
				return false;
			}
			else if(this.defaultUnwrapModeSecurity!=null) {
				return this.defaultUnwrapModeSecurity.booleanValue();
			}
			else {
				String prefix = javaProperty ? JAVA_PREFIX : ENV_PREFIX;
				throw new UtilsException("("+prefix+key+ERROR_DEFAULT_MODE_NOT_FOUND);
			}
		}
	}
	
	private String getUnwrapId(boolean javaProperty, String key) throws UtilsException {
		if(this.reader==null) {
			return null; // non inizializzato
		}
		String pSecurityName = javaProperty ? JAVA_SECURITY_PREFIX : ENV_SECURITY_PREFIX;
		String tmp = this.reader.getValue_convertEnvProperties(pSecurityName+key);
		if(tmp!=null && StringUtils.isNotEmpty(tmp.trim())) {
			return tmp.trim();
		}
		else {
			String pKsmName = javaProperty ? JAVA_KSM_PREFIX : ENV_KSM_PREFIX;
			tmp = this.reader.getValue_convertEnvProperties(pKsmName+key);
			if(tmp!=null && StringUtils.isNotEmpty(tmp.trim())) {
				return tmp.trim();
			}
			else if(this.defaultUnwrapId!=null) {
				return this.defaultUnwrapId;
			}
			else {
				String prefix = javaProperty ? JAVA_PREFIX : ENV_PREFIX;
				throw new UtilsException("("+prefix+key+ERROR_DEFAULT_MODE_NOT_FOUND);
			}
		}
	}
	
	private DriverBYOK getDriverBYOK(String securityId) {
		if(!this.mapDriverSecurity.containsKey(securityId)) {
			initDriverBYOK(securityId);
		}
		return this.mapDriverSecurity.get(securityId);
	}
	private synchronized void initDriverBYOK(String securityId) {
		this.mapDriverSecurity.computeIfAbsent(securityId, k -> new DriverBYOK(this.log, this.securityPolicy, this.securityRemotePolicy,
				newDynamicMap(), this.checkJmxPrefixOperazioneNonRiuscita));
	}

	private Map<String, Object> newDynamicMap(){
		if(this.dynamicMap==null) {
			initDynamicMap();
		}
		// cro una nuova mappa per non sovrascrivere le varie variabili di input
		Map<String, Object> map = new HashMap<>();
		if(this.dynamicMap!=null && !this.dynamicMap.isEmpty()) {
			map.putAll(this.dynamicMap);
		}
		return map;
	}
	private synchronized void initDynamicMap() {
		if(this.dynamicMap==null) {
			this.dynamicMap = DriverBYOK.buildDynamicMap(this.log);
		}
	}
	
	private Map<String, String> readKsmInputMap(String ksmId) throws UtilsException{
		Map<String, String> map = new HashMap<>();
		Properties p = this.reader.readProperties_convertEnvProperties(getKsmParamPrefixPropertyName(ksmId));
		if(p!=null && !p.isEmpty()) {
			for (Map.Entry<Object,Object> entry : p.entrySet()) {
				if(entry.getKey() instanceof String && entry.getValue() instanceof String) {
					map.put((String)entry.getKey(), (String)entry.getValue());
				}
			}
		}
		return map;
	}
	private Map<String, String> getKsmInputMap(String ksmId) {
		if(!this.mapKsmInput.containsKey(ksmId)) {
			initKsmInputMap(ksmId);
		}
		return this.mapKsmInput.get(ksmId);
	}
	private synchronized void initKsmInputMap(String ksmId) {
		this.mapKsmInput.computeIfAbsent(ksmId, k -> {
			try {
				return readKsmInputMap(ksmId);
			} catch (UtilsException e) {
				throw new UtilsRuntimeException(e.getMessage(),e);
			}
		});
	}
	
	private String unwrapByKsmId(String value, String ksmId) throws UtilsException {
		
		Map<String, String> inputMap = getKsmInputMap(ksmId);
		BYOKRequestParams params = DriverBYOK.getBYOKRequestParamsByKsmId(ksmId, inputMap, newDynamicMap());
		if(!BYOKMode.UNWRAP.equals(params.getConfig().getMode())) {
			throw new UtilsException("Ksm '"+ksmId+"' unusable in unwrap operation");
		}
		BYOKInstance instance = BYOKInstance.newInstance(this.log, params, value.getBytes());
		byte[] unwrappedValue = DriverBYOK.processInstance(instance, this.checkJmxPrefixOperazioneNonRiuscita);
		return new String(unwrappedValue);

	}
}