Compare commits
No commits in common. "8d190c29700794371ce748fd886b95a368e5b0b4" and "e4b4497b30b38458c1155f295b0904ea636289ec" have entirely different histories.
8d190c2970
...
e4b4497b30
3 changed files with 77 additions and 75 deletions
|
|
@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue