Skip to content

Commit 05d2ec5

Browse files
author
Sasa Berberovic
committed
Merge branch 'release/0.8.4'
2 parents 9d8295a + 0007cfc commit 05d2ec5

18 files changed

Lines changed: 324 additions & 122 deletions

File tree

Jenkinsfile

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
pipeline {
2+
3+
agent {
4+
kubernetes {
5+
yamlFile 'kubernetesPod.yaml'
6+
}
7+
}
8+
9+
options {
10+
buildDiscarder(logRotator(numToKeepStr: '3'))
11+
}
12+
13+
stages {
14+
15+
stage('build and deploy to nexus'){
16+
17+
steps {
18+
19+
container('containerproxy-build') {
20+
21+
configFileProvider([configFile(fileId: 'maven-settings-rsb', variable: 'MAVEN_SETTINGS_RSB')]) {
22+
23+
sh 'mvn -s $MAVEN_SETTINGS_RSB -U clean install deploy -DskipTests=true'
24+
25+
}
26+
}
27+
}
28+
}
29+
}
30+
}

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ It is the engine that powers a.o. [ShinyProxy](https://shinyproxy.io) but can be
1818

1919
Learn more at https://containerproxy.io (in progress)
2020

21-
#### (c) Copyright Open Analytics NV, 2017-2018 - Apache License 2.0
21+
#### (c) Copyright Open Analytics NV, 2017-2019 - Apache License 2.0
2222

2323
## Building from source
2424

kubernetesPod.yaml

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
apiVersion: v1
2+
kind: Pod
3+
metadata:
4+
name: containerproxy
5+
labels:
6+
ci: containerproxy-build
7+
spec:
8+
volumes:
9+
- name: maven-repo
10+
emptyDir: {}
11+
containers:
12+
- name: containerproxy-build
13+
image: 196229073436.dkr.ecr.eu-west-1.amazonaws.com/openanalytics/containerproxy-build
14+
securityContext:
15+
privileged: true
16+
command: ["sh"]
17+
args: ["/usr/src/app/docker-entrypoint.sh"]
18+
tty: true
19+
volumeMounts:
20+
- mountPath: ~/.m2
21+
name: maven-repo

pom.xml

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
<groupId>eu.openanalytics</groupId>
77
<artifactId>containerproxy</artifactId>
8-
<version>0.8.3</version>
8+
<version>0.8.4</version>
99
<name>ContainerProxy</name>
1010
<packaging>jar</packaging>
1111

@@ -25,12 +25,12 @@
2525
<snapshotRepository>
2626
<id>oa-nexus-snapshots</id>
2727
<name>OpenAnalytics Snapshots Repository</name>
28-
<url>http://nexus.openanalytics.eu/nexus/content/repositories/snapshots</url>
28+
<url>https://nexus.openanalytics.eu/nexus/content/repositories/snapshots</url>
2929
</snapshotRepository>
3030
<repository>
3131
<id>oa-nexus-releases</id>
3232
<name>OpenAnalytics Release Repository</name>
33-
<url>http://nexus.openanalytics.eu/nexus/content/repositories/releases</url>
33+
<url>https://nexus.openanalytics.eu/nexus/content/repositories/releases</url>
3434
</repository>
3535
</distributionManagement>
3636

@@ -293,6 +293,7 @@
293293
<exclude>**/*.yml</exclude>
294294
<exclude>**/*.json</exclude>
295295
<exclude>**/*.raml</exclude>
296+
<exclude>**/*.sh</exclude>
296297
<exclude>LICENSE</exclude>
297298
<exclude>LICENSE_HEADER</exclude>
298299
<exclude>README.md</exclude>

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040

4141
import eu.openanalytics.containerproxy.util.ProxyMappingManager;
4242
import io.undertow.Handlers;
43+
import io.undertow.servlet.api.ServletSessionConfig;
4344

4445
@SpringBootApplication
4546
@ComponentScan("eu.openanalytics")
@@ -80,6 +81,10 @@ public UndertowServletWebServerFactory servletContainer() {
8081
info.addInnerHandlerChainWrapper(defaultHandler -> {
8182
return mappingManager.createHttpHandler(defaultHandler);
8283
});
84+
ServletSessionConfig sessionConfig = new ServletSessionConfig();
85+
sessionConfig.setHttpOnly(true);
86+
sessionConfig.setSecure(Boolean.valueOf(environment.getProperty("server.secureCookies", "false")));
87+
info.setServletSessionConfig(sessionConfig);
8388
});
8489
try {
8590
factory.setAddress(InetAddress.getByName(environment.getProperty("proxy.bind-address", "0.0.0.0")));

src/main/java/eu/openanalytics/containerproxy/auth/impl/KeycloakAuthenticationBackend.java

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
import javax.inject.Inject;
3131
import javax.servlet.ServletException;
3232

33+
import org.keycloak.OAuth2Constants;
3334
import org.keycloak.adapters.AdapterDeploymentContext;
3435
import org.keycloak.adapters.KeycloakConfigResolver;
3536
import org.keycloak.adapters.KeycloakDeployment;
@@ -43,6 +44,7 @@
4344
import org.keycloak.adapters.springsecurity.authentication.KeycloakLogoutHandler;
4445
import org.keycloak.adapters.springsecurity.filter.KeycloakAuthenticationProcessingFilter;
4546
import org.keycloak.adapters.springsecurity.filter.KeycloakPreAuthActionsFilter;
47+
import org.keycloak.adapters.springsecurity.filter.QueryParamPresenceRequestMatcher;
4648
import org.keycloak.adapters.springsecurity.management.HttpSessionManager;
4749
import org.keycloak.adapters.springsecurity.token.KeycloakAuthenticationToken;
4850
import org.keycloak.representations.IDToken;
@@ -63,6 +65,10 @@
6365
import org.springframework.security.web.authentication.session.RegisterSessionAuthenticationStrategy;
6466
import org.springframework.security.web.authentication.session.SessionAuthenticationStrategy;
6567
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
68+
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
69+
import org.springframework.security.web.util.matcher.OrRequestMatcher;
70+
import org.springframework.security.web.util.matcher.RequestHeaderRequestMatcher;
71+
import org.springframework.security.web.util.matcher.RequestMatcher;
6672
import org.springframework.stereotype.Component;
6773

6874
import eu.openanalytics.containerproxy.auth.IAuthenticationBackend;
@@ -118,7 +124,17 @@ public String getLogoutSuccessURL() {
118124
@Bean
119125
@ConditionalOnProperty(name="proxy.authentication", havingValue="keycloak")
120126
protected KeycloakAuthenticationProcessingFilter keycloakAuthenticationProcessingFilter() throws Exception {
121-
KeycloakAuthenticationProcessingFilter filter = new KeycloakAuthenticationProcessingFilter(authenticationManager);
127+
// Possible solution for issue #21037, create a custom RequestMatcher that doesn't include a QueryParamPresenceRequestMatcher(OAuth2Constants.ACCESS_TOKEN) request matcher.
128+
// The QueryParamPresenceRequestMatcher(OAuth2Constants.ACCESS_TOKEN) caused the HTTP requests to be changed before they where processed.
129+
// Because the HTTP requests are adapted before they are processed, the requested failed to complete successfully and caused an io.undertow.server.TruncatedResponseException
130+
// If in the future we need a RequestMatcher for het ACCESS_TOKEN, we can implement one ourself
131+
RequestMatcher requestMatcher =
132+
new OrRequestMatcher(
133+
new AntPathRequestMatcher(KeycloakAuthenticationProcessingFilter.DEFAULT_LOGIN_URL),
134+
new RequestHeaderRequestMatcher(KeycloakAuthenticationProcessingFilter.AUTHORIZATION_HEADER)
135+
);
136+
137+
KeycloakAuthenticationProcessingFilter filter = new KeycloakAuthenticationProcessingFilter(authenticationManager, requestMatcher);
122138
filter.setSessionAuthenticationStrategy(sessionAuthenticationStrategy());
123139
// Fix: call afterPropertiesSet manually, because Spring doesn't invoke it for some reason.
124140
filter.setApplicationContext(ctx);

src/main/java/eu/openanalytics/containerproxy/auth/impl/OpenIDAuthenticationBackend.java

Lines changed: 77 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
*/
2121
package eu.openanalytics.containerproxy.auth.impl;
2222

23+
import java.util.ArrayList;
2324
import java.util.Collections;
2425
import java.util.HashSet;
2526
import java.util.List;
@@ -41,17 +42,25 @@
4142
import org.springframework.security.oauth2.client.InMemoryOAuth2AuthorizedClientService;
4243
import org.springframework.security.oauth2.client.OAuth2AuthorizedClient;
4344
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientService;
45+
import org.springframework.security.oauth2.client.oidc.userinfo.OidcUserRequest;
46+
import org.springframework.security.oauth2.client.oidc.userinfo.OidcUserService;
4447
import org.springframework.security.oauth2.client.registration.ClientRegistration;
4548
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
4649
import org.springframework.security.oauth2.client.registration.InMemoryClientRegistrationRepository;
4750
import org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestRedirectFilter;
4851
import org.springframework.security.oauth2.core.AuthorizationGrantType;
52+
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
4953
import org.springframework.security.oauth2.core.oidc.OidcIdToken;
54+
import org.springframework.security.oauth2.core.oidc.OidcUserInfo;
55+
import org.springframework.security.oauth2.core.oidc.user.DefaultOidcUser;
5056
import org.springframework.security.oauth2.core.oidc.user.OidcUser;
5157
import org.springframework.security.oauth2.core.oidc.user.OidcUserAuthority;
5258

5359
import eu.openanalytics.containerproxy.auth.IAuthenticationBackend;
5460
import eu.openanalytics.containerproxy.util.SessionHelper;
61+
import net.minidev.json.JSONArray;
62+
import net.minidev.json.parser.JSONParser;
63+
import net.minidev.json.parser.ParseException;
5564

5665
public class OpenIDAuthenticationBackend implements IAuthenticationBackend {
5766

@@ -89,7 +98,9 @@ public void configureHttpSecurity(HttpSecurity http) throws Exception {
8998
.loginPage("/login")
9099
.clientRegistrationRepository(clientRegistrationRepo)
91100
.authorizedClientService(authorizedClientService)
92-
.userInfoEndpoint().userAuthoritiesMapper(createAuthoritiesMapper());
101+
.userInfoEndpoint()
102+
.userAuthoritiesMapper(createAuthoritiesMapper())
103+
.oidcUserService(createOidcUserService());
93104
}
94105

95106
@Override
@@ -165,21 +176,82 @@ protected GrantedAuthoritiesMapper createAuthoritiesMapper() {
165176
String claims = idToken.getClaims().entrySet().stream()
166177
.map(e -> String.format("%s -> %s", e.getKey(), e.getValue()))
167178
.collect(Collectors.joining(lineSep));
168-
log.debug(String.format("Checking for roles in claim '%s'. Available claims in ID token:%s%s",
169-
rolesClaimName, lineSep, claims));
170-
179+
log.debug(String.format("Checking for roles in claim '%s'. Available claims in ID token (%d):%s%s",
180+
rolesClaimName, idToken.getClaims().size(), lineSep, claims));
171181
}
172182

183+
Object claimValue = idToken.getClaims().get(rolesClaimName);
184+
if (claimValue == null) {
185+
log.debug("No matching claim found.");
186+
} else {
187+
log.debug(String.format("Matching claim found: %s -> %s (%s)", rolesClaimName, claimValue, claimValue.getClass()));
188+
}
189+
190+
// Workaround: in some cases, getClaimAsStringList fails to parse??
173191
List<String> roles = idToken.getClaimAsStringList(rolesClaimName);
174-
if (roles == null) continue;
192+
if (roles == null && claimValue instanceof String) {
193+
List<String> parsedRoles = new ArrayList<>();
194+
try {
195+
Object value = new JSONParser(JSONParser.MODE_PERMISSIVE).parse((String) claimValue);
196+
if (value instanceof List) {
197+
List<?> valueList = (List<?>) value;
198+
valueList.forEach(o -> parsedRoles.add(o.toString()));
199+
}
200+
} catch (ParseException e) {
201+
// Unable to parse JSON
202+
}
203+
roles = parsedRoles;
204+
}
205+
if (roles == null) {
206+
if (log.isDebugEnabled()) log.debug("Failed to parse claim value as an array: " + claimValue);
207+
continue;
208+
}
209+
175210
for (String role: roles) {
176211
String mappedRole = role.toUpperCase().startsWith("ROLE_") ? role : "ROLE_" + role;
177212
mappedAuthorities.add(new SimpleGrantedAuthority(mappedRole.toUpperCase()));
178213
}
214+
if (log.isDebugEnabled()) log.debug("The following roles were successfully parsed: " + roles);
179215
}
180216
}
181217
return mappedAuthorities;
182218
};
183219
}
184220
}
221+
222+
protected OidcUserService createOidcUserService() {
223+
// Use a custom UserService that supports the 'emails' array attribute.
224+
return new OidcUserService() {
225+
@Override
226+
public OidcUser loadUser(OidcUserRequest userRequest) throws OAuth2AuthenticationException {
227+
OidcUser user = super.loadUser(userRequest);
228+
String nameAttributeKey = environment.getProperty("proxy.openid.username-attribute", "email");
229+
return new CustomNameOidcUser(new HashSet<>(user.getAuthorities()), user.getIdToken(), user.getUserInfo(), nameAttributeKey);
230+
}
231+
};
232+
}
233+
234+
private static class CustomNameOidcUser extends DefaultOidcUser {
235+
236+
private static final long serialVersionUID = 7563253562760236634L;
237+
private static final String ID_ATTR_EMAILS = "emails";
238+
239+
private boolean isEmailsAttribute;
240+
241+
public CustomNameOidcUser(Set<GrantedAuthority> authorities, OidcIdToken idToken, OidcUserInfo userInfo, String nameAttributeKey) {
242+
super(authorities, idToken, userInfo, nameAttributeKey);
243+
this.isEmailsAttribute = nameAttributeKey.equals(ID_ATTR_EMAILS);
244+
}
245+
246+
@Override
247+
public String getName() {
248+
if (isEmailsAttribute) {
249+
Object emails = getAttributes().get(ID_ATTR_EMAILS);
250+
if (emails instanceof String[]) return ((String[]) emails)[0];
251+
else if (emails instanceof JSONArray) return ((JSONArray) emails).get(0).toString();
252+
else return emails.toString();
253+
}
254+
else return super.getName();
255+
}
256+
}
185257
}

