Giter Site home page Giter Site logo

luzifer / nginx-sso Goto Github PK

View Code? Open in Web Editor NEW
281.0 12.0 41.0 27.45 MB

SSO authentication provider for the auth_request nginx module

License: Apache License 2.0

Go 92.88% HTML 5.25% Makefile 0.35% Shell 0.49% Dockerfile 1.03%
sso nginx golang yubikey atlassian-crowd ldap mfa duo google-authenticator totp

nginx-sso's Introduction

Go Report Card

Luzifer / nginx-sso

This program is intended to be used within the ngx_http_auth_request_module of nginx to provide a single-sign-on for a domain using one central authentication directory.

Documentation

In order to increase readability of the documentation it has been moved to the Github project Wiki. You can find everything previously documented in the README there.

nginx-sso's People

Contributors

luzifer avatar mxey avatar nyanloutre avatar zcalusic avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

nginx-sso's Issues

Supporting LDAPS

As it stands, ldaps connections fail to bind with a Unable to authenticate with manager_dn: unable to read LDAP response packet: unexpected EOF. OpenLDAP mentions the following:

TLS: can't accept: An unexpected TLS packet was received

From looking at the ldap package, there is a DialTLS function that provides the functionality of setting up a TLS connection.
https://github.com/go-ldap/ldap/blob/master/conn.go#L119

auth/oidc - Error 400 on fetching user info

nginx-sso-0.22.0

I got this error:

déc 09 14:05:20 vm-nginx nginx-sso[8258]: time="2019-12-09T14:05:20-05:00" level=error msg="Error while handling auth request" error="Unable to fetch user info: Unable to fetch user info: 400 Bad Request: {\"error\":\"invalid_request\",\"error_description\":\"Invalid token\",\"error_uri\":\"https:\\/\\/tools.ietf.org\\/html\\/draft-ietf-oauth-v2-31#section-7.2\"}"

I deleted my cookies and it was working again. It's possible that the problem is caused by the bad oauth server I use but would it be possible to make the user log in again if that error happens?

EDIT: I forgot to say that when this error happens, the user only gets a 500 error page. That's why I wanted the login to try again. But maybe that could cause an infinite loop.

MFA provider "google" is really "totp"

The MFA provider currently called "google" is just a TOTP implementation and doesn't have anything specifically to do with Google Authenticator. I use a different TOTP wallet app on my phone and haven't noticed any problem with it.

How about having "totp" as the canonical name for this provider and "google" as an alias? This would make it clearer what this provider actually works with.

no valid user found (google_auth)

Hello,

I'm fairly certain I'm missing something simple, what that something is escapes me. I configured the OAuth consent screen and Credentials from my GCP console. I'm testing with Nginx on host, nginx-sso in docker on the "sso" subdomain, a one pager on "www", a grafana docker on the "gf" subdomain, and "test.org" as the domain in local DNS (resolution to each subdomain works as expected).

My Nginx conf in sites-enabled looks like this:

map $http_upgrade $connection_upgrade {
    default upgrade;
    '' close;
}

server {

	listen 80;
	listen [::]:80;

	server_name www.test.org;

# I wasn't sure if the HTTPS redirect was causing an issue, so I commented it out.
#	if ($scheme = "http") {
#		return 301 https://$server_name$request_uri;
#	}


	listen 443 ssl;
	listen [::]:443 ssl;
        ssl_certificate /etc/ssl/test.org/local.crt;
        ssl_certificate_key /etc/ssl/test.org/local.key;
        ssl_protocols TLSv1.2 TLSv1.1 TLSv1;

	root /var/www/test/html;
	index index.html;

	# Redirect the user to the login page when they are not logged in
	error_page 401 = @error401;

	location / {

	    ## Optionally set a header to pass through the username
	    auth_request_set $username $upstream_http_x_username;
	    proxy_set_header X-User $username;

	    # Protect this location using the auth_request
	    auth_request /sso-auth;

	    # Automatically renew SSO cookie on request
	    auth_request_set $cookie $upstream_http_set_cookie;
	    add_header Set-Cookie $cookie;

		try_files $uri $uri/ =404;
	}

	location /logout {
		# Another server{} directive also proxying to http://127.0.0.1:8082
		return 302 https://sso.test.org/logout?go=$scheme://$http_host/;
	}

	location /sso-auth {
		# Do not allow requests from outside
		internal;
		# Access /auth endpoint to query login state
		proxy_pass https://sso.test.org/auth;
		# Do not forward the request body (nginx-sso does not care about it)
		proxy_pass_request_body off;
		proxy_set_header Content-Length "";
		# Set custom information for ACL matching: Each one is available as
		# a field for matching: X-Host = x-host, ...
		proxy_set_header X-Origin-URI $request_uri;
		proxy_set_header X-Host $http_host;
		proxy_set_header X-Real-IP $remote_addr;
		proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
		proxy_set_header X-Forwarded-Proto $scheme;
		proxy_set_header X-Application "www";
	}

	# Define where to send the user to login and specify how to get back
	location @error401 {
		# Another server{} directive also proxying to http://127.0.0.1:8082
		return 302 https://sso.test.org/login?go=$scheme://$http_host$request_uri;
	}

}

server {
	listen 80;
	listen [::]:80;

	server_name gf.test.org;

	if ($scheme = "http") {
		return 301 https://$server_name$request_uri;
	}


	listen 443 ssl;
	listen [::]:443 ssl;
        ssl_certificate /etc/ssl/test.org/local.crt;
        ssl_certificate_key /etc/ssl/test.org/local.key;
        ssl_protocols TLSv1.2 TLSv1.1 TLSv1;

	location / {
		proxy_http_version 1.1;
		proxy_set_header Upgrade $http_upgrade;
		proxy_set_header Connection $connection_upgrade;
		proxy_set_header Host $http_host;
		proxy_pass http://0.0.0.0:3000;
	}
	
}

