[
https://issues.apache.org/jira/browse/HBASE-26770?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=17497599#comment-17497599
]
Bryan Beaudreault commented on HBASE-26770:
-------------------------------------------
The below research took place in branch-2 initially. Afterwards I checked out
master as well, and most of the below still applies there. The one difference
is that master unifies everything on the AsyncConnectionImpl, so it's actually
somewhat simpler there. Tracking usages, all the same usages still apply
otherwise, for the async client.
--
The entry point for all of this is ConnectionFactory, where you can pass in
your own User or get one automatically from AuthUtil.loginClient. The
loginClient call does some kerberos keytab checking, so we probably don't want
to remove that entirely. The resulting User is passed into the constructor for
a Connection (AsyncConnectionImpl for async or ConnectionImplementation for
blocking), where it is stashed for further use. Both implementations use the
ticket in the same ways:
# The user is returned via a getUser() method, which is only used in creating
tracing spans.
# Seemingly more important (for our case), in creating the RpcChannel whether
blocking or async. In both cases, the ticket is passed into the newly created
channel's constructor.
Both async and blocking rpc channels extend AbstractRpcChannel, where the
ticket is further stashed. Tracing usages of AbstractRpcChannel.ticket, it is
passed into callBlockingMethod and callMethod for blocking and async channels
respectively. callBlockingMethod actually delegates to the same callMethod, at
which point the ticket is passed into a ConnectionId object. This object is
used for creating either a BlockingRpcConnection or NettyRpcConnection, which
both extend RpcConnection. It's at this point that we start to see the real use
of the ticket, now solely accessed from ConnectionId.
ConnectionId's ticket field is package-private, and there are usages of both
the field itself and a getter, getTicket:
# For creating the ConnectionHeader in RpcConnection.getConnectionHeader().
This is specifically where the appropriate proxy username should be getting
used but is not. Naively changing this line to use
UserGroupInformation.getCurrentUser() resolves this JIRA, as the appropriate
proxy user and real user will be passed to the regionserver. The remainder is
due diligence to try to figure out if there would be unintended consequences of
modifying this here, or if we should modify at a different level.
# In BlockingRpcConnection, it's used in setting the thread name. More
importantly, it's also used in setting up the SASL connection, through
ticket.doAs..
## The ticket is actually passed into
SaslClientAuthenticationProvider.getRealUser here. The default impl is to just
return the user directly, but GssSaslClientAuthenticationProvider overrides to
extract the UGI.getRealUser directly if available.
# In NettyRpcConnection's saslNegotiate, it's similarly passed into the
SaslClientAuthenticationProvider and then similarly used to ticket.doAs in
handling the netty sasl negotiation
# In RpcConnection's constructor, it's passed into
SaslClientAuthenticationProviders.selectProvider. This is a pluggable class, so
it's hard to know how all extensions of this might use the passed in ticket.
That said, the interface is LimitedPrivate and Evolving, so we might have some
leeway here if necessary. The default implementation checks the User's UGI and
underlying realUser (if present) to see if either hasKerberosCredentials. It
also checks for any tokens on the user for digest auth.
> HBase client does not honor UserGroupInformation.doAs
> -----------------------------------------------------
>
> Key: HBASE-26770
> URL: https://issues.apache.org/jira/browse/HBASE-26770
> Project: HBase
> Issue Type: Bug
> Reporter: Bryan Beaudreault
> Priority: Major
>
> Despite passing necessary UserInformation to the RegionServer, which does
> authorize the request, the async and block clients do not work correctly with
> the following access pattern:
> {code:java}
> Connection connection = ConnectionFactory.createConnection();
> Table table = connection.getTable(name);
> UserGroupInformation proxy = UserGroupInformation.createProxyUser(
> "testUser",
> UserGroupInformation.getCurrentUser()
> );
> Result result = proxy.doAs(() -> table.get(get));{code}
> In this case, you would expect the get to be executed as "testUser", but
> instead it is executed as whichever user created the initial connection. This
> can be verified by checking the security logger on the RegionServer side.
> The reason for this is we stash the current User onto the actual
> ConnectionImplementation, and we pass that through all calls in the stack
> when executing an RPC. I think the appropriate way would be to replace usage
> of this stashed User with a call to UserGroupInformation.getCurrentUser() in
> RpcConnection, where sasl is negotiated and headers generated.
--
This message was sent by Atlassian Jira
(v8.20.1#820001)