Skip to content

Commit

Permalink
[JENKINS-27026] Fire authentication events when using SSH (#22)
Browse files Browse the repository at this point in the history
* [JENKINS-27026] Fire authentication events when using SSH

* - remove whitespace changes

* - implement the user.impersonate as proposed by Jesse
- also manage to create a UserDetails to notify the SecurityListener

* - constructor of the inner class can even be private

* - privatize the inner class to avoid API exposure
- adjust log messages

* - remove single quote in log
- use getById instead of get
  • Loading branch information
Wadeck authored and oleg-nenashev committed Oct 26, 2017
1 parent 74d265e commit 000dcee
Showing 1 changed file with 64 additions and 10 deletions.
@@ -1,12 +1,19 @@
package org.jenkinsci.main.modules.sshd;

import hudson.model.User;
import jenkins.security.SecurityListener;
import org.acegisecurity.Authentication;
import org.acegisecurity.userdetails.UserDetails;
import org.acegisecurity.userdetails.UsernameNotFoundException;
import org.apache.sshd.server.auth.pubkey.PublickeyAuthenticator;
import org.apache.sshd.server.session.ServerSession;
import org.jenkinsci.main.modules.cli.auth.ssh.PublicKeySignatureWriter;
import org.jenkinsci.main.modules.cli.auth.ssh.UserPropertyImpl;

import javax.annotation.CheckForNull;
import javax.annotation.Nonnull;
import java.security.PublicKey;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
Expand All @@ -20,27 +27,74 @@ class PublicKeyAuthenticatorImpl implements PublickeyAuthenticator {
private final PublicKeySignatureWriter signatureWriter = new PublicKeySignatureWriter();

public boolean authenticate(String username, PublicKey key, ServerSession session) {
LOGGER.fine("Authentication attempted from "+username+" with "+key);
User u = User.get(username, false);
if (u==null) {
LOGGER.fine("No such user exists: "+username);
User user = this.retrieveOnlyKeyValidatedUser(username, key);

if (user == null) {
SecurityListener.fireFailedToAuthenticate(username);
return false;
}

UserPropertyImpl sshKey = u.getProperty(UserPropertyImpl.class);
if (sshKey==null) {
LOGGER.fine("No SSH key registered for user: "+username);
Authentication auth = this.verifyUserUsingSecurityRealm(user);
if (auth == null) {
SecurityListener.fireFailedToAuthenticate(username);
return false;
}

UserDetails userDetails = new SSHUserDetails(username, auth);
SecurityListener.fireAuthenticated(userDetails);
return true;
}

private @CheckForNull User retrieveOnlyKeyValidatedUser(String username, PublicKey key) {
LOGGER.log(Level.FINE, "Authentication attempted from {0} with {1}", new Object[]{ username, key });
User u = User.getById(username, false);
if (u == null) {
LOGGER.log(Level.FINE, "No such user exists: {0}", new Object[]{ username });
return null;
}

UserPropertyImpl sshKey = u.getProperty(UserPropertyImpl.class);
if (sshKey == null) {
LOGGER.log(Level.FINE, "No SSH key registered for user: {0}", new Object[]{ username });
return null;
}

String signature = signatureWriter.asString(key);
if (!sshKey.isAuthorizedKey(signature)) {
LOGGER.fine("Key signature didn't match for the user: "+username+" : " + signature);
return false;
LOGGER.log(Level.FINE,"Key signature did not match for the user: {0} : {1}", new Object[]{ username, signature });
return null;
}

return true;
return u;
}

private @CheckForNull Authentication verifyUserUsingSecurityRealm(@Nonnull User user) {
try {
return user.impersonate();
} catch (UsernameNotFoundException e) {
LOGGER.log(Level.FINE, user.getId() + " is not a real user according to SecurityRealm", e);
return null;
}
}

private static final Logger LOGGER = Logger.getLogger(PublicKeyAuthenticatorImpl.class.getName());

/**
* UserDetails built from the authentication provided by {@link User#impersonate()}.
* It's not completely accurate since the internal UserDetails used in impersonate is not exposed at the moment
*
* TODO temporary solution since the User#getUserDetailsForImpersonation is not implemented
* https://github.com/jenkinsci/jenkins/pull/3074
* Will be removed in future version (with higher jenkins version dependency)
*/
private static class SSHUserDetails extends org.acegisecurity.userdetails.User {
private SSHUserDetails(@Nonnull String username, @Nonnull Authentication auth) {
super(
username, "",
// account validity booleans
true, true, true, true,
auth.getAuthorities()
);
}
}
}

0 comments on commit 000dcee

Please sign in to comment.