Skip to content
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
1 change: 1 addition & 0 deletions Console/Command/GenerateVclCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int
'sslOffloadedHeader' => $this->varnishExtendedConfig->getSslOffloadedHeader(),
'trackingParameters' => $this->varnishExtendedConfig->getTrackingParameters(),
'designExceptions' => $this->varnishExtendedConfig->getDesignExceptions(),
'ipForwardHeader' => $this->varnishExtendedConfig->getIpForwardHeader(),
]);
$vclGenerator = $this->vclGeneratorFactory->create($vclParameters);
$vcl = $vclGenerator->generateVcl($varnishVersion, $inputFile);
Expand Down
7 changes: 7 additions & 0 deletions Model/Config.php
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ class Config extends PageCacheConfig

public const XML_PATH_VARNISH_PASS_ON_COOKIE_PRESENCE = 'system/full_page_cache/varnish/pass_on_cookie_presence';

public const XML_PATH_VARNISH_IP_FORWARD_HEADER = 'system/full_page_cache/varnish/ip_forward_header';

public function __construct(
ReadFactory $readFactory,
ScopeConfigInterface $scopeConfig,
Expand Down Expand Up @@ -132,4 +134,9 @@ public function getEnableStaticCache(): bool
{
return (bool) $this->scopeConfig->getValue(static::XML_PATH_VARNISH_ENABLE_STATIC_CACHE);
}

public function getIpForwardHeader(): string
{
return (string) $this->scopeConfig->getValue(static::XML_PATH_VARNISH_IP_FORWARD_HEADER);
}
}
3 changes: 2 additions & 1 deletion Model/Varnish/VCLGenerator.php
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,8 @@ public function getVariables(): array
'use_xkey_vmod' => (int) $this->varnishExtendedConfig->getUseXkeyVmod(),
'use_soft_purging' => (int) $this->varnishExtendedConfig->getUseSoftPurging(),
'pass_on_cookie_presence' => $this->varnishExtendedConfig->getPassOnCookiePresence(),
'design_exceptions_code' => $this->getRegexForDesignExceptions()
'design_exceptions_code' => $this->getRegexForDesignExceptions(),
'ip_forward_header' => $this->varnishExtendedConfig->getIpForwardHeader()
];
}

Expand Down
7 changes: 7 additions & 0 deletions etc/adminhtml/system.xml
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,13 @@
<field id="system/full_page_cache/varnish/use_xkey_vmod">1</field>
</depends>
</field>
<field id="ip_forward_header" type="text" translate="label comment" sortOrder="37" showInDefault="1" showInWebsite="0" showInStore="0">
<label>IP Forward Header</label>
<comment><![CDATA[Header name for forwarded client IP address (e.g., X-Forwarded-For, X-Real-IP). Used for purge access control.]]></comment>
<depends>
<field id="caching_application">1</field>
</depends>
</field>
</group>
</group>
</section>
Expand Down
1 change: 1 addition & 0 deletions etc/config.xml
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@
</tracking_parameters>
<use_xkey_vmod>0</use_xkey_vmod>
<use_soft_purging>0</use_soft_purging>
<ip_forward_header>X-Forwarded-For</ip_forward_header>
</varnish>
</full_page_cache>
</system>
Expand Down
4 changes: 2 additions & 2 deletions etc/varnish6.vcl
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ sub vcl_recv {

# Allow cache purge via Ctrl-Shift-R or Cmd-Shift-R for IP's in purge ACL list
if (req.http.pragma ~ "no-cache" || req.http.Cache-Control ~ "no-cache") {
if (client.ip ~ purge) {
if (std.ip(regsub(req.http.{{var ip_forward_header}}, "^([^,]+),?.*$", "\1"), client.ip) ~ purge) {
set req.hash_always_miss = true;
}
}
Expand All @@ -67,7 +67,7 @@ sub vcl_recv {
# The X-Magento-Tags-Pattern value is matched to the tags in the X-Magento-Tags header
# If X-Magento-Tags-Pattern is not set, a URL-based purge is executed
if (req.method == "PURGE") {
if (client.ip !~ purge) {
if (std.ip(regsub(req.http.{{var ip_forward_header}}, "^([^,]+),?.*$", "\1"), client.ip) !~ purge) {
return (synth(405));
}

Expand Down
1 change: 1 addition & 0 deletions tests/varnish/helpers/parse_vcl.pl
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
'PORT' => $ENV{PORT} || $ENV{s1_port},
'GRACE_PERIOD' => '300',
'SSL_OFFLOADED_HEADER' => 'X-Forwarded-Proto',
'IP_FORWARD_HEADER' => 'X-Forwarded-For',
'USE_XKEY_VMOD' => '1',
'ENABLE_BFCACHE' => '1',
'ENABLE_MEDIA_CACHE' => '1',
Expand Down
59 changes: 59 additions & 0 deletions tests/varnish/ip_forward_header.vtc
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
varnishtest "IP Forward Header allows purge from forwarded IP"

barrier b1 cond 2

server s1 {
# first request will be the probe, handle it and be on our way
rxreq
expect req.url == "/health_check.php"
txresp

# the probe expects the connection to close
close
barrier b1 sync
accept

rxreq
expect req.url == "/"
expect req.method == "GET"
txresp

rxreq
expect req.url == "/"
expect req.method == "PURGE"
txresp
} -start

# Generate the VCL file based on included variables and write it to output.vcl
# Set up ACL with a single whitelisted IP that is not the test client
shell {
export ACCESS_LIST="whitelisted"

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should not be set as SERVER1_IP overrides it

export WHITELISTED_IP="10.0.0.1"

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

export SERVER1_IP="10.0.0.1"

export s1_addr="${s1_addr}"
export s1_port="${s1_port}"
${testdir}/helpers/parse_vcl.pl "${testdir}/../../etc/varnish6.vcl" "${tmpdir}/output.vcl"
}

varnish v1 -arg "-f" -arg "${tmpdir}/output.vcl" -arg "-p" -arg "vsl_mask=+Hash" -start

# make sure the probe request fired
barrier b1 sync

client c1 {
# First request to cache the page
txreq -method "GET" -url "/"
rxresp
expect resp.http.X-Magento-Cache-Debug == "MISS"

# Second request: PURGE without X-Forwarded-For header should fail (405)
# because client IP (127.0.0.1) is not whitelisted
txreq -method "PURGE" -url "/"
rxresp
expect resp.status == 405

# Third request: PURGE with X-Forwarded-For header should succeed
# because forwarded IP (10.0.0.1) is whitelisted
txreq -method "PURGE" -url "/" -hdr "X-Forwarded-For: 10.0.0.1"
rxresp
expect resp.status == 200
} -run