Commit 17c423f added RFC6265 validation when building a new ResponseCookie, but the validation seems too "strict" for the domain attribute-value.
The RFC says : (https://tools.ietf.org/html/rfc6265#section-5.2.3):
>
5.2.3. The Domain Attribute
[...]
If the first character of the attribute-value string is %x2E ("."):Let cookie-domain be the attribute-value without the leading %x2E (".") character.Otherwise:
Let cookie-domain be the entire attribute-value.
And
4.1.2.3. The Domain Attribute
The Domain attribute specifies those hosts to which the cookie will
be sent. For example, if the value of the Domain attribute is
"example.com", the user agent will include the cookie in the Cookie
header when making HTTP requests to example.com, www.example.com, and
www.corp.example.com. (Note that a leading %x2E ("."), if present,
is ignored even though that character is not permitted, but a
trailing %x2E ("."), if present, will cause the user agent to ignore
the attribute.) If the server omits the Domain attribute, the user
agent will return the cookie only to the origin server.
The current implementation of the Rfc6265Utils validateDomain method throws an IllegalArgumentException if the domain attribute-value starts with a . (dot)
If I understand well, the expected behavior should ignore/remove the leading dot instead of throwing an exception. Am I right ?
This seems good for a newbie, may I pick it up?
@puhlerblet, yes.
Commit 17c423f added RFC6265 validation when building a new ResponseCookie, but the validation seems too "strict" for the domain attribute-value.
The RFC says : (https://tools.ietf.org/html/rfc6265#section-5.2.3):
5.2.3. The Domain Attribute
[...]
If the first character of the attribute-value string is %x2E ("."):Let cookie-domain be the attribute-value without the leading %x2E (".") character.Otherwise:
Let cookie-domain be the entire attribute-value.And
4.1.2.3. The Domain Attribute
The Domain attribute specifies those hosts to which the cookie will
be sent. For example, if the value of the Domain attribute is
"example.com", the user agent will include the cookie in the Cookie
header when making HTTP requests to example.com, www.example.com, and
www.corp.example.com. (Note that a leading %x2E ("."), if present,
is ignored even though that character is not permitted, but a
trailing %x2E ("."), if present, will cause the user agent to ignore
the attribute.) If the server omits the Domain attribute, the user
agent will return the cookie only to the origin server.The current implementation of the
Rfc6265Utils validateDomainmethod throws an IllegalArgumentException if the domain attribute-value starts with a . (dot)If I understand well, the expected behavior should ignore/remove the leading dot instead of throwing an exception. Am I right ?
Section 5.2.3 of RFC 6265 is related to the User Agent Requirements and says that a leading %x2E (".") should be removed if present.
Section 4.1.2.3 of RFC 6265, however, is related to the Server Requirements and says that a leading %x2E (".") is not permitted which corresponds to the current implementation Rfc6265Utils validateDomain.
If I understand correctly nothing needs to be changed.
Good point that 5.2 is for user agents. The validation in Rfc6265Utils validateDomain follows the syntax for server requirements in section 4.1 and that does not permit leading ".". Technically since clients are required to ignore the dot, it should be okay anyway, but arguably we should be strict with output and lenient with input. So we'll leave it as is. Thanks for the help @puhlerblet.
@rstoyanchev this behaviour in combination with spring-sleuth 2.1.4 and Spring Webflux 5.2.0 version is unable to retrieve the data from an external service, which is setting the cookie domain value with leading 'dot'.
In this flow the org.springframework.http.client.reactive.ReactorClientHttpResponse class is using ResponseCookie class not in the output data but in the input data flow
If I understood correcly, this would be a strcit validation of Rfc6265Utils for input data and that should be avoided.
Here is the exception I am getting::
2019-10-28 14:34:51.675 ERROR --- [reactor-http-ni] r.n.c.ChannelOperationsHandler []: [id: 0x9b62c9fd, L:/10. - ] Error was received while reading the incoming data. The connection will be closed.
reactor.core.Exceptions$BubblingException: java.lang.IllegalArgumentException: Invalid first/last char in cookie domain: .api.businesshub*******.com
at reactor.core.Exceptions.bubble(Exceptions.java:154)
at reactor.core.publisher.Operators.onErrorDropped(Operators.java:521)
at reactor.core.publisher.MonoFlatMapMany$FlatMapManyMain.onError(MonoFlatMapMany.java:194)
at org.springframework.cloud.sleuth.instrument.reactor.ScopePassingSpanSubscriber.onError(ScopePassingSpanSubscriber.java:104)
at reactor.core.publisher.FluxRetryPredicate$RetryPredicateSubscriber.onError(FluxRetryPredicate.java:101)
at org.springframework.cloud.sleuth.instrument.reactor.ScopePassingSpanSubscriber.onError(ScopePassingSpanSubscriber.java:104)
at reactor.core.publisher.MonoCreate$DefaultMonoSink.success(MonoCreate.java:160)
at reactor.netty.http.client.HttpClientConnect$HttpObserver.onStateChange(HttpClientConnect.java:397)
at reactor.netty.ReactorNetty$CompositeConnectionObserver.onStateChange(ReactorNetty.java:473)
at reactor.netty.resources.PooledConnectionProvider$DisposableAcquire.onStateChange(PooledConnectionProvider.java:525)
at reactor.netty.resources.PooledConnectionProvider$PooledConnection.onStateChange(PooledConnectionProvider.java:434)
at reactor.netty.http.client.HttpClientOperations.onInboundNext(HttpClientOperations.java:522)
at reactor.netty.channel.ChannelOperationsHandler.channelRead(ChannelOperationsHandler.java:91)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:374)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:360)
at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:352)
at io.netty.handler.timeout.IdleStateHandler.channelRead(IdleStateHandler.java:287)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:374)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:360)
at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:352)
at io.netty.handler.codec.MessageToMessageDecoder.channelRead(MessageToMessageDecoder.java:102)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:374)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:360)
at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:352)
at io.netty.channel.CombinedChannelDuplexHandler$DelegatingChannelHandlerContext.fireChannelRead(CombinedChannelDuplexHandler.java:438)
at io.netty.handler.codec.ByteToMessageDecoder.fireChannelRead(ByteToMessageDecoder.java:328)
at io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:302)
at io.netty.channel.CombinedChannelDuplexHandler.channelRead(CombinedChannelDuplexHandler.java:253)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:374)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:360)
at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:352)
at io.netty.handler.ssl.SslHandler.unwrap(SslHandler.java:1475)
at io.netty.handler.ssl.SslHandler.decodeJdkCompatible(SslHandler.java:1224)
at io.netty.handler.ssl.SslHandler.decode(SslHandler.java:1271)
at io.netty.handler.codec.ByteToMessageDecoder.decodeRemovalReentryProtection(ByteToMessageDecoder.java:505)
at io.netty.handler.codec.ByteToMessageDecoder.callDecode(ByteToMessageDecoder.java:444)
at io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:283)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:374)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:360)
at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:352)
at io.netty.handler.proxy.ProxyHandler.channelRead(ProxyHandler.java:255)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:374)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:360)
at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:352)
at io.netty.channel.CombinedChannelDuplexHandler$DelegatingChannelHandlerContext.fireChannelRead(CombinedChannelDuplexHandler.java:438)
at io.netty.channel.CombinedChannelDuplexHandler.channelRead(CombinedChannelDuplexHandler.java:255)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:374)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:360)
at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:352)
at io.netty.channel.DefaultChannelPipeline$HeadContext.channelRead(DefaultChannelPipeline.java:1422)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:374)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:360)
at io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:931)
at io.netty.channel.nio.AbstractNioByteChannel$NioByteUnsafe.read(AbstractNioByteChannel.java:163)
at io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:700)
at io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:635)
at io.netty.channel.nio.NioEventLoop.processSelectedKeys(NioEventLoop.java:552)
at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:514)
at io.netty.util.concurrent.SingleThreadEventExecutor$6.run(SingleThreadEventExecutor.java:1044)
at io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74)
at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
at java.lang.Thread.run(Thread.java:748)
Caused by: java.lang.IllegalArgumentException: Invalid first/last char in cookie domain: .api.businesshub****.com
at org.springframework.http.ResponseCookie$Rfc6265Utils.validateDomain(ResponseCookie.java:378)
at org.springframework.http.ResponseCookie.<init>(ResponseCookie.java:72)
at org.springframework.http.ResponseCookie.<init>(ResponseCookie.java:36)
at org.springframework.http.ResponseCookie$1.build(ResponseCookie.java:253)
at org.springframework.http.client.reactive.ReactorClientHttpResponse.lambda$getCookies$4(ReactorClientHttpResponse.java:110)
at java.util.stream.ForEachOps$ForEachOp$OfRef.accept(ForEachOps.java:184)
at java.util.HashMap$KeySpliterator.forEachRemaining(HashMap.java:1556)
at java.util.stream.ReferencePipeline$Head.forEach(ReferencePipeline.java:580)
at java.util.stream.ReferencePipeline$7$1.accept(ReferencePipeline.java:270)
at java.util.HashMap$ValueSpliterator.forEachRemaining(HashMap.java:1628)
at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:481)
at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:471)
at java.util.stream.ForEachOps$ForEachOp.evaluateSequential(ForEachOps.java:151)
at java.util.stream.ForEachOps$ForEachOp$OfRef.evaluateSequential(ForEachOps.java:174)
at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
at java.util.stream.ReferencePipeline.forEach(ReferencePipeline.java:418)
at org.springframework.http.client.reactive.ReactorClientHttpResponse.getCookies(ReactorClientHttpResponse.java:103)
at org.springframework.web.reactive.function.client.DefaultClientResponse.cookies(DefaultClientResponse.java:104)
at org.springframework.web.reactive.function.client.DefaultClientResponseBuilder.lambda$new$1(DefaultClientResponseBuilder.java:92)
at org.springframework.web.reactive.function.client.DefaultClientResponseBuilder.cookies(DefaultClientResponseBuilder.java:138)
at org.springframework.web.reactive.function.client.DefaultClientResponseBuilder.<init>(DefaultClientResponseBuilder.java:92)
at org.springframework.web.reactive.function.client.ClientResponse.from(ClientResponse.java:214)
at org.springframework.cloud.sleuth.instrument.web.client.TraceExchangeFilterFunction$MonoWebClientTrace$WebClientTracerSubscriber.onNext(TraceWebClientBeanPostProcessor.java:294)
at org.springframework.cloud.sleuth.instrument.web.client.TraceExchangeFilterFunction$MonoWebClientTrace$WebClientTracerSubscriber.onNext(TraceWebClientBeanPostProcessor.java:232)
at org.springframework.cloud.sleuth.instrument.reactor.ScopePassingSpanSubscriber.onNext(ScopePassingSpanSubscriber.java:96)
at reactor.core.publisher.FluxMap$MapSubscriber.onNext(FluxMap.java:114)
at org.springframework.cloud.sleuth.instrument.reactor.ScopePassingSpanSubscriber.onNext(ScopePassingSpanSubscriber.java:96)
at reactor.core.publisher.FluxPeek$PeekSubscriber.onNext(FluxPeek.java:192)
at org.springframework.cloud.sleuth.instrument.reactor.ScopePassingSpanSubscriber.onNext(ScopePassingSpanSubscriber.java:96)
at reactor.core.publisher.FluxPeek$PeekSubscriber.onNext(FluxPeek.java:192)
at org.springframework.cloud.sleuth.instrument.reactor.ScopePassingSpanSubscriber.onNext(ScopePassingSpanSubscriber.java:96)
at reactor.core.publisher.MonoNext$NextSubscriber.onNext(MonoNext.java:76)
at org.springframework.cloud.sleuth.instrument.reactor.ScopePassingSpanSubscriber.onNext(ScopePassingSpanSubscriber.java:96)
at reactor.core.publisher.MonoFlatMapMany$FlatMapManyInner.onNext(MonoFlatMapMany.java:242)
at reactor.core.publisher.Operators$ScalarSubscription.request(Operators.java:2148)
at reactor.core.publisher.MonoFlatMapMany$FlatMapManyMain.onSubscribeInner(MonoFlatMapMany.java:143)
at reactor.core.publisher.MonoFlatMapMany$FlatMapManyInner.onSubscribe(MonoFlatMapMany.java:237)
at reactor.core.publisher.MonoJust.subscribe(MonoJust.java:54)
at reactor.core.publisher.Flux.subscribe(Flux.java:8134)
at reactor.core.publisher.MonoFlatMapMany$FlatMapManyMain.onNext(MonoFlatMapMany.java:188)
at org.springframework.cloud.sleuth.instrument.reactor.ScopePassingSpanSubscriber.onNext(ScopePassingSpanSubscriber.java:96)
at reactor.core.publisher.FluxRetryPredicate$RetryPredicateSubscriber.onNext(FluxRetryPredicate.java:82)
at org.springframework.cloud.sleuth.instrument.reactor.ScopePassingSpanSubscriber.onNext(ScopePassingSpanSubscriber.java:96)
at reactor.core.publisher.MonoCreate$DefaultMonoSink.success(MonoCreate.java:156)
... 55 common frames omitted
The only solution for now is to downgrade to sleuth 2.1.3 version. Could you suggest any solution for this?
Hello,
We have an issue because of this change. We are using an external service hosted at cloudflare which add a cookie in the response __cfuid which include a leading dot in the domain.
https://support.cloudflare.com/hc/en-us/articles/200170156-Understanding-the-Cloudflare-Cookies#12345682
We cannot change how the third party hosts his service neither change how cloudflare deposit its cookie. At the very least, I would expect that such a change would be subject to a feature property to enable or disable this behavior. Would that be acceptable?
Experiencing same issue but for different providers.
Current only solution was to downgrade to 2.1.5.
Please make validation optional or configurable.
It has been removed since, see #23924, and coming in 5.2.2. I will update this ticket as duplicate accordingly.
Most helpful comment
Hello,
We have an issue because of this change. We are using an external service hosted at cloudflare which add a cookie in the response __cfuid which include a leading dot in the domain.
https://support.cloudflare.com/hc/en-us/articles/200170156-Understanding-the-Cloudflare-Cookies#12345682
We cannot change how the third party hosts his service neither change how cloudflare deposit its cookie. At the very least, I would expect that such a change would be subject to a feature property to enable or disable this behavior. Would that be acceptable?