Compare commits

..

No commits in common. "8d190c29700794371ce748fd886b95a368e5b0b4" and "e4b4497b30b38458c1155f295b0904ea636289ec" have entirely different histories.

3 changed files with 77 additions and 75 deletions

View file

@ -0,0 +1,50 @@
package de.ccc.hamburg.keycloak.ssh_key;
import jakarta.ws.rs.ForbiddenException;
import jakarta.ws.rs.NotAuthorizedException;
import org.keycloak.models.ClientModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.services.managers.AppAuthManager;
import org.keycloak.services.managers.Auth;
import org.keycloak.services.managers.AuthenticationManager;
import java.util.function.Function;
public class AuthHelper {
public static AuthenticationManager.AuthResult getAuthResult(KeycloakSession session, Function<AuthenticationManager.AuthResult, Boolean> authFn) {
AuthenticationManager.AuthResult auth = new AppAuthManager.BearerTokenAuthenticator(session).authenticate();
if (auth == null) {
throw new NotAuthorizedException("Bearer");
} else if (!authFn.apply(auth)) {
throw new ForbiddenException();
}
return auth;
}
public static Auth getAuth(KeycloakSession session, Function<AuthenticationManager.AuthResult, Boolean> authFn) {
return getAuth(session, getAuthResult(session, authFn));
}
public static Auth getAuth(KeycloakSession session, String clientId, Function<AuthenticationManager.AuthResult, Boolean> authFn) {
return getAuth(session, getAuthResult(session, authFn), clientId);
}
public static Auth getAuth(KeycloakSession session, AuthenticationManager.AuthResult authResult) {
return getAuth(session, authResult, null);
}
public static Auth getAuth(KeycloakSession session, AuthenticationManager.AuthResult authResult, String clientId) {
RealmModel realm = session.getContext().getRealm();
ClientModel client;
if (clientId == null) {
client = authResult.getClient();
} else {
client = realm.getClientByClientId(clientId);
}
return new Auth(realm, authResult.getToken(), authResult.getUser(), client, authResult.getSession(), false);
}
}

View file

