From 373fb8595ce8f0de9c1ec721bbb9b7aa2ccba98b Mon Sep 17 00:00:00 2001 From: Martin Skarsaune Date: Sat, 22 Aug 2020 21:58:54 +0200 Subject: [PATCH] Solution to problem that Kubernetes proxy strips standard Authorization header --- .../util}/AuthorizationHeaderParser.java | 2 +- .../util}/AuthorizationHeaderParserTest.java | 10 +-- .../security/DelegatingAuthenticator.java | 8 ++- .../security/UserPasswordAuthenticator.java | 63 +++++++++++++------ .../security/DelegatingAuthenticatorTest.java | 21 ++++--- .../osgi/security/BaseAuthenticator.java | 5 ++ .../osgi/security/BasicAuthenticator.java | 1 + .../osgi/security/JaasAuthenticator.java | 1 + .../BasicAuthenticationHttpContextTest.java | 12 ++++ .../osgi/security/JaasAuthenticatorTest.java | 3 +- .../client/MinimalHttpClientAdapter.java | 8 +-- 11 files changed, 95 insertions(+), 39 deletions(-) rename agent/{osgi/src/main/java/org/jolokia/osgi/security => core/src/main/java/org/jolokia/util}/AuthorizationHeaderParser.java (98%) rename agent/{osgi/src/test/java/org/jolokia/osgi/security => core/src/test/java/org/jolokia/util}/AuthorizationHeaderParserTest.java (86%) diff --git a/agent/osgi/src/main/java/org/jolokia/osgi/security/AuthorizationHeaderParser.java b/agent/core/src/main/java/org/jolokia/util/AuthorizationHeaderParser.java similarity index 98% rename from agent/osgi/src/main/java/org/jolokia/osgi/security/AuthorizationHeaderParser.java rename to agent/core/src/main/java/org/jolokia/util/AuthorizationHeaderParser.java index 5c7c3eb69..e6fe2c12f 100644 --- a/agent/osgi/src/main/java/org/jolokia/osgi/security/AuthorizationHeaderParser.java +++ b/agent/core/src/main/java/org/jolokia/util/AuthorizationHeaderParser.java @@ -1,4 +1,4 @@ -package org.jolokia.osgi.security; +package org.jolokia.util; /* * Copyright 2009-2013 Roland Huss diff --git a/agent/osgi/src/test/java/org/jolokia/osgi/security/AuthorizationHeaderParserTest.java b/agent/core/src/test/java/org/jolokia/util/AuthorizationHeaderParserTest.java similarity index 86% rename from agent/osgi/src/test/java/org/jolokia/osgi/security/AuthorizationHeaderParserTest.java rename to agent/core/src/test/java/org/jolokia/util/AuthorizationHeaderParserTest.java index f85611ce9..b879ad579 100644 --- a/agent/osgi/src/test/java/org/jolokia/osgi/security/AuthorizationHeaderParserTest.java +++ b/agent/core/src/test/java/org/jolokia/util/AuthorizationHeaderParserTest.java @@ -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 diff --git a/agent/jvm/src/main/java/org/jolokia/jvmagent/security/DelegatingAuthenticator.java b/agent/jvm/src/main/java/org/jolokia/jvmagent/security/DelegatingAuthenticator.java index 3714c09a6..820a080b7 100644 --- a/agent/jvm/src/main/java/org/jolokia/jvmagent/security/DelegatingAuthenticator.java +++ b/agent/jvm/src/main/java/org/jolokia/jvmagent/security/DelegatingAuthenticator.java @@ -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) { diff --git a/agent/jvm/src/main/java/org/jolokia/jvmagent/security/UserPasswordAuthenticator.java b/agent/jvm/src/main/java/org/jolokia/jvmagent/security/UserPasswordAuthenticator.java index 65f4889e1..07b761de1 100644 --- a/agent/jvm/src/main/java/org/jolokia/jvmagent/security/UserPasswordAuthenticator.java +++ b/agent/jvm/src/main/java/org/jolokia/jvmagent/security/UserPasswordAuthenticator.java @@ -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); + } } diff --git a/agent/jvm/src/test/java/org/jolokia/jvmagent/security/DelegatingAuthenticatorTest.java b/agent/jvm/src/test/java/org/jolokia/jvmagent/security/DelegatingAuthenticatorTest.java index a3afad9f6..c7e993837 100644 --- a/agent/jvm/src/test/java/org/jolokia/jvmagent/security/DelegatingAuthenticatorTest.java +++ b/agent/jvm/src/test/java/org/jolokia/jvmagent/security/DelegatingAuthenticatorTest.java @@ -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(); @@ -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 { @@ -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]); } @@ -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); @@ -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(), ""); } diff --git a/agent/osgi/src/main/java/org/jolokia/osgi/security/BaseAuthenticator.java b/agent/osgi/src/main/java/org/jolokia/osgi/security/BaseAuthenticator.java index 79ff413bb..a7eb0cf11 100644 --- a/agent/osgi/src/main/java/org/jolokia/osgi/security/BaseAuthenticator.java +++ b/agent/osgi/src/main/java/org/jolokia/osgi/security/BaseAuthenticator.java @@ -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. @@ -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"); + } if (auth == null) { return false; } diff --git a/agent/osgi/src/main/java/org/jolokia/osgi/security/BasicAuthenticator.java b/agent/osgi/src/main/java/org/jolokia/osgi/security/BasicAuthenticator.java index 382c21c6f..ee0e9a9ce 100644 --- a/agent/osgi/src/main/java/org/jolokia/osgi/security/BasicAuthenticator.java +++ b/agent/osgi/src/main/java/org/jolokia/osgi/security/BasicAuthenticator.java @@ -2,6 +2,7 @@ import javax.servlet.http.HttpServletRequest; +import org.jolokia.util.AuthorizationHeaderParser; import org.osgi.service.http.HttpContext; /** diff --git a/agent/osgi/src/main/java/org/jolokia/osgi/security/JaasAuthenticator.java b/agent/osgi/src/main/java/org/jolokia/osgi/security/JaasAuthenticator.java index 852d6b9ef..649468d1e 100644 --- a/agent/osgi/src/main/java/org/jolokia/osgi/security/JaasAuthenticator.java +++ b/agent/osgi/src/main/java/org/jolokia/osgi/security/JaasAuthenticator.java @@ -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; diff --git a/agent/osgi/src/test/java/org/jolokia/osgi/security/BasicAuthenticationHttpContextTest.java b/agent/osgi/src/test/java/org/jolokia/osgi/security/BasicAuthenticationHttpContextTest.java index ac986cd11..aec600e7d 100644 --- a/agent/osgi/src/test/java/org/jolokia/osgi/security/BasicAuthenticationHttpContextTest.java +++ b/agent/osgi/src/test/java/org/jolokia/osgi/security/BasicAuthenticationHttpContextTest.java @@ -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.anyObject()); response.sendError(HttpServletResponse.SC_UNAUTHORIZED); replay(request, response); diff --git a/agent/osgi/src/test/java/org/jolokia/osgi/security/JaasAuthenticatorTest.java b/agent/osgi/src/test/java/org/jolokia/osgi/security/JaasAuthenticatorTest.java index 4164f6ada..a91a3ca37 100644 --- a/agent/osgi/src/test/java/org/jolokia/osgi/security/JaasAuthenticatorTest.java +++ b/agent/osgi/src/test/java/org/jolokia/osgi/security/JaasAuthenticatorTest.java @@ -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; @@ -84,4 +85,4 @@ private HttpServletRequest prepareRequest() { } -} \ No newline at end of file +} diff --git a/client/kubernetes/src/main/java/org/jolokia/kubernetes/client/MinimalHttpClientAdapter.java b/client/kubernetes/src/main/java/org/jolokia/kubernetes/client/MinimalHttpClientAdapter.java index 34a5ec140..0975d89b2 100644 --- a/client/kubernetes/src/main/java/org/jolokia/kubernetes/client/MinimalHttpClientAdapter.java +++ b/client/kubernetes/src/main/java/org/jolokia/kubernetes/client/MinimalHttpClientAdapter.java @@ -66,7 +66,8 @@ public MinimalHttpClientAdapter(BaseClient client, String urlPath, Map 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())); } } @@ -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(); }