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

match Get() API in libsecret and keychain, and add tests #46

Merged
merged 12 commits into from
Jul 3, 2019

Conversation

joemiller
Copy link
Contributor

@joemiller joemiller commented Jun 9, 2019

This PR includes several commits that came out of my attempt to address #45

  • adds a GetMetadata() stub to pass.go which was necessary to build or run tests on the package.
  • adds a GetMetadata() stub to wincred.go which was necessary to build or run tests on the package.
  • Added new libsecret_test.go to assert behavior of the secret-service backend (NOTE: these tests can't be run in CI or a headless environment right now because they require an app providing the 'Prompt' service on DBus. I left some notes about this and future directions in the comments.)
  • adds tests to wincred_test.go, pass_test.go, and keychain_test.go to assert behavior or get/remove/keys when the backing store is empty.
  • adds directory vagrant/ which contains GUI VM's for fedora and windows10 to provide a way to dev/test on the libsecret and wincred backends.
  • Finally, libsecret.go API behavior around Get/Remove/Keys has changed. Get() now returns ErrKeyNotFound just like the keychain and wincred backends. I also added a new public ErrCollectionNotFound that is returned by Remove() and Keys() instead of the previous private secretsError.

Current master branch behavior of all backends when each of the API calls are made with an empty backing store (ie: before the first call to Set() occurs:

backend get set remove keys
keychain ErrKeyNotFound nil gokeychain.ErrorNoSuchKeychain gokeychain.ErrorNoSuchKeychain
libsecret secretsError nil secretsError secretsError
wincred ErrKeyNotFound nil ErrKeyNotFound nil
pass exec.ExitError nil exec.ExitError nil

After change to libsecret.go in this PR. Changes are highlighted:

backend get set remove keys
keychain ErrKeyNotFound nil gokeychain.ErrorNoSuchKeychain gokeychain.ErrorNoSuchKeychain
libsecret ErrKeyNotFound nil ErrCollectionNotFound ErrCollectionNotFound
wincred ErrKeyNotFound nil ErrKeyNotFound nil
pass exec.ExitError nil exec.ExitError nil

I have not included any changes to pass, kwallet, or file in this PR.

This doesn't address the API inconsistencies in the kwallet.go backend. I could probably take a crack at that in another PR but in my own application I'm considering not even supporting KWallet. I'm not a Linux desktop user and don't know the details behind it but it sounds like the project might be orphaned? And might have a replacement in KSecretsManager (or some such) that speaks the DBus Secrets API protocol like Gnome Keyring. If that's true then KDE users should be serviceable via the libsecret.go/"secret-service" backend.

@joemiller
Copy link
Contributor Author

Here is the thread I found on the status of KWallet:

https://mail.kde.org/pipermail/plasma-devel/2016-July/055671.html
https://mail.kde.org/pipermail/plasma-devel/2016-July/055791.html
https://mail.kde.org/pipermail/plasma-devel/2016-July/055668.html

The last link in that chain indicates maybe there's another thing, QtKeyChain, that replaces KWallet. 🤷

@joemiller joemiller changed the title match Get() API in libsecret and keychain, and add tests [WIP] match Get() API in libsecret and keychain, and add tests Jun 10, 2019
@joemiller joemiller force-pushed the libsecret-fix-get-api branch from 5f9e77d to b9e0f8a Compare June 10, 2019 13:45
@lox
Copy link
Collaborator

lox commented Jun 11, 2019

I'd be pretty open to removing the kwallet backend, frankly. I wonder if we can figure out how many folks are using it.

@joemiller joemiller force-pushed the libsecret-fix-get-api branch 3 times, most recently from 15841a1 to 6089a4c Compare June 12, 2019 14:47
@joemiller
Copy link
Contributor Author

joemiller commented Jun 12, 2019

Added a for windows 10 Vagrant box to help with dev/test on the wincred backend

@joemiller joemiller force-pushed the libsecret-fix-get-api branch from 6089a4c to 7e20bfc Compare June 13, 2019 14:34
@joemiller joemiller changed the title [WIP] match Get() API in libsecret and keychain, and add tests match Get() API in libsecret and keychain, and add tests Jun 13, 2019
@joemiller
Copy link
Contributor Author

@lox I removed the '[WIP] tag from the PR title and updated the summary in the top comment of the PR. I would like to review/discuss what, if anything, you'd like to do with this PR.

keychain_test.go Outdated
}

_, err := k.Keys()
// TODO: we should consider making a generic package error like keyring.ErrNoKeyChain instead of letting
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, let's do that

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps with wrapping from https://godoc.org/github.com/pkg/errors? I guess type checking gets harder then. We could go for keychain.IsErrNoKeyChain()?

Copy link
Contributor Author

@joemiller joemiller Jun 17, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will do. I had a thought that perhaps something along the lines of BackendNotInitialized instead of 'NoKeyChain' since "not initialized" is more generic.

RE: pkg/errors. I don't follow. Can you provide an example? Using the existing pattern for no such key is easy to check for errors:

i, err := kr.Get(vaultAddr)
		if err == keyring.ErrKeyNotFound {
			...
		}

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All good, ignore me for now, I think constant errors are fine here.

@joemiller
Copy link
Contributor Author

joemiller commented Jun 18, 2019

@lox While setting about implementing something like a new error I thought that perhaps the best thing to do for all of these cases is to return the existing ErrNoSuchKey err whenever any of these methods is called before data has been added to the backend, so the upgraded grid for proposed behavior change would look like:

current:

backend get set remove keys
keychain ErrKeyNotFound nil gokeychain.ErrorNoSuchKeychain gokeychain.ErrorNoSuchKeychain
libsecret secretsError nil secretsError secretsError
wincred ErrKeyNotFound nil ErrKeyNotFound nil
pass exec.ExitError nil exec.ExitError nil

proposed:

backend get set remove keys
keychain ErrKeyNotFound nil ErrKeyNotFound ErrKeyNotFound
libsecret ErrKeyNotFound nil ErrKeyNotFound ErrKeyNotFound
wincred ErrKeyNotFound nil ErrKeyNotFound ErrKeyNotFound
pass ErrKeyNotFound nil ErrKeyNotFound ErrKeyNotFound

(behaviors in bold would change)

My rationale is that it is easier to reason about if all the read methods return a consistent error when the backend does not have any data yet.

@lox
Copy link
Collaborator

lox commented Jun 18, 2019

That makes sense to me too @joemiller

@joemiller
Copy link
Contributor Author

@lox pushed a new ☝️ commit that implements the ErrKeyNotFound change

@joemiller joemiller force-pushed the libsecret-fix-get-api branch 2 times, most recently from 5880c96 to d46fdbc Compare June 25, 2019 00:47
joemiller added 10 commits June 24, 2019 17:48
This commit adds test cases covering the expected behavior of the
libsecret backend based on the macos keychain implementation as the
reference implementation. In particular this means the tests do not
currently pass. The next commit will include API changes in libsecret.go
to align the behavior to keychain backend, such as returning
`ErrKeyNotFound` instead of a general libsecret error about a missing
collection when `Get()`.

NOTE: These tests are not runnable from a headless environment such as
Docker or a CI pipeline due to the DBus "prompt" interface being called
when creating and unlocking a keychain.
Adds coverage for Get/List/Remove when the keychain does not yet exist
as well as when it does already exist, such as having called .Set() at
least once.

This is useful to help identify API behavior changes within the keychain
backend as well as helping ensure consistent behavior across the other
backends.
See `vagrant/README.md` for usage and details.

Next: A Windows VM for wincred.go dev/test.
Add tests to assert the response from the Get/Remove/Keys API when the
pass keyring has not been created yet.
@joemiller joemiller force-pushed the libsecret-fix-get-api branch from d46fdbc to 83f5f71 Compare June 25, 2019 00:49
@joemiller
Copy link
Contributor Author

rebased from master and fixed a new unit test covering the pass backend that was breaking on travis

@joemiller joemiller force-pushed the libsecret-fix-get-api branch from 83f5f71 to 7ba77b8 Compare June 25, 2019 03:32
@joemiller
Copy link
Contributor Author

I realize this ended up being quite a large surface area to change when it started out as just the Get() implementation in libsecret.go. The changes make sense for the app I'm using but it's difficult for me to reason about how it make impact the other caller, aws-vault. I've never used aws-vault but I wonder if I could help test this PR against aws-vault. I may give that a shot. Any pointers on a test strategy would be appreciated.

@lox
Copy link
Collaborator

lox commented Jun 25, 2019

@joemiller I'm pretty close to merging this, as I 100% agree with your direction, but if you had the chance to test with aws-vault, it would rapidly increase that happening :)

@joemiller
Copy link
Contributor Author

joemiller commented Jun 25, 2019

@lox I ran some tests to compare the output and exit codes of aws-vault with the current keyring lib and the proposed changes in this fork. There are some differences. I think the most notable differences is with the remove cmd since the exit code changes from 0 to 1, example:

## current keyring master branch:
$ aws-vault --backend=keychain --keychain="aws-vault-test" remove test1
Deleted credentials.
Deleted 0 sessions.
# (exit: 0)

## this PR #46 
$ aws-vault --backend=pass --pass-prefix="aws-vault-test" remove test1
Deleted credentials.
aws-vault: error: The specified item could not be found in the keyring
# (exit: 1)

The full output from my tests are in this gist: https://gist.github.com/joemiller/c26b1092a3d58ef09f5c65c692edbe6a There are 3 files. I included a diff because I can't think of a better way to do a side-by-side comparison of the differences. Hopefully the 2 test output files and the diff can help.

I had a feeling that perhaps the best approach might be for the list command to return nil and an empty list instead of ErrKeyNotFound. This is already the case for the wincred and pass backends, so I would revert to that behavior and so keychain and libsecret would be updated to return nil. The updated table would look like this:

backend get set remove keys
keychain ErrKeyNotFound nil ErrKeyNotFound nil
libsecret ErrKeyNotFound nil ErrKeyNotFound nil
wincred ErrKeyNotFound nil ErrKeyNotFound nil
pass ErrKeyNotFound nil ErrKeyNotFound nil

I'm going to try this out and run the same series of tests and add to the gist

@joemiller
Copy link
Contributor Author

joemiller commented Jun 25, 2019

Ok, I re-ran the suite of tests and I think that aligning all the backends to return nil and empty list in Keys() is the right way to go. The results with these changes in aws-vault are almost identical to the current master version. The error on 'remove' is also fixed now. There are two exceptions: keychain and libsecret are now identical to the other backends instead of returning their own unique error, which I think is a good thing.

The full diff is small enough to include here:

@@ -37,7 +37,9 @@
 # keychain
 $ aws-vault --backend=keychain --keychain="aws-vault-test" list
-aws-vault: error: No such keychain (-25294)
+Profile                  Credentials              Sessions                 
+=======                  ===========              ========                 
+aws-vault: error: No credentials found
 # (exit: 1)
 
 $ aws-vault --backend=keychain --keychain="aws-vault-test" add test1
@@ -122,7 +124,9 @@
 
 $ aws-vault --backend=secret-service list
-aws-vault: error: The collection "awsvault" does not exist. Please add a key first
+Profile                  Credentials              Sessions
+=======                  ===========              ========
+aws-vault: error: No credentials found
 # (exit: 1)

Another gist with 3 files here - https://gist.github.com/joemiller/b099b6043f19957fac4fdfacf400f656

@akerl-unpriv
Copy link

Just went to rebuild my codebase with the latest keyring source, and it fails to build on windows due to lack of GetMetadata. Would rock to get this merged in.

Additionally, it looks like the tagged release isn't getting picked up by go mod since it lacks a leading "v"

@joemiller
Copy link
Contributor Author

fwiw, just released my own project that uses this lib - https://github.com/joemiller/vault-token-helper running off my fork of keyring for now but hoping to get back to upstream soon =)

@joemiller
Copy link
Contributor Author

@akerl-unpriv In the meantime, if you need to use this fork with go mod you should be able to do something like this: https://github.com/joemiller/vault-token-helper/blob/master/go.mod#L17

@akerl-unpriv
Copy link

Hah, yup; I just did similarly with github.com/akerl/keyring 😄

@lox
Copy link
Collaborator

lox commented Jul 2, 2019

Can I merge this @joemiller?

@mtibben
Copy link
Member

mtibben commented Jul 3, 2019

Hey @joemiller I've made you a collaborator to this repo, fee free to merge

@joemiller
Copy link
Contributor Author

@lox I feel pretty confident this is ready to merge. I tested this branch with aws-vault a few comments above and the results looked good to me. The only difference in behavior I expect is the different error messages from keychain and secret-service backends outlined in the diff in this comment - #46 (comment) - which actually make them identical to the error messages emitted by the other backends (pass, wincred) given the same context. 👍

@lox
Copy link
Collaborator

lox commented Jul 3, 2019

🚢 and welcome as a collaborator! Thanks for such a thoughtful and well executed PR.

@lox lox merged commit e3f923b into 99designs:master Jul 3, 2019
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.

4 participants