-
-
Notifications
You must be signed in to change notification settings - Fork 1.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Initial work on OIDC (SSO) integration #126
Conversation
Hi @unreality This is great! I have not come around to starting to review the code yet, but I have built it and played a bit with it. On the first attempt I got the following panic when the callback came back from my OIDC (I use dex): GET /oidc/callback?code=xatlvybq3mfbs3l6vm4jfdr57&state=0c22a99162dfaacbbe31287a666f41e4 HTTP/2.0
Host: headscale.example.no
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Encoding: gzip, deflate, br
Accept-Language: en-us
Referer: https://id.example.no/dex/approval?req=zepbtkldlpe7h3nrlhh7emot5
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.1.2 Safari/605.1.15
runtime error: index out of range [0] with length 0
/usr/lib/go-1.17/src/runtime/panic.go:90 (0x439054)
goPanicIndex: panic(boundsError{x: int64(x), signed: true, y: y, code: boundsIndex})
/root/git/headscale_oidc/oidc.go:81 (0xbdc3dd)
/root/git/headscale_oidc/oidc.go:256 (0xbde16b)
/root/go/pkg/mod/github.com/gin-gonic/gin@v1.7.4/context.go:165 (0x977081)
/root/go/pkg/mod/github.com/gin-gonic/gin@v1.7.4/recovery.go:99 (0x97706c)
/root/go/pkg/mod/github.com/gin-gonic/gin@v1.7.4/context.go:165 (0x9762e6)
/root/go/pkg/mod/github.com/gin-gonic/gin@v1.7.4/logger.go:241 (0x9762c9)
/root/go/pkg/mod/github.com/gin-gonic/gin@v1.7.4/context.go:165 (0x97581d)
/root/go/pkg/mod/github.com/gin-gonic/gin@v1.7.4/gin.go:489 (0x9754a5)
/root/go/pkg/mod/github.com/gin-gonic/gin@v1.7.4/gin.go:445 (0x975004)
/usr/lib/go-1.17/src/net/http/server.go:2878 (0x785bba)
serverHandler.ServeHTTP: handler.ServeHTTP(rw, req)
/usr/lib/go-1.17/src/net/http/server.go:3479 (0x787944)
initALPNRequest.ServeHTTP: h.h.ServeHTTP(rw, req)
/usr/lib/go-1.17/src/net/http/h2_bundle.go:5832 (0x75af77)
(*http2serverConn).runHandler: handler(rw, req)
/usr/lib/go-1.17/src/runtime/asm_amd64.s:1581 (0x46bc40)
goexit: BYTE $0x90 // NOP I have not had any time to dig into what can cause this and will have a look tomorrow if no one beats me to it. Heck, it could even be me having misconfigured something. Other than that I think it is a good start, but I would make a little list of things to discuss before merging (@juanfont, @qbit):
|
That trace is probably a bug on my part, I have made an assumption about the JWKs returned by the OIDC server. I'll see if I can spin up a Dex server and do some testing. All my testing has been done using Keycloak.
|
That would be good, we want to ensure that we are compliant if we are advertising OpenID Connect.
Sounds good
Do you mean expiry as in on the server-side?
There is quite a lot of "hard-to-follow" stuff with the registration and all the database calls all over the place. It is on my cleanup list, but time. I'll try to have a look at that while reviewing.
I agree, I think email should be good, or at least a good default. My concern is that I would concern headscale a hobbyist project, which means that a setup might have users from all over the place, domain-wise. I dont have a good solution as of now, maybe I can think of something while reviewing the code. |
…ame oidc_endpoint to oidc_issuer to be more inline with spec
This new build should work with Dex now, i replaced my code with go-oidc. I'll try to tackle nodekey expiration etc as well, unless you want to separate that into a separate pull request? |
Let's make it feature complete before we merge :), so implementing expiry and logout makes sense. Great work on using the upstream lib! I'll take a look. Sorry for the delay I was hunting some other bugs and got distracted. |
Co-authored-by: Kristoffer Dalby <kradalby@kradalby.no>
@kradalby ive implemented most of your suggestions, and implemented some basic expiry checks, although i'd love to have some review over the expiry code since im sure ive missed some scenarios |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hi, it is looking like we are closing in :)
I posted mostly comments about adding comments. Machines are good at reading code, but extracting logic and intents by reading this much code is hard and I think we need more "why" explanations of decisions to understand why they were made.
If we dont have that, people will look in the future and wonder why, but not dear to change it.
That said, just using it works for me, I have pulled down the artefact from the build pipeline and the registration is good, will check back tomorrow and do some more client tests to look at the behaviour.
I've done some more work on the expiry code and attempted to clean up the registration method. |
Im on Holiday so ill have a look when I get back :) |
Could you see if you can resolve the conflicts so I can have that as part of the next review? |
Implement namespace mappings
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good job!
Mostly just comments and making the tests pass left, then we can merge and let users try it in main
.
api.go
Outdated
return | ||
} | ||
|
||
resp := tailcfg.RegisterResponse{} | ||
|
||
// We have the updated key! | ||
if m.NodeKey == wgkey.Key(req.NodeKey).HexString() { | ||
if m.Registered { | ||
|
||
// The client sends an Expiry in the past if the client is requesting a logout |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we have a reference for this? would be good to have in the comment, for example if it can be found in the Tailscale source code.
cmd/headscale/cli/utils.go
Outdated
@@ -161,6 +161,18 @@ func getHeadscaleApp() (*headscale.Headscale, error) { | |||
return nil, err | |||
} | |||
|
|||
// maxMachineRegistrationDuration is the maximum time a client can request for a client registration |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We need a comment explaining why maxMachineRegistrationDuration
is the max time, and why we have a max time.
cmd/headscale/cli/utils.go
Outdated
maxMachineRegistrationDuration = viper.GetDuration("max_machine_registration_duration") | ||
} | ||
|
||
// defaultMachineRegistrationDuration is the default time assigned to a client registration if one is not specified by the client |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We need a comment explaining why defaultMachineRegistrationDuration is the default time, and why we have a default time.
machine.go
Outdated
// If the Machine is expired, updateMachineExpiry updates the Machine Expiry time to the maximum allowed duration, | ||
// or the default duration if no Expiry time was requested by the client | ||
func (h *Headscale) updateMachineExpiry(m *Machine) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What does actually expire here? The full login? as in, does the machine have to log in again after this expire? Or is it the expiry of how long the oidc login can last?
I think we need larger godoc comment here explaining:
- What is this function doing
- Why is it necessary
- An example to make it easier to understand
cmd/headscale/cli/utils.go
Outdated
ClientSecret: viper.GetString("oidc.client_secret"), | ||
}, | ||
|
||
MaxMachineRegistrationDuration: maxMachineRegistrationDuration, // the maximum duration a client may request for expiry time |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We need a comment here explaining what "expiry time" means, preferably with an example.
machine.go
Outdated
@@ -36,6 +36,7 @@ type Machine struct { | |||
LastSeen *time.Time | |||
LastSuccessfulUpdate *time.Time | |||
Expiry *time.Time | |||
RequestedExpiry *time.Time // when a client connects, it may request a specific expiry time, use this field to store it |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
RequestedExpiry *time.Time // when a client connects, it may request a specific expiry time, use this field to store it | |
// when a client connects, it may request a specific expiry time, use this field to store it | |
RequestedExpiry *time.Time |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We need a comment explaining what is the expiry time and why it might request a dedicated one.
Please consider adding an example.
README.md
Outdated
"domain_map": { | ||
".*": "default-namespace" | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
} | |
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, github suggestions does not handle this well, there is a "end of codeblock" missing
Ive added some comments and hopefully made a few things clearer, let me know what you think |
Fix conflict, prepare for merge
This pull adds initial support for SSO. If you add
oidc_endpoint
,oidc_client_id
andoidc_client_secret
to config.json, when a new machine attempts to register, the OIDC endpoint will be used for authentication. A new namespace is created using the email address of the user, and the machine is added to that namespace.Expiry times probably need to be implemented/used so that Logging out works correctly, and i think further work needs to be done on the register endpoint to become secure.
oidc_endpoint
needs to be the base url of the OIDC endpoint (ie if your well-known configuration is athttp://localhost:8080/auth/realms/master/.well-known/openid-configuration
your endpoint url should behttp://localhost:8080/auth/realms/master/
-- it does not do proper path joining at the moment, so the training/
is required