src/main/java/eu/openanalytics/containerproxy/auth/impl/saml/SAMLConfiguration.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,7 @@ public SAMLEntryPoint samlEntryPoint() {
110110
public WebSSOProfileOptions defaultWebSSOProfileOptions() {
111111
WebSSOProfileOptions webSSOProfileOptions = new WebSSOProfileOptions();
112112
webSSOProfileOptions.setIncludeScoping(false);
113+
webSSOProfileOptions.setForceAuthN(Boolean.valueOf(environment.getProperty("proxy.saml.force-authn", "false")));
113114
return webSSOProfileOptions;
114115
}
115116

src/main/java/eu/openanalytics/containerproxy/backend/docker/DockerEngineBackend.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ protected Container startContainer(ContainerSpec spec, Proxy proxy) throws Excep
7676
ContainerConfig containerConfig = ContainerConfig.builder()
7777
.hostConfig(hostConfigBuilder.build())
7878
.image(spec.getImage())
79+
.labels(spec.getLabels())
7980
.exposedPorts(portBindings.keySet())
8081
.cmd(spec.getCmd())
8182
.env(buildEnv(spec, proxy))

src/main/java/eu/openanalytics/containerproxy/backend/docker/DockerSwarmBackend.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ protected Container startContainer(ContainerSpec spec, Proxy proxy) throws Excep
7171
com.spotify.docker.client.messages.swarm.ContainerSpec containerSpec =
7272
com.spotify.docker.client.messages.swarm.ContainerSpec.builder()
7373
.image(spec.getImage())
74+
.labels(spec.getLabels())
7475
.command(spec.getCmd())
7576
.env(buildEnv(spec, proxy))
7677
.dnsConfig(DnsConfig.builder().nameServers(spec.getDns()).build())

0 commit comments

Comments
 (0)