Filter out access_token when logging exception
When the user uses OAuth for accessing Gerrit, the query parameters contain an access token. This token is used for authenticating the user with Gerrit. It should never be logged as a logged token could be used to impersonate the user the user when sending requests to Gerrit. Previously, we already redacted the access token from Jetty logs, now we move this logic to RestApiServlet to redact it in other environments as well. We'll leave it in the Jetty logger for now as the filtering is cheap and there might be other logging sources besides RestApiServlet. Change-Id: Id7c207f697f53f319ff9f959754a95a3f5f92409
This commit is contained in:
parent
7adc8f25a7
commit
76e588b688
57
java/com/google/gerrit/httpd/restapi/LogRedactUtil.java
Normal file
57
java/com/google/gerrit/httpd/restapi/LogRedactUtil.java
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
// Copyright (C) 2018 The Android Open Source Project
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
// WARNING: NoteDbUpdateManager cares about the package name RestApiServlet lives in.
|
||||||
|
|
||||||
|
package com.google.gerrit.httpd.restapi;
|
||||||
|
|
||||||
|
import static com.google.gerrit.httpd.restapi.RestApiServlet.XD_AUTHORIZATION;
|
||||||
|
|
||||||
|
import com.google.common.annotations.VisibleForTesting;
|
||||||
|
import com.google.common.base.Splitter;
|
||||||
|
import com.google.common.collect.ImmutableSet;
|
||||||
|
import com.google.gerrit.extensions.restapi.Url;
|
||||||
|
import java.util.Iterator;
|
||||||
|
|
||||||
|
public class LogRedactUtil {
|
||||||
|
private static final ImmutableSet<String> REDACT_PARAM = ImmutableSet.of(XD_AUTHORIZATION);
|
||||||
|
|
||||||
|
private LogRedactUtil() {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Redacts sensitive information such as an access token from the query string to make it suitable
|
||||||
|
* for logging.
|
||||||
|
*/
|
||||||
|
@VisibleForTesting
|
||||||
|
public static String redactQueryString(String qs) {
|
||||||
|
StringBuilder b = new StringBuilder();
|
||||||
|
for (String kvPair : Splitter.on('&').split(qs)) {
|
||||||
|
Iterator<String> i = Splitter.on('=').limit(2).split(kvPair).iterator();
|
||||||
|
String key = i.next();
|
||||||
|
if (b.length() > 0) {
|
||||||
|
b.append('&');
|
||||||
|
}
|
||||||
|
b.append(key);
|
||||||
|
if (i.hasNext()) {
|
||||||
|
b.append('=');
|
||||||
|
if (REDACT_PARAM.contains(Url.decode(key))) {
|
||||||
|
b.append('*');
|
||||||
|
} else {
|
||||||
|
b.append(i.next());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return b.toString();
|
||||||
|
}
|
||||||
|
}
|
@ -1408,10 +1408,9 @@ public class RestApiServlet extends HttpServlet {
|
|||||||
Throwable err, HttpServletRequest req, HttpServletResponse res) throws IOException {
|
Throwable err, HttpServletRequest req, HttpServletResponse res) throws IOException {
|
||||||
String uri = req.getRequestURI();
|
String uri = req.getRequestURI();
|
||||||
if (!Strings.isNullOrEmpty(req.getQueryString())) {
|
if (!Strings.isNullOrEmpty(req.getQueryString())) {
|
||||||
uri += "?" + req.getQueryString();
|
uri += "?" + LogRedactUtil.redactQueryString(req.getQueryString());
|
||||||
}
|
}
|
||||||
logger.atSevere().withCause(err).log("Error in %s %s", req.getMethod(), uri);
|
logger.atSevere().withCause(err).log("Error in %s %s", req.getMethod(), uri);
|
||||||
|
|
||||||
if (!res.isCommitted()) {
|
if (!res.isCommitted()) {
|
||||||
res.reset();
|
res.reset();
|
||||||
return replyError(req, res, SC_INTERNAL_SERVER_ERROR, "Internal server error", err);
|
return replyError(req, res, SC_INTERNAL_SERVER_ERROR, "Internal server error", err);
|
||||||
|
@ -14,18 +14,12 @@
|
|||||||
|
|
||||||
package com.google.gerrit.pgm.http.jetty;
|
package com.google.gerrit.pgm.http.jetty;
|
||||||
|
|
||||||
import static com.google.gerrit.httpd.restapi.RestApiServlet.XD_AUTHORIZATION;
|
|
||||||
|
|
||||||
import com.google.common.annotations.VisibleForTesting;
|
|
||||||
import com.google.common.base.Splitter;
|
|
||||||
import com.google.common.base.Strings;
|
import com.google.common.base.Strings;
|
||||||
import com.google.common.collect.ImmutableSet;
|
|
||||||
import com.google.gerrit.extensions.restapi.Url;
|
|
||||||
import com.google.gerrit.httpd.GetUserFilter;
|
import com.google.gerrit.httpd.GetUserFilter;
|
||||||
|
import com.google.gerrit.httpd.restapi.LogRedactUtil;
|
||||||
import com.google.gerrit.server.util.SystemLog;
|
import com.google.gerrit.server.util.SystemLog;
|
||||||
import com.google.gerrit.server.util.time.TimeUtil;
|
import com.google.gerrit.server.util.time.TimeUtil;
|
||||||
import com.google.inject.Inject;
|
import com.google.inject.Inject;
|
||||||
import java.util.Iterator;
|
|
||||||
import org.apache.log4j.AsyncAppender;
|
import org.apache.log4j.AsyncAppender;
|
||||||
import org.apache.log4j.Level;
|
import org.apache.log4j.Level;
|
||||||
import org.apache.log4j.Logger;
|
import org.apache.log4j.Logger;
|
||||||
@ -39,7 +33,6 @@ import org.eclipse.jetty.util.component.AbstractLifeCycle;
|
|||||||
class HttpLog extends AbstractLifeCycle implements RequestLog {
|
class HttpLog extends AbstractLifeCycle implements RequestLog {
|
||||||
private static final Logger log = Logger.getLogger(HttpLog.class);
|
private static final Logger log = Logger.getLogger(HttpLog.class);
|
||||||
private static final String LOG_NAME = "httpd_log";
|
private static final String LOG_NAME = "httpd_log";
|
||||||
private static final ImmutableSet<String> REDACT_PARAM = ImmutableSet.of(XD_AUTHORIZATION);
|
|
||||||
|
|
||||||
interface HttpLogFactory {
|
interface HttpLogFactory {
|
||||||
HttpLog get();
|
HttpLog get();
|
||||||
@ -87,8 +80,9 @@ class HttpLog extends AbstractLifeCycle implements RequestLog {
|
|||||||
);
|
);
|
||||||
|
|
||||||
String uri = req.getRequestURI();
|
String uri = req.getRequestURI();
|
||||||
uri = redactQueryString(uri, req.getQueryString());
|
if (!Strings.isNullOrEmpty(req.getQueryString())) {
|
||||||
|
uri += "?" + LogRedactUtil.redactQueryString(req.getQueryString());
|
||||||
|
}
|
||||||
String user = (String) req.getAttribute(GetUserFilter.USER_ATTR_KEY);
|
String user = (String) req.getAttribute(GetUserFilter.USER_ATTR_KEY);
|
||||||
if (user != null) {
|
if (user != null) {
|
||||||
event.setProperty(P_USER, user);
|
event.setProperty(P_USER, user);
|
||||||
@ -106,31 +100,6 @@ class HttpLog extends AbstractLifeCycle implements RequestLog {
|
|||||||
async.append(event);
|
async.append(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
@VisibleForTesting
|
|
||||||
static String redactQueryString(String uri, String qs) {
|
|
||||||
if (Strings.isNullOrEmpty(qs)) {
|
|
||||||
return uri;
|
|
||||||
}
|
|
||||||
|
|
||||||
StringBuilder b = new StringBuilder(uri);
|
|
||||||
boolean first = true;
|
|
||||||
for (String kvPair : Splitter.on('&').split(qs)) {
|
|
||||||
Iterator<String> i = Splitter.on('=').limit(2).split(kvPair).iterator();
|
|
||||||
String key = i.next();
|
|
||||||
b.append(first ? '?' : '&').append(key);
|
|
||||||
first = false;
|
|
||||||
if (i.hasNext()) {
|
|
||||||
b.append('=');
|
|
||||||
if (REDACT_PARAM.contains(Url.decode(key))) {
|
|
||||||
b.append('*');
|
|
||||||
} else {
|
|
||||||
b.append(i.next());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return b.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void set(LoggingEvent event, String key, String val) {
|
private static void set(LoggingEvent event, String key, String val) {
|
||||||
if (val != null && !val.isEmpty()) {
|
if (val != null && !val.isEmpty()) {
|
||||||
event.setProperty(key, val);
|
event.setProperty(key, val);
|
||||||
|
@ -0,0 +1,35 @@
|
|||||||
|
// Copyright (C) 2017 The Android Open Source Project
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package com.google.gerrit.httpd.restapi;
|
||||||
|
|
||||||
|
import static com.google.common.truth.Truth.assertThat;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
public class HttpLogRedactTest {
|
||||||
|
@Test
|
||||||
|
public void redactAuth() {
|
||||||
|
assertThat(LogRedactUtil.redactQueryString("query=status:open")).isEqualTo("query=status:open");
|
||||||
|
|
||||||
|
assertThat(LogRedactUtil.redactQueryString("query=status:open&access_token=foo"))
|
||||||
|
.isEqualTo("query=status:open&access_token=*");
|
||||||
|
|
||||||
|
assertThat(LogRedactUtil.redactQueryString("access_token=foo")).isEqualTo("access_token=*");
|
||||||
|
|
||||||
|
assertThat(
|
||||||
|
LogRedactUtil.redactQueryString("query=status:open&access_token=foo&access_token=bar"))
|
||||||
|
.isEqualTo("query=status:open&access_token=*&access_token=*");
|
||||||
|
}
|
||||||
|
}
|
@ -1,46 +0,0 @@
|
|||||||
// Copyright (C) 2017 The Android Open Source Project
|
|
||||||
//
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
// you may not use this file except in compliance with the License.
|
|
||||||
// You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
// See the License for the specific language governing permissions and
|
|
||||||
// limitations under the License.
|
|
||||||
|
|
||||||
package com.google.gerrit.pgm.http.jetty;
|
|
||||||
|
|
||||||
import static com.google.common.truth.Truth.assertThat;
|
|
||||||
|
|
||||||
import org.junit.Test;
|
|
||||||
|
|
||||||
public class HttpLogRedactTest {
|
|
||||||
@Test
|
|
||||||
public void includeQueryString() {
|
|
||||||
assertThat(HttpLog.redactQueryString("/changes/", null)).isEqualTo("/changes/");
|
|
||||||
assertThat(HttpLog.redactQueryString("/changes/", "")).isEqualTo("/changes/");
|
|
||||||
assertThat(HttpLog.redactQueryString("/changes/", "x")).isEqualTo("/changes/?x");
|
|
||||||
assertThat(HttpLog.redactQueryString("/changes/", "x=y")).isEqualTo("/changes/?x=y");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void redactAuth() {
|
|
||||||
assertThat(HttpLog.redactQueryString("/changes/", "query=status:open"))
|
|
||||||
.isEqualTo("/changes/?query=status:open");
|
|
||||||
|
|
||||||
assertThat(HttpLog.redactQueryString("/changes/", "query=status:open&access_token=foo"))
|
|
||||||
.isEqualTo("/changes/?query=status:open&access_token=*");
|
|
||||||
|
|
||||||
assertThat(HttpLog.redactQueryString("/changes/", "access_token=foo"))
|
|
||||||
.isEqualTo("/changes/?access_token=*");
|
|
||||||
|
|
||||||
assertThat(
|
|
||||||
HttpLog.redactQueryString(
|
|
||||||
"/changes/", "query=status:open&access_token=foo&access_token=bar"))
|
|
||||||
.isEqualTo("/changes/?query=status:open&access_token=*&access_token=*");
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user