Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add exclusions mechanism into jwtproxy config builder & exlude liveness probes from auth #10463

Merged
merged 11 commits into from
Jul 24, 2018
Merged
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,17 @@
"protocol": "http",
"path" : "/process",
"attributes": {
"secure": "true"
"secure": "true",
"unsecuredPaths": "/liveness"
}
},
"exec-agent/ws": {
"port": "4412/tcp",
"protocol": "ws",
"path": "/connect",
"attributes": {
"secure": "true"
"secure": "true",
"unsecuredPaths": "/liveness"
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,17 @@
"protocol": "http",
"path" : "/process",
"attributes": {
"secure": "true"
"secure": "true",
"unsecuredPaths": "/liveness"
}
},
"exec-agent/ws": {
"port": "4412/tcp",
"protocol": "ws",
"path": "/connect",
"attributes": {
"secure": "true"
"secure": "true",
"unsecuredPaths": "/liveness"
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@
"protocol": "ws",
"path" : "/pty",
"attributes": {
"secure": "true"
"secure": "true",
"unsecuredPaths": "/liveness"
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@
"protocol": "ws",
"path" : "/pty",
"attributes": {
"secure": "true"
"secure": "true",
"unsecuredPaths": "/liveness"
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,13 @@ public interface ServerConfig {
*/
String SECURE_SERVER_ATTRIBUTE = "secure";

/**
* {@link ServerConfig} and {@link Server} attribute name which can contain an comma-separated
* list of URI-s which are considered as non-secure on the given server and can be accessible with
* unauthenticated requests.
*/
String UNSECURED_PATHS_ATTRIBUTE = "unsecuredPaths";

/**
* Port used by server.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,13 @@
*/
package org.eclipse.che.workspace.infrastructure.kubernetes.server.secure.jwtproxy;

import static java.lang.String.format;
import static org.eclipse.che.workspace.infrastructure.kubernetes.server.secure.jwtproxy.JwtProxyProvisioner.JWT_PROXY_CONFIG_FOLDER;
import static org.eclipse.che.workspace.infrastructure.kubernetes.server.secure.jwtproxy.JwtProxyProvisioner.JWT_PROXY_PUBLIC_KEY_FILE;

import java.util.ArrayList;
import java.util.List;
import java.util.Set;

/**
* Helps to build JWTProxy config with several verifier proxies.
Expand All @@ -29,8 +31,8 @@ public JwtProxyConfigBuilder(String workspaceId) {
this.workspaceId = workspaceId;
}

public void addVerifierProxy(Integer listenPort, String upstream) {
verifierProxies.add(new VerifierProxy(listenPort, upstream));
public void addVerifierProxy(Integer listenPort, String upstream, Set<String> excludes) {
verifierProxies.add(new VerifierProxy(listenPort, upstream, excludes));
}

public String build() {
Expand All @@ -39,18 +41,18 @@ public String build() {
configBuilder.append("jwtproxy:\n" + " verifier_proxies:\n");
for (VerifierProxy verifierProxy : verifierProxies) {
configBuilder.append(
String.format(
format(
" - listen_addr: :%s\n" // :4471
+ " verifier:\n"
+ " upstream: %s/\n" // http://localhost:4401
+ " audience: http://%s\n"
+ " audience: %s\n"
+ " max_skew: 1m\n"
+ " max_ttl: 3h\n"
+ " max_ttl: 8800h\n"
+ " key_server:\n"
+ " type: preshared\n"
+ " options:\n"
+ " issuer: wsmaster\n"
+ " key_id: mykey\n"
+ " key_id: %s\n"
+ " public_key_path: "
+ JWT_PROXY_CONFIG_FOLDER
+ "/"
Expand All @@ -64,19 +66,27 @@ public String build() {
+ " type: void\n",
verifierProxy.listenPort,
verifierProxy.upstream,
workspaceId,
workspaceId));
if (!verifierProxy.excludes.isEmpty()) {
configBuilder.append(" excludes:\n");
verifierProxy.excludes.forEach(s -> configBuilder.append(format(" - %s\n", s)));
}
}

configBuilder.append(" signer_proxy:\n" + " enabled: false\n");
return configBuilder.toString();
}

private class VerifierProxy {
private Integer listenPort;
private String upstream;
private Set<String> excludes;

VerifierProxy(Integer listenPort, String upstream) {
VerifierProxy(Integer listenPort, String upstream, Set<String> excludes) {
this.listenPort = listenPort;
this.upstream = upstream;
this.excludes = excludes;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@

import static java.util.Collections.emptyList;
import static java.util.Collections.emptyMap;
import static org.eclipse.che.api.core.model.workspace.config.ServerConfig.UNSECURED_PATHS_ATTRIBUTE;
import static org.eclipse.che.commons.lang.NameGenerator.generate;
import static org.eclipse.che.workspace.infrastructure.kubernetes.Constants.CHE_ORIGINAL_NAME_LABEL;
import static org.eclipse.che.workspace.infrastructure.kubernetes.server.KubernetesServerExposer.SERVER_PREFIX;
Expand All @@ -30,9 +31,13 @@
import io.fabric8.kubernetes.api.model.VolumeBuilder;
import io.fabric8.kubernetes.api.model.VolumeMount;
import java.security.KeyPair;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import org.eclipse.che.api.core.model.workspace.config.MachineConfig;
import org.eclipse.che.api.core.model.workspace.config.ServerConfig;
import org.eclipse.che.api.core.model.workspace.runtime.RuntimeIdentity;
import org.eclipse.che.api.workspace.server.spi.InfrastructureException;
import org.eclipse.che.api.workspace.server.spi.InternalInfrastructureException;
Expand Down Expand Up @@ -105,21 +110,31 @@ public JwtProxyProvisioner(RuntimeIdentity identity, SignatureKeyManager signatu
* @param backendServiceName service name that will be exposed
* @param backendServicePort service port that will be exposed
* @param protocol protocol that will be used for exposed port
* @param secureServers secure servers to expose
* @return JWTProxy service port that expose the specified one
* @throws InfrastructureException if any exception occurs during port exposing
*/
public ServicePort expose(
KubernetesEnvironment k8sEnv,
String backendServiceName,
int backendServicePort,
String protocol)
String protocol,
Map<String, ServerConfig> secureServers)
throws InfrastructureException {
ensureJwtProxyInjected(k8sEnv);

int listenPort = availablePort++;

Set<String> excludes = new HashSet<>();
for (ServerConfig config : secureServers.values()) {
if (config.getAttributes().containsKey(UNSECURED_PATHS_ATTRIBUTE)) {
Collections.addAll(
excludes, config.getAttributes().get(UNSECURED_PATHS_ATTRIBUTE).split(","));
}
}

proxyConfigBuilder.addVerifierProxy(
listenPort, "http://" + backendServiceName + ":" + backendServicePort);
listenPort, "http://" + backendServiceName + ":" + backendServicePort, excludes);
k8sEnv
.getConfigMaps()
.get(getConfigMapName())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,8 @@ public void expose(
k8sEnv,
serviceName,
servicePort.getTargetPort().getIntVal(),
servicePort.getProtocol());
servicePort.getProtocol(),
secureServers);

exposerStrategy.expose(
k8sEnv, machineName, proxyProvisioner.getServiceName(), exposedServicePort, secureServers);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@

import static org.testng.Assert.assertEquals;

import java.util.HashSet;
import java.util.Set;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
import org.testng.reporters.Files;
Expand All @@ -33,8 +35,11 @@ public void setUp() {
@Test
public void shouldBuildJwtProxyConfigInYamlFormat() throws Exception {
// given
jwtProxyConfigBuilder.addVerifierProxy(8080, "http://tomcat:8080");
jwtProxyConfigBuilder.addVerifierProxy(4101, "ws://terminal:4101");
Set<String> excludes = new HashSet<>();
jwtProxyConfigBuilder.addVerifierProxy(8080, "http://tomcat:8080", new HashSet<>(excludes));
excludes.add("/api/liveness");
excludes.add("/other/exclude");
jwtProxyConfigBuilder.addVerifierProxy(4101, "ws://terminal:4101", new HashSet<>(excludes));

// when
String jwtProxyConfigYaml = jwtProxyConfigBuilder.build();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
import java.security.KeyPair;
import java.security.PublicKey;
import java.util.Base64;
import java.util.Collections;
import java.util.regex.Pattern;
import org.eclipse.che.api.core.model.workspace.runtime.RuntimeIdentity;
import org.eclipse.che.api.workspace.server.model.impl.RuntimeIdentityImpl;
Expand Down Expand Up @@ -91,7 +92,7 @@ public void shouldReturnGeneratedJwtProxyConfigMapName() {
@Test
public void shouldProvisionJwtProxyRelatedObjectsIntoKubernetesEnvironment() throws Exception {
// when
jwtProxyProvisioner.expose(k8sEnv, "terminal", 4401, "TCP");
jwtProxyProvisioner.expose(k8sEnv, "terminal", 4401, "TCP", Collections.EMPTY_MAP);

// then
InternalMachineConfig jwtProxyMachine =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@

import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyMap;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.verify;
Expand Down Expand Up @@ -71,7 +72,7 @@ public void shouldExposeSecureServersWithNewJwtProxyServicePort() throws Excepti
ServicePort jwtProxyServicePort = new ServicePort();
doReturn(jwtProxyServicePort)
.when(jwtProxyProvisioner)
.expose(any(), anyString(), anyInt(), anyString());
.expose(any(), anyString(), anyInt(), anyString(), anyMap());

when(jwtProxyProvisioner.getServiceName()).thenReturn(JWT_PROXY_SERVICE_NAME);

Expand All @@ -80,7 +81,7 @@ public void shouldExposeSecureServersWithNewJwtProxyServicePort() throws Excepti
k8sEnv, MACHINE_NAME, MACHINE_SERVICE_NAME, machineServicePort, servers);

// then
verify(jwtProxyProvisioner).expose(k8sEnv, MACHINE_SERVICE_NAME, 8080, "TCP");
verify(jwtProxyProvisioner).expose(k8sEnv, MACHINE_SERVICE_NAME, 8080, "TCP", servers);
verify(externalServerExposer)
.expose(k8sEnv, MACHINE_NAME, JWT_PROXY_SERVICE_NAME, jwtProxyServicePort, servers);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@ jwtproxy:
- listen_addr: :8080
verifier:
upstream: http://tomcat:8080/
audience: http://workspace123
audience: workspace123
max_skew: 1m
max_ttl: 3h
max_ttl: 8800h
key_server:
type: preshared
options:
issuer: wsmaster
key_id: mykey
key_id: workspace123
public_key_path: /config/mykey.pub
claims_verifiers:
- type: static
Expand All @@ -21,20 +21,23 @@ jwtproxy:
- listen_addr: :4101
verifier:
upstream: ws://terminal:4101/
audience: http://workspace123
audience: workspace123
max_skew: 1m
max_ttl: 3h
max_ttl: 8800h
key_server:
type: preshared
options:
issuer: wsmaster
key_id: mykey
key_id: workspace123
public_key_path: /config/mykey.pub
claims_verifiers:
- type: static
options:
iss: wsmaster
nonce_storage:
type: void
excludes:
- /api/liveness
- /other/exclude
signer_proxy:
enabled: false
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,17 @@
*/
package org.eclipse.che.multiuser.machine.authentication.server;

import static io.jsonwebtoken.SignatureAlgorithm.RS512;
import static io.jsonwebtoken.SignatureAlgorithm.RS256;
import static java.lang.String.format;
import static java.time.temporal.ChronoUnit.DAYS;
import static org.eclipse.che.multiuser.machine.authentication.shared.Constants.MACHINE_TOKEN_KIND;

import com.google.common.collect.HashBasedTable;
import com.google.common.collect.Table;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import java.security.PrivateKey;
import java.time.Instant;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
Expand Down Expand Up @@ -92,14 +94,21 @@ private String createToken(String userId, String workspaceId)
final User user = userManager.getById(userId);
final Map<String, Object> header = new HashMap<>(2);
header.put("kind", MACHINE_TOKEN_KIND);
final Map<String, Object> claims = new HashMap<>(4);
header.put("kid", workspaceId);
final Map<String, Object> claims = new HashMap<>();
// to ensure that each token is unique
claims.put(Claims.ID, UUID.randomUUID().toString());
claims.put(Constants.USER_ID_CLAIM, userId);
claims.put(Constants.USER_NAME_CLAIM, user.getName());
claims.put(Constants.WORKSPACE_ID_CLAIM, workspaceId);
// jwtproxy required claims
claims.put(Claims.ISSUER, "wsmaster");
claims.put(Claims.AUDIENCE, workspaceId);
claims.put(Claims.EXPIRATION, Instant.now().plus(365, DAYS).getEpochSecond());
claims.put(Claims.NOT_BEFORE, -1); // always
claims.put(Claims.ISSUED_AT, Instant.now().getEpochSecond());
final String token =
Jwts.builder().setClaims(claims).setHeader(header).signWith(RS512, privateKey).compact();
Jwts.builder().setClaims(claims).setHeader(header).signWith(RS256, privateKey).compact();
tokens.put(workspaceId, userId, token);
return token;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,17 @@
"protocol": "http",
"path" : "/api",
"attributes": {
"secure": "true"
"secure": "true",
"unsecuredPaths": "/api/liveness"
}
},
"wsagent/ws": {
"port": "4401/tcp",
"protocol": "ws",
"path" : "/wsagent",
"attributes": {
"secure": "true"
"secure": "true",
"unsecuredPaths": "/api/liveness"
}
}
}
Expand Down
Loading