Luzifer / nginx-sso

SSO authentication provider for the auth_request nginx module
Apache License 2.0
286 stars 41 forks source link

no valid user found (google_auth) #84

Closed TS-development closed 10 months ago

TS-development commented 10 months ago

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?

TS-development commented 10 months ago

After rereading the Wiki a dozen times or so I answered my own question.

This:

    redirect_url: "https://www.test.org"

Is supposed to be this:

    redirect_url: "https://sso.test.org/login"

It took a minute for this to process "The redirect_url must point back to the /login path of nginx-sso to complete the login process.", but it works as expected now!