Skip to content

Commit 83d96d9

Browse files
committed
Merge pull request '#27504: Add EC2 backend' (#67) from feature/27504/aws into develop
2 parents 95398d2 + 38bbfc1 commit 83d96d9

File tree

199 files changed

+9580
-4938
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

199 files changed

+9580
-4938
lines changed

pom.xml

Lines changed: 160 additions & 145 deletions
Large diffs are not rendered by default.
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
/**
2+
* ContainerProxy
3+
*
4+
* Copyright (C) 2016-2021 Open Analytics
5+
*
6+
* ===========================================================================
7+
*
8+
* This program is free software: you can redistribute it and/or modify
9+
* it under the terms of the Apache License as published by
10+
* The Apache Software Foundation, either version 2 of the License, or
11+
* (at your option) any later version.
12+
*
13+
* This program is distributed in the hope that it will be useful,
14+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
15+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16+
* Apache License for more details.
17+
*
18+
* You should have received a copy of the Apache License
19+
* along with this program. If not, see <http://www.apache.org/licenses/>
20+
*/
21+
package eu.openanalytics.containerproxy;
22+
23+
import eu.openanalytics.containerproxy.model.runtime.Container;
24+
25+
public class ContainerFailedToStartException extends RuntimeException {
26+
27+
private final Container Container;
28+
29+
public ContainerFailedToStartException(String message, Throwable cause, Container container) {
30+
super(message, cause);
31+
this.Container = container;
32+
}
33+
34+
public Container getContainer() {
35+
return Container;
36+
}
37+
}

src/main/java/eu/openanalytics/containerproxy/ContainerProxyApplication.java

Lines changed: 62 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,14 @@
2121
package eu.openanalytics.containerproxy;
2222

2323
import com.fasterxml.jackson.datatype.jsr353.JSR353Module;
24+
import eu.openanalytics.containerproxy.backend.ContainerBackendFactory;
25+
import eu.openanalytics.containerproxy.backend.docker.DockerEngineBackend;
26+
import eu.openanalytics.containerproxy.backend.docker.DockerSwarmBackend;
27+
import eu.openanalytics.containerproxy.backend.kubernetes.KubernetesBackend;
2428
import eu.openanalytics.containerproxy.service.hearbeat.ActiveProxiesService;
2529
import eu.openanalytics.containerproxy.service.hearbeat.HeartbeatService;
26-
import eu.openanalytics.containerproxy.service.hearbeat.SessionReActivatorService;
30+
import eu.openanalytics.containerproxy.service.hearbeat.IHeartbeatProcessor;
31+
import eu.openanalytics.containerproxy.util.LoggingConfigurer;
2732
import eu.openanalytics.containerproxy.util.ProxyMappingManager;
2833
import io.undertow.Handlers;
2934
import io.undertow.server.handlers.SameSiteCookieHandler;
@@ -38,7 +43,9 @@
3843
import org.springframework.boot.actuate.health.HealthIndicator;
3944
import org.springframework.boot.actuate.redis.RedisHealthIndicator;
4045
import org.springframework.boot.autoconfigure.SpringBootApplication;
46+
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
4147
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
48+
import org.springframework.boot.autoconfigure.security.servlet.UserDetailsServiceAutoConfiguration;
4249
import org.springframework.boot.web.embedded.undertow.UndertowServletWebServerFactory;
4350
import org.springframework.boot.web.server.PortInUseException;
4451
import org.springframework.boot.web.servlet.FilterRegistrationBean;
@@ -47,13 +54,14 @@
4754
import org.springframework.core.env.Environment;
4855
import org.springframework.data.redis.connection.RedisConnectionFactory;
4956
import org.springframework.scheduling.annotation.EnableAsync;
57+
import org.springframework.scheduling.annotation.EnableScheduling;
5058
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
5159
import org.springframework.security.core.session.SessionRegistry;
5260
import org.springframework.security.web.session.HttpSessionEventPublisher;
5361
import org.springframework.session.FindByIndexNameSessionRepository;
5462
import org.springframework.session.Session;
55-
import org.springframework.session.web.http.DefaultCookieSerializer;
5663
import org.springframework.session.security.SpringSessionBackedSessionRegistry;
64+
import org.springframework.session.web.http.DefaultCookieSerializer;
5765
import org.springframework.web.filter.FormContentFilter;
5866

5967
import javax.annotation.PostConstruct;
@@ -63,13 +71,17 @@
6371
import java.nio.file.Files;
6472
import java.nio.file.Paths;
6573
import java.security.Security;
66-
import java.util.Arrays;
74+
import java.util.List;
6775
import java.util.Objects;
6876
import java.util.Properties;
6977
import java.util.concurrent.Executor;
7078

79+
import static eu.openanalytics.containerproxy.api.ApiSecurityService.PROP_API_SECURITY_HIDE_SPEC_DETAILS;
80+
import static eu.openanalytics.containerproxy.service.ProxyService.PROPERTY_STOP_PROXIES_ON_SHUTDOWN;
81+
82+
@EnableScheduling
7183
@EnableAsync
72-
@SpringBootApplication
84+
@SpringBootApplication(exclude = {UserDetailsServiceAutoConfiguration.class})
7385
@ComponentScan("eu.openanalytics")
7486
public class ContainerProxyApplication {
7587
public static final String CONFIG_FILENAME = "application.yml";
@@ -94,10 +106,18 @@ public class ContainerProxyApplication {
94106
public static Boolean secureCookiesEnabled;
95107
public static String sameSiteCookiePolicy;
96108

97-
public static void main(String[] args) {
109+
static {
98110
Security.addProvider(new BouncyCastleProvider());
111+
ContainerBackendFactory.addBackend("docker", DockerEngineBackend.class);
112+
ContainerBackendFactory.addBackend("docker-swarm", DockerSwarmBackend.class);
113+
ContainerBackendFactory.addBackend("kubernetes", KubernetesBackend.class);
114+
}
115+
116+
public static void main(String[] args) {
99117
SpringApplication app = new SpringApplication(ContainerProxyApplication.class);
100118

119+
app.addListeners(new LoggingConfigurer());
120+
101121
boolean hasExternalConfig = Files.exists(Paths.get(CONFIG_FILENAME));
102122
if (!hasExternalConfig) app.setAdditionalProfiles(CONFIG_DEMO_PROFILE);
103123

@@ -131,6 +151,26 @@ public void init() {
131151
if (sameSiteCookiePolicy.equalsIgnoreCase("none") && !secureCookiesEnabled) {
132152
log.warn("WARNING: Invalid configuration detected: same-site-cookie policy is set to None, but secure-cookies are not enabled. Secure cookies must be enabled when using None as same-site-cookie policy ");
133153
}
154+
155+
156+
if (environment.getProperty("proxy.store-mode", "").equalsIgnoreCase("Redis")) {
157+
if (!environment.getProperty("spring.session.store-type", "").equalsIgnoreCase("redis")) {
158+
// running in HA mode, but not using Redis sessions
159+
log.warn("WARNING: Invalid configuration detected: store-mode is set to Redis (i.e. High-Availability mode), but you are not using Redis for user sessions!");
160+
}
161+
if (environment.getProperty(PROPERTY_STOP_PROXIES_ON_SHUTDOWN, Boolean.class, true)) {
162+
// running in HA mode, but proxies are removed when shutting down
163+
log.warn("WARNING: Invalid configuration detected: store-mode is set to Redis (i.e. High-Availability mode), but proxies are stopped at shutdown of server!");
164+
}
165+
}
166+
167+
boolean hideSpecDetails = environment.getProperty(PROP_API_SECURITY_HIDE_SPEC_DETAILS, Boolean.class, true);
168+
if (!hideSpecDetails) {
169+
log.warn("WARNING: Insecure configuration detected: The API is configured to return the full spec of proxies, " +
170+
"this may contain sensitive values such as the container image, secret environment variables etc. " +
171+
"Remove the proxy.api-security.hide-spec-details property to enable API security.");
172+
}
173+
134174
}
135175

136176
@Autowired(required = false)
@@ -213,7 +253,7 @@ public Health health() {
213253
@Bean
214254
@ConditionalOnProperty(name = "spring.session.store-type", havingValue = "redis")
215255
public <S extends Session> SessionRegistry sessionRegistry(FindByIndexNameSessionRepository<S> sessionRepository) {
216-
return new SpringSessionBackedSessionRegistry<S>(sessionRepository);
256+
return new SpringSessionBackedSessionRegistry<>(sessionRepository);
217257
}
218258

219259
@Bean
@@ -231,8 +271,14 @@ public Executor taskExecutor() {
231271
}
232272

233273
@Bean
234-
public HeartbeatService heartbeatService(ActiveProxiesService activeProxiesService, SessionReActivatorService sessionReActivatorService) {
235-
return new HeartbeatService(Arrays.asList(activeProxiesService, sessionReActivatorService));
274+
public HeartbeatService heartbeatService(List<IHeartbeatProcessor> heartbeatProcessors) {
275+
return new HeartbeatService(heartbeatProcessors);
276+
}
277+
278+
@Bean
279+
@ConditionalOnMissingBean
280+
public ActiveProxiesService activeProxiesService() {
281+
return new ActiveProxiesService();
236282
}
237283

238284
public static Properties getDefaultProperties() {
@@ -249,6 +295,7 @@ public static Properties getDefaultProperties() {
249295

250296
// disable logging of requests, since this reads part of the requests and therefore undertow is unable to correctly handle those requests
251297
properties.put("logging.level.org.springframework.web.servlet.DispatcherServlet", "INFO");
298+
properties.put("logging.level.io.fabric8.kubernetes.client.dsl.internal.VersionUsageUtils", "ERROR");
252299

253300
properties.put("spring.application.name", "ContainerProxy");
254301

@@ -263,8 +310,9 @@ public static Properties getDefaultProperties() {
263310
properties.put("management.server.port", "9090");
264311
// enable prometheus endpoint by default (but not the exporter)
265312
properties.put("management.endpoint.prometheus.enabled", "true");
313+
properties.put("management.endpoint.recyclable.enabled", "true");
266314
// include prometheus and health endpoint in exposure
267-
properties.put("management.endpoints.web.exposure.include", "health,prometheus");
315+
properties.put("management.endpoints.web.exposure.include", "health,prometheus,recyclable");
268316

269317
// ====================
270318

@@ -284,6 +332,10 @@ public static Properties getDefaultProperties() {
284332

285333
properties.put("spring.config.use-legacy-processing", true);
286334

335+
// disable openapi docs and swagger ui
336+
properties.put("springdoc.api-docs.enabled", false);
337+
properties.put("springdoc.swagger-ui.enabled", false);
338+
287339
return properties;
288340
}
289341

@@ -293,4 +345,4 @@ private static void setDefaultProperties(SpringApplication app) {
293345
System.setProperty("jdk.serialSetFilterAfterRead", "true");
294346
}
295347

296-
}
348+
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
/**
2+
* ContainerProxy
3+
*
4+
* Copyright (C) 2016-2021 Open Analytics
5+
*
6+
* ===========================================================================
7+
*
8+
* This program is free software: you can redistribute it and/or modify
9+
* it under the terms of the Apache License as published by
10+
* The Apache Software Foundation, either version 2 of the License, or
11+
* (at your option) any later version.
12+
*
13+
* This program is distributed in the hope that it will be useful,
14+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
15+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16+
* Apache License for more details.
17+
*
18+
* You should have received a copy of the Apache License
19+
* along with this program. If not, see <http://www.apache.org/licenses/>
20+
*/
21+
package eu.openanalytics.containerproxy;
22+
23+
import eu.openanalytics.containerproxy.model.store.IProxyStore;
24+
import eu.openanalytics.containerproxy.model.store.IHeartbeatStore;
25+
import eu.openanalytics.containerproxy.model.store.memory.MemoryProxyStore;
26+
import eu.openanalytics.containerproxy.model.store.memory.MemoryHeartbeatStore;
27+
import eu.openanalytics.containerproxy.service.leader.memory.MemoryLeaderService;
28+
import eu.openanalytics.containerproxy.service.portallocator.memory.MemoryPortAllocator;
29+
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
30+
import org.springframework.context.annotation.Bean;
31+
import org.springframework.context.annotation.Configuration;
32+
33+
@Configuration
34+
@ConditionalOnProperty(name = "proxy.store-mode", havingValue = "None", matchIfMissing=true)
35+
public class MemoryStoreConfiguration {
36+
37+
@Bean
38+
public IProxyStore proxyStore() {
39+
return new MemoryProxyStore();
40+
}
41+
42+
@Bean
43+
public IHeartbeatStore heartbeatStore() {
44+
return new MemoryHeartbeatStore();
45+
}
46+
47+
@Bean
48+
public MemoryLeaderService leaderService() {
49+
return new MemoryLeaderService();
50+
}
51+
52+
@Bean
53+
public MemoryPortAllocator portAllocator() {
54+
return new MemoryPortAllocator();
55+
}
56+
57+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
/**
2+
* ContainerProxy
3+
*
4+
* Copyright (C) 2016-2021 Open Analytics
5+
*
6+
* ===========================================================================
7+
*
8+
* This program is free software: you can redistribute it and/or modify
9+
* it under the terms of the Apache License as published by
10+
* The Apache Software Foundation, either version 2 of the License, or
11+
* (at your option) any later version.
12+
*
13+
* This program is distributed in the hope that it will be useful,
14+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
15+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16+
* Apache License for more details.
17+
*
18+
* You should have received a copy of the Apache License
19+
* along with this program. If not, see <http://www.apache.org/licenses/>
20+
*/
21+
package eu.openanalytics.containerproxy;
22+
23+
import eu.openanalytics.containerproxy.model.runtime.Proxy;
24+
25+
public class ProxyFailedToStartException extends RuntimeException {
26+
27+
private final Proxy proxy;
28+
29+
public ProxyFailedToStartException(String message, Throwable cause, Proxy proxy) {
30+
super(message, cause);
31+
this.proxy = proxy;
32+
}
33+
34+
public Proxy getProxy() {
35+
return proxy;
36+
}
37+
}

src/main/java/eu/openanalytics/containerproxy/RedisSessionConfig.java

Lines changed: 11 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -21,63 +21,27 @@
2121
package eu.openanalytics.containerproxy;
2222

2323
import eu.openanalytics.containerproxy.service.IdentifierService;
24-
import org.springframework.beans.factory.annotation.Autowired;
25-
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
26-
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
27-
import org.springframework.boot.autoconfigure.session.RedisSessionProperties;
28-
import org.springframework.boot.autoconfigure.session.SessionProperties;
29-
import org.springframework.boot.context.properties.EnableConfigurationProperties;
3024
import org.springframework.context.annotation.Bean;
3125
import org.springframework.context.annotation.Configuration;
32-
import org.springframework.core.env.Environment;
33-
import org.springframework.session.data.redis.config.ConfigureNotifyKeyspaceEventsAction;
34-
import org.springframework.session.data.redis.config.ConfigureRedisAction;
35-
import org.springframework.session.data.redis.config.annotation.web.http.RedisHttpSessionConfiguration;
26+
import org.springframework.session.config.SessionRepositoryCustomizer;
27+
import org.springframework.session.data.redis.RedisIndexedSessionRepository;
3628

37-
import javax.inject.Inject;
38-
import java.time.Duration;
39-
40-
@ConditionalOnProperty(name = "spring.session.store-type", havingValue = "redis")
4129
@Configuration(proxyBeanMethods = false)
42-
@EnableConfigurationProperties(RedisSessionProperties.class)
43-
public class RedisSessionConfig extends RedisHttpSessionConfiguration {
44-
45-
private String redisNamespace;
46-
47-
@Inject
48-
private IdentifierService identifierService;
30+
public class RedisSessionConfig {
4931

50-
@Bean
51-
@ConditionalOnMissingBean
52-
public ConfigureRedisAction configureRedisAction(RedisSessionProperties redisSessionProperties) {
53-
switch (redisSessionProperties.getConfigureAction()) {
54-
case NOTIFY_KEYSPACE_EVENTS:
55-
return new ConfigureNotifyKeyspaceEventsAction();
56-
case NONE:
57-
return ConfigureRedisAction.NO_OP;
58-
}
59-
throw new IllegalStateException(
60-
"Unsupported redis configure action '" + redisSessionProperties.getConfigureAction() + "'.");
61-
62-
}
63-
64-
@Autowired
65-
public void customize(SessionProperties sessionProperties, RedisSessionProperties redisSessionProperties) {
66-
Duration timeout = sessionProperties.getTimeout();
67-
if (timeout != null) {
68-
setMaxInactiveIntervalInSeconds((int) timeout.getSeconds());
69-
}
70-
setFlushMode(redisSessionProperties.getFlushMode());
71-
setSaveMode(redisSessionProperties.getSaveMode());
72-
setCleanupCron(redisSessionProperties.getCleanupCron());
32+
private final String redisNamespace;
7333

34+
public RedisSessionConfig(IdentifierService identifierService) {
7435
if (identifierService.realmId != null) {
75-
redisNamespace = String.format("shinyproxy__%s__%s", identifierService.realmId, redisSessionProperties.getNamespace());
36+
redisNamespace = String.format("shinyproxy__%s__%s", identifierService.realmId, RedisIndexedSessionRepository.DEFAULT_NAMESPACE);
7637
} else {
77-
redisNamespace = String.format("shinyproxy__%s", redisSessionProperties.getNamespace());
38+
redisNamespace = String.format("shinyproxy__%s", RedisIndexedSessionRepository.DEFAULT_NAMESPACE);
7839
}
40+
}
7941

80-
setRedisNamespace(redisNamespace);
42+
@Bean
43+
public SessionRepositoryCustomizer<RedisIndexedSessionRepository> sessionRepositorySessionRepositoryCustomizer() {
44+
return redisIndexedSessionRepository -> redisIndexedSessionRepository.setRedisKeyNamespace(redisNamespace);
8145
}
8246

8347
public String getNamespace() {

0 commit comments

Comments
 (0)