Skip to content

Commit

Permalink
fix: Ip filter per tenant (#652)
Browse files Browse the repository at this point in the history
* fix: ip filter impl

* fix: ip filter impl

* fix: ip filter impl

* fix: jwt fix
  • Loading branch information
sattvikc authored Apr 28, 2023
1 parent 8343f62 commit 88fa059
Show file tree
Hide file tree
Showing 5 changed files with 185 additions and 57 deletions.
21 changes: 21 additions & 0 deletions src/main/java/io/supertokens/config/CoreConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,14 @@
import io.supertokens.cliOptions.CLIOptions;
import io.supertokens.pluginInterface.LOG_LEVEL;
import io.supertokens.pluginInterface.exceptions.InvalidConfigException;
import org.apache.catalina.filters.RemoteAddrFilter;
import org.jetbrains.annotations.TestOnly;

import java.io.File;
import java.io.IOException;
import java.util.HashSet;
import java.util.Set;
import java.util.regex.PatternSyntaxException;

@JsonIgnoreProperties(ignoreUnknown = true)
public class CoreConfig {
Expand Down Expand Up @@ -514,6 +516,25 @@ void validate(Main main) throws InvalidConfigException {
throw new InvalidConfigException(
"'log_level' config must be one of \"NONE\",\"DEBUG\", \"INFO\", \"WARN\" or \"ERROR\".");
}

{
// IP Filter validation
RemoteAddrFilter filter = new RemoteAddrFilter();
if (ip_allow_regex != null) {
try {
filter.setAllow(ip_allow_regex);
} catch (PatternSyntaxException e) {
throw new InvalidConfigException("Provided regular expression is invalid for ip_allow_regex config");
}
}
if (ip_deny_regex != null) {
try {
filter.setDeny(ip_deny_regex);
} catch (PatternSyntaxException e) {
throw new InvalidConfigException("Provided regular expression is invalid for ip_deny_regex config");
}
}
}
}

public void createLoggingFile(Main main) throws IOException {
Expand Down
22 changes: 14 additions & 8 deletions src/main/java/io/supertokens/signingkeys/JWTSigningKey.java
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,13 @@ public class JWTSigningKey extends ResourceDistributor.SingletonResource {

public static void initForBaseTenant(Main main) throws UnsupportedJWTSigningAlgorithmException {
try {
main.getResourceDistributor().setResource(new AppIdentifier(null, null), RESOURCE_KEY,
new JWTSigningKey(new AppIdentifier(null, null), main));
JWTSigningKey jwtSigningKey = new JWTSigningKey(new AppIdentifier(null, null), main);
main.getResourceDistributor().setResource(new AppIdentifier(null, null), RESOURCE_KEY, jwtSigningKey);

if (!Main.isTesting) {
// init JWT signing keys, we create one key for each supported algorithm type
jwtSigningKey.generateKeysForSupportedAlgos(main);
}
} catch (TenantOrAppNotFoundException e) {
throw new IllegalStateException(e);
}
Expand Down Expand Up @@ -86,9 +91,14 @@ public static void loadForAllTenants(Main main, List<AppIdentifier> apps)
resource);
} else {
try {
JWTSigningKey jwtSigningKey = new JWTSigningKey(app, main);
main.getResourceDistributor()
.setResource(app, RESOURCE_KEY,
new JWTSigningKey(app, main));
.setResource(app, RESOURCE_KEY, jwtSigningKey);

if (!Main.isTesting) {
jwtSigningKey.generateKeysForSupportedAlgos(main);
}

} catch (TenantOrAppNotFoundException e) {
throw new IllegalStateException(e);
}
Expand Down Expand Up @@ -132,10 +142,6 @@ private JWTSigningKey(AppIdentifier appIdentifier, Main main)
throws UnsupportedJWTSigningAlgorithmException, TenantOrAppNotFoundException {
this.appIdentifier = appIdentifier;
this.main = main;
if (!Main.isTesting) {
// init JWT signing keys, we create one key for each supported algorithm type
generateKeysForSupportedAlgos(main);
}
}

private void generateKeysForSupportedAlgos(Main main)
Expand Down
43 changes: 0 additions & 43 deletions src/main/java/io/supertokens/webserver/Webserver.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@

import io.supertokens.Main;
import io.supertokens.OperatingSystem;
import io.supertokens.ProcessState;
import io.supertokens.ResourceDistributor;
import io.supertokens.cliOptions.CLIOptions;
import io.supertokens.config.Config;
Expand Down Expand Up @@ -59,17 +58,13 @@
import org.apache.catalina.LifecycleState;
import org.apache.catalina.connector.Connector;
import org.apache.catalina.core.StandardContext;
import org.apache.catalina.filters.RemoteAddrFilter;
import org.apache.catalina.startup.Tomcat;
import org.apache.tomcat.util.descriptor.web.FilterDef;
import org.apache.tomcat.util.descriptor.web.FilterMap;
import org.apache.tomcat.util.http.fileupload.FileUtils;

import java.io.File;
import java.util.UUID;
import java.util.logging.Handler;
import java.util.logging.Logger;
import java.util.regex.PatternSyntaxException;

public class Webserver extends ResourceDistributor.SingletonResource {

Expand Down Expand Up @@ -147,9 +142,6 @@ public void start() {
// calling stop
context.setUnloadDelay(5000);

// we add remote address filter so that only certain IPs can query the core.
addRemoteAddressFilter(context, main);

// start tomcat
try {
tomcat.start();
Expand All @@ -170,41 +162,6 @@ public void start() {
setupRoutes();
}

private void addRemoteAddressFilter(StandardContext context, Main main) {
String allow = Config.getBaseConfig(main).getIpAllowRegex();
String deny = Config.getBaseConfig(main).getIpDenyRegex();
if (allow == null && deny == null) {
return;
}
ProcessState.getInstance(main).addState(ProcessState.PROCESS_STATE.ADDING_REMOTE_ADDRESS_FILTER, null);
RemoteAddrFilter filter = new RemoteAddrFilter();
if (allow != null) {
try {
filter.setAllow(allow);
} catch (PatternSyntaxException e) {
throw new QuitProgramException("Provided regular expression is invalid for ip_allow_regex config");
}
}
if (deny != null) {
try {
filter.setDeny(deny);
} catch (PatternSyntaxException e) {
throw new QuitProgramException("Provided regular expression is invalid for ip_deny_regex config");
}
}
filter.setDenyStatus(403);

FilterDef filterDefinition = new FilterDef();
filterDefinition.setFilter(filter);
filterDefinition.setFilterName(RemoteAddrFilter.class.getSimpleName());
context.addFilterDef(filterDefinition);

FilterMap filterMapping = new FilterMap();
filterMapping.setFilterName(RemoteAddrFilter.class.getSimpleName());
filterMapping.addURLPattern("*");
context.addFilterMap(filterMapping);
}

private void setupRoutes() {
addAPI(new NotFoundOrHelloAPI(main));
addAPI(new HelloAPI(main));
Expand Down
52 changes: 52 additions & 0 deletions src/main/java/io/supertokens/webserver/WebserverAPI.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,10 @@
import com.google.gson.JsonElement;
import io.supertokens.AppIdentifierWithStorageAndUserIdMapping;
import io.supertokens.Main;
import io.supertokens.ProcessState;
import io.supertokens.TenantIdentifierWithStorageAndUserIdMapping;
import io.supertokens.config.Config;
import io.supertokens.config.CoreConfig;
import io.supertokens.exceptions.QuitProgramException;
import io.supertokens.featureflag.exceptions.FeatureNotEnabledException;
import io.supertokens.multitenancy.exception.BadPermissionException;
Expand All @@ -35,15 +37,20 @@
import io.supertokens.storageLayer.StorageLayer;
import io.supertokens.useridmapping.UserIdType;
import io.supertokens.utils.SemVer;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.apache.catalina.filters.RemoteAddrFilter;
import org.jetbrains.annotations.TestOnly;

import java.io.IOException;
import java.util.HashSet;
import java.util.Set;
import java.util.regex.PatternSyntaxException;

public abstract class WebserverAPI extends HttpServlet {

Expand Down Expand Up @@ -337,10 +344,55 @@ protected AppIdentifierWithStorageAndUserIdMapping getAppIdentifierWithStorageAn
appIdentifierWithStorage, appIdentifierWithStorage.getStorage(), userId, userIdType);
}

protected boolean checkIPAccess(HttpServletRequest req, HttpServletResponse resp)
throws TenantOrAppNotFoundException, ServletException, IOException {
CoreConfig config = Config.getConfig(getTenantIdentifierWithStorageFromRequest(req), main);
String allow = config.getIpAllowRegex();
String deny = config.getIpDenyRegex();
if (allow == null && deny == null) {
return true;
}
RemoteAddrFilter filter = new RemoteAddrFilter();
if (allow != null) {
try {
filter.setAllow(allow);
} catch (PatternSyntaxException e) {
throw new RuntimeException("should never happen");
}
}
if (deny != null) {
try {
filter.setDeny(deny);
} catch (PatternSyntaxException e) {
throw new RuntimeException("should never happen");
}
}
filter.setDenyStatus(403);

final boolean[] isAllowed = {false};
FilterChain dummyFilterChain = new FilterChain() {
@Override
public void doFilter(ServletRequest request, ServletResponse response)
throws IOException, ServletException {
isAllowed[0] = true;
}
};

filter.doFilter(req, resp, dummyFilterChain);
return isAllowed[0];
}

@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws IOException {


TenantIdentifier tenantIdentifier = null;
try {
if (!this.checkIPAccess(req, resp)) {
// IP access denied and the filter has already sent the response
return;
}

if (this.checkAPIKey(req)) {
assertThatAPIKeyCheckPasses(req);
}
Expand Down
Loading

0 comments on commit 88fa059

Please sign in to comment.