-
Notifications
You must be signed in to change notification settings - Fork 4.7k
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
Router support for mutual tls authentication #19891
Conversation
cc @openshift/sig-security |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nits
{{- if ne (env "ROUTER_MUTUAL_TLS_AUTH" "none") "none" }} | ||
{{- with (env "ROUTER_MUTUAL_TLS_AUTH_CN") }} | ||
# If a mutual TLS auth CN is set, we deny requests if the common name doesn't | ||
# match. A custom template can change this behavior (e.g. set custom headers). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please specify exactly what name the cert needs to match
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Common Name ... will update this to make this clearer.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am not asking what CN is, I am asking what it should match, it is not clear to me what you have to set CN to.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So to make it a wee bit clearer:
- If the DN set to
/CN=header.test/ST=CA/C=US/O=Security/OU=OpenShift3
. - A couple (example set) of the headers set in the request sent to the backend would be:
X-SSL-Client-DN=/CN=header.test/ST=CA/C=US/O=Security/OU=OpenShift3
X-SSL-Client-CN=header.test
And the ROUTER_MUTUAL_TLS_AUTH_CN
environment variable value allows you to "filter" requests based on that CN value (header.test
).
Example:
A. For ROUTER_MUTUAL_TLS_AUTH_CN=der.test
or ROUTER_MUTUAL_TLS_AUTH_CN=header.te
would match the value and the request would be sent to the backend.
B. For ROUTER_MUTUAL_TLS_AUTH_CN=MyClient
, the request would be rejected.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This sounds wrong, we should probably have a ROUTER_MUTUAL_TLS_AUTH_DN variable and match the exact subject as a default.
In what case matching a substring of a random CN in the subject is useful ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's what the customer was using/asking - ref: https://bugzilla.redhat.com/show_bug.cgi?id=1477349
In any case, this is moot because I just changed this to be an auth filter (see latest commit) to filter based on the subject (substring) so you can run a router ala:
oc adm router --latest-images=true --mutual-tls-auth=optional \
--mutual-tls-auth-ca=_ramr/nodejs-header-echo/config/CA/cacert.pem \
--mutual-tls-auth-filter="CN=header.test"
As re: an exact match, see the other comment re: filtering on organizations or OUs.
{{- if ne (env "ROUTER_MUTUAL_TLS_AUTH" "none") "none" }} | ||
{{- with (env "ROUTER_MUTUAL_TLS_AUTH_CN") }} | ||
# If a mutual TLS auth CN is set, we deny requests if the common name doesn't | ||
# match. A custom template can change this behavior (e.g. set custom headers). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
same as above
http-request set-header X-SSL-Issuer %{+Q}[ssl_c_i_dn] | ||
http-request set-header X-SSL-Client-NotBefore %{+Q}[ssl_c_notbefore] | ||
http-request set-header X-SSL-Client-NotAfter %{+Q}[ssl_c_notafter] | ||
{{- end }} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would be nice to have access to the whole client cert, is it possible via env vars ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok will do - there's a ssl_c_der
that gives the client cert in DER format (will convert to base64 if possible [otherwise hex] as it is in binary form).
pkg/oc/admin/router/router.go
Outdated
|
||
// MutualTLSAuthCRL contains the certificate revocation list used to | ||
// verify a client's certificate. | ||
MutualTLSAuthCRL string | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Given we default to no mutual TLS, it may be nice to specify a field with the Subject the cert need to match so that we do not need custom templates but a simple configuration change.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, ok - let me check with @knobunc on today's call on that.
Reason is that I felt the ROUTER_MUTUAL_TLS_AUTH_CN
might not even be needed.
For the specific customer use case, they need to add/remove custom headers, so they would need a custom template to filter on the CN and then do the custom header magic anyway.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, am not sure doing this based on the subject may be possible. I did just briefly glance at the haproxy docs (ref: https://cbonte.github.io/haproxy-dconv/1.8/configuration.html#7.3.4) and didn't see a way to get at the subject field easily.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The subject is in SSL_CLIENT_S_DN
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Or ssl_c_s_dn in haproxy
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Okay, will rework this a bit later this afternoon to pass that filtering value as a command line option. And to do a substring match on that field ssl_c_s_dn
. Thx
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
you probably want an exact match
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
so the existing RFE from https://bugzilla.redhat.com/show_bug.cgi?id=1477349
which the customer has uses a substring match.
In general, an exact match will be too restrictive if for example you want to filter on just say organizations, departments/units in an organization etc.
pkg/oc/admin/router/router.go
Outdated
// generateSecretsConfig generates any Secret and Volume objects, such | ||
// as SSH private keys, that are necessary for the router container. | ||
func generateSecretsConfig(cfg *RouterConfig, namespace string, defaultCert []byte, certName string) ([]*kapi.Secret, []kapi.Volume, []kapi.VolumeMount, error) { | ||
func generateSecretsConfig(cfg *RouterConfig, namespace string, certName string, defaultCert, mtlsAuthCA, mtlsAuthCRL []byte) ([]*kapi.Secret, []kapi.Volume, []kapi.VolumeMount, error) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
May as well also drop string after namespace if we are into reordering arguments
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Doh!! Thanks for catching that.
mtlsAuth := strings.TrimSpace(cfg.MutualTLSAuth) | ||
if len(mtlsAuth) > 0 && mtlsAuth != defaultMutualTLSAuth { | ||
env["ROUTER_MUTUAL_TLS_AUTH"] = cfg.MutualTLSAuth | ||
if len(mtlsAuthCA) > 0 { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does it use the system store if the CA flag is not set? Otherwise it should be required when mTLS is enabled
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The defaults section in the haproxy-config.template sets the default ca-base
to the system store which gets used if one is not specified on the bind directive.
pkg/oc/admin/router/router.go
Outdated
@@ -309,15 +334,25 @@ func NewCmdRouter(f *clientcmd.Factory, parentName, name string, out, errout io. | |||
cmd.Flags().BoolVar(&cfg.StrictSNI, "strict-sni", cfg.StrictSNI, "Use strict-sni bind processing (do not use default cert). Not supported for F5.") | |||
cmd.Flags().BoolVar(&cfg.Local, "local", cfg.Local, "If true, do not contact the apiserver") | |||
|
|||
cmd.Flags().StringVar(&cfg.MutualTLSAuth, "mutual-tls-auth", cfg.MutualTLSAuth, "Controls access to the router using mutually agreed upon TLS configuration (ala client certificates). You can choose one of 'required', 'optional', or 'none'. The default is none.") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(ala client certificates)
ala a typo ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Meaning as an example but I will "rework" that text along with any other new review comments. Thx
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ala is fine, but slightly obscure -- https://en.wiktionary.org/wiki/ala#Etymology_2
Might be better to say i.e.
/hold |
{{- with (env "ROUTER_MUTUAL_TLS_AUTH_FILTER") }} | ||
# If a mutual TLS auth subject filter environment variable is set, we deny | ||
# requests if the DN field in the client certificate doesn't match that value. | ||
# Please note that this match is a subset (substring) match. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you give an example showing that "head" would also match? And if you can anchor it so that it is an exact match? Thanks
# If a mutual TLS auth subject filter environment variable is set, we deny | ||
# requests if the DN field in the client certificate doesn't match that value. | ||
# Please note that this match is a subset (substring) match. | ||
# Example: For DN set to: /CN=header.test/ST=CA/C=US/O=Security/OU=OpenShift3, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's just lose these examples and refer them to the examples above?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done and referred to the examples above.
pkg/oc/admin/router/router.go
Outdated
@@ -309,15 +334,25 @@ func NewCmdRouter(f *clientcmd.Factory, parentName, name string, out, errout io. | |||
cmd.Flags().BoolVar(&cfg.StrictSNI, "strict-sni", cfg.StrictSNI, "Use strict-sni bind processing (do not use default cert). Not supported for F5.") | |||
cmd.Flags().BoolVar(&cfg.Local, "local", cfg.Local, "If true, do not contact the apiserver") | |||
|
|||
cmd.Flags().StringVar(&cfg.MutualTLSAuth, "mutual-tls-auth", cfg.MutualTLSAuth, "Controls access to the router using mutually agreed upon TLS configuration (ala client certificates). You can choose one of 'required', 'optional', or 'none'. The default is none.") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ala is fine, but slightly obscure -- https://en.wiktionary.org/wiki/ala#Etymology_2
Might be better to say i.e.
/retest |
Any news about this merge? |
Master is frozen from anything but critical fixes until the release-3.10 branch is cut. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
/lgtm
/hold cancel |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
/lgtm
http-request set-header X-SSL-Client-NotAfter %{+Q}[ssl_c_notafter] | ||
http-request set-header X-SSL-Client-DER %{+Q}[ssl_c_der,base64] | ||
{{- end }} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are these headers required for edge routes which use plain http backend ? seems just re-encrypt route need them.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No strong opinion either way. But the headers do reflect information about the connection as it was handled on the edge by the router, so it is still relevant request info (irrespective of the type of backend/route) much like the [X-]Forwarded[-*]
and Via
headers.
@juanvallejo @soltysh @enj -- Can someone approve the completion changes please. |
|
||
// MutualTLSAuthFilter contains the value to filter requests based on | ||
// a client certificate subject field substring match. | ||
MutualTLSAuthFilter string |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I still do not think this should be a substring match.
In what case would you want to match only part of a cert without explicitly specifying which part at least ?
Unless there is a very specific reason to use a substring match we should only do an exact match.
Otherwise we risk opening up users to easy attacks.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The intent here is to restrict an already validated set of requests. It is important to note here that the connections are already filtered based on the CA signing the certificate (and/or any revoked certificates).
So the ROUTER_MUTUAL_TLS_AUTH_CA
and ROUTER_MUTUAL_TLS_AUTH_CRL
control the first pass of which connections (or certificates) are allowed and the substring match here is the second pass that is restricting that first block of connections to a smaller subset.
If we restrict that to specific match, then we can really only allow one single certifying authority per router. Multiple orgs/depts/units cannot be serviced by a single router instance.
This allows us flexibility to support that and if you really wanted a further locked down environment, you could just as well make the substring match more restrictive ala:
ROUTER_MUTUAL_TLS_AUTH_FILTER="/CN=header.test/ST=CA/C=US/O=Security/OU=OpenShift3"
which effectively becomes a specific match.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Won't this match also: /CN=blah/CN=blah/CN=more-blah/CN=header.test/ST=CA/C=US/O=Security/OU=OpenShift3 ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, it would string substring match that. But that said, is that a valid DN/subject?
From the looks of it the / is a separator in the subject, correct?
So shouldn't that be escaped if someone forces that into an input field? Otherwise, there's potential of other issues, no?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@simo5 also pertinent here is the fact that the ROUTER_MUTUAL_TLS_AUTH_FILTER
is optional. Meaning if its not specified, we don't do any restrictive checks. This is a secondary check.
But if this is still a sticking point for you, we could potentially change the substring match to a regular expression ... so you could potentially do a ^<escaped-text>$
kind of expression for an exact match.
That said, it does make the user interface a bit more awkward as it requires the user to specify a regular expression. Not a back breaker though!
And it is worth noting that a regular expression without the positional anchor ... ala CN=foo
would still match a /CN=blah/CN=foo/CN=blah/
string.
@knobunc / @ironcladlou any issues with that?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can have as many CN elements as you want in a DN.
Changing it to a regular expression would be definitely better and I would accept as a solution because then you can do exact matches if you want or anything.
An example with exact match and one with subfiltering should be provided as doc then.
completions lgtm |
/approve for completions |
@simo5 changed to use a regular expression. Commit sha: 06615ab No changes to the usage/command line except that the string is now a regular expression ala:
The |
@juanvallejo completions didn't change but in case you want to re-verify - the last commit only changed the command line option help text. Thx |
Rebased with upstream to fix conflict in |
/retest |
please squash commits and I'll apply lgtm |
@simo5 squashed the commits. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
/lgtm
@simo5 -- Over to you |
/lgtm |
/retest |
Opened new issue - test flake #20295 |
/retest |
…ient side certificates and its use can be mandated to be either required or optional. In addition, an env variable ROUTER_MUTUAL_TLS_AUTH_CN provides more fine-grain control on access (via regular expressions) based on certificate common names.
@knobunc may need a new |
/lgtm |
/retest |
[APPROVALNOTIFIER] This PR is APPROVED This pull-request has been approved by: juanvallejo, knobunc, pravisankar, ramr, simo5 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 |
/retest |
@ramr: The following test failed, say
Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes/test-infra repository. I understand the commands that are listed here. |
/test unit |
Changes as per RFE: https://bugzilla.redhat.com/show_bug.cgi?id=1477349
Adds mutual tls authentication support. Verification is via client-side certificates and its
use can be mandated to be either required or optional.
Additionally, an env variable ROUTER_MUTUAL_TLS_AUTH_CN provides more fine-grain
control on access based on certificate common names.
Usage:
Start the router with the new command line options to specify the mutual tls options.
Example:
In the above example, the regular expression for
mutual-tls-auth-filter
forces an exact match with a certificate's subject.To Test:
And test without the certificate/key to check that access is not allowed.
Other test scenarios can use different combinations of the filter (and/or --mutual-tls-auth flag) to test access.
@knobunc PTAL Thx
Edited Updated usage and tests