1 package org
.argeo
.cms
.internal
.runtime
;
3 import java
.io
.BufferedInputStream
;
4 import java
.io
.IOException
;
6 import java
.net
.InetAddress
;
8 import java
.net
.UnknownHostException
;
9 import java
.nio
.charset
.StandardCharsets
;
10 import java
.nio
.file
.Files
;
11 import java
.nio
.file
.Path
;
12 import java
.nio
.file
.Paths
;
13 import java
.nio
.file
.attribute
.PosixFilePermission
;
14 import java
.security
.KeyStore
;
15 import java
.util
.ArrayList
;
16 import java
.util
.Arrays
;
17 import java
.util
.Collections
;
18 import java
.util
.HashMap
;
19 import java
.util
.HashSet
;
20 import java
.util
.List
;
21 import java
.util
.Locale
;
23 import java
.util
.Objects
;
25 import java
.util
.StringJoiner
;
26 import java
.util
.UUID
;
27 import java
.util
.concurrent
.ExecutionException
;
28 import java
.util
.concurrent
.ForkJoinPool
;
29 import java
.util
.concurrent
.ForkJoinTask
;
30 import java
.util
.concurrent
.TimeUnit
;
31 import java
.util
.concurrent
.TimeoutException
;
33 import javax
.security
.auth
.login
.Configuration
;
35 import org
.argeo
.api
.cms
.CmsConstants
;
36 import org
.argeo
.api
.cms
.CmsLog
;
37 import org
.argeo
.api
.cms
.CmsState
;
38 import org
.argeo
.api
.uuid
.UuidFactory
;
39 import org
.argeo
.cms
.CmsDeployProperty
;
40 import org
.argeo
.cms
.auth
.ident
.IdentClient
;
41 import org
.argeo
.cms
.util
.FsUtils
;
44 * Implementation of a {@link CmsState}, initialising the required services.
46 public class CmsStateImpl
implements CmsState
{
47 private final static CmsLog log
= CmsLog
.getLog(CmsStateImpl
.class);
50 private Long availableSince
;
53 // private final boolean cleanState;
54 private String hostname
;
56 private UuidFactory uuidFactory
;
58 private final Map
<CmsDeployProperty
, String
> deployPropertyDefaults
;
60 public CmsStateImpl() {
61 this.deployPropertyDefaults
= Collections
.unmodifiableMap(createDeployPropertiesDefaults());
64 protected Map
<CmsDeployProperty
, String
> createDeployPropertiesDefaults() {
65 Map
<CmsDeployProperty
, String
> deployPropertyDefaults
= new HashMap
<>();
66 deployPropertyDefaults
.put(CmsDeployProperty
.NODE_INIT
, "../../init");
67 deployPropertyDefaults
.put(CmsDeployProperty
.LOCALE
, Locale
.getDefault().toString());
70 deployPropertyDefaults
.put(CmsDeployProperty
.SSL_KEYSTORETYPE
, KernelConstants
.PKCS12
);
71 deployPropertyDefaults
.put(CmsDeployProperty
.SSL_PASSWORD
, KernelConstants
.DEFAULT_KEYSTORE_PASSWORD
);
72 Path keyStorePath
= getDataPath(KernelConstants
.DEFAULT_KEYSTORE_PATH
);
73 if (keyStorePath
!= null) {
74 deployPropertyDefaults
.put(CmsDeployProperty
.SSL_KEYSTORE
, keyStorePath
.toAbsolutePath().toString());
77 Path trustStorePath
= getDataPath(KernelConstants
.DEFAULT_TRUSTSTORE_PATH
);
78 if (trustStorePath
!= null) {
79 deployPropertyDefaults
.put(CmsDeployProperty
.SSL_TRUSTSTORE
, trustStorePath
.toAbsolutePath().toString());
81 deployPropertyDefaults
.put(CmsDeployProperty
.SSL_TRUSTSTORETYPE
, KernelConstants
.PKCS12
);
82 deployPropertyDefaults
.put(CmsDeployProperty
.SSL_TRUSTSTOREPASSWORD
, KernelConstants
.DEFAULT_KEYSTORE_PASSWORD
);
85 Path authorizedKeysPath
= getDataPath(KernelConstants
.NODE_SSHD_AUTHORIZED_KEYS_PATH
);
86 if (authorizedKeysPath
!= null) {
87 deployPropertyDefaults
.put(CmsDeployProperty
.SSHD_AUTHORIZEDKEYS
,
88 authorizedKeysPath
.toAbsolutePath().toString());
90 return deployPropertyDefaults
;
94 // Runtime.getRuntime().addShutdownHook(new CmsShutdown());
100 if (log
.isTraceEnabled())
101 log
.trace("CMS State started");
103 // String stateUuidStr = KernelUtils.getFrameworkProp(Constants.FRAMEWORK_UUID);
104 // this.uuid = UUID.fromString(stateUuidStr);
105 this.uuid
= uuidFactory
.timeUUID();
106 // this.cleanState = stateUuid.equals(frameworkUuid);
109 this.hostname
= getDeployProperty(CmsDeployProperty
.HOST
);
110 // TODO verify we have access to the IP address
111 if (hostname
== null) {
112 final String LOCALHOST_IP
= "::1";
113 ForkJoinTask
<String
> hostnameFJT
= ForkJoinPool
.commonPool().submit(() -> {
115 String hostname
= InetAddress
.getLocalHost().getHostName();
117 } catch (UnknownHostException e
) {
118 throw new IllegalStateException("Cannot get local hostname", e
);
122 this.hostname
= hostnameFJT
.get(5, TimeUnit
.SECONDS
);
123 } catch (InterruptedException
| ExecutionException
| TimeoutException e
) {
124 this.hostname
= LOCALHOST_IP
;
125 log
.warn("Could not get local hostname, using " + this.hostname
);
129 availableSince
= System
.currentTimeMillis();
130 if (log
.isDebugEnabled()) {
131 // log.debug("## CMS starting... stateUuid=" + this.stateUuid + (cleanState ? "
132 // (clean state) " : " "));
133 StringJoiner sb
= new StringJoiner("\n");
134 CmsDeployProperty
[] deployProperties
= CmsDeployProperty
.values();
135 Arrays
.sort(deployProperties
, (o1
, o2
) -> o1
.name().compareTo(o2
.name()));
136 for (CmsDeployProperty deployProperty
: deployProperties
) {
137 List
<String
> values
= getDeployProperties(deployProperty
);
138 for (int i
= 0; i
< values
.size(); i
++) {
139 String value
= values
.get(i
);
141 boolean isDefault
= deployPropertyDefaults
.containsKey(deployProperty
)
142 && value
.equals(deployPropertyDefaults
.get(deployProperty
));
143 String line
= deployProperty
.getProperty() + (i
== 0 ?
"" : "." + i
) + "=" + value
144 + (isDefault ?
" (default)" : "");
149 log
.debug("## CMS starting... (" + uuid
+ ")\n" + sb
+ "\n");
152 Path privateBase
= getDataPath(KernelConstants
.DIR_PRIVATE
);
153 if (privateBase
!= null && !Files
.exists(privateBase
)) {// first init
155 Files
.createDirectories(privateBase
);
158 } catch (RuntimeException
| IOException e
) {
159 log
.error("## FATAL: CMS state failed", e
);
163 private void initSecurity() {
164 // private directory permissions
165 Path privateDir
= KernelUtils
.getOsgiInstancePath(KernelConstants
.DIR_PRIVATE
);
166 if (privateDir
!= null) {
167 // TODO rather check whether we can read and write
168 Set
<PosixFilePermission
> posixPermissions
= new HashSet
<>();
169 posixPermissions
.add(PosixFilePermission
.OWNER_READ
);
170 posixPermissions
.add(PosixFilePermission
.OWNER_WRITE
);
171 posixPermissions
.add(PosixFilePermission
.OWNER_EXECUTE
);
173 if (!Files
.exists(privateDir
))
174 Files
.createDirectories(privateDir
);
175 Files
.setPosixFilePermissions(privateDir
, posixPermissions
);
176 } catch (IOException e
) {
177 log
.error("Cannot set permissions on " + privateDir
, e
);
181 if (getDeployProperty(CmsDeployProperty
.JAVA_LOGIN_CONFIG
) == null) {
182 String jaasConfig
= KernelConstants
.JAAS_CONFIG
;
183 URL url
= getClass().getResource(jaasConfig
);
184 // System.setProperty(KernelConstants.JAAS_CONFIG_PROP,
185 // url.toExternalForm());
186 KernelUtils
.setJaasConfiguration(url
);
188 // explicitly load JAAS configuration
189 Configuration
.getConfiguration();
191 boolean initSsl
= getDeployProperty(CmsDeployProperty
.HTTPS_PORT
) != null;
197 private void initCertificates() {
198 // server certificate
199 Path keyStorePath
= Paths
.get(getDeployProperty(CmsDeployProperty
.SSL_KEYSTORE
));
200 Path pemKeyPath
= getDataPath(KernelConstants
.DEFAULT_PEM_KEY_PATH
);
201 Path pemCertPath
= getDataPath(KernelConstants
.DEFAULT_PEM_CERT_PATH
);
202 char[] keyStorePassword
= getDeployProperty(CmsDeployProperty
.SSL_PASSWORD
).toCharArray();
205 // if PEM files both exists, update the PKCS12 file
206 if (Files
.exists(pemCertPath
) && Files
.exists(pemKeyPath
)) {
207 // TODO check certificate update time? monitor changes?
208 KeyStore keyStore
= PkiUtils
.getKeyStore(keyStorePath
, keyStorePassword
,
209 getDeployProperty(CmsDeployProperty
.SSL_KEYSTORETYPE
));
210 try (Reader key
= Files
.newBufferedReader(pemKeyPath
, StandardCharsets
.US_ASCII
);
211 BufferedInputStream cert
= new BufferedInputStream(Files
.newInputStream(pemCertPath
));) {
212 PkiUtils
.loadPrivateCertificatePem(keyStore
, CmsConstants
.NODE
, key
, keyStorePassword
, cert
);
213 Files
.createDirectories(keyStorePath
.getParent());
214 PkiUtils
.saveKeyStore(keyStorePath
, keyStorePassword
, keyStore
);
215 if (log
.isDebugEnabled())
216 log
.debug("PEM certificate stored in " + keyStorePath
);
217 } catch (IOException e
) {
218 log
.error("Cannot read PEM files " + pemKeyPath
+ " and " + pemCertPath
, e
);
223 Path trustStorePath
= Paths
.get(getDeployProperty(CmsDeployProperty
.SSL_TRUSTSTORE
));
224 char[] trustStorePassword
= getDeployProperty(CmsDeployProperty
.SSL_TRUSTSTOREPASSWORD
).toCharArray();
227 Path ipaCaCertPath
= Paths
.get(KernelConstants
.IPA_PEM_CA_CERT_PATH
);
228 if (Files
.exists(ipaCaCertPath
)) {
229 KeyStore trustStore
= PkiUtils
.getKeyStore(trustStorePath
, trustStorePassword
,
230 getDeployProperty(CmsDeployProperty
.SSL_TRUSTSTORETYPE
));
231 try (BufferedInputStream cert
= new BufferedInputStream(Files
.newInputStream(ipaCaCertPath
));) {
232 PkiUtils
.loadTrustedCertificatePem(trustStore
, trustStorePassword
, cert
);
233 Files
.createDirectories(keyStorePath
.getParent());
234 PkiUtils
.saveKeyStore(trustStorePath
, trustStorePassword
, trustStore
);
235 if (log
.isDebugEnabled())
236 log
.debug("IPA CA certificate stored in " + trustStorePath
);
237 } catch (IOException e
) {
238 log
.error("Cannot trust CA certificate", e
);
242 // if (!Files.exists(keyStorePath))
243 // PkiUtils.createSelfSignedKeyStore(keyStorePath, keyStorePassword, PkiUtils.PKCS12);
247 if (log
.isDebugEnabled())
248 log
.debug("CMS stopping... (" + this.uuid
+ ")");
250 long duration
= ((System
.currentTimeMillis() - availableSince
) / 1000) / 60;
251 log
.info("## ARGEO CMS STOPPED after " + (duration
/ 60) + "h " + (duration
% 60) + "min uptime ##");
254 private void firstInit() throws IOException
{
255 log
.info("## FIRST INIT ##");
256 List
<String
> nodeInits
= getDeployProperties(CmsDeployProperty
.NODE_INIT
);
257 // if (nodeInits == null)
258 // nodeInits = "../../init";
259 CmsStateImpl
.prepareFirstInitInstanceArea(nodeInits
);
263 public String
getDeployProperty(String property
) {
264 CmsDeployProperty deployProperty
= CmsDeployProperty
.find(property
);
265 if (deployProperty
== null) {
267 if (property
.startsWith("argeo.node.")) {
268 return doGetDeployProperty(property
);
270 if (property
.equals("argeo.i18n.locales")) {
271 String value
= doGetDeployProperty(property
);
273 log
.warn("Property " + property
+ " was ignored (value=" + value
+ ")");
278 throw new IllegalArgumentException("Unsupported deploy property " + property
);
280 int index
= CmsDeployProperty
.getPropertyIndex(property
);
281 return getDeployProperty(deployProperty
, index
);
285 public List
<String
> getDeployProperties(String property
) {
286 CmsDeployProperty deployProperty
= CmsDeployProperty
.find(property
);
287 if (deployProperty
== null)
288 return new ArrayList
<>();
289 return getDeployProperties(deployProperty
);
292 public static List
<String
> getDeployProperties(CmsState cmsState
, CmsDeployProperty deployProperty
) {
293 return ((CmsStateImpl
) cmsState
).getDeployProperties(deployProperty
);
296 public List
<String
> getDeployProperties(CmsDeployProperty deployProperty
) {
297 List
<String
> res
= new ArrayList
<>(deployProperty
.getMaxCount());
298 for (int i
= 0; i
< deployProperty
.getMaxCount(); i
++) {
299 // String propertyName = i == 0 ? deployProperty.getProperty() :
300 // deployProperty.getProperty() + "." + i;
301 String value
= getDeployProperty(deployProperty
, i
);
307 public static String
getDeployProperty(CmsState cmsState
, CmsDeployProperty deployProperty
) {
308 return ((CmsStateImpl
) cmsState
).getDeployProperty(deployProperty
);
311 public String
getDeployProperty(CmsDeployProperty deployProperty
) {
312 String value
= getDeployProperty(deployProperty
, 0);
316 public String
getDeployProperty(CmsDeployProperty deployProperty
, int index
) {
317 String propertyName
= deployProperty
.getProperty() + (index
== 0 ?
"" : "." + index
);
318 String value
= doGetDeployProperty(propertyName
);
319 if (value
== null && index
== 0) {
321 if (deployPropertyDefaults
.containsKey(deployProperty
)) {
322 value
= deployPropertyDefaults
.get(deployProperty
);
323 if (deployProperty
.isSystemPropertyOnly())
324 System
.setProperty(deployProperty
.getProperty(), value
);
328 // try legacy properties
329 String legacyProperty
= switch (deployProperty
) {
330 case DIRECTORY
-> "argeo.node.useradmin.uris";
331 case DB_URL
-> "argeo.node.dburl";
332 case DB_USER
-> "argeo.node.dbuser";
333 case DB_PASSWORD
-> "argeo.node.dbpassword";
334 case HTTP_PORT
-> "org.osgi.service.http.port";
335 case HTTPS_PORT
-> "org.osgi.service.http.port.secure";
336 case HOST
-> "org.eclipse.equinox.http.jetty.http.host";
337 case LOCALE
-> "argeo.i18n.defaultLocale";
341 if (legacyProperty
!= null) {
342 value
= doGetDeployProperty(legacyProperty
);
344 log
.warn("Retrieved deploy property " + deployProperty
.getProperty()
345 + " through deprecated property " + legacyProperty
);
350 if (index
== 0 && deployProperty
.isSystemPropertyOnly()) {
351 String systemPropertyValue
= System
.getProperty(deployProperty
.getProperty());
352 if (!Objects
.equals(value
, systemPropertyValue
))
353 throw new IllegalStateException(
354 "Property " + deployProperty
+ " must be a ssystem property, but its value is " + value
355 + ", while the system property value is " + systemPropertyValue
);
357 return value
!= null ? value
.toString() : null;
360 protected String
getLegacyProperty(String legacyProperty
, CmsDeployProperty deployProperty
) {
361 String value
= doGetDeployProperty(legacyProperty
);
363 log
.warn("Retrieved deploy property " + deployProperty
.getProperty() + " through deprecated property "
364 + legacyProperty
+ ".");
369 protected String
doGetDeployProperty(String property
) {
370 return KernelUtils
.getFrameworkProp(property
);
374 public Path
getDataPath(String relativePath
) {
375 return KernelUtils
.getOsgiInstancePath(relativePath
);
379 public Long
getAvailableSince() {
380 return availableSince
;
387 public UUID
getUuid() {
391 public void setUuidFactory(UuidFactory uuidFactory
) {
392 this.uuidFactory
= uuidFactory
;
395 public String
getHostname() {
400 * Called before node initialisation, in order populate OSGi instance are with
401 * some files (typically LDIF, etc).
403 public static void prepareFirstInitInstanceArea(List
<String
> nodeInits
) {
405 for (String nodeInit
: nodeInits
) {
406 if (nodeInit
== null)
409 if (nodeInit
.startsWith("http")) {
411 // registerRemoteInit(nodeInit);
414 // TODO use java.nio.file
416 if (nodeInit
.startsWith("."))
417 initDir
= KernelUtils
.getExecutionDir(nodeInit
);
419 initDir
= Paths
.get(nodeInit
);
420 // TODO also uncompress archives
421 if (Files
.exists(initDir
)) {
422 Path dataPath
= KernelUtils
.getOsgiInstancePath("");
423 FsUtils
.copyDirectory(initDir
, dataPath
);
424 log
.info("CMS initialized from " + initDir
);
433 public static IdentClient
getIdentClient(String remoteAddr
) {
434 if (!IdentClient
.isDefaultAuthdPassphraseFileAvailable())
436 // TODO make passphrase more configurable
437 return new IdentClient(remoteAddr
);