Skip to content

Commit eb1df72

Browse files
ramkcursoragent
andcommitted
RANGER-5643: docker setup: ranger-audit-displatcher-solr fails in sending audit logs to Solr
Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent 409e8a7 commit eb1df72

8 files changed

Lines changed: 383 additions & 14 deletions

File tree

agents-audit/core/src/main/java/org/apache/ranger/audit/utils/InMemoryJAASConfiguration.java

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,10 @@ public final class InMemoryJAASConfiguration extends Configuration {
121121
public static final String JAAS_CONFIG_LOGIN_OPTIONS_PREFIX = "option";
122122
public static final String JAAS_PRINCIPAL_PROP = "principal";
123123

124+
/** Same property as audit-server Kafka/HDFS dispatchers ({@code ranger.audit.dispatcher.host}). */
125+
public static final String PROP_DISPATCHER_HOST = "ranger.audit.dispatcher.host";
126+
public static final String PROP_INGESTOR_HOST = "ranger.audit.ingestor.host";
127+
124128
private final Configuration parent;
125129
private final Map<String, List<AppConfigurationEntry>> applicationConfigEntryMap = new HashMap<>();
126130

@@ -215,6 +219,8 @@ public AppConfigurationEntry[] getAppConfigurationEntry(String name) {
215219
private void initialize(Properties properties) {
216220
LOG.debug("==> InMemoryJAASConfiguration.initialize()");
217221

222+
final String hostnameForJaas = resolveHostForJaas(properties);
223+
218224
int prefixLen = JAAS_CONFIG_PREFIX_PARAM.length();
219225
Map<String, SortedSet<Integer>> jaasClients = new HashMap<>();
220226

@@ -306,10 +312,11 @@ private void initialize(Properties properties) {
306312

307313
try {
308314
if (optionKey.equalsIgnoreCase(JAAS_PRINCIPAL_PROP)) {
309-
optionVal = SecurityUtil.getServerPrincipal(optionVal, (String) null);
315+
optionVal = resolveJaasPrincipal(optionVal, hostnameForJaas);
310316
}
311317
} catch (IOException e) {
312-
LOG.warn("Failed to build serverPrincipal. Using provided value:[{}]", optionVal);
318+
LOG.warn("Failed to resolve JAAS principal [{}] with host [{}]. Using raw value.",
319+
optionVal, hostnameForJaas, e);
313320
}
314321
}
315322

@@ -344,6 +351,36 @@ private void initialize(Properties properties) {
344351
LOG.debug("<== InMemoryJAASConfiguration.initialize()");
345352
}
346353

354+
static String resolveHostForJaas(Properties properties) {
355+
String host = properties.getProperty(PROP_DISPATCHER_HOST);
356+
357+
if (host == null || host.isBlank()) {
358+
host = properties.getProperty(PROP_INGESTOR_HOST);
359+
}
360+
361+
return host != null ? host.trim() : null;
362+
}
363+
364+
/**
365+
* Expand {@code _HOST} in JAAS principals the same way audit-server HDFS/Kafka paths do
366+
* ({@code SecurityUtil.getServerPrincipal} with {@code ranger.audit.dispatcher.host}).
367+
*/
368+
static String resolveJaasPrincipal(String principal, String hostname) throws IOException {
369+
if (principal == null || principal.isBlank() || !principal.contains("_HOST")) {
370+
return principal;
371+
}
372+
373+
if (hostname != null && !hostname.isBlank()) {
374+
String resolved = SecurityUtil.getServerPrincipal(principal, hostname.trim());
375+
376+
LOG.info("Resolved JAAS principal {} -> {} using host {}", principal, resolved, hostname);
377+
378+
return resolved;
379+
}
380+
381+
return SecurityUtil.getServerPrincipal(principal, (String) null);
382+
}
383+
347384
private static boolean isNumeric(String str) {
348385
return str.matches("-?\\d+(\\.\\d+)?"); //match a number with optional '-' and decimal.
349386
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
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+
19+
package org.apache.ranger.audit.utils;
20+
21+
import org.junit.jupiter.api.Test;
22+
23+
import javax.security.auth.login.AppConfigurationEntry;
24+
25+
import java.util.Properties;
26+
27+
import static org.junit.jupiter.api.Assertions.assertEquals;
28+
import static org.junit.jupiter.api.Assertions.assertNotNull;
29+
30+
class InMemoryJAASConfigurationTest {
31+
32+
@Test
33+
void resolveJaasPrincipalExpandsHostForServicePrincipal() throws Exception {
34+
String resolved = InMemoryJAASConfiguration.resolveJaasPrincipal(
35+
"rangerauditserver/_HOST@EXAMPLE.COM",
36+
"ranger-audit-dispatcher-solr.rangernw");
37+
38+
assertEquals("rangerauditserver/ranger-audit-dispatcher-solr.rangernw@EXAMPLE.COM", resolved);
39+
}
40+
41+
@Test
42+
void initExpandsHostFromDispatcherHostProperty() throws Exception {
43+
Properties props = new Properties();
44+
props.setProperty("ranger.audit.dispatcher.host", "ranger-audit-dispatcher-solr.rangernw");
45+
props.setProperty("xasecure.audit.jaas.Client.loginModuleName", "com.sun.security.auth.module.Krb5LoginModule");
46+
props.setProperty("xasecure.audit.jaas.Client.loginModuleControlFlag", "required");
47+
props.setProperty("xasecure.audit.jaas.Client.option.useKeyTab", "true");
48+
props.setProperty("xasecure.audit.jaas.Client.option.storeKey", "true");
49+
props.setProperty("xasecure.audit.jaas.Client.option.useTicketCache", "false");
50+
props.setProperty("xasecure.audit.jaas.Client.option.principal", "rangerauditserver/_HOST@EXAMPLE.COM");
51+
props.setProperty("xasecure.audit.jaas.Client.option.keyTab", "/etc/keytabs/rangerauditserver.keytab");
52+
53+
InMemoryJAASConfiguration conf = InMemoryJAASConfiguration.init(props);
54+
AppConfigurationEntry[] entries = conf.getAppConfigurationEntry("Client");
55+
56+
assertNotNull(entries);
57+
assertEquals("rangerauditserver/ranger-audit-dispatcher-solr.rangernw@EXAMPLE.COM",
58+
entries[0].getOptions().get("principal"));
59+
}
60+
}

agents-audit/dest-solr/pom.xml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,12 @@
140140
</dependency>
141141

142142
<!-- Test -->
143+
<dependency>
144+
<groupId>org.junit.jupiter</groupId>
145+
<artifactId>junit-jupiter</artifactId>
146+
<version>${junit.jupiter.version}</version>
147+
<scope>test</scope>
148+
</dependency>
143149
<dependency>
144150
<groupId>org.slf4j</groupId>
145151
<artifactId>log4j-over-slf4j</artifactId>

agents-audit/dest-solr/src/main/java/org/apache/ranger/audit/destination/SolrAuditDestination.java

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@
5959
import java.security.SecureRandom;
6060
import java.security.UnrecoverableKeyException;
6161
import java.security.cert.CertificateException;
62+
import java.net.URI;
6263
import java.util.ArrayList;
6364
import java.util.Arrays;
6465
import java.util.Collection;
@@ -70,6 +71,7 @@ public class SolrAuditDestination extends AuditDestination {
7071
private static final Logger LOG = LoggerFactory.getLogger(SolrAuditDestination.class);
7172

7273
public static final String PROP_SOLR_URLS = "urls";
74+
public static final String PROP_SOLR_HOST = "host";
7375
public static final String PROP_SOLR_ZK = "zookeepers";
7476
public static final String PROP_SOLR_COLLECTION = "collection";
7577
public static final String PROP_SOLR_FORCE_USE_INMEMORY_JAAS_CONFIG = "force.use.inmemory.jaas.config";
@@ -217,6 +219,8 @@ synchronized void connect() {
217219
urls = urls.trim();
218220
}
219221

222+
urls = resolveSolrUrls(urls);
223+
220224
if (urls != null && urls.equalsIgnoreCase("NONE")) {
221225
urls = null;
222226
}
@@ -363,6 +367,64 @@ private void init() {
363367
LOG.info("<==SolrAuditDestination.init()");
364368
}
365369

370+
/**
371+
* When {@code xasecure.audit.destination.solr.host} is set, rewrite Solr URL hostnames to that FQDN
372+
* so SPNEGO matches Solr's {@code HTTP/<host>@REALM} service principal (same idea as
373+
* {@code ranger.audit.dispatcher.host} for JAAS {@code _HOST}).
374+
*/
375+
static String resolveSolrUrls(String urls, Properties props, String propPrefix) {
376+
if (StringUtils.isBlank(urls)) {
377+
return urls;
378+
}
379+
380+
String solrHost = MiscUtil.getStringProperty(props, propPrefix + "." + PROP_SOLR_HOST);
381+
382+
if (StringUtils.isBlank(solrHost)) {
383+
return urls;
384+
}
385+
386+
solrHost = solrHost.trim();
387+
List<String> resolved = new ArrayList<>();
388+
boolean changed = false;
389+
390+
for (String entry : MiscUtil.toArray(urls, ",")) {
391+
if (StringUtils.isBlank(entry)) {
392+
continue;
393+
}
394+
395+
entry = entry.trim();
396+
397+
try {
398+
URI uri = URI.create(entry);
399+
400+
if (uri.getHost() != null && !uri.getHost().equalsIgnoreCase(solrHost)) {
401+
int port = uri.getPort() > 0 ? uri.getPort() : 8983;
402+
String scheme = StringUtils.isNotBlank(uri.getScheme()) ? uri.getScheme() : "http";
403+
String path = uri.getRawPath() != null ? uri.getRawPath() : "";
404+
String query = uri.getRawQuery() != null ? "?" + uri.getRawQuery() : "";
405+
String fragment = uri.getRawFragment() != null ? "#" + uri.getRawFragment() : "";
406+
String updated = scheme + "://" + solrHost + ":" + port + path + query + fragment;
407+
408+
LOG.info("Solr audit URL host rewritten for Kerberos SPNEGO: {} -> {} (configured host={})",
409+
entry, updated, solrHost);
410+
411+
entry = updated;
412+
changed = true;
413+
}
414+
} catch (IllegalArgumentException e) {
415+
LOG.warn("Unable to parse Solr audit URL [{}]; leaving unchanged", entry, e);
416+
}
417+
418+
resolved.add(entry);
419+
}
420+
421+
return changed ? String.join(",", resolved) : urls;
422+
}
423+
424+
private String resolveSolrUrls(String urls) {
425+
return resolveSolrUrls(urls, props, propPrefix);
426+
}
427+
366428
private KeyManager[] getKeyManagers() {
367429
KeyManager[] kmList = null;
368430
String credentialProviderPath = MiscUtil.getStringProperty(props, RANGER_POLICYMGR_CLIENT_KEY_FILE_CREDENTIAL);
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
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+
19+
package org.apache.ranger.audit.destination;
20+
21+
import org.apache.ranger.audit.provider.AuditProviderFactory;
22+
import org.junit.jupiter.api.Test;
23+
24+
import java.util.Properties;
25+
26+
import static org.junit.jupiter.api.Assertions.assertEquals;
27+
28+
class SolrAuditDestinationTest {
29+
30+
private static final String PROP_PREFIX = AuditProviderFactory.AUDIT_DEST_BASE + ".solr";
31+
32+
@Test
33+
void resolveSolrUrlsRewritesHostnameForSpnego() {
34+
Properties props = new Properties();
35+
props.setProperty(PROP_PREFIX + ".host", "ranger-solr.rangernw");
36+
37+
String resolved = SolrAuditDestination.resolveSolrUrls(
38+
"http://ranger-solr:8983/solr/ranger_audits",
39+
props,
40+
PROP_PREFIX);
41+
42+
assertEquals("http://ranger-solr.rangernw:8983/solr/ranger_audits", resolved);
43+
}
44+
45+
@Test
46+
void resolveSolrUrlsLeavesUrlUnchangedWhenHostMatches() {
47+
Properties props = new Properties();
48+
props.setProperty(PROP_PREFIX + ".host", "ranger-solr.rangernw");
49+
50+
String url = "http://ranger-solr.rangernw:8983/solr/ranger_audits";
51+
52+
assertEquals(url, SolrAuditDestination.resolveSolrUrls(url, props, PROP_PREFIX));
53+
}
54+
}

audit-server/audit-dispatcher/dispatcher-solr/src/main/resources/conf/ranger-audit-dispatcher-solr-site.xml

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@
3232
<name>ranger.audit.dispatcher.service.kerberos.principal</name>
3333
<value>rangerauditserver/_HOST@EXAMPLE.COM</value>
3434
<description>
35-
rangerauditserver user kerberos principal for authentication into kafka
35+
rangerauditserver Kerberos principal for Kafka and Solr (_HOST expanded using ranger.audit.dispatcher.host)
3636
</description>
3737
</property>
3838

@@ -122,13 +122,21 @@
122122
</property>
123123

124124
<!-- SOLR DESTINATION CONFIGURATION -->
125+
<property>
126+
<name>xasecure.audit.destination.solr.host</name>
127+
<value>ranger-solr.rangernw</value>
128+
<description>
129+
FQDN for Kerberos SPNEGO to Solr (must match HTTP/&lt;host&gt;@REALM).
130+
Rewrites xasecure.audit.destination.solr.urls hostname when they differ.
131+
</description>
132+
</property>
133+
125134
<property>
126135
<name>xasecure.audit.destination.solr.urls</name>
127136
<value>http://ranger-solr:8983/solr/ranger_audits</value>
128137
<description>
129138
Solr URLs for audits when SolrCloud is not enabled.
130-
Docker supports only standalone mode for now,
131-
configure http://ranger-solr:8983/solr/ranger_audits
139+
Hostname is rewritten to xasecure.audit.destination.solr.host when set (Kerberos SPNEGO).
132140
</description>
133141
</property>
134142

@@ -175,8 +183,8 @@
175183

176184
<property>
177185
<name>xasecure.audit.jaas.Client.option.useTicketCache</name>
178-
<value>true</value>
179-
<description>Allow use of cached Kerberos tickets for Solr authentication</description>
186+
<value>false</value>
187+
<description>Keytab-only login for Solr JAAS (required with in-memory JAAS in containers)</description>
180188
</property>
181189

182190
<property>
@@ -188,7 +196,7 @@
188196
<property>
189197
<name>xasecure.audit.jaas.Client.option.principal</name>
190198
<value>rangerauditserver/_HOST@EXAMPLE.COM</value>
191-
<description>Principal for Solr authentication</description>
199+
<description>Principal for Solr authentication (_HOST expanded using ranger.audit.dispatcher.host)</description>
192200
</property>
193201

194202
<property>

0 commit comments

Comments
 (0)