Cache results of auth_request module, keyed by username - authentication

I am running the Kubernetes dashboard which, as one of its authentication methods, will allow you to pass the Authorization header with a bearer token in it. The token has to be a bearer token, i.e. Authorization: Bearer <token goes here>.
I am also running pam_hook as my authentication service. It uses HTTP basic auth to query PAM to get a user's groups, and it constructs a bearer token with that. I've modified it slightly to return the bearer token in the Authorization header after seeing this answer that tells me that you need to have it in the header for what I am trying to do: https://stackoverflow.com/a/31485557/691859
I have a fronting proxy (nginx) that uses the HTTP auth PAM module to secure the dashboard. Of course, I can have nginx forward the Authorization: Basic <basic auth goes here> header from this to pam_hook, and pam_hook can reply with the bearer token in an Authorization: Bearer <token goes here> header.
However, I'm having two problems:
I am struggling to get the bearer token from pam_hook forwarded to the kubernetes dashboard; nginx is only forwarding the basic auth.
Even if I was able to get it passed, I'm not sure how I can safely cache the responses. I need to cache each user's bearer token separately, otherwise, people will be able to act as each other. This answer alludes to that, but it doesn't tell you how to avoid the problem: https://stackoverflow.com/a/44232557/691859
So right now my config looks like this (I'm simplifying greatly here):
nginx.conf:
http {
proxy_cache_path /tmp/cache levels=1:2 keys_zone=authentication:10m inactive=15m;
proxy_cache_valid 200 15m;
include /etc/nginx/conf.d/pam.conf;
include /etc/nginx/conf.d/upstream.conf;
}
pam.conf:
auth_pam "Log in please";
auth_pam_service_name "nginx";
upstream.conf:
upstream backend {
server localhost:9090
}
server {
listen 443 default_server;
location #upstream {
proxy_pass https://backend;
proxy_redirect off;
}
include /etc/nginx/conf.d/basic_auth_proxy.conf;
location / {
root /dev/null;
try_files $uri #upstream;
auth_request /basicauthproxy;
auth_request_set $authorization_header $upstream_http_authorization;
proxy_set_header Authorization $authorization_header;
}
}
basic_auth_proxy.conf:
location /basicauthproxy {
internal;
proxy_pass https://pamhook/token;
proxy_cache authentication;
proxy_cache_key $cookie_authentication;
proxy_pass_request_body off;
proxy_hide_header Set-Cookie;
proxy_ignore_headers Set-Cookie;
proxy_set_header Cookie "";
proxy_set_header Host $host;
proxy_set_header Content-Length "";
proxy_set_header X-Original-URI $request_uri;
}
The behavior I get right now is that I get prompted for basic auth, pass my credentials, that succeeds, and then the dashboard gets the forwarded request... but the Authorization header contains the basic auth info from the proxy, not the bearer token. What am I missing to get this to work?
Edit: I upped the verbosity of pam_hook's logs. It's not getting any request from nginx. So that's probably the first problem to address, not that I understand why it's happening. I do know that I can curl pam_hook from the nginx container, so it's not a matter of a bad NetworkPolicy.

Related

Nginx -> Apache 2 authentication -> return to Nginix