server {
	listen 80;
	listen [::]:80;

	server_name sso.test.org;

	if ($scheme = "http") {
		return 301 https://$server_name$request_uri;
	}


	listen 443 ssl;
	listen [::]:443 ssl;
        ssl_certificate /etc/ssl/test.org/local.crt;
        ssl_certificate_key /etc/ssl/test.org/local.key;
        ssl_protocols TLSv1.2 TLSv1.1 TLSv1;

	location / {
		proxy_http_version 1.1;
		proxy_set_header Upgrade $http_upgrade;
		proxy_set_header Connection $connection_upgrade;
		proxy_set_header Host $http_host;
		proxy_pass http://0.0.0.0:8082;
	}
	
}

This is all well and good, if we comment out auth_request /sso-auth; from location / we can resolve each subdomain normally. When we try to do SSO with auth_request we get the redirect for the SSO login, follow the Google login prompt, and are then kicked back to the SSO login with the log output below.

{"event_type":"validate","headers":{"x-origin-uri":"/"},"remote_addr":"192.168.29.178","result":"no valid user found","timestamp":"2023-12-14T00:27:41Z"}
{"event_type":"validate","headers":{"x-origin-uri":"/?state=google_oauth\u0026code=4%2F0AfJohXkhLw11lpaiQKaiAVcu-gyji8FhzbghJBHOWPuLC0ftbVlQg56a_llknIEtCx70Xw\u0026scope=email+profile+https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.email+https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.profile+openid\u0026authuser=0\u0026prompt=consent"},"remote_addr":"192.168.29.178","result":"no valid user found","timestamp":"2023-12-14T00:27:56Z"}
{"event_type":"login_failure","go":"https://www.test.org/?authuser=0\u0026code=4%2F0AfJohXkhLw11lpaiQKaiAVcu-gyji8FhzbghJBHOWPuLC0ftbVlQg56a_llknIEtCx70Xw\u0026prompt=consent\u0026scope=email+profile+https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.email+https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.profile+openid\u0026state=google_oauth","headers":{},"reason":"invalid credentials","remote_addr":"172.24.0.1","timestamp":"2023-12-14T00:27:56Z"}

My config yaml is as shown (keys/hashes disregarded, as this is a local test instance):

---

login:
  title: "Nginx SSO - Login"
  default_method: "google_oauth"
  default_redirect: "https://www.test.org/"
  hide_mfa_field: false
  names:
    simple: "Username / Password"
    yubikey: "Yubikey"

cookie:
  domain: ".test.org"
  authentication_key: "ahh1bah9feejiW5ahnga3fieF9aec2ToovohY5ovaoNg2ookaequ8quu5oof"
  expire: 3600        # Optional, default: 3600
  prefix: "test-sso" # Optional, default: nginx-sso
  secure: true        # Optional, default: false

# Optional, default: 127.0.0.1:8082
listen:
  addr: "0.0.0.0"
  port: 8082

audit_log:
  targets:
    - fd://stdout
    - file:///var/log/nginx-sso/audit.jsonl
  events: ['access_denied', 'login_success', 'login_failure', 'logout', 'validate']
  headers: ['x-origin-uri', 'x-username']
  trusted_ip_headers: ["X-Forwarded-For", "RemoteAddr", "X-Real-IP"]

acl:
  rule_sets:
  - rules:
    - field: "x-host"
      regexp: ".*"
#    - field: "host"
#      equals: "www.test.org"
#    - field: "host"
#      equals: "gf.test.org"
#    - field: "x-origin-uri"
#      regexp: "^/api"
#    allow: ["luzifer", "@admins"]
    allow: ["maxxdoutvdub", "@_authenticated"]

# mfa:
#   yubikey:
#     # Get your client / secret from https://upgrade.yubico.com/getapikey/
#     client_id: "12345"
#     secret_key: "foobar"

#   duo:
#     # Get your ikey / skey / host from  https://duo.com/docs/duoweb#first-steps
#     ikey: "IKEY"
#     skey: "SKEY"
#     host: "HOST"
#     user_agent: "nginx-sso"

plugins:
  directory: ./plugins/

providers:
  # Authentication against an Atlassian Crowd directory server
  # Supports: Users, Groups
  # crowd:
  #   url: "https://crowd.example.com/crowd/"
  #   app_name: ""
  #   app_pass: ""

  # Authentication through OAuth2 workflow with Google Account
  # Supports: Users
  google_oauth:
    client_id: "79186546095-sol0fdirf6ud358ipueia0hk2f89ujdu.apps.googleusercontent.com"
    client_secret: "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"
    redirect_url: "https://www.test.org"
    # redirect_url: "https://www.test.org/sso-auth"

    # Optional, defaults to no limitations
    #require_domain: "example.com"
    # Optional, defaults to "user-id"
    # user_id_method: "user-id"
    # user_id_method: "full-email"
    # user_id_method: "local-part"

  # Authentication against (Open)LDAP server
  # Supports: Users, Groups
  # ldap:
  #   enable_basic_auth: false
  #   manager_dn: "cn=admin,dc=example,dc=com"
  #   manager_password: ""
  #   root_dn: "dc=example,dc=com"
  #   server: "ldap://ldap.example.com"
  #   # Optional, defaults to root_dn
  #   user_search_base: ou=users,dc=example,dc=com
  #   # Optional, defaults to '(uid={0})'
  #   user_search_filter: ""
  #   # Optional, defaults to root_dn
  #   group_search_base: "ou=groups,dc=example,dc=com"
  #   # Optional, defaults to '(|(member={0})(uniqueMember={0}))'
  #   group_membership_filter: ""
  #   # Replace DN as the username with another attribute
  #   # Optional, defaults to "dn"
  #   username_attribute: "uid"
  #   # Configure TLS parameters for LDAPs connections
  #   # Optional, defaults to null
  #   tls_config:
  #     # Set the hostname for certificate validation
  #     # Optional, defaults to host from the connection URI
  #     validate_hostname: ldap.example.com
  #     # Disable certificate validation
  #     # Optional, defaults to false
  #     allow_insecure: false

  # # Authentication through OAuth2 workflow with OpenID Connect provider
  # # Supports: Users
  # oidc:
  #   client_id: ""
  #   client_secret: ""
  #   # Optional, defaults to "OpenID Connect"
  #   issuer_name: ""
  #   issuer_url: ""
  #   redirect_url: "https://login.luifer.io/login"

  #   # Optional, defaults to no limitations
  #   require_domain: "example.com"
  #   # Optional, defaults to "subject"
  #   user_id_method: "full-email"


  # # Authentication against embedded user database
  # # Supports: Users, Groups, MFA
  # simple:
  #   enable_basic_auth: true

  #   # Unique username mapped to bcrypt hashed password
  #   users:
  #     # luzifer: "$2a$10$FSGAF8qDWX52aBID8.WpxOyCvfSQ3JIUVFiwyd1jolb4jM3BzJmNu"
  #     maxxd: "$2y$10$z/1UXkBPoBQ2z4OYbYTNDuPSLiiDkVCz.JvlT7FBEobsd07.ymLAy"

  #   # Groupname to users mapping
  #   groups:
  #     admins: ["luzifer"]

  #   # MFA configs: Username to configs mapping
  #   mfa:
  #     luzifer:
  #       - provider: duo

  #       - provider: totp
  #         attributes:
  #           secret: MZXW6YTBOIFA  # required
  #           period: 30            # optional, defaults to 30 (Google Authenticator)
  #           skew: 1               # optional, defaults to 1 (Google Authenticator)
  #           digits: 8             # optional, defaults to 6 (Google Authenticator)
  #           algorithm: sha1       # optional (sha1, sha256, sha512), defaults to sha1 (Google Authenticator)

  #       - provider: yubikey
  #         attributes:
  #           device: ccccccfcvuul

  # # Authentication against embedded token directory
  # # Supports: Users, Groups
  # token:
  #   # Mapping of unique token names to the token
  #   tokens:
  #     tokenname: "MYTOKEN"

  #   # Groupname to token mapping
  #   groups:
  #     mytokengroup: ["tokenname"]

  # # Authentication against Yubikey cloud validation sehttps://www.test.orgrvers
  # # Supports: Users, Groups
  # yubikey:
  #   # Get your client / secret from https://upgrade.yubico.com/getapikey/
  #   client_id: "12345"
  #   secret_key: "foobar"

  #   # First 12 characters of the OTP string mapped to the username
  #   devices:
  #     ccccccfcvuul: "luzifer"

  #   # Groupname to users mapping
  #   groups:
  #     admins: ["luzifer"]

...

In an attempt to suss out a misconfig, I tried to disable google_auth and try simple. In this case, I click the login button, saw a flash of what looks like username/password prompt, but got kicked right back to first login screen. Now it's no secret that skull, at any given time, is populated with 30-40% igneous rock and 60-70% gray matter, but the latter told me to run it through Burp Proxy, intercept the requests, and see if I can catch the login screen. No joy, which makes me think that a red herring and a greater underlying misconfig is present.

My authorized redirect in GCP is set to https://www.test.org as is my default_redirect above. I think this is correct? The first log entry indicates "no valid user found", though my gmail address is set in the Test users section of the OAuth consent screen config on GCP. Then in the second log entry "invalid credentials" presumably relates to that?

Are there any additional details I could provide or suggested tests?

LDAP uid mapping DN

Hello!

I was fiddling around with this (awesome!) but there is one thing that might be a possible option: LDAP DN. I already had a solution in place that returned username, so when I'm doing proxy_set_header X-WEBAUTH-USER $username; (grafana), I got the whole userdn: uid=myuser,ou=people,dc=foo,dc=org - but since I had already used just the username (myuser) the mapping was messed up. While l like the possibility of using the whole LDAP DN in the rules, just mapping the DC was a bit bad for me, so having an option of just getting the username back in X-username would be nice.

For anyone curious, I implemented it with a map in nginx for now:

map $x_username $username{
  ~^uid=(?<uid>.*),ou=people,dc=foo,dc=org $uid;
}

location / {
    auth_request /sso-auth;
    auth_request_set $x_username $upstream_http_x_username;
    proxy_set_header X-WEBAUTH-USER $username;
}

TL;DR: $x_username is mapped to $username

LDAP documentation confusion (user attr vs DN)

The LDAP auth documentation states:

When using the LDAP provider you need to pay attention when writing your ACL. As DNs are used as names for users and groups you also need to specify those in the ACL

However, looking through the code I see that /auth works by calling DetectUser which in the case of LDAP returns alias. ACLs are then applied based on that value. If username_attribute is set to, say, uid, then I would expect based on the code the ACLs would be applied to the value of uid and not dn.

I'm wondering if I'm misinterpreting this or whether this is an omission in the documentation. It seems like an important aspect.

Add MFA support for Crowd auth provider

As a system administrator using Crowd as an auth backend for nginx-sso I want to be able to configure MFA inside the Crowd backend using attributes.

Acceptance criteria:

  • It should be possible to have users with and without MFA configured
  • Configuration must be fully done in the backend

