Merge pull request #138 from sdgx/url-whitelist-2
Implement URL host whitelist
This commit is contained in:
+13
-2
@@ -27,6 +27,16 @@
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<!-- By default, no host whitelist is added to the binary, so it can be used
|
||||
to download and run applications from any server. To create a custom
|
||||
Getdown build that can only talk to whitelisted servers, set this
|
||||
property on the command line, e.g. -Dgetdown.host.whitelist=my.server.com
|
||||
Wildcards can be used (*.mycompany.com) and multiple values can be
|
||||
separated by commas (app1.foo.com,app2.bar.com,app3.baz.com). -->
|
||||
<properties>
|
||||
<getdown.host.whitelist></getdown.host.whitelist>
|
||||
</properties>
|
||||
|
||||
<build>
|
||||
<resources>
|
||||
<resource> <!-- include the LICENSE file in the jar -->
|
||||
@@ -66,14 +76,15 @@
|
||||
<configuration>
|
||||
<target>
|
||||
<tstamp>
|
||||
<format property="build_time" pattern="yyyy-MM-dd HH:mm"/>
|
||||
<format property="getdown.build.time" pattern="yyyy-MM-dd HH:mm"/>
|
||||
</tstamp>
|
||||
<copy file="${project.build.sourceDirectory}/com/threerings/getdown/data/Build.java.tmpl"
|
||||
tofile="${project.build.sourceDirectory}/com/threerings/getdown/data/Build.java"
|
||||
overwrite="true">
|
||||
<filterset>
|
||||
<filter token="build_time" value="${build_time}"/>
|
||||
<filter token="build_time" value="${getdown.build.time}"/>
|
||||
<filter token="build_version" value="${project.version}"/>
|
||||
<filter token="host_whitelist" value="${getdown.host.whitelist}"/>
|
||||
</filterset>
|
||||
</copy>
|
||||
</target>
|
||||
|
||||
@@ -484,7 +484,7 @@ public class Application
|
||||
String suffix = _trackingURLSuffix == null ? "" : _trackingURLSuffix;
|
||||
String ga = getGATrackingCode();
|
||||
return _trackingURL == null ? null :
|
||||
new URL(_trackingURL + encodePath(event + suffix + ga));
|
||||
HostWhitelist.INSTANCE.verify(new URL(_trackingURL + encodePath(event + suffix + ga)));
|
||||
} catch (MalformedURLException mue) {
|
||||
log.warning("Invalid tracking URL", "path", _trackingURL, "event", event, "error", mue);
|
||||
return null;
|
||||
@@ -586,7 +586,7 @@ public class Application
|
||||
|
||||
// if we are a versioned deployment, create a versioned appbase
|
||||
try {
|
||||
_vappbase = (_version < 0) ? new URL(_appbase) : createVAppBase(_version);
|
||||
_vappbase = createVAppBase(_version);
|
||||
} catch (MalformedURLException mue) {
|
||||
String err = MessageUtil.tcompose("m.invalid_appbase", _appbase);
|
||||
throw (IOException) new IOException(err).initCause(mue);
|
||||
@@ -601,7 +601,7 @@ public class Application
|
||||
latest = SysProps.replaceDomain(latest);
|
||||
}
|
||||
try {
|
||||
_latest = new URL(latest);
|
||||
_latest = HostWhitelist.INSTANCE.verify(new URL(latest));
|
||||
} catch (MalformedURLException mue) {
|
||||
log.warning("Invalid URL for latest attribute.", mue);
|
||||
}
|
||||
@@ -1408,7 +1408,8 @@ public class Application
|
||||
protected URL createVAppBase (long version)
|
||||
throws MalformedURLException
|
||||
{
|
||||
return new URL(_appbase.replace("%VERSION%", "" + version));
|
||||
String url = version < 0 ? _appbase : _appbase.replace("%VERSION%", "" + version);
|
||||
return HostWhitelist.INSTANCE.verify(new URL(url));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -5,6 +5,11 @@
|
||||
|
||||
package com.threerings.getdown.data;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import com.threerings.getdown.util.StringUtil;
|
||||
|
||||
/**
|
||||
* Contains static data provided during the build process.
|
||||
*/
|
||||
@@ -19,4 +24,20 @@ public class Build {
|
||||
public static String version () {
|
||||
return "@build_version@";
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>The hosts which Getdown is allowed to communicate with. An empty list indicates that
|
||||
* no whitelist is configured and there are no limitations. By default, no host whitelist
|
||||
* is added to the binary, so it can be used to download and run applications from any
|
||||
* server.
|
||||
*
|
||||
* <p>To create a custom Getdown build that can only talk to whitelisted servers, set
|
||||
* the {@code getdown.host.whitelist} property on the command line while building the JAR
|
||||
* (e.g. {@code mvn package -Dgetdown.host.whitelist=my.server.com}). Wildcards can be used
|
||||
* (e.g. {@code *.mycompany.com}) and multiple values can be separated by commas
|
||||
* (e.g. {@code app1.foo.com,app2.bar.com,app3.baz.com}).
|
||||
*/
|
||||
public static List<String> hostWhitelist () {
|
||||
return Arrays.asList(StringUtil.parseStringArray("@host_whitelist@"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,8 @@ import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.Reader;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
@@ -17,8 +19,6 @@ import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
|
||||
import com.threerings.getdown.util.StringUtil;
|
||||
|
||||
import static com.threerings.getdown.Log.log;
|
||||
|
||||
/**
|
||||
@@ -320,11 +320,21 @@ public class Config
|
||||
public String getUrl (String name, String def)
|
||||
{
|
||||
String value = getString(name + "." + Locale.getDefault().getLanguage());
|
||||
if (!StringUtil.isBlank(value)) {
|
||||
return value;
|
||||
if (StringUtil.isBlank(value)) {
|
||||
value = getString(name);
|
||||
}
|
||||
value = getString(name);
|
||||
return StringUtil.isBlank(value) ? def : value;
|
||||
if (StringUtil.isBlank(value)) {
|
||||
value = def;
|
||||
}
|
||||
if (!StringUtil.isBlank(value)) {
|
||||
try {
|
||||
HostWhitelist.INSTANCE.verify(new URL(value));
|
||||
} catch (MalformedURLException e) {
|
||||
log.warning("Invalid URL.", "url", value, e);
|
||||
value = null;
|
||||
}
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
//
|
||||
// Getdown - application installer, patcher and launcher
|
||||
// Copyright (C) 2004-2016 Getdown authors
|
||||
// https://github.com/threerings/getdown/blob/master/LICENSE
|
||||
|
||||
package com.threerings.getdown.util;
|
||||
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import com.threerings.getdown.data.Build;
|
||||
|
||||
/**
|
||||
* Optional support for compiling a URL host whitelist into the Getdown JAR.
|
||||
* Useful if you're on the paranoid end of the security spectrum.
|
||||
*
|
||||
* @see Build#hostWhitelist()
|
||||
*/
|
||||
public final class HostWhitelist
|
||||
{
|
||||
public static final HostWhitelist INSTANCE = new HostWhitelist();
|
||||
|
||||
private final List<String> _hosts;
|
||||
|
||||
HostWhitelist ()
|
||||
{
|
||||
this(Build.hostWhitelist());
|
||||
}
|
||||
|
||||
HostWhitelist (List<String> hosts)
|
||||
{
|
||||
_hosts = Collections.unmodifiableList(hosts);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies that the specified URL should be accessible, per this host whitelist.
|
||||
* If the URL should not be accessible, this method throws a {@link MalformedURLException}.
|
||||
* If the URL should be accessible, this method simply returns the {@link URL} passed in.
|
||||
*/
|
||||
public final URL verify (URL url) throws MalformedURLException
|
||||
{
|
||||
if (url == null || _hosts.isEmpty()) {
|
||||
// either there is no URL to validate or no whitelist was configured
|
||||
return url;
|
||||
}
|
||||
|
||||
String urlHost = url.getHost();
|
||||
|
||||
for (String host : _hosts) {
|
||||
String regex = host.replace(".", "\\.").replace("*", ".*");
|
||||
if (urlHost.matches(regex)) {
|
||||
return url;
|
||||
}
|
||||
}
|
||||
|
||||
throw new MalformedURLException("The host for the specified URL (" + url
|
||||
+ ") is not in the host whitelist: " + _hosts);
|
||||
}
|
||||
|
||||
/** {@inheritDoc} */
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return _hosts.toString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,158 @@
|
||||
//
|
||||
// Getdown - application installer, patcher and launcher
|
||||
// Copyright (C) 2004-2016 Getdown authors
|
||||
// https://github.com/threerings/getdown/blob/master/LICENSE
|
||||
|
||||
package com.threerings.getdown.util;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
/**
|
||||
* Tests {@link HostWhitelist}.
|
||||
*/
|
||||
public class HostWhitelistTest
|
||||
{
|
||||
@Test
|
||||
public void testVerify () throws MalformedURLException
|
||||
{
|
||||
checkCanVerify("foo.com", "http://foo.com", true);
|
||||
checkCanVerify("foo.com", "http://foo.com/", true);
|
||||
checkCanVerify("foo.com", "http://foo.com/x/y/z", true);
|
||||
checkCanVerify("foo.com", "http://www.foo.com", false);
|
||||
checkCanVerify("foo.com", "http://www.foo.com/", false);
|
||||
checkCanVerify("foo.com", "http://www.foo.com/x/y/z", false);
|
||||
checkCanVerify("foo.com", "http://a.b.foo.com", false);
|
||||
checkCanVerify("foo.com", "http://a.b.foo.com/", false);
|
||||
checkCanVerify("foo.com", "http://a.b.foo.com/x/y/z", false);
|
||||
checkCanVerify("foo.com", "http://oo.com", false);
|
||||
checkCanVerify("foo.com", "http://f.oo.com", false);
|
||||
checkCanVerify("foo.com", "http://a.f.oo.com", false);
|
||||
|
||||
checkCanVerify("*.foo.com", "http://foo.com", false);
|
||||
checkCanVerify("*.foo.com", "http://foo.com/", false);
|
||||
checkCanVerify("*.foo.com", "http://foo.com/x/y/z", false);
|
||||
checkCanVerify("*.foo.com", "http://www.foo.com", true);
|
||||
checkCanVerify("*.foo.com", "http://www.foo.com/", true);
|
||||
checkCanVerify("*.foo.com", "http://www.foo.com/x/y/z", true);
|
||||
checkCanVerify("*.foo.com", "http://a.b.foo.com", true);
|
||||
checkCanVerify("*.foo.com", "http://a.b.foo.com/", true);
|
||||
checkCanVerify("*.foo.com", "http://a.b.foo.com/x/y/z", true);
|
||||
checkCanVerify("*.foo.com", "http://oo.com", false);
|
||||
checkCanVerify("*.foo.com", "http://f.oo.com", false);
|
||||
checkCanVerify("*.foo.com", "http://a.f.oo.com", false);
|
||||
|
||||
checkCanVerify("*.com", "http://foo.com", true);
|
||||
checkCanVerify("*.com", "http://foo.com/", true);
|
||||
checkCanVerify("*.com", "http://foo.com/x/y/z", true);
|
||||
checkCanVerify("*.com", "http://www.foo.com", true);
|
||||
checkCanVerify("*.com", "http://www.foo.com/", true);
|
||||
checkCanVerify("*.com", "http://www.foo.com/x/y/z", true);
|
||||
checkCanVerify("*.com", "http://a.b.foo.com", true);
|
||||
checkCanVerify("*.com", "http://a.b.foo.com/", true);
|
||||
checkCanVerify("*.com", "http://a.b.foo.com/x/y/z", true);
|
||||
checkCanVerify("*.com", "http://oo.com", true);
|
||||
checkCanVerify("*.com", "http://f.oo.com", true);
|
||||
checkCanVerify("*.com", "http://a.f.oo.com", true);
|
||||
|
||||
checkCanVerify("*.net", "http://foo.com", false);
|
||||
checkCanVerify("*.net", "http://foo.com/", false);
|
||||
checkCanVerify("*.net", "http://foo.com/x/y/z", false);
|
||||
checkCanVerify("*.net", "http://www.foo.com", false);
|
||||
checkCanVerify("*.net", "http://www.foo.com/", false);
|
||||
checkCanVerify("*.net", "http://www.foo.com/x/y/z", false);
|
||||
checkCanVerify("*.net", "http://a.b.foo.com", false);
|
||||
checkCanVerify("*.net", "http://a.b.foo.com/", false);
|
||||
checkCanVerify("*.net", "http://a.b.foo.com/x/y/z", false);
|
||||
checkCanVerify("*.net", "http://oo.com", false);
|
||||
checkCanVerify("*.net", "http://f.oo.com", false);
|
||||
checkCanVerify("*.net", "http://a.f.oo.com", false);
|
||||
|
||||
checkCanVerify("www.*.com", "http://foo.com", false);
|
||||
checkCanVerify("www.*.com", "http://foo.com/", false);
|
||||
checkCanVerify("www.*.com", "http://foo.com/x/y/z", false);
|
||||
checkCanVerify("www.*.com", "http://www.foo.com", true);
|
||||
checkCanVerify("www.*.com", "http://www.foo.com/", true);
|
||||
checkCanVerify("www.*.com", "http://www.foo.com/x/y/z", true);
|
||||
checkCanVerify("www.*.com", "http://a.b.foo.com", false);
|
||||
checkCanVerify("www.*.com", "http://a.b.foo.com/", false);
|
||||
checkCanVerify("www.*.com", "http://a.b.foo.com/x/y/z", false);
|
||||
checkCanVerify("www.*.com", "http://oo.com", false);
|
||||
checkCanVerify("www.*.com", "http://f.oo.com", false);
|
||||
checkCanVerify("www.*.com", "http://a.f.oo.com", false);
|
||||
checkCanVerify("www.*.com", "http://www.a.f.oo.com", true);
|
||||
|
||||
checkCanVerify("foo.*", "http://foo.com", true);
|
||||
checkCanVerify("foo.*", "http://foo.com/", true);
|
||||
checkCanVerify("foo.*", "http://foo.com/x/y/z", true);
|
||||
checkCanVerify("foo.*", "http://www.foo.com", false);
|
||||
checkCanVerify("foo.*", "http://www.foo.com/", false);
|
||||
checkCanVerify("foo.*", "http://www.foo.com/x/y/z", false);
|
||||
checkCanVerify("foo.*", "http://a.b.foo.com", false);
|
||||
checkCanVerify("foo.*", "http://a.b.foo.com/", false);
|
||||
checkCanVerify("foo.*", "http://a.b.foo.com/x/y/z", false);
|
||||
checkCanVerify("foo.*", "http://oo.com", false);
|
||||
checkCanVerify("foo.*", "http://f.oo.com", false);
|
||||
checkCanVerify("foo.*", "http://a.f.oo.com", false);
|
||||
|
||||
checkCanVerify("*.foo.*", "http://foo.com", false);
|
||||
checkCanVerify("*.foo.*", "http://foo.com/", false);
|
||||
checkCanVerify("*.foo.*", "http://foo.com/x/y/z", false);
|
||||
checkCanVerify("*.foo.*", "http://www.foo.com", true);
|
||||
checkCanVerify("*.foo.*", "http://www.foo.com/", true);
|
||||
checkCanVerify("*.foo.*", "http://www.foo.com/x/y/z", true);
|
||||
checkCanVerify("*.foo.*", "http://a.b.foo.com", true);
|
||||
checkCanVerify("*.foo.*", "http://a.b.foo.com/", true);
|
||||
checkCanVerify("*.foo.*", "http://a.b.foo.com/x/y/z", true);
|
||||
checkCanVerify("*.foo.*", "http://oo.com", false);
|
||||
checkCanVerify("*.foo.*", "http://f.oo.com", false);
|
||||
checkCanVerify("*.foo.*", "http://a.f.oo.com", false);
|
||||
|
||||
checkCanVerify("127.0.0.1", "http://127.0.0.1", true);
|
||||
checkCanVerify("127.0.0.1", "http://127.0.0.1/", true);
|
||||
checkCanVerify("127.0.0.1", "http://127.0.0.1/x/y/z", true);
|
||||
checkCanVerify("*.0.0.1", "http://127.0.0.1/abc", true);
|
||||
checkCanVerify("127.*.0.1", "http://127.0.0.1/abc", true);
|
||||
checkCanVerify("127.0.*.1", "http://127.0.0.1/abc", true);
|
||||
checkCanVerify("127.0.0.*", "http://127.0.0.1/abc", true);
|
||||
checkCanVerify("127.*.1", "http://127.0.0.1/abc", true);
|
||||
checkCanVerify("*.0.1", "http://127.0.0.1/abc", true);
|
||||
checkCanVerify("127.0.*", "http://127.0.0.1/abc", true);
|
||||
checkCanVerify("*", "http://127.0.0.1/abc", true);
|
||||
checkCanVerify("127.0.0.2", "http://127.0.0.1", false);
|
||||
checkCanVerify("127.0.2.1", "http://127.0.0.1", false);
|
||||
checkCanVerify("127.2.0.1", "http://127.0.0.1", false);
|
||||
checkCanVerify("222.0.0.1", "http://127.0.0.1", false);
|
||||
|
||||
checkCanVerify("", "http://foo.com", true);
|
||||
checkCanVerify("", "http://aaa.bbb.net/xyz", true);
|
||||
checkCanVerify("", "https://127.0.0.1/abc", true);
|
||||
|
||||
checkCanVerify("aaa.bbb.com,xxx.yyy.com, *.jjj.net", "http://aaa.bbb.com/m", true);
|
||||
checkCanVerify("aaa.bbb.com, xxx.yyy.com,*.jjj.net", "http://xxx.yyy.com/n", true);
|
||||
checkCanVerify("aaa.bbb.com,xxx.yyy.com, *.jjj.net", "http://www.jjj.net/o", true);
|
||||
}
|
||||
|
||||
private static void checkCanVerify (String whitelist, String url, boolean expectedToPass)
|
||||
throws MalformedURLException
|
||||
{
|
||||
HostWhitelist w = new HostWhitelist(Arrays.asList(StringUtil.parseStringArray(whitelist)));
|
||||
URL u = new URL(url);
|
||||
boolean passed;
|
||||
|
||||
try {
|
||||
w.verify(u);
|
||||
passed = true;
|
||||
} catch (MalformedURLException e) {
|
||||
passed = false;
|
||||
}
|
||||
|
||||
assertEquals("with whitelist '" + whitelist + "' and URL '" + url + "'",
|
||||
expectedToPass, passed);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user