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

Add use-forwarded-headers configmap option. #2616

Merged
merged 7 commits into from
Aug 16, 2018

Conversation

Dirbaio
Copy link
Contributor

@Dirbaio Dirbaio commented Jun 10, 2018

This PR adds a configmap option use-forwarded-headers to enable/disable trust of incoming X-Forwarded-* headers.

Setting it to true corresponds to the current ingress behavior.

It is set to false by default. this is a breaking change, but IMO it's the right thing to do if we follow the "secure by default" principle:

  • If the default is true, it might lead to vulnerable installations: If it's set to true in a scenario when it shouldn't (direct port forwarding, L3 load balancers) the config is vulnerable to source IP spoofing. However, since everything appears to work correctly, the user probably won't notice, and will end up vulnerable in production.
  • If the default is false, it'll just cause incorrect behavior if it's wrong. The user is likely to notice he's getting incorrect source IPs and will set it to true. At no point the user is vulnerable to spoofing.

Fixes #1815 #1309 #1668
Rebase/update of #1851

@k8s-ci-robot k8s-ci-robot added cncf-cla: yes Indicates the PR's author has signed the CNCF CLA. size/M Denotes a PR that changes 30-99 lines, ignoring generated files. labels Jun 10, 2018
@codecov-io
Copy link

codecov-io commented Jun 10, 2018

Codecov Report

Merging #2616 into master will decrease coverage by <.01%.
The diff coverage is 100%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master    #2616      +/-   ##
==========================================
- Coverage   47.57%   47.56%   -0.01%     
==========================================
  Files          77       77              
  Lines        5532     5533       +1     
==========================================
  Hits         2632     2632              
- Misses       2560     2562       +2     
+ Partials      340      339       -1
Impacted Files Coverage Δ
internal/ingress/controller/config/config.go 98.3% <100%> (+0.01%) ⬆️
internal/watch/file_watcher.go 80.76% <0%> (-3.85%) ⬇️

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 082325b...b5bcb93. Read the comment docs.

Copy link
Contributor

@antoineco antoineco left a comment

Choose a reason for hiding this comment

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

Looks good but requires at least a unit test.

@antoineco
Copy link
Contributor

This is indeed a breaking change, and not the behaviour most people will expect (why would my web proxy decide for me whether a header is secure or not?). If this gets approved we should first leave this to true and log a verbose message notifying users this default will change in future versions.

@Dirbaio
Copy link
Contributor Author

Dirbaio commented Jun 11, 2018

why would my web proxy decide for me whether a header is secure or not?

It has to decide something if the user doesn't. IMO it's better to go with the most conservative option, which is assuming XFF headers aren't secure and not trusting them.

If doing breaking changes is not OK then I guess logging a warning is the best we can do, though..

I'll add unit tests and docs soon.

@antoineco
Copy link
Contributor

antoineco commented Jun 11, 2018

I'm perfectly with you @Dirbaio, just trying to think about a safe path so that users can be warned in a timely manner before we do change the default (not everybody reads release notes carefully 😉).
The change to false by default can be done in 2 versions instead of 1.

@k8s-ci-robot k8s-ci-robot added size/L Denotes a PR that changes 100-499 lines, ignoring generated files. and removed size/M Denotes a PR that changes 30-99 lines, ignoring generated files. labels Jun 12, 2018
@Dirbaio
Copy link
Contributor Author

Dirbaio commented Jun 12, 2018

  • Changed setting to be true by default.
  • Added e2e test that checks for both behaviors
  • Added docs.

I haven't added the warning when the option is unset though because I'm unsure where to add it :) Help would be appreciated (or we can just not add it?)

@Dirbaio
Copy link
Contributor Author

Dirbaio commented Jun 13, 2018

CI failed due to something unrelated (I think). How do I retest?

also ping @antoineco PTAL

@antoineco
Copy link
Contributor

I restarted it.

@@ -596,6 +597,12 @@ _References:_

