| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package org.apache.drill.exec.server.rest.auth; |
| |
| |
| import org.apache.drill.common.exceptions.DrillException; |
| import org.apache.drill.exec.ExecConstants; |
| import org.apache.drill.exec.server.DrillbitContext; |
| import org.apache.drill.exec.server.options.SystemOptionManager; |
| import org.apache.drill.exec.util.ImpersonationUtil; |
| import org.apache.hadoop.security.HadoopKerberosName; |
| import org.apache.hadoop.security.UserGroupInformation; |
| import org.eclipse.jetty.security.DefaultIdentityService; |
| import org.eclipse.jetty.security.SpnegoLoginService; |
| import org.eclipse.jetty.server.UserIdentity; |
| import org.ietf.jgss.GSSContext; |
| import org.ietf.jgss.GSSCredential; |
| import org.ietf.jgss.GSSException; |
| import org.ietf.jgss.GSSManager; |
| import org.ietf.jgss.GSSName; |
| import org.ietf.jgss.Oid; |
| |
| import javax.security.auth.Subject; |
| import javax.servlet.ServletRequest; |
| import java.io.IOException; |
| import java.lang.reflect.Field; |
| import java.security.Principal; |
| import java.security.PrivilegedExceptionAction; |
| import java.util.Base64; |
| |
| /** |
| * Custom implementation of DrillSpnegoLoginService to avoid the need of passing targetName in a config file, |
| * to include the SPNEGO OID and the way UserIdentity is created. |
| */ |
| public class DrillSpnegoLoginService extends SpnegoLoginService { |
| private static final org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger(DrillSpnegoLoginService.class); |
| |
| private static final String TARGET_NAME_FIELD_NAME = "_targetName"; |
| |
| private final DrillbitContext drillContext; |
| |
| private final SpnegoConfig spnegoConfig; |
| |
| private final UserGroupInformation loggedInUgi; |
| |
| public DrillSpnegoLoginService(DrillbitContext drillBitContext) throws DrillException { |
| super(DrillSpnegoLoginService.class.getName()); |
| setIdentityService(new DefaultIdentityService()); |
| drillContext = drillBitContext; |
| |
| // Load and verify SPNEGO config. Then Login using creds to get an UGI instance |
| spnegoConfig = new SpnegoConfig(drillBitContext.getConfig()); |
| spnegoConfig.validateSpnegoConfig(); |
| loggedInUgi = spnegoConfig.getLoggedInUgi(); |
| } |
| |
| @Override |
| protected void doStart() throws Exception { |
| // Override the parent implementation, setting _targetName to be the serverPrincipal |
| // without the need for a one-line file to do the same thing. |
| final Field targetNameField = SpnegoLoginService.class.getDeclaredField(TARGET_NAME_FIELD_NAME); |
| targetNameField.setAccessible(true); |
| targetNameField.set(this, spnegoConfig.getSpnegoPrincipal()); |
| } |
| |
| @Override |
| public UserIdentity login(final String username, final Object credentials, ServletRequest request) { |
| |
| UserIdentity identity = null; |
| try { |
| identity = loggedInUgi.doAs((PrivilegedExceptionAction<UserIdentity>) () -> spnegoLogin(credentials, request)); |
| } catch (Exception e) { |
| logger.error("Failed to login using SPNEGO", e); |
| } |
| |
| return identity; |
| } |
| |
| private UserIdentity spnegoLogin(Object credentials, ServletRequest request) { |
| |
| String encodedAuthToken = (String) credentials; |
| byte[] authToken = Base64.getDecoder().decode(encodedAuthToken); |
| GSSManager manager = GSSManager.getInstance(); |
| |
| try { |
| // Providing both OID's is required here. If we provide only one, |
| // we're requiring that clients provide us the SPNEGO OID to authenticate via Kerberos. |
| Oid[] knownOids = new Oid[2]; |
| knownOids[0] = new Oid("1.3.6.1.5.5.2"); // spnego |
| knownOids[1] = new Oid("1.2.840.113554.1.2.2"); // kerberos |
| |
| GSSName gssName = manager.createName(spnegoConfig.getSpnegoPrincipal(), null); |
| GSSCredential serverCreds = manager.createCredential(gssName, GSSCredential.INDEFINITE_LIFETIME, |
| knownOids, GSSCredential.ACCEPT_ONLY); |
| GSSContext gContext = manager.createContext(serverCreds); |
| |
| if (gContext == null) { |
| logger.debug("SPNEGOUserRealm: failed to establish GSSContext"); |
| } else { |
| while (!gContext.isEstablished()) { |
| authToken = gContext.acceptSecContext(authToken, 0, authToken.length); |
| } |
| |
| if (gContext.isEstablished()) { |
| final String clientName = gContext.getSrcName().toString(); |
| final String realm = clientName.substring(clientName.indexOf(64) + 1); |
| |
| // Get the client user short name |
| final String userShortName = new HadoopKerberosName(clientName).getShortName(); |
| logger.info("WebUser {} logged in from {}:{}", userShortName, request.getRemoteHost(), |
| request.getRemotePort()); |
| logger.debug("Client Name: {}, realm: {} and shortName: {}", clientName, realm, userShortName); |
| final SystemOptionManager sysOptions = drillContext.getOptionManager(); |
| final boolean isAdmin = ImpersonationUtil.hasAdminPrivileges(userShortName, |
| ExecConstants.ADMIN_USERS_VALIDATOR.getAdminUsers(sysOptions), |
| ExecConstants.ADMIN_USER_GROUPS_VALIDATOR.getAdminUserGroups(sysOptions)); |
| |
| final Principal user = new DrillUserPrincipal(userShortName, isAdmin); |
| final Subject subject = new Subject(); |
| subject.getPrincipals().add(user); |
| |
| if (isAdmin) { |
| return this._identityService.newUserIdentity(subject, user, DrillUserPrincipal.ADMIN_USER_ROLES); |
| } else { |
| return this._identityService.newUserIdentity(subject, user, DrillUserPrincipal.NON_ADMIN_USER_ROLES); |
| } |
| } |
| } |
| } catch (GSSException gsse) { |
| logger.warn("Caught GSSException trying to authenticate the client", gsse); |
| } catch (IOException ex) { |
| logger.warn("Caught IOException trying to get shortName of client user", ex); |
| } |
| return null; |
| } |
| } |