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 support for keyring password storage #29

Draft
wants to merge 7 commits into
base: master
Choose a base branch
from

Conversation

tedwardd
Copy link
Contributor

@tedwardd tedwardd commented Jun 16, 2023

Add support for secure password storage in system keyring. Addresses #20

First pass just adds keyring support for Lemmy but it's a repeatable pattern. Might be better to consolidate in to a global method we can call to clean it up a little... might do that before merging...

@tedwardd
Copy link
Contributor Author

I encountered a merge conflict with go.mod and go.sum... I did my best to resolve the conflict but I fear I may have goofed. Please test and make sure you can still build using these new files.

@benonions
Copy link

For what it's worth, I tested this branch locally (on MacOS) and worked great for me!

@tedwardd
Copy link
Contributor Author

That's excellent news, thank you! I need someone with Windows and someone with a Linux system (that has a keyring installed) to test it. I may be able to test a Linux keyring (I don't currently have one installed) but I do not have a Windows system available at this time.

@benonions
Copy link

That's excellent news, thank you! I need someone with Windows and someone with a Linux system (that has a keyring installed) to test it. I may be able to test a Linux keyring (I don't currently have one installed) but I do not have a Windows system available at this time.

I might be able to test Ubuntu a little bit later, I've got it installed on a VM.

@tedwardd
Copy link
Contributor Author

tedwardd commented Jun 17, 2023

Do not attempt to build df1594d, it is broken, pushed only to sync end-of-day changes (I'll rebase this whole mess before merging).

@benonions
Copy link

Sadly didn't go as well on Ubuntu 23.04 with Gnome keyring.

Was able to log in and add the password to keyring, but starting neonmodem after that crashes.

panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1 addr=0x80 pc=0x8f194c]

goroutine 1 [running]:
github.com/mrusme/neonmodem/cmd.loadSystems(0x40001880a0)
	/home/ben/neonmodem/cmd/root.go:105 +0x9c
github.com/mrusme/neonmodem/cmd.glob..func1(0x138f240?, {0xa6a043?, 0x0?, 0x0?})
	/home/ben/neonmodem/cmd/root.go:122 +0xb4
github.com/spf13/cobra.(*Command).execute(0x138f240, {0x4000032240, 0x0, 0x0})
	/home/ben/go/pkg/mod/github.com/spf13/cobra@v1.7.0/command.go:944 +0x5ac
github.com/spf13/cobra.(*Command).ExecuteC(0x138f240)
	/home/ben/go/pkg/mod/github.com/spf13/cobra@v1.7.0/command.go:1068 +0x340
github.com/spf13/cobra.(*Command).Execute(...)
	/home/ben/go/pkg/mod/github.com/spf13/cobra@v1.7.0/command.go:992
github.com/mrusme/neonmodem/cmd.Execute(0x95a980?)
	/home/ben/neonmodem/cmd/root.go:140 +0x4c
main.main()
	/home/ben/neonmodem/neonmodem.go:13 +0x24

Went over it with delve, looks like system.New() returns a nil system interface here

The error is that The specified item could not be found in the keyring. However, I looked at the keyring myself and it's there. So not sure what the dealio is.

@tedwardd
Copy link
Contributor Author

OK, after sleeping on it I have simpliefied the whole workflow with a fresh perspective. I've confirmed that it works on Linux without a keyring present and on MacOS with the MacOS keychain.


