bug4811: interims commit

This commit is contained in:
Axel Uhl committed 2021-01-13 01:22:55 +01:00
1 parent 00cf53fba6
commit a43dc46bb2
36 files changed
+683 -282

No files matched your search

@@ -36,84 +36,82 @@
<booleanAttribute key="org.eclipse.jdt.debug.ui.INCLUDE_EXTERNAL_JARS" value="true"/>
<booleanAttribute key="org.eclipse.jdt.launching.ATTR_USE_CLASSPATH_ONLY_JAR" value="false"/>
<listAttribute key="org.eclipse.jdt.launching.CLASSPATH">
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry containerPath=&quot;org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8&quot; javaProject=&quot;com.sap.sailing.gwt.ui&quot; path=&quot;1&quot; type=&quot;4&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.gwt.ui/src/main/resources&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.gwt.ui/src/main/java&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.domain/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.googlecode.java-diff-utils/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.domain.shared.android/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.domain.common/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sse.common/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sse.datamining.annotations/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sse.security.common/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sse.shared.android/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/org.json.simple/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.geocoding/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sse.datamining.shared/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sse/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.declination/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sse.datamining.ui/src/main/resources&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.declination/resources&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.server/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.server/resources&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.expeditionconnector/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.udpconnector/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.server.gateway.serialization.shared.android/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.expeditionconnector.common/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sse.gwt/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sse.gwt/resources&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sse.filestorage/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sse.filestorage/resources&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sse.mongodb/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sse.replication/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sse.operationaltransformation/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.expeditionconnector.persistence/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.domain.persistence/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.server.gateway.serialization/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.simulator/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.simulator/resources&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.domain.tractracadapter/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.tractrac.clientmodule/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sse.security/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sse.security/resources&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sse.mail/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.domain.swisstimingadapter/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.xrr.resultimport/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.resultimport/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.competitorimport/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.xrr.schema/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.domain.swisstimingadapter.persistence/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.domain.tractracadapter.persistence/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/org.moxieapps.gwt.highcharts/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.freg.resultimport/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.domain.swisstimingreplayadapter/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.domain.igtimiadapter/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.domain.igtimiadapter.persistence/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.datamining.shared/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.datamining/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.datamining/resources&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sse.datamining/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sse.datamining/resources&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.datamining.provider/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.domain.racelogtrackingadapter/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.domain.racelogtrackingadapter/resources&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.manage2sail/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.manage2sail/resources&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.xrr.structureimport/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sse.security.ui/src/main/java&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sse.security.ui/src/main/resources&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sse.gwt.adminconsole/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.news/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.polars.datamining.shared/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.polars.datamining/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.polars.datamining/resources&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry id=&quot;org.eclipse.jdt.launching.classpathentry.defaultClasspath&quot;&gt;&#13;&#10;&lt;memento exportedEntriesOnly=&quot;false&quot; project=&quot;com.sap.sailing.gwt.ui&quot;/&gt;&#13;&#10;&lt;/runtimeClasspathEntry&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry path=&quot;3&quot; projectName=&quot;com.sap.sse.common&quot; type=&quot;1&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sse.datamining.ui/src/main/java&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.github.branflake2267.gwt-maps-api/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.google.gwt.ajaxloader/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.landscape.common/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.landscape.ui/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#10;&lt;runtimeClasspathEntry containerPath=&quot;org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8&quot; javaProject=&quot;com.sap.sailing.gwt.ui&quot; path=&quot;1&quot; type=&quot;4&quot;/&gt;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.gwt.ui/src/main/resources&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.gwt.ui/src/main/java&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.domain/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.googlecode.java-diff-utils/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.domain.shared.android/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.domain.common/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sse.common/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sse.datamining.annotations/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sse.security.common/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sse.shared.android/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/org.json.simple/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.geocoding/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sse.datamining.shared/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sse/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.declination/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sse.datamining.ui/src/main/resources&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.declination/resources&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.server/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.server/resources&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.expeditionconnector/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.udpconnector/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.server.gateway.serialization.shared.android/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.expeditionconnector.common/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sse.gwt/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sse.gwt/resources&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sse.filestorage/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sse.filestorage/resources&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sse.mongodb/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sse.replication/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sse.operationaltransformation/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.expeditionconnector.persistence/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.domain.persistence/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.server.gateway.serialization/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.simulator/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.simulator/resources&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.domain.tractracadapter/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.tractrac.clientmodule/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sse.security/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sse.security/resources&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sse.mail/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.domain.swisstimingadapter/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.xrr.resultimport/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.resultimport/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.competitorimport/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.xrr.schema/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.domain.swisstimingadapter.persistence/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.domain.tractracadapter.persistence/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/org.moxieapps.gwt.highcharts/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.freg.resultimport/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.domain.swisstimingreplayadapter/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.domain.igtimiadapter/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.domain.igtimiadapter.persistence/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.datamining.shared/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.datamining/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.datamining/resources&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sse.datamining/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sse.datamining/resources&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.datamining.provider/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.domain.racelogtrackingadapter/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.domain.racelogtrackingadapter/resources&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.manage2sail/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.manage2sail/resources&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.xrr.structureimport/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sse.security.ui/src/main/java&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sse.security.ui/src/main/resources&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sse.gwt.adminconsole/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.news/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.polars.datamining.shared/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.polars.datamining/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.polars.datamining/resources&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#10;&lt;runtimeClasspathEntry id=&quot;org.eclipse.jdt.launching.classpathentry.defaultClasspath&quot;&gt;&#10;&lt;memento exportedEntriesOnly=&quot;false&quot; project=&quot;com.sap.sailing.gwt.ui&quot;/&gt;&#10;&lt;/runtimeClasspathEntry&gt;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#10;&lt;runtimeClasspathEntry path=&quot;3&quot; projectName=&quot;com.sap.sse.common&quot; type=&quot;1&quot;/&gt;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sse.datamining.ui/src/main/java&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.github.branflake2267.gwt-maps-api/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.google.gwt.ajaxloader/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#10;"/>
</listAttribute>
<stringAttribute key="org.eclipse.jdt.launching.CLASSPATH_PROVIDER" value="com.gwtplugins.gwt.eclipse.core.moduleClasspathProvider"/>
<booleanAttribute key="org.eclipse.jdt.launching.DEFAULT_CLASSPATH" value="false"/>
@@ -51,6 +51,7 @@ import com.sap.sse.landscape.aws.orchestration.StartMongoDBServer;
import com.sap.sse.landscape.mongodb.MongoEndpoint;
import com.sap.sse.landscape.mongodb.MongoProcess;
import com.sap.sse.landscape.mongodb.MongoReplicaSet;
import com.sap.sse.landscape.ssh.SSHKeyPair;
import com.sap.sse.landscape.ssh.SshCommandChannel;
import software.amazon.awssdk.regions.Region;
@@ -78,8 +79,15 @@ public class TestProcedures {
private String securityServiceReplicationBearerToken;
private String mailSmtpPassword;
/**
* Used for the symmetric encryption / decryption of private SSH keys. See also
* {@link #getDecryptedPrivateKey(SSHKeyPair, byte[])}.
*/
private byte[] privateKeyEncryptionPassphrase;
@Before
public void setUp() {
privateKeyEncryptionPassphrase = ("awptyf87l"+"097384sf;,57").getBytes();
landscape = AwsLandscape.obtain();
region = new AwsRegion(Region.EU_WEST_2);
securityServiceReplicationBearerToken = System.getProperty(SECURITY_SERVICE_REPLICATION_BEARER_TOKEN);
@@ -112,12 +120,13 @@ public class TestProcedures {
@Test
public void testStartupEmptyMultiServerAndDeployAnotherProcess() throws Exception {
final String keyName = "MyKey-"+UUID.randomUUID();
landscape.createKeyPair(region, keyName);
landscape.createKeyPair(region, keyName, privateKeyEncryptionPassphrase);
final StartMultiServer.Builder<?, String> builder = StartMultiServer.builder();
final String sailingAnalyticsServerTag = "sailing-analytics-server";
final StartMultiServer<String> startEmptyMultiServer = builder
.setLandscape(landscape)
.setKeyName(keyName)
.setPrivateKeyEncryptionPassphrase(privateKeyEncryptionPassphrase)
.setTags(Tags.with(sailingAnalyticsServerTag, ""))
.setOptionalTimeout(optionalTimeout)
.build();
@@ -125,7 +134,7 @@ public class TestProcedures {
// this is expected to have connected to the default "live" replica set.
startEmptyMultiServer.run();
final ApplicationProcessHost<String, SailingAnalyticsMetrics, SailingAnalyticsProcess<String>> host = startEmptyMultiServer.getHost();
final SshCommandChannel sshChannel = host.createRootSshChannel(optionalTimeout);
final SshCommandChannel sshChannel = host.createRootSshChannel(optionalTimeout, privateKeyEncryptionPassphrase);
final String result = sshChannel.runCommandAndReturnStdoutAndLogStderr("ls "+ApplicationProcessHost.DEFAULT_SERVERS_PATH, /* stderr prefix */ null, /* stderr log level */ null);
assertTrue(result.isEmpty());
final HttpURLConnection connection = (HttpURLConnection) new URL("http", host.getPublicAddress().getCanonicalHostName(), 80, "").openConnection();
@@ -140,10 +149,10 @@ public class TestProcedures {
assertEquals(new HashSet<>(Arrays.asList(SailingAnalyticsApplicationConfiguration.Builder.DEFAULT_PORT, SailingAnalyticsApplicationConfiguration.Builder.DEFAULT_PORT+1)),
new HashSet<>(Arrays.asList(processA.getPort(), processB.getPort())));
assertEquals(new HashSet<>(Arrays.asList(SailingAnalyticsApplicationConfiguration.Builder.DEFAULT_TELNET_PORT, SailingAnalyticsApplicationConfiguration.Builder.DEFAULT_TELNET_PORT+1)),
new HashSet<>(Arrays.asList(processA.getTelnetPortToOSGiConsole(optionalTimeout), processB.getTelnetPortToOSGiConsole(optionalTimeout))));
new HashSet<>(Arrays.asList(processA.getTelnetPortToOSGiConsole(optionalTimeout, privateKeyEncryptionPassphrase), processB.getTelnetPortToOSGiConsole(optionalTimeout, privateKeyEncryptionPassphrase))));
assertEquals(new HashSet<>(Arrays.asList(SailingAnalyticsApplicationConfiguration.Builder.DEFAULT_EXPEDITION_PORT, SailingAnalyticsApplicationConfiguration.Builder.DEFAULT_EXPEDITION_PORT+1)),
new HashSet<>(Arrays.asList(processA.getExpeditionUdpPort(optionalTimeout), processB.getExpeditionUdpPort(optionalTimeout))));
final SshCommandChannel curlChannel = host.createRootSshChannel(optionalTimeout);
new HashSet<>(Arrays.asList(processA.getExpeditionUdpPort(optionalTimeout, privateKeyEncryptionPassphrase), processB.getExpeditionUdpPort(optionalTimeout, privateKeyEncryptionPassphrase))));
final SshCommandChannel curlChannel = host.createRootSshChannel(optionalTimeout, privateKeyEncryptionPassphrase);
final ByteArrayOutputStream stderr = new ByteArrayOutputStream();
curlChannel.sendCommandLineSynchronously(
"curl -k -i -H \"Host: b.sapsailing.com\" \"https://127.0.0.1\"", stderr);
@@ -154,11 +163,11 @@ public class TestProcedures {
final Iterable<ApplicationReplicaSet<String, SailingAnalyticsMetrics, SailingAnalyticsProcess<String>>> replicaSets =
landscape.getApplicationReplicaSetsByTag(region, sailingAnalyticsServerTag, (theHost, dir)->{
try {
return new SailingAnalyticsProcessImpl<String>(theHost, dir, optionalTimeout);
return new SailingAnalyticsProcessImpl<String>(theHost, dir, optionalTimeout, privateKeyEncryptionPassphrase);
} catch (NumberFormatException | JSchException | IOException | InterruptedException e) {
throw new RuntimeException(e);
}
}, optionalTimeout);
}, optionalTimeout, privateKeyEncryptionPassphrase);
// expecting to find at least "a" and "b"
assertTrue(Util.containsAll(Util.map(replicaSets, ApplicationReplicaSet::getName), Arrays.asList(aServerName, bServerName)));
Util.filter(replicaSets, rs->rs.getName().equals(aServerName) || rs.getName().equals(bServerName)).forEach(rs->assertTrue(Util.isEmpty(rs.getReplicas()) && rs.getMaster() != null));
@@ -200,11 +209,12 @@ public class TestProcedures {
@Test
public void testAddMongoReplica() throws Exception {
final String keyName = "MyKey-"+UUID.randomUUID();
landscape.createKeyPair(region, keyName);
landscape.createKeyPair(region, keyName, privateKeyEncryptionPassphrase);
final StartMongoDBServer.Builder<?, String, SailingAnalyticsMetrics, SailingAnalyticsProcess<String>> builder = StartMongoDBServer.builder();
final StartMongoDBServer<String, SailingAnalyticsMetrics, SailingAnalyticsProcess<String>> startMongoDBServerProcedure = builder
.setLandscape(landscape)
.setKeyName(keyName)
.setPrivateKeyEncryptionPassphrase(privateKeyEncryptionPassphrase)
.setOptionalTimeout(optionalTimeout)
.build();
try {
@@ -228,7 +238,7 @@ public class TestProcedures {
boolean fine = true;
do {
try {
final SshCommandChannel sshChannel = mongoProcess.getHost().createSshChannel("ec2-user", optionalTimeout);
final SshCommandChannel sshChannel = mongoProcess.getHost().createSshChannel("ec2-user", optionalTimeout, privateKeyEncryptionPassphrase);
final String stdout = sshChannel.runCommandAndReturnStdoutAndLogStderr(
"i=0; while [ $i -lt $(echo \"rs.status().members.length\" | mongo 2>/dev/null | tail -n +5 | head -n +1) ]; do echo \"rs.status().members[$i].stateStr\" | mongo 2>/dev/null | tail -n +5 | head -n +1; i=$((i+1)); done",
"stderr while trying to fetch replica set members", Level.WARNING);
@@ -245,13 +255,14 @@ public class TestProcedures {
final String keyName = "MyKey-"+UUID.randomUUID();
// Comment the following line to get default eu-west-2 test environment; uncomment to upgrade current production images in eu-west-1
// final AwsRegion region = new AwsRegion(Region.EU_WEST_1);
landscape.createKeyPair(region, keyName);
landscape.createKeyPair(region, keyName, privateKeyEncryptionPassphrase);
final com.sap.sailing.landscape.procedures.UpgradeAmi.Builder<?, String, SailingAnalyticsProcess<String>> imageUpgradeProcedureBuilder = UpgradeAmi.builder();
final UpgradeAmi<String> imageUpgradeProcedure =
imageUpgradeProcedureBuilder
.setLandscape(landscape)
.setRegion(region) // only required in case a non-default region is used; see Landscape.getDefaultRegion()
.setKeyName(keyName)
.setPrivateKeyEncryptionPassphrase(privateKeyEncryptionPassphrase)
.setOptionalTimeout(optionalTimeout)
.build();
try {
@@ -276,7 +287,7 @@ public class TestProcedures {
void testConnectivity() throws Exception {
final String serverName = "test"+new Random().nextInt();
final String keyName = "MyKey-"+UUID.randomUUID();
landscape.createKeyPair(region, keyName);
landscape.createKeyPair(region, keyName, privateKeyEncryptionPassphrase);
SailingAnalyticsMasterConfiguration.Builder<AppConfigBuilderT, String> applicationConfigurationBuilder = SailingAnalyticsMasterConfiguration.masterBuilder();
applicationConfigurationBuilder
.setServerName(serverName)
@@ -292,6 +303,7 @@ public class TestProcedures {
.setRegion(region)
.setInstanceType(InstanceType.T3_LARGE)
.setKeyName(keyName)
.setPrivateKeyEncryptionPassphrase(privateKeyEncryptionPassphrase)
.setTags(Tags.with("Hello", "World"))
.setOptionalTimeout(optionalTimeout)
.build();
@@ -315,10 +327,10 @@ public class TestProcedures {
// check env.sh access
final SailingAnalyticsProcess<String> process = startSailingAnalyticsMaster.getSailingAnalyticsProcess();
assertTrue(process.waitUntilReady(optionalTimeout));
final String envSh = process.getEnvSh(optionalTimeout);
final String envSh = process.getEnvSh(optionalTimeout, privateKeyEncryptionPassphrase);
assertFalse(envSh.isEmpty());
assertTrue("Couldn't find SERVER_NAME=\""+serverName+"\" in env.sh:\n"+envSh, envSh.contains("SERVER_NAME=\""+serverName+"\""));
assertEquals(14888, process.getTelnetPortToOSGiConsole(optionalTimeout));
assertEquals(14888, process.getTelnetPortToOSGiConsole(optionalTimeout, privateKeyEncryptionPassphrase));
// Now create an ALB mapping, assuming to create the dynamic ALB:
final String domain = "wiesen-weg.de";
final String hostname = serverName+"."+domain;
@@ -9,7 +9,6 @@ import com.sap.sailing.landscape.SailingReleaseRepository;
import com.sap.sse.common.Util;
public class TestReleaseRepository {
@Test
public void testForAtLeastOneRelease() {
assertFalse(Util.isEmpty(SailingReleaseRepository.INSTANCE));
@@ -17,7 +17,8 @@ Require-Bundle: com.sap.sse.gwt,
com.sap.sailing.landscape.common,
com.sap.sailing.landscape,
com.sap.sse.landscape,
com.sap.sse.landscape.aws
com.sap.sse.landscape.aws,
com.amazon.aws.aws-java-api
Export-Package: com.sap.sailing.landscape.ui.client,
com.sap.sailing.landscape.ui.server,
com.sap.sailing.landscape.ui.shared
@@ -14,6 +14,7 @@ import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.rpc.ServiceDefTarget;
import com.google.gwt.user.client.ui.CaptionPanel;
import com.google.gwt.user.client.ui.Grid;
import com.google.gwt.user.client.ui.HorizontalPanel;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.PasswordTextBox;
import com.google.gwt.user.client.ui.TextBox;
@@ -70,8 +71,10 @@ public class LandscapeManagementPanel extends VerticalPanel {
public LandscapeManagementPanel(StringMessages stringMessages, UserService userService,
AdminConsoleTableResources tableResources, ErrorReporter errorReporter) {
landscapeManagementService = initAndRegisterLandscapeManagementService();
final HorizontalPanel awsCredentialsAndSshKeys = new HorizontalPanel();
add(awsCredentialsAndSshKeys);
final CaptionPanel awsCredentialsPanel = new CaptionPanel(stringMessages.awsCredentials());
add(awsCredentialsPanel);
awsCredentialsAndSshKeys.add(awsCredentialsPanel);
final Grid awsCredentialsGrid = new Grid(2, 2);
awsCredentialsPanel.add(awsCredentialsGrid);
awsCredentialsGrid.setWidget(0, 0, new Label(stringMessages.awsAccessKey()));
@@ -101,6 +104,10 @@ public class LandscapeManagementPanel extends VerticalPanel {
awsCredentialsGrid.setWidget(1, 0, new Label(stringMessages.awsSecret()));
awsSecretPasswordTextBox = new PasswordTextBox();
awsCredentialsGrid.setWidget(1, 1, awsSecretPasswordTextBox);
final SshKeyManagementPanel sshKeyManagementPanel = new SshKeyManagementPanel(stringMessages, userService, landscapeManagementService, tableResources, errorReporter);
final CaptionPanel sshKeysCaptionPanel = new CaptionPanel(stringMessages.sshKeys());
awsCredentialsAndSshKeys.add(sshKeysCaptionPanel);
sshKeysCaptionPanel.add(sshKeyManagementPanel);
regionsTable = new TableWrapperWithSingleSelectionAndFilter<String, StringMessages, AdminConsoleTableResources>(
stringMessages, errorReporter, /* enablePager */ false,
/* entity identity comparator */ Optional.empty(), GWT.create(AdminConsoleTableResources.class),
@@ -164,6 +171,8 @@ public class LandscapeManagementPanel extends VerticalPanel {
add(mongoEndpointsBusy);
regionsTable.getSelectionModel().addSelectionChangeHandler(e->
{
sshKeyManagementPanel.showKeysInRegion(awsAccessKeyTextBox.getValue(), awsSecretPasswordTextBox.getValue(),
regionsTable.getSelectionModel().getSelectedObject());
mongoEndpointsTable.getDataProvider().getList().clear();
if (regionsTable.getSelectionModel().getSelectedObject() != null) {
mongoEndpointsBusy.setBusy(true);
@@ -185,16 +194,17 @@ public class LandscapeManagementPanel extends VerticalPanel {
mongoEndpointsTable.getDataProvider().getList().clear();
}
});
// TODO region could be a drop-down maybe
// TODO upon region selection show MongoDB, RabbitMQ, AppServer and Central Reverse Proxy clusters, and upgradable AMIs in region
// TODO support SSH key management: show existing keys, allow user to paste an encrypted private key for storage in MongoDB; allow user to upload public key and choose for launch and login procedures
// TODO upon region selection show AppServer clusters, and upgradable AMIs in region
// TODO try to identify archive servers
// TODO support creating a new app server cluster
// TODO support AMI upgrade
// TODO support archiving and dismantling of an application server cluster
// TODO support archive server upgrade
// TODO support upgrading all app server instances in a region
// TODO region could be a drop-down maybe, and it could remember its last selection
// TODO upon region selection show RabbitMQ, and Central Reverse Proxy clusters in region
// TODO support archiving and dismantling of an application server cluster
// TODO support deploying a new app server process instance onto an existing app server host (multi-instance)
// TODO support SSH key management: show existing keys, allow user to paste an encrypted private key for storage in MongoDB; allow user to upload public key and choose for launch and login procedures
}
private LandscapeManagementWriteServiceAsync initAndRegisterLandscapeManagementService() {
@@ -4,6 +4,7 @@ import java.util.ArrayList;
import com.google.gwt.user.client.rpc.RemoteService;
import com.sap.sailing.landscape.ui.shared.MongoEndpointDTO;
import com.sap.sailing.landscape.ui.shared.SSHKeyPairDTO;
public interface LandscapeManagementWriteService extends RemoteService {
ArrayList<String> getRegions();
@@ -11,4 +12,6 @@ public interface LandscapeManagementWriteService extends RemoteService {
ArrayList<MongoEndpointDTO> getMongoEndpoints(String awsAccessKey, String awsSecret, String region);
MongoEndpointDTO getMongoEndpoint(String awsAccessKey, String awsSecret, String region, String replicaSetName);
ArrayList<SSHKeyPairDTO> getSshKeys(String awsAccessKey, String awsSecret, String regionId);
}
@@ -4,11 +4,17 @@ import java.util.ArrayList;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.sap.sailing.landscape.ui.shared.MongoEndpointDTO;
import com.sap.sailing.landscape.ui.shared.SSHKeyPairDTO;
public interface LandscapeManagementWriteServiceAsync {
void getRegions(AsyncCallback<ArrayList<String>> asyncCallback);
void getMongoEndpoints(String awsAccessKey, String awsSecret, String region, AsyncCallback<ArrayList<MongoEndpointDTO>> callback);
void getMongoEndpoints(String awsAccessKey, String awsSecret, String regionId,
AsyncCallback<ArrayList<MongoEndpointDTO>> callback);
void getMongoEndpoint(String awsAccessKey, String awsSecret, String region, String replicaSetName, AsyncCallback<MongoEndpointDTO> callback);
void getMongoEndpoint(String awsAccessKey, String awsSecret, String region, String replicaSetName,
AsyncCallback<MongoEndpointDTO> callback);
void getSshKeys(String awsAccessKey, String awsSecret, String regionId,
AsyncCallback<ArrayList<SSHKeyPairDTO>> callback);
}
@@ -0,0 +1,81 @@
package com.sap.sailing.landscape.ui.client;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Optional;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.VerticalPanel;
import com.sap.sailing.landscape.ui.client.i18n.StringMessages;
import com.sap.sailing.landscape.ui.shared.SSHKeyPairDTO;
import com.sap.sse.common.Util;
import com.sap.sse.gwt.adminconsole.AdminConsoleTableResources;
import com.sap.sse.gwt.client.ErrorReporter;
import com.sap.sse.gwt.client.celltable.EntityIdentityComparator;
import com.sap.sse.gwt.client.celltable.TableWrapperWithSingleSelectionAndFilter;
import com.sap.sse.gwt.client.controls.busyindicator.BusyIndicator;
import com.sap.sse.gwt.client.controls.busyindicator.SimpleBusyIndicator;
import com.sap.sse.security.ui.client.UserService;
public class SshKeyManagementPanel extends VerticalPanel {
private final LandscapeManagementWriteServiceAsync landscapeManagementService;
private final TableWrapperWithSingleSelectionAndFilter<SSHKeyPairDTO, StringMessages, AdminConsoleTableResources> sshKeyTable;
private final BusyIndicator sshKeyLoadingBusy;
private final ErrorReporter errorReporter;
public SshKeyManagementPanel(StringMessages stringMessages, UserService userService,
LandscapeManagementWriteServiceAsync landscapeManagementService, AdminConsoleTableResources tableResources,
ErrorReporter errorReporter) {
this.landscapeManagementService = landscapeManagementService;
this.errorReporter = errorReporter;
sshKeyTable =
new TableWrapperWithSingleSelectionAndFilter<SSHKeyPairDTO, StringMessages, AdminConsoleTableResources>(stringMessages, errorReporter, /* enablePager */ true,
Optional.of(new EntityIdentityComparator<SSHKeyPairDTO>() {
@Override
public boolean representSameEntity(SSHKeyPairDTO dto1, SSHKeyPairDTO dto2) {
return Util.equalsWithNull(dto1.getRegionId(), dto2.getRegionId()) &&
Util.equalsWithNull(dto1.getName(), dto2.getName());
}
@Override
public int hashCode(SSHKeyPairDTO t) {
return (t.getRegionId() == null ? 0 : t.getRegionId().hashCode()) ^
(t.getName() == null ? 0 : t.getName().hashCode());
}
}), tableResources, Optional.empty(), /* filter label */ Optional.empty(), /* filter checkbox label */ null) {
@Override
protected Iterable<String> getSearchableStrings(SSHKeyPairDTO t) {
return Arrays.asList(t.getName(), t.getRegionId(), t.getCreatorName());
}
};
sshKeyTable.addColumn(object->object.getRegionId(), stringMessages.region());
sshKeyTable.addColumn(object->object.getName(), stringMessages.name());
sshKeyTable.addColumn(object->object.getCreatorName(), stringMessages.creator());
sshKeyTable.addColumn(object->object.getCreationTime().toString(), stringMessages.creationTime());
add(sshKeyTable);
sshKeyLoadingBusy = new SimpleBusyIndicator();
add(sshKeyLoadingBusy);
}
public void showKeysInRegion(String awsAccessKey, String awsSecret, String regionId) {
sshKeyTable.getDataProvider().getList().clear();
if (regionId != null) {
sshKeyLoadingBusy.setBusy(true);
landscapeManagementService.getSshKeys(awsAccessKey, awsSecret, regionId, new AsyncCallback<ArrayList<SSHKeyPairDTO>>() {
@Override
public void onFailure(Throwable caught) {
errorReporter.reportError(caught.getMessage());
sshKeyLoadingBusy.setBusy(false);
}
@Override
public void onSuccess(ArrayList<SSHKeyPairDTO> sshKeyPairDTOs) {
sshKeyTable.refresh(sshKeyPairDTOs);
sshKeyLoadingBusy.setBusy(false);
}
});
} else {
sshKeyTable.getDataProvider().getList().clear();
}
}
}
@@ -17,4 +17,7 @@ com.sap.sse.gwt.adminconsole.StringMessages {
String awsCredentials();
String awsAccessKey();
String awsSecret();
String creator();
String creationTime();
String sshKeys();
}
@@ -5,4 +5,7 @@ upgrade=Upgrade
replicaSet=Replica Set
awsCredentials=AWS Credentials
awsAccessKey=AWS Access Key
awsSecret=AWS Secret
awsSecret=AWS Secret
creator=Creator
creationTime=Creation Time
sshKeys=SSH Keys
@@ -5,4 +5,7 @@ upgrade=Aktualisieren
replicaSet=Replica-Set
awsCredentials=AWS Zugangsdaten
awsAccessKey=AWS Zugangsschlüssel
awsSecret=AWS Geheimnis
awsSecret=AWS Geheimnis
creator=Ersteller
creationTime=Erstellungszeitpunkt
sshKeys=SSH-Schlüssel
@@ -3,17 +3,25 @@ package com.sap.sailing.landscape.ui.server;
import java.util.ArrayList;
import java.util.Collections;
import org.apache.shiro.SecurityUtils;
import com.sap.sailing.landscape.ui.client.LandscapeManagementWriteService;
import com.sap.sailing.landscape.ui.shared.MongoEndpointDTO;
import com.sap.sailing.landscape.ui.shared.SSHKeyPairDTO;
import com.sap.sse.common.Util;
import com.sap.sse.common.Util.Pair;
import com.sap.sse.gwt.server.ResultCachingProxiedRemoteServiceServlet;
import com.sap.sse.landscape.application.ApplicationProcessMetrics;
import com.sap.sse.landscape.aws.AwsLandscape;
import com.sap.sse.landscape.aws.impl.AwsRegion;
import com.sap.sse.landscape.mongodb.MongoEndpoint;
import com.sap.sse.landscape.mongodb.MongoProcess;
import com.sap.sse.landscape.mongodb.MongoProcessInReplicaSet;
import com.sap.sse.landscape.mongodb.MongoReplicaSet;
import com.sap.sse.landscape.ssh.SSHKeyPair;
import com.sap.sse.security.shared.impl.SecuredSecurityTypes;
import software.amazon.awssdk.services.ec2.model.KeyPairInfo;
public class LandscapeManagementWriteServiceImpl extends ResultCachingProxiedRemoteServiceServlet
implements LandscapeManagementWriteService {
@@ -24,6 +32,7 @@ public class LandscapeManagementWriteServiceImpl extends ResultCachingProxiedRem
@Override
public ArrayList<String> getRegions() {
SecurityUtils.getSubject().checkPermission(SecuredSecurityTypes.LANDSCAPE.getStringPermission(SecuredSecurityTypes.LandscapeActions.MANAGE));
final ArrayList<String> result = new ArrayList<>();
Util.addAll(Util.map(AwsLandscape.obtain().getRegions(), r->r.getId()), result);
return result;
@@ -31,6 +40,7 @@ public class LandscapeManagementWriteServiceImpl extends ResultCachingProxiedRem
@Override
public ArrayList<MongoEndpointDTO> getMongoEndpoints(String awsAccessKey, String awsSecret, String region) {
SecurityUtils.getSubject().checkPermission(SecuredSecurityTypes.LANDSCAPE.getStringPermission(SecuredSecurityTypes.LandscapeActions.MANAGE));
final ArrayList<MongoEndpointDTO> result = new ArrayList<>();
for (final MongoEndpoint mongoEndpoint : AwsLandscape.obtain(awsAccessKey, awsSecret).getMongoEndpoints(new AwsRegion(region))) {
final MongoEndpointDTO dto;
@@ -54,4 +64,18 @@ public class LandscapeManagementWriteServiceImpl extends ResultCachingProxiedRem
public MongoEndpointDTO getMongoEndpoint(String awsAccessKey, String awsSecret, String region, String replicaSetName) {
return getMongoEndpoints(awsAccessKey, awsSecret, region).stream().filter(mep->Util.equalsWithNull(mep.getReplicaSetName(), replicaSetName)).findAny().orElse(null);
}
@Override
public ArrayList<SSHKeyPairDTO> getSshKeys(String awsAccessKey, String awsSecret, String regionId) {
final ArrayList<SSHKeyPairDTO> result = new ArrayList<>();
final AwsLandscape<Object, ApplicationProcessMetrics, ?> landscape = AwsLandscape.obtain(awsAccessKey, awsSecret);
final AwsRegion region = new AwsRegion(regionId);
for (final KeyPairInfo keyPairInfo : landscape.getAllKeyPairInfos(region)) {
final SSHKeyPair key = landscape.getSSHKeyPair(region, keyPairInfo.keyName());
if (key != null) {
result.add(new SSHKeyPairDTO(key.getRegionId(), key.getName(), key.getCreatorName(), key.getCreationTime()));
}
}
return result;
}
}
@@ -0,0 +1,40 @@
package com.sap.sailing.landscape.ui.shared;
import com.google.gwt.user.client.rpc.IsSerializable;
import com.sap.sse.common.TimePoint;
public class SSHKeyPairDTO implements IsSerializable {
private String regionId;
private String name;
private String creatorName;
private TimePoint creationTime;
@Deprecated
SSHKeyPairDTO() {
// for GWT RPC serialization only
}
public SSHKeyPairDTO(String regionId, String name, String creatorName, TimePoint creationTime) {
super();
this.regionId = regionId;
this.name = name;
this.creatorName = creatorName;
this.creationTime = creationTime;
}
public String getRegionId() {
return regionId;
}
public String getName() {
return name;
}
public String getCreatorName() {
return creatorName;
}
public TimePoint getCreationTime() {
return creationTime;
}
}
@@ -13,7 +13,9 @@ public interface SailingAnalyticsProcess<ShardingKey> extends ApplicationProcess
static Logger logger = Logger.getLogger(SailingAnalyticsProcess.class.getName());
static String HEALTH_CHECK_PATH = "/gwt/status";
default int getExpeditionUdpPort(Optional<Duration> optionalTimeout) throws NumberFormatException, JSchException, IOException, InterruptedException, SftpException {
return Integer.parseInt(getEnvShValueFor(SailingAnalyticsProcessConfigurationVariable.EXPEDITION_PORT.name(), optionalTimeout));
default int getExpeditionUdpPort(Optional<Duration> optionalTimeout, byte[] privateKeyEncryptionPassphrase)
throws NumberFormatException, JSchException, IOException, InterruptedException, SftpException {
return Integer.parseInt(getEnvShValueFor(SailingAnalyticsProcessConfigurationVariable.EXPEDITION_PORT.name(),
optionalTimeout, privateKeyEncryptionPassphrase));
}
}
@@ -32,9 +32,10 @@ implements SailingAnalyticsProcess<ShardingKey> {
/**
* Tries to obtain the port from the {@code env.sh} file found in the {@code serverDirectory}
*/
public SailingAnalyticsProcessImpl(Host host, String serverDirectory, Optional<Duration> optionalTimeout)
public SailingAnalyticsProcessImpl(Host host, String serverDirectory, Optional<Duration> optionalTimeout,
byte[] privateKeyEncryptionPassphrase)
throws NumberFormatException, JSchException, IOException, InterruptedException {
super(host, serverDirectory, optionalTimeout);
super(host, serverDirectory, optionalTimeout, privateKeyEncryptionPassphrase);
}
public SailingAnalyticsProcessImpl(int port, Host host, String serverDirectory) {
@@ -60,7 +61,7 @@ implements SailingAnalyticsProcess<ShardingKey> {
* information about server name as well as availability and replication status can be obtained.
*/
@Override
public String getServerName(Optional<Duration> optionalTimeout)
public String getServerName(Optional<Duration> optionalTimeout, byte[] privateKeyEncryptionPassphrase)
throws JSchException, IOException, InterruptedException, SftpException, ParseException {
String result = null;
final TimePoint start = TimePoint.now();
@@ -45,6 +45,7 @@ implements Procedure<ShardingKey, SailingAnalyticsMetrics, SailingAnalyticsProce
private final ApplicationProcessHost<ShardingKey, SailingAnalyticsMetrics, SailingAnalyticsProcess<ShardingKey>> hostToDeployTo;
private final ApplicationConfigurationT applicationConfiguration;
private final Optional<Duration> optionalTimeout;
private byte[] privateKeyEncryptionPassphrase;
/**
* The process launched by this procedure. {@link #hostToDeployTo} is expected to be identical to
@@ -88,6 +89,7 @@ implements Procedure<ShardingKey, SailingAnalyticsMetrics, SailingAnalyticsProce
ApplicationConfigurationBuilderT extends SailingAnalyticsApplicationConfiguration.Builder<ApplicationConfigurationBuilderT, ApplicationConfigurationT, ShardingKey>>
extends com.sap.sse.landscape.orchestration.Procedure.Builder<BuilderT, DeployProcessOnMultiServer<ShardingKey, HostT, ApplicationConfigurationT, ApplicationConfigurationBuilderT>, ShardingKey, SailingAnalyticsMetrics, SailingAnalyticsProcess<ShardingKey>> {
BuilderT setHostToDeployTo(ApplicationProcessHost<ShardingKey, SailingAnalyticsMetrics, SailingAnalyticsProcess<ShardingKey>> hostToDeployTo);
BuilderT setPrivateKeyEncryptionPassphrase(byte[] privateKeyEncryptionPassphrase);
}
public static class BuilderImpl<BuilderT extends Builder<BuilderT, ShardingKey, HostT, ApplicationConfigurationT, ApplicationConfigurationBuilderT>, ShardingKey, HostT extends AwsInstance<ShardingKey, SailingAnalyticsMetrics>,
@@ -97,6 +99,7 @@ implements Procedure<ShardingKey, SailingAnalyticsMetrics, SailingAnalyticsProce
implements Builder<BuilderT, ShardingKey, HostT, ApplicationConfigurationT, ApplicationConfigurationBuilderT> {
private final SailingAnalyticsApplicationConfiguration.BuilderImpl<ApplicationConfigurationBuilderT, ApplicationConfigurationT, ShardingKey> applicationConfigurationBuilder;
private ApplicationProcessHost<ShardingKey, SailingAnalyticsMetrics, SailingAnalyticsProcess<ShardingKey>> hostToDeployTo;
private byte[] privateKeyEncryptionPassphrase;
protected BuilderImpl(SailingAnalyticsApplicationConfiguration.BuilderImpl<ApplicationConfigurationBuilderT, ApplicationConfigurationT, ShardingKey> applicationConfigurationBuilder) {
super();
@@ -110,7 +113,7 @@ implements Procedure<ShardingKey, SailingAnalyticsMetrics, SailingAnalyticsProce
if (!getApplicationConfigurationBuilder().isServerDirectorySet()) {
getApplicationConfigurationBuilder().setServerDirectory(ApplicationProcessHost.DEFAULT_SERVERS_PATH+"/"+getApplicationConfigurationBuilder().getServerName());
}
final Iterable<SailingAnalyticsProcess<ShardingKey>> applicationProcesses = getHostToDeployTo().getApplicationProcesses(getOptionalTimeout());
final Iterable<SailingAnalyticsProcess<ShardingKey>> applicationProcesses = getHostToDeployTo().getApplicationProcesses(getOptionalTimeout(), privateKeyEncryptionPassphrase);
if (!getApplicationConfigurationBuilder().isPortSet()) {
getApplicationConfigurationBuilder().setPort(getNextAvailablePort(applicationProcesses,
SailingAnalyticsApplicationConfiguration.Builder.DEFAULT_PORT,
@@ -121,7 +124,7 @@ implements Procedure<ShardingKey, SailingAnalyticsMetrics, SailingAnalyticsProce
SailingAnalyticsApplicationConfiguration.Builder.DEFAULT_TELNET_PORT,
ap->{
try {
return ap.getTelnetPortToOSGiConsole(getOptionalTimeout());
return ap.getTelnetPortToOSGiConsole(getOptionalTimeout(), privateKeyEncryptionPassphrase);
} catch (NumberFormatException | JSchException | IOException | SftpException
| InterruptedException e) {
throw new RuntimeException(e);
@@ -133,7 +136,7 @@ implements Procedure<ShardingKey, SailingAnalyticsMetrics, SailingAnalyticsProce
SailingAnalyticsApplicationConfiguration.Builder.DEFAULT_EXPEDITION_PORT,
ap->{
try {
return ap.getExpeditionUdpPort(getOptionalTimeout());
return ap.getExpeditionUdpPort(getOptionalTimeout(), privateKeyEncryptionPassphrase);
} catch (NumberFormatException | JSchException | IOException | InterruptedException
| SftpException e) {
throw new RuntimeException(e);
@@ -197,6 +200,16 @@ implements Procedure<ShardingKey, SailingAnalyticsMetrics, SailingAnalyticsProce
protected Optional<Duration> getOptionalTimeout() {
return super.getOptionalTimeout();
}
@Override
public BuilderT setPrivateKeyEncryptionPassphrase(byte[] privateKeyEncryptionPassphrase) {
this.privateKeyEncryptionPassphrase = privateKeyEncryptionPassphrase;
return self();
}
protected byte[] getPrivateKeyEncryptionPassphrase() {
return privateKeyEncryptionPassphrase;
}
}
public static <BuilderT extends Builder<BuilderT, ShardingKey, HostT, ApplicationConfigurationT, ApplicationConfigurationBuilderT>, ShardingKey, HostT extends AwsInstance<ShardingKey, SailingAnalyticsMetrics>,
@@ -213,6 +226,7 @@ implements Procedure<ShardingKey, SailingAnalyticsMetrics, SailingAnalyticsProce
super(builder);
this.hostToDeployTo = builder.getHostToDeployTo();
this.optionalTimeout = builder.getOptionalTimeout();
this.privateKeyEncryptionPassphrase = builder.getPrivateKeyEncryptionPassphrase();
this.applicationConfiguration = builder.getApplicationConfigurationBuilder().build();
assert getHostToDeployTo() != null;
}
@@ -222,7 +236,7 @@ implements Procedure<ShardingKey, SailingAnalyticsMetrics, SailingAnalyticsProce
assert getHostToDeployTo() != null;
final String serverDirectory = applicationConfiguration.getServerDirectory();
{
final SshCommandChannel sshChannel = getHostToDeployTo().createSshChannel("sailing", optionalTimeout);
final SshCommandChannel sshChannel = getHostToDeployTo().createSshChannel("sailing", optionalTimeout, privateKeyEncryptionPassphrase);
logger.info("stdout: "+sshChannel.runCommandAndReturnStdoutAndLogStderr(
"mkdir -p "+serverDirectory+"; "+
"cd "+serverDirectory+"; "+
@@ -231,7 +245,7 @@ implements Procedure<ShardingKey, SailingAnalyticsMetrics, SailingAnalyticsProce
"stderr: ", Level.WARNING));
}
{
final SshCommandChannel sshChannel = getHostToDeployTo().createRootSshChannel(optionalTimeout);
final SshCommandChannel sshChannel = getHostToDeployTo().createRootSshChannel(optionalTimeout, privateKeyEncryptionPassphrase);
logger.info("stdout: "+sshChannel.runCommandAndReturnStdoutAndLogStderr("service httpd reload", "stderr: ", Level.WARNING));
}
process = new SailingAnalyticsProcessImpl<>(applicationConfiguration.getPort(), getHostToDeployTo(), serverDirectory);
@@ -96,7 +96,7 @@ implements StartFromSailingAnalyticsImage {
result = (instanceId, az, landscape)->
new ApplicationProcessHostImpl<ShardingKey, SailingAnalyticsMetrics, SailingAnalyticsProcess<ShardingKey>>(instanceId, az, landscape, (host, serverDirectory)->{
try {
return new SailingAnalyticsProcessImpl<ShardingKey>(host, serverDirectory, getOptionalTimeout());
return new SailingAnalyticsProcessImpl<ShardingKey>(host, serverDirectory, getOptionalTimeout(), getPrivateKeyEncryptionPassphrase());
} catch (NumberFormatException | JSchException | IOException | InterruptedException e) {
throw new RuntimeException(e);
}
@@ -141,11 +141,11 @@ implements StartFromSailingAnalyticsImage {
super.run(); // this will trigger the "sailing" init.d script running in the background, triggering the image upgrade, then the httpd stop and clean-up
copyRootAuthorizedKeysToOtherUser(SAILING_USER_NAME, optionalTimeout);
final String instanceId = getHost().getInstanceId();
getHost().getReverseProxy().createInternalStatusRedirect(optionalTimeout);
getHost().getReverseProxy().createInternalStatusRedirect(optionalTimeout, getPrivateKeyEncryptionPassphrase());
boolean fileFound = false;
final TimePoint startedToWaitForImageUpgradeToFinish = TimePoint.now();
do {
final ChannelSftp sftpChannel = getHost().createRootSftpChannel(optionalTimeout);
final ChannelSftp sftpChannel = getHost().createRootSftpChannel(optionalTimeout, getPrivateKeyEncryptionPassphrase());
sftpChannel.connect();
try {
final SftpATTRS stat = sftpChannel.stat("/tmp/image-upgrade-finished");
@@ -160,7 +160,7 @@ implements StartFromSailingAnalyticsImage {
// wait until the file indicating the finishing of the image upgrade process was found
// or, if a timeout was provided, the timeout expired
} while (!fileFound && optionalTimeout.map(timeout->startedToWaitForImageUpgradeToFinish.plus(timeout).after(TimePoint.now())).orElse(true));
final SshCommandChannel sshCommandChannel = getHost().createRootSshChannel(optionalTimeout);
final SshCommandChannel sshCommandChannel = getHost().createRootSshChannel(optionalTimeout, getPrivateKeyEncryptionPassphrase());
logger.info("stdout for removing "+ApplicationProcessHost.DEFAULT_SERVER_PATH+" and starting httpd service on instance "+instanceId+": "+
sshCommandChannel.runCommandAndReturnStdoutAndLogStderr("rm -rf "+ApplicationProcessHost.DEFAULT_SERVER_PATH+"; service httpd start",
"stderr for removing "+ApplicationProcessHost.DEFAULT_SERVER_PATH+" and starting httpd service on instance \"+instanceId+\": ", Level.INFO));
@@ -93,7 +93,7 @@ implements Procedure<ShardingKey, SailingAnalyticsMetrics, SailingAnalyticsProce
new ApplicationProcessHostImpl<>(instanceId, az, landscape,
(host, serverDirectory)->{
try {
return new SailingAnalyticsProcessImpl<ShardingKey>(host, serverDirectory, getOptionalTimeout());
return new SailingAnalyticsProcessImpl<ShardingKey>(host, serverDirectory, getOptionalTimeout(), getPrivateKeyEncryptionPassphrase());
} catch (NumberFormatException | JSchException | IOException | InterruptedException e) {
throw new RuntimeException(e);
}
@@ -208,7 +208,7 @@ implements Procedure<ShardingKey, SailingAnalyticsMetrics, SailingAnalyticsProce
new ApplicationProcessHostImpl<>(instanceId, az, landscape,
(host, serverDirectory)->{
try {
return new SailingAnalyticsProcessImpl<ShardingKey>(host, serverDirectory, getOptionalTimeout());
return new SailingAnalyticsProcessImpl<ShardingKey>(host, serverDirectory, getOptionalTimeout(), getPrivateKeyEncryptionPassphrase());
} catch (NumberFormatException | JSchException | IOException | InterruptedException e) {
throw new RuntimeException(e);
}
@@ -1,11 +1,13 @@
package com.sap.sse.gwt.client.celltable;
import java.util.Comparator;
import java.util.function.Function;
import com.google.gwt.core.client.GWT;
import com.google.gwt.user.cellview.client.Column;
import com.google.gwt.user.cellview.client.ColumnSortEvent.ListHandler;
import com.google.gwt.user.cellview.client.SimplePager;
import com.google.gwt.user.cellview.client.TextColumn;
import com.google.gwt.user.client.ui.IsWidget;
import com.google.gwt.user.client.ui.VerticalPanel;
import com.google.gwt.user.client.ui.Widget;
@@ -14,6 +16,7 @@ import com.google.gwt.view.client.ListDataProvider;
import com.google.gwt.view.client.MultiSelectionModel;
import com.google.gwt.view.client.Range;
import com.sap.sse.common.Util;
import com.sap.sse.common.util.NaturalComparator;
import com.sap.sse.gwt.client.ErrorReporter;
import com.sap.sse.gwt.client.StringMessages;
import com.sap.sse.gwt.client.panels.AbstractFilterablePanel;
@@ -125,6 +128,20 @@ public abstract class TableWrapper<T, S extends RefreshableSelectionModel<T>, SM
boatColumnListHandler.setComparator(column, comparator);
addColumn(column, header);
}
/**
* Adds a sortable {@link TextColumn} whose {@link TextColumn#getValue(Object)} method is based on the {@code valueMapper}
* and whose sorting is based on a {@link NaturalComparator}.
*/
public void addColumn(Function<T, String> valueMapper, String header) {
final TextColumn<T> textColumn = new TextColumn<T>() {
@Override
public String getValue(T object) {
return valueMapper.apply(object);
}
};
addColumn(textColumn, header, Comparator.comparing(t->textColumn.getValue(t), new NaturalComparator()));
}
public void setEmptyTableWidget(Widget widget) {
table.setEmptyTableWidget(widget);
@@ -15,6 +15,7 @@ import java.net.InetAddress;
import java.net.URL;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Base64;
import java.util.Collections;
import java.util.Optional;
import java.util.Random;
@@ -23,7 +24,6 @@ import java.util.logging.Level;
import java.util.logging.Logger;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import com.jcraft.jsch.ChannelSftp;
@@ -32,6 +32,7 @@ import com.jcraft.jsch.JSchException;
import com.jcraft.jsch.KeyPair;
import com.jcraft.jsch.SftpException;
import com.sap.sailing.landscape.SailingAnalyticsMetrics;
import com.sap.sailing.landscape.SailingReleaseRepository;
import com.sap.sailing.landscape.impl.SailingAnalyticsProcessImpl;
import com.sap.sailing.landscape.procedures.StartFromSailingAnalyticsImage;
import com.sap.sse.common.Duration;
@@ -43,6 +44,8 @@ import com.sap.sse.landscape.application.ApplicationProcess;
import com.sap.sse.landscape.aws.impl.AwsRegion;
import com.sap.sse.landscape.aws.orchestration.CreateDNSBasedLoadBalancerMapping;
import com.sap.sse.landscape.impl.ReleaseRepositoryImpl;
import com.sap.sse.landscape.mongodb.MongoEndpoint;
import com.sap.sse.landscape.mongodb.impl.DatabaseImpl;
import com.sap.sse.landscape.ssh.SSHKeyPair;
import com.sap.sse.landscape.ssh.SshCommandChannel;
@@ -72,26 +75,72 @@ public class ConnectivityTest<ProcessT extends ApplicationProcess<String, Sailin
private AwsLandscape<String, SailingAnalyticsMetrics, ProcessT> landscape;
private AwsRegion region;
private byte[] keyPass;
private String AXELS_KEY_PASS;
@Before
public void setUp() {
landscape = AwsLandscape.obtain();
region = new AwsRegion(Region.EU_WEST_2);
AXELS_KEY_PASS = new String(Base64.getDecoder().decode(System.getProperty("axelskeypassphrase")));
keyPass = "lkayrelakuesyrlasp8caorewyc".getBytes();
}
@Ignore("Fill in key details for the key used to launch the central reverse proxy in the test landscape")
//@Ignore("Fill in key details for the key used to launch the central reverse proxy in the test landscape")
@Test
public void readAndStoreSSHKey() throws JSchException, IOException, InterruptedException {
final String AXELS_KEY_NAME = "The key name goes here";
final String KEY_PASSPHRASE = "your passphrase goes here";
final String PATH_TO_YOUR_PRIVATE_KEY = "the path to your id_rsa file";
final String AXELS_KEY_NAME = "Axel";
final String PATH_TO_YOUR_PRIVATE_KEY = "c:/Users/d043530/.ssh/id_rsa";
landscape.deleteKeyPair(region, AXELS_KEY_NAME);
final KeyPair keyPair = KeyPair.load(new JSch(), PATH_TO_YOUR_PRIVATE_KEY, PATH_TO_YOUR_PRIVATE_KEY+".pub");
final byte[] pubKeyBytes = getPublicKeyBytes(keyPair);
final byte[] privKeyBytes = getPrivateKeyBytes(keyPair, KEY_PASSPHRASE.getBytes());
landscape.importKeyPair(region, pubKeyBytes, privKeyBytes, AXELS_KEY_NAME);
final SshCommandChannel sshChannel = landscape.getCentralReverseProxy(region).getHosts().iterator().next().createRootSshChannel(optionalTimeout);
// WORKS:
// 1) decrypt private key read from file containing encrypted key
// 2) obtain bytes of private key unencrypted
// 3) "load"/create new KeyPair from private key decrypted this way
// 4) encrypt private key based on KeyPair loaded from decrypted private key
// 5) "load"/create new KeyPair from key *encrypted* this way
// 6) decryption of private key works
final ByteArrayOutputStream privateKey = new ByteArrayOutputStream();
keyPair.decrypt(AXELS_KEY_PASS.getBytes());
keyPair.writePrivateKey(privateKey); // writing plain unencrypted private key WORKS
final KeyPair keyPairReadFromFileAndDescrypted = KeyPair.load(new JSch(), privateKey.toByteArray(), pubKeyBytes);
assertFalse(keyPairReadFromFileAndDescrypted.isEncrypted());
final ByteArrayOutputStream privateKeyEncrypted = new ByteArrayOutputStream();
keyPairReadFromFileAndDescrypted.writePrivateKey(privateKeyEncrypted, AXELS_KEY_PASS.getBytes());
final KeyPair toAndFrom = KeyPair.load(new JSch(), privateKeyEncrypted.toByteArray(), pubKeyBytes);
assertTrue(toAndFrom.isEncrypted());
assertTrue(toAndFrom.decrypt(AXELS_KEY_PASS.getBytes()));
assertFalse(toAndFrom.isEncrypted());
// WORKS:
// 1) decrypt private key read from file containing encrypted key using getDecryptedPrivateKeyBytes(...)
// 2) obtain bytes of private key unencrypted
// 3) "load"/create new KeyPair from private key decrypted this way
// 4) encrypt private key based on KeyPair loaded from encrypted private key
// 5) "load"/create new KeyPair from key *encrypted* this way
// 6) decryption of private key works
final byte[] privKeyBytesDecrypted = getDecryptedPrivateKeyBytes(keyPair, AXELS_KEY_PASS.getBytes());
final KeyPair testKeyFromDecryptedBytes = KeyPair.load(new JSch(), privKeyBytesDecrypted, pubKeyBytes);
assertFalse(testKeyFromDecryptedBytes.isEncrypted());
final ByteArrayOutputStream encryptedPrivateKeyFromDecrypted = new ByteArrayOutputStream();
testKeyFromDecryptedBytes.writePrivateKey(encryptedPrivateKeyFromDecrypted, AXELS_KEY_PASS.getBytes());
final KeyPair testKeyFromEncryptedBytes = KeyPair.load(new JSch(), encryptedPrivateKeyFromDecrypted.toByteArray(), pubKeyBytes);
assertTrue(testKeyFromEncryptedBytes.isEncrypted());
assertTrue(testKeyFromEncryptedBytes.decrypt(AXELS_KEY_PASS.getBytes()));
assertFalse(testKeyFromEncryptedBytes.isEncrypted());
// BROKEN:
// 1) decrypt private key read from file containing encrypted key
// 2) obtain bytes of private key *encrypted* (writePrivateKey with non-null passphrase)
// 3) "load"/create new KeyPair from *encrypted* private key
// 4) decryption of private key FAILS
// We manifest this BROKENNESS here in this test case:
final byte[] privKeyBytesEncrypted = getPrivateKeyBytes(keyPair, AXELS_KEY_PASS.getBytes());
final KeyPair testKeyFromBytes = KeyPair.load(new JSch(), privKeyBytesEncrypted, pubKeyBytes);
assertTrue(testKeyFromBytes.isEncrypted());
assertFalse(testKeyFromBytes.decrypt(AXELS_KEY_PASS.getBytes())); // THIS IS THE STRANGE CASE; I THINK IT SHOULD HAVE WORKED!
assertTrue(testKeyFromBytes.isEncrypted()); // AND AS A CONSEQUENCE THE KEY IS STILL ENCRYPTED!
// In order to use this to import a valid key existing pair we'll import the last valid key, namely testKeyFromEncryptedBytes:
landscape.importKeyPair(region, pubKeyBytes, encryptedPrivateKeyFromDecrypted.toByteArray(), AXELS_KEY_NAME);
final SshCommandChannel sshChannel = landscape.getCentralReverseProxy(region).getHosts().iterator().next().createRootSshChannel(optionalTimeout, AXELS_KEY_PASS.getBytes());
final String stdout = sshChannel.runCommandAndReturnStdoutAndLogStderr("ls -al", /* stderr prefix */ null, /* stderr log level */ null);
assertFalse(stdout.isEmpty());
}
@@ -105,16 +154,16 @@ public class ConnectivityTest<ProcessT extends ApplicationProcess<String, Sailin
final String hostname = "kw2021.sapsailing.com";
final AwsInstance<String, SailingAnalyticsMetrics> proxyHost = proxy.getHosts().iterator().next();
final ProcessT process = createApplicationProcess(proxyHost);
proxy.setEventRedirect(hostname, process, UUID.randomUUID());
proxy.setEventRedirect(hostname, process, UUID.randomUUID(), AXELS_KEY_PASS.getBytes());
final ByteArrayOutputStream configFileContents = new ByteArrayOutputStream();
final ChannelSftp sftpChannel = proxyHost.createRootSftpChannel(optionalTimeout);
final ChannelSftp sftpChannel = proxyHost.createRootSftpChannel(optionalTimeout, AXELS_KEY_PASS.getBytes());
sftpChannel.connect((int) optionalTimeout.orElse(Duration.NULL).asMillis());
final String configFileName = "/etc/httpd/conf.d/"+hostname+".conf";
sftpChannel.get(configFileName, configFileContents);
assertTrue(configFileContents.toString().startsWith("Use Event-SSL "+hostname));
sftpChannel.disconnect();
proxy.removeRedirect(hostname);
final SshCommandChannel lsSshChannel = proxyHost.createRootSshChannel(optionalTimeout);
proxy.removeRedirect(hostname, AXELS_KEY_PASS.getBytes());
final SshCommandChannel lsSshChannel = proxyHost.createRootSshChannel(optionalTimeout, AXELS_KEY_PASS.getBytes());
final String lsOutput = lsSshChannel.runCommandAndReturnStdoutAndLogStderr("ls "+configFileName, /* stderr prefix */ null, /* stderr log level */ null);
assertTrue(lsOutput.isEmpty());
}
@@ -133,10 +182,13 @@ public class ConnectivityTest<ProcessT extends ApplicationProcess<String, Sailin
final String hostname = "test-"+new Random().nextInt()+"."+hostedZoneName;
final String keyName = "MyKey-"+UUID.randomUUID();
createKeyPair(keyName);
final MongoEndpoint mongoEndpoint = landscape.getMongoEndpoints(region).iterator().next();
final AwsInstance<String, SailingAnalyticsMetrics> host = landscape.launchHost(
getLatestSailingImage(),
InstanceType.T3_SMALL, landscape.getAvailabilityZoneByName(region, "eu-west-2b"), keyName, Collections.singleton(()->"sg-0b2afd48960251280"),
Optional.of(Tags.with("Name", "MyHost").and("Hello", "World")));
Optional.of(Tags.with("Name", "MyHost").and("Hello", "World")),
"MONGODB_URI=\""+mongoEndpoint.getURI(Optional.of(new DatabaseImpl(mongoEndpoint, "winddbTest")))+"\"",
"INSTALL_FROM_RELEASE="+SailingReleaseRepository.INSTANCE.getLatestRelease("bug4811").getName());
try {
assertNotNull(host);
final Instance instance = landscape.getInstance(host.getInstanceId(), region);
@@ -155,12 +207,12 @@ public class ConnectivityTest<ProcessT extends ApplicationProcess<String, Sailin
final ProcessT process = createApplicationProcess(host);
process.waitUntilReady(optionalTimeout);
// check env.sh access
final String envSh = process.getEnvSh(optionalTimeout);
final String envSh = process.getEnvSh(optionalTimeout, keyPass);
assertFalse(envSh.isEmpty());
assertTrue(envSh.contains("SERVER_NAME="));
final Release release = process.getRelease(new ReleaseRepositoryImpl("http://releases.sapsailing.com", "build"), optionalTimeout);
final Release release = process.getRelease(new ReleaseRepositoryImpl("http://releases.sapsailing.com", "build"), optionalTimeout, keyPass);
assertNotNull(release);
assertEquals(14888, process.getTelnetPortToOSGiConsole(optionalTimeout));
assertEquals(14888, process.getTelnetPortToOSGiConsole(optionalTimeout, keyPass));
final AwsLandscape<String, SailingAnalyticsMetrics, ProcessT> castLandscape = (AwsLandscape<String, SailingAnalyticsMetrics, ProcessT>) landscape;
final CreateDNSBasedLoadBalancerMapping.Builder<?, ?, String, SailingAnalyticsMetrics, ProcessT, AwsInstance<String, SailingAnalyticsMetrics>> builder = CreateDNSBasedLoadBalancerMapping.builder();
builder
@@ -176,7 +228,7 @@ public class ConnectivityTest<ProcessT extends ApplicationProcess<String, Sailin
createDNSBasedLoadBalancerMappingProcedure.run();
assertNotNull(createDNSBasedLoadBalancerMappingProcedure.getLoadBalancerUsed());
assertNotNull(createDNSBasedLoadBalancerMappingProcedure.getMasterTargetGroupCreated());
assertEquals(TARGET_GROUP_NAME_PREFIX+process.getServerName(optionalTimeout), createDNSBasedLoadBalancerMappingProcedure.getPublicTargetGroupCreated().getName());
assertEquals(TARGET_GROUP_NAME_PREFIX+process.getServerName(optionalTimeout, keyPass), createDNSBasedLoadBalancerMappingProcedure.getPublicTargetGroupCreated().getName());
} finally {
if (createDNSBasedLoadBalancerMappingProcedure.getLoadBalancerUsed() != null) {
createDNSBasedLoadBalancerMappingProcedure.getLoadBalancerUsed().delete();
@@ -218,6 +270,9 @@ public class ConnectivityTest<ProcessT extends ApplicationProcess<String, Sailin
keyPair.writePublicKey(publicKey, publicKeyComment);
final KeyPair keyPairReadFromFile = KeyPair.load(jsch, privateKey.toByteArray(), publicKey.toByteArray());
assertEquals(publicKeyComment, keyPairReadFromFile.getPublicKeyComment());
assertTrue(keyPairReadFromFile.isEncrypted());
assertTrue(keyPairReadFromFile.decrypt(keyPass));
assertFalse(keyPairReadFromFile.isEncrypted());
}
@Test
@@ -226,7 +281,7 @@ public class ConnectivityTest<ProcessT extends ApplicationProcess<String, Sailin
final JSch jsch = new JSch();
final KeyPair keyPair = KeyPair.genKeyPair(jsch, KeyPair.RSA, 4096);
final byte[] pubKeyBytes = getPublicKeyBytes(keyPair);
final byte[] privKeyBytes = getPrivateKeyBytes(keyPair, /* passphrase */ null);
final byte[] privKeyBytes = getDecryptedPrivateKeyBytes(keyPair, /* passphrase */ null);
final String keyId = landscape.importKeyPair(region, pubKeyBytes, privKeyBytes, testKeyName);
assertTrue(keyId.startsWith("key-"));
final KeyPairInfo awsKeyPairInfo = landscape.getKeyPairInfo(region, testKeyName);
@@ -241,16 +296,24 @@ public class ConnectivityTest<ProcessT extends ApplicationProcess<String, Sailin
return pubKeyBytes;
}
private byte[] getPrivateKeyBytes(final KeyPair keyPair, final byte[] passphrase) {
if (!keyPair.decrypt(passphrase)) { // need to decrypt before writePrivateKey would work
private byte[] getDecryptedPrivateKeyBytes(final KeyPair keyPair, final byte[] passphrase) {
return getPrivateKeyBytes(keyPair, passphrase, null);
}
private byte[] getPrivateKeyBytes(final KeyPair keyPair, final byte[] passphraseForDecryption, final byte[] passphraseForEncryption) {
if (!keyPair.decrypt(passphraseForDecryption)) { // need to decrypt before writePrivateKey would work
throw new IllegalArgumentException("Passphrase didn't unlock private key of key pair "+keyPair);
}
final ByteArrayOutputStream bos = new ByteArrayOutputStream();
keyPair.writePrivateKey(bos, /* passphrase */ null);
keyPair.writePrivateKey(bos, passphraseForEncryption);
final byte[] privKeyBytes = bos.toByteArray();
return privKeyBytes;
}
private byte[] getPrivateKeyBytes(final KeyPair keyPair, byte[] passphrase) {
return getPrivateKeyBytes(keyPair, passphrase, passphrase);
}
@Test
public void testSshConnectWithCreatedKey() throws JSchException, InterruptedException, IOException {
final String keyName = "MyKey-"+UUID.randomUUID();
@@ -262,7 +325,7 @@ public class ConnectivityTest<ProcessT extends ApplicationProcess<String, Sailin
public void testSshConnectWithImportedKey() throws JSchException, InterruptedException, IOException {
final String keyName = "MyKey-"+UUID.randomUUID();
final KeyPair keyPair = KeyPair.genKeyPair(new JSch(), KeyPair.RSA, 4096);
landscape.importKeyPair(region, getPublicKeyBytes(keyPair), getPrivateKeyBytes(keyPair, /* passphrase */ null), keyName);
landscape.importKeyPair(region, getPublicKeyBytes(keyPair), getDecryptedPrivateKeyBytes(keyPair, /* passphrase */ null), keyName);
testSshConnectWithKey(keyName);
}
@@ -277,13 +340,13 @@ public class ConnectivityTest<ProcessT extends ApplicationProcess<String, Sailin
InetAddress address = host.getPublicAddress(optionalTimeout);
assertNotNull(address);
logger.info("Obtained public IP address "+address);
SshCommandChannel shellChannel = host.createRootSshChannel(optionalTimeout);
SshCommandChannel shellChannel = host.createRootSshChannel(optionalTimeout, keyPass);
assertNotNull(shellChannel);
logger.info("Shell channel connected. Waiting for it to become responsive...");
final String stdout = shellChannel.runCommandAndReturnStdoutAndLogStderr("pwd", /* stderr prefix */ null, /* stderr log level */ Level.WARNING);
assertEquals("/root\n", turnAllLineSeparatorsIntoLineFeed(stdout));
// now try a simple command, checking for the "init" process to be found
final SshCommandChannel commandChannel = host.createRootSshChannel(Optional.empty());
final SshCommandChannel commandChannel = host.createRootSshChannel(Optional.empty(), keyPass);
final String processToLookFor = "init";
final String output = commandChannel.runCommandAndReturnStdoutAndLogStderr("ps axlw | grep "+processToLookFor, /* stderr prefix */ null, /* stderr log level */ null);
assertTrue(output.contains(processToLookFor));
@@ -299,7 +362,7 @@ public class ConnectivityTest<ProcessT extends ApplicationProcess<String, Sailin
}
private void createKeyPair(final String keyName) throws JSchException {
final SSHKeyPair sshKeyPair = landscape.createKeyPair(region, keyName);
final SSHKeyPair sshKeyPair = landscape.createKeyPair(region, keyName, keyPass);
assertNotNull(sshKeyPair);
assertEquals(keyName, sshKeyPair.getName());
}
@@ -32,8 +32,9 @@ extends AwsInstance<ShardingKey, MetricsT> {
/**
* Obtains the Sailing Analytics processes running on this host. Can be zero or more.
* @param optionalTimeout TODO
* @param privateKeyEncryptionPassphrase TODO
*/
Iterable<ProcessT> getApplicationProcesses(Optional<Duration> optionalTimeout) throws SftpException, JSchException, IOException, InterruptedException;
Iterable<ProcessT> getApplicationProcesses(Optional<Duration> optionalTimeout, byte[] privateKeyEncryptionPassphrase) throws SftpException, JSchException, IOException, InterruptedException;
AwsLandscape<ShardingKey, MetricsT, ProcessT> getLandscape();
}
@@ -221,24 +221,33 @@ extends Landscape<ShardingKey, MetricsT, ProcessT> {
/**
* Uploads the public key to AWS under the name "keyName", stores it in this landscape and returns the key pair ID
*/
String importKeyPair(Region region, byte[] publicKey, byte[] unencryptedPrivateKey, String keyName) throws JSchException;
String importKeyPair(Region region, byte[] publicKey, byte[] encryptedPrivateKey, String keyName) throws JSchException;
void terminate(AwsInstance<ShardingKey, MetricsT> host);
SSHKeyPair getSSHKeyPair(Region region, String keyName);
byte[] getDecryptedPrivateKey(SSHKeyPair keyPair) throws JSchException;
/**
* Assumes that the {@code keyPair}'s private key has been encrypted using this landscape's default encryption passphrase
* and uses that to decrypt it.
*/
byte[] getDecryptedPrivateKey(SSHKeyPair keyPair, byte[] privateKeyEncryptionPassphrase) throws JSchException;
void addSSHKeyPair(com.sap.sse.landscape.Region region, String creator, String keyName, KeyPair keyPairWithDecryptedPrivateKey);
/**
* Adds a key pair with {@link KeyPair#decrypt(byte[]) decrypted} private key to the AWS {@code region} identified
* and stores it persistently also in the local server's database with the private key encrypted.
*/
void addSSHKeyPair(com.sap.sse.landscape.Region region, String creator, String keyName, KeyPair keyPairWithDecryptedPrivateKey) throws JSchException;
/**
* Creates a key pair with the given name in the region specified and obtains the key details and stores them in
* this landscape persistently, such that {@link #getKeyPairInfo(Region, String)} as well as
* {@link #getSSHKeyPair(Region, String)} will be able to obtain (information on) the key.
* {@link #getSSHKeyPair(Region, String)} will be able to obtain (information on) the key. The private key is
* stored encrypted with the passphrase provided as parameter {@code privateKeyEncryptionPassphrase}.
*
* @return the key ID as string, usually starting with the prefix "key-"
*/
SSHKeyPair createKeyPair(Region region, String keyName) throws JSchException;
SSHKeyPair createKeyPair(Region region, String keyName, byte[] privateKeyEncryptionPassphrase) throws JSchException;
Instance getInstance(String instanceId, Region region);
@@ -487,19 +496,19 @@ extends Landscape<ShardingKey, MetricsT, ProcessT> {
/**
* Obtains all {@link #getApplicationProcessHostsByTag(Region, String, BiFunction) hosts} with a tag whose key is
* specified by {@code tagName} and discovers all application server processes configured on it. These are then
* grouped by {@link ApplicationProcess#getServerName(Optional) server name}, and using
* grouped by {@link ApplicationProcess#getServerName(Optional, byte[]) server name}, and using
* {@link ApplicationProcess#getMasterServerName(Optional)} the master/replica relationships between the processes with equal server
* name are discovered. From this, an {@link ApplicationReplicaSet} is established per server name.
*
* @param processFactoryFromHostAndServerDirectory
* takes the host and the server directory as arguments and is expected to produce an
* {@link ApplicationProcess} object of some sort.
* @param optionalTimeout
* an optional timeout for communicating with the application server(s) to try to read the application
* configuration; used, e.g., as timeout during establishing SSH connections
* @param privateKeyEncryptionPassphrase TODO
*/
Iterable<ApplicationReplicaSet<ShardingKey, MetricsT, ProcessT>> getApplicationReplicaSetsByTag(Region region,
String tagName, BiFunction<Host, String, ProcessT> processFactoryFromHostAndServerDirectory,
Optional<Duration> optionalTimeout) throws Exception;
Optional<Duration> optionalTimeout, byte[] privateKeyEncryptionPassphrase) throws Exception;
}
@@ -52,29 +52,33 @@ public interface ReverseProxy<ShardingKey, MetricsT extends ApplicationProcessMe
/**
* Configures a redirect in this reverse proxy such that requests for it will go to the
* {@code /index.html} landing page for the application replica set provided.
* @param privateKeyEncryptionPassphrase TODO
*/
void setPlainRedirect(String hostname, ProcessT applicationProcess) throws InterruptedException, JSchException, IOException;
void setPlainRedirect(String hostname, ProcessT applicationProcess, byte[] privateKeyEncryptionPassphrase) throws InterruptedException, JSchException, IOException;
/**
* Configures a redirect in this reverse proxy such that requests for it will go to the
* {@code /gwt/Home.html} landing page for the application replica set provided.
* @param privateKeyEncryptionPassphrase TODO
*/
void setHomeRedirect(String hostname, ProcessT applicationProcess) throws InterruptedException, JSchException, IOException;
void setHomeRedirect(String hostname, ProcessT applicationProcess, byte[] privateKeyEncryptionPassphrase) throws InterruptedException, JSchException, IOException;
/**
* Configures a redirect in this reverse proxy such that requests for it will go to the
* event page for the event with ID {@code eventId} that is expected to be hosted by the
* application replica set provided.
* @param privateKeyEncryptionPassphrase TODO
*/
void setEventRedirect(String hostname, ProcessT applicationProcess, UUID eventId) throws InterruptedException, JSchException, IOException;
void setEventRedirect(String hostname, ProcessT applicationProcess, UUID eventId, byte[] privateKeyEncryptionPassphrase) throws InterruptedException, JSchException, IOException;
/**
* Configures a redirect in this reverse proxy such that requests for it will go to the event series page for the
* event series identified by the UUID of the leaderboard group that represents the series and which is expected to
* be hosted by the application replica set provided.
* @param privateKeyEncryptionPassphrase TODO
*/
void setEventSeriesRedirect(String hostname, ProcessT applicationProcess,
UUID leaderboardGroupId) throws InterruptedException, JSchException, IOException;
UUID leaderboardGroupId, byte[] privateKeyEncryptionPassphrase) throws InterruptedException, JSchException, IOException;
/**
* Configures a rule for requests for anything from within {@code scope} such that those requests
@@ -84,16 +88,18 @@ public interface ReverseProxy<ShardingKey, MetricsT extends ApplicationProcessMe
/**
* Creates a mapping for the {@code /internal-server-status} path using the host's generic external ec2 host name
* @param privateKeyEncryptionPassphrase TODO
*/
void createInternalStatusRedirect(Optional<Duration> optionalTimeout) throws InterruptedException, JSchException, IOException;
void createInternalStatusRedirect(Optional<Duration> optionalTimeout, byte[] privateKeyEncryptionPassphrase) throws InterruptedException, JSchException, IOException;
/**
* Removes any existing redirect mapping for the {@code hostname} provided. If no such mapping
* exists, the method does nothing.
* @param privateKeyEncryptionPassphrase TODO
*/
void removeRedirect(String hostname) throws InterruptedException, JSchException, IOException;
void removeRedirect(String hostname, byte[] privateKeyEncryptionPassphrase) throws InterruptedException, JSchException, IOException;
void removeRedirect(Scope<ShardingKey> scope) throws IOException, InterruptedException, JSchException;
void removeRedirect(Scope<ShardingKey> scope, byte[] privateKeyEncryptionPassphrase) throws IOException, InterruptedException, JSchException;
/**
* {@link AwsLandscape#terminate(AwsInstance) Terminates} all {@link #getHosts() hosts} that form this reverse
@@ -80,15 +80,21 @@ implements com.sap.sse.landscape.Process<RotatingFileBasedLog, MetricsT> {
return hostname+CONFIG_FILE_EXTENSION;
}
private void setRedirect(String configFileNameForHostname, String macroName, String hostname, String... macroArguments) throws InterruptedException, JSchException, IOException {
final String command = "echo \"Use "+macroName+" "+hostname+" "+String.join(" ", macroArguments)+"\" >"+getConfigFilePath(configFileNameForHostname)+
"; service httpd reload";
logger.info("Standard output from setting up the re-direct for "+hostname+" and reloading the Apache httpd server: "+
runCommandAndReturnStdoutAndStderr(command, "Standard error from setting up the re-direct for "+hostname+" and reloading the Apache httpd server: ", Level.INFO));
private void setRedirect(String configFileNameForHostname, String macroName, String hostname,
byte[] privateKeyEncryptionPassphrase, String... macroArguments)
throws InterruptedException, JSchException, IOException {
final String command = "echo \"Use " + macroName + " " + hostname + " " + String.join(" ", macroArguments)
+ "\" >" + getConfigFilePath(configFileNameForHostname) + "; service httpd reload";
logger.info("Standard output from setting up the re-direct for " + hostname
+ " and reloading the Apache httpd server: "
+ runCommandAndReturnStdoutAndStderr(command,
"Standard error from setting up the re-direct for " + hostname
+ " and reloading the Apache httpd server: ",
Level.INFO, privateKeyEncryptionPassphrase));
}
private String runCommandAndReturnStdoutAndStderr(String command, String stderrLogPrefix, Level stderrLogLevel) throws IOException, InterruptedException, JSchException {
final SshCommandChannel sshChannel = getHost().createRootSshChannel(TIMEOUT);
private String runCommandAndReturnStdoutAndStderr(String command, String stderrLogPrefix, Level stderrLogLevel, byte[] privateKeyEncryptionPassphrase) throws IOException, InterruptedException, JSchException {
final SshCommandChannel sshChannel = getHost().createRootSshChannel(TIMEOUT, privateKeyEncryptionPassphrase);
final String stdout = sshChannel.runCommandAndReturnStdoutAndLogStderr(command, stderrLogPrefix, stderrLogLevel);
return stdout;
}
@@ -98,60 +104,65 @@ implements com.sap.sse.landscape.Process<RotatingFileBasedLog, MetricsT> {
}
@Override
public void setScopeRedirect(Scope<ShardingKey> scope,ProcessT applicationReplicaSet) {
public void setScopeRedirect(Scope<ShardingKey> scope, ProcessT applicationReplicaSet) {
// TODO Implement ApacheReverseProxy.setScopeRedirect(...)
}
@Override
public void setPlainRedirect(String hostname, ProcessT applicationProcess) throws InterruptedException, JSchException, IOException {
public void setPlainRedirect(String hostname, ProcessT applicationProcess, byte[] privateKeyEncryptionPassphrase) throws InterruptedException, JSchException, IOException {
final String host = applicationProcess.getHost().getPrivateAddress().getHostAddress();
final int port = applicationProcess.getPort();
setRedirect(getConfigFileNameForHostname(hostname), PLAIN_REDIRECT_MACRO, hostname, host, ""+port);
setRedirect(getConfigFileNameForHostname(hostname), PLAIN_REDIRECT_MACRO, hostname, privateKeyEncryptionPassphrase, host, ""+port);
}
@Override
public void setHomeRedirect(String hostname, ProcessT applicationProcess) throws InterruptedException, JSchException, IOException {
public void setHomeRedirect(String hostname, ProcessT applicationProcess, byte[] privateKeyEncryptionPassphrase) throws InterruptedException, JSchException, IOException {
final String host = applicationProcess.getHost().getPrivateAddress().getHostAddress();
final int port = applicationProcess.getPort();
setRedirect(getConfigFileNameForHostname(hostname), HOME_REDIRECT_MACRO, hostname, host, ""+port);
setRedirect(getConfigFileNameForHostname(hostname), HOME_REDIRECT_MACRO, hostname, privateKeyEncryptionPassphrase, host, ""+port);
}
@Override
public void setEventRedirect(String hostname, ProcessT applicationProcess, UUID eventId) throws InterruptedException, JSchException, IOException {
public void setEventRedirect(String hostname, ProcessT applicationProcess, UUID eventId, byte[] privateKeyEncryptionPassphrase) throws InterruptedException, JSchException, IOException {
final String host = applicationProcess.getHost().getPrivateAddress().getHostAddress();
final int port = applicationProcess.getPort();
setRedirect(getConfigFileNameForHostname(hostname), EVENT_REDIRECT_MACRO, hostname, eventId.toString(), host, ""+port);
setRedirect(getConfigFileNameForHostname(hostname), EVENT_REDIRECT_MACRO, hostname, privateKeyEncryptionPassphrase, eventId.toString(), host, ""+port);
}
@Override
public void setEventSeriesRedirect(String hostname, ProcessT applicationProcess,
UUID leaderboardGroupId) throws InterruptedException, JSchException, IOException {
UUID leaderboardGroupId, byte[] privateKeyEncryptionPassphrase) throws InterruptedException, JSchException, IOException {
final String host = applicationProcess.getHost().getPrivateAddress().getHostAddress();
final int port = applicationProcess.getPort();
setRedirect(getConfigFileNameForHostname(hostname), SERIES_REDIRECT_MACRO, hostname, leaderboardGroupId.toString(), host, ""+port);
setRedirect(getConfigFileNameForHostname(hostname), SERIES_REDIRECT_MACRO, hostname, privateKeyEncryptionPassphrase, leaderboardGroupId.toString(), host, ""+port);
}
@Override
public void createInternalStatusRedirect(Optional<Duration> optionalTimeout) throws InterruptedException, JSchException, IOException {
setRedirect(CONFIG_FILE_FOR_INTERNALS, STATUS, getHost().getPublicAddress(optionalTimeout).getCanonicalHostName(), INTERNAL_SERVER_STATUS);
public void createInternalStatusRedirect(Optional<Duration> optionalTimeout, byte[] privateKeyEncryptionPassphrase) throws InterruptedException, JSchException, IOException {
setRedirect(CONFIG_FILE_FOR_INTERNALS, STATUS, getHost().getPublicAddress(optionalTimeout).getCanonicalHostName(), privateKeyEncryptionPassphrase, INTERNAL_SERVER_STATUS);
}
@Override
public void removeRedirect(Scope<ShardingKey> scope) throws IOException, InterruptedException, JSchException {
public void removeRedirect(Scope<ShardingKey> scope, byte[] privateKeyEncryptionPassphrase) throws IOException, InterruptedException, JSchException {
final String configFilePath = getConfigFilePath(getConfigFileNameForScope(scope));
removeRedirect(configFilePath, scope.toString());
removeRedirect(configFilePath, scope.toString(), privateKeyEncryptionPassphrase);
}
@Override
public void removeRedirect(String hostname) throws IOException, InterruptedException, JSchException {
public void removeRedirect(String hostname, byte[] privateKeyEncryptionPassphrase) throws IOException, InterruptedException, JSchException {
final String configFilePath = getConfigFilePath(getConfigFileNameForHostname(hostname));
removeRedirect(configFilePath, hostname);
removeRedirect(configFilePath, hostname, privateKeyEncryptionPassphrase);
}
private void removeRedirect(String configFilePath, String redirectNameForLogOutput) throws IOException, InterruptedException, JSchException {
final String command = "rm "+configFilePath+"; service httpd reload";
logger.info("Standard output from removing the re-direct for "+redirectNameForLogOutput+" and reloading the Apache httpd server: "+
runCommandAndReturnStdoutAndStderr(command, "Standard error from removing the re-direct for "+redirectNameForLogOutput+" and reloading the Apache httpd server: ", Level.INFO));
private void removeRedirect(String configFilePath, String redirectNameForLogOutput,
byte[] privateKeyEncryptionPassphrase) throws IOException, InterruptedException, JSchException {
final String command = "rm " + configFilePath + "; service httpd reload";
logger.info("Standard output from removing the re-direct for " + redirectNameForLogOutput
+ " and reloading the Apache httpd server: "
+ runCommandAndReturnStdoutAndStderr(command,
"Standard error from removing the re-direct for " + redirectNameForLogOutput
+ " and reloading the Apache httpd server: ",
Level.INFO, privateKeyEncryptionPassphrase));
}
@Override
@@ -90,32 +90,32 @@ implements ReverseProxyCluster<ShardingKey, MetricsT, ProcessT, RotatingFileBase
}
@Override
public void setPlainRedirect(String hostname, ProcessT applicationProcess) throws InterruptedException, JSchException, IOException {
public void setPlainRedirect(String hostname, ProcessT applicationProcess, byte[] privateKeyEncryptionPassphrase) throws InterruptedException, JSchException, IOException {
for (final ApacheReverseProxy<ShardingKey, MetricsT, ProcessT> proxy : getReverseProxies()) {
proxy.setPlainRedirect(hostname, applicationProcess);
proxy.setPlainRedirect(hostname, applicationProcess, privateKeyEncryptionPassphrase);
}
}
@Override
public void setHomeRedirect(String hostname, ProcessT applicationProcess) throws InterruptedException, JSchException, IOException {
public void setHomeRedirect(String hostname, ProcessT applicationProcess, byte[] privateKeyEncryptionPassphrase) throws InterruptedException, JSchException, IOException {
for (final ApacheReverseProxy<ShardingKey, MetricsT, ProcessT> proxy : getReverseProxies()) {
proxy.setHomeRedirect(hostname, applicationProcess);
proxy.setHomeRedirect(hostname, applicationProcess, privateKeyEncryptionPassphrase);
}
}
@Override
public void setEventRedirect(String hostname, ProcessT applicationProcess,
UUID eventId) throws InterruptedException, JSchException, IOException {
UUID eventId, byte[] privateKeyEncryptionPassphrase) throws InterruptedException, JSchException, IOException {
for (final ApacheReverseProxy<ShardingKey, MetricsT, ProcessT> proxy : getReverseProxies()) {
proxy.setEventRedirect(hostname, applicationProcess, eventId);
proxy.setEventRedirect(hostname, applicationProcess, eventId, privateKeyEncryptionPassphrase);
}
}
@Override
public void setEventSeriesRedirect(String hostname, ProcessT applicationProcess,
UUID leaderboardGroupId) throws InterruptedException, JSchException, IOException {
UUID leaderboardGroupId, byte[] privateKeyEncryptionPassphrase) throws InterruptedException, JSchException, IOException {
for (final ApacheReverseProxy<ShardingKey, MetricsT, ProcessT> proxy : getReverseProxies()) {
proxy.setEventSeriesRedirect(hostname, applicationProcess, leaderboardGroupId);
proxy.setEventSeriesRedirect(hostname, applicationProcess, leaderboardGroupId, privateKeyEncryptionPassphrase);
}
}
@@ -127,23 +127,23 @@ implements ReverseProxyCluster<ShardingKey, MetricsT, ProcessT, RotatingFileBase
}
@Override
public void createInternalStatusRedirect(Optional<Duration> optionalTimeout) throws InterruptedException, JSchException, IOException {
public void createInternalStatusRedirect(Optional<Duration> optionalTimeout, byte[] privateKeyEncryptionPassphrase) throws InterruptedException, JSchException, IOException {
for (final ApacheReverseProxy<ShardingKey, MetricsT, ProcessT> proxy : getReverseProxies()) {
proxy.createInternalStatusRedirect(optionalTimeout);
proxy.createInternalStatusRedirect(optionalTimeout, privateKeyEncryptionPassphrase);
}
}
@Override
public void removeRedirect(String hostname) throws IOException, InterruptedException, JSchException {
public void removeRedirect(String hostname, byte[] privateKeyEncryptionPassphrase) throws IOException, InterruptedException, JSchException {
for (final ApacheReverseProxy<ShardingKey, MetricsT, ProcessT> proxy : getReverseProxies()) {
proxy.removeRedirect(hostname);
proxy.removeRedirect(hostname, privateKeyEncryptionPassphrase);
}
}
@Override
public void removeRedirect(Scope<ShardingKey> scope) throws IOException, InterruptedException, JSchException {
public void removeRedirect(Scope<ShardingKey> scope, byte[] privateKeyEncryptionPassphrase) throws IOException, InterruptedException, JSchException {
for (final ApacheReverseProxy<ShardingKey, MetricsT, ProcessT> proxy : getReverseProxies()) {
proxy.removeRedirect(scope);
proxy.removeRedirect(scope, privateKeyEncryptionPassphrase);
}
}
}
@@ -51,13 +51,18 @@ implements ApplicationProcessHost<ShardingKey, MetricsT, ProcessT> {
/**
* The implementation scans the {@link ApplicationProcessHost#DEFAULT_SERVERS_PATH application server deployment
* folder} for sub-folders. In those sub-folders, the configuration file is analyzed for the port number to instantiate
* an {@link ApplicationProcess} object for each one.
* folder} for sub-folders. In those sub-folders, the configuration file is analyzed for the port number to
* instantiate an {@link ApplicationProcess} object for each one.
*
* @param privateKeyEncryptionPassphrase
* the pass phrase for the private key that belongs to the instance's public key used for start-up
*/
@Override
public Iterable<ProcessT> getApplicationProcesses(Optional<Duration> optionalTimeout) throws SftpException, JSchException, IOException, InterruptedException {
public Iterable<ProcessT> getApplicationProcesses(Optional<Duration> optionalTimeout,
byte[] privateKeyEncryptionPassphrase)
throws SftpException, JSchException, IOException, InterruptedException {
final Set<ProcessT> result = new HashSet<>();
final ChannelSftp sftpChannel = createRootSftpChannel(optionalTimeout);
final ChannelSftp sftpChannel = createRootSftpChannel(optionalTimeout, privateKeyEncryptionPassphrase);
if (optionalTimeout.isPresent()) {
sftpChannel.connect((int) optionalTimeout.get().asMillis());
} else {
@@ -120,23 +120,27 @@ public class AwsInstanceImpl<ShardingKey, MetricsT extends ApplicationProcessMet
/**
* Establishes an unconnected session configured for the "root" user.
*
* @param privateKeyEncryptionPassphrase
* the pass phrase for the private key that belongs to the instance's public key used for start-up
* @see #createRootSshChannel
*/
public com.jcraft.jsch.Session createRootSshSession() throws JSchException {
return createSshSession(ROOT_USER_NAME);
public com.jcraft.jsch.Session createRootSshSession(byte[] privateKeyEncryptionPassphrase) throws JSchException {
return createSshSession(ROOT_USER_NAME, privateKeyEncryptionPassphrase);
}
/**
* Establishes an unconnected session configured for the "root" user.
*
* @param privateKeyEncryptionPassphrase
* the pass phrase for the private key that belongs to the instance's public key used for start-up
* @see #createRootSshChannel
*/
public com.jcraft.jsch.Session createSshSession(String sshUserName) throws JSchException {
public com.jcraft.jsch.Session createSshSession(String sshUserName, byte[] privateKeyEncryptionPassphrase) throws JSchException {
final String keyName = getInstance().keyName(); // the SSH key pair name that can be used to log on
final SSHKeyPair keyPair = landscape.getSSHKeyPair(getRegion(), keyName);
final JSch jsch = new JSch();
JSch.setLogger(new JCraftLogAdapter());
jsch.addIdentity(keyName, landscape.getDecryptedPrivateKey(keyPair), keyPair.getPublicKey(), /* passphrase */ null);
jsch.addIdentity(keyName, landscape.getDecryptedPrivateKey(keyPair, privateKeyEncryptionPassphrase), keyPair.getPublicKey(), /* passphrase */ null);
final InetAddress address = getPublicAddress();
if (address == null) {
throw new IllegalStateException("Instance "+getInstanceId()+" doesn't have a public IP address");
@@ -146,12 +150,15 @@ public class AwsInstanceImpl<ShardingKey, MetricsT extends ApplicationProcessMet
/**
* Connects to an SSH session for the "root" user with a "shell" channel
* @param privateKeyEncryptionPassphrase
* the pass phrase for the private key that belongs to the instance's public key used for start-up
*
* @see #createSshChannel(String, Optional)
* @see #createSshChannel(String, Optional, byte[])
*/
@Override
public SshCommandChannel createRootSshChannel(Optional<Duration> optionalTimeout) throws JSchException, IOException, InterruptedException {
return createSshChannel(ROOT_USER_NAME, optionalTimeout);
public SshCommandChannel createRootSshChannel(Optional<Duration> optionalTimeout,
byte[] privateKeyEncryptionPassphrase) throws JSchException, IOException, InterruptedException {
return createSshChannel(ROOT_USER_NAME, optionalTimeout, privateKeyEncryptionPassphrase);
}
/**
@@ -161,22 +168,28 @@ public class AwsInstanceImpl<ShardingKey, MetricsT extends ApplicationProcessMet
* output stream} to which the server will send its output. You will usually want to use either a
* {@link ByteArrayInputStream} to provide a set of predefined commands to sent to the server, and a
* {@link PipedInputStream} wrapped around a {@link PipedOutputStream} which you set to the channel.
*
* @param privateKeyEncryptionPassphrase
* the pass phrase for the private key that belongs to the instance's public key used for start-up
*/
@Override
public SshCommandChannel createSshChannel(String sshUserName, Optional<Duration> optionalTimeout) throws JSchException, IOException, InterruptedException {
return new SshCommandChannelImpl((ChannelExec) createSshChannelInternal(sshUserName, "exec", optionalTimeout));
public SshCommandChannel createSshChannel(String sshUserName, Optional<Duration> optionalTimeout,
byte[] privateKeyEncryptionPassphrase) throws JSchException, IOException, InterruptedException {
return new SshCommandChannelImpl((ChannelExec) createSshChannelInternal(sshUserName, "exec", optionalTimeout, privateKeyEncryptionPassphrase));
}
private Channel createSshChannelInternal(String sshUserName, String channelType, Optional<Duration> optionalTimeout) throws JSchException, IOException, InterruptedException {
private Channel createSshChannelInternal(String sshUserName, String channelType, Optional<Duration> optionalTimeout,
byte[] privateKeyEncryptionPassphrase) throws JSchException, IOException, InterruptedException {
final TimePoint start = TimePoint.now();
logger.info("Creating SSH "+channelType+" channel for SSH user "+sshUserName+
logger.info(
"Creating SSH "+channelType+" channel for SSH user "+sshUserName+
" to instance with ID "+getInstanceId());
Channel channel = null;
while (channel == null && (!optionalTimeout.isPresent() || start.until(TimePoint.now()).compareTo(optionalTimeout.get()) < 0)) {
Session session = null;
try {
session = createSshSession(sshUserName);
session = createSshSession(sshUserName, privateKeyEncryptionPassphrase);
session.setUserInfo(new YesUserInfo());
session.connect(optionalTimeout.map(d->d.asMillis()).orElse(0l).intValue());
channel = session.openChannel(channelType);
@@ -200,13 +213,16 @@ public class AwsInstanceImpl<ShardingKey, MetricsT extends ApplicationProcessMet
}
@Override
public ChannelSftp createSftpChannel(String sshUserName, Optional<Duration> optionalTimeout) throws JSchException, IOException, InterruptedException {
return (ChannelSftp) createSshChannelInternal(sshUserName, "sftp", optionalTimeout);
public ChannelSftp createSftpChannel(String sshUserName, Optional<Duration> optionalTimeout,
byte[] privateKeyEncryptionPassphrase) throws JSchException, IOException, InterruptedException {
return (ChannelSftp) createSshChannelInternal(sshUserName, "sftp", optionalTimeout,
privateKeyEncryptionPassphrase);
}
@Override
public ChannelSftp createRootSftpChannel(Optional<Duration> optionalTimeout) throws JSchException, IOException, InterruptedException {
return createSftpChannel(ROOT_USER_NAME, optionalTimeout);
public ChannelSftp createRootSftpChannel(Optional<Duration> optionalTimeout, byte[] privateKeyEncryptionPassphrase)
throws JSchException, IOException, InterruptedException {
return createSftpChannel(ROOT_USER_NAME, optionalTimeout, privateKeyEncryptionPassphrase);
}
@Override
@@ -157,12 +157,6 @@ implements AwsLandscape<ShardingKey, MetricsT, ProcessT> {
private ConcurrentMap<Pair<String, String>, SSHKeyPair> sshKeyPairs;
private final AwsRegion globalRegion;
/**
* Used for the symmetric encryption / decryption of private SSH keys. See also
* {@link #getDecryptedPrivateKey(SSHKeyPair)}.
*/
private final byte[] privateKeyEncryptionPassphrase;
public AwsLandscapeImpl() {
this(System.getProperty(ACCESS_KEY_ID_SYSTEM_PROPERTY_NAME),
System.getProperty(SECRET_ACCESS_KEY_SYSTEM_PROPERTY_NAME));
@@ -177,7 +171,6 @@ implements AwsLandscape<ShardingKey, MetricsT, ProcessT> {
public AwsLandscapeImpl(String accessKeyId, String secretAccessKey,
DomainObjectFactory domainObjectFactory, MongoObjectFactory mongoObjectFactory) {
this.privateKeyEncryptionPassphrase = ("aw4raif87l"+"098sf;;50").getBytes();
this.accessKeyId = accessKeyId;
this.secretAccessKey = secretAccessKey;
this.globalRegion = new AwsRegion(Region.AWS_GLOBAL);
@@ -203,7 +196,8 @@ implements AwsLandscape<ShardingKey, MetricsT, ProcessT> {
}
@Override
public void addSSHKeyPair(com.sap.sse.landscape.Region region, String creator, String keyName, KeyPair keyPairWithDecryptedPrivateKey) {
public void addSSHKeyPair(com.sap.sse.landscape.Region region, String creator, String keyName, KeyPair keyPairWithDecryptedPrivateKey) throws JSchException {
assert !keyPairWithDecryptedPrivateKey.isEncrypted();
addSSHKeyPair(new SSHKeyPair(region.getId(), creator, TimePoint.now(), keyName, keyPairWithDecryptedPrivateKey.getPublicKeyBlob(),
getPrivateKeyBytes(keyPairWithDecryptedPrivateKey)));
}
@@ -214,7 +208,7 @@ implements AwsLandscape<ShardingKey, MetricsT, ProcessT> {
}
@Override
public SSHKeyPair createKeyPair(com.sap.sse.landscape.Region region, String keyName) throws JSchException {
public SSHKeyPair createKeyPair(com.sap.sse.landscape.Region region, String keyName, byte[] privateKeyEncryptionPassphrase) throws JSchException {
final CreateKeyPairResponse keyPairResponse = getEc2Client(getRegion(region))
.createKeyPair(CreateKeyPairRequest.builder().keyName(keyName).build());
final String keyMaterial = keyPairResponse.keyMaterial();
@@ -609,7 +603,7 @@ implements AwsLandscape<ShardingKey, MetricsT, ProcessT> {
}
@Override
public String importKeyPair(com.sap.sse.landscape.Region region, byte[] publicKey, byte[] unencryptedPrivateKey, String keyName) throws JSchException {
public String importKeyPair(com.sap.sse.landscape.Region region, byte[] publicKey, byte[] encryptedPrivateKey, String keyName) throws JSchException {
final String keyId = getEc2Client(getRegion(region)).importKeyPair(ImportKeyPairRequest.builder().keyName(keyName)
.publicKeyMaterial(SdkBytes.fromByteArray(publicKey)).build()).keyPairId();
Object principal;
@@ -620,7 +614,7 @@ implements AwsLandscape<ShardingKey, MetricsT, ProcessT> {
principal = null;
}
final SSHKeyPair keyPair = new SSHKeyPair(region.getId(), principal==null?"":principal.toString(),
TimePoint.now(), keyName, publicKey, unencryptedPrivateKey, privateKeyEncryptionPassphrase);
TimePoint.now(), keyName, publicKey, encryptedPrivateKey);
addSSHKeyPair(keyPair);
return keyId;
}
@@ -631,7 +625,7 @@ implements AwsLandscape<ShardingKey, MetricsT, ProcessT> {
}
@Override
public byte[] getDecryptedPrivateKey(SSHKeyPair keyPair) throws JSchException {
public byte[] getDecryptedPrivateKey(SSHKeyPair keyPair, byte[] privateKeyEncryptionPassphrase) throws JSchException {
final ByteArrayOutputStream bos = new ByteArrayOutputStream();
keyPair.getKeyPair(new JSch(), privateKeyEncryptionPassphrase).writePrivateKey(bos);
return bos.toByteArray();
@@ -1033,13 +1027,13 @@ implements AwsLandscape<ShardingKey, MetricsT, ProcessT> {
@Override
public Iterable<ApplicationReplicaSet<ShardingKey, MetricsT, ProcessT>> getApplicationReplicaSetsByTag(com.sap.sse.landscape.Region region, String tagName,
BiFunction<Host, String, ProcessT> processFactoryFromHostAndServerDirectory, Optional<Duration> optionalTimeout) throws Exception {
BiFunction<Host, String, ProcessT> processFactoryFromHostAndServerDirectory, Optional<Duration> optionalTimeout, byte[] privateKeyEncryptionPassphrase) throws Exception {
final Iterable<ApplicationProcessHost<ShardingKey, MetricsT, ProcessT>> hosts = getApplicationProcessHostsByTag(region, tagName, processFactoryFromHostAndServerDirectory);
final Map<String, ProcessT> mastersByServerName = new HashMap<>();
final Map<String, Set<ProcessT>> replicasByServerName = new HashMap<>();
for (final ApplicationProcessHost<ShardingKey, MetricsT, ProcessT> host : hosts) {
for (final ProcessT applicationProcess : host.getApplicationProcesses(optionalTimeout)) {
final String serverName = applicationProcess.getServerName(optionalTimeout);
for (final ProcessT applicationProcess : host.getApplicationProcesses(optionalTimeout, privateKeyEncryptionPassphrase)) {
final String serverName = applicationProcess.getServerName(optionalTimeout, privateKeyEncryptionPassphrase);
final String masterServerName = applicationProcess.getMasterServerName(optionalTimeout);
if (masterServerName != null && Util.equalsWithNull(masterServerName, serverName)) {
// then applicationProcess is a replica in the serverName cluster:
@@ -101,6 +101,7 @@ extends ProcedureWithTargetGroup<ShardingKey, MetricsT, ProcessT, HostT> {
BuilderT setProcess(ProcessT process);
BuilderT setHostname(String hostname);
BuilderT setTimeout(Duration timeout);
BuilderT setPrivateKeyEncryptionPassphrase(byte[] privateKeyEncryptionPassphrase);
}
protected abstract static class BuilderImpl<BuilderT extends Builder<BuilderT, T, ShardingKey, MetricsT, ProcessT, HostT>,
@@ -113,6 +114,7 @@ extends ProcedureWithTargetGroup<ShardingKey, MetricsT, ProcessT, HostT> {
private String hostname;
private ProcessT process;
private Optional<Duration> optionalTimeout = Optional.empty();
private byte[] privateKeyEncryptionPassphrase;
@Override
public BuilderT setProcess(ProcessT process) {
@@ -143,6 +145,12 @@ extends ProcedureWithTargetGroup<ShardingKey, MetricsT, ProcessT, HostT> {
protected Optional<Duration> getOptionalTimeout() {
return optionalTimeout;
}
@Override
public BuilderT setPrivateKeyEncryptionPassphrase(byte[] privateKeyEncryptionPassphrase) {
this.privateKeyEncryptionPassphrase = privateKeyEncryptionPassphrase;
return self();
}
@Override
protected String getServerName() throws Exception {
@@ -150,7 +158,7 @@ extends ProcedureWithTargetGroup<ShardingKey, MetricsT, ProcessT, HostT> {
if (super.getServerName() != null) {
result = super.getServerName();
} else {
result = getProcess().getServerName(getOptionalTimeout());
result = getProcess().getServerName(getOptionalTimeout(), privateKeyEncryptionPassphrase);
}
return result;
}
@@ -16,6 +16,7 @@ import com.jcraft.jsch.JSchException;
import com.sap.sse.common.Duration;
import com.sap.sse.common.Util;
import com.sap.sse.landscape.AvailabilityZone;
import com.sap.sse.landscape.Host;
import com.sap.sse.landscape.Landscape;
import com.sap.sse.landscape.ProcessConfigurationVariable;
import com.sap.sse.landscape.Region;
@@ -56,6 +57,7 @@ extends StartHost<ShardingKey, MetricsT, ProcessT, HostT> {
private final Iterable<SecurityGroup> securityGroups;
private final Optional<Tags> tags;
private final HostSupplier<ShardingKey, MetricsT, ProcessT, HostT> hostSupplier;
private final byte[] privateKeyEncryptionPassphrase;
private HostT host;
/**
@@ -89,7 +91,19 @@ extends StartHost<ShardingKey, MetricsT, ProcessT, HostT> {
BuilderT setAvailabilityZone(AwsAvailabilityZone availabilityZone);
/**
* Tells the name of the SSH key stored in the AWS landscape that is to be used to launch the instance; the
* respective public key will be added so that an owner of the corresponding private key can
* {@link Host#createSshChannel(String, Optional, byte[]) SSH} into the resulting host.<p>
*
* Make sure to also provide the {@link #setPrivateKeyEncryptionPassphrase(byte[]) private key pass phrase}
* that is required to decrypt the pass phrase of the private key belonging to the public key identified
* by {@code keyName}. This private key is expected to be stored together with the entire key pair in the
* "landscape" persistently.
*/
BuilderT setKeyName(String keyName);
BuilderT setPrivateKeyEncryptionPassphrase(byte[] privateKeyEncryptionPassphrase);
BuilderT setSecurityGroups(Iterable<SecurityGroup> securityGroups);
@@ -120,6 +134,7 @@ extends StartHost<ShardingKey, MetricsT, ProcessT, HostT> {
private AwsRegion region;
private String instanceName;
private HostSupplier<ShardingKey, MetricsT, ProcessT, HostT> hostSupplier;
private byte[] privateKeyEncryptionPassphrase;
protected AwsLandscape<ShardingKey, MetricsT, ProcessT> getLandscape() {
return (AwsLandscape<ShardingKey, MetricsT, ProcessT>) super.getLandscape();
@@ -233,6 +248,16 @@ extends StartHost<ShardingKey, MetricsT, ProcessT, HostT> {
this.hostSupplier = hostSupplier;
return self();
}
@Override
public BuilderT setPrivateKeyEncryptionPassphrase(byte[] privateKeyEncryptionPassphrase) {
this.privateKeyEncryptionPassphrase = privateKeyEncryptionPassphrase;
return self();
}
protected byte[] getPrivateKeyEncryptionPassphrase() {
return privateKeyEncryptionPassphrase;
}
}
protected StartAwsHost(BuilderImpl<?, ? extends StartAwsHost<ShardingKey, MetricsT, ProcessT, HostT>, ShardingKey, MetricsT, ProcessT, HostT> builder) {
@@ -247,6 +272,7 @@ extends StartHost<ShardingKey, MetricsT, ProcessT, HostT> {
this.tags = Optional.of(builder.getTags().orElse(Tags.empty()).and(NAME_TAG_NAME, builder.getInstanceName()));
this.securityGroups = builder.getSecurityGroups();
this.hostSupplier = builder.getHostSupplier();
this.privateKeyEncryptionPassphrase = builder.getPrivateKeyEncryptionPassphrase();
}
protected static <ShardingKey,
@@ -350,10 +376,14 @@ extends StartHost<ShardingKey, MetricsT, ProcessT, HostT> {
}
}
}
protected byte[] getPrivateKeyEncryptionPassphrase() {
return privateKeyEncryptionPassphrase;
}
protected void copyRootAuthorizedKeysToOtherUser(String username, Optional<Duration> optionalTimeout)
throws JSchException, IOException, InterruptedException {
final SshCommandChannel sshChannel = getHost().createRootSshChannel(optionalTimeout);
final SshCommandChannel sshChannel = getHost().createRootSshChannel(optionalTimeout, getPrivateKeyEncryptionPassphrase());
final String sailingUserSsh = "/home/" + username + "/.ssh";
final String sailingUserAuthorizedKeys = sailingUserSsh + "/authorized_keys";
logger.info("Appended root's authorized_keys also to " + username + "'s authorized_keys. stdout: "
@@ -259,6 +259,4 @@ extends StartAwsHost<ShardingKey, MetricsT, ProcessT, AwsInstance<ShardingKey, M
mongoProcess = new MongoProcessInReplicaSetImpl(replicaSet, MongoProcess.DEFAULT_PORT, instance);
}
}
}
@@ -56,19 +56,33 @@ public interface Host {
* output. You will usually want to use either a {@link ByteArrayInputStream} to provide a set of predefined
* commands to sent to the server, and a {@link PipedInputStream} wrapped around a {@link PipedOutputStream} which
* you set to the channel.
*
* @param privateKeyEncryptionPassphrase
* the pass phrase for the private key that belongs to the instance's public key used for start-up
*/
SshCommandChannel createSshChannel(String sshUserName, Optional<Duration> optionalTimeout) throws JSchException, IOException, InterruptedException;
SshCommandChannel createSshChannel(String sshUserName, Optional<Duration> optionalTimeout, byte[] privateKeyEncryptionPassphrase) throws JSchException, IOException, InterruptedException;
/**
* Connects to an SSH session for the "root" user with a "shell" channel
*
* @see #createSshChannel(String, Optional)
* @param privateKeyEncryptionPassphrase
* the pass phrase for the private key that belongs to the instance's public key used for start-up
*
* @see #createSshChannel(String, Optional, byte[])
*/
SshCommandChannel createRootSshChannel(Optional<Duration> optionalTimeout) throws JSchException, IOException, InterruptedException;
ChannelSftp createSftpChannel(String sshUserName, Optional<Duration> optionalTimeout) throws JSchException, IOException, InterruptedException;
SshCommandChannel createRootSshChannel(Optional<Duration> optionalTimeout, byte[] privateKeyEncryptionPassphrase) throws JSchException, IOException, InterruptedException;
ChannelSftp createRootSftpChannel(Optional<Duration> optionalTimeout) throws JSchException, IOException, InterruptedException;
/**
* @param privateKeyEncryptionPassphrase
* the pass phrase for the private key that belongs to the instance's public key used for start-up
*/
ChannelSftp createSftpChannel(String sshUserName, Optional<Duration> optionalTimeout, byte[] privateKeyEncryptionPassphrase) throws JSchException, IOException, InterruptedException;
/**
* @param privateKeyEncryptionPassphrase
* the pass phrase for the private key that belongs to the instance's public key used for start-up
*/
ChannelSftp createRootSftpChannel(Optional<Duration> optionalTimeout, byte[] privateKeyEncryptionPassphrase) throws JSchException, IOException, InterruptedException;
/**
* Tells where in the cloud this host runs; the availability zone {@link AvailabilityZone#getRegion() implies} the
@@ -29,9 +29,10 @@ extends Process<RotatingFileBasedLog, MetricsT> {
* @param releaseRepository
* mandatory parameter required to enable resolving the release and enabling the download of its
* artifacts, including its release notes
* @param privateKeyEncryptionPassphrase TODO
* @return the release that this process is currently running
*/
Release getRelease(ReleaseRepository releaseRepository, Optional<Duration> optionalTimeout) throws JSchException, IOException, SftpException, InterruptedException;
Release getRelease(ReleaseRepository releaseRepository, Optional<Duration> optionalTimeout, byte[] privateKeyEncryptionPassphrase) throws JSchException, IOException, SftpException, InterruptedException;
/**
* Tries to shut down an OSGi application server process cleanly by sending the "shutdown" OSGi command to this
@@ -48,7 +49,7 @@ extends Process<RotatingFileBasedLog, MetricsT> {
*/
boolean tryCleanShutdown(Duration timeout, boolean forceAfterTimeout);
int getTelnetPortToOSGiConsole(Optional<Duration> optionalTimeout) throws NumberFormatException, JSchException, IOException, SftpException, InterruptedException;
int getTelnetPortToOSGiConsole(Optional<Duration> optionalTimeout, byte[] privateKeyEncryptionPassphrase) throws NumberFormatException, JSchException, IOException, SftpException, InterruptedException;
/**
* @return the directory as an absolute path that can be used, e.g., in a {@link ChannelSftp} to change directory to
@@ -62,10 +63,11 @@ extends Process<RotatingFileBasedLog, MetricsT> {
* as the base name of the {@link #getServerDirectory() server's directory}. Often, it is also used as the name of
* the {@link Database}, at least when this is a master node, and the name of the RabbitMQ fan-out exchange used
* for replication.
* @param privateKeyEncryptionPassphrase TODO
*/
String getServerName(Optional<Duration> optionalTimeout) throws Exception;
String getServerName(Optional<Duration> optionalTimeout, byte[] privateKeyEncryptionPassphrase) throws Exception;
String getEnvSh(Optional<Duration> optionalTimeout) throws JSchException, IOException, SftpException, InterruptedException;
String getEnvSh(Optional<Duration> optionalTimeout, byte[] privateKeyEncryptionPassphrase) throws JSchException, IOException, SftpException, InterruptedException;
/**
* The URL path (everything following the hostname and starting with "/" but without any fragment) that can be
@@ -80,15 +82,17 @@ extends Process<RotatingFileBasedLog, MetricsT> {
/**
* Obtains the last definition of the process configuration variable specified, or {@code null} if that variable cannot be found
* in the evaluated {@code env.sh} file.
* @param privateKeyEncryptionPassphrase TODO
*/
String getEnvShValueFor(ProcessConfigurationVariable variable, Optional<Duration> optionalTimeout)
String getEnvShValueFor(ProcessConfigurationVariable variable, Optional<Duration> optionalTimeout, byte[] privateKeyEncryptionPassphrase)
throws JSchException, IOException, InterruptedException;
/**
* Obtains the last definition of the process configuration variable specified, or {@code null} if that variable isn't set
* by evaluating the {@code env.sh} file on the {@link #getHost() host}.
* @param privateKeyEncryptionPassphrase TODO
*/
String getEnvShValueFor(String variableName, Optional<Duration> optionalTimeout)
String getEnvShValueFor(String variableName, Optional<Duration> optionalTimeout, byte[] privateKeyEncryptionPassphrase)
throws JSchException, IOException, InterruptedException;
/**
@@ -45,16 +45,19 @@ implements ApplicationProcess<ShardingKey, MetricsT, ProcessT> {
/**
* Absolute path in the file system of the host on which this process is running and that represents
* this process's working directory. This directory is expected to contain a file named {@link #ENV_SH}
* whose contents can be obtained using the {@link #getEnvSh(Optional)} method.
* whose contents can be obtained using the {@link #getEnvSh(Optional, byte[])} method.
*/
private final String serverDirectory;
/**
* Alternative constructor that doesn't take the port number as argument but instead tries to obtain it from the {@link #ENV_SH env.sh} file
* located on the {@code host} in the {@code serverDirectory} specified.
* Alternative constructor that doesn't take the port number as argument but instead tries to obtain it from the
* {@link #ENV_SH env.sh} file located on the {@code host} in the {@code serverDirectory} specified.
*
* @param privateKeyEncryptionPassphrase
* the pass phrase for the private key that belongs to the instance's public key used for start-up
*/
public ApplicationProcessImpl(Host host, String serverDirectory, Optional<Duration> optionalTimeout) throws NumberFormatException, JSchException, IOException, InterruptedException {
this(readPortFromDirectory(host, serverDirectory, optionalTimeout), host, serverDirectory);
public ApplicationProcessImpl(Host host, String serverDirectory, Optional<Duration> optionalTimeout, byte[] privateKeyEncryptionPassphrase) throws NumberFormatException, JSchException, IOException, InterruptedException {
this(readPortFromDirectory(host, serverDirectory, optionalTimeout, privateKeyEncryptionPassphrase), host, serverDirectory);
}
public ApplicationProcessImpl(int port, Host host, String serverDirectory) {
@@ -62,14 +65,20 @@ implements ApplicationProcess<ShardingKey, MetricsT, ProcessT> {
this.serverDirectory = serverDirectory;
}
private static int readPortFromDirectory(Host host, String serverDirectory, Optional<Duration> optionalTimeout) throws NumberFormatException, JSchException, IOException, InterruptedException {
return Integer.parseInt(getEnvShValueFor(host, serverDirectory, DefaultProcessConfigurationVariables.SERVER_PORT.name(), optionalTimeout));
private static int readPortFromDirectory(Host host, String serverDirectory, Optional<Duration> optionalTimeout,
byte[] privateKeyEncryptionPassphrase)
throws NumberFormatException, JSchException, IOException, InterruptedException {
return Integer.parseInt(
getEnvShValueFor(host, serverDirectory, DefaultProcessConfigurationVariables.SERVER_PORT.name(),
optionalTimeout, privateKeyEncryptionPassphrase));
}
@Override
public Release getRelease(ReleaseRepository releaseRepository, Optional<Duration> optionalTimeout) throws JSchException, IOException, SftpException, InterruptedException {
public Release getRelease(ReleaseRepository releaseRepository, Optional<Duration> optionalTimeout,
byte[] privateKeyEncryptionPassphrase)
throws JSchException, IOException, SftpException, InterruptedException {
final Pattern pattern = Pattern.compile("^[^-]*-([^ ]*) System:");
final Matcher matcher = pattern.matcher(getVersionTxt(optionalTimeout));
final Matcher matcher = pattern.matcher(getVersionTxt(optionalTimeout, privateKeyEncryptionPassphrase));
final Release result;
if (matcher.find()) {
result = new ReleaseImpl(matcher.group(1), releaseRepository);
@@ -80,14 +89,18 @@ implements ApplicationProcess<ShardingKey, MetricsT, ProcessT> {
}
/**
* Under the path {@code configuration/jetty/version.txt} each release is expected to present a version string in the
* format <tt>{commitId}-{releaseName} System: {mongoDbUriWithoutSchemaOrHostPortDBName}-{expeditionPort}-{replicationHost}:{replicationPort}/{replicationChannel} Started: {yyyyMMddHHmm}</tt>.
* With this it is possible to infer the release that will be run upon the next process start, which is also the
* one running now if this process is currently running and no other release has been deployed since the process
* has started.
* Under the path {@code configuration/jetty/version.txt} each release is expected to present a version string in
* the format
* <tt>{commitId}-{releaseName} System: {mongoDbUriWithoutSchemaOrHostPortDBName}-{expeditionPort}-{replicationHost}:{replicationPort}/{replicationChannel} Started: {yyyyMMddHHmm}</tt>.
* With this it is possible to infer the release that will be run upon the next process start, which is also the one
* running now if this process is currently running and no other release has been deployed since the process has
* started.
*
* @param privateKeyEncryptionPassphrase
* the pass phrase for the private key that belongs to the instance's public key used for start-up
*/
private String getVersionTxt(Optional<Duration> optionalTimeout) throws JSchException, IOException, SftpException, InterruptedException {
return getFileContents(getServerDirectory()+"/"+VERSION_TXT, optionalTimeout);
private String getVersionTxt(Optional<Duration> optionalTimeout, byte[] privateKeyEncryptionPassphrase) throws JSchException, IOException, SftpException, InterruptedException {
return getFileContents(getServerDirectory()+"/"+VERSION_TXT, optionalTimeout, privateKeyEncryptionPassphrase);
}
@Override
@@ -97,8 +110,10 @@ implements ApplicationProcess<ShardingKey, MetricsT, ProcessT> {
}
@Override
public int getTelnetPortToOSGiConsole(Optional<Duration> optionalTimeout) throws NumberFormatException, JSchException, IOException, SftpException, InterruptedException {
return Integer.parseInt(getEnvShValueFor(DefaultProcessConfigurationVariables.TELNET_PORT, optionalTimeout));
public int getTelnetPortToOSGiConsole(Optional<Duration> optionalTimeout, byte[] privateKeyEncryptionPassphrase)
throws NumberFormatException, JSchException, IOException, SftpException, InterruptedException {
return Integer.parseInt(getEnvShValueFor(DefaultProcessConfigurationVariables.TELNET_PORT, optionalTimeout,
privateKeyEncryptionPassphrase));
}
/**
@@ -106,12 +121,15 @@ implements ApplicationProcess<ShardingKey, MetricsT, ProcessT> {
* by evaluating the {@code env.sh} file on the {@link #getHost() host}.
*/
@Override
public String getEnvShValueFor(String variableName, Optional<Duration> optionalTimeout) throws JSchException, IOException, InterruptedException {
return getEnvShValueFor(getHost(), getServerDirectory(), variableName, optionalTimeout);
public String getEnvShValueFor(String variableName, Optional<Duration> optionalTimeout,
byte[] privateKeyEncryptionPassphrase) throws JSchException, IOException, InterruptedException {
return getEnvShValueFor(getHost(), getServerDirectory(), variableName, optionalTimeout, privateKeyEncryptionPassphrase);
}
protected static String getEnvShValueFor(Host host, String serverDirectory, String variableName, Optional<Duration> optionalTimeout) throws JSchException, IOException, InterruptedException {
final SshCommandChannel sshChannel = host.createRootSshChannel(optionalTimeout);
protected static String getEnvShValueFor(Host host, String serverDirectory, String variableName,
Optional<Duration> optionalTimeout, byte[] privateKeyEncryptionPassphrase)
throws JSchException, IOException, InterruptedException {
final SshCommandChannel sshChannel = host.createRootSshChannel(optionalTimeout, privateKeyEncryptionPassphrase);
final String variableValue = sshChannel.runCommandAndReturnStdoutAndLogStderr(". "+getEnvShPath(serverDirectory)+">/dev/null 2>/dev/null; "+
"echo \"${"+variableName+"}\"", /* stderr prefix */ null, /* stderr log level */ null);
return variableValue.endsWith("\n") ? variableValue.substring(0, variableValue.length()-1) : variableValue;
@@ -122,8 +140,8 @@ implements ApplicationProcess<ShardingKey, MetricsT, ProcessT> {
* in the evaluated {@code env.sh} file.
*/
@Override
public String getEnvShValueFor(ProcessConfigurationVariable variable, Optional<Duration> optionalTimeout) throws JSchException, IOException, InterruptedException {
return getEnvShValueFor(variable.name(), optionalTimeout);
public String getEnvShValueFor(ProcessConfigurationVariable variable, Optional<Duration> optionalTimeout, byte[] privateKeyEncryptionPassphrase) throws JSchException, IOException, InterruptedException {
return getEnvShValueFor(variable.name(), optionalTimeout, privateKeyEncryptionPassphrase);
}
@Override
@@ -140,18 +158,18 @@ implements ApplicationProcess<ShardingKey, MetricsT, ProcessT> {
}
@Override
public String getServerName(Optional<Duration> optionalTimeout) throws Exception {
return getEnvShValueFor(DefaultProcessConfigurationVariables.SERVER_NAME, optionalTimeout);
public String getServerName(Optional<Duration> optionalTimeout, byte[] privateKeyEncryptionPassphrase) throws Exception {
return getEnvShValueFor(DefaultProcessConfigurationVariables.SERVER_NAME, optionalTimeout, privateKeyEncryptionPassphrase);
}
@Override
public String getEnvSh(Optional<Duration> optionalTimeout) throws JSchException, IOException, SftpException, InterruptedException {
return getFileContents(getEnvShPath(), optionalTimeout);
public String getEnvSh(Optional<Duration> optionalTimeout, byte[] privateKeyEncryptionPassphrase) throws JSchException, IOException, SftpException, InterruptedException {
return getFileContents(getEnvShPath(), optionalTimeout, privateKeyEncryptionPassphrase);
}
protected String getFileContents(String path, Optional<Duration> optionalTimeout)
protected String getFileContents(String path, Optional<Duration> optionalTimeout, byte[] privateKeyEncryptionPassphrase)
throws JSchException, IOException, SftpException, InterruptedException {
final ChannelSftp sftpChannel = getHost().createRootSftpChannel(optionalTimeout);
final ChannelSftp sftpChannel = getHost().createRootSftpChannel(optionalTimeout, privateKeyEncryptionPassphrase);
final ByteArrayOutputStream bos = new ByteArrayOutputStream();
sftpChannel.connect((int) optionalTimeout.orElse(Duration.NULL).asMillis());
sftpChannel.get(path, bos);
@@ -8,6 +8,7 @@ import com.jcraft.jsch.KeyPair;
import com.sap.sse.common.Named;
import com.sap.sse.common.TimePoint;
import com.sap.sse.common.impl.NamedImpl;
import com.sap.sse.landscape.Host;
/**
* Region ID and key name together form a unique key for AWS key pairs. The private key is always stored in an encrypted form,
@@ -55,13 +56,19 @@ public class SSHKeyPair extends NamedImpl implements Named {
}
/**
* Returns a {@link KeyPair} with decrypted private key that can be used to initiate an SSH session, e.g., using
* {@link Host#createSshChannel(String, java.util.Optional, byte[])}.
*
* @param passphrase
* used to {@link KeyPair#decrypt(byte[]) decrypt} the encrypted private key so that the resulting key
* pair can be used to initiate sessions.
* pair can be used to initiate sessions. Has to equal the passphrase used when encrypting the private key,
* e.g., when calling {@link #SSHKeyPair(String, String, TimePoint, String, byte[], byte[], byte[])}.
*/
public KeyPair getKeyPair(JSch jsch, byte[] passphrase) throws JSchException {
final KeyPair result = KeyPair.load(jsch, getEncryptedPrivateKey(), getPublicKey());
result.decrypt(passphrase);
if (!result.decrypt(passphrase)) {
throw new IllegalStateException("Could not decrypt private key of "+this+"; probably incorrect passphrase?");
}
return result;
}