Skip to content
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 for credential stores #32

Merged
merged 27 commits into from
Dec 13, 2023
Merged

Add basic support for credential stores #32

merged 27 commits into from
Dec 13, 2023

Conversation

bk2204
Copy link
Owner

@bk2204 bk2204 commented Dec 9, 2023

Add a new type of abstraction, a store, and add a variant of this that handles credentials. Add a backend for Git and one for an in-memory store which persists during the entire life of the server.

Partially implements #20

We'd like to add support for credential handling in Lawn.  Add a
generic, URL-like structure called a Store that supports CRUD operations
and the data types to support it, as well as various credential-specific
datastructures.

Signed-off-by: brian m. carlson <sandals@crustytoothpaste.net>
This is useful for our credential client wrappers.

Signed-off-by: brian m. carlson <sandals@crustytoothpaste.net>
@bk2204 bk2204 force-pushed the credential-store branch 2 times, most recently from 6bfd143 to 87f33bd Compare December 10, 2023 00:06
It's helpful to provide some basic interfaces around credentials.  They
each have a secret, plus optionally a username, one or more URLs or URL
patterns associated with them, and additional metadata.  We also have a
form of inquiring about these credentials via a credential request.

Add various APIs around accessing these in a useful way using the
primitives provided by the protocol.

As part of this, add client and protocol handler methods that return the
ID of a message we've sent, since when we're performing certain types of
authentication, we need the ID of the last message to help chain
commands together.

Signed-off-by: brian m. carlson <sandals@crustytoothpaste.net>

protocol: allow sending a message and returning the ID

Signed-off-by: brian m. carlson <sandals@crustytoothpaste.net>
The Git credential protocol is relatively simple.  Add a parser for it
that can handle a credential request or response.

Add two possible extensions for Lawn-specific data: the authentication
type and the service that this is for.  Normally, the default service
will be `git`, but users may want to store credentials using this
protocol for other services.

Signed-off-by: brian m. carlson <sandals@crustytoothpaste.net>
Now that we have the client pieces for talking to a generic store, let's
implement a manager for a generic store and a set of traits that can be
used to implement one.  Add a parser for store paths, which can be
tricky to handle, as well as some basic tests for it.

Signed-off-by: brian m. carlson <sandals@crustytoothpaste.net>
Add the config file entries for credentials, including a backend type
for using `git credential`.

Signed-off-by: brian m. carlson <sandals@crustytoothpaste.net>
Add credential stores as a kind of store.  In addition, add several
helper functions for credential stores and a credential store backend
using `git credential`.

Signed-off-by: brian m. carlson <sandals@crustytoothpaste.net>
Implement the server code for handling stores, including credential
stores.  Adjust the continuation code to handle the new types of
continuations.

Signed-off-by: brian m. carlson <sandals@crustytoothpaste.net>
Let's add some basic tests for our Git credential backend by speaking
the protocol from the client side to a test script in Ruby that mimics
`git credential`.

Signed-off-by: brian m. carlson <sandals@crustytoothpaste.net>
In the future, we'll want to provide additional data stored in this
structure, notably additional environment variables required by the
signin code for some credential helpers.  Having three separate
lifetimes is already a little silly, so let's make this data structure
own its data to avoid adding a fourth lifetime.

This will also be useful in the future for query code, where we'll want
to be able to store a context in the server for a longer period of time.

Signed-off-by: brian m. carlson <sandals@crustytoothpaste.net>
We'll want to have some shared state across multiple different server
connections, and in order to do that, let's create a structure for that
purpose and put an `Arc` of it into the regular server state.  In
addition, move the configuration into it, since that is shared across
multiple different connection instances.

Signed-off-by: brian m. carlson <sandals@crustytoothpaste.net>
We don't expect panics to occur during normal processing of messages,
but they can nevertheless occur during development.  To help in the
debugging of tests and identifying problems if a panic really does occur
in actual usage, let's print the panic message to the log if that should
happen.

Signed-off-by: brian m. carlson <sandals@crustytoothpaste.net>
We already have a client and server environment.  However, in some
cases, such as some credential backends, we need to preserve environment
variables to be passed to those backends when running commands.  This
can be useful to allow a user to log in once, get an environment
variable back, and then pass that environment variable into other calls
to that backend.  Notably, this will be useful for a future 1Password
credential helper, among others.

