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

fix: Adding ability to use kubernetes API proxy on Kubernetes #6

Merged
merged 15 commits into from
Jun 8, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -445,9 +445,7 @@ private void configureMultiUserMode(

bindConstant().annotatedWith(Names.named("che.agents.auth_enabled")).to(true);

if (OpenShiftInfrastructure.NAME.equals(infrastructure)) {
install(new InfraProxyModule());
}
install(new InfraProxyModule());
}

private void configureJwtProxySecureProvisioner(String infrastructure) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -535,6 +535,13 @@ che.infra.kubernetes.trusted_ca.mount_path=/public-certs
# See the `che.infra.kubernetes.trusted_ca.dest_configmap` property.
che.infra.kubernetes.trusted_ca.dest_configmap_labels=

# Enables the `/unsupported/k8s` endpoint to resolve calls on Kubernetes infrastructure.
# Provides direct access to the underlying infrastructure REST API.
# This results in huge privilege escalation.
# It impacts only Kubernetes infrastructure. Therefore it implies no security risk on OpenShift with OAuth.
# Do not enable this, unless you understand the risks.
che.infra.kubernetes.enable_unsupported_k8s=false
xbaran4 marked this conversation as resolved.
Show resolved Hide resolved

### OpenShift Infra parameters

# Comma separated list of labels to add to the CA certificates ConfigMap in user workspace.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -151,9 +151,11 @@ private static Headers toOkHttpHeaders(HttpHeaders headers) {
if (headers != null) {
for (Map.Entry<String, List<String>> e : headers.getRequestHeaders().entrySet()) {
String name = e.getKey();
List<String> values = e.getValue();
for (String value : values) {
headersBuilder.add(name, value);
if (HttpHeaders.CONTENT_TYPE.equalsIgnoreCase(name)) {
List<String> values = e.getValue();
for (String value : values) {
headersBuilder.add(name, value);
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -142,13 +142,16 @@ protected OkHttpClient getHttpClient() {
* <p>Unlike {@link #getHttpClient()}, this method creates a new HTTP client instance each time it
* is called.
*
* <p>This is a workaround to allow direct k8s API access on Kubernetes infrastructure, since
* impersonating the current user is not supported in the Kubernetes Client. This is insecure and
* will only be called if permitted by enabling it in Che properties.
*
* @return HTTP client with authorization set up
* @throws InfrastructureException if it is not possible to build the client with authentication
* infromation
*/
public OkHttpClient getAuthenticatedHttpClient() throws InfrastructureException {
throw new InfrastructureException(
"Impersonating the current user is not supported in the Kubernetes Client.");
return create(getDefaultConfig()).getHttpClient();
}

/**
Expand Down Expand Up @@ -216,7 +219,7 @@ protected Interceptor buildKubernetesInterceptor(Config config) {
* authenticate with the credentials (user/password or Oauth token) contained in the {@code
* config} parameter.
*/
private KubernetesClient create(Config config) {
private DefaultKubernetesClient create(Config config) {
OkHttpClient clientHttpClient =
httpClient.newBuilder().authenticator(Authenticator.NONE).build();
OkHttpClient.Builder builder = clientHttpClient.newBuilder();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,8 @@ public OkHttpClient getAuthenticatedHttpClient() throws InfrastructureException
throw new InfrastructureException(
"Not able to construct impersonating openshift API client.");
}
return clientForConfig(buildConfig(getDefaultConfig(), null));
// Ensure to get OkHttpClient with all necessary interceptors.
return createOC(buildConfig(getDefaultConfig(), null)).getHttpClient();
}

@Override
Expand Down Expand Up @@ -222,7 +223,7 @@ protected Interceptor buildKubernetesInterceptor(Config config) {
};
}

private OpenShiftClient createOC(Config config) {
private DefaultOpenShiftClient createOC(Config config) {
return new UnclosableOpenShiftClient(
clientForConfig(config), config, this::initializeRequestTracing);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,24 +56,35 @@ public class InfrastructureApiService extends Service {

@Context private MediaType mediaType;

private static boolean determineAllowed(String infra, String identityProvider) {
return "openshift".equals(infra)
&& identityProvider != null
&& identityProvider.startsWith("openshift");
private static boolean determineAllowed(
String infra, String identityProvider, boolean allowedForKubernetes) {
if ("openshift".equals(infra)) {
return identityProvider != null && identityProvider.startsWith("openshift");
}
return allowedForKubernetes;
}

@Inject
public InfrastructureApiService(
@Nullable @Named("che.infra.openshift.oauth_identity_provider") String identityProvider,
@Named("che.infra.kubernetes.enable_unsupported_k8s") boolean allowedForKubernetes,
RuntimeInfrastructure runtimeInfrastructure) {
this(System.getenv("CHE_INFRASTRUCTURE_ACTIVE"), identityProvider, runtimeInfrastructure);
this(
System.getenv("CHE_INFRASTRUCTURE_ACTIVE"),
allowedForKubernetes,
identityProvider,
runtimeInfrastructure);
}

@VisibleForTesting
InfrastructureApiService(String infraName, String identityProvider, RuntimeInfrastructure infra) {
InfrastructureApiService(
String infraName,
boolean allowedForKubernetes,
String identityProvider,
RuntimeInfrastructure infra) {
this.runtimeInfrastructure = infra;
this.mapper = new ObjectMapper();
this.allowed = determineAllowed(infraName, identityProvider);
this.allowed = determineAllowed(infraName, identityProvider, allowedForKubernetes);
}

@GET
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,13 +37,15 @@ public class InfrastructureApiServiceTest {

@BeforeMethod
public void setup() throws Exception {
apiService = new InfrastructureApiService("openshift", "openshift-identityProvider", infra);
apiService =
new InfrastructureApiService("openshift", false, "openshift-identityProvider", infra);
}

@Test
public void testFailsAuthWhenNotOnOpenShift() throws Exception {
public void testFailsAuthWhenNotAllowedForKubernetesAndNotOnOpenShift() throws Exception {
// given
apiService = new InfrastructureApiService("not-openshift", "openshift-identityProvider", infra);
apiService =
new InfrastructureApiService("not-openshift", false, "openshift-identityProvider", infra);

// when
Response response =
Expand All @@ -59,7 +61,49 @@ public void testFailsAuthWhenNotOnOpenShift() throws Exception {
@Test
public void testFailsAuthWhenNotUsingOpenShiftIdentityProvider() throws Exception {
// given
apiService = new InfrastructureApiService("openshift", "not-openshift-identityProvider", infra);
apiService =
new InfrastructureApiService("openshift", false, "not-openshift-identityProvider", infra);

// when
Response response =
given()
.contentType("application/json; charset=utf-8")
.when()
.get("/unsupported/k8s/nazdar/");

// then
assertEquals(response.getStatusCode(), 403);
}

@Test
public void testResolvesCallWhenAllowedForKubernetesOnKubernetes() throws Exception {
// given
apiService =
new InfrastructureApiService("kubernetes", true, "not-openshift-identityProvider", infra);
when(infra.sendDirectInfrastructureRequest(any(), any(), any(), any()))
.thenReturn(
javax.ws.rs.core.Response.ok()
.header("Content-Type", "application/json; charset=utf-8")
.build());

// when
Response response =
given()
.contentType("application/json; charset=utf-8")
.when()
.get("/unsupported/k8s/nazdar/");

// then
assertEquals(response.getStatusCode(), 200);
assertEquals(response.getContentType(), "application/json;charset=utf-8");
}

@Test
public void testFailsAuthWhenAllowedForKubernetesOnOpenshiftWithNonOpenshiftIdentityProvider()
throws Exception {
// given
apiService =
new InfrastructureApiService("openshift", true, "not-openshift-identityProvider", infra);

// when
Response response =
Expand Down