# This test ensures that h2 requests with invalid pseudo-headers are properly
# rejected. Also, the host header must be ignored if authority is present. This
# is necessary to avoid http/2 desync attacks through haproxy as described here
# https://portswigger.net/research/http2

varnishtest "h2 desync attacks"
feature ignore_unknown_macro

server s1 {
	rxreq
	expect req.http.host == "hostname"
	txresp
} -start

# haproxy frontend
haproxy hap -conf {
	defaults
		timeout client 30s
		timeout server 30s
		timeout connect 30s
		mode http

	listen feSrvH1
		bind "fd@${feSrvH1}"
		http-request return status 200

	listen feSrvH2
		bind "fd@${feSrvH2}" proto h2
		http-request return status 200

	listen fe1
		bind "fd@${fe1}" proto h2
		server srv-hapSrv "${hap_feSrvH1_addr}:${hap_feSrvH1_port}"

	listen fe2
		bind "fd@${fe2}" proto h2
		server srv-hapSrv "${hap_feSrvH2_addr}:${hap_feSrvH2_port}" proto h2

	listen fe3
		bind "fd@${fe3}" proto h2
		server s1 "${s1_addr}:${s1_port}"
} -start

# valid request
client c1 -connect ${hap_fe1_sock} {
	txpri
	stream 0 {
		txsettings
		rxsettings
		txsettings -ack
		rxsettings
		expect settings.ack == true
	} -run

	stream 1 {
		txreq \
		  -method "GET" \
		  -scheme "http" \
		  -url "/"
		rxresp
		expect resp.status == 200
	} -run
} -run

# valid request
client c2 -connect ${hap_fe2_sock} {
	txpri
	stream 0 {
		txsettings
		rxsettings
		txsettings -ack
		rxsettings
		expect settings.ack == true
	} -run

	stream 1 {
		txreq \
		  -method "GET" \
		  -scheme "http" \
		  -url "/"
		rxresp
		expect resp.status == 200
	} -run
} -run

# invalid path
client c3-path -connect ${hap_fe1_sock} {
	txpri
	stream 0 {
		txsettings
		rxsettings
		txsettings -ack
		rxsettings
		expect settings.ack == true
	} -run

	stream 1 {
		txreq \
		  -method "GET" \
		  -scheme "http" \
		  -url "hello-world"
		rxrst
	} -run
} -run

# invalid scheme
client c4-scheme -connect ${hap_fe1_sock} {
	txpri
	stream 0 {
		txsettings
		rxsettings
		txsettings -ack
		rxsettings
		expect settings.ack == true
	} -run

	stream 1 {
		txreq \
		  -method "GET" \
		  -scheme "http://localhost/?" \
		  -url "/"
		rxresp
		expect resp.status == 400
	} -run
} -run

# invalid method
client c5-method -connect ${hap_fe2_sock} {
	txpri
	stream 0 {
		txsettings
		rxsettings
		txsettings -ack
		rxsettings
		expect settings.ack == true
	} -run

	stream 1 {
		txreq \
		  -method "GET?" \
		  -scheme "http" \
		  -url "/"
		rxresp
		expect resp.status == 400
	} -run
} -run

# different authority and host headers
# in this case, host should be ignored in favor of the authority
client c6-host-authority -connect ${hap_fe3_sock} {
	txpri
	stream 0 {
		txsettings
		rxsettings
		txsettings -ack
		rxsettings
		expect settings.ack == true
	} -run

	stream 1 {
		txreq \
		  -method "GET" \
		  -scheme "http" \
		  -url "/" \
		  -hdr ":authority" "hostname" \
		  -hdr "host" "other_host"
	} -run
} -run

server s1 -wait
