Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 23 additions & 1 deletion query/api-src/org/labkey/remoteapi/RemoteConnections.java
Original file line number Diff line number Diff line change
Expand Up @@ -92,9 +92,10 @@ public static boolean createOrEditRemoteConnection(RemoteConnectionForm remoteCo
}

// validate the url string and connection
URL urlObj;
try
{
URL urlObj = new URL(url);
urlObj = new URL(url);
URLConnection conn = urlObj.openConnection();
conn.connect();
}
Expand Down Expand Up @@ -155,6 +156,13 @@ public static boolean createOrEditRemoteConnection(RemoteConnectionForm remoteCo
if (CONNECTION_KIND_QUERY.equals(connectionKind))
singleConnectionMap.put(RemoteConnections.FIELD_CONTAINER, folderPath);
singleConnectionMap.save();

if (isCleartextHttpUrl(urlObj))
{
// Log the host only — interpolating the full URL could leak credentials embedded in userinfo (e.g., http://user:pass@host/).
LOG.warn("Remote connection '{}' is configured with a cleartext http:// URL (host: {}). Credentials will be sent unencrypted. Use https:// instead.", newName, urlObj.getHost());
}

return true;
}

Expand All @@ -164,6 +172,11 @@ public static String getBriefMessage(Throwable t)
return t.getMessage() == null ? t.getClass().getSimpleName() : t.getMessage();
}

public static boolean isCleartextHttpUrl(@NotNull URL url)
{
return "http".equalsIgnoreCase(url.getProtocol());
}

public static boolean deleteRemoteConnection(RemoteConnectionForm remoteConnectionForm, Container container)
{
String name = remoteConnectionForm.getConnectionName();
Expand Down Expand Up @@ -346,5 +359,14 @@ public void testGetBriefMessage()
assertEquals("boom", getBriefMessage(new IOException("boom")));
assertEquals("IOException", getBriefMessage(new IOException()));
}

@Test
public void testIsCleartextHttpUrl() throws MalformedURLException
{
assertTrue("http:// must be detected as cleartext", isCleartextHttpUrl(new URL("http://example.com/labkey")));
assertTrue("scheme match is case-insensitive", isCleartextHttpUrl(new URL("HTTP://example.com/labkey")));
assertFalse("https:// is encrypted", isCleartextHttpUrl(new URL("https://example.com/labkey")));
assertFalse("HTTPS:// is encrypted", isCleartextHttpUrl(new URL("HTTPS://example.com/labkey")));
}
}
}
22 changes: 21 additions & 1 deletion query/src/org/labkey/query/view/createRemoteConnection.jsp
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@
<%@ page import="org.labkey.api.view.JspView" %>
<%@ page import="org.labkey.query.controllers.QueryController" %>
<%@ page import="org.labkey.remoteapi.RemoteConnections" %>
<%@ page import="java.net.MalformedURLException" %>
<%@ page import="java.net.URL" %>
<%@ page extends="org.labkey.api.jsp.FormPage" %>
<%@ taglib prefix="labkey" uri="http://www.labkey.org/taglib" %>
<%
Expand All @@ -35,14 +37,32 @@
String connectionKind = remoteConnectionForm.getConnectionKind();
boolean editConnection = StringUtils.isNotEmpty(name);
String nameToShow = editConnection ? name : remoteConnectionForm.getNewConnectionName();
boolean usingCleartextHttp = false;
if (url != null)
{
try
{
usingCleartextHttp = RemoteConnections.isCleartextHttpUrl(new URL(url));
}
catch (MalformedURLException ignored)
{
// Malformed URLs are surfaced by server-side validation in createOrEditRemoteConnection; suppress here.
}
}
%>
<p><%=h(RemoteConnections.MANAGEMENT_PAGE_INSTRUCTIONS)%></p>
<labkey:errors/>
<% if (usingCleartextHttp) { %>
<p class="labkey-warning-messages">
Warning: this connection uses an http:// URL. The configured user and password will be sent to the remote
server in cleartext on every ETL run. Use https:// so credentials are encrypted in transit.
</p>
<% } %>
<br>
<labkey:form name="editConnection" action="<%=QueryController.RemoteQueryConnectionUrls.urlSaveRemoteConnection(c) %>" method="post" layout="horizontal">
<labkey:input type="text" label="Connection Name *" name="newConnectionName" id="newConnectionName" size="50" value="<%=nameToShow%>" isRequired="true"/>
<labkey:input type="text" label="Server URL *" name="url" id="url" size="50" value="<%=url%>" forceSmallContext="true"
contextContent="Enter in the server URL. Include both the protocol (http:// or https://) and a context path if necessary. As an example, http://localhost:8080/labkey would be a valid name."
contextContent="Enter the server URL, including the protocol and any context path. As an example, https://localhost:8080/labkey would be a valid name."
isRequired="true"/>
<labkey:input type="text" label="User *" name="userEmail" id="userEmail" size="50" value="<%=userEmail%>" isRequired="true"/>
<labkey:input type="password" label="Password *" name="password" id="password" size="50" isRequired="true"/>
Expand Down
37 changes: 36 additions & 1 deletion query/src/org/labkey/query/view/manageRemoteConnections.jsp
Original file line number Diff line number Diff line change
Expand Up @@ -15,25 +15,60 @@
* limitations under the License.
*/
%>
<%@ page import="org.apache.commons.lang3.StringUtils" %>
<%@ page import="org.labkey.api.data.Container" %>
<%@ page import="org.labkey.api.security.permissions.AdminOperationsPermission" %>
<%@ page import="org.labkey.api.settings.AppProps" %>
<%@ page import="org.labkey.api.view.HttpView" %>
<%@ page import="org.labkey.api.view.JspView" %>
<%@ page import="org.labkey.query.controllers.QueryController.RemoteQueryConnectionUrls" %>
<%@ page import="org.labkey.remoteapi.RemoteConnections" %>
<%@ page import="java.net.MalformedURLException" %>
<%@ page import="java.net.URL" %>
<%@ page import="java.util.ArrayList" %>
<%@ page import="java.util.List" %>
<%@ page import="java.util.Map" %>
<%@ page extends="org.labkey.api.jsp.FormPage" %>
<%@ taglib prefix="labkey" uri="http://www.labkey.org/taglib" %>
<p><%=h(RemoteConnections.MANAGEMENT_PAGE_INSTRUCTIONS)%></p>
<%
Container c = getContainer();
boolean hasAdminOpsPerm = c.hasPermission(getUser(), AdminOperationsPermission.class);

Map<String, String> connectionMap = ((JspView<Map<String,String>>) HttpView.currentView()).getModelBean();

List<String> cleartextConnections = new ArrayList<>();
if (connectionMap != null)
{
for (String connectionName : connectionMap.values())
{
Map<String, String> props = RemoteConnections.getRemoteConnection(
RemoteConnections.REMOTE_QUERY_CONNECTIONS_CATEGORY, connectionName, c);
String connUrl = props.get(RemoteConnections.FIELD_URL);
if (connUrl != null)
{
try
{
if (RemoteConnections.isCleartextHttpUrl(new URL(connUrl)))
cleartextConnections.add(connectionName);
}
catch (MalformedURLException ignored)
{
// Malformed URLs are surfaced by server-side validation in createOrEditRemoteConnection; suppress here.
}
}
}
}
%>

<% if (!cleartextConnections.isEmpty()) { %>
<p class="labkey-warning-messages">
Warning: one or more remote connections use a cleartext http:// URL: <%=h(StringUtils.join(cleartextConnections, ", "))%>. Credentials are sent unencrypted on every ETL run.
</p>
<% } %>

<br>
<%
Map<String, String> connectionMap = ((JspView<Map<String,String>>) HttpView.currentView()).getModelBean();
if (connectionMap == null)
{ %>
<p style="color: red">EncryptionKey has not been specified in <%= h(AppProps.getInstance().getWebappConfigurationFilename()) %>, or its value no longer matches key previously in use.</p>
Expand Down