Sets the addresses on which the server will accept requests instead of *. It should be noted that these addresses must exist in the runtime environment or the controller will crash loop.

## use-forwarded-headers

If true, nginx passes the incoming `X-Forwarded-*` headers to upstreams. Use this option when nginx is behind another L7 proxy / load balancer that is setting these headers.
Copy link
Contributor

Choose a reason for hiding this comment

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

(nitpick) NGINX is commonly written in capital letters.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done

@@ -222,7 +226,9 @@ http {
'' close;
}

map {{ buildForwardedFor $cfg.ForwardedForHeader }} $the_real_ip {
# The following is a sneaky way to do "set $the_real_ip $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.

I'd rather use a Go template comment to avoid writing this message to the config file.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

IMO this could be useful to someone cating the rendered config from a running container, to help understand what's going on.

The rendered template already contains other similar comments, and this one is rendered just once so it's not like it'd cause much bloat.

Copy link
Contributor

Choose a reason for hiding this comment

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

You're right, no strong opinion on that one.

Expect(err).NotTo(HaveOccurred())
defer conn.Close()

conn.Write([]byte("GET / HTTP/1.1\r\nHost: forwarded-headers\r\nX-Forwarded-Port: 1234\r\nX-Forwarded-Proto: myproto\r\nX-Forwarded-For: 1.2.3.4\r\nX-Forwarded-Host: myhost\r\n\r\n"))
Copy link
Contributor

Choose a reason for hiding this comment

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

Using the gorequest package may make this easier to read.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done

@antoineco
Copy link
Contributor

antoineco commented Jun 13, 2018

Overall LGTM, however I have a feeling I'm not experienced enough with the NGINX map directive to express a final statement regarding all this map-fu 🙂

@antoineco
Copy link
Contributor

antoineco commented Jun 15, 2018

@aledbf I need your opinion on 2 things:

  1. Is this reasonable to you to change the default in a future version?
    If yes @Dirbaio please add the warning similarly to https://github.com/kubernetes/ingress-nginx/blob/master/cmd/nginx/flags.go#L213

  2. The map logic added to the template.

@Dirbaio
Copy link
Contributor Author

Dirbaio commented Jun 18, 2018

@antoineco the code you linked is for cmdline flags, but this is a configmap flag, which is pulled dynamically from the Kubernetes API.

@antoineco
Copy link
Contributor

Yeah stupid me, ignore that comment.

The next best place would be inside the OnUpdate() function then.

@Dirbaio
Copy link
Contributor Author

Dirbaio commented Jun 21, 2018

ping @aledbf . Does the PR look OK? Should I add the future change warning?

@aledbf
Copy link
Member

aledbf commented Jun 21, 2018

@Dirbaio I will take a look at this PR after we release 0.16. Apologies for the delay

@albertvaka
Copy link
Contributor

Seems this got ignored again like it happened with #1851

Not great for a security issue :/

@antoineco
Copy link
Contributor

antoineco commented Aug 16, 2018

/assign aledbf
#2616 (comment)

Like I said, the PR looks good to me but I'm unable to tell whether these maps are idiomatic.

Also pinging @ElvinEfendi

@aledbf
Copy link
Member

aledbf commented Aug 16, 2018

/lgtm
/approve

@k8s-ci-robot k8s-ci-robot added the lgtm "Looks good to me", indicates that a PR is ready to be merged. label Aug 16, 2018
@k8s-ci-robot
Copy link
Contributor

[APPROVALNOTIFIER] This PR is APPROVED

This pull-request has been approved by: aledbf, Dirbaio

The full list of commands accepted by this bot can be found here.

The pull request process is described here

Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@k8s-ci-robot k8s-ci-robot added the approved Indicates a PR has been approved by an approver from all required OWNERS files. label Aug 16, 2018
@aledbf
Copy link
Member

aledbf commented Aug 16, 2018

@Dirbaio again, apologies for the delay with the review and merge of this PR

@aledbf
Copy link
Member

aledbf commented Aug 16, 2018

@Dirbaio thanks!

@aledbf
Copy link
Member

aledbf commented Aug 16, 2018

/hold

@k8s-ci-robot k8s-ci-robot added the do-not-merge/hold Indicates that a PR should not merge because someone has issued a /hold command. label Aug 16, 2018
@k8s-ci-robot k8s-ci-robot merged commit b4942cc into kubernetes:master Aug 16, 2018
@@ -1047,7 +1069,7 @@ stream {

{{ $proxySetHeader }} X-Request-ID $req_id;
{{ $proxySetHeader }} X-Real-IP $the_real_ip;
{{ if $all.Cfg.ComputeFullForwardedFor }}
{{ if and $all.Cfg.UseForwardedHeaders $all.Cfg.ComputeFullForwardedFor }}
Copy link
Member

Choose a reason for hiding this comment

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

I think we need another else if here, something like

else if $all.Cfg.ComputeFullForwardedFor
{ $proxySetHeader }} X-Forwarded-For $proxy_add_x_forwarded_for;

since this is still not using forwarded headers, rather just passing it to upstream

Copy link
Contributor Author

Choose a reason for hiding this comment

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

$full_x_forwarded_for is the same as $proxy_add_x_forwarded_for but done "by hand" in a map above.

Maybe this is so it appends the proxy protocol addr instead of $remote_addr if using proxy protocol, and nginx's $proxy_add_x_forwarded_for doesn't?

Copy link
Member

Choose a reason for hiding this comment

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

yeah that makes sense @Dirbaio

Why do you require $all.Cfg.UseForwardedHeaders to be true here though?

Copy link
Contributor Author

@Dirbaio Dirbaio Aug 18, 2018

Choose a reason for hiding this comment

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

ComputeFullForwardedFor has no effect when UseForwardedHeaders = false.

There are 3 cases to consider:

  • UseForwardedHeaders = false: incoming X-Forwarded-For value is discarded and replaced with the source IP nginx-ingress sees.
  • UseForwardedHeaders = true, ComputeFullForwardedFor = false: incoming X-Forwarded-For value is passed through if present, otherwise it's filled with the source IP nginx-ingress sees.
  • UseForwardedHeaders = true, ComputeFullForwardedFor = true: incoming X-Forwarded-For value is passed through, appended with the source IP nginx-ingress sees.

For case 3 we use $full_x_forwarded_for, which has been computed earlier in the template.
For cases 1, 2 we use $the_real_ip, which earlier in the template has been set to be the appropriate source IP depending on UseForwardedHeaders

@ElvinEfendi
Copy link
Member

Thanks for the PR @Dirbaio!

I wonder if we can get rid of ComputeFullForwardedFor and X-Original-Forwarded-For at all. There's a case where we do

{{ $proxySetHeader }} X-Forwarded-For        $the_real_ip;

Why would a reverse proxy do this? This seems like a hacky way of satisfying some lazy upstream so that they can get the correct client IP.

@schweikert
Copy link

We noticed by chance that the default behavior of ingress-nginx is to trust the X-Forwarded-For header, which caused a security vulnerability for our setup. Is there an issue tracking the change of the default to false for this? I think that it would be much preferable to follow a "secure by default" approach. How many users of ingress-nginx are affected without knowing?

@ElvinEfendi
Copy link
Member

@schweikert unfortunately that's the case. I think default value forproxy-real-ip-cidr should be "0.0.0.0/32", so that it trusts no client.

#3333 aims to do so.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
approved Indicates a PR has been approved by an approver from all required OWNERS files. cncf-cla: yes Indicates the PR's author has signed the CNCF CLA. do-not-merge/hold Indicates that a PR should not merge because someone has issued a /hold command. lgtm "Looks good to me", indicates that a PR is ready to be merged. size/L Denotes a PR that changes 100-499 lines, ignoring generated files.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

8 participants