Skip to content

Fix http scheme evaluation and removed origin from evaluation #458

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
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
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,6 @@
import com.google.common.net.InternetDomainName;
import java.net.HttpCookie;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.ArrayList;
Expand Down Expand Up @@ -119,8 +118,6 @@ public class HttpSemanticConventionUtils {
private static final String SLASH = "/";

private static final String HTTP_REQUEST_FORWARDED_ATTRIBUTE = HTTP_REQUEST_FORWARDED.getValue();
private static final String HTTP_REQUEST_ORIGIN =
HttpSemanticConventions.HTTP_REQUEST_ORIGIN.getValue();
private static final String HTTP_RESPONSE_HEADER_PREFIX =
RawSpanConstants.getValue(HTTP_RESPONSE_HEADER) + DOT;
public static final String RESPONSE_COOKIE_HEADER_PREFIX =
Expand Down Expand Up @@ -186,6 +183,7 @@ public class HttpSemanticConventionUtils {
RawSpanConstants.getValue(OT_SPAN_TAG_HTTP_STATUS_CODE),
RawSpanConstants.getValue(HTTP_RESPONSE_STATUS_CODE),
OTelHttpSemanticConventions.HTTP_STATUS_CODE.getValue());
public static final String HTTPS = "https";

/**
* @return attribute keys for http method
Expand Down Expand Up @@ -553,6 +551,24 @@ public static Optional<String> getHttpMethod(Event event) {
return Optional.empty();
}

/**
* Returns the HTTP scheme associated with the given {@link Event}. The scheme is determined as
* follows:
* <ol>
* <li>If the event has an attribute like {@code http.url} that contains a full URL, the scheme
* from that URL is returned if it is https.
* <li>If the scheme from the full URL is not 'https', then other attributes like
* {@code x-forwarded-proto}, {@code forwarded}, {@code http.scheme} are checked. If any of
* these has the value 'https', then that is returned.
* <li>If none of the above steps yield a scheme of 'https', then the scheme from the full URL
* (which should be 'http') is returned.
* <li>If the event does not have a full URL attribute, then the other attributes are checked for
* the scheme.
* </ol>
*
* @param event the event to extract the scheme from
* @return an {@link Optional} containing the scheme if it could be determined, empty otherwise
*/
public static Optional<String> getHttpScheme(Event event) {

Optional<String> scheme = Optional.empty();
Expand All @@ -573,10 +589,11 @@ public static Optional<String> getHttpScheme(Event event) {
if (scheme.get().equalsIgnoreCase("https")) {
return scheme;
}
// 3. else check if there are other attributes that like 'origin', 'x-forwarded-proto' etc
// 3. else check if there are other attributes that like 'x-forwarded-proto', forwarded,
// http.scheme etc.
// that have the actual scheme
// this is for scenarios where a LB fronts the service and terminates SSL.
// If this scheme is 'https' then return this instead
// this is for scenarios where a LB fronts the service and terminates SSL.
// If this scheme is 'https' then return this instead
else {
schemeFromOtherRawAttributes = getHttpSchemeFromRawAttributes(event);
if (schemeFromOtherRawAttributes.isPresent()
Expand All @@ -602,46 +619,37 @@ private static Optional<String> getHttpSchemeFromRawAttributes(Event event) {

private static Optional<String> getHttpSchemeFromRawAttributes(
Map<String, AttributeValue> attributeValueMap) {
String scheme = null;
// dealing with the Forwarded header separately as it may have
// more info than just the protocol
// 1. Check 'forwarded' header
AttributeValue httpRequestForwardedAttributeValue =
attributeValueMap.get(HTTP_REQUEST_FORWARDED_ATTRIBUTE);
if (httpRequestForwardedAttributeValue != null
&& !StringUtils.isEmpty(httpRequestForwardedAttributeValue.getValue())) {
String schemeValue = httpRequestForwardedAttributeValue.getValue();
Optional<String> optionalExtractedProtoValue = getProtocolFromForwarded(schemeValue);
&& StringUtils.isNotEmpty(httpRequestForwardedAttributeValue.getValue())) {
Optional<String> optionalExtractedProtoValue =
getProtocolFromForwarded(httpRequestForwardedAttributeValue.getValue());
if (optionalExtractedProtoValue.isPresent()) {
return optionalExtractedProtoValue;
scheme = optionalExtractedProtoValue.get();
if (HTTPS.equalsIgnoreCase(scheme)) {
return Optional.of(HTTPS);
}
}
}

AttributeValue httpRequestOriginValue = attributeValueMap.get(HTTP_REQUEST_ORIGIN);
if (httpRequestOriginValue != null && !StringUtils.isEmpty(httpRequestOriginValue.getValue())) {
String origin = httpRequestOriginValue.getValue();
/** Syntax: Origin: null Origin: <scheme>://<hostname> Origin: <scheme>://<hostname>:<port> */
if (StringUtils.isNotEmpty(origin)) {
try {
String scheme = new URI(origin).getScheme();
// handle the case where the value of origin is the "null" string
if (StringUtils.isNotEmpty(scheme)) {
return Optional.of(scheme);
}
} catch (Exception e) {
LOGGER.debug(
"On extracting scheme, received an invalid origin header: {}, {}",
origin,
e.getMessage());
// 2. Check other scheme-related attributes like x-forwarded-proto, http.scheme, etc.
for (String schemeAttr : SCHEME_ATTRIBUTES) {
AttributeValue attrValue = attributeValueMap.get(schemeAttr);
if (attrValue != null && StringUtils.isNotEmpty(attrValue.getValue())) {
scheme = attrValue.getValue();
if (HTTPS.equalsIgnoreCase(scheme)) {
return Optional.of(HTTPS);
}
}
}

for (String scheme : SCHEME_ATTRIBUTES) {
if (attributeValueMap.get(scheme) != null
&& !StringUtils.isEmpty(attributeValueMap.get(scheme).getValue())) {
return Optional.of(attributeValueMap.get(scheme).getValue());
}
}
return Optional.empty();
// 3. Return scheme if found
return Optional.ofNullable(scheme);
}

private static Optional<String> getProtocolFromForwarded(String value) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -368,8 +368,8 @@ public void testGetHttpScheme() {
event =
createMockEventWithAttribute(
HttpSemanticConventions.HTTP_REQUEST_FORWARDED.getValue(),
"by= random ; proto = https; for=modnar");
assertEquals(Optional.of("https"), HttpSemanticConventionUtils.getHttpScheme(event));
"by= random ; proto=http; for=modnar");
assertEquals(Optional.of("http"), HttpSemanticConventionUtils.getHttpScheme(event));

event =
createMockEventWithAttribute(
Expand All @@ -394,66 +394,106 @@ public void testGetHttpScheme() {
when(e.getEnrichedAttributes()).thenReturn(null);
assertEquals(Optional.of("https"), HttpSemanticConventionUtils.getHttpScheme(e));

// when only origin header exists expect whatever is the scheme of the origin header
// when origin header has 'https'
event =
createMockEventWithAttribute(
HttpSemanticConventions.HTTP_REQUEST_ORIGIN.getValue(), "https://abc.xyz");
// when http url and X-forwarded proto header exists with scheme https then expect https
event = mock(Event.class);
when(event.getAttributes())
.thenReturn(
Attributes.newBuilder()
.setAttributeMap(
Map.of(
HttpSemanticConventions.HTTP_REQUEST_X_FORWARDED_PROTO.getValue(),
AttributeValue.newBuilder().setValue("https").build(),
RawSpanConstants.getValue(Http.HTTP_URL),
AttributeValue.newBuilder()
.setValue("http://abc.xyz.ai/apis/5673/events?a1=v1&a2=v2")
.build()))
.build());
assertEquals(Optional.of("https"), HttpSemanticConventionUtils.getHttpScheme(event));

// when origin header has 'http'
event =
createMockEventWithAttribute(
HttpSemanticConventions.HTTP_REQUEST_ORIGIN.getValue(), "http://abc.xyz");
assertEquals(Optional.of("http"), HttpSemanticConventionUtils.getHttpScheme(event));

// when http url and origin header exists with scheme https then expect https
// when http url and forwarded header exists with scheme https then expect https
event = mock(Event.class);
when(event.getAttributes())
.thenReturn(
Attributes.newBuilder()
.setAttributeMap(
Map.of(
HttpSemanticConventions.HTTP_REQUEST_ORIGIN.getValue(),
AttributeValue.newBuilder().setValue("https://abc.xyz.ai").build(),
HttpSemanticConventions.HTTP_REQUEST_FORWARDED.getValue(),
AttributeValue.newBuilder().setValue("proto=https").build(),
RawSpanConstants.getValue(Http.HTTP_URL),
AttributeValue.newBuilder()
.setValue("http://abc.xyz.ai/apis/5673/events?a1=v1&a2=v2")
.build()))
.build());
assertEquals(Optional.of("https"), HttpSemanticConventionUtils.getHttpScheme(event));

// when http url and origin header exists with scheme http then expect http
// when http url exists with scheme http then expect http
event = mock(Event.class);
when(event.getAttributes())
.thenReturn(
Attributes.newBuilder()
.setAttributeMap(
Map.of(
HttpSemanticConventions.HTTP_REQUEST_ORIGIN.getValue(),
AttributeValue.newBuilder().setValue("http://abc.xyz.ai").build(),
RawSpanConstants.getValue(Http.HTTP_URL),
AttributeValue.newBuilder()
.setValue("http://abc.xyz.ai/apis/5673/events?a1=v1&a2=v2")
.build()))
.build());
assertEquals(Optional.of("http"), HttpSemanticConventionUtils.getHttpScheme(event));

// when http url and origin header is null string then expect scheme of the url
// when http url exists with scheme https but other attributes http only.
event = mock(Event.class);
when(event.getAttributes())
.thenReturn(
Attributes.newBuilder()
.setAttributeMap(
Map.of(
HttpSemanticConventions.HTTP_REQUEST_ORIGIN.getValue(),
AttributeValue.newBuilder().setValue("null").build(),
RawSpanConstants.getValue(Http.HTTP_URL),
AttributeValue.newBuilder()
.setValue("http://abc.xyz.ai/apis/5673/events?a1=v1&a2=v2")
.build(),
HTTP_SCHEME.getValue(),
AttributeValue.newBuilder().setValue("https").build(),
HttpSemanticConventions.HTTP_REQUEST_X_FORWARDED_PROTO.getValue(),
AttributeValue.newBuilder().setValue("http").build(),
HttpSemanticConventions.HTTP_REQUEST_FORWARDED.getValue(),
AttributeValue.newBuilder()
.setValue("by= random ;proto = ; for=modnar")
.build()))
.build());
assertEquals(Optional.of("http"), HttpSemanticConventionUtils.getHttpScheme(event));
assertEquals(Optional.of("https"), HttpSemanticConventionUtils.getHttpScheme(event));

// when http url not present and forwarded header is https and others are http then scheme
// should be https.
event = mock(Event.class);
when(event.getAttributes())
.thenReturn(
Attributes.newBuilder()
.setAttributeMap(
Map.of(
HTTP_SCHEME.getValue(),
AttributeValue.newBuilder().setValue("http").build(),
HttpSemanticConventions.HTTP_REQUEST_X_FORWARDED_PROTO.getValue(),
AttributeValue.newBuilder().setValue("http").build(),
HttpSemanticConventions.HTTP_REQUEST_FORWARDED.getValue(),
AttributeValue.newBuilder()
.setValue("by= random ;proto = https; for=modnar")
.build()))
.build());
assertEquals(Optional.of("https"), HttpSemanticConventionUtils.getHttpScheme(event));

// when http url is present with scheme https
event = mock(Event.class);
when(event.getAttributes())
.thenReturn(
Attributes.newBuilder()
.setAttributeMap(
Map.of(
RawSpanConstants.getValue(Http.HTTP_URL),
AttributeValue.newBuilder()
.setValue("https://abc.xyz.ai/apis/5673/events?a1=v1&a2=v2")
.build()))
.build());
assertEquals(Optional.of("https"), HttpSemanticConventionUtils.getHttpScheme(event));
}

@Test
Expand Down
Loading