Can't remove just one Set-Cookie header in HAProxy 2.2 - haproxy

A response from our web server might set a few cookies:
Set-Cookie: JSESSIONID=123;Secure
Set-Cookie: JSESSIONIDSSO=234;Secure
In HAProxy, we want to remove one of these. With 1.7, we could do:
rspidel ^Set-Cookie:\ JSESSIONIDSSO=
But we are upgrading to HAProxy 2.2, where rspidel has been removed. The replacement (http-response del-header) does not handle regex. How can I remove just the one Set-Cookie header?

Response edited because I misunderstood the question
I believe you are looking for http-response replace-header
http-response replace-header <name> <regex-match> <replace-fmt>
[ { if | unless } <condition> ]
This works like "http-request replace-header" except that it works on the
server's response instead of the client's request.
Example:
http-response replace-header Set-Cookie (C=[^;]*);(.*) \1;ip=%bi;\2
# applied to:
Set-Cookie: C=1; expires=Tue, 14-Jun-2016 01:40:45 GMT
# outputs:
Set-Cookie: C=1;ip=192.168.1.20; expires=Tue, 14-Jun-2016 01:40:45 GMT
# assuming the backend IP is 192.168.1.20.
Documentation here
You can possibly do something like this:
http-response replace-header Set-Cookie (JSESSIONIDSSO=.*) \2
Although, that would leave you with an empty Set-Cookie header.
Another option, would be to replace the value JSESSIONIDSSO with something else. i.e.
http-response replace-header Set-Cookie (JSESSIONIDSSO=[^;]*);(.*) None=None;\2

Related

How do I secure cookies in HAProxy 2.2+ using an `http-response` line?

I'm upgrading from HAProxy 1.8 to 2.2 and the command reqirep has been deprecated and removed. I used this previously to automatically add Secure to cookies that weren't previously secure. I want to use the new http-response syntax.
My old code looks like this:
rspirep ^(set-cookie:\ (?:(?!(\ Secure|ASPXAUTH=)).)*)$ \1;\ Secure
This adds ; Secure to any cookie header that doesn't contain Secure or ASPXAUTH=.
I'd like to do the same thing with one of the modern http-response commands.
Here's my initial translation:
http-request replace-header Set-Cookie (.*) %[src];\ Secure if { hdr_reg(Set-Cookie) -i (?!(\ Secure|ASPXAUTH=)) }
# Replace the "Set-Cookie" header
# That contains any value
# With the initial value with "; Secure" appended to the end
# If the cookie doesn't contain " Secure" or "ASPXAUTH=", ignoring case
Is this the right approach? Have you done this successfully?
We ended up with this as a solution. It's not perfect because it will only look for Secure modifier on the end of the Set-Cookie line but it works for what we need.
http-response replace-header Set-Cookie ^((?:.(?!\ [Ss]ecure))*)$ \1;\ Secure

Remove not needed cookies from the back end request on haproxy

My back-end website is a very small IOT application. Sometimes when a request is send to the back-end server it returns a 404 because the header is to big. This is caused by the cookies send to the server.
Is it possible to send only the needed cookie to the back-end server by rewriting the header on the Haproxy ?
I’m looking for something that rewrites the header for cookies from
sesion=xyz; othercookie=123
to
sesion=xyz
You can use http-request replace-header or http-request replace-value to manipulate the Cookie header for the values you need. This is available in haproxy since version 1.5.
Example from the haproxy documentation:
http-request replace-header Cookie foo=([^;]);(.) foo=\1;ip=%bi;\2
applied to:
Cookie: foo=foobar; expires=Tue, 14-Jun-2016 01:40:45 GMT;
outputs:
Cookie: foo=foobar;ip=192.168.1.20; expires=Tue, 14-Jun-2016 01:40:45 GMT;
assuming the backend IP is 192.168.1.20
haproxy http-request documentation

404 redirect to another server/domain