We have a nginx and an apache2 server.
Apache2 is configured to manage Kerberos (Active Directory) authentication.
We have a website managed by nginx with a reserved area.
I would know if this is possible:
the user goes to main site managed by nginx
from main site, there is a link to "/login" mapped to apache2:
location /login/ {
proxy_pass http://apache2server/testlogin;
}
when the login is successful, apache2 is configured to go to another nginx webpage, using proxypass too:
ProxyPass /testlogin http://nginxserver/logindone.php
ProxyPassReverse /testlogin http://nginxserver/logindone.php
I wonder if this is the right solution to the problem.
The best way you can implement an external authentication to your NGiNX website is using auth_request directive.
Basically, you can protect any request doing a subrequest to any external web server. The subrequest must return HTTP code 2XX to allow proceeding to the content, and any other HTTP code returned will deny access.
To accomplish that, be sure you've NGiNX with auth_request enabled (compiled with --with-http_auth_request_module). To check that, use the following command at shell:
nginx -V 2>&1 | grep "http_auth_request_module"
Add the auth_request directive to the location you want to protect, specifying an internal location where the authorization subrequest will be forwarded to, using:
location /system/ {
auth_request /auth;
#...
}
So, when a request is made to /system/ location, the system will create a subrequest to /auth location. Now we need to create the internal /auth location. We can use the following example below:
location = /auth {
internal;
proxy_pass http://my.app.webserver/auth_endpoint;
proxy_pass_request_body off;
proxy_set_header Content-Length "";
#...
}
Here, we created the /auth internal location. We used the internal directive to disable external NGiNX access (any external request to /auth will not be processed by this location). Also, we removed the request body content and set the request length to zero, removing any original request variable. We do a subrequest to http://my.app.webserver/auth_endpoint passing all requested cookies, so your backend application could determine if user has access or not.
If you need to know the original requested URI, you can add it on an extra HTTP header at subrequest adding:
proxy_set_header X-Original-URI $request_uri;
You can learn more about NGiNX auth_request directive here.

nginx authentication from external service

My goal is to enable access on one (static) web page/folder only to authenticated users.
auth_basic is NOT option.
There are 2 servers: nginx (contains http,css and javascript) and REST server (doing authentication and provides secure content).
REST server provides access token.
On www.somesite.com/secret (hosted by nginx) should access only authenticated users.
I have tried something similar to this https://www.nginx.com/resources/admin-guide/restricting-access-auth-request/
this is my config example:
location /secret/ {
auth_request /restauth;
auth_request_set $auth_status $upstream_status;
}
location /restauth/ {
internal;
proxy_pass http://localhost:8080/api/auth/login;
proxy_pass_request_body off;
proxy_set_header Content-Length "";
proxy_set_header X-Original-URI $request_uri;
}
With this, only GET request is sent, REST service support only POST.
How can I send POST with access token, and with code 200 to allow access to content of /auth folder?
Another interesting thing I found is nginscript, but this simple example from this site doesn't work to me.

Removing basic authorization header in Nginx or Apache

The Nginx/Apache serves as a reverse proxy to the backend server. I'd like to write a rule to remove the Authorization header if the value starts with "Basic". That is the Nginx/Apache server won't pass the Basic auth header, but it will pass all other Authorization headers.
You will need to forge the 401 response. One way is to do than is by:
{
error_page 401 = #error401
location #error401 {
...
proxy_set_header WWW-Authenticate ...
}

Jenkins/Nginx - Double prompted for basic auth, why? Why is there an internal Jenkins auth?

Below is my nginx configuration file for Jenkins. Most of it is exactly as per I've read in the documentation.
Config file:
upstream app_server {
server 127.0.0.1:8080 fail_timeout=0;
}
server {
listen 80;
listen [::]:80 default ipv6only=on;
server_name sub.mydomain.net;
location ^~ /jenkins/ {
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $http_host;
proxy_redirect off;
if (!-f $request_filename) {
proxy_pass http://app_server;
break;
}
auth_basic "[....] Please confirm identity...";
auth_basic_user_file /etc/nginx/.htpasswd;
}
}
When navigating to http://sub.mydomain.net/jenkins I get prompted for my basic auth with Server says: [....] Please confirm identify....
This is correct, but as soon a I enter the proper credentials I then get PROMPTED AGAIN for basic auth once again, but this time: Server says: Jenkins.
Where is this second hidden basic_auth coming from?! It's not making any sense to me.
Hitting CANCEL on the first prompt I then correctly receive a 401 authorization required error.
Hitting CANCEL on the second basic auth ("Server says: Jenkins") I get:
HTTP ERROR 401
Problem accessing /jenkins/. Reason:
Invalid password/token for user: _____
Powered by Jetty://
Does anyone know what's possibly going on?
Found the solution to my issue by searching for Nginx used as a reverse proxy for any other application with basic_auth.
Solution was the answer found here:
https://serverfault.com/questions/511846/basic-auth-for-a-tomcat-app-jira-with-nginx-as-reverse-proxy
The line I was missing from my nginx configuration was:
# Don't forward auth to Tomcat
proxy_set_header Authorization "";
By default, it appears that after basic auth Nginx will additionally forward the auth headers to Jenkins and this is what was leading to my issue. Jenkins receives the forwarded auth headers and then thinks it needs to authorize itself too?!
If we set our reverse proxy to not forward any authorization headers as shown above then everything works as it should. Nginx will prompt basic_auth and after successful auth we explicitly clear (reset?) the auth headers when forwarding to our reverse proxy.
I had this issue as well, in my case it was caused by having security enabled in jenkins itself, disabling security resolved the issue.
According to their docs:
If you do access control in Apache, do not enable security in Jenkins, as those two things will interfere with each other.
https://wiki.jenkins-ci.org/display/JENKINS/Apache+frontend+for+security
What seems to be happening is that nginx forwards the auth_basic response to jenkins, which attempts to perform auth_basic in response. I have not yet found a satisfying solution to the issue.

