Skip to content
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

handle indeterminate length proxy chains - fixes #449 #453

Merged
merged 3 commits into from
Mar 6, 2018

Conversation

aaronhurt
Copy link
Member

No description provided.

@aaronhurt
Copy link
Member Author

@magiconair What are your thoughts on this as a solution for handling layered proxies?

Copy link
Contributor

@magiconair magiconair left a comment

Choose a reason for hiding this comment

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

This looks sane but I want to let the problem sink in a bit more. Both of you have discussed this in detail and I don't think there is anything to add right now.

As a general rule, Fabio should do the 'right thing' if possible and should pick the 'right' default if there are multiple options.

If I can't think of a glaring omission I'll merge this.

// ensure we only get the ip string
xip = strings.TrimSpace(xip)
// only continue if xip differs from host
if xip != host {
Copy link
Contributor

Choose a reason for hiding this comment

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

I'd prefer not to nest the ifs.

if xip == host {
    continue
}
if ip = net.ParseIP(xip); ip == nil {
    log.Printf("[WARN] failed to parse xff address %s", xip)
    continue
}
if t.denyByIP(ip) {
    return true
}

Copy link
Member Author

Choose a reason for hiding this comment

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

Can do.

@magiconair
Copy link
Contributor

I think this is the right approach.

The chain of ips you have to validate is client, proxy1, proxy2, ...., proxyN. The chain is composed of the xff header and the remote ip of your incoming connection which cannot be spoofed. Also, previous proxies only append the remote ip of their incoming connection to the xff header.

For this to work you have to trust a chain of proxies in front of you. The end of your trust chain contains the ip address of the inbound connection you care about and it cannot be spoofed. If someone sends a bogus xff header from an ip that is not in the list then this ip will be added to the xff header by your trusted proxy and the other trusted proxies in front of you will pass on that information to fabio which will see the ip and deny access.

continue
}
if ip = net.ParseIP(xip); ip == nil {
log.Printf("[WARN] failed to parse xff address %s", xip)
Copy link
Contributor

Choose a reason for hiding this comment

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

don't you need to continue here?

Copy link
Member Author

Choose a reason for hiding this comment

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

The behavior of denyByIP is to immediately return false when passed a nil ... so it is effectively doing a continue. However, I can see the point that adding a continue would be more explicit and be more future proof should additional code be added between this statement and the denyByIP call.

@magiconair
Copy link
Contributor

With that in mind I wouldn't call this a work around. This is how this should work I think

@aaronhurt aaronhurt changed the title workaround for #449 handle indeterminate length proxy chains - fixes #449 Feb 27, 2018
@aaronhurt
Copy link
Member Author

I updated the commit description and PR title to reflect the discussion.

@aaronhurt
Copy link
Member Author

aaronhurt commented Feb 27, 2018

If someone sends a bogus xff header from an ip that is not in the list then this ip will be added to the xff header by your trusted proxy and the other trusted proxies in front of you will pass on that information to fabio which will see the ip and deny access.

Yes, exactly which is why I settled on validating all the elements contained within the XFF header. If some client wants to be clever and inject a bogus XFF header, including a header of an allowed netblock, they will still be denied if any member of the header fails to match.

@aaronhurt
Copy link
Member Author

@magiconair anything else that is still needed with this one?

Copy link
Contributor

@magiconair magiconair left a comment

Choose a reason for hiding this comment

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

Two small comments and then we're good. In the test itself you're not handling the error from url.Parse. In picker_test.go there is a mustParse function which panics which you could use.

target: &Target{
Opts: map[string]string{"allow": "ip:10.0.0.0/8,ip:192.168.0.0/24"},
},
xff: "1.1.1.2, 10.11.12.13, 10.11.12.14",
xff: "10.11.12.13, 1.1.1.2, 10.11.12.14",
Copy link
Contributor

Choose a reason for hiding this comment

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

Is there a reason the order changed?

Copy link
Member Author

Choose a reason for hiding this comment

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

Not particularly, the result is the same regardless of the order. I can’t remember why it was changed.

Copy link
Member Author

Choose a reason for hiding this comment

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

Ohh, the old code only checked the first element. I moved it to ensure it was looping over the elements and still denying on a middle element.

@@ -183,13 +183,13 @@ func TestAccessRules_AccessDeniedHTTP(t *testing.T) {
denied: true,
},
{
desc: "allowed xff and allowed remote addr",
desc: "single allowed xff and allowed remote addr",
Copy link
Contributor

Choose a reason for hiding this comment

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

This comment doesn't match the result since the behavior changed. Can you double check?

Copy link
Member Author

Choose a reason for hiding this comment

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

Only a single element of the XFF is allowed. The others are denied.

Copy link
Member Author

Choose a reason for hiding this comment

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

In the old code this was allowed as it only checked the first element.

Copy link
Contributor

Choose a reason for hiding this comment

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

Ah, I thought that because the XFF address and the remote address are allowed the request should be allowed. But 1.2.3.4 isn't whitelisted. This wasn't obvious to me. I'll clean up the comments after the merge.

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes, that was part of the new behavior. If any one of the addresses presented in the XFF fail to match an allow/whitelist rule the request is denied. If any one of the addresses match a deny/blacklist rule the request is denied.

@magiconair magiconair merged commit dde725f into fabiolb:master Mar 6, 2018
@magiconair magiconair added this to the 1.5.9 milestone May 16, 2018
@aaronhurt aaronhurt deleted the fix/aws-xff branch January 22, 2020 22:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants