NHAS / wag

Simple Wireguard 2FA
BSD 3-Clause "New" or "Revised" License
499 stars 27 forks source link

Feature request: support authentication using an oauth2 client #19

Closed olekristoffer closed 1 year ago

olekristoffer commented 1 year ago

Hi

Thank you for a great project, it is really useful.

Would it be possible for to extend wag to support user authentication using OpenID Connect / oauth 2.0 protocol? This would add the benefit of both simplifying the user managment and allow for a rich set of MFA solutions in addtion to supporting single sign-on from different providers. Authentication could be implemented using locally hosted services, e.g. by using implementations such as keycloak, or provided by trusted third parties for example google or github?

https://github.com/coreos/go-oidc https://pkg.go.dev/golang.org/x/oauth2#section-readme

examples: https://fusionauth.io/blog/2020/10/22/securing-a-golang-app-with-oauth https://medium.com/@pliutau/getting-started-with-oauth2-in-go-2c9fae55d187

NHAS commented 1 year ago

Hi there @olekristoffer,

I think this is a great idea! However I cant promise it any time soon as it would require a substantial amount of work to reorganize how I manage user identities (which Im planning on doing as I want this to be easier to manage).

So yes! Hopefully! Maybe! Soon :P

paulb-smartit commented 1 year ago

Would be a big change. A move from device based auth to user based auth, or a combination of the two.

Right now I like its relative simplicity.

NHAS commented 1 year ago