Logging very picky?

I've copied your logging config 1:1, but set it to log to file:///var/log/nginx-sso/audit.json, which didn't work. For some reason, it only works if i use file:///var/log/nginx-sso/audit.jsonl (notice the l at the end).

Supporting Anonymous Bind for LDAP

Some LDAP configurations allow searching without having to authenticate (i.e. only require an anonymous bind). This is not supported by nginx-sso.

The go/ldap's Bind() method does not allow for anonymous bind, as per https://github.com/go-ldap/ldap/blob/master/bind.go#L112 it is suggested to use UnauthenticatedBind() instead for that.

As a catch-both option, you could use the SimpleBind method that both Bind and UnauthenticatedBind use, with AllowEmptyPassword set to True. This will mimic the UnauthenticatedBind() functionality, while allowing for binding with username and password.

How do I use the preferred_username for OIDC connect

I'm using kanidm as my OIDC provider.

In the OIDC "subject" field (sub), kanidm uses a UUID for this field and uses the preferred_username for a fully qualified username (e.g. [email protected]).

I would like to use this field instead, since not all users need to have an email address defined in their profile.

I have tried using subject, full-email and local-part options for the oidc.user_id_method

I would also like to be able to use the scopes field to map to the @groupname.

Token Auth / XHR

Hi,

probably not an issue but a misconfiguration:

I'm trying to perform a token-based authentication with a XMLHttpRequest, but I cant get it to work. This is the request:

xhr = new XMLHttpRequest();
xhr.open('GET',url);
xhr.setRequestHeader('Authorization', 'Token MYTOKEN');
xhr.withCredentials = true;
xhr.send(null);

Apparently, a cookie is set, but the prefix is nginx-sso-main - when I use the login page, it's nginx-sso-ldap. The log then states "no valid user found".

The config.yaml looks like this:

acl:
rule_sets:
...
allow:
- "@_authenticated"
...
token:
tokens:
test: "MYTOKEN"

Also, I'm wondering which endpoint to use in the URL. When it's https://login.example.org/login two requests are made to either /login and /debug

Cheers
Tobias

allow nginx-sso in path

vouch-proxy allows this by providing a document_root variable in it's config

Since I couldn't find any such variable in nginx-sso 's config, I tried mapping nginx-sso at /nginx-sso of my server, but it keeps throwing 302.

When i map /nginx-auth -> localhost:8082/auth it works, but mapping /nginx-sso -> localhost:8082, and putting auth_request /nginx-sso/auth it throws 302. I suppose it would need to know it's been mapped at /nginx-sso somehow, in order to allow this, like vouch proxy's document_root variable

I want to put it in path instead of a separate server directive so that I can avoid having 2 dns entries, and 2 set of certs for a simple app I am working on.

Sorry if it's already there and I missed it.
Thank You!!

search group membership use uid instead of userDN in LDAP provider

This tool is almost like a Swiss army knife for authentication. I can put it in front of almost everything. Only problem is that the group in my ldap server use uid to list members instead of using DN. I can't find a way to make it work. I would really like to use uid attribute to lookup for the groups of a member.

cp: can't create directory '/data/frontend': Permission denied

Hi, i think you broke the Docker image a bit, as i get this error when trying to start it:

docker run --rm docker.io/luzifer/nginx-sso:v0.27.0
cp: can't create directory '/data/frontend': Permission denied

I also noticed that you're using -d in docker-start.sh to check if the file /data/frontend/index.html exists - shouldn't that be -f then?

And lastly, i'm using my own frontend, so when mounting /etc/nginx-sso to /data, i get this:

cp: can't create '/data/frontend/index.html': File exists

Everything works as expected with 0.26.0, though!

404 at /

Hi there,
if you visit the root domain of the container (https://login.luzifer.io/) you get a 404 error.
It would be cool to have an option to configure a redirect to another page.

If you like this idea, let me know. I would be happy to have a task to learn some golang :)

Logging of auth requests

Hello @Luzifer and thanks for your work on this absolutely amazing utility and great documentation. 👍

Took me only 2 hours to migrate half a dozen services behind SSO, a long overdue task made really simple by using nginx-sso. ❤️

Here's what I feel is missing: logging of both successful and unsuccessful auth requests. Nginx-sso now being central place for authorization, I feel it is quite important to have such a log available.

So far I tried setting log level to debug (from default info) level, but even in that case, there's nothing logged?!

More detailed getting started documentation/examples.

Less of an issue, and more of an offer of an enhancement. I implemented nginx-sso today, and, well, it's awesome. Thank you! Showed it to a friend who would more than likely have thought it was awesome as well, and he asked for some pointers, so I wrote a slightly more detailed How-to document. I've linked it below, and you're more than welcome to steal some or all of it as you see fit for your documentation on the wiki pages here on GitHub.

https://gist.github.com/mjbnz/b402edf819a69e517b0c59710f291da9

Thanks,
Mike.

Taking allowed users from Header

Hi.

Thanks a lot for this tool, it is very useful for me. However, there's one feature I'd like to have which would be helping me a lot:

I'd like to automatically set ACLs for users, but without being able to modify nginx-sso's config. Currently I'm doing this via nginx' config:

location /nginx-sso-auth {
    internal;
    proxy_pass http://nginx-sso/auth;
    ...
    proxy_set_header X-Allow "bob";
}

and in nginx-sso config:

acl:
  rule_sets:
    - rules:
      - field: "x-allow"
        equals: "bob"
      allow:
        - "bob"
    - rules:
      - field: "x-allow"
        equals: "@staff"
      allow:
        - "@cn=staff,ou=groups,dc=company"

