Skip to content

Commit 2c54eb9

Browse files
committed
HBASE-28368 Backport "HBASE-27693 Support for Hadoop's LDAP Authentication mechanism (Web UI only)" to branch-2
Co-authored-by: Yash Dodeja <[email protected]>
1 parent c51f0f6 commit 2c54eb9

File tree

6 files changed

+408
-0
lines changed

6 files changed

+408
-0
lines changed

hbase-http/pom.xml

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,60 @@
170170
<artifactId>log4j-1.2-api</artifactId>
171171
<scope>test</scope>
172172
</dependency>
173+
<dependency>
174+
<groupId>org.apache.directory.server</groupId>
175+
<artifactId>apacheds-core</artifactId>
176+
<version>${apacheds.version}</version>
177+
<scope>test</scope>
178+
<exclusions>
179+
<exclusion>
180+
<groupId>org.bouncycastle</groupId>
181+
<artifactId>bcprov-jdk15on</artifactId>
182+
</exclusion>
183+
</exclusions>
184+
</dependency>
185+
<dependency>
186+
<groupId>org.apache.directory.server</groupId>
187+
<artifactId>apacheds-protocol-ldap</artifactId>
188+
<version>${apacheds.version}</version>
189+
<scope>test</scope>
190+
<exclusions>
191+
<exclusion>
192+
<groupId>org.bouncycastle</groupId>
193+
<artifactId>bcprov-jdk15on</artifactId>
194+
</exclusion>
195+
</exclusions>
196+
</dependency>
197+
<dependency>
198+
<groupId>org.apache.directory.server</groupId>
199+
<artifactId>apacheds-ldif-partition</artifactId>
200+
<version>${apacheds.version}</version>
201+
<scope>test</scope>
202+
</dependency>
203+
<dependency>
204+
<groupId>org.apache.directory.api</groupId>
205+
<artifactId>api-ldap-codec-core</artifactId>
206+
<version>${ldap-api.version}</version>
207+
<scope>test</scope>
208+
</dependency>
209+
<dependency>
210+
<groupId>org.apache.directory.api</groupId>
211+
<artifactId>api-ldap-model</artifactId>
212+
<version>${ldap-api.version}</version>
213+
<scope>test</scope>
214+
</dependency>
215+
<dependency>
216+
<groupId>org.apache.directory.server</groupId>
217+
<artifactId>apacheds-server-integ</artifactId>
218+
<version>${apacheds.version}</version>
219+
<scope>test</scope>
220+
<exclusions>
221+
<exclusion>
222+
<groupId>log4j</groupId>
223+
<artifactId>log4j</artifactId>
224+
</exclusion>
225+
</exclusions>
226+
</dependency>
173227
</dependencies>
174228

