Skip to content

Commit

Permalink
Solution to problem that Kubernetes proxy strips standard Authorizati…
Browse files Browse the repository at this point in the history
…on header
  • Loading branch information
skarsaune committed Aug 30, 2020
1 parent b0f9875 commit 373fb85
Show file tree
Hide file tree
Showing 11 changed files with 95 additions and 39 deletions.
@@ -1,4 +1,4 @@
package org.jolokia.osgi.security;
package org.jolokia.util;

/*
* Copyright 2009-2013 Roland Huss
Expand Down
@@ -1,10 +1,10 @@
package org.jolokia.osgi.security;
package org.jolokia.util;

import org.jolokia.util.Base64Util;
import org.testng.Assert;
import org.testng.annotations.Test;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertFalse;
import static org.testng.Assert.assertTrue;

import static org.testng.Assert.*;
import org.testng.annotations.Test;

/**
* @author roland
Expand Down
Expand Up @@ -50,8 +50,14 @@ public DelegatingAuthenticator(String pRealm, String pUrl, String pPrincipalSpec
public Result authenticate(HttpExchange pHttpExchange) {
try {
URLConnection connection = delegateURL.openConnection();
String authorization = pHttpExchange.getRequestHeaders()
.getFirst("Authorization");
if(authorization == null){//In case middleware strips Authorization, allow alternate header
authorization = pHttpExchange.getRequestHeaders()
.getFirst("X-jolokia-authorization");
}
connection.addRequestProperty("Authorization",
pHttpExchange.getRequestHeaders().getFirst("Authorization"));
authorization);
connection.setConnectTimeout(2000);
connection.connect();
if (connection instanceof HttpURLConnection) {
Expand Down
@@ -1,32 +1,59 @@
package org.jolokia.jvmagent.security;

import com.sun.net.httpserver.Authenticator;
import com.sun.net.httpserver.BasicAuthenticator;
import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpPrincipal;
import java.util.Base64;
import org.jolokia.util.AuthorizationHeaderParser;
import org.jolokia.util.AuthorizationHeaderParser.Result;
import org.jolokia.util.Base64Util;

/**
* Simple authenticator using user and password for basic authentication.
*
* @author roland
* @since 07.06.13
*/
*/
public class UserPasswordAuthenticator extends BasicAuthenticator {
private String user;
private String password;

/**
* Authenticator which checks against a given user and password
*
* @param pRealm realm for this authentication
* @param pUser user to check again
* @param pPassword her password
*/
public UserPasswordAuthenticator(String pRealm, String pUser, String pPassword) {
super(pRealm);
user = pUser;
password = pPassword;
}
private String user;
private String password;

/**
* Authenticator which checks against a given user and password
*
* @param pRealm realm for this authentication
* @param pUser user to check again
* @param pPassword her password
*/
public UserPasswordAuthenticator(String pRealm, String pUser, String pPassword) {
super(pRealm);
user = pUser;
password = pPassword;
}

/**
* {@inheritDoc}
*/
public boolean checkCredentials(String pUserGiven, String pPasswordGiven) {
return user.equals(pUserGiven) && password.equals(pPasswordGiven);
}

/** {@inheritDoc} */
public boolean checkCredentials(String pUserGiven, String pPasswordGiven) {
return user.equals(pUserGiven) && password.equals(pPasswordGiven);
@Override
public Result authenticate(HttpExchange httpExchange) {
String auth = httpExchange.getRequestHeaders().getFirst("Authorization");
if (auth == null) {//in the case where the alternate header is used
final String alternateAuth = httpExchange.getRequestHeaders()
.getFirst("X-jolokia-authorization");
if (alternateAuth != null) {
final AuthorizationHeaderParser.Result parsed = AuthorizationHeaderParser
.parse(alternateAuth);
if(parsed.isValid()&&checkCredentials(parsed.getUser(), parsed.getPassword())){
return new Success(new HttpPrincipal(parsed.getUser(), this.realm));
}
}
}
return super.authenticate(httpExchange);
}
}
Expand Up @@ -41,6 +41,11 @@ public class DelegatingAuthenticatorTest extends BaseAuthenticatorTest {
private Server jettyServer;
private String url;

@DataProvider
public static Object[][] headers() {
return new Object[][]{{"Authorization"}, {"X-jolokia-authorization"}};
}

@BeforeClass
public void setup() throws Exception {
int port = EnvTestUtil.getFreePort();
Expand Down Expand Up @@ -86,8 +91,8 @@ public void noAuth() {
assertEquals(((Authenticator.Failure) result).getResponseCode(), 401);
}

@Test
public void withAuth() {
@Test(dataProvider = "headers")
public void withAuth(String header) {
SSLSocketFactory sFactory = HttpsURLConnection.getDefaultSSLSocketFactory();
HostnameVerifier hVerifier = HttpsURLConnection.getDefaultHostnameVerifier();
try {
Expand All @@ -98,7 +103,7 @@ public void withAuth() {
null, ""
};
for (int i = 0; i < data.length; i += 2) {
HttpPrincipal principal = executeAuthCheck(data[i]);
HttpPrincipal principal = executeAuthCheck(data[i], header);
assertEquals(principal.getRealm(), "jolokia");
assertEquals(principal.getUsername(), data[i+1]);
}
Expand All @@ -108,11 +113,11 @@ public void withAuth() {
}
}

private HttpPrincipal executeAuthCheck(String pSpec) {
private HttpPrincipal executeAuthCheck(String pSpec, String header) {
DelegatingAuthenticator authenticator = new DelegatingAuthenticator("jolokia", url, pSpec, true);

Headers respHeader = new Headers();
HttpExchange ex = createHttpExchange(respHeader, "Authorization", "Bearer blub");
HttpExchange ex = createHttpExchange(respHeader, header, "Bearer blub");

Authenticator.Result result = authenticator.authenticate(ex);
assertNotNull(result);
Expand Down Expand Up @@ -177,10 +182,10 @@ public void malformedUrl() {
new DelegatingAuthenticator("jolokia","blub//://bla",null,false);
}

@Test
public void emptySpec() {
@Test(dataProvider = "headers")
public void emptySpec(String header){

HttpPrincipal principal = executeAuthCheck("empty:");
HttpPrincipal principal = executeAuthCheck("empty:", header);
assertEquals(principal.getRealm(), "jolokia");
assertEquals(principal.getUsername(), "");
}
Expand Down
@@ -1,6 +1,7 @@
package org.jolokia.osgi.security;

import javax.servlet.http.HttpServletRequest;
import org.jolokia.util.AuthorizationHeaderParser;

/**
* Interface used for performing the authentication.
Expand All @@ -17,6 +18,10 @@ public abstract class BaseAuthenticator implements Authenticator {
*/
public boolean authenticate(HttpServletRequest pRequest) {
String auth = pRequest.getHeader("Authorization");
if(auth==null){
//For cases where middleware may strip credentials
auth=pRequest.getHeader("X-jolokia-authorization");

This comment has been minimized.

Copy link
@rhuss

rhuss Aug 31, 2020

Member

Can we put this into a constant ? thx!

}
if (auth == null) {
return false;
}
Expand Down
Expand Up @@ -2,6 +2,7 @@

import javax.servlet.http.HttpServletRequest;

import org.jolokia.util.AuthorizationHeaderParser;
import org.osgi.service.http.HttpContext;

/**
Expand Down
Expand Up @@ -5,6 +5,7 @@
import javax.servlet.http.HttpServletRequest;

import org.jolokia.config.ConfigKey;
import org.jolokia.util.AuthorizationHeaderParser;
import org.jolokia.util.UserPasswordCallbackHandler;
import org.osgi.service.http.HttpContext;

Expand Down
Expand Up @@ -59,10 +59,22 @@ public void correctAuth() throws IOException {
assertTrue(context.handleSecurity(request,response));
}

@Test
public void correctAlternateAuth() throws IOException {
expect(request.getHeader("Authorization")).andReturn(null);
expect(request.getHeader("X-jolokia-authorization")).andReturn("Basic cm9sYW5kOnMhY3IhdA==");
request.setAttribute(HttpContext.AUTHENTICATION_TYPE,HttpServletRequest.BASIC_AUTH);
request.setAttribute(HttpContext.REMOTE_USER, "roland");
replay(request,response);

assertTrue(context.handleSecurity(request,response));
}

@Test
public void noAuth() throws IOException {

expect(request.getHeader("Authorization")).andReturn(null);
expect(request.getHeader("X-jolokia-authorization")).andReturn(null);
response.setHeader(eq("WWW-Authenticate"), EasyMock.<String>anyObject());
response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
replay(request, response);
Expand Down
Expand Up @@ -8,6 +8,7 @@

import org.easymock.EasyMock;
import org.jolokia.config.ConfigKey;
import org.jolokia.util.AuthorizationHeaderParser;
import org.osgi.service.http.HttpContext;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
Expand Down Expand Up @@ -84,4 +85,4 @@ private HttpServletRequest prepareRequest() {
}


}
}
Expand Up @@ -66,7 +66,8 @@ public MinimalHttpClientAdapter(BaseClient client, String urlPath, Map<String, O

static void authenticate(Map<String, String> headers, String username, String password) {
if (username != null) {
headers.put("Authorization","Basic " + Base64Util
//use custom header as Authorization will be stripped by kubernetes proxy
headers.put("X-jolokia-authorization","Basic " + Base64Util
.encode(( username + ":" + password).getBytes()));
}
}
Expand Down Expand Up @@ -128,10 +129,7 @@ private static URL buildHttpUri(BaseClient client, String resourcePath,
final URL masterUrl = client.getMasterUrl();
final HttpUrl.Builder builder = new HttpUrl.Builder().scheme(masterUrl.getProtocol())
.host(masterUrl.getHost()).port(masterUrl.getPort()).query(query);
final StringTokenizer splitter = new StringTokenizer(resourcePath, "/");
while (splitter.hasMoreElements()) {
builder.addPathSegment(splitter.nextElement().toString());
}
builder.encodedPath(resourcePath);
return builder.build().url();
}

Expand Down

1 comment on commit 373fb85

@skarsaune
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe not the most elegant solution, but we somehow need to bypass the kuberntes proxy handling in order to secure Jolokia endpoints.

Please sign in to comment.