Let's add a server context environment which can be used to pass these
specific environment variables and which will keep them independent from
the main server environment variable state.  Add a template context
builder which can be used to easily instantiate just the required values
as well.

Signed-off-by: brian m. carlson <sandals@crustytoothpaste.net>
Add a backend which allows storing credentials in memory and optionally
allows authentication.  This exists primarily for tests, but it is also
valuable for general credential caching.

Signed-off-by: brian m. carlson <sandals@crustytoothpaste.net>
We'd like to be able to look up an arbitrary credential object by path,
so let's add an enum for the possibilities and return a suitable object
if found.

Signed-off-by: brian m. carlson <sandals@crustytoothpaste.net>
When we need an errno value, we don't want to require the caller to
consume the entire `io::Error` if they want to hold onto it.  Accept
references to `io::Error` as well to make this easier to use.

Signed-off-by: brian m. carlson <sandals@crustytoothpaste.net>
The memory backend's data is stored in a credential store whose data is
destroyed at latest at the end of the client session.  This is
unhelpful, because it means that every separate command-line invocation
of the Lawn client will have its own state, which makes things like
using credential helpers not work.

To fix this, let's use our special shared credential state, and convert
it to handle arbitrary data.  Then, insert our vaults into the store
once the backend is created and re-use them for subsequent sessions, so
that our data is persisted across sessions as long as the server is
running.

Signed-off-by: brian m. carlson <sandals@crustytoothpaste.net>
Lawn is designed to be flexible and scriptable.  To make it easy to
script commands, let's add a script syntax based off the IMAP message
syntax.

Our deserializer doesn't use Serde because Serde assumes that we can
distinctly determine the type inside of an `Option` based on context.
However, we cannot do that because all of our data is serialized as
strings, so without knowing the type of the data, it's not possible to
distinguish the hexadecimal integer `0xff` from the text or byte strings
with that same value.  This is fine for scripting, where most of the
time we're working with strings anyway, but makes decoding a bit more
difficult.

Signed-off-by: brian m. carlson <sandals@crustytoothpaste.net>
We'll want to report error values in our scripting commands using a
standard, machine-readable approach.  Let's add a trait for accessing
that information in a helpful way.

Signed-off-by: brian m. carlson <sandals@crustytoothpaste.net>
When working with the memory backend, we need functionality to create
vaults and directories.  Also, in general, it's helpful to have
scripting support, which allows users to interact with credentials from
the command line.  To make these possible, let's add some basic
scripting support.

Signed-off-by: brian m. carlson <sandals@crustytoothpaste.net>
Add some basic command line functionality, including a script interface
and a Git credential helper.  For now, always insert the item in the
first vault in the list, but in the future, we'll allow putting it in an
arbitrary location.

As part of this, declare that we support the appropriate capability.

Signed-off-by: brian m. carlson <sandals@crustytoothpaste.net>
Accept only authentication methods that have been negotiated as
capabilities.  To make this easier, declare auth=PLAIN and
auth=keyboard-interactive as capabilities.  Add a test for this case as
well.

Signed-off-by: brian m. carlson <sandals@crustytoothpaste.net>
Document the new store functionality and specifically the credential
type of store.

Signed-off-by: brian m. carlson <sandals@crustytoothpaste.net>
This requires a newer MSRV than we're supporting, so don't complain
about it.

Signed-off-by: brian m. carlson <sandals@crustytoothpaste.net>
Switch to using `into` where possible and convert the no-longer-needed
`match` statement into an if-else.

Signed-off-by: brian m. carlson <sandals@crustytoothpaste.net>
Signed-off-by: brian m. carlson <sandals@crustytoothpaste.net>
On FreeBSD and NetBSD, Ruby, which is used in our test fixture, is
located in a different location (`/usr/local/bin` on FreeBSD and
`/usr/pkg/bin`).  Let's change our `PATH` such that it inherits the
system one, just with our helper directory in front, so we'll always
find the correct binaries.

Signed-off-by: brian m. carlson <sandals@crustytoothpaste.net>
@bk2204 bk2204 marked this pull request as ready for review December 13, 2023 00:56
@bk2204 bk2204 merged commit 42b425d into dev Dec 13, 2023
16 of 19 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

1 participant