diff --git a/fiat-api/src/main/java/com/netflix/spinnaker/fiat/shared/FiatAccessDeniedExceptionHandler.java b/fiat-api/src/main/java/com/netflix/spinnaker/fiat/shared/FiatAccessDeniedExceptionHandler.java index 1a4d37177..5028c6079 100644 --- a/fiat-api/src/main/java/com/netflix/spinnaker/fiat/shared/FiatAccessDeniedExceptionHandler.java +++ b/fiat-api/src/main/java/com/netflix/spinnaker/fiat/shared/FiatAccessDeniedExceptionHandler.java @@ -19,8 +19,6 @@ import com.netflix.spinnaker.kork.api.exceptions.AccessDeniedDetails; import com.netflix.spinnaker.kork.web.exceptions.ExceptionMessageDecorator; import java.io.IOException; -import java.util.Enumeration; -import java.util.HashMap; import java.util.Map; import java.util.StringJoiner; import javax.servlet.http.HttpServletRequest; @@ -38,6 +36,7 @@ public class FiatAccessDeniedExceptionHandler { private final Logger log = LoggerFactory.getLogger(getClass()); private final DefaultErrorAttributes defaultErrorAttributes = new DefaultErrorAttributes(); + private final HeadersRedactor headersRedactor = new HeadersRedactor(); private final ExceptionMessageDecorator exceptionMessageDecorator; @@ -51,7 +50,7 @@ public void handleAccessDeniedException( throws IOException { storeException(request, response, e); - Map headers = requestHeaders(request); + Map headers = headersRedactor.getRedactedHeaders(request); log.error( "Encountered exception while processing request {}:{} with headers={}", @@ -105,20 +104,6 @@ private void defaultErrorDecoration( } } - private Map requestHeaders(HttpServletRequest request) { - Map headers = new HashMap<>(); - - if (request.getHeaderNames() != null) { - for (Enumeration h = request.getHeaderNames(); h.hasMoreElements(); ) { - String headerName = h.nextElement(); - String headerValue = request.getHeader(headerName); - headers.put(headerName, headerValue); - } - } - - return headers; - } - private void storeException( HttpServletRequest request, HttpServletResponse response, Exception ex) { // store exception as an attribute of HttpServletRequest such that it can be referenced by diff --git a/fiat-api/src/main/java/com/netflix/spinnaker/fiat/shared/HeadersRedactor.java b/fiat-api/src/main/java/com/netflix/spinnaker/fiat/shared/HeadersRedactor.java new file mode 100644 index 000000000..550838946 --- /dev/null +++ b/fiat-api/src/main/java/com/netflix/spinnaker/fiat/shared/HeadersRedactor.java @@ -0,0 +1,49 @@ +/* + * Copyright 2023 Armory, Inc + * + * 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.netflix.spinnaker.fiat.shared; + +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Map; +import javax.servlet.http.HttpServletRequest; + +class HeadersRedactor { + + private static final String WHITELIST_HEADER_PREFIX = "X-"; + private static final String SECRET_DATA_VALUE = "**REDACTED**"; + + public Map getRedactedHeaders(HttpServletRequest request) { + Map headers = new HashMap<>(); + + if (request.getHeaderNames() != null) { + for (Enumeration h = request.getHeaderNames(); h.hasMoreElements(); ) { + String headerName = h.nextElement(); + String headerValue = getRedactedHeaderValue(headerName, request.getHeader(headerName)); + headers.put(headerName, headerValue); + } + } + return headers; + } + + private String getRedactedHeaderValue(String headerName, String headerValue) { + if (!headerName.startsWith(WHITELIST_HEADER_PREFIX)) { + return SECRET_DATA_VALUE; + } else { + return headerValue; + } + } +} diff --git a/fiat-api/src/test/java/com/netflix/spinnaker/fiat/shared/HeadersRedactorTest.java b/fiat-api/src/test/java/com/netflix/spinnaker/fiat/shared/HeadersRedactorTest.java new file mode 100644 index 000000000..d49013f3a --- /dev/null +++ b/fiat-api/src/test/java/com/netflix/spinnaker/fiat/shared/HeadersRedactorTest.java @@ -0,0 +1,44 @@ +/* + * Copyright 2023 Armory, Inc + * + * 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.netflix.spinnaker.fiat.shared; + +import static org.junit.jupiter.api.Assertions.*; + +import java.util.Map; +import org.junit.jupiter.api.Test; +import org.springframework.mock.web.MockHttpServletRequest; + +class HeadersRedactorTest { + + MockHttpServletRequest request = new MockHttpServletRequest(); + HeadersRedactor unit = new HeadersRedactor(); + + @Test + public void verifyNoSecretDataIsShownTest() { + request.addHeader("Content-Type", "text/html"); + request.addHeader("Authorization", "bearer token"); + request.addHeader("Proxy-Authorization", "bearer token"); + request.addHeader("X-Frame-Options", "DENY"); + + Map result = unit.getRedactedHeaders(request); + + assertEquals("**REDACTED**", result.get("Content-Type")); + assertEquals("**REDACTED**", result.get("Authorization")); + assertEquals("**REDACTED**", result.get("Proxy-Authorization")); + assertEquals("DENY", result.get("X-Frame-Options")); + } +}