@ -1,44 +1,27 @@
package de.ccc.hamburg.keycloak.ssh_key; package de.ccc.hamburg.keycloak.ssh_key;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Stream; import java.util.stream.Stream;
import org.jboss.logging.Logger; import org.jboss.logging.Logger;
import org.keycloak.models.ClientModel; import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.PathParam;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.Response;
import jakarta.ws.rs.core.MediaType;
import org.keycloak.models.GroupModel; import org.keycloak.models.GroupModel;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel; import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel; import org.keycloak.models.UserModel;
import org.keycloak.models.UserProvider; import org.keycloak.models.UserProvider;
import org.keycloak.representations.userprofile.config.UPConfig;
import org.keycloak.services.managers.AppAuthManager;
import org.keycloak.services.managers.AppAuthManager.BearerTokenAuthenticator;
import org.keycloak.services.managers.Auth; import org.keycloak.services.managers.Auth;
import org.keycloak.services.managers.AuthenticationManager.AuthResult;
import org.keycloak.services.resource.RealmResourceProvider; import org.keycloak.services.resource.RealmResourceProvider;
import org.keycloak.userprofile.UserProfileProvider;
import jakarta.ws.rs.ForbiddenException;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.NotAuthorizedException;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.PathParam;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
public class SSHKeyResourceProvider implements RealmResourceProvider { public class SSHKeyResourceProvider implements RealmResourceProvider {
private static final Logger LOG = Logger.getLogger(SSHKeyResourceProvider.class); private static final Logger LOG = Logger.getLogger(SSHKeyResourceProvider.class);
private final KeycloakSession session; private final KeycloakSession session;
// taken from: https://github.com/nemchik/ssh-key-regex
private static final Pattern SSH_PUBLIC_KEY = Pattern.compile(
"^(?<key>(ssh-dss AAAAB3NzaC1kc3|ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNT|ecdsa-sha2-nistp384 AAAAE2VjZHNhLXNoYTItbmlzdHAzOD|ecdsa-sha2-nistp521 AAAAE2VjZHNhLXNoYTItbmlzdHA1Mj|sk-ecdsa-sha2-nistp256@openssh.com AAAAInNrLWVjZHNhLXNoYTItbmlzdHAyNTZAb3BlbnNzaC5jb2|ssh-ed25519 AAAAC3NzaC1lZDI1NTE5|sk-ssh-ed25519@openssh.com AAAAGnNrLXNzaC1lZDI1NTE5QG9wZW5zc2guY29t|ssh-rsa AAAAB3NzaC1yc2)[0-9A-Za-z+/]+[=]{0,3})(\\s.*)?$");
public SSHKeyResourceProvider(KeycloakSession keycloakSession) { public SSHKeyResourceProvider(KeycloakSession keycloakSession) {
this.session = keycloakSession; this.session = keycloakSession;
} }
@ -56,62 +39,32 @@ public class SSHKeyResourceProvider implements RealmResourceProvider {
@Path("export/{group_id}") @Path("export/{group_id}")
@Produces(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON)
public Response exportKeys(@PathParam("group_id") String groupId) { public Response exportKeys(@PathParam("group_id") String groupId) {
UserProvider userProvider = session.users();
try { try {
SSHKeyResourceProvider.getAuth(session); Auth auth = AuthHelper.getAuth(session,
authResult -> authResult.getToken().getIssuedFor().equals("admin-cli"));
RealmModel realm = session.getContext().getRealm();
// TODO: add allowlist check
GroupModel group = realm.getGroupById(groupId);
LOG.info(String.format("Getting Users from Group \"%s\" with ID %s", group.getName(), group.getId()));
Stream<UserModel> users = userProvider.getGroupMembersStream(realm, group);
users.forEach(user -> {
String sshKey = user.getAttributeStream("ssh-key-1").findFirst().get();
LOG.info(String.format("SSH Key of %s: %s", user.getUsername(), sshKey));
});
return Response.ok(Map.of("hello", auth.getUser().getUsername())).build();
} catch (Exception e) { } catch (Exception e) {
System.err.println(e); System.err.println(e);
return Response.status(401, e.getMessage()).build(); return Response.status(401, e.getMessage()).build();
} }
UserProvider userProvider = session.users();
UserProfileProvider profileProvider = session.getProvider(UserProfileProvider.class);
UPConfig upconfig = profileProvider.getConfiguration();
List<String> attributeNames = upconfig.getAttributes()
.stream()
.filter(a -> a.getGroup() != null && a.getGroup().equals("de.ccc.hamburg.keycloak.ssh_key.keys"))
.map(a -> a.getName())
.toList();
RealmModel realm = session.getContext().getRealm();
// TODO: add allowlist check
GroupModel group = realm.getGroupById(groupId);
Stream<UserModel> users = userProvider.getGroupMembersStream(realm, group);
List<String> keys = users
.map(user -> {
return attributeNames
.stream()
.map(attributeName -> user.getAttributeStream(attributeName).findFirst())
.filter(attribute -> attribute.isPresent())
.map(attribute -> attribute.get())
.toList();
})
.flatMap(List::stream)
.map(key -> {
final Matcher matcher = SSH_PUBLIC_KEY.matcher(key);
return matcher.find() ? matcher.group("key") : null;
})
.filter(Objects::nonNull)
.toList();
return Response.ok(Map.of("keys", keys)).build();
}
private static Auth getAuth(KeycloakSession session) {
AuthResult auth = new AppAuthManager.BearerTokenAuthenticator(session).authenticate();
if (auth == null) {
throw new NotAuthorizedException("Bearer");
} else if (!auth.getToken().getIssuedFor().equals("admin-cli")) {
throw new ForbiddenException();
}
RealmModel realm = session.getContext().getRealm();
ClientModel client = auth.getClient();
return new Auth(realm, auth.getToken(), auth.getUser(), client, auth.getSession(), false);
} }
} }

View file

@ -1,12 +1,11 @@
package de.ccc.hamburg.keycloak.ssh_key; package de.ccc.hamburg.keycloak.ssh_key;
import org.keycloak.Config;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory; import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.services.resource.RealmResourceProvider; import org.keycloak.services.resource.RealmResourceProvider;
import org.keycloak.services.resource.RealmResourceProviderFactory; import org.keycloak.services.resource.RealmResourceProviderFactory;
import com.google.auto.service.AutoService; import com.google.auto.service.AutoService;
import org.keycloak.Config;
@AutoService(RealmResourceProviderFactory.class) @AutoService(RealmResourceProviderFactory.class)
public class SSHKeyResourceProviderFactory implements RealmResourceProviderFactory { public class SSHKeyResourceProviderFactory implements RealmResourceProviderFactory {