[ 
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)

Reply via email to