This does work, but isn't really comfortable - I have to create a rule set for each user or group or combination of those.

Would it be possible, do add something like this:

acl:
  rule_sets:
    - rules:
      allow:
        - $x-allow

That way, one could put all users allowed to access an resource into the X-Allow header and leave the rest to nginx-sso.

Is it possible to use OpenID roles/attributes?

I'm using keycloak for OpenID. I can create groups/roles and set attributes to users.

Is there any way to use those with the ACLs or maybe pass them to my application server with proxy pass?

Improper port handling in LDAP

When attempting to connect to an LDAP server with a nonstandard port number, I get the following in the logs:

time="2019-01-17T19:01:24Z" level=error msg="Error while handling auth request" error="Unable to connect to LDAP: LDAP Result Code 200 \"Network Error\": dial tcp: address localhost:3389:3389: too many colons in address"

A quick glance at the code reveals the problem, in auth_ldap.go in dial(), the host variable has the colon and port number in it already, and gets the port number appended a second time by the Sprintf calls.

access_denied after successful login

Hi,

I am trying out this project with the configuration from https://github.com/Luzifer/nginx-sso/wiki/Nginx-Reverse-Proxy-for-homelab-services-using-SSO (so far I did not even change the password of the user). But I always get a 403 from the proxied application back.

My yaml looks like the following:

---

login:
  title: "Login"
  default_method: "simple"
  hide_mfa_field: true
  names:
    simple: "Username / Password"

cookie:
  domain: ".domain.com"
  # You'll want to regenerate this. Use something like: cat /dev/urandom | tr -dc 'A-Za-z0-9' | dd bs=1 count=60
  authentication_key: "nottheactualkey"

listen:
  addr: "0.0.0.0"
  port: 8082

audit_log:
  targets:
    #- fd://stdout
    - file:///var/log/nginx-sso/audit.jsonl
  events: ['access_denied', 'login_success', 'login_failure', 'logout', 'validate']
  headers: ['x-origin-uri']
  trusted_ip_headers: ["X-Forwarded-For", "RemoteAddr", "X-Real-IP"]

providers:
  simple:
    enable_basic_auth: false
    users:
      # This password is 'admin'. Use this to generate a new password:
      #      htpasswd -BnC 10 ""
      admin: "$2y$10$3aJxJ6ttJNPeky/bCdg1OOVvGU8pLVj9L.U9kN0F0JWLN.nt3b5WO"

Logging:

==> /var/log/nginx/access.log <==
192.168.178.140 - - [21/Jun/2020:20:53:43 +0200] "GET /homer/ HTTP/1.1" 403 548 "https://login.domain.com/login?go=https://app.domain.com:8443/homer/" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.106 Safari/537.36"
192.168.178.140 - - [21/Jun/2020:20:53:43 +0200] "GET /favicon.ico HTTP/1.1" 403 548 "https://app.domain.com:8443/homer/" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.106 Safari/537.36"

==> /var/log/nginx/login.yourdomain.com_access.log <==
1.1.1.1 - - [21/Jun/2020:20:53:43 +0200] "POST /login HTTP/1.1" 302 0 "https://login.domain.com/login?go=https://app.domain.com:8443/homer/" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.106 Safari/537.36"

==> /var/log/nginx-sso/audit.jsonl <==
{"event_type":"login_success","go":"https://app.domain.com:8443/homer/","headers":{},"remote_addr":"127.0.0.1","timestamp":"2020-06-21T18:53:43Z"}
{"event_type":"access_denied","headers":{"x-origin-uri":"/homer/"},"remote_addr":"192.168.178.140","timestamp":"2020-06-21T18:53:43Z","username":"admin"}
{"event_type":"access_denied","headers":{"x-origin-uri":"/favicon.ico"},"remote_addr":"192.168.178.140","timestamp":"2020-06-21T18:53:43Z","username":"admin"}

I am getting the feeling I have a general misunderstanding here as I am unable to spot why the second requests does not get through.

Multiple ldap providers

Hi!

Is it possible to setup multiple ldap providers? I assume that if I do "ldap:" multiple times it would just overwrite the value, right?

Possible documentation improvements

Just some small nitpicks about documentation:

  • logout method is not documented, while easy to implement, should be mentioned (didn't even know it existed, until you mentioned it in another ticket). Maybe an additional skeleton server block which better reflects how login/logout are to be handled?

  • nginx.conf uses X-Host & X-Original-URI as an example additional headers, but then SSO config.yaml refers to fields host and x-origin-uri (notice the difference). Might save somebody time if those are brought in sync, so everything works almost out of the box.

Multiple Domains

Hi,

is it possible to "unlock" multiple domains with one user?

I'm already trying to get that work with the Config, unfortunately it does not work as desired.

cookie:
  domain: ".pixx.xcom"
  authentication_key: "xxxxxxxxxxxxxxxxxxxxx"
  expire: 3600        # Optional, default: 3600
  prefix: "nginx-sso" # Optional, default: nginx-sso
  secure: true        # Optional, default: false
  cookie:
  domain: "fnm.exxx.net"
  authentication_key: "xxxxxxxxxxxxxxxx"
  expire: 3600        # Optional, default: 3600
  prefix: "nginx-sso" # Optional, default: nginx-sso
  secure: true        # Optional, default: false

Would this be correct, or can someone help me here please :) ?

Creating plugin

Hello there,
I'm trying to create a plugin, but i get the following error when i load nginx-sso container.

Starting nginx-sso
 time="2020-03-12T15:19:04Z" level=error msg="Unable to open plugin" error="plugin.Open(\"/data/plugins/auth-apachepasswd\"): plugin was built with a different version of package golang.org/x/crypto/blowfish" plugin=auth-apachepasswd.so