175229
<build>
@@ -380,6 +434,12 @@
380434
<groupId>org.apache.hadoop</groupId>
381435
<artifactId>hadoop-minikdc</artifactId>
382436
<scope>test</scope>
437+
<exclusions>
438+
<exclusion>
439+
<groupId>org.apache.directory.api</groupId>
440+
<artifactId>api-all</artifactId>
441+
</exclusion>
442+
</exclusions>
383443
</dependency>
384444
</dependencies>
385445
<build>
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing, software
13+
* distributed under the License is distributed on an "AS IS" BASIS,
14+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
* See the License for the specific language governing permissions and
16+
* limitations under the License.
17+
*/
18+
package org.apache.hadoop.hbase.http.lib;
19+
20+
import java.io.IOException;
21+
import java.util.HashMap;
22+
import java.util.Map;
23+
import org.apache.hadoop.conf.Configuration;
24+
import org.apache.hadoop.hbase.HBaseInterfaceAudience;
25+
import org.apache.hadoop.hbase.http.FilterContainer;
26+
import org.apache.hadoop.hbase.http.FilterInitializer;
27+
import org.apache.hadoop.hbase.http.HttpServer;
28+
import org.apache.hadoop.security.SecurityUtil;
29+
import org.apache.hadoop.security.authentication.server.AuthenticationFilter;
30+
import org.apache.hadoop.security.authentication.server.KerberosAuthenticationHandler;
31+
import org.apache.yetus.audience.InterfaceAudience;
32+
33+
/**
34+
* This class is copied from Hadoop. Initializes hadoop-auth AuthenticationFilter which provides
35+
* support for Kerberos HTTP SPNEGO authentication.
36+
* <p>
37+
* It enables anonymous access, simple/pseudo and Kerberos HTTP SPNEGO authentication for HBase web
38+
* UI endpoints.
39+
* <p>
40+
* Refer to the <code>core-default.xml</code> file, after the comment 'HTTP Authentication' for
41+
* details on the configuration options. All related configuration properties have
42+
* 'hadoop.http.authentication.' as prefix.
43+
*/
44+
@InterfaceAudience.LimitedPrivate(HBaseInterfaceAudience.CONFIG)
45+
public class AuthenticationFilterInitializer extends FilterInitializer {
46+
47+
static final String PREFIX = "hadoop.http.authentication.";
48+
49+
/**
50+
* Initializes hadoop-auth AuthenticationFilter.
51+
* <p>
52+
* Propagates to hadoop-auth AuthenticationFilter configuration all Hadoop configuration
53+
* properties prefixed with "hadoop.http.authentication."
54+
* @param container The filter container
55+
* @param conf Configuration for run-time parameters
56+
*/
57+
@Override
58+
public void initFilter(FilterContainer container, Configuration conf) {
59+
Map<String, String> filterConfig = getFilterConfigMap(conf, PREFIX);
60+
61+
container.addFilter("authentication", AuthenticationFilter.class.getName(), filterConfig);
62+
}
63+
64+
public static Map<String, String> getFilterConfigMap(Configuration conf, String prefix) {
65+
Map<String, String> filterConfig = new HashMap<String, String>();
66+
67+
// setting the cookie path to root '/' so it is used for all resources.
68+
filterConfig.put(AuthenticationFilter.COOKIE_PATH, "/");
69+
Map<String, String> propsWithPrefix = conf.getPropsWithPrefix(prefix);
70+
71+
for (Map.Entry<String, String> entry : propsWithPrefix.entrySet()) {
72+
filterConfig.put(entry.getKey(), entry.getValue());
73+
}
74+
75+
// Resolve _HOST into bind address
76+
String bindAddress = conf.get(HttpServer.BIND_ADDRESS);
77+
String principal = filterConfig.get(KerberosAuthenticationHandler.PRINCIPAL);
78+
if (principal != null) {
79+
try {
80+
principal = SecurityUtil.getServerPrincipal(principal, bindAddress);
81+
} catch (IOException ex) {
82+
throw new RuntimeException("Could not resolve Kerberos principal name: " + ex.toString(),
83+
ex);
84+
}
85+
filterConfig.put(KerberosAuthenticationHandler.PRINCIPAL, principal);
86+
}
87+
return filterConfig;
88+
}
89+
90+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing, software
13+
* distributed under the License is distributed on an "AS IS" BASIS,
14+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
* See the License for the specific language governing permissions and
16+
* limitations under the License.
17+
*/
18+
package org.apache.hadoop.hbase.http;
19+
20+
/**
21+
* This class defines the constants used by the LDAP integration tests.
22+
*/
23+
public final class LdapConstants {
24+
25+
/**
26+
* This class defines constants to be used for LDAP integration testing. Hence this class is not
27+
* expected to be instantiated.
28+
*/
29+
private LdapConstants() {
30+
}
31+
32+
public static final String LDAP_BASE_DN = "dc=example,dc=com";
33+
public static final String LDAP_SERVER_ADDR = "localhost";
34+
35+
}
Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing, software
13+
* distributed under the License is distributed on an "AS IS" BASIS,
14+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
* See the License for the specific language governing permissions and
16+
* limitations under the License.
17+
*/
18+
package org.apache.hadoop.hbase.http;
19+
20+
import static org.junit.Assert.assertEquals;
21+
22+
import java.io.IOException;
23+
import java.net.HttpURLConnection;
24+
import java.net.URL;
25+
import org.apache.commons.codec.binary.Base64;
26+
import org.apache.directory.server.annotations.CreateLdapServer;
27+
import org.apache.directory.server.annotations.CreateTransport;
28+
import org.apache.directory.server.core.annotations.ApplyLdifs;
29+
import org.apache.directory.server.core.annotations.ContextEntry;
30+
import org.apache.directory.server.core.annotations.CreateDS;
31+
import org.apache.directory.server.core.annotations.CreatePartition;
32+
import org.apache.directory.server.core.integ.CreateLdapServerRule;
33+
import org.apache.hadoop.conf.Configuration;
34+
import org.apache.hadoop.hbase.HBaseClassTestRule;
35+
import org.apache.hadoop.hbase.http.resource.JerseyResource;
36+
import org.apache.hadoop.hbase.testclassification.MiscTests;
37+
import org.apache.hadoop.hbase.testclassification.SmallTests;
38+
import org.junit.AfterClass;
39+
import org.junit.BeforeClass;
40+
import org.junit.ClassRule;
41+
import org.junit.Test;
42+
import org.junit.experimental.categories.Category;
43+
import org.slf4j.Logger;
44+
import org.slf4j.LoggerFactory;
45+
46+
/**
47+
* Test class for LDAP authentication on the HttpServer.
48+
*/
49+
@Category({ MiscTests.class, SmallTests.class })
50+
@CreateLdapServer(
51+
transports = { @CreateTransport(protocol = "LDAP", address = LdapConstants.LDAP_SERVER_ADDR), })
52+
@CreateDS(allowAnonAccess = true,
53+
partitions = { @CreatePartition(name = "Test_Partition", suffix = LdapConstants.LDAP_BASE_DN,
54+
contextEntry = @ContextEntry(entryLdif = "dn: " + LdapConstants.LDAP_BASE_DN + " \n"
55+
+ "dc: example\n" + "objectClass: top\n" + "objectClass: domain\n\n")) })
56+
@ApplyLdifs({ "dn: uid=bjones," + LdapConstants.LDAP_BASE_DN, "cn: Bob Jones", "sn: Jones",
57+
"objectClass: inetOrgPerson", "uid: bjones", "userPassword: p@ssw0rd" })
58+
public class TestLdapHttpServer extends HttpServerFunctionalTest {
59+
60+
@ClassRule
61+
public static final HBaseClassTestRule CLASS_RULE =
62+
HBaseClassTestRule.forClass(TestLdapHttpServer.class);
63+
@ClassRule
64+
public static CreateLdapServerRule serverRule = new CreateLdapServerRule();
65+
66+
private static final Logger LOG = LoggerFactory.getLogger(TestLdapHttpServer.class);
67+
68+
private static HttpServer server;
69+
private static URL baseUrl;
70+
71+
@BeforeClass
72+
public static void setupServer() throws Exception {
73+
Configuration conf = new Configuration();
74+
buildLdapConfiguration(conf);
75+
server = createTestServer(conf);
76+
server.addUnprivilegedServlet("echo", "/echo", TestHttpServer.EchoServlet.class);
77+
server.addJerseyResourcePackage(JerseyResource.class.getPackage().getName(), "/jersey/*");
78+
server.start();
79+
baseUrl = getServerURL(server);
80+
81+
LOG.info("HTTP server started: " + baseUrl);
82+
}
83+
84+
@AfterClass
85+
public static void stopServer() throws Exception {
86+
try {
87+
if (null != server) {
88+
server.stop();
89+
}
90+
} catch (Exception e) {
91+
LOG.info("Failed to stop info server", e);
92+
}
93+
}
94+
95+
private static Configuration buildLdapConfiguration(Configuration conf) {
96+
97+
conf.setInt(HttpServer.HTTP_MAX_THREADS, TestHttpServer.MAX_THREADS);
98+
99+
// Enable LDAP (pre-req)
100+
conf.set(HttpServer.HTTP_UI_AUTHENTICATION, "ldap");
101+
conf.set(HttpServer.FILTER_INITIALIZERS_PROPERTY,
102+
"org.apache.hadoop.hbase.http.lib.AuthenticationFilterInitializer");
103+
conf.set("hadoop.http.authentication.type", "ldap");
104+
conf.set("hadoop.http.authentication.ldap.providerurl", String.format("ldap://%s:%s",
105+
LdapConstants.LDAP_SERVER_ADDR, serverRule.getLdapServer().getPort()));
106+
conf.set("hadoop.http.authentication.ldap.enablestarttls", "false");
107+
conf.set("hadoop.http.authentication.ldap.basedn", LdapConstants.LDAP_BASE_DN);
108+
return conf;
109+
}
110+
111+
@Test
112+
public void testUnauthorizedClientsDisallowed() throws IOException {
113+
URL url = new URL(getServerURL(server), "/echo?a=b");
114+
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
115+
assertEquals(HttpURLConnection.HTTP_UNAUTHORIZED, conn.getResponseCode());
116+
}
117+
118+
@Test
119+
public void testAllowedClient() throws IOException {
120+
URL url = new URL(getServerURL(server), "/echo?a=b");
121+
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
122+
final Base64 base64 = new Base64(0);
123+
String userCredentials = "bjones:p@ssw0rd";
124+
String basicAuth = "Basic " + base64.encodeToString(userCredentials.getBytes());
125+
conn.setRequestProperty("Authorization", basicAuth);
126+
assertEquals(HttpURLConnection.HTTP_OK, conn.getResponseCode());
127+
}
128+
129+
@Test
130+
public void testWrongAuthClientsDisallowed() throws IOException {
131+
URL url = new URL(getServerURL(server), "/echo?a=b");
132+
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
133+
final Base64 base64 = new Base64(0);
134+
String userCredentials = "bjones:password";
135+
String basicAuth = "Basic " + base64.encodeToString(userCredentials.getBytes());
136+
conn.setRequestProperty("Authorization", basicAuth);
137+
assertEquals(HttpURLConnection.HTTP_FORBIDDEN, conn.getResponseCode());
138+
}
139+
140+
}

0 commit comments

Comments
 (0)