unifiHack: init

This package takes the upstream Unifi package, and then applies a AspectJ
aspect which replaces the auth logic with stuff which checks whether there's a
Pomerium header.
This commit is contained in:
Luke Granger-Brown 2021-01-04 20:49:04 +00:00
parent 2d52be000f
commit 85c2c4d507
4 changed files with 260 additions and 0 deletions

View file

@ -18,6 +18,7 @@
envoy = import ./envoy args; envoy = import ./envoy args;
deluge = import ./deluge args; deluge = import ./deluge args;
grafana-plugins = import ./grafana-plugins args; grafana-plugins = import ./grafana-plugins args;
unifiHacked = import ./unifi-hack args;
tiny-remapper = import ./tiny-remapper.nix args; tiny-remapper = import ./tiny-remapper.nix args;
} // (import ./heptapod-runner.nix args) } // (import ./heptapod-runner.nix args)
// (import ./lightspeed args) // (import ./lightspeed args)

View file

@ -0,0 +1,89 @@
import javax.servlet.FilterChain;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.slf4j.Logger;
import com.ubnt.data.Admin;
import com.ubnt.data.X;
import com.ubnt.ace.view.AuthFilter;
import com.ubnt.service.DatabaseService;
import com.ubnt.data.oOOO.C;
import com.ubnt.data.oOOO.Filter;
import com.ubnt.service.system.SystemInitializingBean;
aspect MyLogin {
private static final boolean LOG_STUFF = false;
private static final void log(String stuff) {
if (!LOG_STUFF) return;
SystemInitializingBean.SYSTEM_LOGGER.error(stuff);
}
static final X doLoginByHeader(HttpServletRequest request, HttpServletResponse response, X context) {
log("in doLoginByHeader");
String currentEmail = "";
if (context != null) {
Admin adminData = (Admin)context.getX("admin", X.EMPTY_INSTANCE);
currentEmail = adminData.getString("email");
}
log("currentEmail: " + currentEmail);
String proxyEmail = request.getHeader("x-pomerium-claim-email");
if (proxyEmail == null) {
proxyEmail = "";
}
log("proxyEmail: " + proxyEmail);
if (!currentEmail.equals(proxyEmail)) {
// We're not logged in as who we should be.
if (proxyEmail.equals("")) {
// We should be logged out.
log("logging out");
AuthFilter.getInner().logout(request, response);
return null;
} else {
// We should be logged in as somebody.
if (!currentEmail.equals("")) {
log("logging out first");
// We have an existing session; log out first.
AuthFilter.getInner().logout(request, response);
}
// Now log in as proxyEmail.
Admin newAdmin = null;
for (final Admin admin : DatabaseService.database().<Admin>queryList(Admin.class, new C.Factory().or(Filter.equals("name", proxyEmail), Filter.equals("email", proxyEmail)).create())) {
newAdmin = admin; break;
}
if (newAdmin == null) {
// TODO: make this create a new user account?
log("couldn't find admin user email " + proxyEmail);
throw new NoMatchingUser();
}
log("got admin with email " + proxyEmail);
return AuthFilter.getInner().createSession(newAdmin, false, false, request, response);
}
}
return context;
}
pointcut authFilter(ServletRequest request, ServletResponse response, FilterChain filterChain): within(com.ubnt.ace.view.AuthFilter) && execution(void com.ubnt.ace.view.AuthFilter.doFilter(javax.servlet.ServletRequest, javax.servlet.ServletResponse, javax.servlet.FilterChain)) && args(request, response, filterChain);
pointcut thePart(HttpServletRequest httpRequest, ServletRequest request, ServletResponse response, FilterChain filterChain): within(com.ubnt.ace.view.AuthFilter) && call(X com.ubnt.ace.view.AuthFilter.inner.getAnXGivenARequestSomehow(HttpServletRequest)) && args(httpRequest) && cflow(authFilter(request, response, filterChain));
// Wrap the part where we fetch the current user.
X around(HttpServletRequest httpRequest, ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain): thePart(httpRequest, servletRequest, servletResponse, filterChain) {
X originalContext = proceed(httpRequest, servletRequest, servletResponse, filterChain);
return doLoginByHeader((HttpServletRequest)servletRequest, (HttpServletResponse)servletResponse, originalContext);
}
// Add some extra exception handling.
void around(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws java.io.IOException: authFilter(servletRequest, servletResponse, filterChain) {
try {
proceed(servletRequest, servletResponse, filterChain);
} catch (NoMatchingUser e) {
HttpServletResponse response = (HttpServletResponse)servletResponse;
response.sendError(HttpServletResponse.SC_FORBIDDEN, "You probably don't have a user account with a matching email.");
}
}
public static class NoMatchingUser extends java.lang.RuntimeException {}
}

View file

@ -0,0 +1,122 @@
tiny 2 0 intermediary named
c com/ubnt/ace/api/ApiUtils com/ubnt/ace/api/ApiUtils
c com/ubnt/data/X com/ubnt/data/X
f Ljava/lang/String; Ô00000 ATTR_PREFIX_X
f Lorg/slf4j/Logger; õ00000 LOGGER
f Ljava/lang/String; Õo0000 MONGO_GTE
f Ljava/lang/String; ÕÒ0000 MONGO_ALL
f Ljava/lang/String; o00000 ATTR_NO_DELETE
f Ljava/lang/String; õO0000 ATTR_HIDDEN_ID
f Ljava/lang/String; ö00000 ATTR_HIDDEN
f Ljava/util/List; ÕO0000 EMPTY_LIST
f Ljava/lang/String; ôO0000 MONGO_INC
f Ljava/lang/String; ÔÒ0000 MONGO_EXISTS
f Ljava/lang/String; ôo0000 MONGO_IN
f Ljava/util/List; oÒ0000 EMPTY_INTEGER_LIST
f Ljava/lang/String; public ATTR_NO_EDIT
f Lcom/ubnt/data/X; ÖÒ0000 EMPTY_INSTANCE
f Ljava/lang/String; voidnew MONGO_TYPE
f Ljava/lang/String; öÒ0000 MONGO_PULL
f Ljava/lang/String; interfacesuper MONGO_REGEX
f Ljava/lang/String; õÒ0000 MONGO_SET
f Ljava/lang/String; ÒO0000 MONGO_RENAME
f Ljava/lang/String; Object MONGO_NE
f Lcom/fasterxml/jackson/databind/ObjectMapper; Õ00000 JACKSON_OBJECTMAPPER
f Ljava/lang/String; õo0000 MONGO_OR
f Ljava/util/List; ÖO0000 EMPTY_CLASS_LIST
f Ljava/lang/String; intsuper ID_ATTR_NAME
f Ljava/lang/String; oO0000 MONGO_NIN
f Ljava/lang/String; dosuper MONGO_LT
f Ljava/lang/String; ô00000 MONGO_UNSET
f Ljava/lang/String; float MONGO_GT
f Ljava/util/List; supersuper EMPTY_STRING_LIST
c com/ubnt/data/oOOO/o0OO com/ubnt/data/oOOO/Filter
m (Ljava/lang/String;Ljava/lang/Object;)Lcom/ubnt/data/oOOO/o0OO; Õ00000 equals
c com/ubnt/ace/void com/ubnt/ace/void
m (Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String; õ00000 hashPassword
m (Ljavax/servlet/http/HttpServletResponse;Ljava/lang/String;Ljava/lang/String;)V o00000 setCookie
m (Ljavax/servlet/http/HttpServletResponse;Ljava/lang/String;Ljava/lang/String;Z)V o00000 setCookie
c com/ubnt/ace/api/ApiServlet com/ubnt/ace/api/ApiServlet
m (Ljavax/servlet/http/HttpServletRequest;Ljavax/servlet/http/HttpServletResponse;)Lcom/ubnt/ace/api/B; o00000 serviceAuthless
m (Ljava/lang/String;)Z o00000 skipAuthForPath
c com/ubnt/service/system/OOOoOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO com/ubnt/service/system/OOOoOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO
m (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lcom/ubnt/data/X;)V o00000 addToCache
c com/ubnt/data/oOOO/C com/ubnt/data/oOOO/C
c com/ubnt/data/oOOO/C$_o com/ubnt/data/oOOO/C$Factory
m (Ljava/lang/String;Ljava/lang/Object;)Lcom/ubnt/data/oOOO/C$_o; Ô00000 equals
m ()Lcom/ubnt/data/oOOO/C; super create
m ([Lcom/ubnt/data/oOOO/o0OO;)Lcom/ubnt/data/oOOO/C$_o; Ò00000 or
c com/ubnt/service/system/O00o com/ubnt/service/system/DatabaseBean
m (Ljava/lang/Class;Lcom/ubnt/data/oOOO/C;)V return deleteFromDB
m (Ljava/lang/Class;Lcom/ubnt/data/oOOO/C;)Ljava/util/List; new queryList
c com/ubnt/service/OoOO com/ubnt/service/DatabaseService
m ()Lcom/ubnt/service/system/O00o; õ00000 database
c com/ubnt/ace/view/AuthFilter com/ubnt/ace/view/AuthFilter
m (Ljavax/servlet/http/HttpServletRequest;)V Ô00000 checkDemoRequest
f [Ljava/lang/String; Ò00000 skipAuthUcorePaths
m (Ljavax/servlet/http/HttpServletRequest;Lcom/ubnt/data/X;)Lcom/ubnt/data/X; o00000 makeContext
f Lcom/ubnt/ace/view/AuthFilter$_Oo; Ø00000 innerInstance
f Lorg/slf4j/Logger; õ00000 logger
m ()Lcom/ubnt/ace/view/AuthFilter$_Oo; new getInner
f Ljava/lang/String; ø00000 DEMO
f Ljava/lang/String; null CONTEXT
f [Ljava/lang/String; Ö00000 skipAuthPaths
f J ö00000 thingyVersion
m (Ljavax/servlet/http/HttpServletRequest;)Z new isDemoRequest
f [Ljava/lang/String; Õ00000 requireAuthPaths
m (Ljava/lang/String;)Z o00000 authRequired
c com/ubnt/ace/view/AuthFilter$_Oo com/ubnt/ace/view/AuthFilter$inner
m (Ljavax/servlet/http/HttpServletRequest;)Lcom/ubnt/data/X; o00000 getAnXGivenARequestSomehow
m (Lcom/ubnt/data/Admin;ZZLjava/lang/String;Ljavax/servlet/http/HttpServletRequest;Ljavax/servlet/http/HttpServletResponse;)Lcom/ubnt/data/X; Ò00000 createSSession
p 3 bl2 strict
p 2 bl remember
p 4 string accessToken
m (Ljava/lang/String;Lcom/ubnt/data/X;)V Õ00000 saveAccessTokenToCache
p 1 string accessToken
f Ljava/lang/String; Ô00000 SESSION_COOKIE_NAME
m (Ljava/lang/String;)Lcom/ubnt/data/X; float checkPrivilege
p 1 string adminId
f Ljava/lang/String; Ò00000 CSRF_TOKEN_NAME
m (Ljava/lang/String;Ljava/lang/String;Ljava/util/Optional;)Lcom/ubnt/data/Admin; o00000 login
p 2 string2 password
p 3 optional twoFAToken
p 1 string1 username
m (Lcom/ubnt/data/Admin;Ljavax/servlet/http/HttpServletRequest;Ljavax/servlet/http/HttpServletResponse;)Lcom/ubnt/data/X; o00000 createSessionMaybe
m (Ljava/lang/String;)Lcom/ubnt/data/X; o00000 loadSessionById
m (Lcom/ubnt/data/Admin;ZZLjava/lang/String;Ljavax/servlet/http/HttpServletRequest;Ljavax/servlet/http/HttpServletResponse;)Lcom/ubnt/data/X; o00000 createSession
p 2 bl remember
p 3 bl2 strict
p 4 string accessToken
m (Lcom/ubnt/data/Admin;ZZLjavax/servlet/http/HttpServletRequest;Ljavax/servlet/http/HttpServletResponse;)Lcom/ubnt/data/X; o00000 createSession
p 2 boolean2 remember
p 3 boolean3 strict
m (Ljava/lang/String;)Lcom/ubnt/data/X; ö00000 invalidateAccessToken
m (Ljavax/servlet/http/HttpServletRequest;Ljavax/servlet/http/HttpServletResponse;)V o00000 logout
c com/ubnt/ace/api/for com/ubnt/ace/api/ApiException
f Lcom/ubnt/ace/api/for; Ô00000 NotFound
f Lcom/ubnt/data/X; ÕO0000 errorData
f Lcom/ubnt/ace/api/for; Ö00000 NotImplemented
f Lcom/ubnt/ace/api/for; oO0000 DeviceBusy
f Lcom/ubnt/ace/api/for; supersuper NoApplicableDevices
f Lcom/ubnt/ace/api/for; o00000 NoDelete
f Lcom/ubnt/ace/api/for; ÖO0000 LoginRequired
f Lcom/ubnt/ace/api/for; OO0000 NotSupported
f Lcom/ubnt/ace/api/for; ö00000 NotCompatible
f Lcom/ubnt/ace/api/for; õO0000 NoEdit
m ()Lcom/ubnt/data/X; o00000 getErrorData
f Lcom/ubnt/ace/api/for; Ò00000 UpgradeInProgress
f Lcom/ubnt/ace/api/for; Oo0000 InvalidArgs
f Lcom/ubnt/ace/api/for; public EmptyGoogleApiKey
f Lcom/ubnt/ace/api/for; classsuper IdInvalid
f Lcom/ubnt/ace/api/for; void Invalid
f Lcom/ubnt/ace/api/for; ô00000 InvalidLogin
f Lcom/ubnt/ace/api/for; float NoPermission
f Lcom/ubnt/ace/api/for; ÔO0000 MissingUbbDevice
f Lcom/ubnt/ace/api/for; Õ00000 IdRequired
f Lcom/ubnt/ace/api/for; ôO0000 NotUDM
f Lcom/ubnt/ace/api/for; õ00000 NotCloudKey
f Lcom/ubnt/ace/api/for; öO0000 InvalidPayload
f Lcom/ubnt/ace/api/for; ÒO0000 LocateSiteFailed
f Lcom/ubnt/ace/api/for; Object NoSuchCommand
c com/ubnt/service/system/O0oO com/ubnt/service/system/SystemInitializingBean
f Lorg/slf4j/Logger; õo0000 SYSTEM_LOGGER

View file

@ -0,0 +1,48 @@
{ depot, pkgs, ... }:
let
inherit (pkgs) stdenvNoCC jdk aspectj unifi;
in
stdenvNoCC.mkDerivation {
pname = "unifi-hack";
version = "depot";
src = ./aspect;
nativeBuildInputs = [ jdk aspectj depot.pkgs.tiny-remapper ];
buildPhase = ''
mkdir $NIX_BUILD_TOP/tmp
cp $(type -p ajc) $NIX_BUILD_TOP/tmp/ajc
substituteInPlace $NIX_BUILD_TOP/tmp/ajc \
--replace '-Xmx64M' ""
ajc_classpath="$(ls ${unifi}/lib/*.jar | tr '\n' ':' | sed 's/:$//')"
tiny-remapper ${unifi}/lib/ace.jar $NIX_BUILD_TOP/tmp/acedeobf.jar ./ace.jar.tiny2map intermediary named
$NIX_BUILD_TOP/tmp/ajc -8 \
-classpath "${aspectj}/lib/aspectjrt.jar:$ajc_classpath" \
-inpath $NIX_BUILD_TOP/tmp/acedeobf.jar \
-outjar $NIX_BUILD_TOP/tmp/aceaspected.jar \
./MyLogin.aj
tiny-remapper $NIX_BUILD_TOP/tmp/aceaspected.jar $NIX_BUILD_TOP/tmp/acereobf.jar ./ace.jar.tiny2map named intermediary
# Repack the manifest
pushd $NIX_BUILD_TOP/tmp
jar xf acereobf.jar META-INF/MANIFEST.MF
awk \
'/^[^ ]/ { inclasspath=0 } /Class-Path:/ { inclasspath=1 } { if (inclasspath) print; }' \
META-INF/MANIFEST.MF > classpath.MF
substituteInPlace classpath.MF \
--replace 'Class-Path: ' 'Class-Path: aspectjrt.jar '
jar -ufm acereobf.jar classpath.MF
popd
'';
installPhase = ''
cp -R ${unifi} $out
chmod -R u+w $out
rm $out/lib/ace.jar
cp $NIX_BUILD_TOP/tmp/acereobf.jar $out/lib/ace.jar
cp ${aspectj}/lib/aspectjrt.jar $out/lib/aspectjrt.jar
'';
}