Skip to content

Commit ab60c25

Browse files
committed
fix: use short name from authToLocal rules as Kerberos authenticated identity
1 parent 8ce2f36 commit ab60c25

5 files changed

Lines changed: 317 additions & 1 deletion

File tree

docs/development/extensions-core/druid-kerberos.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,5 +121,19 @@ To access Coordinator/Overlord console from browser you will need to configure y
121121
1. Configure trusted websites to include `"druid-coordinator-hostname"` and `"druid-overlord-hostname"`
122122
2. Allow negotiation for the UI website.
123123
124+
## User identity and `authToLocal` rules
125+
126+
The Kerberos authenticator uses the short name produced by `authToLocal` rules as the authenticated user identity.
127+
For example, if the Kerberos principal is `user@EXAMPLE.COM` and the `authToLocal` rule maps it to `user`, then the
128+
identity used for authorization is `user`, not the full principal `user@EXAMPLE.COM`.
129+
130+
When configuring authorizer permissions, use the short (local) user name rather than the full Kerberos principal.
131+
132+
:::info
133+
In previous versions, the authenticated identity was incorrectly set to the full Kerberos principal
134+
(e.g., `user@EXAMPLE.COM`) instead of the short name (e.g., `user`). If you have authorizer rules that
135+
reference full Kerberos principals, update them to use the short name after upgrading.
136+
:::
137+
124138
## Sending Queries programmatically
125139
Many HTTP client libraries, such as Apache Commons [HttpComponents](https://hc.apache.org/), already have support for performing SPNEGO authentication. You can use any of the available HTTP client library to communicate with druid cluster.

extensions-core/druid-kerberos/src/main/java/org/apache/druid/security/kerberos/KerberosAuthenticator.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -321,7 +321,7 @@ public Principal getUserPrincipal()
321321
// Since this request is validated also set DRUID_AUTHENTICATION_RESULT
322322
request.setAttribute(
323323
AuthConfig.DRUID_AUTHENTICATION_RESULT,
324-
new AuthenticationResult(token.getName(), authorizerName, name, null)
324+
createAuthenticationResult(token)
325325
);
326326
doFilter(filterChain, httpRequest, httpResponse);
327327
}
@@ -594,4 +594,9 @@ private static String tokenToCookieString(
594594
sb.append("; HttpOnly");
595595
return sb.toString();
596596
}
597+
598+
AuthenticationResult createAuthenticationResult(AuthenticationToken token)
599+
{
600+
return new AuthenticationResult(token.getUserName(), authorizerName, name, null);
601+
}
597602
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
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,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
package org.apache.druid.security.kerberos;
21+
22+
import org.apache.druid.server.security.AuthenticationResult;
23+
import org.apache.hadoop.security.authentication.server.AuthenticationToken;
24+
import org.apache.hadoop.security.authentication.util.KerberosName;
25+
import org.junit.After;
26+
import org.junit.Assert;
27+
import org.junit.Before;
28+
import org.junit.Test;
29+
30+
/**
31+
* Tests verifying that AuthenticationToken userName (short name) is used for
32+
* AuthenticationResult identity, not the full Kerberos principal.
33+
*
34+
* <p>The DruidKerberosAuthenticationHandler creates tokens with:
35+
* {@code new AuthenticationToken(userName, clientPrincipal, getType())}
36+
* where userName is the result of KerberosName.getShortName() (applying authToLocal rules).
37+
*
38+
* <p>The KerberosAuthenticator must use token.getUserName() (not token.getName()) when
39+
* constructing AuthenticationResult so that the identity matches the short name
40+
* produced by authToLocal rules.
41+
*/
42+
public class DruidKerberosAuthenticationHandlerTokenTest
43+
{
44+
private static final String SAFE_RULES = "RULE:[1:$1] RULE:[2:$1]";
45+
46+
@Before
47+
public void setUp()
48+
{
49+
KerberosName.setRules(SAFE_RULES);
50+
}
51+
52+
@After
53+
public void tearDown()
54+
{
55+
KerberosName.setRules(SAFE_RULES);
56+
}
57+
58+
@Test
59+
public void testSimplePrincipalMapping()
60+
{
61+
final String shortName = "druid";
62+
final String principal = "druid@EXAMPLE.COM";
63+
verifyTokenIdentity(shortName, principal);
64+
}
65+
66+
@Test
67+
public void testServicePrincipalMapping()
68+
{
69+
final String shortName = "druid";
70+
final String principal = "druid/host.example.com@EXAMPLE.COM";
71+
verifyTokenIdentity(shortName, principal);
72+
}
73+
74+
@Test
75+
public void testPrincipalWithDifferentShortName()
76+
{
77+
// Simulates authToLocal rule mapping a principal to a different local name
78+
final String shortName = "admin";
79+
final String principal = "admin/secure@CORP.EXAMPLE.COM";
80+
verifyTokenIdentity(shortName, principal);
81+
}
82+
83+
@Test
84+
public void testAuthToLocalRuleExtractsShortName() throws Exception
85+
{
86+
// Use an explicit rule that strips the realm, matching what DEFAULT does with a krb5.conf
87+
KerberosName.setRules("RULE:[1:$1@$0](.*@EXAMPLE.COM)s/@.*//");
88+
final KerberosName kerberosName = new KerberosName("user@EXAMPLE.COM");
89+
final String shortName = kerberosName.getShortName();
90+
91+
Assert.assertEquals("user", shortName);
92+
93+
// This is what DruidKerberosAuthenticationHandler does
94+
final AuthenticationToken token = new AuthenticationToken(shortName, "user@EXAMPLE.COM", "kerberos");
95+
96+
// And KerberosAuthenticator must use getUserName() for identity
97+
final AuthenticationResult result = new AuthenticationResult(
98+
token.getUserName(),
99+
"authorizer",
100+
"kerberos",
101+
null
102+
);
103+
Assert.assertEquals("user", result.getIdentity());
104+
}
105+
106+
@Test
107+
public void testCustomAuthToLocalRule() throws Exception
108+
{
109+
// Test with a custom rule that maps druid@EXAMPLE.COM -> druid
110+
KerberosName.setRules("RULE:[1:$1@$0](.*@EXAMPLE.COM)s/@.*// DEFAULT");
111+
final KerberosName kerberosName = new KerberosName("myuser@EXAMPLE.COM");
112+
final String shortName = kerberosName.getShortName();
113+
114+
Assert.assertEquals("myuser", shortName);
115+
116+
final AuthenticationToken token = new AuthenticationToken(shortName, "myuser@EXAMPLE.COM", "kerberos");
117+
118+
final AuthenticationResult result = new AuthenticationResult(
119+
token.getUserName(),
120+
"authorizer",
121+
"kerberos",
122+
null
123+
);
124+
Assert.assertEquals("myuser", result.getIdentity());
125+
}
126+
127+
@Test
128+
public void testTwoComponentPrincipalRule() throws Exception
129+
{
130+
// Rule for two-component principals: extract the first component (service name)
131+
KerberosName.setRules("RULE:[2:$1@$0](.*@EXAMPLE.COM)s/@.*//");
132+
final KerberosName kerberosName = new KerberosName("HTTP/host.example.com@EXAMPLE.COM");
133+
final String shortName = kerberosName.getShortName();
134+
135+
Assert.assertEquals("HTTP", shortName);
136+
137+
final AuthenticationToken token = new AuthenticationToken(
138+
shortName,
139+
"HTTP/host.example.com@EXAMPLE.COM",
140+
"kerberos"
141+
);
142+
143+
final AuthenticationResult result = new AuthenticationResult(
144+
token.getUserName(),
145+
"authorizer",
146+
"kerberos",
147+
null
148+
);
149+
Assert.assertEquals("HTTP", result.getIdentity());
150+
Assert.assertNotEquals("HTTP/host.example.com@EXAMPLE.COM", result.getIdentity());
151+
}
152+
153+
private void verifyTokenIdentity(String expectedShortName, String fullPrincipal)
154+
{
155+
final AuthenticationToken token = new AuthenticationToken(expectedShortName, fullPrincipal, "kerberos");
156+
157+
// getUserName() should return the short name
158+
Assert.assertEquals(expectedShortName, token.getUserName());
159+
// getName() should return the full principal
160+
Assert.assertEquals(fullPrincipal, token.getName());
161+
162+
// AuthenticationResult identity must use the short name
163+
final AuthenticationResult result = new AuthenticationResult(
164+
token.getUserName(),
165+
"testAuthorizer",
166+
"kerberos",
167+
null
168+
);
169+
Assert.assertEquals(expectedShortName, result.getIdentity());
170+
}
171+
}
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
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,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
package org.apache.druid.security.kerberos;
21+
22+
import org.apache.druid.server.security.AuthenticationResult;
23+
import org.apache.hadoop.security.authentication.server.AuthenticationToken;
24+
import org.junit.Assert;
25+
import org.junit.Test;
26+
27+
public class KerberosAuthenticatorFilterTest
28+
{
29+
@Test
30+
public void testAuthenticationTokenGetUserNameReturnsShortName()
31+
{
32+
final String shortName = "druid";
33+
final String fullPrincipal = "druid@EXAMPLE.COM";
34+
final AuthenticationToken token = new AuthenticationToken(shortName, fullPrincipal, "kerberos");
35+
36+
Assert.assertEquals(shortName, token.getUserName());
37+
Assert.assertEquals(fullPrincipal, token.getName());
38+
}
39+
40+
@Test
41+
public void testAuthenticationResultUsesShortName()
42+
{
43+
final String shortName = "druid";
44+
final String fullPrincipal = "druid@EXAMPLE.COM";
45+
final String authorizerName = "testAuthorizer";
46+
final String authenticatorName = "kerberos";
47+
48+
final AuthenticationToken token = new AuthenticationToken(shortName, fullPrincipal, "kerberos");
49+
50+
// This mirrors the fix: using getUserName() instead of getName()
51+
final AuthenticationResult result = new AuthenticationResult(
52+
token.getUserName(),
53+
authorizerName,
54+
authenticatorName,
55+
null
56+
);
57+
58+
Assert.assertEquals(
59+
"Identity should be the short name, not the full Kerberos principal",
60+
shortName,
61+
result.getIdentity()
62+
);
63+
Assert.assertNotEquals(
64+
"Identity should not be the full Kerberos principal",
65+
fullPrincipal,
66+
result.getIdentity()
67+
);
68+
}
69+
70+
@Test
71+
public void testAuthenticationResultWithMultiComponentPrincipal()
72+
{
73+
final String shortName = "druid";
74+
final String fullPrincipal = "druid/admin@EXAMPLE.COM";
75+
final String authorizerName = "testAuthorizer";
76+
final String authenticatorName = "kerberos";
77+
78+
final AuthenticationToken token = new AuthenticationToken(shortName, fullPrincipal, "kerberos");
79+
80+
final AuthenticationResult result = new AuthenticationResult(
81+
token.getUserName(),
82+
authorizerName,
83+
authenticatorName,
84+
null
85+
);
86+
87+
Assert.assertEquals(shortName, result.getIdentity());
88+
}
89+
90+
@Test
91+
public void testGetNameReturnsFullPrincipalNotShortName()
92+
{
93+
final String shortName = "user";
94+
final String fullPrincipal = "user@REALM.COM";
95+
final AuthenticationToken token = new AuthenticationToken(shortName, fullPrincipal, "kerberos");
96+
97+
// Verify getName() returns the full principal — this is what was being used before the fix
98+
Assert.assertEquals(fullPrincipal, token.getName());
99+
// Verify getUserName() returns the short name — this is what the fix uses
100+
Assert.assertEquals(shortName, token.getUserName());
101+
}
102+
}