Now this is one of my first go items i'm working on.
I have configured the exact same version of the plugin, copied over the stuff in vendor, using -mod=vendor etc.. etc.. but i can't get it to work.

I'm using an external module from https://github.com/BlackDex/go-htpasswd where i have put everything the same regarding versions. But i still can't get it to work.

Do you have any suggestions or pointers?

Move documentation from README to Wiki

In order to have a better maintainability, the chance to introduce some structure (sub-pages) and improve the readabilty, the documentation should be moved out of the README.md file and should go into the Wiki.

Allow anonymous access

It would be helpful to have a special group like @_authenticated, perhaps @_anonymous, that allowed an acl rule to be written to allow access without logging in.

This could be useful for the purposes of allowing access to a known IP and/or IP range (from RFC1918 ranges perhaps?), or where a non-human entity requires access but is unable to log in using the login page.

Feature-Request: Reload config on SIGHUP

It would be great if one could trigger a reload of the config file by sending a signal like SIGHUP to the nginx-sso process.

This would, for example, be pretty useful for automatic creation of config files on Docker events. (Automatic generation of a config file based on container labels using docker-gen, for example.)

Add ARM support

I have used this program for 2 years on x86 but now, for reasons of reliability, I have to migrate it to an ARM (Rpi).
Since the NGINX part works on ARM (SWAG image by Linuxserver.io), I was hoping to be able to keep the SSO part as well (instead of splitting it on another x86 machine).
Thank you

args cut off on login

hi,
if the $request_uri contains several args in the query string, apparently all but the first one are cut off after using the /login endpoint, although they were still present/shown in the "go=" parameters.

cheers
tobias

Default cookie values not set on authentication cookies

Hi!

I noticed that my auth sessions did not seem to expire so I decided to take a look at the cookies.

It seems that if "expires" and/or "prefix" is not specified in the config file, the default cookie values are applied to the main cookie "nginx-sso-main", but not the authentication cookies (In my case "nginx-sso-simple").

This is how the main and simple cookies look with "expire" and "prefix" unset:

nginx-sso-main:"MTU1NzkyMzg5OHxEdi1CQkFFQ180SUFBUkFCRUFBQVJ2LUNBQUVHYzNSeWFXNW5EQVFBQW1kdkJuTjBjbWx1Wnd3c0FDcG9kSFJ3Y3pvdkwyRmtiV2x1TG1keWRXNWtjM1J5YjIwdVkyeHZkV1F2WTJkcExXSnBiaTloWTJZPXzPO_uS-NiZJdUeV3hX_T9PWRcRWqQzWy1w9drXZWJrNg=="
  CreationTime:"Wed, 15 May 2019 12:38:23 GMT"
  Domain:".grundstrom.cloud"
  Expires:"Wed, 15 May 2019 13:38:23 GMT"
  HostOnly:false
  HttpOnly:true
  LastAccessed:"Wed, 15 May 2019 12:38:23 GMT"
  Path:"/"
  Secure:true
  sameSite:"Unset"

-simple:"MTU1NzkyMzkzNnxEdi1CQkFFQ180SUFBUkFCRUFBQUpQLUNBQUVHYzNSeWFXNW5EQVlBQkhWelpYSUdjM1J5YVc1bkRBZ0FCbUZzYldkeWRRPT18fH_szi7DkzLjKg8_Mlq4dlSrZpUisiE1qN6p2bVTheI="
  CreationTime:"Wed, 15 May 2019 12:39:01 GMT"
  Domain:".grundstrom.cloud"
  HostOnly:false
  HttpOnly:true
  LastAccessed:"Wed, 15 May 2019 12:39:01 GMT"
  Path:"/"
  Secure:true
  sameSite:"Unset"

..and with the default values expire: 3600 and prefix: "nginx-sso" explicitly set:

nginx-sso-main:"MTU1NzkyNDQxM3xEdi1CQkFFQ180SUFBUkFCRUFBQVJ2LUNBQUVHYzNSeWFXNW5EQVFBQW1kdkJuTjBjbWx1Wnd3c0FDcG9kSFJ3Y3pvdkwyRmtiV2x1TG1keWRXNWtjM1J5YjIwdVkyeHZkV1F2WTJkcExXSnBiaTloWTJZPXwDS01tYBNt35XyOWZ2ODkbw_kkEOa_1xwEjrOU2KGtzg=="
  CreationTime:"Wed, 15 May 2019 12:46:58 GMT"
  Domain:".grundstrom.cloud"
  Expires:"Wed, 15 May 2019 13:46:58 GMT"
  HostOnly:false
  HttpOnly:true
  LastAccessed:"Wed, 15 May 2019 12:46:58 GMT"
  Path:"/"
  Secure:true
  sameSite:"Unset"

nginx-sso-simple:"MTU1NzkyNDQyNXxEdi1CQkFFQ180SUFBUkFCRUFBQUpQLUNBQUVHYzNSeWFXNW5EQVlBQkhWelpYSUdjM1J5YVc1bkRBZ0FCbUZzYldkeWRRPT18piKft7TC3bE-C20oakYgKMWaCLB7NRNR9QjfDPbwmeU="
  CreationTime:"Wed, 15 May 2019 12:47:11 GMT"
  Domain:".grundstrom.cloud"
  Expires:"Wed, 15 May 2019 13:47:11 GMT"
  HostOnly:false
  HttpOnly:true
  LastAccessed:"Wed, 15 May 2019 12:47:11 GMT"
  Path:"/"
  Secure:true
  sameSite:"Unset"

change Bootstrap theme via config

Hey, it would be great if the Bootstrap theme would be configurable in the config file. I am a dark mode fan, so I change the theme after every update to a dark theme manually, because my changes in index.html are overwritten.