Well,` one step closer to this now as the most recent version separates device from user. Gonna add Webauthn support before I get to this however.

NHAS commented 1 year ago

Wag now has webauthn, and the changes I've made to facilitate that have made adding this almost trivial, so I'll be looking in to adding this real soon.

My main concern with this change is that you'll still have to define your policies for what authorised users can access in the wag configuration file itself. So it'd be rather split in terms of management, users could be managed with but the actual route access wouldnt be

NHAS commented 1 year ago

My current plan of attack is to use OIDC with a groups claim where the groups will match with the polices defined in the config.json this means that all management will be able to be done centrally for the most part, bar tweaking the access policies.

Unfortunately this will not support googles IdP as for some reason they dont support the group claims that other identity providers do, so its not possible to map user groups to oidc assertion (and Im not writing a special case just for google)

paulb-smartit commented 1 year ago

Nice. We use Keycloak for OAuth/IdP so having this support would be awesome. It moves all our authentication into a common mechanism and makes the use of existing groups really useful.

Looking forward to this.

NHAS commented 1 year ago

Ha! Well good, ironically I'm using keycloak to test my first versions!

NHAS commented 1 year ago

Hm, unfortunately while adding oidc as an authentication method is trivial, having the groups be able to change for a user on demand is less so, might take a bit longer than I thought.

NHAS commented 1 year ago

@olekristoffer this feature now exists in its beta form here: https://github.com/NHAS/wag/releases/tag/v3.2.1-pre-release

Currently all this allows users to do is authorise with the identity provider, and will mean that users will still need policies, groups and devices created and registered with wag directly.

Im planning on using the OIDC claims to do groups automatically in the future, so only group policy and device registration will be handled on the wag server itself.

Hope this works for you!

As usual, please provide any UI/UX feedack :)

NHAS commented 1 year ago

Sweet this feature is now fully completely, the OIDC IdP can now manage what groups a user is in on the wag side, without wag ever having to directly know about the user.

As this was a huge change, Im still going to leave it in pre-release for a bit until someone tests it.

But it can be found here: https://github.com/NHAS/wag/releases/tag/v3.2.1-pre-release

For completeness (and since I havent written up the docs in a sensible place yet) here are some instructions on running it with keycloak.

config.json policies:

"Policies": {
            "*": {
                "Mfa": ["1.1.1.1"],
                "Allow": [
                    "google.com"
                ]
            },
            "group:nerds": {
                "Mfa": [
                    "192.168.3.4/32"
                ],
                "Allow": [
                    "192.168.3.5/32"
                ]
            },
            "group:administrators": {
                "Mfa": [
                    "8.8.8.8"
                ]
            }
        }

After setting up your realm in keycloak create groups (note the group: prefix): image

Create a client (i just used the default account): image

Click on account

In Settings:

  1. enable Client Authentication, Authorisation, Standard Flow and Direct Access grants like so: image

  2. Set your valid redirect URLS to your vpn domain with /authorise/oidc as the direction, also add + in web origins. image

Add your credentials from Credentials to the wag config file Authenticators.OIDC.ClientID/ClientSecret

In Client scopes:

  1. Click on the dedicated scope image

  2. Add a mapper by configuration image

  3. Choose Group Membership image

  4. Set Name and Token Claim Name to groups (or whatever group name you want, just update it in the wag config file if its not groups). And disable Full group path

image

Then from here, add your users as you usually would, you can also disable other authentication methods in wag. But this message is long enough.

Hope someone plays with it!

NHAS commented 1 year ago

Now on main branch so this issue will be closed :)

paulb-smartit commented 1 year ago

I'd like to reopen this, only so I can understand how it is supposed to work. More than a little excited about the prospect of mapping groups to our domain.

The oidc part in keycloak, no problem. But I'm not sure what I'm to expect as a user when I try to logon. Am I supposed to get a redirect to the OIDC.IssuerURL and be returned on successful authentication?

I'm not getting anywhere.

    "Authenticators": {                                                          
        "Issuer": "otp-uat.domain.tld",                                      
        "DomainURL": "https://otp-uat.domain.tld",                               
        "DefaultMethod": "oidc",                                             
        "Methods": [                                                             
            "totp",                                                          
            "oidc"                                                               
        ],                                                                   
        "OIDC": {                                                            
            "IssuerURL": "https://sso.domain.tld/auth/realms/opusvl",    
            "ClientSecret": "SecretKey",              
            "ClientID": "wag-uat",                                       
            "GroupsClaimName": "groups"                                      
        }                                                                
    }, 

I even tried removing the totp entry. I just get the same MFA behaviour.

paulb-smartit commented 1 year ago

I'm missing the bit about mfa_type in Users.

Updated my record with mfa_type of oidc and now I get a redirect to auth. I just need to resolve the redirect now.

NHAS commented 1 year ago

Effectively a user has an MFA method that gets set in the sqlite3 database (which you've seen).

If their mfa_type is already set then they'll continue to use the previous MFA method of their choice, so we dont accidentally lock users out if we suddenly add methods. You should use wag users -reset-mfa -username <your.username.here> command to reset their MFA. This will then prompt them on next usage to select a method of MFA which should do the rest for you.

Other than that your configuration file looks correct. If resetting the users mfa doesnt work, try creating a new user (and give me the logs of what happens and I'll sort it out).

paulb-smartit commented 1 year ago

Wowsers, just got this working on my UAT system, and have to say - well done.

NHAS commented 1 year ago

Awesome!

Really glad to hear it's all working for you. This was an especially big piece of work and pretty fun to build.

Tell me how it goes or if you find any issues with it.

But for now since you've confirmed its working and it's released to the world I'm going to mark this as closed.

paulb-smartit commented 1 year ago

I may be a bit premature.

The SSO auth works just fine. I sign in and see in the console the groups that it pulls from the claim.

I thought I would then be able to create Policies that match the groups, and not have any Group section in config.json as the groups and membership come from the claim. All I needed to do was add the policy for that group. But I even added in an empty group to test.

"Acls": {
  "Policies": {
    "group:access-role-people": {
        "MFA": [
          "10.0.0.1/32",
          "10.0.0.2/32"
        ]
    }
  }

But when I attempt to access something in the group, I get nowhere. If I look at the firewall I also don't see any "Mfa" rules against my device.

2023/03/02 20:46:05 paul.b 192.168.254.28 used sso to login with groups:  [access-files-developer access-role-people ... ]
  "paul.b": {
    "LastPacketTimestamp": 0,
    "Expiry": 0,
    "MFA": null,
    "Public": [
      "10.0.6.254/32",
      "192.168.254.1/32"
    ],
    "Devices": [
      {
        "IP": "192.168.254.28",
        "Authorized": true
      },
      {
        "IP": "192.168.254.27",
        "Authorized": false
      },
      {
        "IP": "192.168.254.3",
        "Authorized": false
      }
    ]
  },
NHAS commented 1 year ago

Ah this is one of those times where the devil is in the detail. You need to make your groups be prefixed with the wag group: prefix.

So the log should look like:

2023/03/02 20:46:05 paul.b 192.168.254.28 used sso to login with groups:  [group:access-files-developer group:access-role-people ... ]

This is actually done for a good reason. Its so if you accidentally have things in the groups claim (say you have another application that also wants groups in its oidc claim) it doesnt overlap with wag's groups and you dont end up giving users unintentional access.

I think this could be made clear by making it wag:<group_name> or wag-group:<group_name>.

This is just a lack of docs on my behalf (docs website coming soon, as soon as I can bear writing web stuff again)

paulb-smartit commented 1 year ago

ooh, not liking that so much :( My whole point of using sso was to reuse the group membership.

In fact I would have imaginged if you wanted wag specific groups you could make wag specific groups, no need for a group: prefix, eg.

wag-access-roles-people

... and should the groups claim contain only groups anyhow?

NHAS commented 1 year ago

That's a valid point. More than happy to change it, very easy to change.

paulb-smartit commented 1 year ago

PS. If you would like some assistance with documentation, I have been given the go ahead to spend some time to assist.

NHAS commented 1 year ago

Frankly the real blocker for documentation at the moment is the fact I have to create a website for it. I dont mind writing documentation once I've got a little thing set up, I've just currently got 0 motivation to actually make the site (going to be statically generated using hugo with the docsy theme unless I find something better).

But when it exists, I'll add you to the repo to write up things if you want!

NHAS commented 1 year ago

@paulb-opusvl Howdy, I've added you to the docs page I've made. Feel free to put whatever you want in there, I'll be getting a proper domain for it soon, then adding it to the wag main page.

paulb-smartit commented 1 year ago

For documentation, I was expecting a readthedocs or github pages, both would require me to learn how.

Back on oauth2 and groups. Upgraded to 4.1.1.1 and it works, as long as you understand what's expected. The oauth group claim contains groups that are NOT prefixed with group:. To manage their ACL you use them as you would a user, eg. no group: prefix.

            "access-role-projectmanagement": {
                "Mfa": [
                    "10.0.6.125/32",
                    "10.0.4.125/32"
                ]
            },

This seems a little strange, especially when you get to the management UI which I'm loving. As you can't manage these as groups, as the UI expects the prefix. So you manage the rule as if a user.

Screenshot

When you logon as an oauth2 user you wont see anything in the groups to indicate what you are a member of, but looking at the user you will see all of your oauth2 groups. Also the UI Diagnostics, Firewall State will show you the rules are getting applied.

NHAS commented 1 year ago

On the docs side of things: The repo is github pages but uses Hugo instead of readthedocs. So a simple commit will be built and displayed on the page (ill add a link to the page in that repos read me).

For writing your own docs, have a look in the content folder and make sure it works by using the local build first, which I have instructions for in the read me.

On the oauth2 side of things:

You should upgrade to v4.1.3, after you pointed out that having the group: requirement for OIDC claims made it hard to reuse existing groups I did a small release to remove that restriction.

Also, is it important a user knows what groups they're part of? As I can add that to the /status page and/or the authorisation confirmation page

NHAS commented 1 year ago

E.g the code below is how an oidc group is converted into a wag group.

groups := []string{}
for i := range groupsIntf {
    conv, ok := groupsIntf[i].(string)
    if !ok {
        log.Println("Error, could not convert group claim to string, probably error in oidc idP configuration")
        http.Error(w, "Server Error", http.StatusInternalServerError)
        return
    }
    groups = append(groups, "group:"+conv)
}
NHAS commented 1 year ago

Docs for this: https://nhas.github.io/wag-vpn.github.io/docs/guides/single_sign_on_with_keycloak/

Check out the management section :)