francoismichel / ssh3

SSH3: faster and rich secure shell using HTTP/3, checkout our article here: https://arxiv.org/abs/2312.08396 and our Internet-Draft: https://datatracker.ietf.org/doc/draft-michel-ssh3/
https://arxiv.org/abs/2312.08396
Apache License 2.0
3.18k stars 81 forks source link

Plugins system for authentication #135

Open francoismichel opened 3 months ago

francoismichel commented 3 months ago

This PR is a first attempt for providing a pluggable authentication system. The end goal is to allow compile-time plugins to implement new authentication behaviours (e.g. SAML, passkeys, ...).

francoismichel commented 1 month ago

Here's the current plugins interface. For people reading it, let me know your thoughts on the interface ! What's scaring me the most is people starting to develop plugins and then having to change the interface afterwards, so it's best putting as much thought in it as possible before merging that.

Plugins interface

Developers can develop server-side and client-side authentication plugins. The plugins have to be inserted at compile time, for instance by adding an import in cmd/ssh3/main.go or cmd/ssh3-server/main.go. Plugins can register themselves by implementing an init() function in which they call the plugins.RegisterServerAuthPlugin() or plugins.RegisterClientAuthPlugin() function to register the authentication plugin.

Server-side plugins provide a ServerAuthPlugin function that takes a username and an ssh3 identity line in the authorized_identities/authorized_keys file and generates a RequestIdentityVerifier if the identity line correctly represents the authentication method defined by the plugin. The RequestIdentityVerifier interface received an HTTP request and returns whether or not the request contains all the needed authentication material required to authenticate the user.

Client-side plugins provide a ClientAuthPlugin structure. This structure defines its own SSH3 options that can then be put in the SSH config (and optionally as a CLI arg). This structure should also hold a GetClientAuthMethodsFunc that provides all the suitable authentication methods to be tried against the server in the form of a slice of ClientAuthMethod. Every ClientAuthMethod will have the opportunity to prepare an HTTP request with authentication material to startup an SSH3 conversation. For instance, for pubkey authentication using the private key files on the filesystem, the GetClientAuthMethodsFunc can return a slice containing one ClientAuthMethod for each private key file it wants to try.

/////////////////////////////////////
//       Server auth plugins       //
/////////////////////////////////////

// In ssh3, authorized_keys are replaced by authorized_identities where a use can specify classical
// public keys as well as other authentication and authorization methods such as OAUTH2 and SAML 2.0
type RequestIdentityVerifier interface {
    Verify(request *http.Request, base64ConversationID string) bool
}

// parses an AuthorizedIdentity line (`identityStr`). Returns a new Identity and a nil error if the
// line was successfully parsed. Returns a nil identity and a nil error if the line format is unknown
// to the plugin. Returns a non-nil error if any other error that is worth to be logged occurs.
//
// plugins are currently a single function so that they are completely stateless
type ServerAuthPlugin func(username string, identityStr string) (RequestIdentityVerifier, error)

/////////////////////////////////////
//       Client auth plugins       //
/////////////////////////////////////

// returns all the suitable authentication methods to be tried against the server in the form
// of a slice of ClientAuthMethod. Every ClientAuthMethod will have the opportunity to prepare
// an HTTP request with authentication material to startup an SSH3 conversation. For instance,
// for pubkey authentication using the private key files on the filesystem, the
// GetClientAuthMethodsFunc can return a slice containing one ClientAuthMethod for
// each private key file it wants to try.
// if no SSH agent socket if found, sshAgent is nil
type GetClientAuthMethodsFunc func(request *http.Request, sshAgent agent.ExtendedAgent, clientConfig *client_config.Config, roundTripper *http3.RoundTripper) ([]ClientAuthMethod, error)

type ClientAuthMethod interface {
    // PrepareRequestForAuth updated the provided request with the needed headers
    // for authentication.
    // The method must not alter the request method (must always be CONNECT) nor the
    // Host/:origin, User-Agent or :path headers.
    // The agent is the connected SSH agent if it exists, nil otherwise
    // The provided roundTripper can be used to perform requests with the server to prepare
    // the authentication process.
    // username is the username to authenticate
    // conversation is the Conversation we want to establish
    PrepareRequestForAuth(request *http.Request, sshAgent agent.ExtendedAgent, roundTripper *http3.RoundTripper, username string, conversation *ssh3.Conversation) error
}

type ClientAuthPlugin struct {
    // A plugin can define one or more new SSH3 config options.
    // A new option is defined by providing a dedicated option parser.
    // The key in PluginOptions must be a unique name for each option
    // and must not conflict with any existing option
    // (good practice: "<your_repo_name>[-<option_name>]")
    PluginOptions map[client_config.OptionName]client_config.OptionParser

    PluginFunc GetClientAuthMethodsFunc
}