So it would be really great if thees lines would be accessible over the config file:

<!-- Bootstrap -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/dist/sandstone/bootstrap.min.css"
integrity="sha256-qgpZ1V8XkWmm9APL5rLtRW+Tyhp+0TPKJm4JMprrSOw=" crossorigin="anonymous">

as an optional argument. In this way it would be also possible to self host the theme so you don't depend on jsdelivr for the login page. (I needed some time to find out, that I blocked jsdelivr in PiHole by mistake.)

Thank you very much for your work and this great pice of software, which protect my personal web apps. Danke!

Add MFA support for LDAP auth provider

As a system administrator using LDAP as an auth backend for nginx-sso I want to be able to configure MFA inside the LDAP backend.

Acceptance criteria:

  • It should be possible to have users with and without MFA configured
  • Configuration must be fully done (except field name being specified in config) in the backend

Add user name to Nginx header

Hi, I start using your tool and it is a really useful tool, thank you for it.
But I need to put the user name who was authenticated to the Nginx header.
Is there some chance how to do it? I am not so experienced with Nginx.
Thank you.

Can access sso protected webapp after logout. not sure why

Hi!
i'm trying to use simple auth.

nginx as reverse proxy on host machine:
  nas.home.arpa

some containers in rootless podman:
  login.nas.home.arpa - nginx-sso
  banner.nas.home.arpa - serving short ascii banner for tests
  speed.nas.home.arpa - librespeed

on nas.home.arpa there are some links to my containerized services,
  nothing fancy, just simple html + css page.

#########################################