// Open system keyring object
ring, _ := keyring.Open(keyring.Config{
ServiceName: "NeonModem - Lemmy",
Copy link
Contributor Author

Choose a reason for hiding this comment

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

So that this is reusable, the keyring entry should probably be the url for the system being added.

Copy link
Owner

Choose a reason for hiding this comment

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

Unfortunately it is a little bit more complicated than that. Check the following helpful interaction I had with the KeePassX guys a while ago: keepassxreboot/keepassxc#8475

// Attempt to save the password to system keyring
// If we can't, ask if should save it in clear text
err = ring.Set(keyring.Item{
Key: "password",
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Just thinking out loud... maybe we should also store the username in the keyring. I'm not super familiar with keyring integration on different systems but if we were using the system url AND we had username and password stored in the keyring, maybe we could check if the user already has their credentials saved for the url (from a web browser, for example)... thinking about how password managers do it... maybe food for later thought.

Copy link
Owner

Choose a reason for hiding this comment

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

Yes, indeed, the complete login can be stored in the keyring.

func SetPassword() (string, *PasswordError) {
// Prompt for password input
fmt.Println("Please enter your password (will not echo): ")
bytepw, err := term.ReadPassword(int(syscall.Stdin))
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Will check to see if the promptui package has a feature for a no-echo prompt so this can be simplified and have a consistent feel to the yes/no prompt when a supported keyring is not available.

Copy link
Owner

Choose a reason for hiding this comment

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

I believe bubbletea already has some bubbles that might accomplish what you're looking to do on the same Bubbletea basis that neonmodem is already using. :)

@@ -144,9 +145,25 @@ func (sys *System) Load() error {
credentials[k] = v.(string)
}

ring, _ := keyring.Open(keyring.Config{
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Only initialize this if we find the password should be in the keyring below.

Copy link
Owner

@mrusme mrusme left a comment

Choose a reason for hiding this comment

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

Hey @tedwardd, thank you for taking the initiative, I appreciate it!

The keyring stuff seems a bit fiddly and I'm mainly worried that we might overcomplicate and thereby break stuff for individual platforms on which there is no keyring implementation available or for users that might not require it.

Hence my suggestion would be build an abstraction on top of the keyring library -- e.g. credentials.go -- and allow each system to use the Credentials service to store and retrieve credentials. The Credentials service in turn would implement different backends, one of which is the keyring library, the other one the plaintext config; Another one could be a command runner (e.g. for using pass ...). These backends would reside in dedicated files, which we could prefix with the respective // +build tag for Go. That way we wouldn't include the feature/dependency on platforms on which it isn't available anyway.

Wdyt?

func SetPassword() (string, *PasswordError) {
// Prompt for password input
fmt.Println("Please enter your password (will not echo): ")
bytepw, err := term.ReadPassword(int(syscall.Stdin))
Copy link
Owner

Choose a reason for hiding this comment

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

I believe bubbletea already has some bubbles that might accomplish what you're looking to do on the same Bubbletea basis that neonmodem is already using. :)


// Open system keyring object
ring, _ := keyring.Open(keyring.Config{
ServiceName: "NeonModem - Lemmy",
Copy link
Owner

Choose a reason for hiding this comment

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

Unfortunately it is a little bit more complicated than that. Check the following helpful interaction I had with the KeePassX guys a while ago: keepassxreboot/keepassxc#8475

// Attempt to save the password to system keyring
// If we can't, ask if should save it in clear text
err = ring.Set(keyring.Item{
Key: "password",
Copy link
Owner

Choose a reason for hiding this comment

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

Yes, indeed, the complete login can be stored in the keyring.

})
if err != nil {
fmt.Println("Unable to save password to a keyring. Would you like to proceed to save the password in clear text in the neonmodem.toml?")
if resp, _ := YesNo(); resp != true {
Copy link
Owner

Choose a reason for hiding this comment

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

This seems to be the only instance of YesNo, hence I'd probably even use scanner.Scan() in order to keep dependencies low. See the connect.go files of the systems for example.


var password string

if credentials["password"] == "password_in_keyring" {
Copy link
Owner

Choose a reason for hiding this comment

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

It might make sense to store the keyring identifier, prefixed with a special preamble/symbol here, so that it can reference a specific entry rather than just say password_in_keyring, e.g. @lemmy_myaccount123, where lemmy_myaccount123 is the identifier of the keyring item. However, this depends on the implementation of the keyring, which is yet tbc.

@mrusme mrusme marked this pull request as draft June 21, 2023 19:23
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

3 participants