Hi,
I'm moving this conversation from the subversion dev mailing list for now
I'm working on adding 2FA to subversion
I have a working PoC of this system, and I'd like to start submitting
patches.
The serf changes are attached, what is the process to get approval for this?
The patches, configs and scripts to make this work are publicly
available here:
https://pub.svnplus.com/repo/2fa/trunk
The implementation uses a special 499 http response code to request 2FA
auth, I have a working server config that serves this without any server
side changes https://pub.svnplus.com/repo/2fa/trunk/webdav.conf
The system is running at https://2fa.svnplus.com/repo/2fa/trunk/, and it
works in the browser and the svn cli with the patches applied, but the
svn client does not yet save the cookie, so every command needs a 2fa
To access the demo system, use the username: user and password: pass
The 2FA QR code is displayed on the page
Please let me know, what you think
Best regards,
Peter
On 2025. 04. 02. 9:25, Daniel Sahlberg wrote:
Hi,
Den mån 31 mars 2025 kl 13:09 skrev Peter Balogh <pe...@svnplus.com>:
Hi,
I'm working on a proof of concept to add multi factor
authentication to
subversion
I have multiple directions, and I'd like to get some feedback from
this
group before I head down the wrong road
Please let me know, if I should read up on any of these, I did not
find
any recent conversation about these topics
I think this is a very interesting concept!
1. One option would be to extend the existing Basic auth with another
challenge. This would support standard TOTP without external
connections
I couldn't find any standard way to communicating this through
HTTP, my
best solution would be to
1.A extend the 401 response with information that the system also
needs
a TOTP token
I've only found the realm string I can use for this without modifying
the serf library
1.B reject the username + password attempt with a different 401
This would have to be handled inside the serf library
I don't see a problem modifying Serf if it is needed. Several of the
Subversion committers are also Serf committers and we can help there.
2. Provide an external login path instead Basic auth. This would
open up
using oauth or similar external providers, but a web based provider
could be run besides svn as well
The server would respond with a 30X response, and the cmdline could
print the url for the user to open, login and return to the terminal
after authentication
This would feel less native, but pretty common these days
I like this idea - I guess this would open up to for example Microsoft
Entra.
Make sure to use the standard baton/callback method to communicate the
redirect URL to the client. This way GUI clients (for example
TortoiseSVN) can implement a more "native" solution to the redirect.
All of the solutions above would result in a HTTP cookie that
would be
stored encrypted in the auth folder, and sent with every
subsequent request
See existing discussions about storing the basic auth password in the
auth folder. On Windows, there are APIs to encrypt the password linked
to the user profile. In general no such on Unix, but I assume it could
be stored in Gnome Keyring/Kwallet/GPG-agent.
Do you have any thoughts or preferences about this?
Do you see any technical or security issue with these?
I know very little about the technical details of authentication on
the http level but I think it would be god to adhere to existing
standards as much as possible instead of inventing something unique to
Subversion. For example it would be good if this can be done across a
forward/reverse proxy.
If this feature is enabled on a server, it would be impossible to
authenticate with an older client. Don't know if this is a
compatibility concern or not, but I think this addition would warrant
new releases.
Kind regards,
Daniel
Index: auth/auth.c
===================================================================
--- auth/auth.c (revision 1925381)
+++ auth/auth.c (working copy)
@@ -216,8 +216,33 @@
serf_bucket_t *response,
apr_pool_t *pool)
{
+ serf_bucket_t* hdrs;
+ if (code == 499) {
+ serf_connection_t* conn = request->conn;
+ serf_context_t* ctx = conn->ctx;
+ apr_status_t status;
+ hdrs = serf_bucket_response_get_headers(response);
+ const char *location = serf_bucket_headers_get(hdrs, "Location");
+
+ char* url;
+ if (location[0] == '/') {
+ url = apr_psprintf(pool, "%s://%s:%d%s",
+ conn->host_info.scheme,
+ conn->host_info.hostname,
+ conn->host_info.port,
+ location);
+ }
+ else {
+ url = apr_pstrdup(pool, location);
+ }
+
+ status = ctx->otp_cb(request, request->auth_baton, url, pool);
+ if (status)
+ return status;
+
+ return APR_SUCCESS;
+ }
if (code == 401 || code == 407) {
- serf_bucket_t *hdrs;
auth_baton_t ab = { 0 };
ab.hdrs = apr_hash_make(pool);
@@ -311,7 +336,7 @@
return APR_SUCCESS;
}
- if (sl.code == 401 || sl.code == 407) {
+ if (sl.code == 401 || sl.code == 407 || sl.code == 499) {
/* Authentication requested. */
/* Don't bother handling the authentication request if the response
Index: serf.h
===================================================================
--- serf.h (revision 1925381)
+++ serf.h (working copy)
@@ -454,6 +454,17 @@
apr_pool_t *pool);
/**
+ * Callback function to be implemented by the application, so that serf
+ * can handle otp authentication.
+ * code = 499.
+ * baton = the baton passed to serf_context_run.
+ */
+typedef apr_status_t (*serf_otp_callback_t)(
+ serf_request_t *request, void *baton,
+ const char *auth_url,
+ apr_pool_t *pool);
+
+/**
* Create a new connection associated with the @a ctx serf context.
*
* If no proxy server is configured, a connection will be created to
@@ -915,6 +926,13 @@
serf_context_t *ctx,
serf_credentials_callback_t cred_cb);
+/**
+ * Set the credentials callback handler.
+ */
+void serf_config_otp_callback(
+ serf_context_t *ctx,
+ serf_otp_callback_t cred_cb);
+
/* ### maybe some connection control functions for flood? */
/*** Special bucket creation functions ***/
Index: serf_private.h
===================================================================
--- serf_private.h (revision 1925381)
+++ serf_private.h (working copy)
@@ -421,6 +421,8 @@
int authn_types;
/* Callback function used to get credentials for a realm. */
serf_credentials_callback_t cred_cb;
+ /* Callback function used to get otp for a realm. */
+ serf_otp_callback_t otp_cb;
serf_config_t *config;
};
Index: src/context.c
===================================================================
--- src/context.c (revision 1925381)
+++ src/context.c (working copy)
@@ -129,6 +129,13 @@
}
+void serf_config_otp_callback(serf_context_t* ctx,
+ serf_otp_callback_t otp_cb)
+{
+ ctx->otp_cb = otp_cb;
+}
+
+
void serf_config_authn_types(serf_context_t *ctx,
int authn_types)
{