step 1:
  goto nas.home.arpa, press sso protected link banner.nas.home.arpa (DON'T go to speed.nas.home.arpa)

step 2:
  nginx-sso login screen appears. enter correct username/password pair.
  opens my web app page. cool!

step 3:
  press "logout" link on nas.home.arpa

step 4:
  let's check.
  open speed.nas.home.arpa - nginx-sso login screen appears. good.
  open banner.nas.home.arpa - banner page loads. what?
  reload (F5) banner.nas.home.arpa - finally nginx-sso login screen.

so basically if you've visited that web app earlier you can load it again even after logging out.
you can load it a few times using links on page or by manually entering web app address in browser's addressbar.
am i doing something wrong?

about logs:
  first two entries in log shows me accessing web app while logged in
  third entry - logout
  nothing logged when succesfully accessing web app page after logout
  fourth entry - manually reloading web app page

#########################################


Here are my configs:
#1. nginx-sso config.yaml
#2. nginx-sso logs prettified
#3. nginx config
#4. nginx include config for nginx-sso


#####1. cat filesout/config.yaml

---
login:
  title: "Login to nas.home.arpa"
  default_method: "simple"
  default_redirect: "https://nas.home.arpa/"
  hide_mfa_field: true
  names:
    simple: "Username / Password"
cookie:
  domain: ".nas.home.arpa"
  authentication_key: "strippedfdhstfhgcnfht"
  expire: 3600
  prefix: "nginx-sso"
  secure: true
listen:
  addr: "0.0.0.0"
  port: 8082
audit_log:
  targets:
    - file:///data/log/sso_audit.jsonl
  events: ['access_denied', 'login_success', 'login_failure', 'logout', 'validate']
  headers: ['x-host', 'x-origin-uri', 'x-real-ip', 'x-forwarded-for']
  trusted_ip_headers: ["X-Forwarded-For", "RemoteAddr", "X-Real-IP"]
acl:
  rule_sets:
  - rules:
    - field: "x-host"
      equals: "speed.nas.home.arpa"
    allow: ["tuser", "@admins"]
  - rules:
    - field: "x-host"
      equals: "banner.nas.home.arpa"
    allow: ["tuser", "@admins"]
providers:
  simple:
    enable_basic_auth: false
    users:
      tuser: "$2y$10$strippedsdgkjhjasadgkflm"
    groups:
      admins: ["tuser"]
...

#####2. cat filesout/logs_sso_pretty

{
    "event_type": "validate",
    "headers": {
        "x-forwarded-for": "192.168.0.6",
        "x-host": "banner.nas.home.arpa",
        "x-origin-uri": "/",
        "x-real-ip": "192.168.0.6"
    },
    "remote_addr": "192.168.0.6",
    "result": "valid user found",
    "timestamp": "2022-05-27T01:07:21+03:00",
    "username": "tuser"
}

{
    "event_type": "validate",
    "headers": {
        "x-forwarded-for": "192.168.0.6",
        "x-host": "banner.nas.home.arpa",
        "x-origin-uri": "/",
        "x-real-ip": "192.168.0.6"
    },
    "remote_addr": "192.168.0.6",
    "result": "valid user found",
    "timestamp": "2022-05-27T01:07:23+03:00",
    "username": "tuser"
}

{
    "event_type": "logout",
    "headers": {},
    "remote_addr": "10.0.2.100",
    "timestamp": "2022-05-27T01:07:29+03:00"
}

{
    "event_type": "validate",
    "headers": {
        "x-forwarded-for": "192.168.0.6",
        "x-host": "banner.nas.home.arpa",
        "x-origin-uri": "/",
        "x-real-ip": "192.168.0.6"
    },
    "remote_addr": "192.168.0.6",
    "result": "no valid user found",
    "timestamp": "2022-05-27T01:09:40+03:00"
}


#####3. cat filesout/nginx.conf

user					http;
worker_processes			4;
events {
	worker_connections		1024;
}
http {
	types_hash_max_size		4096;
	charset				utf-8;
	sendfile			on;
	tcp_nopush			on;
	tcp_nodelay			on;
	server_tokens			off;
	client_max_body_size		32M;
	proxy_cache			off;
	include				mime.types;
	default_type			application/octet-stream;
	error_log			/var/log/nginx/error.log notice;
	access_log			/var/log/nginx/access.log;
	keepalive_timeout		65;
	ssl_ciphers			"EECDH+AESGCM";
	ssl_protocols			TLSv1.2 TLSv1.3;
	ssl_prefer_server_ciphers	on;
	ssl_certificate			/etc/nginx/keys/nascert.crt;
	ssl_certificate_key		/etc/nginx/keys/nascert.pem;
	server {
		listen			80 default_server;
		server_name		"";
		return			301 https://$host$request_uri;
	}
	server {
		listen			443 ssl;
		server_name		localhost;
		ssl_session_cache	shared:SSL:1m;
		ssl_session_timeout	5m;
		location / {
			root			/srv/http/general;
			index			index.html;
		}
		location /sso-logout {
			return			302 https://login.nas.home.arpa/logout?go=https://nas.home.arpa/;
		}
	}
	server {
		listen			443 ssl;
		server_name		login.nas.home.arpa;
		access_log		/var/log/nginx/site_login.nas.home.arpa_access.log;
		error_log		/var/log/nginx/site_login.nas.home.arpa_error.log;
		location / {
			proxy_pass		http://127.0.0.1:8082;
			proxy_cache		off;
		}
	}
	server {
		listen			443 ssl;
		server_name		speed.nas.home.arpa;
		include			/etc/nginx/ssoinclude.conf;
		access_log		/var/log/nginx/site_speed.nas.home.arpa_access.log;
		error_log		/var/log/nginx/site_speed.nas.home.arpa_error.log;
		location / {
			auth_request_set	$cookie $upstream_http_set_cookie;
			add_header		Set-Cookie $cookie;
			proxy_pass		http://127.0.0.1:8034;
			proxy_set_header	X-Real-IP $remote_addr;
			proxy_set_header	X-Forwarded-For $proxy_add_x_forwarded_for;
		}
	}
	server {
		listen			443 ssl;
		server_name		banner.nas.home.arpa;
		include			/etc/nginx/ssoinclude.conf;
		access_log		/var/log/nginx/site_banner.nas.home.arpa_access.log;
		error_log		/var/log/nginx/site_banner.nas.home.arpa_error.log;
		location / {
			auth_request_set	$cookie $upstream_http_set_cookie;
			add_header		Set-Cookie $cookie;
			proxy_pass		http://127.0.0.1:1234;
		}
	}
}

#####4. cat filesout/ssoinclude.conf

auth_request /sso-auth;
error_page 401 = @error401;
location /sso-auth {
    internal;
    proxy_pass http://127.0.0.1:8082/auth;
    proxy_pass_request_body off;
    proxy_set_header Content-Length "";
    proxy_set_header X-Origin-URI $request_uri;
    proxy_set_header X-Host $http_host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
}
location /sso-logout {
    return 302 https://login.nas.home.arpa/logout?go=https://$http_host/;
}
location @error401 {
    return 302 https://login.nas.home.arpa/login?go=https://$http_host$request_uri;
}

Multiple rulesets not working as expected

Hello,

I have the below ACL

acl:
  rule_sets:
  - rules:
    - field: "X-Host"
      equals: "ssh.ti***.co.uk"
    allow: ["@admins"]


  rule_sets:
  - rules:
    - field: "X-Host"
      equals: "netbox.ti***.co.uk"
    allow: ["@netbox"]

However only the netbox host allows connection, when logged in as a member of the admin group i always get a forbidden on the ssh subdomain, If i comment out the netbox entry the ssh works as expected, am i doing something wrong or is this a bug?

Add support for OpenID Connect

Add support for OpenID Connect and Oauth2. This would make nginx-sso a great option to replace bitly/oauth2_proxy that is quite popular but unmaintained.

Allow anonymous connections on a regex path

As it stands, you can only allow anonymous access with "equals" in the ACL, not "regex". I want to disable the auth on a /api/* but I can't as the rule seems to be ignored.

acl:
  rule_sets:
  - rules:
    - field: "X-API"
      equals: "1"
    - field: "X-Origin-URI"
      regex: "^/api"
    allow:
      - "@_anonymous"
  - rules:
    - field: "X-Host"
      regexp: ".*"
    allow: ["@admins"]

That would rule set basically allows all access to the entire site if X-API is set to 1. Ideally, the following would also work:

acl:
  rule_sets:
  - rules:
    - field: "X-Origin-URI"
      regex: "^/api"
    allow:
      - "@_anonymous"
  - rules:
    - field: "X-Host"
      regexp: ".*"
    allow: ["@admins"]

This would allow anonymous/public access to /api without any type of auth or header to be set.

Support for PAM authentication

Hello!

Would it be possible to add support for PAM-based authentication? In other words, I'd like to be able to enter the username and password of a unix account on the same server to login. Thanks!

Support HAProxy SPOE

HAProxy does not support external-auth like NGINX, but has its own extension mechanism SPOE. It is a different protocol and not based on HTTP. I would like to standardize my authentication on nginx-sso, so it would be really cool if nginx-sso could be used with HAProxy. Do you consider this out of scope or would you accept a pull request for this? There is a Go library implementing the protocol.

Map arbitrary values from auth to headers

Just an idea, but a future feature could be that map things like LDAP data such as email, cn, whatnot to headers, such as X-email, X-cn or so, then a simple SSO request could provide that data as well, just like a proper identity provider could do.

Google Federation

Do you think it would be possible to add google federation to the list authentication backends?

edit: for clarity I plan on doing the work =)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.