-
Notifications
You must be signed in to change notification settings - Fork 23
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
Add basic support & documentation for built-in and plugin-based auth #51
Conversation
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.
Neat plugin interface. I had some comments that might help as you harden things.
if authResult.WriteResponse(w) { | ||
return | ||
} |
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.
This is a critical path to understand, so it might be worth a comment.
// Write a 401 or 404, if needed
if authResult.WriteResponse(w) {
return
}
// Authorized, continue
The other way to think about it is to consider "authorized" as a boolean condition, and if false there is something to be done with WriteResponse
:
if b.authorize != nil {
authResult := b.authorize(r, owner, repo)
if !authResult.authorized() {
authResult.WriteResponse(w)
return
}
}
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.
I originally had it as the latter, but I wasn't thrilled with needing to re-check Authorized()
in WriteResponse()
(because if I was doing that anyway, why even have the original Authorized()
check?).
I'll add a comment, but I'll also try to come up with a better name (AuthorizeOrWriteResponse()
? or maybe something simpler like ApplyResult()
).
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.
If we wanted to give auth middleware full control of the pipeline then really we should really only expect a boolean "return bundles or exit?" and have the middleware handle the entire response?
if b.authorize != nil {
if !b.authorize(r, w, owner, repo) {
return
}
}
I assume the only expected front-end/consumer of the bundle server endpoints is Git (never a browser etc)?
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.
It took a few iterations, but I've made the AuthResult
s a bit more generic (where Allow()
== "apply headers & serve content" and Deny()
== "apply headers & respond with given 4XX code"). I'd like to keep the middleware at least a bit sandboxed (it helps keep the bundle server aligned with what Git expects/supports and IMO makes plugin writing simpler), but if there's anything else that might be valuable to add to the current interface, I'm happy to update!
cmd/git-bundle-web-server/main.go
Outdated
} | ||
|
||
// First, verify plugin checksum matches expected | ||
// NEEDSWORK: time-of-check/time-of-use |
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.
Just pointing out the NEEDSWORK here, probably part of your "draft" status?
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.
But what else can we do? Can we access the file descriptor of the plugin from p.Open()
to verify the hash of exactly what we opened? Does the plugin
library have security checks?
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.
The plugin
library has no way of doing that, unfortunately 😞. I considered opening an issue in golang/go
, but the more I thought about it the less feasible that seemed. Under the hood, Go is using dlopen
& dlsym
to load the plugin symbols, so even if it did verify the checksum before running the init tasks of the Go package, someone could still (carefully) overwrite the file contents before invoking the initializer
symbol.
If we really wanted to do this, we'd probably need to write our own version of plugin
that:
- opens the file with
dlopen()
- locks it with
flock()
- checks the checksum
- loads & runs the init tasks
- loads the requested symbol with
dlsym()
- invokes the symbol
- releases the lock with
flock()
- closes the file
Nearly all of that would need to be done with C bindings and would need platform-specific implementations for Mac & Linux due to slight differences in flock()
(and would further distance us from Windows support, if that matters?). To be honest, that doesn't necessarily seem worth it.
So my next question is, should we remove/change the corresponding warning in docs/technical/auth-config.md
to drop the specific mention of TOCTOU and just recommend general safety around custom plugins?
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.
If no one's opposed, I'll leave the TOCTOU mention - seems better to inform users to watch for/mitigate it than to pretend it's not an issue.
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.
One overarching question I had is do we want to relax the plugin interface even more? From the bundle server's perspective the only real decision it needs to make is "should I return bundles or not".
Splitting out unauthorized 401, forbidden 403(404?) and success (200) means plugins can only use those response options.
490d7e8
to
065d28d
Compare
Add an 'authorize' function to the bundle web server for managing access to bundle server content. This function returns one of two states: - Deny, which indicates that bundle server content should not be served. This is configured with the desired 4XX response code and (optional) headers. Invoking 'ApplyResult()' with this result will fill in the response information and trigger 'serve' to exit immediately. - Allow, indicating that the requstor may access the requested resource. This does not return an immediate response, but if headers are specified, they will be added to the eventual response. After applying this result with 'ApplyResult()', 'serve' will continue on to add the appropriate content to the response. For now, the 'authorize' function is always nil, so it's never invoked. In later patches, it will be configured via a new '--auth-config' option to the web server. Signed-off-by: Victoria Dye <[email protected]>
Add an '--auth-config' flag to specify the path to a JSON file containing information used to configure bundle server auth, as well as a function for parsing the config. For now, no modes are configured, so attempting to pass a value to the option will fail. Update the 'git-bundle-web-server' manpage to describe the auth config and its usage. Also add 'auth-config.md' technical documentation with more detail around the schema of the auth config file. These will be updated as new modes are introduced. Signed-off-by: Victoria Dye <[email protected]>
Upgrade the minimum Go version for the project. This update is necessary to take advantage of some useful new features (including, in an upcoming patch, the ability to convert between variable- and fixed-length byte slices). Signed-off-by: Victoria Dye <[email protected]>
@derrickstolee @mjcheetham I think I've addressed all of the feedback so far. The PR has changed a fair amount since the first round of reviews (added integration tests, manpage docs, and better in-code documentation for |
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.
Looks really good! Very impressed all-up, but the plugin work is especially awesome.
|
||
// Check for invalid username characters | ||
if strings.Contains(params.Username, ":") { | ||
return nil, fmt.Errorf("username contains a colon (\":\")") |
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.
Why can't usernames contain colons specifically?
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.
I'd imagine to avoid issues encoding this in a URL?
protocol://user:pass@host/path
Same with @
; you'd need to escape 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.
It's part of RFC 7617, which defines Basic auth:
Furthermore, a user-id containing a colon character is invalid, as the first colon in a user-pass string separates user-id and password from one another; text after the first colon is part of the password. User-ids containing colons cannot be encoded in user-pass strings.
expectedResponseCode int | ||
expectedHeaders http.Header | ||
}{ | ||
{ |
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.
Really thorough set of test cases here!
Create and test a built-in auth mode for a single fixed username/password authenticated against a provided 'Authorization: Basic' header, implementing the AuthMiddleware interface. Add the mode to 'parseAuthConfig()' in 'git-bundle-web-server' to enable its usage in the web server. Update documentation for the mode in 'docs/technical/auth-config.md' and 'git-bundle-web-server' manpage, and an explicit example JSON in the 'examples/auth/config' directory. Signed-off-by: Victoria Dye <[email protected]>
Update the 'bundleServer.startWebServer()' method to wait for indication that the web server has successfully started (the "Server is running at address..." message) before returning successfully. If the process stops before that, or a timeout of 2s is reached, an error is thrown. However, because the "Server is running..." printout happens in parallel with the setup done in 'ListenAndServe[TLS]()', it is possible that the message is printed and immediately followed with an exit due to a server startup error. To mitigate that, add a 0.1s pause before printing the message and, in the tests, only set the status to 'ok' if the 'close' event has not been triggered by the time the startup message is processed. Signed-off-by: Victoria Dye <[email protected]>
Add tests of the bundle web server with no auth and with a 'fixed' auth configuration. Exercise cases where the user provides no credentials, incorrect credentials, and correct credentials and the expected bundle server responses for each. Signed-off-by: Victoria Dye <[email protected]>
Implement and document auth configurations using user-specified runtime plugins. The goal of this change is to allow users to customize their access control to a more specific application or domain than is supported by built-in modes. A plugin is loaded in 'git-bundle-web-server' from its 'path' by first comparing the file contents to a specified SHA256 checksum; the web server fails to start if there is a mismatch. Otherwise, the plugin is loaded and the specified 'initializer' symbol is looked up. If that symbol exists, the initializer is called, creating the 'AuthMiddleware' instance and/or an error. From there, 'git-bundle-web-server' passes the middleware's 'Authorize' function reference to the created 'bundleWebServer' so that it is invoked after parsing the route of each request. Additionally, update manpage and technical documentation. Add an example plugin (built from a standalone '.go' file) & config to 'examples/auth/'. Signed-off-by: Victoria Dye <[email protected]>
Closes #41
This pull request lays the foundation for specialized auth configuration in the bundle server:
AuthResult
type, which the web server will use to relay the output of a custom auth function as an HTTP response--auth-config
type and initial schema; in this commit, it doesn't do much of anything (since there aren't any validmode
s yet). Also introduce theAuthMiddleware
interface, which will be used in both built-in modes and the basis for plugin modes.fixed
built-in auth mode, which validates a request's Basic header against a static username/password pair.fixed
auth modeplugin
auth mode, which allows a user to load (at runtime) a custom implementation ofAuthMiddleware
This is a pretty major feature (or the start of one, at least), so I'd really appreciate an extra reviewer focus on making sure the architecture is sound and there aren't any glaring security issues (other than what's already mentioned in the docs, i.e. "don't run untrusted plugins").