From: Mathieu Baudier Date: Mon, 27 Jun 2022 15:50:11 +0000 (+0200) Subject: Disable OSGi configuration admin and LDIF-based deploy config. X-Git-Tag: v2.3.10~155 X-Git-Url: https://git.argeo.org/?a=commitdiff_plain;h=336930c69f0cd3e1242e518479624c6366541275;p=lgpl%2Fargeo-commons.git Disable OSGi configuration admin and LDIF-based deploy config. --- diff --git a/eclipse/org.argeo.cms.servlet/OSGI-INF/jettyServiceFactory.xml b/eclipse/org.argeo.cms.servlet/OSGI-INF/jettyServiceFactory.xml index c007351aa..c2cf1c73b 100644 --- a/eclipse/org.argeo.cms.servlet/OSGI-INF/jettyServiceFactory.xml +++ b/eclipse/org.argeo.cms.servlet/OSGI-INF/jettyServiceFactory.xml @@ -1,8 +1,6 @@ - - - - - + + + diff --git a/eclipse/org.argeo.cms.servlet/src/org/argeo/cms/servlet/CmsServletContext.java b/eclipse/org.argeo.cms.servlet/src/org/argeo/cms/servlet/CmsServletContext.java index 1ae6286ac..cc2bc02d1 100644 --- a/eclipse/org.argeo.cms.servlet/src/org/argeo/cms/servlet/CmsServletContext.java +++ b/eclipse/org.argeo.cms.servlet/src/org/argeo/cms/servlet/CmsServletContext.java @@ -76,7 +76,9 @@ public class CmsServletContext extends ServletContextHelper { protected LoginContext processUnauthorized(HttpServletRequest request, HttpServletResponse response) { // anonymous + ClassLoader currentContextClassLoader = Thread.currentThread().getContextClassLoader(); try { + Thread.currentThread().setContextClassLoader(CmsServletContext.class.getClassLoader()); LoginContext lc = new LoginContext(CmsAuth.LOGIN_CONTEXT_ANONYMOUS, new RemoteAuthCallbackHandler(new ServletHttpRequest(request), new ServletHttpResponse(response))); lc.login(); @@ -85,6 +87,8 @@ public class CmsServletContext extends ServletContextHelper { if (log.isDebugEnabled()) log.error("Cannot log in as anonymous", e1); return null; + } finally { + Thread.currentThread().setContextClassLoader(currentContextClassLoader); } } diff --git a/eclipse/org.argeo.cms.servlet/src/org/argeo/cms/servlet/internal/jetty/InternalHttpConstants.java b/eclipse/org.argeo.cms.servlet/src/org/argeo/cms/servlet/internal/jetty/InternalHttpConstants.java new file mode 100644 index 000000000..462bf7a6b --- /dev/null +++ b/eclipse/org.argeo.cms.servlet/src/org/argeo/cms/servlet/internal/jetty/InternalHttpConstants.java @@ -0,0 +1,23 @@ +package org.argeo.cms.servlet.internal.jetty; + +/** Compatible with Jetty. */ +public interface InternalHttpConstants { + static final String HTTP_ENABLED = "http.enabled"; + static final String HTTP_PORT = "http.port"; + static final String HTTP_HOST = "http.host"; + static final String HTTPS_ENABLED = "https.enabled"; + static final String HTTPS_HOST = "https.host"; + static final String HTTPS_PORT = "https.port"; + static final String SSL_KEYSTORE = "ssl.keystore"; + static final String SSL_PASSWORD = "ssl.password"; + static final String SSL_KEYPASSWORD = "ssl.keypassword"; + static final String SSL_NEEDCLIENTAUTH = "ssl.needclientauth"; + static final String SSL_WANTCLIENTAUTH = "ssl.wantclientauth"; + static final String SSL_PROTOCOL = "ssl.protocol"; + static final String SSL_ALGORITHM = "ssl.algorithm"; + static final String SSL_KEYSTORETYPE = "ssl.keystoretype"; + static final String JETTY_PROPERTY_PREFIX = "org.eclipse.equinox.http.jetty."; + // Argeo specific + static final String WEBSOCKET_ENABLED = "websocket.enabled"; + +} diff --git a/eclipse/org.argeo.cms.servlet/src/org/argeo/cms/servlet/internal/jetty/JettyConfig.java b/eclipse/org.argeo.cms.servlet/src/org/argeo/cms/servlet/internal/jetty/JettyConfig.java new file mode 100644 index 000000000..026993eab --- /dev/null +++ b/eclipse/org.argeo.cms.servlet/src/org/argeo/cms/servlet/internal/jetty/JettyConfig.java @@ -0,0 +1,231 @@ +package org.argeo.cms.servlet.internal.jetty; + +import java.io.IOException; +import java.io.Reader; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.security.KeyStore; +import java.util.Dictionary; +import java.util.Hashtable; +import java.util.Map; +import java.util.concurrent.ForkJoinPool; + +import javax.websocket.DeploymentException; +import javax.websocket.server.ServerContainer; +import javax.websocket.server.ServerEndpointConfig; + +import org.argeo.api.cms.CmsConstants; +import org.argeo.api.cms.CmsLog; +import org.argeo.api.cms.CmsState; +import org.argeo.cms.security.PkiUtils; +import org.argeo.cms.websocket.javax.server.CmsWebSocketConfigurator; +import org.argeo.cms.websocket.javax.server.TestEndpoint; +import org.argeo.util.LangUtils; +import org.eclipse.equinox.http.jetty.JettyConfigurator; +import org.osgi.framework.BundleContext; +import org.osgi.framework.FrameworkUtil; +import org.osgi.framework.ServiceReference; +import org.osgi.util.tracker.ServiceTracker; + +public class JettyConfig { + private final static CmsLog log = CmsLog.getLog(JettyConfig.class); + + final static String CMS_JETTY_CUSTOMIZER_CLASS = "org.argeo.equinox.jetty.CmsJettyCustomizer"; + // Argeo specific + final static String WEBSOCKET_ENABLED = "websocket.enabled"; + + private CmsState cmsState; + + private final BundleContext bc = FrameworkUtil.getBundle(JettyConfig.class).getBundleContext(); + + public void start() { + // We need to start asynchronously so that Jetty bundle get started by lazy init + // due to the non-configurable behaviour of its activator + ForkJoinPool.commonPool().execute(() -> { + Dictionary properties = getHttpServerConfig(); + startServer(properties); + }); + + ServiceTracker serverSt = new ServiceTracker( + bc, ServerContainer.class, null) { + + @Override + public ServerContainer addingService(ServiceReference reference) { + ServerContainer serverContainer = super.addingService(reference); + + BundleContext bc = reference.getBundle().getBundleContext(); + ServiceReference srConfigurator = bc + .getServiceReference(ServerEndpointConfig.Configurator.class); + ServerEndpointConfig.Configurator endpointConfigurator = bc.getService(srConfigurator); + ServerEndpointConfig config = ServerEndpointConfig.Builder + .create(TestEndpoint.class, "/ws/test/events/").configurator(endpointConfigurator).build(); + try { + serverContainer.addEndpoint(config); + } catch (DeploymentException e) { + throw new IllegalStateException("Cannot initalise the WebSocket server runtime.", e); + } + return serverContainer; + } + + }; + serverSt.open(); + } + + public void stop() { + try { + JettyConfigurator.stopServer(CmsConstants.DEFAULT); + } catch (Exception e) { + log.error("Cannot stop default Jetty server.", e); + } + + } + + public void startServer(Dictionary properties) { + // Explicitly configures Jetty so that the default server is not started by the + // activator of the Equinox Jetty bundle. + Map config = LangUtils.dictToStringMap(properties); + if (!config.isEmpty()) { + config.put("customizer.class", CMS_JETTY_CUSTOMIZER_CLASS); + + // TODO centralise with Jetty extender + Object webSocketEnabled = config.get(WEBSOCKET_ENABLED); + if (webSocketEnabled != null && webSocketEnabled.toString().equals("true")) { + bc.registerService(ServerEndpointConfig.Configurator.class, new CmsWebSocketConfigurator(), null); + config.put(WEBSOCKET_ENABLED, "true"); + } + } + + int tryCount = 30; + try { + tryGettyJetty: while (tryCount > 0) { + try { + // FIXME deal with multiple ids + JettyConfigurator.startServer(CmsConstants.DEFAULT, new Hashtable<>(config)); + + Object httpPort = config.get(InternalHttpConstants.HTTP_PORT); + Object httpsPort = config.get(InternalHttpConstants.HTTPS_PORT); + log.info(httpPortsMsg(httpPort, httpsPort)); + + // Explicitly starts Jetty OSGi HTTP bundle, so that it gets triggered if OSGi + // configuration is not cleaned + FrameworkUtil.getBundle(JettyConfigurator.class).start(); + break tryGettyJetty; + } catch (IllegalStateException e) { + // e.printStackTrace(); + // Jetty may not be ready + try { + Thread.sleep(1000); + } catch (Exception e1) { + // silent + } + tryCount--; + } + } + } catch (Exception e) { + log.error("Cannot start default Jetty server with config " + properties, e); + } + + } + + private String httpPortsMsg(Object httpPort, Object httpsPort) { + return (httpPort != null ? "HTTP " + httpPort + " " : " ") + (httpsPort != null ? "HTTPS " + httpsPort : ""); + } + + /** Override the provided config with the framework properties */ + public Dictionary getHttpServerConfig() { + String httpPort = getFrameworkProp("org.osgi.service.http.port"); + String httpsPort = getFrameworkProp("org.osgi.service.http.port.secure"); + /// TODO make it more generic + String httpHost = getFrameworkProp( + InternalHttpConstants.JETTY_PROPERTY_PREFIX + InternalHttpConstants.HTTP_HOST); + String httpsHost = getFrameworkProp( + InternalHttpConstants.JETTY_PROPERTY_PREFIX + InternalHttpConstants.HTTPS_HOST); + String webSocketEnabled = getFrameworkProp( + InternalHttpConstants.JETTY_PROPERTY_PREFIX + InternalHttpConstants.WEBSOCKET_ENABLED); + + final Hashtable props = new Hashtable(); + // try { + if (httpPort != null || httpsPort != null) { + boolean httpEnabled = httpPort != null; + props.put(InternalHttpConstants.HTTP_ENABLED, httpEnabled); + boolean httpsEnabled = httpsPort != null; + props.put(InternalHttpConstants.HTTPS_ENABLED, httpsEnabled); + + if (httpEnabled) { + props.put(InternalHttpConstants.HTTP_PORT, httpPort); + if (httpHost != null) + props.put(InternalHttpConstants.HTTP_HOST, httpHost); + } + + if (httpsEnabled) { + props.put(InternalHttpConstants.HTTPS_PORT, httpsPort); + if (httpsHost != null) + props.put(InternalHttpConstants.HTTPS_HOST, httpsHost); + + // server certificate + Path keyStorePath = cmsState.getDataPath(PkiUtils.DEFAULT_KEYSTORE_PATH); + Path pemKeyPath = cmsState.getDataPath(PkiUtils.DEFAULT_PEM_KEY_PATH); + Path pemCertPath = cmsState.getDataPath(PkiUtils.DEFAULT_PEM_CERT_PATH); + String keyStorePasswordStr = getFrameworkProp( + InternalHttpConstants.JETTY_PROPERTY_PREFIX + InternalHttpConstants.SSL_PASSWORD); + char[] keyStorePassword; + if (keyStorePasswordStr == null) + keyStorePassword = "changeit".toCharArray(); + else + keyStorePassword = keyStorePasswordStr.toCharArray(); + + // if PEM files both exists, update the PKCS12 file + if (Files.exists(pemCertPath) && Files.exists(pemKeyPath)) { + // TODO check certificate update time? monitor changes? + KeyStore keyStore = PkiUtils.getKeyStore(keyStorePath, keyStorePassword, PkiUtils.PKCS12); + try (Reader key = Files.newBufferedReader(pemKeyPath, StandardCharsets.US_ASCII); + Reader cert = Files.newBufferedReader(pemCertPath, StandardCharsets.US_ASCII);) { + PkiUtils.loadPem(keyStore, key, keyStorePassword, cert); + PkiUtils.saveKeyStore(keyStorePath, keyStorePassword, keyStore); + if (log.isDebugEnabled()) + log.debug("PEM certificate stored in " + keyStorePath); + } catch (IOException e) { + log.error("Cannot read PEM files " + pemKeyPath + " and " + pemCertPath, e); + } + } + + if (!Files.exists(keyStorePath)) + PkiUtils.createSelfSignedKeyStore(keyStorePath, keyStorePassword, PkiUtils.PKCS12); + props.put(InternalHttpConstants.SSL_KEYSTORETYPE, PkiUtils.PKCS12); + props.put(InternalHttpConstants.SSL_KEYSTORE, keyStorePath.toString()); + props.put(InternalHttpConstants.SSL_PASSWORD, new String(keyStorePassword)); + +// props.put(InternalHttpConstants.SSL_KEYSTORETYPE, "PKCS11"); +// props.put(InternalHttpConstants.SSL_KEYSTORE, "../../nssdb"); +// props.put(InternalHttpConstants.SSL_PASSWORD, keyStorePassword); + + // client certificate authentication + String wantClientAuth = getFrameworkProp( + InternalHttpConstants.JETTY_PROPERTY_PREFIX + InternalHttpConstants.SSL_WANTCLIENTAUTH); + if (wantClientAuth != null) + props.put(InternalHttpConstants.SSL_WANTCLIENTAUTH, Boolean.parseBoolean(wantClientAuth)); + String needClientAuth = getFrameworkProp( + InternalHttpConstants.JETTY_PROPERTY_PREFIX + InternalHttpConstants.SSL_NEEDCLIENTAUTH); + if (needClientAuth != null) + props.put(InternalHttpConstants.SSL_NEEDCLIENTAUTH, Boolean.parseBoolean(needClientAuth)); + } + + // web socket + if (webSocketEnabled != null && webSocketEnabled.equals("true")) + props.put(InternalHttpConstants.WEBSOCKET_ENABLED, true); + + props.put(CmsConstants.CN, CmsConstants.DEFAULT); + } + return props; + } + + private String getFrameworkProp(String key) { + return cmsState.getDeployProperty(key); + } + + public void setCmsState(CmsState cmsState) { + this.cmsState = cmsState; + } + +} diff --git a/jcr/org.argeo.cms.jcr/OSGI-INF/jcrDeployment.xml b/jcr/org.argeo.cms.jcr/OSGI-INF/jcrDeployment.xml index a94b15168..033ddbd2f 100644 --- a/jcr/org.argeo.cms.jcr/OSGI-INF/jcrDeployment.xml +++ b/jcr/org.argeo.cms.jcr/OSGI-INF/jcrDeployment.xml @@ -1,5 +1,4 @@ - diff --git a/jcr/org.argeo.cms.jcr/OSGI-INF/repositoryContextsFactory.xml b/jcr/org.argeo.cms.jcr/OSGI-INF/repositoryContextsFactory.xml index db2bfaa26..2d06acad5 100644 --- a/jcr/org.argeo.cms.jcr/OSGI-INF/repositoryContextsFactory.xml +++ b/jcr/org.argeo.cms.jcr/OSGI-INF/repositoryContextsFactory.xml @@ -2,8 +2,5 @@ - - - - + diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/internal/jcr/JcrInitUtils.java b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/internal/jcr/JcrInitUtils.java index 0536fb645..a1cba1a62 100644 --- a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/internal/jcr/JcrInitUtils.java +++ b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/internal/jcr/JcrInitUtils.java @@ -22,30 +22,31 @@ import org.osgi.framework.Constants; import org.osgi.framework.FrameworkUtil; /** JCR specific init utilities. */ +@Deprecated public class JcrInitUtils { private final static CmsLog log = CmsLog.getLog(JcrInitUtils.class); private final static BundleContext bundleContext = FrameworkUtil.getBundle(JcrInitUtils.class).getBundleContext(); - public static void addToDeployment(CmsDeployment nodeDeployment) { - // node repository -// Dictionary provided = null; - Dictionary provided = nodeDeployment.getProps(CmsConstants.NODE_REPOS_FACTORY_PID, - CmsConstants.NODE); - Dictionary nodeConfig = JcrInitUtils.getNodeRepositoryConfig(provided); - // node repository is mandatory - nodeDeployment.addFactoryDeployConfig(CmsConstants.NODE_REPOS_FACTORY_PID, nodeConfig); - - // additional repositories -// dataModels: for (DataModels.DataModel dataModel : dataModels.getNonAbstractDataModels()) { -// if (NodeConstants.NODE_REPOSITORY.equals(dataModel.getName())) -// continue dataModels; -// Dictionary config = JcrInitUtils.getRepositoryConfig(dataModel.getName(), -// getProps(NodeConstants.NODE_REPOS_FACTORY_PID, dataModel.getName())); -// if (config.size() != 0) -// putFactoryDeployConfig(NodeConstants.NODE_REPOS_FACTORY_PID, config); -// } - - } +// public static void addToDeployment(CmsDeployment nodeDeployment) { +// // node repository +//// Dictionary provided = null; +// Dictionary provided = nodeDeployment.getProps(CmsConstants.NODE_REPOS_FACTORY_PID, +// CmsConstants.NODE); +// Dictionary nodeConfig = JcrInitUtils.getNodeRepositoryConfig(provided); +// // node repository is mandatory +// nodeDeployment.addFactoryDeployConfig(CmsConstants.NODE_REPOS_FACTORY_PID, nodeConfig); +// +// // additional repositories +//// dataModels: for (DataModels.DataModel dataModel : dataModels.getNonAbstractDataModels()) { +//// if (NodeConstants.NODE_REPOSITORY.equals(dataModel.getName())) +//// continue dataModels; +//// Dictionary config = JcrInitUtils.getRepositoryConfig(dataModel.getName(), +//// getProps(NodeConstants.NODE_REPOS_FACTORY_PID, dataModel.getName())); +//// if (config.size() != 0) +//// putFactoryDeployConfig(NodeConstants.NODE_REPOS_FACTORY_PID, config); +//// } +// +// } /** Override the provided config with the framework properties */ public static Dictionary getNodeRepositoryConfig(Dictionary provided) { @@ -62,24 +63,24 @@ public class JcrInitUtils { return props; } - public static Dictionary getRepositoryConfig(String dataModelName, - Dictionary provided) { - if (dataModelName.equals(CmsConstants.NODE_REPOSITORY) || dataModelName.equals(CmsConstants.EGO_REPOSITORY)) - throw new IllegalArgumentException("Data model '" + dataModelName + "' is reserved."); - Dictionary props = provided != null ? provided : new Hashtable(); - for (RepoConf repoConf : RepoConf.values()) { - Object value = getFrameworkProp( - CmsConstants.NODE_REPOS_PROP_PREFIX + dataModelName + '.' + repoConf.name()); - if (value != null) { - props.put(repoConf.name(), value); - if (log.isDebugEnabled()) - log.debug("Set " + dataModelName + " repo configuration " + repoConf.name() + " to " + value); - } - } - if (props.size() != 0) - props.put(CmsConstants.CN, dataModelName); - return props; - } +// public static Dictionary getRepositoryConfig(String dataModelName, +// Dictionary provided) { +// if (dataModelName.equals(CmsConstants.NODE_REPOSITORY) || dataModelName.equals(CmsConstants.EGO_REPOSITORY)) +// throw new IllegalArgumentException("Data model '" + dataModelName + "' is reserved."); +// Dictionary props = provided != null ? provided : new Hashtable(); +// for (RepoConf repoConf : RepoConf.values()) { +// Object value = getFrameworkProp( +// CmsConstants.NODE_REPOS_PROP_PREFIX + dataModelName + '.' + repoConf.name()); +// if (value != null) { +// props.put(repoConf.name(), value); +// if (log.isDebugEnabled()) +// log.debug("Set " + dataModelName + " repo configuration " + repoConf.name() + " to " + value); +// } +// } +// if (props.size() != 0) +// props.put(CmsConstants.CN, dataModelName); +// return props; +// } private static void registerRemoteInit(String uri) { try { diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/CmsJcrDeployment.java b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/CmsJcrDeployment.java index 8fb61d70c..6fdd2009b 100644 --- a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/CmsJcrDeployment.java +++ b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/CmsJcrDeployment.java @@ -75,7 +75,7 @@ public class CmsJcrDeployment { // Readiness private boolean nodeAvailable = false; - CmsDeployment cmsDeployment; +// CmsDeployment cmsDeployment; public void start() { dataModels = new DataModels(bc); @@ -85,7 +85,7 @@ public class CmsJcrDeployment { // nodeDeployment = CmsJcrActivator.getService(NodeDeployment.class); - JcrInitUtils.addToDeployment(cmsDeployment); + //JcrInitUtils.addToDeployment(cmsDeployment); // contentRepository.registerTypes(NamespaceRegistry.PREFIX_JCR, NamespaceRegistry.NAMESPACE_JCR, null); // contentRepository.registerTypes(NamespaceRegistry.PREFIX_MIX, NamespaceRegistry.NAMESPACE_MIX, null); @@ -113,9 +113,9 @@ public class CmsJcrDeployment { } - public void setCmsDeployment(CmsDeployment cmsDeployment) { - this.cmsDeployment = cmsDeployment; - } +// public void setCmsDeployment(CmsDeployment cmsDeployment) { +// this.cmsDeployment = cmsDeployment; +// } /** * Checks whether the deployment is available according to expectations, and @@ -363,7 +363,8 @@ public class CmsJcrDeployment { } boolean isStandalone(String dataModelName) { - return cmsDeployment.getProps(CmsConstants.NODE_REPOS_FACTORY_PID, dataModelName) != null; + return true; + //return cmsDeployment.getProps(CmsConstants.NODE_REPOS_FACTORY_PID, dataModelName) != null; } private void publishLocalRepo(String dataModelName, Repository repository) { diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/RepositoryContextsFactory.java b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/RepositoryContextsFactory.java index e05a0023e..11e9a9e92 100644 --- a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/RepositoryContextsFactory.java +++ b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/RepositoryContextsFactory.java @@ -5,6 +5,7 @@ import java.net.URI; import java.net.URISyntaxException; import java.util.Dictionary; import java.util.HashMap; +import java.util.Hashtable; import java.util.Map; import javax.jcr.Repository; @@ -14,86 +15,111 @@ import javax.jcr.RepositoryFactory; import org.apache.jackrabbit.core.RepositoryContext; import org.argeo.api.cms.CmsConstants; import org.argeo.api.cms.CmsLog; +import org.argeo.api.cms.CmsState; import org.argeo.cms.internal.jcr.RepoConf; import org.argeo.cms.internal.jcr.RepositoryBuilder; import org.argeo.cms.jcr.internal.osgi.CmsJcrActivator; import org.argeo.util.LangUtils; -import org.osgi.framework.Constants; -import org.osgi.service.cm.ConfigurationException; import org.osgi.service.cm.ManagedServiceFactory; /** A {@link ManagedServiceFactory} creating or referencing JCR repositories. */ -public class RepositoryContextsFactory implements ManagedServiceFactory { +public class RepositoryContextsFactory { private final static CmsLog log = CmsLog.getLog(RepositoryContextsFactory.class); // private final BundleContext bc = FrameworkUtil.getBundle(RepositoryServiceFactory.class).getBundleContext(); - private Map repositories = new HashMap(); - private Map pidToCn = new HashMap(); +// private Map repositories = new HashMap(); +// private Map pidToCn = new HashMap(); - public void init() { + private RepositoryContext repositoryContext; + + private CmsState cmsState; + public void init() { + Dictionary config = getNodeRepositoryConfig(); + deployRepository(config); } public void destroy() { - for (String pid : repositories.keySet()) { - try { - RepositoryContext repositoryContext = repositories.get(pid); - // Must start in another thread otherwise shutdown is interrupted - // TODO use an executor? - new Thread(() -> { - repositoryContext.getRepository().shutdown(); - if (log.isDebugEnabled()) - log.debug("Shut down repository " + pid - + (pidToCn.containsKey(pid) ? " (" + pidToCn.get(pid) + ")" : "")); - }, "Shutdown JCR repository " + pid).start(); - } catch (Exception e) { - log.error("Error when shutting down Jackrabbit repository " + pid, e); - } + if (this.repositoryContext != null) { + this.repositoryContext.getRepository().shutdown(); } +// for (String pid : repositories.keySet()) { +// try { +// RepositoryContext repositoryContext = repositories.get(pid); +// // Must start in another thread otherwise shutdown is interrupted +// // TODO use an executor? +// new Thread(() -> { +// repositoryContext.getRepository().shutdown(); +// if (log.isDebugEnabled()) +// log.debug("Shut down repository " + pid +// + (pidToCn.containsKey(pid) ? " (" + pidToCn.get(pid) + ")" : "")); +// }, "Shutdown JCR repository " + pid).start(); +// } catch (Exception e) { +// log.error("Error when shutting down Jackrabbit repository " + pid, e); +// } +// } } - @Override - public String getName() { - return "Jackrabbit repository service factory"; +// @Override +// public String getName() { +// return "Jackrabbit repository service factory"; +// } + + /** Override the provided config with the framework properties */ + private Dictionary getNodeRepositoryConfig() { + Dictionary props = new Hashtable(); + for (RepoConf repoConf : RepoConf.values()) { + Object value = getFrameworkProp(CmsConstants.NODE_REPO_PROP_PREFIX + repoConf.name()); + if (value != null) { + props.put(repoConf.name(), value); + if (log.isDebugEnabled()) + log.debug("Set node repo configuration " + repoConf.name() + " to " + value); + } + } + props.put(CmsConstants.CN, CmsConstants.NODE_REPOSITORY); + return props; } - @Override - public void updated(String pid, Dictionary properties) throws ConfigurationException { - if (repositories.containsKey(pid)) - throw new IllegalArgumentException("Already a repository registered for " + pid); +// @Override +// public void updated(String pid, Dictionary properties) throws ConfigurationException { + protected void deployRepository(Dictionary properties) { +// if (repositories.containsKey(pid)) +// throw new IllegalArgumentException("Already a repository registered for " + pid); if (properties == null) return; Object cn = properties.get(CmsConstants.CN); - if (cn != null) - for (String otherPid : pidToCn.keySet()) { - Object o = pidToCn.get(otherPid); - if (cn.equals(o)) { - RepositoryContext repositoryContext = repositories.remove(otherPid); - repositories.put(pid, repositoryContext); - if (log.isDebugEnabled()) - log.debug("Ignoring update of Jackrabbit repository " + cn); - // FIXME perform a proper update (also of the OSGi service) - return; - } - } +// if (cn != null) +// for (String otherPid : pidToCn.keySet()) { +// Object o = pidToCn.get(otherPid); +// if (cn.equals(o)) { +// RepositoryContext repositoryContext = repositories.remove(otherPid); +// repositories.put(pid, repositoryContext); +// if (log.isDebugEnabled()) +// log.debug("Ignoring update of Jackrabbit repository " + cn); +// // FIXME perform a proper update (also of the OSGi service) +// return; +// } +// } try { Object labeledUri = properties.get(RepoConf.labeledUri.name()); if (labeledUri == null) { RepositoryBuilder repositoryBuilder = new RepositoryBuilder(); RepositoryContext repositoryContext = repositoryBuilder.createRepositoryContext(properties); - repositories.put(pid, repositoryContext); - Dictionary props = LangUtils.dict(Constants.SERVICE_PID, pid); +// repositories.put(pid, repositoryContext); +// Dictionary props = LangUtils.dict(Constants.SERVICE_PID, pid); + Dictionary props = new Hashtable<>(); // props.put(ArgeoJcrConstants.JCR_REPOSITORY_URI, // properties.get(RepoConf.labeledUri.name())); if (cn != null) { props.put(CmsConstants.CN, cn); // props.put(NodeConstants.JCR_REPOSITORY_ALIAS, cn); - pidToCn.put(pid, cn); +// pidToCn.put(pid, cn); } CmsJcrActivator.registerService(RepositoryContext.class, repositoryContext, props); + this.repositoryContext = repositoryContext; } else { Object defaultWorkspace = properties.get(RepoConf.defaultWorkspace.name()); if (defaultWorkspace == null) @@ -108,14 +134,14 @@ public class RepositoryContextsFactory implements ManagedServiceFactory { Repository repository = repositoryFactory.getRepository(parameters); // Repository repository = NodeUtils.getRepositoryByUri(repositoryFactory, // uri.toString()); - Dictionary props = LangUtils.dict(Constants.SERVICE_PID, pid); +// Dictionary props = LangUtils.dict(Constants.SERVICE_PID, pid); + Dictionary props = new Hashtable<>(); props.put(RepoConf.labeledUri.name(), new URI(uri.getScheme(), null, uri.getHost(), uri.getPort(), uri.getPath(), null, null) .toString()); if (cn != null) { props.put(CmsConstants.CN, cn); - // props.put(NodeConstants.JCR_REPOSITORY_ALIAS, cn); - pidToCn.put(pid, cn); +// pidToCn.put(pid, cn); } CmsJcrActivator.registerService(Repository.class, repository, props); @@ -127,17 +153,25 @@ public class RepositoryContextsFactory implements ManagedServiceFactory { } } } catch (RepositoryException | URISyntaxException | IOException e) { - throw new IllegalStateException("Cannot create Jackrabbit repository " + pid, e); + throw new IllegalStateException("Cannot create Jackrabbit repository " + properties, e); } } - @Override - public void deleted(String pid) { - RepositoryContext repositoryContext = repositories.remove(pid); - repositoryContext.getRepository().shutdown(); - if (log.isDebugEnabled()) - log.debug("Deleted repository " + pid); +// @Override +// public void deleted(String pid) { +// RepositoryContext repositoryContext = repositories.remove(pid); +// repositoryContext.getRepository().shutdown(); +// if (log.isDebugEnabled()) +// log.debug("Deleted repository " + pid); +// } + + private String getFrameworkProp(String key) { + return cmsState.getDeployProperty(key); + } + + public void setCmsState(CmsState cmsState) { + this.cmsState = cmsState; } } diff --git a/org.argeo.api.cms/src/org/argeo/api/cms/CmsDeployment.java b/org.argeo.api.cms/src/org/argeo/api/cms/CmsDeployment.java index 5893d2ec5..c69be8eeb 100644 --- a/org.argeo.api.cms/src/org/argeo/api/cms/CmsDeployment.java +++ b/org.argeo.api.cms/src/org/argeo/api/cms/CmsDeployment.java @@ -5,7 +5,7 @@ import java.util.Dictionary; /** A configured node deployment. */ public interface CmsDeployment { - void addFactoryDeployConfig(String factoryPid, Dictionary props); - - Dictionary getProps(String factoryPid, String cn); +// void addFactoryDeployConfig(String factoryPid, Dictionary props); +// +// Dictionary getProps(String factoryPid, String cn); } diff --git a/org.argeo.api.cms/src/org/argeo/api/cms/CmsState.java b/org.argeo.api.cms/src/org/argeo/api/cms/CmsState.java index 3828b080b..3e0e8b757 100644 --- a/org.argeo.api.cms/src/org/argeo/api/cms/CmsState.java +++ b/org.argeo.api.cms/src/org/argeo/api/cms/CmsState.java @@ -1,5 +1,6 @@ package org.argeo.api.cms; +import java.nio.file.Path; import java.util.UUID; /** A running node process. */ @@ -9,4 +10,8 @@ public interface CmsState { Long getAvailableSince(); UUID getUuid(); + + String getDeployProperty(String key); + + Path getDataPath(String relativePath); } diff --git a/org.argeo.cms/OSGI-INF/cmsDeployment.xml b/org.argeo.cms/OSGI-INF/cmsDeployment.xml index d36a91110..5f5ed5fcc 100644 --- a/org.argeo.cms/OSGI-INF/cmsDeployment.xml +++ b/org.argeo.cms/OSGI-INF/cmsDeployment.xml @@ -1,6 +1,5 @@ - diff --git a/org.argeo.cms/OSGI-INF/nodeUserAdmin.xml b/org.argeo.cms/OSGI-INF/nodeUserAdmin.xml index 268908e57..50a9ea6eb 100644 --- a/org.argeo.cms/OSGI-INF/nodeUserAdmin.xml +++ b/org.argeo.cms/OSGI-INF/nodeUserAdmin.xml @@ -1,11 +1,11 @@ - + - - - - + + + + diff --git a/org.argeo.cms/bnd.bnd b/org.argeo.cms/bnd.bnd index 52987f3da..d0047dcc6 100644 --- a/org.argeo.cms/bnd.bnd +++ b/org.argeo.cms/bnd.bnd @@ -12,7 +12,6 @@ OSGI-INF/cmsState.xml,\ OSGI-INF/simpleTransactionManager.xml,\ OSGI-INF/nodeUserAdmin.xml,\ OSGI-INF/cmsUserManager.xml,\ -OSGI-INF/deployConfig.xml,\ OSGI-INF/uuidFactory.xml,\ OSGI-INF/acrContentRepository.xml,\ OSGI-INF/cmsDeployment.xml,\ diff --git a/org.argeo.cms/src/org/argeo/cms/auth/RemoteSessionLoginModule.java b/org.argeo.cms/src/org/argeo/cms/auth/RemoteSessionLoginModule.java index b5734afd3..19875e88a 100644 --- a/org.argeo.cms/src/org/argeo/cms/auth/RemoteSessionLoginModule.java +++ b/org.argeo.cms/src/org/argeo/cms/auth/RemoteSessionLoginModule.java @@ -211,7 +211,8 @@ public class RemoteSessionLoginModule implements LoginModule { if (log.isDebugEnabled()) log.debug("Client certificate " + certDn + " verified by servlet container"); } // Reverse proxy verified the client certificate - String clientDnHttpHeader = KernelUtils.getFrameworkProp(CmsConstants.HTTP_PROXY_SSL_DN); + String clientDnHttpHeader = CmsContextImpl.getCmsContext().getCmsState() + .getDeployProperty(CmsConstants.HTTP_PROXY_SSL_DN); if (clientDnHttpHeader != null) { String certDn = req.getHeader(clientDnHttpHeader); // TODO retrieve more cf. https://httpd.apache.org/docs/current/mod/mod_ssl.html diff --git a/org.argeo.cms/src/org/argeo/cms/internal/http/InternalHttpConstants.java b/org.argeo.cms/src/org/argeo/cms/internal/http/InternalHttpConstants.java deleted file mode 100644 index c888c291b..000000000 --- a/org.argeo.cms/src/org/argeo/cms/internal/http/InternalHttpConstants.java +++ /dev/null @@ -1,23 +0,0 @@ -package org.argeo.cms.internal.http; - -/** Compatible with Jetty. */ -public interface InternalHttpConstants { - static final String HTTP_ENABLED = "http.enabled"; - static final String HTTP_PORT = "http.port"; - static final String HTTP_HOST = "http.host"; - static final String HTTPS_ENABLED = "https.enabled"; - static final String HTTPS_HOST = "https.host"; - static final String HTTPS_PORT = "https.port"; - static final String SSL_KEYSTORE = "ssl.keystore"; - static final String SSL_PASSWORD = "ssl.password"; - static final String SSL_KEYPASSWORD = "ssl.keypassword"; - static final String SSL_NEEDCLIENTAUTH = "ssl.needclientauth"; - static final String SSL_WANTCLIENTAUTH = "ssl.wantclientauth"; - static final String SSL_PROTOCOL = "ssl.protocol"; - static final String SSL_ALGORITHM = "ssl.algorithm"; - static final String SSL_KEYSTORETYPE = "ssl.keystoretype"; - static final String JETTY_PROPERTY_PREFIX = "org.eclipse.equinox.http.jetty."; - // Argeo specific - static final String WEBSOCKET_ENABLED = "websocket.enabled"; - -} diff --git a/org.argeo.cms/src/org/argeo/cms/internal/osgi/CmsActivator.java b/org.argeo.cms/src/org/argeo/cms/internal/osgi/CmsActivator.java index 038d7029c..d4b2f4595 100644 --- a/org.argeo.cms/src/org/argeo/cms/internal/osgi/CmsActivator.java +++ b/org.argeo.cms/src/org/argeo/cms/internal/osgi/CmsActivator.java @@ -8,16 +8,13 @@ import org.argeo.cms.ArgeoLogger; import org.osgi.framework.BundleActivator; import org.osgi.framework.BundleContext; import org.osgi.framework.Constants; -import org.osgi.framework.ServiceReference; import org.osgi.service.condpermadmin.BundleLocationCondition; import org.osgi.service.condpermadmin.ConditionInfo; import org.osgi.service.condpermadmin.ConditionalPermissionAdmin; import org.osgi.service.condpermadmin.ConditionalPermissionInfo; import org.osgi.service.condpermadmin.ConditionalPermissionUpdate; -import org.osgi.service.http.HttpService; import org.osgi.service.log.LogReaderService; import org.osgi.service.permissionadmin.PermissionInfo; -import org.osgi.util.tracker.ServiceTracker; /** * Activates the kernel. Gives access to kernel information for the rest of the @@ -179,18 +176,18 @@ public class CmsActivator implements BundleActivator { // userAdminSt = new ServiceTracker<>(bundleContext, UserAdmin.class, null); // userAdminSt.open(); - ServiceTracker httpSt = new ServiceTracker(bc, HttpService.class, null) { - - @Override - public HttpService addingService(ServiceReference sr) { - Object httpPort = sr.getProperty("http.port"); - Object httpsPort = sr.getProperty("https.port"); - log.info(httpPortsMsg(httpPort, httpsPort)); - close(); - return super.addingService(sr); - } - }; - httpSt.open(); +// ServiceTracker httpSt = new ServiceTracker(bc, HttpService.class, null) { +// +// @Override +// public HttpService addingService(ServiceReference sr) { +// Object httpPort = sr.getProperty("http.port"); +// Object httpsPort = sr.getProperty("https.port"); +// log.info(httpPortsMsg(httpPort, httpsPort)); +// close(); +// return super.addingService(sr); +// } +// }; +// httpSt.open(); } private String httpPortsMsg(Object httpPort, Object httpsPort) { diff --git a/org.argeo.cms/src/org/argeo/cms/internal/osgi/DeployConfig.java b/org.argeo.cms/src/org/argeo/cms/internal/osgi/DeployConfig.java index b8fa8a73f..205ab35de 100644 --- a/org.argeo.cms/src/org/argeo/cms/internal/osgi/DeployConfig.java +++ b/org.argeo.cms/src/org/argeo/cms/internal/osgi/DeployConfig.java @@ -70,59 +70,50 @@ public class DeployConfig { private void setFromFrameworkProperties(boolean isFirstInit) { // user admin - List> userDirectoryConfigs = InitUtils.getUserDirectoryConfigs(); - if (userDirectoryConfigs.size() != 0) { - List activeCns = new ArrayList<>(); - for (int i = 0; i < userDirectoryConfigs.size(); i++) { - Dictionary userDirectoryConfig = userDirectoryConfigs.get(i); - String baseDn = (String) userDirectoryConfig.get(DirectoryConf.baseDn.name()); - String cn; - if (CmsConstants.ROLES_BASEDN.equals(baseDn)) - cn = ROLES; - else - cn = DirectoryConf.baseDnHash(userDirectoryConfig); - activeCns.add(cn); - userDirectoryConfig.put(CmsConstants.CN, cn); - putFactoryDeployConfig(CmsConstants.NODE_USER_ADMIN_PID, userDirectoryConfig); - } - // disable others - LdapName userAdminFactoryName = serviceFactoryDn(CmsConstants.NODE_USER_ADMIN_PID); - for (LdapName name : deployConfigs.keySet()) { - if (name.startsWith(userAdminFactoryName) && !name.equals(userAdminFactoryName)) { -// try { - Attributes attrs = deployConfigs.get(name); - String cn = name.getRdn(name.size() - 1).getValue().toString(); - if (!activeCns.contains(cn)) { - attrs.put(DirectoryConf.disabled.name(), "true"); - } -// } catch (Exception e) { -// throw new CmsException("Cannot disable user directory " + name, e); +// List> userDirectoryConfigs = InitUtils.getUserDirectoryConfigs(); +// if (userDirectoryConfigs.size() != 0) { +// List activeCns = new ArrayList<>(); +// for (int i = 0; i < userDirectoryConfigs.size(); i++) { +// Dictionary userDirectoryConfig = userDirectoryConfigs.get(i); +// String baseDn = (String) userDirectoryConfig.get(DirectoryConf.baseDn.name()); +// String cn; +// if (CmsConstants.ROLES_BASEDN.equals(baseDn)) +// cn = ROLES; +// else +// cn = DirectoryConf.baseDnHash(userDirectoryConfig); +// activeCns.add(cn); +// userDirectoryConfig.put(CmsConstants.CN, cn); +// putFactoryDeployConfig(CmsConstants.NODE_USER_ADMIN_PID, userDirectoryConfig); +// } +// // disable others +// LdapName userAdminFactoryName = serviceFactoryDn(CmsConstants.NODE_USER_ADMIN_PID); +// for (LdapName name : deployConfigs.keySet()) { +// if (name.startsWith(userAdminFactoryName) && !name.equals(userAdminFactoryName)) { +//// try { +// Attributes attrs = deployConfigs.get(name); +// String cn = name.getRdn(name.size() - 1).getValue().toString(); +// if (!activeCns.contains(cn)) { +// attrs.put(DirectoryConf.disabled.name(), "true"); // } - } - } - } +//// } catch (Exception e) { +//// throw new CmsException("Cannot disable user directory " + name, e); +//// } +// } +// } +// } // http server - Dictionary webServerConfig = InitUtils - .getHttpServerConfig(getProps(KernelConstants.JETTY_FACTORY_PID, CmsConstants.DEFAULT)); - if (!webServerConfig.isEmpty()) { - // TODO check for other customizers -// webServerConfig.put("customizer.class", "org.argeo.equinox.jetty.CmsJettyCustomizer"); - putFactoryDeployConfig(KernelConstants.JETTY_FACTORY_PID, webServerConfig); - } -// LdapName defaultHttpServiceDn = serviceDn(KernelConstants.JETTY_FACTORY_PID, CmsConstants.DEFAULT); -// if (deployConfigs.containsKey(defaultHttpServiceDn)) { -// // remove old default configs since we have now to start Jetty servlet bridge -// // indirectly -// deployConfigs.remove(defaultHttpServiceDn); +// Dictionary webServerConfig = InitUtils +// .getHttpServerConfig(getProps(KernelConstants.JETTY_FACTORY_PID, CmsConstants.DEFAULT)); +// if (!webServerConfig.isEmpty()) { +// // TODO check for other customizers +// putFactoryDeployConfig(KernelConstants.JETTY_FACTORY_PID, webServerConfig); // } // SAVE save(); // -// Dictionary webServerConfig = InitUtils -// .getHttpServerConfig(getProps(KernelConstants.JETTY_FACTORY_PID, CmsConstants.DEFAULT)); } public void start() { diff --git a/org.argeo.cms/src/org/argeo/cms/internal/runtime/CmsDeploymentImpl.java b/org.argeo.cms/src/org/argeo/cms/internal/runtime/CmsDeploymentImpl.java index 44a2866f8..5ba811c92 100644 --- a/org.argeo.cms/src/org/argeo/cms/internal/runtime/CmsDeploymentImpl.java +++ b/org.argeo.cms/src/org/argeo/cms/internal/runtime/CmsDeploymentImpl.java @@ -19,28 +19,28 @@ public class CmsDeploymentImpl implements CmsDeployment { private HttpService httpService; private CmsState cmsState; - private DeployConfig deployConfig; +// private DeployConfig deployConfig; public void start() { - httpExpected = deployConfig.getProps(KernelConstants.JETTY_FACTORY_PID, "default") != null; - if (deployConfig.hasDomain()) { - loadIpaJaasConfiguration(); - } +// httpExpected = deployConfig.getProps(KernelConstants.JETTY_FACTORY_PID, "default") != null; +// if (deployConfig.hasDomain()) { +// loadIpaJaasConfiguration(); +// } } - public void addFactoryDeployConfig(String factoryPid, Dictionary props) { - deployConfig.putFactoryDeployConfig(factoryPid, props); - deployConfig.save(); - try { - deployConfig.loadConfigs(); - } catch (IOException e) { - throw new IllegalStateException(e); - } - } - - public Dictionary getProps(String factoryPid, String cn) { - return deployConfig.getProps(factoryPid, cn); - } +// public void addFactoryDeployConfig(String factoryPid, Dictionary props) { +// deployConfig.putFactoryDeployConfig(factoryPid, props); +// deployConfig.save(); +// try { +// deployConfig.loadConfigs(); +// } catch (IOException e) { +// throw new IllegalStateException(e); +// } +// } +// +// public Dictionary getProps(String factoryPid, String cn) { +// return deployConfig.getProps(factoryPid, cn); +// } public boolean isHttpAvailableOrNotExpected() { return (httpExpected ? httpService != null : true); @@ -56,14 +56,14 @@ public class CmsDeploymentImpl implements CmsDeployment { } public void stop() { - if (deployConfig != null) { - deployConfig.save(); - } +// if (deployConfig != null) { +// deployConfig.save(); +// } } - public void setDeployConfig(DeployConfig deployConfig) { - this.deployConfig = deployConfig; - } +// public void setDeployConfig(DeployConfig deployConfig) { +// this.deployConfig = deployConfig; +// } public void setCmsState(CmsState cmsState) { this.cmsState = cmsState; diff --git a/org.argeo.cms/src/org/argeo/cms/internal/runtime/CmsStateImpl.java b/org.argeo.cms/src/org/argeo/cms/internal/runtime/CmsStateImpl.java index 41e326534..ae85a2fc8 100644 --- a/org.argeo.cms/src/org/argeo/cms/internal/runtime/CmsStateImpl.java +++ b/org.argeo.cms/src/org/argeo/cms/internal/runtime/CmsStateImpl.java @@ -3,6 +3,7 @@ package org.argeo.cms.internal.runtime; import java.net.InetAddress; import java.net.URL; import java.net.UnknownHostException; +import java.nio.file.Path; import java.util.UUID; import javax.security.auth.login.Configuration; @@ -81,6 +82,16 @@ public class CmsStateImpl implements CmsState { log.info("## ARGEO CMS STOPPED after " + (duration / 60) + "h " + (duration % 60) + "min uptime ##"); } + @Override + public String getDeployProperty(String key) { + return KernelUtils.getFrameworkProp(key); + } + + @Override + public Path getDataPath(String relativePath) { + return KernelUtils.getOsgiInstancePath(relativePath); + } + @Override public Long getAvailableSince() { return availableSince; diff --git a/org.argeo.cms/src/org/argeo/cms/internal/runtime/CmsUserAdmin.java b/org.argeo.cms/src/org/argeo/cms/internal/runtime/CmsUserAdmin.java index 64e32b16a..18a880e31 100644 --- a/org.argeo.cms/src/org/argeo/cms/internal/runtime/CmsUserAdmin.java +++ b/org.argeo.cms/src/org/argeo/cms/internal/runtime/CmsUserAdmin.java @@ -5,12 +5,14 @@ import java.net.Inet6Address; import java.net.InetAddress; import java.net.URI; import java.net.URISyntaxException; +import java.net.URL; import java.nio.file.Files; import java.nio.file.Path; import java.security.PrivilegedExceptionAction; import java.util.ArrayList; import java.util.Dictionary; import java.util.Iterator; +import java.util.List; import java.util.Optional; import java.util.Set; @@ -31,10 +33,11 @@ import org.apache.commons.httpclient.params.HttpParams; import org.argeo.api.cms.CmsAuth; import org.argeo.api.cms.CmsConstants; import org.argeo.api.cms.CmsLog; +import org.argeo.api.cms.CmsState; import org.argeo.cms.internal.http.client.HttpCredentialProvider; import org.argeo.cms.internal.http.client.SpnegoAuthScheme; -import org.argeo.osgi.useradmin.DirectoryUserAdmin; import org.argeo.osgi.useradmin.AggregatingUserAdmin; +import org.argeo.osgi.useradmin.DirectoryUserAdmin; import org.argeo.osgi.useradmin.UserDirectory; import org.argeo.util.directory.DirectoryConf; import org.argeo.util.naming.dns.DnsBrowser; @@ -65,14 +68,27 @@ public class CmsUserAdmin extends AggregatingUserAdmin { private WorkControl transactionManager; private WorkTransaction userTransaction; + private CmsState cmsState; + public CmsUserAdmin() { super(CmsConstants.ROLES_BASEDN, CmsConstants.TOKENS_BASEDN); } public void start() { + super.start(); + List> configs = InitUtils.getUserDirectoryConfigs(); + for (Dictionary config : configs) { + UserDirectory userDirectory = enableUserDirectory(config); + if (userDirectory.getRealm().isPresent()) + loadIpaJaasConfiguration(); + } } public void stop() { +// for (UserDirectory userDirectory : getUserDirectories()) { +// removeUserDirectory(userDirectory); +// } + super.stop(); } public UserDirectory enableUserDirectory(Dictionary properties) { @@ -206,6 +222,15 @@ public class CmsUserAdmin extends AggregatingUserAdmin { } } + private void loadIpaJaasConfiguration() { + if (System.getProperty(KernelConstants.JAAS_CONFIG_PROP) == null) { + String jaasConfig = KernelConstants.JAAS_CONFIG_IPA; + URL url = getClass().getClassLoader().getResource(jaasConfig); + KernelUtils.setJaasConfiguration(url); + log.debug("Set IPA JAAS configuration."); + } + } + private String getKerberosServicePrincipal(String realm) { String hostname; try (DnsBrowser dnsBrowser = new DnsBrowser()) { @@ -288,8 +313,8 @@ public class CmsUserAdmin extends AggregatingUserAdmin { this.userTransaction = userTransaction; } - /* - * STATIC - */ + public void setCmsState(CmsState cmsState) { + this.cmsState = cmsState; + } } diff --git a/org.argeo.cms/src/org/argeo/cms/internal/runtime/InitUtils.java b/org.argeo.cms/src/org/argeo/cms/internal/runtime/InitUtils.java index 986f7914d..f634e43b5 100644 --- a/org.argeo.cms/src/org/argeo/cms/internal/runtime/InitUtils.java +++ b/org.argeo.cms/src/org/argeo/cms/internal/runtime/InitUtils.java @@ -5,25 +5,14 @@ import static org.argeo.cms.internal.runtime.KernelUtils.getFrameworkProp; import java.io.File; import java.io.FileFilter; import java.io.IOException; -import java.io.Reader; -import java.net.InetAddress; import java.net.URI; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.nio.file.Path; -import java.security.KeyStore; import java.util.ArrayList; -import java.util.Arrays; import java.util.Dictionary; -import java.util.Hashtable; import java.util.List; -import javax.security.auth.x500.X500Principal; - import org.apache.commons.io.FileUtils; import org.argeo.api.cms.CmsConstants; import org.argeo.api.cms.CmsLog; -import org.argeo.cms.internal.http.InternalHttpConstants; import org.argeo.util.directory.DirectoryConf; /** @@ -34,92 +23,92 @@ public class InitUtils { private final static CmsLog log = CmsLog.getLog(InitUtils.class); /** Override the provided config with the framework properties */ - public static Dictionary getHttpServerConfig(Dictionary provided) { - String httpPort = getFrameworkProp("org.osgi.service.http.port"); - String httpsPort = getFrameworkProp("org.osgi.service.http.port.secure"); - /// TODO make it more generic - String httpHost = getFrameworkProp( - InternalHttpConstants.JETTY_PROPERTY_PREFIX + InternalHttpConstants.HTTP_HOST); - String httpsHost = getFrameworkProp( - InternalHttpConstants.JETTY_PROPERTY_PREFIX + InternalHttpConstants.HTTPS_HOST); - String webSocketEnabled = getFrameworkProp( - InternalHttpConstants.JETTY_PROPERTY_PREFIX + InternalHttpConstants.WEBSOCKET_ENABLED); - - final Hashtable props = new Hashtable(); - // try { - if (httpPort != null || httpsPort != null) { - boolean httpEnabled = httpPort != null; - props.put(InternalHttpConstants.HTTP_ENABLED, httpEnabled); - boolean httpsEnabled = httpsPort != null; - props.put(InternalHttpConstants.HTTPS_ENABLED, httpsEnabled); - - if (httpEnabled) { - props.put(InternalHttpConstants.HTTP_PORT, httpPort); - if (httpHost != null) - props.put(InternalHttpConstants.HTTP_HOST, httpHost); - } - - if (httpsEnabled) { - props.put(InternalHttpConstants.HTTPS_PORT, httpsPort); - if (httpsHost != null) - props.put(InternalHttpConstants.HTTPS_HOST, httpsHost); - - // server certificate - Path keyStorePath = KernelUtils.getOsgiInstancePath(KernelConstants.DEFAULT_KEYSTORE_PATH); - Path pemKeyPath = KernelUtils.getOsgiInstancePath(KernelConstants.DEFAULT_PEM_KEY_PATH); - Path pemCertPath = KernelUtils.getOsgiInstancePath(KernelConstants.DEFAULT_PEM_CERT_PATH); - String keyStorePasswordStr = getFrameworkProp( - InternalHttpConstants.JETTY_PROPERTY_PREFIX + InternalHttpConstants.SSL_PASSWORD); - char[] keyStorePassword; - if (keyStorePasswordStr == null) - keyStorePassword = "changeit".toCharArray(); - else - keyStorePassword = keyStorePasswordStr.toCharArray(); - - // if PEM files both exists, update the PKCS12 file - if (Files.exists(pemCertPath) && Files.exists(pemKeyPath)) { - // TODO check certificate update time? monitor changes? - KeyStore keyStore = PkiUtils.getKeyStore(keyStorePath, keyStorePassword, PkiUtils.PKCS12); - try (Reader key = Files.newBufferedReader(pemKeyPath, StandardCharsets.US_ASCII); - Reader cert = Files.newBufferedReader(pemCertPath, StandardCharsets.US_ASCII);) { - PkiUtils.loadPem(keyStore, key, keyStorePassword, cert); - PkiUtils.saveKeyStore(keyStorePath, keyStorePassword, keyStore); - if (log.isDebugEnabled()) - log.debug("PEM certificate stored in " + keyStorePath); - } catch (IOException e) { - log.error("Cannot read PEM files " + pemKeyPath + " and " + pemCertPath, e); - } - } - - if (!Files.exists(keyStorePath)) - createSelfSignedKeyStore(keyStorePath, keyStorePassword, PkiUtils.PKCS12); - props.put(InternalHttpConstants.SSL_KEYSTORETYPE, PkiUtils.PKCS12); - props.put(InternalHttpConstants.SSL_KEYSTORE, keyStorePath.toString()); - props.put(InternalHttpConstants.SSL_PASSWORD, new String(keyStorePassword)); - -// props.put(InternalHttpConstants.SSL_KEYSTORETYPE, "PKCS11"); -// props.put(InternalHttpConstants.SSL_KEYSTORE, "../../nssdb"); -// props.put(InternalHttpConstants.SSL_PASSWORD, keyStorePassword); - - // client certificate authentication - String wantClientAuth = getFrameworkProp( - InternalHttpConstants.JETTY_PROPERTY_PREFIX + InternalHttpConstants.SSL_WANTCLIENTAUTH); - if (wantClientAuth != null) - props.put(InternalHttpConstants.SSL_WANTCLIENTAUTH, Boolean.parseBoolean(wantClientAuth)); - String needClientAuth = getFrameworkProp( - InternalHttpConstants.JETTY_PROPERTY_PREFIX + InternalHttpConstants.SSL_NEEDCLIENTAUTH); - if (needClientAuth != null) - props.put(InternalHttpConstants.SSL_NEEDCLIENTAUTH, Boolean.parseBoolean(needClientAuth)); - } - - // web socket - if (webSocketEnabled != null && webSocketEnabled.equals("true")) - props.put(InternalHttpConstants.WEBSOCKET_ENABLED, true); - - props.put(CmsConstants.CN, CmsConstants.DEFAULT); - } - return props; - } +// public static Dictionary getHttpServerConfig(Dictionary provided) { +// String httpPort = getFrameworkProp("org.osgi.service.http.port"); +// String httpsPort = getFrameworkProp("org.osgi.service.http.port.secure"); +// /// TODO make it more generic +// String httpHost = getFrameworkProp( +// InternalHttpConstants.JETTY_PROPERTY_PREFIX + InternalHttpConstants.HTTP_HOST); +// String httpsHost = getFrameworkProp( +// InternalHttpConstants.JETTY_PROPERTY_PREFIX + InternalHttpConstants.HTTPS_HOST); +// String webSocketEnabled = getFrameworkProp( +// InternalHttpConstants.JETTY_PROPERTY_PREFIX + InternalHttpConstants.WEBSOCKET_ENABLED); +// +// final Hashtable props = new Hashtable(); +// // try { +// if (httpPort != null || httpsPort != null) { +// boolean httpEnabled = httpPort != null; +// props.put(InternalHttpConstants.HTTP_ENABLED, httpEnabled); +// boolean httpsEnabled = httpsPort != null; +// props.put(InternalHttpConstants.HTTPS_ENABLED, httpsEnabled); +// +// if (httpEnabled) { +// props.put(InternalHttpConstants.HTTP_PORT, httpPort); +// if (httpHost != null) +// props.put(InternalHttpConstants.HTTP_HOST, httpHost); +// } +// +// if (httpsEnabled) { +// props.put(InternalHttpConstants.HTTPS_PORT, httpsPort); +// if (httpsHost != null) +// props.put(InternalHttpConstants.HTTPS_HOST, httpsHost); +// +// // server certificate +// Path keyStorePath = KernelUtils.getOsgiInstancePath(KernelConstants.DEFAULT_KEYSTORE_PATH); +// Path pemKeyPath = KernelUtils.getOsgiInstancePath(KernelConstants.DEFAULT_PEM_KEY_PATH); +// Path pemCertPath = KernelUtils.getOsgiInstancePath(KernelConstants.DEFAULT_PEM_CERT_PATH); +// String keyStorePasswordStr = getFrameworkProp( +// InternalHttpConstants.JETTY_PROPERTY_PREFIX + InternalHttpConstants.SSL_PASSWORD); +// char[] keyStorePassword; +// if (keyStorePasswordStr == null) +// keyStorePassword = "changeit".toCharArray(); +// else +// keyStorePassword = keyStorePasswordStr.toCharArray(); +// +// // if PEM files both exists, update the PKCS12 file +// if (Files.exists(pemCertPath) && Files.exists(pemKeyPath)) { +// // TODO check certificate update time? monitor changes? +// KeyStore keyStore = PkiUtils.getKeyStore(keyStorePath, keyStorePassword, PkiUtils.PKCS12); +// try (Reader key = Files.newBufferedReader(pemKeyPath, StandardCharsets.US_ASCII); +// Reader cert = Files.newBufferedReader(pemCertPath, StandardCharsets.US_ASCII);) { +// PkiUtils.loadPem(keyStore, key, keyStorePassword, cert); +// PkiUtils.saveKeyStore(keyStorePath, keyStorePassword, keyStore); +// if (log.isDebugEnabled()) +// log.debug("PEM certificate stored in " + keyStorePath); +// } catch (IOException e) { +// log.error("Cannot read PEM files " + pemKeyPath + " and " + pemCertPath, e); +// } +// } +// +// if (!Files.exists(keyStorePath)) +// createSelfSignedKeyStore(keyStorePath, keyStorePassword, PkiUtils.PKCS12); +// props.put(InternalHttpConstants.SSL_KEYSTORETYPE, PkiUtils.PKCS12); +// props.put(InternalHttpConstants.SSL_KEYSTORE, keyStorePath.toString()); +// props.put(InternalHttpConstants.SSL_PASSWORD, new String(keyStorePassword)); +// +//// props.put(InternalHttpConstants.SSL_KEYSTORETYPE, "PKCS11"); +//// props.put(InternalHttpConstants.SSL_KEYSTORE, "../../nssdb"); +//// props.put(InternalHttpConstants.SSL_PASSWORD, keyStorePassword); +// +// // client certificate authentication +// String wantClientAuth = getFrameworkProp( +// InternalHttpConstants.JETTY_PROPERTY_PREFIX + InternalHttpConstants.SSL_WANTCLIENTAUTH); +// if (wantClientAuth != null) +// props.put(InternalHttpConstants.SSL_WANTCLIENTAUTH, Boolean.parseBoolean(wantClientAuth)); +// String needClientAuth = getFrameworkProp( +// InternalHttpConstants.JETTY_PROPERTY_PREFIX + InternalHttpConstants.SSL_NEEDCLIENTAUTH); +// if (needClientAuth != null) +// props.put(InternalHttpConstants.SSL_NEEDCLIENTAUTH, Boolean.parseBoolean(needClientAuth)); +// } +// +// // web socket +// if (webSocketEnabled != null && webSocketEnabled.equals("true")) +// props.put(InternalHttpConstants.WEBSOCKET_ENABLED, true); +// +// props.put(CmsConstants.CN, CmsConstants.DEFAULT); +// } +// return props; +// } public static List> getUserDirectoryConfigs() { List> res = new ArrayList<>(); @@ -258,33 +247,4 @@ public class InitUtils { } } - private static void createSelfSignedKeyStore(Path keyStorePath, char[] keyStorePassword, String keyStoreType) { - // for (Provider provider : Security.getProviders()) - // System.out.println(provider.getName()); -// File keyStoreFile = keyStorePath.toFile(); - char[] keyPwd = Arrays.copyOf(keyStorePassword, keyStorePassword.length); - if (!Files.exists(keyStorePath)) { - try { - Files.createDirectories(keyStorePath.getParent()); - KeyStore keyStore = PkiUtils.getKeyStore(keyStorePath, keyStorePassword, keyStoreType); - PkiUtils.generateSelfSignedCertificate(keyStore, - new X500Principal("CN=" + InetAddress.getLocalHost().getHostName() + ",OU=UNSECURE,O=UNSECURE"), - 1024, keyPwd); - PkiUtils.saveKeyStore(keyStorePath, keyStorePassword, keyStore); - if (log.isDebugEnabled()) - log.debug("Created self-signed unsecure keystore " + keyStorePath); - } catch (Exception e) { - try { - if (Files.size(keyStorePath) == 0) - Files.delete(keyStorePath); - } catch (IOException e1) { - // silent - } - log.error("Cannot create keystore " + keyStorePath, e); - } - } else { - throw new IllegalStateException("Keystore " + keyStorePath + " already exists"); - } - } - } diff --git a/org.argeo.cms/src/org/argeo/cms/internal/runtime/KernelConstants.java b/org.argeo.cms/src/org/argeo/cms/internal/runtime/KernelConstants.java index dfe86cfaa..a02c557f2 100644 --- a/org.argeo.cms/src/org/argeo/cms/internal/runtime/KernelConstants.java +++ b/org.argeo.cms/src/org/argeo/cms/internal/runtime/KernelConstants.java @@ -12,9 +12,6 @@ public interface KernelConstants { // Files String DEPLOY_CONFIG_PATH = DIR_NODE + '/' + CmsConstants.DEPLOY_BASEDN + ".ldif"; - String DEFAULT_KEYSTORE_PATH = DIR_NODE + '/' + CmsConstants.NODE + ".p12"; - String DEFAULT_PEM_KEY_PATH = DIR_NODE + '/' + CmsConstants.NODE + ".key"; - String DEFAULT_PEM_CERT_PATH = DIR_NODE + '/' + CmsConstants.NODE + ".crt"; String NODE_KEY_TAB_PATH = DIR_NODE + "/krb5.keytab"; // Security diff --git a/org.argeo.cms/src/org/argeo/cms/internal/runtime/KernelUtils.java b/org.argeo.cms/src/org/argeo/cms/internal/runtime/KernelUtils.java index 60c796af7..5635bccfe 100644 --- a/org.argeo.cms/src/org/argeo/cms/internal/runtime/KernelUtils.java +++ b/org.argeo.cms/src/org/argeo/cms/internal/runtime/KernelUtils.java @@ -101,7 +101,7 @@ public class KernelUtils implements KernelConstants { return value; } - public static String getFrameworkProp(String key) { + static String getFrameworkProp(String key) { return getFrameworkProp(key, null); } diff --git a/org.argeo.cms/src/org/argeo/cms/internal/runtime/PkiUtils.java b/org.argeo.cms/src/org/argeo/cms/internal/runtime/PkiUtils.java deleted file mode 100644 index 474a89950..000000000 --- a/org.argeo.cms/src/org/argeo/cms/internal/runtime/PkiUtils.java +++ /dev/null @@ -1,259 +0,0 @@ -package org.argeo.cms.internal.runtime; - -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.io.Reader; -import java.math.BigInteger; -import java.nio.file.Files; -import java.nio.file.Path; -import java.security.GeneralSecurityException; -import java.security.KeyPair; -import java.security.KeyPairGenerator; -import java.security.KeyStore; -import java.security.KeyStoreException; -import java.security.PrivateKey; -import java.security.SecureRandom; -import java.security.Security; -import java.security.cert.Certificate; -import java.security.cert.CertificateException; -import java.security.cert.X509Certificate; -import java.util.Date; - -import javax.security.auth.x500.X500Principal; - -import org.bouncycastle.asn1.pkcs.PrivateKeyInfo; -import org.bouncycastle.cert.X509CertificateHolder; -import org.bouncycastle.cert.X509v3CertificateBuilder; -import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter; -import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder; -import org.bouncycastle.jce.provider.BouncyCastleProvider; -import org.bouncycastle.openssl.PEMParser; -import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter; -import org.bouncycastle.openssl.jcajce.JceOpenSSLPKCS8DecryptorProviderBuilder; -import org.bouncycastle.operator.ContentSigner; -import org.bouncycastle.operator.InputDecryptorProvider; -import org.bouncycastle.operator.OperatorCreationException; -import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder; -import org.bouncycastle.pkcs.PKCS8EncryptedPrivateKeyInfo; -import org.bouncycastle.pkcs.PKCSException; - -/** - * Utilities around private keys and certificate, mostly wrapping BouncyCastle - * implementations. - */ -class PkiUtils { - final static String PKCS12 = "PKCS12"; - - private final static String SECURITY_PROVIDER; - static { - Security.addProvider(new BouncyCastleProvider()); - SECURITY_PROVIDER = "BC"; - } - - public static X509Certificate generateSelfSignedCertificate(KeyStore keyStore, X500Principal x500Principal, - int keySize, char[] keyPassword) { - try { - KeyPairGenerator kpGen = KeyPairGenerator.getInstance("RSA", SECURITY_PROVIDER); - kpGen.initialize(keySize, new SecureRandom()); - KeyPair pair = kpGen.generateKeyPair(); - Date notBefore = new Date(System.currentTimeMillis() - 10000); - Date notAfter = new Date(System.currentTimeMillis() + 365 * 24L * 3600 * 1000); - BigInteger serial = BigInteger.valueOf(System.currentTimeMillis()); - X509v3CertificateBuilder certGen = new JcaX509v3CertificateBuilder(x500Principal, serial, notBefore, - notAfter, x500Principal, pair.getPublic()); - ContentSigner sigGen = new JcaContentSignerBuilder("SHA256WithRSAEncryption").setProvider(SECURITY_PROVIDER) - .build(pair.getPrivate()); - X509Certificate cert = new JcaX509CertificateConverter().setProvider(SECURITY_PROVIDER) - .getCertificate(certGen.build(sigGen)); - cert.checkValidity(new Date()); - cert.verify(cert.getPublicKey()); - - keyStore.setKeyEntry(x500Principal.getName(), pair.getPrivate(), keyPassword, new Certificate[] { cert }); - return cert; - } catch (GeneralSecurityException | OperatorCreationException e) { - throw new RuntimeException("Cannot generate self-signed certificate", e); - } - } - - public static KeyStore getKeyStore(Path keyStoreFile, char[] keyStorePassword, String keyStoreType) { - try { - KeyStore store = KeyStore.getInstance(keyStoreType, SECURITY_PROVIDER); - if (Files.exists(keyStoreFile)) { - try (InputStream fis = Files.newInputStream(keyStoreFile)) { - store.load(fis, keyStorePassword); - } - } else { - store.load(null); - } - return store; - } catch (GeneralSecurityException | IOException e) { - throw new RuntimeException("Cannot load keystore " + keyStoreFile, e); - } - } - - public static void saveKeyStore(Path keyStoreFile, char[] keyStorePassword, KeyStore keyStore) { - try { - try (OutputStream fis = Files.newOutputStream(keyStoreFile)) { - keyStore.store(fis, keyStorePassword); - } - } catch (GeneralSecurityException | IOException e) { - throw new RuntimeException("Cannot save keystore " + keyStoreFile, e); - } - } - -// public static byte[] pemToPKCS12(final String keyFile, final String cerFile, final String password) -// throws Exception { -// // Get the private key -// FileReader reader = new FileReader(keyFile); -// -// PEMReader pem = new PemReader(reader, new PasswordFinder() { -// @Override -// public char[] getPassword() { -// return password.toCharArray(); -// } -// }); -// -// PrivateKey key = ((KeyPair) pem.readObject()).getPrivate(); -// -// pem.close(); -// reader.close(); -// -// // Get the certificate -// reader = new FileReader(cerFile); -// pem = new PEMReader(reader); -// -// X509Certificate cert = (X509Certificate) pem.readObject(); -// -// pem.close(); -// reader.close(); -// -// // Put them into a PKCS12 keystore and write it to a byte[] -// ByteArrayOutputStream bos = new ByteArrayOutputStream(); -// KeyStore ks = KeyStore.getInstance("PKCS12"); -// ks.load(null); -// ks.setKeyEntry("alias", (Key) key, password.toCharArray(), new java.security.cert.Certificate[] { cert }); -// ks.store(bos, password.toCharArray()); -// bos.close(); -// return bos.toByteArray(); -// } - - public static void loadPem(KeyStore keyStore, Reader key, char[] keyPassword, Reader cert) { - PrivateKey privateKey = loadPemPrivateKey(key, keyPassword); - X509Certificate certificate = loadPemCertificate(cert); - try { - keyStore.setKeyEntry(certificate.getSubjectX500Principal().getName(), privateKey, keyPassword, - new java.security.cert.Certificate[] { certificate }); - } catch (KeyStoreException e) { - throw new RuntimeException("Cannot store PEM certificate", e); - } - } - - public static PrivateKey loadPemPrivateKey(Reader reader, char[] keyPassword) { - try (PEMParser pemParser = new PEMParser(reader)) { - JcaPEMKeyConverter converter = new JcaPEMKeyConverter().setProvider("BC"); - Object object = pemParser.readObject(); - PrivateKeyInfo privateKeyInfo; - if (object instanceof PKCS8EncryptedPrivateKeyInfo) { - if (keyPassword == null) - throw new IllegalArgumentException("A key password is required"); - InputDecryptorProvider decProv = new JceOpenSSLPKCS8DecryptorProviderBuilder().build(keyPassword); - privateKeyInfo = ((PKCS8EncryptedPrivateKeyInfo) object).decryptPrivateKeyInfo(decProv); - } else if (object instanceof PrivateKeyInfo) { - privateKeyInfo = (PrivateKeyInfo) object; - } else { - throw new IllegalArgumentException("Unsupported format for private key"); - } - return converter.getPrivateKey(privateKeyInfo); - } catch (IOException | OperatorCreationException | PKCSException e) { - throw new RuntimeException("Cannot read private key", e); - } - } - - public static X509Certificate loadPemCertificate(Reader reader) { - try (PEMParser pemParser = new PEMParser(reader)) { - X509CertificateHolder certHolder = (X509CertificateHolder) pemParser.readObject(); - X509Certificate cert = new JcaX509CertificateConverter().setProvider(SECURITY_PROVIDER) - .getCertificate(certHolder); - return cert; - } catch (IOException | CertificateException e) { - throw new RuntimeException("Cannot read private key", e); - } - } - - public static void main(String[] args) throws Exception { - final String ALGORITHM = "RSA"; - final String provider = "BC"; - SecureRandom secureRandom = new SecureRandom(); - long begin = System.currentTimeMillis(); - for (int i = 512; i < 1024; i = i + 2) { - try { - KeyPairGenerator keyGen = KeyPairGenerator.getInstance(ALGORITHM, provider); - keyGen.initialize(i, secureRandom); - keyGen.generateKeyPair(); - } catch (Exception e) { - System.err.println(i + " : " + e.getMessage()); - } - } - System.out.println((System.currentTimeMillis() - begin) + " ms"); - - // // String text = "a"; - // String text = - // "testtesttesttesttesttesttesttesttesttesttesttesttesttesttest"; - // try { - // System.out.println(text); - // PrivateKey privateKey; - // PublicKey publicKey; - // char[] password = "changeit".toCharArray(); - // String alias = "CN=test"; - // KeyStore keyStore = KeyStore.getInstance("pkcs12"); - // File p12file = new File("test.p12"); - // p12file.delete(); - // if (!p12file.exists()) { - // keyStore.load(null); - // generateSelfSignedCertificate(keyStore, new X500Principal(alias), - // 513, password); - // try (OutputStream out = new FileOutputStream(p12file)) { - // keyStore.store(out, password); - // } - // } - // try (InputStream in = new FileInputStream(p12file)) { - // keyStore.load(in, password); - // privateKey = (PrivateKey) keyStore.getKey(alias, password); - // publicKey = keyStore.getCertificateChain(alias)[0].getPublicKey(); - // } - // // KeyPair key; - // // final KeyPairGenerator keyGen = - // // KeyPairGenerator.getInstance(ALGORITHM); - // // keyGen.initialize(4096, new SecureRandom()); - // // long begin = System.currentTimeMillis(); - // // key = keyGen.generateKeyPair(); - // // System.out.println((System.currentTimeMillis() - begin) + " ms"); - // // keyStore.load(null); - // // keyStore.setKeyEntry("test", key.getPrivate(), password, null); - // // try(OutputStream out=new FileOutputStream(p12file)) { - // // keyStore.store(out, password); - // // } - // // privateKey = key.getPrivate(); - // // publicKey = key.getPublic(); - // - // Cipher encrypt = Cipher.getInstance(ALGORITHM); - // encrypt.init(Cipher.ENCRYPT_MODE, publicKey); - // byte[] encrypted = encrypt.doFinal(text.getBytes()); - // String encryptedBase64 = - // Base64.getEncoder().encodeToString(encrypted); - // System.out.println(encryptedBase64); - // byte[] encryptedFromBase64 = - // Base64.getDecoder().decode(encryptedBase64); - // - // Cipher decrypt = Cipher.getInstance(ALGORITHM); - // decrypt.init(Cipher.DECRYPT_MODE, privateKey); - // byte[] decrypted = decrypt.doFinal(encryptedFromBase64); - // System.out.println(new String(decrypted)); - // } catch (Exception e) { - // e.printStackTrace(); - // } - - } - -} diff --git a/org.argeo.cms/src/org/argeo/cms/runtime/StaticCms.java b/org.argeo.cms/src/org/argeo/cms/runtime/StaticCms.java index a868e8c26..916a413a9 100644 --- a/org.argeo.cms/src/org/argeo/cms/runtime/StaticCms.java +++ b/org.argeo.cms/src/org/argeo/cms/runtime/StaticCms.java @@ -55,12 +55,12 @@ public class StaticCms { .build(register); // Deployment Configuration - DeployConfig deployConfig = new DeployConfig(); - Component deployConfigC = new Component.Builder<>(deployConfig) // - .addType(DeployConfig.class) // - .addActivation(deployConfig::start) // - .addDeactivation(deployConfig::stop) // - .build(register); +// DeployConfig deployConfig = new DeployConfig(); +// Component deployConfigC = new Component.Builder<>(deployConfig) // +// .addType(DeployConfig.class) // +// .addActivation(deployConfig::start) // +// .addDeactivation(deployConfig::stop) // +// .build(register); // CMS Deployment CmsDeploymentImpl cmsDeployment = new CmsDeploymentImpl(); @@ -69,7 +69,7 @@ public class StaticCms { .addActivation(cmsDeployment::start) // .addDeactivation(cmsDeployment::stop) // .addDependency(cmsStateC.getType(CmsState.class), cmsDeployment::setCmsState, null) // - .addDependency(deployConfigC.getType(DeployConfig.class), cmsDeployment::setDeployConfig, null) // +// .addDependency(deployConfigC.getType(DeployConfig.class), cmsDeployment::setDeployConfig, null) // .build(register); // Transaction manager @@ -85,10 +85,10 @@ public class StaticCms { .addType(UserAdmin.class) // .addDependency(transactionManagerC.getType(WorkControl.class), userAdmin::setTransactionManager, null) // .addDependency(transactionManagerC.getType(WorkTransaction.class), userAdmin::setUserTransaction, null) // - .addDependency(deployConfigC.getType(DeployConfig.class), (d) -> { - for (Dictionary userDirectoryConfig : d.getUserDirectoryConfigs()) - userAdmin.enableUserDirectory(userDirectoryConfig); - }, null) // +// .addDependency(deployConfigC.getType(DeployConfig.class), (d) -> { +// for (Dictionary userDirectoryConfig : d.getUserDirectoryConfigs()) +// userAdmin.enableUserDirectory(userDirectoryConfig); +// }, null) // .build(register); // User manager diff --git a/org.argeo.cms/src/org/argeo/cms/security/PkiUtils.java b/org.argeo.cms/src/org/argeo/cms/security/PkiUtils.java new file mode 100644 index 000000000..d6f90acfb --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/security/PkiUtils.java @@ -0,0 +1,300 @@ +package org.argeo.cms.security; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.Reader; +import java.math.BigInteger; +import java.net.InetAddress; +import java.nio.file.Files; +import java.nio.file.Path; +import java.security.GeneralSecurityException; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.PrivateKey; +import java.security.SecureRandom; +import java.security.Security; +import java.security.cert.Certificate; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; +import java.util.Arrays; +import java.util.Date; + +import javax.security.auth.x500.X500Principal; + +import org.argeo.api.cms.CmsConstants; +import org.argeo.api.cms.CmsLog; +import org.argeo.cms.internal.runtime.KernelConstants; +import org.bouncycastle.asn1.pkcs.PrivateKeyInfo; +import org.bouncycastle.cert.X509CertificateHolder; +import org.bouncycastle.cert.X509v3CertificateBuilder; +import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter; +import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder; +import org.bouncycastle.jce.provider.BouncyCastleProvider; +import org.bouncycastle.openssl.PEMParser; +import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter; +import org.bouncycastle.openssl.jcajce.JceOpenSSLPKCS8DecryptorProviderBuilder; +import org.bouncycastle.operator.ContentSigner; +import org.bouncycastle.operator.InputDecryptorProvider; +import org.bouncycastle.operator.OperatorCreationException; +import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder; +import org.bouncycastle.pkcs.PKCS8EncryptedPrivateKeyInfo; +import org.bouncycastle.pkcs.PKCSException; + +/** + * Utilities around private keys and certificate, mostly wrapping BouncyCastle + * implementations. + */ +public class PkiUtils { + private final static CmsLog log = CmsLog.getLog(PkiUtils.class); + + public final static String PKCS12 = "PKCS12"; + public static final String DEFAULT_KEYSTORE_PATH = KernelConstants.DIR_NODE + '/' + CmsConstants.NODE + ".p12"; + + public static final String DEFAULT_PEM_KEY_PATH = KernelConstants.DIR_NODE + '/' + CmsConstants.NODE + ".key"; + + public static final String DEFAULT_PEM_CERT_PATH = KernelConstants.DIR_NODE + '/' + CmsConstants.NODE + ".crt"; + + private final static String SECURITY_PROVIDER; + static { + Security.addProvider(new BouncyCastleProvider()); + SECURITY_PROVIDER = "BC"; + } + + public static X509Certificate generateSelfSignedCertificate(KeyStore keyStore, X500Principal x500Principal, + int keySize, char[] keyPassword) { + try { + KeyPairGenerator kpGen = KeyPairGenerator.getInstance("RSA", SECURITY_PROVIDER); + kpGen.initialize(keySize, new SecureRandom()); + KeyPair pair = kpGen.generateKeyPair(); + Date notBefore = new Date(System.currentTimeMillis() - 10000); + Date notAfter = new Date(System.currentTimeMillis() + 365 * 24L * 3600 * 1000); + BigInteger serial = BigInteger.valueOf(System.currentTimeMillis()); + X509v3CertificateBuilder certGen = new JcaX509v3CertificateBuilder(x500Principal, serial, notBefore, + notAfter, x500Principal, pair.getPublic()); + ContentSigner sigGen = new JcaContentSignerBuilder("SHA256WithRSAEncryption").setProvider(SECURITY_PROVIDER) + .build(pair.getPrivate()); + X509Certificate cert = new JcaX509CertificateConverter().setProvider(SECURITY_PROVIDER) + .getCertificate(certGen.build(sigGen)); + cert.checkValidity(new Date()); + cert.verify(cert.getPublicKey()); + + keyStore.setKeyEntry(x500Principal.getName(), pair.getPrivate(), keyPassword, new Certificate[] { cert }); + return cert; + } catch (GeneralSecurityException | OperatorCreationException e) { + throw new RuntimeException("Cannot generate self-signed certificate", e); + } + } + + public static KeyStore getKeyStore(Path keyStoreFile, char[] keyStorePassword, String keyStoreType) { + try { + KeyStore store = KeyStore.getInstance(keyStoreType, SECURITY_PROVIDER); + if (Files.exists(keyStoreFile)) { + try (InputStream fis = Files.newInputStream(keyStoreFile)) { + store.load(fis, keyStorePassword); + } + } else { + store.load(null); + } + return store; + } catch (GeneralSecurityException | IOException e) { + throw new RuntimeException("Cannot load keystore " + keyStoreFile, e); + } + } + + public static void saveKeyStore(Path keyStoreFile, char[] keyStorePassword, KeyStore keyStore) { + try { + try (OutputStream fis = Files.newOutputStream(keyStoreFile)) { + keyStore.store(fis, keyStorePassword); + } + } catch (GeneralSecurityException | IOException e) { + throw new RuntimeException("Cannot save keystore " + keyStoreFile, e); + } + } + +// public static byte[] pemToPKCS12(final String keyFile, final String cerFile, final String password) +// throws Exception { +// // Get the private key +// FileReader reader = new FileReader(keyFile); +// +// PEMReader pem = new PemReader(reader, new PasswordFinder() { +// @Override +// public char[] getPassword() { +// return password.toCharArray(); +// } +// }); +// +// PrivateKey key = ((KeyPair) pem.readObject()).getPrivate(); +// +// pem.close(); +// reader.close(); +// +// // Get the certificate +// reader = new FileReader(cerFile); +// pem = new PEMReader(reader); +// +// X509Certificate cert = (X509Certificate) pem.readObject(); +// +// pem.close(); +// reader.close(); +// +// // Put them into a PKCS12 keystore and write it to a byte[] +// ByteArrayOutputStream bos = new ByteArrayOutputStream(); +// KeyStore ks = KeyStore.getInstance("PKCS12"); +// ks.load(null); +// ks.setKeyEntry("alias", (Key) key, password.toCharArray(), new java.security.cert.Certificate[] { cert }); +// ks.store(bos, password.toCharArray()); +// bos.close(); +// return bos.toByteArray(); +// } + + public static void loadPem(KeyStore keyStore, Reader key, char[] keyPassword, Reader cert) { + PrivateKey privateKey = loadPemPrivateKey(key, keyPassword); + X509Certificate certificate = loadPemCertificate(cert); + try { + keyStore.setKeyEntry(certificate.getSubjectX500Principal().getName(), privateKey, keyPassword, + new java.security.cert.Certificate[] { certificate }); + } catch (KeyStoreException e) { + throw new RuntimeException("Cannot store PEM certificate", e); + } + } + + public static PrivateKey loadPemPrivateKey(Reader reader, char[] keyPassword) { + try (PEMParser pemParser = new PEMParser(reader)) { + JcaPEMKeyConverter converter = new JcaPEMKeyConverter().setProvider("BC"); + Object object = pemParser.readObject(); + PrivateKeyInfo privateKeyInfo; + if (object instanceof PKCS8EncryptedPrivateKeyInfo) { + if (keyPassword == null) + throw new IllegalArgumentException("A key password is required"); + InputDecryptorProvider decProv = new JceOpenSSLPKCS8DecryptorProviderBuilder().build(keyPassword); + privateKeyInfo = ((PKCS8EncryptedPrivateKeyInfo) object).decryptPrivateKeyInfo(decProv); + } else if (object instanceof PrivateKeyInfo) { + privateKeyInfo = (PrivateKeyInfo) object; + } else { + throw new IllegalArgumentException("Unsupported format for private key"); + } + return converter.getPrivateKey(privateKeyInfo); + } catch (IOException | OperatorCreationException | PKCSException e) { + throw new RuntimeException("Cannot read private key", e); + } + } + + public static X509Certificate loadPemCertificate(Reader reader) { + try (PEMParser pemParser = new PEMParser(reader)) { + X509CertificateHolder certHolder = (X509CertificateHolder) pemParser.readObject(); + X509Certificate cert = new JcaX509CertificateConverter().setProvider(SECURITY_PROVIDER) + .getCertificate(certHolder); + return cert; + } catch (IOException | CertificateException e) { + throw new RuntimeException("Cannot read private key", e); + } + } + + public static void main(String[] args) throws Exception { + final String ALGORITHM = "RSA"; + final String provider = "BC"; + SecureRandom secureRandom = new SecureRandom(); + long begin = System.currentTimeMillis(); + for (int i = 512; i < 1024; i = i + 2) { + try { + KeyPairGenerator keyGen = KeyPairGenerator.getInstance(ALGORITHM, provider); + keyGen.initialize(i, secureRandom); + keyGen.generateKeyPair(); + } catch (Exception e) { + System.err.println(i + " : " + e.getMessage()); + } + } + System.out.println((System.currentTimeMillis() - begin) + " ms"); + + // // String text = "a"; + // String text = + // "testtesttesttesttesttesttesttesttesttesttesttesttesttesttest"; + // try { + // System.out.println(text); + // PrivateKey privateKey; + // PublicKey publicKey; + // char[] password = "changeit".toCharArray(); + // String alias = "CN=test"; + // KeyStore keyStore = KeyStore.getInstance("pkcs12"); + // File p12file = new File("test.p12"); + // p12file.delete(); + // if (!p12file.exists()) { + // keyStore.load(null); + // generateSelfSignedCertificate(keyStore, new X500Principal(alias), + // 513, password); + // try (OutputStream out = new FileOutputStream(p12file)) { + // keyStore.store(out, password); + // } + // } + // try (InputStream in = new FileInputStream(p12file)) { + // keyStore.load(in, password); + // privateKey = (PrivateKey) keyStore.getKey(alias, password); + // publicKey = keyStore.getCertificateChain(alias)[0].getPublicKey(); + // } + // // KeyPair key; + // // final KeyPairGenerator keyGen = + // // KeyPairGenerator.getInstance(ALGORITHM); + // // keyGen.initialize(4096, new SecureRandom()); + // // long begin = System.currentTimeMillis(); + // // key = keyGen.generateKeyPair(); + // // System.out.println((System.currentTimeMillis() - begin) + " ms"); + // // keyStore.load(null); + // // keyStore.setKeyEntry("test", key.getPrivate(), password, null); + // // try(OutputStream out=new FileOutputStream(p12file)) { + // // keyStore.store(out, password); + // // } + // // privateKey = key.getPrivate(); + // // publicKey = key.getPublic(); + // + // Cipher encrypt = Cipher.getInstance(ALGORITHM); + // encrypt.init(Cipher.ENCRYPT_MODE, publicKey); + // byte[] encrypted = encrypt.doFinal(text.getBytes()); + // String encryptedBase64 = + // Base64.getEncoder().encodeToString(encrypted); + // System.out.println(encryptedBase64); + // byte[] encryptedFromBase64 = + // Base64.getDecoder().decode(encryptedBase64); + // + // Cipher decrypt = Cipher.getInstance(ALGORITHM); + // decrypt.init(Cipher.DECRYPT_MODE, privateKey); + // byte[] decrypted = decrypt.doFinal(encryptedFromBase64); + // System.out.println(new String(decrypted)); + // } catch (Exception e) { + // e.printStackTrace(); + // } + + } + + public static void createSelfSignedKeyStore(Path keyStorePath, char[] keyStorePassword, String keyStoreType) { + // for (Provider provider : Security.getProviders()) + // System.out.println(provider.getName()); + // File keyStoreFile = keyStorePath.toFile(); + char[] keyPwd = Arrays.copyOf(keyStorePassword, keyStorePassword.length); + if (!Files.exists(keyStorePath)) { + try { + Files.createDirectories(keyStorePath.getParent()); + KeyStore keyStore = getKeyStore(keyStorePath, keyStorePassword, keyStoreType); + generateSelfSignedCertificate(keyStore, + new X500Principal("CN=" + InetAddress.getLocalHost().getHostName() + ",OU=UNSECURE,O=UNSECURE"), + 1024, keyPwd); + saveKeyStore(keyStorePath, keyStorePassword, keyStore); + if (log.isDebugEnabled()) + log.debug("Created self-signed unsecure keystore " + keyStorePath); + } catch (Exception e) { + try { + if (Files.size(keyStorePath) == 0) + Files.delete(keyStorePath); + } catch (IOException e1) { + // silent + } + log.error("Cannot create keystore " + keyStorePath, e); + } + } else { + throw new IllegalStateException("Keystore " + keyStorePath + " already exists"); + } + } + +} diff --git a/org.argeo.util/src/org/argeo/osgi/useradmin/AggregatingUserAdmin.java b/org.argeo.util/src/org/argeo/osgi/useradmin/AggregatingUserAdmin.java index 3857b08d0..79d2bd3cb 100644 --- a/org.argeo.util/src/org/argeo/osgi/useradmin/AggregatingUserAdmin.java +++ b/org.argeo.util/src/org/argeo/osgi/useradmin/AggregatingUserAdmin.java @@ -238,7 +238,11 @@ public class AggregatingUserAdmin implements UserAdmin { // return res; // } - public void destroy() { + public void start() { + + } + + public void stop() { for (LdapName name : businessRoles.keySet()) { DirectoryUserAdmin userDirectory = businessRoles.get(name); destroy(userDirectory); @@ -254,6 +258,14 @@ public class AggregatingUserAdmin implements UserAdmin { userDirectory.destroy(); } +// protected void removeUserDirectory(UserDirectory userDirectory) { +// LdapName baseDn = toLdapName(userDirectory.getContext()); +// businessRoles.remove(baseDn); +// if (userDirectory instanceof DirectoryUserAdmin) +// destroy((DirectoryUserAdmin) userDirectory); +// } + + @Deprecated protected void removeUserDirectory(String basePath) { if (isSystemRolesBaseDn(basePath)) throw new IllegalArgumentException("System roles cannot be removed "); @@ -274,6 +286,8 @@ public class AggregatingUserAdmin implements UserAdmin { public Set getUserDirectories() { TreeSet res = new TreeSet<>((o1, o2) -> o1.getContext().compareTo(o2.getContext())); res.addAll(businessRoles.values()); + res.add(systemRoles); return res; } + }