Very simple authentication using one-time cookie on nginx

I have a site intended only for private consumption by 3 coders. It's simple HTML served by nginx directly but intended for consumption inside and outside the office.
I want to have a simple password or authentication scheme. I could use HTTP auth but these tend to expire fairly often which makes it a pain for people to use. I'm also nervous it's much easier for someone to sniff than cookies.
So I'm wondering if I could just set a cookie on their browsers in JavaScript with a unique long ID and somehow tell nginx to only accept requests (for a particular subdomain) which has this cookie.
Is this simple enough to do? How do I
tell nginx to filter by cookie
in the browser, set a cookie that never expires?
There is a really quite simple looking solution that I found from a blog post by Christian Stocker. It implements the following rules:
If the user is on an internal IP, they are allowed.
If the user has a cookie set, they are allowed.
If neither matches, the user is presented with http basic authentication, and if they successfully authenticate a long term cookie is set
This is really the best of both worlds.
Here's the config:
map $cookie_letmein $mysite_hascookie {
"someRandomValue" "yes";
default "no";
}
geo $mysite_geo {
192.168.0.0/24 "yes"; #some network which should have access
10.10.10.0/24 "yes"; #some other network which should have access
default "no";
}
map $mysite_hascookie$mysite_geo $mysite_authentication{
"yesyes" "off"; #both cookie and IP are correct => OK
"yesno" "off"; #cookie is ok, but IP not => OK
"noyes" "off"; #cookie is not ok, but IP is ok => OK
default "Your credentials please"; #everythingles => NOT OK
}
server {
listen 80;
server_name mysite.example.org;
location / {
auth_basic $mysite_authentication;
auth_basic_user_file htpasswd/mysite;
add_header Set-Cookie "letmein=someRandomValue;max-age=3153600000;path=/"; #set that special cookie, when everything is ok
proxy_pass http://127.0.0.1:8000/;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $remote_addr;
}
}
To have Nginx filter by a cookie, you could perform some action if the cookie isn't present, and then your real action for the 3 people that have access, for example:
server {
listen 443 ssl http2; # Use HTTPS to protect the secret
...
if ($http_cookie !~ 'secretvalue') {
return 401;
}
location / {
# Auth'd behaviour goes here
}
}
And to set a cookie that never expires, fire up your browser's JavaScript console on a page that's on your server's hostname, and enter:
document.cookie = 'cookie=secretvalue;max-age=3153600000;path=/;secure';
That's technically not forever, but 100 years ought to do it. You can also use expires= for an absolute date in RFC1123 format if you're so inclined and can easily adjust the path if you need to. This also sets the secure flag on the cookie so it will only get sent over HTTPS.
There are also browser add-ons that will allow you to create arbitrary cookies, but all modern browsers have a JavaScript console.