Skip to content

Commit 79460b2

Browse files
committed
wip
Signed-off-by: Abhishek Kumar <abhishek.mrt22@gmail.com>
1 parent 5a8a1e2 commit 79460b2

26 files changed

Lines changed: 2008 additions & 795 deletions

File tree

api/src/main/java/org/apache/cloudstack/api/ApiConstants.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -546,6 +546,7 @@ public class ApiConstants {
546546

547547
public static final String SOURCE_CIDR_LIST = "sourcecidrlist";
548548
public static final String SOURCE_ZONE_ID = "sourcezoneid";
549+
public static final String SOURCE_MANAGEMENT_SERVER_ID = "sourcemanagementserverid";
549550
public static final String SSL_VERIFICATION = "sslverification";
550551
public static final String START_ASN = "startasn";
551552
public static final String START_DATE = "startdate";
@@ -566,6 +567,7 @@ public class ApiConstants {
566567
public static final String SWAP_OWNER = "swapowner";
567568
public static final String SYSTEM_VM_TYPE = "systemvmtype";
568569
public static final String TAGS = "tags";
570+
public static final String TARGET_MANAGEMENT_SERVER_IDS = "targetmanagementserverids";
569571
public static final String STORAGE_TAGS = "storagetags";
570572
public static final String STORAGE_ACCESS_GROUPS = "storageaccessgroups";
571573
public static final String STORAGE_ACCESS_GROUP = "storageaccessgroup";

client/src/main/java/org/apache/cloudstack/ServerDaemon.java

Lines changed: 131 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,15 +24,24 @@
2424
import java.io.InputStream;
2525
import java.lang.management.ManagementFactory;
2626
import java.net.URL;
27+
import java.nio.file.Files;
28+
import java.nio.file.Path;
29+
import java.nio.file.Paths;
2730
import java.util.Arrays;
31+
import java.util.EnumSet;
32+
import java.util.List;
2833
import java.util.Properties;
2934

30-
import com.cloud.api.ApiServer;
35+
import javax.servlet.DispatcherType;
36+
3137
import org.apache.commons.daemon.Daemon;
3238
import org.apache.commons.daemon.DaemonContext;
3339
import org.apache.commons.lang3.StringUtils;
40+
import org.apache.logging.log4j.LogManager;
41+
import org.apache.logging.log4j.Logger;
3442
import org.eclipse.jetty.jmx.MBeanContainer;
3543
import org.eclipse.jetty.server.ForwardedRequestCustomizer;
44+
import org.eclipse.jetty.server.Handler;
3645
import org.eclipse.jetty.server.HttpConfiguration;
3746
import org.eclipse.jetty.server.HttpConnectionFactory;
3847
import org.eclipse.jetty.server.RequestLog;
@@ -46,14 +55,19 @@
4655
import org.eclipse.jetty.server.handler.RequestLogHandler;
4756
import org.eclipse.jetty.server.handler.gzip.GzipHandler;
4857
import org.eclipse.jetty.server.session.SessionHandler;
58+
import org.eclipse.jetty.servlet.DefaultServlet;
59+
import org.eclipse.jetty.servlet.FilterHolder;
60+
import org.eclipse.jetty.servlet.ServletContextHandler;
61+
import org.eclipse.jetty.servlet.ServletHolder;
62+
import org.eclipse.jetty.servlets.GzipFilter;
63+
import org.eclipse.jetty.util.resource.Resource;
4964
import org.eclipse.jetty.util.ssl.KeyStoreScanner;
5065
import org.eclipse.jetty.util.ssl.SslContextFactory;
5166
import org.eclipse.jetty.util.thread.QueuedThreadPool;
5267
import org.eclipse.jetty.util.thread.ScheduledExecutorScheduler;
5368
import org.eclipse.jetty.webapp.WebAppContext;
54-
import org.apache.logging.log4j.Logger;
55-
import org.apache.logging.log4j.LogManager;
5669

70+
import com.cloud.api.ApiServer;
5771
import com.cloud.utils.Pair;
5872
import com.cloud.utils.PropertiesUtil;
5973
import com.cloud.utils.server.ServerProperties;
@@ -89,6 +103,12 @@ public class ServerDaemon implements Daemon {
89103
private static final String THREADS_MIN = "threads.min";
90104
private static final String THREADS_MAX = "threads.max";
91105

106+
public static final String SHARE_ENABLED = "share.enabled";
107+
public static final String SHARE_BASE_DIR = "share.base.dir";
108+
public static final String SHARE_CACHE_CONTROL = "share.cache.control";
109+
public static final String SHARE_DIR_ALLOWED = "share.dir.allowed";
110+
public static final String SHARE_SECRET = "share.secret";
111+
92112
////////////////////////////////////////////////////////
93113
/////////////// Server Configuration ///////////////////
94114
////////////////////////////////////////////////////////
@@ -111,6 +131,12 @@ public class ServerDaemon implements Daemon {
111131
private int minThreads;
112132
private int maxThreads;
113133

134+
private boolean shareEnabled = false;
135+
private String shareBaseDir;
136+
private String shareCacheCtl;
137+
private boolean shareDirList = false;
138+
private String shareSecret;
139+
114140
//////////////////////////////////////////////////
115141
/////////////// Public methods ///////////////////
116142
//////////////////////////////////////////////////
@@ -121,6 +147,20 @@ public static void main(final String... anArgs) throws Exception {
121147
daemon.start();
122148
}
123149

150+
protected void initShareConfigFromProperties(Properties properties) {
151+
setShareEnabled(Boolean.parseBoolean(properties.getProperty(SHARE_ENABLED, "true")));
152+
String defaultBaseDir = System.getProperty("user.home") + File.separator + "share";
153+
boolean isMavenRun = ManagementFactory.getRuntimeMXBean().getInputArguments().toString().contains("org.codehaus.plexus.classworlds.launcher.Launcher");
154+
if (isMavenRun) {
155+
// when running from maven, use a share directory below the current working directory
156+
defaultBaseDir = System.getProperty("user.dir") + File.separator + "share";
157+
}
158+
setShareBaseDir(properties.getProperty(SHARE_BASE_DIR, defaultBaseDir));
159+
setShareCacheCtl(properties.getProperty(SHARE_CACHE_CONTROL, "public,max-age=86400,immutable"));
160+
setShareDirList(Boolean.parseBoolean(properties.getProperty(SHARE_DIR_ALLOWED, "false")));
161+
setShareSecret(properties.getProperty(SHARE_SECRET, "change-me"));
162+
}
163+
124164
@Override
125165
public void init(final DaemonContext context) {
126166
final File confFile = PropertiesUtil.findConfigFile("server.properties");
@@ -153,6 +193,7 @@ public void init(final DaemonContext context) {
153193
setMaxFormKeys(Integer.valueOf(properties.getProperty(REQUEST_MAX_FORM_KEYS_KEY, String.valueOf(DEFAULT_REQUEST_MAX_FORM_KEYS))));
154194
setMinThreads(Integer.valueOf(properties.getProperty(THREADS_MIN, "10")));
155195
setMaxThreads(Integer.valueOf(properties.getProperty(THREADS_MAX, "500")));
196+
initShareConfigFromProperties(properties);
156197
} catch (final IOException e) {
157198
logger.warn("Failed to read configuration from server.properties file", e);
158199
} finally {
@@ -288,6 +329,57 @@ private void createHttpsConnector(final HttpConfiguration httpConfig) {
288329
}
289330
}
290331

332+
/**
333+
* Creates a Jetty context at /share to serve static files for modules (e.g. Extensions Framework).
334+
* Controlled via system properties (can be converted to ConfigKeys later):
335+
* -Dcs.share.enabled=true|false
336+
* -Dcs.share.baseDir=/var/lib/cloudstack/share
337+
* -Dcs.share.cacheControl=public,max-age=86400,immutable
338+
* -Dcs.share.dirAllowed=false
339+
* -Dcs.share.requireToken=false
340+
* -Dcs.share.secret=change-me
341+
*
342+
* @return a configured Handler or null if disabled.
343+
*/
344+
private Handler createShareContextHandler() throws IOException {
345+
if (!shareEnabled) {
346+
logger.info("cs.share.enabled=false; /share context not mounted");
347+
return null;
348+
}
349+
350+
final Path base = Paths.get(shareBaseDir);
351+
Files.createDirectories(base);
352+
353+
final ServletContextHandler shareCtx = new ServletContextHandler(ServletContextHandler.NO_SESSIONS);
354+
shareCtx.setContextPath("/share");
355+
shareCtx.setBaseResource(Resource.newResource(base.toAbsolutePath().toUri()));
356+
357+
// Efficient static file serving
358+
ServletHolder def = shareCtx.addServlet(DefaultServlet.class, "/*");
359+
def.setInitParameter("dirAllowed", Boolean.toString(shareDirList));
360+
def.setInitParameter("etags", "true");
361+
def.setInitParameter("cacheControl", shareCacheCtl);
362+
def.setInitParameter("useFileMappedBuffer", "true");
363+
def.setInitParameter("acceptRanges", "true");
364+
365+
// Gzip using modern Jetty handler
366+
org.eclipse.jetty.server.handler.gzip.GzipHandler gzipHandler =
367+
new org.eclipse.jetty.server.handler.gzip.GzipHandler();
368+
gzipHandler.setMinGzipSize(1024);
369+
gzipHandler.setIncludedMimeTypes(
370+
"text/html", "text/plain", "text/css", "text/javascript",
371+
"application/javascript", "application/json", "application/xml");
372+
gzipHandler.setHandler(shareCtx);
373+
374+
// Optional signed-URL guard (path + "|" + exp => HMAC-SHA256, base64url)
375+
if (StringUtils.isNotBlank(shareSecret)) {
376+
shareCtx.addFilter(new FilterHolder(new ShareSignedUrlFilter(true, shareSecret)), "/*", EnumSet.of(DispatcherType.REQUEST));
377+
}
378+
379+
logger.info("Mounted /share static context at baseDir={}", base);
380+
return shareCtx;
381+
}
382+
291383
private Pair<SessionHandler,HandlerCollection> createHandlers() {
292384
final WebAppContext webApp = new WebAppContext();
293385
webApp.setContextPath(contextPath);
@@ -318,8 +410,23 @@ private Pair<SessionHandler,HandlerCollection> createHandlers() {
318410
rootRedirect.setNewContextURL(contextPath);
319411
rootRedirect.setPermanent(true);
320412

413+
// Optional /share handler (served by createShareContextHandler)
414+
Handler shareHandler = null;
415+
try {
416+
shareHandler = createShareContextHandler();
417+
} catch (IOException e) {
418+
logger.error("Failed to initialize /share context", e);
419+
}
420+
421+
List<Handler> handlers = new java.util.ArrayList<>();
422+
handlers.add(log);
423+
handlers.add(gzipHandler);
424+
if (shareHandler != null) {
425+
handlers.add(shareHandler);
426+
}
321427
// Put rootRedirect at the end!
322-
return new Pair<>(webApp.getSessionHandler(), new HandlerCollection(log, gzipHandler, rootRedirect));
428+
handlers.add(rootRedirect);
429+
return new Pair<>(webApp.getSessionHandler(), new HandlerCollection(handlers.toArray(new Handler[0])));
323430
}
324431

325432
private RequestLog createRequestLog() {
@@ -408,4 +515,24 @@ public void setMinThreads(int minThreads) {
408515
public void setMaxThreads(int maxThreads) {
409516
this.maxThreads = maxThreads;
410517
}
518+
519+
public void setShareEnabled(boolean shareEnabled) {
520+
this.shareEnabled = shareEnabled;
521+
}
522+
523+
public void setShareBaseDir(String shareBaseDir) {
524+
this.shareBaseDir = shareBaseDir;
525+
}
526+
527+
public void setShareCacheCtl(String shareCacheCtl) {
528+
this.shareCacheCtl = shareCacheCtl;
529+
}
530+
531+
public void setShareDirList(boolean shareDirList) {
532+
this.shareDirList = shareDirList;
533+
}
534+
535+
public void setShareSecret(String shareSecret) {
536+
this.shareSecret = shareSecret;
537+
}
411538
}
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
// Licensed to the Apache Software Foundation (ASF) under one
2+
// or more contributor license agreements. See the NOTICE file
3+
// distributed with this work for additional information
4+
// regarding copyright ownership. The ASF licenses this file
5+
// to you under the Apache License, Version 2.0 (the
6+
// "License"); you may not use this file except in compliance
7+
// with the License. You may obtain a copy of the License at
8+
//
9+
// http://www.apache.org/licenses/LICENSE-2.0
10+
//
11+
// Unless required by applicable law or agreed to in writing,
12+
// software distributed under the License is distributed on an
13+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
// KIND, either express or implied. See the License for the
15+
// specific language governing permissions and limitations
16+
// under the License.
17+
18+
package org.apache.cloudstack;
19+
20+
import java.io.IOException;
21+
import java.security.InvalidKeyException;
22+
import java.security.NoSuchAlgorithmException;
23+
import java.time.Instant;
24+
25+
import javax.servlet.Filter;
26+
import javax.servlet.FilterChain;
27+
import javax.servlet.ServletException;
28+
import javax.servlet.ServletRequest;
29+
import javax.servlet.ServletResponse;
30+
import javax.servlet.http.HttpServletRequest;
31+
import javax.servlet.http.HttpServletResponse;
32+
33+
import org.apache.cloudstack.utils.security.HMACSignUtil;
34+
import org.apache.commons.codec.DecoderException;
35+
36+
/**
37+
* Optional HMAC token check: /share/...?...&exp=1699999999&sig=BASE64URL(HMACSHA256(path|exp))
38+
* If no token params are present, request is allowed (set requireToken=true to enforce).
39+
*/
40+
public class ShareSignedUrlFilter implements Filter {
41+
private final boolean requireToken;
42+
private final String secret;
43+
44+
public ShareSignedUrlFilter(boolean requireToken, String secret) {
45+
this.requireToken = requireToken;
46+
this.secret = secret;
47+
}
48+
49+
@Override public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
50+
throws IOException, ServletException {
51+
HttpServletRequest r = (HttpServletRequest) req;
52+
HttpServletResponse w = (HttpServletResponse) res;
53+
54+
String expStr = r.getParameter("exp");
55+
String sig = r.getParameter("sig");
56+
57+
if (!requireToken && (expStr == null || sig == null)) {
58+
chain.doFilter(req, res);
59+
return;
60+
}
61+
if (expStr == null || sig == null) {
62+
w.sendError(HttpServletResponse.SC_FORBIDDEN, "Missing token");
63+
return;
64+
}
65+
long exp;
66+
try {
67+
exp = Long.parseLong(expStr);
68+
} catch (NumberFormatException e) {
69+
w.sendError(HttpServletResponse.SC_FORBIDDEN, "Bad exp");
70+
return;
71+
}
72+
if (Instant.now().getEpochSecond() > exp) {
73+
w.sendError(HttpServletResponse.SC_FORBIDDEN, "Token expired");
74+
return;
75+
}
76+
String want = "";
77+
try {
78+
String data = r.getRequestURI() + "|" + expStr;
79+
want = HMACSignUtil.generateSignature(data, secret);
80+
} catch (InvalidKeyException | NoSuchAlgorithmException | DecoderException e) {
81+
w.sendError(HttpServletResponse.SC_FORBIDDEN, "Auth error");
82+
return;
83+
}
84+
if (!want.equals(sig)) {
85+
w.sendError(HttpServletResponse.SC_FORBIDDEN, "Bad signature");
86+
return;
87+
}
88+
chain.doFilter(req, res);
89+
}
90+
}
91+

engine/components-api/src/main/java/com/cloud/hypervisor/ExternalProvisioner.java

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -35,18 +35,6 @@
3535

3636
public interface ExternalProvisioner extends Manager {
3737

38-
String getExtensionsPath();
39-
40-
String getExtensionPath(String relativePath);
41-
42-
String getChecksumForExtensionPath(String extensionName, String relativePath);
43-
44-
void prepareExtensionPath(String extensionName, boolean userDefined, String extensionRelativePath);
45-
46-
void cleanupExtensionPath(String extensionName, String extensionRelativePath);
47-
48-
void cleanupExtensionData(String extensionName, int olderThanDays, boolean cleanupDirectory);
49-
5038
PrepareExternalProvisioningAnswer prepareExternalProvisioning(String hostGuid, String extensionName, String extensionRelativePath, PrepareExternalProvisioningCommand cmd);
5139

5240
StartAnswer startInstance(String hostGuid, String extensionName, String extensionRelativePath, StartCommand cmd);

framework/cluster/src/main/java/com/cloud/cluster/ClusterManagerImpl.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -468,6 +468,8 @@ public String execute(final String strPeer, final long agentId, final String cmd
468468
return null;
469469
}
470470

471+
472+
471473
@Override
472474
public ManagementServerHostVO getPeer(final String mgmtServerId) {
473475
return _mshostDao.findByMsid(Long.parseLong(mgmtServerId));

0 commit comments

Comments
 (0)