Getting Common Name from Distinguished Name of client certificate in NGINX - nginx

I need to get the CN of a client certificate in NGINX to append it to the proxy headers.
I already found the following map code for this.
map $ssl_client_s_dn $ssl_client_s_dn_cn {
default "";
~/CN=(?<CN>[^/]+) $CN;
}
But sadly it only returns an empty string for the following $ssl_client_s_dn:
CN=testcn,O=Test Organization
I tested it with other DNs, too. But the problem is always the same.

The pattern you use requires the legacy DN, since it assumes the / to separate the RDNs. So (since nginx v1.11.6) the following works:
map $ssl_client_s_dn_legacy $ssl_client_s_dn_cn {
default "";
~/CN=(?<CN>[^/]+) $CN;
}
With $ssl_client_s_dn_legacy: /O=Test Organization/CN=testcn

As #christof-r mentioned, your regex matched with the legacy DN pattern. Please use this regex to match with the current ( > v1.11.6) pattern.
map $ssl_client_s_dn $ssl_client_s_dn_cn {
default "";
~CN=(?<CN>[^,]+) $CN;
}

Related

How to set up a hash based director using vcl in Varnish which is using the hashed request body?

I am trying to set up Varnish to route between backends using the request body's hash. I found good examples of using body access to set up caching where the hash of the request body is used as cache key. I cannot find an example of using the body hash for routing.
I tried the following but it doesn't seem to work. It is probably because bodyaccess was not meant to be used this way. How to set up a hash based director using vcl in Varnish which is using the hashed request body?
vcl 4.1;
import directors;
import bodyaccess;
backend backend1 {
.host = "backend1.example.com";
.port = "80";
}
backend backend2 {
.host = "backend2.example.com";
.port = "80";
}
sub vcl_init {
new xhash = directors.hash();
xhash.add_backend(backend1);
xhash.add_backend(backend2);
}
sub vcl_recv {
set req.backend_hint = xhash.backend(bodyaccess.hash_req_body());
}
There is no real way to retrieve the request body as a string in Varnish Cache (open source). The bodyaccess.hash_req_body() function will actually add the request body to the caching hash in the vcl_hash subroutine. But since this function returns a void data type, this won't help you.
The only realistic way that I'm aware of is by using vmod_xbody, which is a Varnish Enterprise module. That module has an xbody.get_req_body() function that returns the request body as a string.
See https://docs.varnish-software.com/varnish-cache-plus/vmods/xbody/#get-req-body for more information.

nginx map to change multiple occurrences

I have following header in upstream response
Location: http://10.0.2.15:8080/auth?&redirect_uri=http%3A%2F%2F10.0.2.15%3A8080%2Foauth
I need to change the private ip to $http_host. Currently this works only for the 1st occurence, not in redirect_uri one.
map $upstream_http_location $location_patch {
"" "";
"~^.*/10.0.2.15:8080/(.*)$" "http://$http_host/$1";
"~.*/10.0.2.15%3A8080%2F(.*)$" "http://$http_host/$1";
"~.*" "";
}
Please advise.

Nginx block all traffic with specific custom header except to some urls

I have a service that is hosted in an internal network that is receiving traffic in port 443 (via https) behind a custom loadbalancer both from the internet but also from the internal network.
Internal network requests are coming with an extra custom header, let's call it X-my-lb-header.
I want to block all external incoming traffic to all uris (return an http response code), except to some specific ones.
Eg, let's say that i want to allow traffic that is coming to two endpoints /endpoind1/ (preffix match) and /endpoint2 actual match.
What is the best way to achieve a behaviour like this?
If my understanding is correct I need something like (not correct syntax bellow)
location = /endpoind2 {
if ($http_x_my_lb_header not exists) {
pass
} else {
return 404
}
... the rest of the directives
}
location ~ / {
if ($http_x_my_lb_header) {
return 404;
}
... the rest of the directives
}
But since else is not supported in nginx, i cannot figure out to do it.
Any ideas?
So you need some logic like
if (header exists) {
if (request URI isn't whitelisted) {
block the request
}
}
or in another words
if ((header exists) AND (request URI isn't whitelisted)) {
block the request
}
Well, nginx don't allow nested if blocks (nor logical conditions). While some people inventing a really weird but creative solutions like this one (emulating AND) or even this one (emulating OR), a huge part of such a problems can be solved using map blocks (an extremely powerfull nginx feature).
Here is an example:
# get the $block variable using 'X-my-lb-header' value
map $http_x_my_lb_header $block {
# if 'X-my-lb-header doesn't exists, get the value from another map block
'' $endpoint;
# default value (if the 'X-my-lb-header' exists) will be an empty string
# (unless not explicitly defined using 'default' keyword)
}
# get the $endpoint variable using request URI
map $uri $endpoint {
# endpoint1 prefix matching (using regex)
~^/endpoint1 ''; don't block
# endpoint2 exact matching
/endpoint2 ''; don't block
default 1; # block everything other
}
Now you can use this check in your server block (don't put it to some location, use at the server context):
if ($block) { return 404; }

httputil DumpRequest Host header excluded?

I am writing a http proxy which needs to call DumpRequest. For some reason, the client does not offer "Host" header while the server needs it. I then do this:
if req.Header.Get("host") == "" {
req.Header.Set("Host", "www.domain.com:80")
}
data, _ := httputil.DumpRequest(req, true)
conn.Write(data)
The problem is after I set "host", data still does not has it. I digged into source code of DumpRequest, found this:
var reqWriteExcludeHeaderDump = map[string]bool{
"Host": true, // not in Header map anyway
"Transfer-Encoding": true,
"Trailer": true,
}
Why is Host "not in Header map anyway"?
Short answer
I guess you should use Header.Add() instead of Header.Set()
I didn't try it. So if my answer is wrong, be free to figure it out.
Reference
From source code , It said that.
It replaces any existing values associated with key
But in you case, you don't have that K existing. So it behaves as we expected.
If you use Set() method, it's cool.
DumpRequest should not be used to implement a proxy - see doc:
It should only be used by servers to debug client requests. The returned
representation is an approximation only ...
If you are trying to change the origin host value of the request while it goes through your proxy, you do not need to change the header of host. You can do the following:
if req.Host == "" {
req.Host = "www.domain.com"
}
data, _ := httputil.DumpRequest(req, true)
conn.Write(data)
Playground
https://play.golang.org/p/f-qaZRC0RMO

How do I only log just the host for a certain host?

I have the following to log just the referer's path:
map $http_referer $just_path {
"~^(?P<path>[^?]*)(\?.*)?$" $path;
}
Basically, I don't want to log the referer's querystring. However, some requests come from ourselves, and for those I want to just log the scheme + host without the path (e.g. http://www.example.com).
How do I achieve that while still logging the scheme + host + path for everyone else? In short:
Site A: http://sitea.com/this/path?v=true -> http://sitea.com/this/path
Site B: https://siteb.com/another/path?another=query -> https://siteb.com/another/path
Our Site: https://www.example.com/this/other/path?x=true&v=false -> https://www.example.com/
Regular expressions are evaluated in order, so you can place a new line before the one you have to match your own host name.
For example:
map $http_referer $just_path {
"~^(?<path>https?://www.example.com/)" $path;
"~^(?<path>[^?]*)" $path;
}
See this document for details.

Resources