extensions-core/druid-kerberos/src/test/java/org/apache/druid/security/kerberos/KerberosAuthenticatorTest.java

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@
2121

2222
import org.apache.druid.error.DruidException;
2323
import org.apache.druid.server.DruidNode;
24+
import org.apache.druid.server.security.AuthenticationResult;
25+
import org.apache.hadoop.security.authentication.server.AuthenticationToken;
2426
import org.junit.Assert;
2527
import org.junit.Test;
2628

@@ -98,4 +100,26 @@ public void testConstructorWithEmptyCookieSignatureSecret()
98100
exception.getMessage().contains("is not set")
99101
);
100102
}
103+
104+
@Test
105+
public void testCreateAuthenticationResultUsesShortName()
106+
{
107+
KerberosAuthenticator authenticator = new KerberosAuthenticator(
108+
TEST_SERVER_PRINCIPAL,
109+
TEST_SERVER_KEYTAB,
110+
TEST_AUTH_TO_LOCAL,
111+
TEST_COOKIE_SECRET,
112+
TEST_AUTHORIZER_NAME,
113+
TEST_NAME,
114+
createTestNode()
115+
);
116+
117+
AuthenticationToken token = new AuthenticationToken("druid", "druid@EXAMPLE.COM", "kerberos");
118+
119+
AuthenticationResult result = authenticator.createAuthenticationResult(token);
120+
121+
Assert.assertEquals("druid", result.getIdentity());
122+
Assert.assertEquals(TEST_AUTHORIZER_NAME, result.getAuthorizerName());
123+
Assert.assertEquals(TEST_NAME, result.getAuthenticatedBy());
124+
}
101125
}

0 commit comments

Comments
 (0)