I'm looking for a solution with redirects to another domain if the response from HTTP server was 404.
acl not_found status 404
acl found_ceph status 200
use_backend minio_s3 rsprep ^HTTP/1.1\ 404\ (.*)$ HTTP/1.1\ 302\ Found\nLocation:\ / if not_found
use_backend ceph if found_ceph
But still not working, this rule goes to minio_s3 backend.
Thank you for you advice.
When the response from this backend has status 404, first add a Location header that will send the browser to example.com with the original URI intact, then set the status code to 302 so the browser executes a redirect.
backend my-backend
mode http
server my-server 203.0.113.113:80 check inter 60000 rise 1 fall 2
http-response set-header Location http://example.com%[capture.req.uri] if { status eq 404 }
http-response set-status 302 if { status eq 404 }
Test:
$ curl -v http://example.org/pics/funny/cat.jpg
* Hostname was NOT found in DNS cache
* Trying 127.0.0.1...
* Connected to example.org (127.0.0.1) port 80 (#0)
> GET /pics/funny/cat.jpg HTTP/1.1
> User-Agent: curl/7.35.0
> Host: example.org
> Accept: */*
The actual back-end returns 404, but we don't see it. Instead...
< HTTP/1.1 302 Moved Temporarily
< Last-Modified: Thu, 04 Aug 2016 16:59:51 GMT
< Content-Type: text/html
< Content-Length: 332
< Date: Sat, 07 Oct 2017 00:03:22 GMT
< Location: http://example.com/pics/funny/cat.jpg
The response body from the back-end's 404 error page will still be sent to the browser, but -- as it turns out -- the browser will not display it, so no harm done. This requires HAProxy 1.6 or later.
#Michael's answer is rather good, but isno't working for me for two reasons:
Mainly because the %[capture.req.uri] tag resolves to empty (HA Proxy 1.7.9 Docker image)
Also due to the fact that the original assumptions are incomplete, due to the fact that the frontend section is missing...
So I struggled for a while, as you find all kinds of answers on the Internet, between those guys who swear the 404 logic should be put in the frontend, vs those who choose the backend, and any possible kind of tags...
This is my answer, which works for me.
My use case is that if an image is not found on the backend behind HA Proxy, then an S3 bucket is checked.
The entry point is: https://myhostname:8080/path/to/image.jpeg
defaults
mode http
global
log 127.0.0.1:514 local0 debug
frontend come_on_over_here
bind :8080
# The following two lines are here to save values while we have access to them. They won't be available in the backend section.
http-request set-var(txn.path) path
http-request set-var(txn.query) query
http-request replace-value Host localhost:8080 dev.local:80
default_backend onprems_or_s3_be
backend onprems_or_s3_be
log global
acl path_photos var(txn.path) -m beg /path/prefix/i/want/to/strip/off
acl p_ext_jpeg var(txn.path) -m end .jpeg
acl is404 status eq 404
http-response set-header Location https://mybucket.s3.eu-west-3.amazonaws.com"%[var(txn.path),regsub(^/path_prefix_i_want_to_strip_off/,/)]?%[var(txn.query)]" if path_photos p_ext_jpeg is404
http-response set-status 301 if is404
server onprems_server dev.local:80 check

HAProxy 1.4: how to replace X-Forwarded-For with custom IP

I have an HAProxy 1.4 server behind an AWS ELB. Logically, the ELB sends the users IP in the X-Forwarded-For header. My app reads that header and behaves differently based on the IP (country).
I want to test that behavior overriding the X-Forwarded-For with custom IPs, but the AWS ELB appends my custom value with my current IP (X-Forwarded-For: 1.2.3.4, 200.1.130.2)
What I have been trying to do is to send another custom header X-Force-IP and once it gets into HAproxy, delete X-Forwarded-For headers and use reqirep to change the name X-Force-IP to X-Forwarded-For
This is how my config chunk looks like
acl custom-ip hdr_cnt(X-Force-IP) 1
reqidel ^X-Forwarded-For:.* if custom-ip
reqrep X-Force-IP X-Forwarded-For if custom-ip
but when it gets into my app, the app server (lighttpd) rejects it with "HTTP 400 Bad Request" as if it were malformed.
[ec2-user#haproxy-stage]$ curl -I -H "X-Forwarded-For: 123.456.7.12" "http://www.example.com"
HTTP/1.1 200 OK
Set-Cookie: PHPSESSID=mcs0tqlsg31haiavqopdvm02i6; path=/; domain=www.example.com
Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0
Pragma: no-cache
Content-Type: text/html; charset=UTF-8
Date: Sun, 11 Jan 2015 02:57:34 GMT
Server: beta
[ec2-user#haproxy-stage]$ curl -I -H "X-Forwarded-For: 123.456.7.12" -H "X-Force-IP: 321.456.7.12" "http://www.example.com"
HTTP/1.1 400 Bad Request
Content-Type: text/html
Content-Length: 349
Date: Sun, 11 Jan 2015 02:57:44 GMT
Server: beta
From the previous it looks like the ACL is working.
I checked with tcpdump in the app server and it seems that it has deleted the X-Forwarded-For header but also deleted the X-Force-IP instead of replacing it.
[ec2-user#beta ~]# sudo tcpdump -A -s 20240 'tcp port 80 and (((ip[2:2] - ((ip[0]&0xf)<<2)) - ((tcp[12]&0xf0)>>2)) != 0)' | egrep --line-buffered "^........(GET |HTTP\/|POST |HEAD )|^[A-Za-z0-9-]+: " | sed -r 's/^........(GET |HTTP\/|POST |HEAD )/\n\1/g'
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on eth0, link-type EN10MB (Ethernet), capture size 20240 bytes
GET / HTTP/1.1
User-Agent: curl/7.38.0
Host: www.example.com
Accept: */*
Connection: close
HTTP/1.1 400 Bad Request
Content-Type: text/html
Content-Length: 349
Connection: close
Date: Sun, 11 Jan 2015 02:56:50 GMT
Server: beta
The previous was with the X-Force-IP, and the following without it:
GET / HTTP/1.1
User-Agent: curl/7.38.0
Host: www.example.com
Accept: */*
X-Forwarded-For: 123.456.7.12
Connection: close
HTTP/1.1 200 OK
X-Powered-By: PHP/5.3.4
Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0
Pragma: no-cache
Content-Type: text/html; charset=UTF-8
Connection: close
Transfer-Encoding: chunked
Date: Sun, 11 Jan 2015 02:57:02 GMT
Server: beta
^C71 packets captured
71 packets received by filter
0 packets dropped by kernel
Any help?
I was expecting to have "X-Force-IP: 321.456.7.12" converted into "X-Forwarded-For: 321.456.7.12"
Thanks!
Ignacio
The regex matching provided here doesn't do simple substitution. It's quite a bit more powerful, and has to be used accordingly.
reqrep ^X-Force-IP:(.*) X-Forwarded-For:\1 if custom-ip
The reqrep (case sensitive request regex replace) and reqirep (case insensitive request regex replace) directives operate at the individual request header level, replacing the header name and its value with the 2nd argument, if the 1st argument matches... so if there's information you want to preserve (such as the value) you need one or more capture groups, such as (.*), in the 1st arg, and a placeholder \1 in the 2nd arg, in order to do the preserve the data.
Your current configuration does indeed invalidate the request, by creating a malformed/incomplete header line.
Also, you should anchor the pattern to the left side of the header name with ^. Otherwise, the expression could match more headers than you expect.

Cookies going through moovweb project have their domains overwritten to "."

I have a project using the mixer "simple-mobile (1.0.183)". I a request returns a response with the Set-Cookie header, those cookies have the domain set to just "."
Here's an example response header from a curl:
HTTP/1.1 200 OK
Server: nginx
Content-Type: text/html; charset=UTF-8
Content-Length: 23
X-Powered-By: ASP.NET
X-AspNet-Version: 2.0.50727
Set-Cookie: ASP.NET_SessionId=xu1qyg45ghzqveeuwa1ix1aa; path=/; HttpOnly; domain=.
Set-Cookie: ec_session=511840684.20480.0000; path=/; domain=.
Vary: Accept-Encoding
Connection: keep-alive
As you can see, the domain section of the cookie is set to just "." I'm not really messing with cookies anywhere in my tritium, and I don't use any of the cookie helper functions. I've had other moovweb projects set the domain correctly (in this case, it would be ".example.com").
Anyone know what might be going on with my cookies?