-
Notifications
You must be signed in to change notification settings - Fork 51
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
feat: Keyring #2557
feat: Keyring #2557
Changes from 4 commits
5f843d9
f7b1295
adadfb2
e7f698e
2a56209
fc74058
3c38648
c514789
35d9a14
1438760
e112134
ebe4152
4ded5fc
e425069
705cd35
ce8a2e5
7983fb5
f4584b1
643ae14
f7eb930
f6b0ed1
ce5e693
a3e0629
0798712
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,127 @@ | ||
// Copyright 2024 Democratized Data Foundation | ||
// | ||
// Use of this software is governed by the Business Source License | ||
// included in the file licenses/BSL.txt. | ||
// | ||
// As of the Change Date specified in that file, in accordance with | ||
// the Business Source License, use of this software will be governed | ||
// by the Apache License, Version 2.0, included in the file | ||
// licenses/APL.txt. | ||
|
||
package cli | ||
|
||
import ( | ||
"crypto/rand" | ||
"errors" | ||
"syscall" | ||
|
||
"github.com/99designs/keyring" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. question: How confident are we in this dependency? The repo hasn't been updated in more than two years, and it is managed by a company that looks to have a focus way away from the security world and may lack the technical expertise or attention to keep it secure (assuming it and its dependencies were secure 2 years ago). Some of its' dependencies are very out of date, and a couple are even more stale and suspicious looking than this package (e.g. https://github.com/gsterjov/go-libsecret with 14 stars, presumably responsible for the very old version of https://github.com/godbus/dbus) Different websites say different things RE its' size, one says it only had 16 employees in 2022 (when the repo was last updated), other top search engine hits estimate 300, and 3777 employees. 99Designs:
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I've replaced this dependency with a custom implementation using a newer library. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Dependencies and code of the new one look much healthier, thanks Keenan :) |
||
"github.com/libp2p/go-libp2p/core/crypto" | ||
"github.com/spf13/cobra" | ||
"golang.org/x/term" | ||
) | ||
|
||
const ( | ||
peerKeyName = "peer_key" | ||
badgerEncryptionKeyName = "badger_encryption_key" | ||
) | ||
|
||
// openKeyring attempts to open the keyring file from the given directory. | ||
// | ||
// If the directory is an empty string the best option for the current OS will be used. | ||
func openKeyring(cmd *cobra.Command, dir string) (keyring.Keyring, error) { | ||
var allowedBackends []keyring.BackendType | ||
if dir != "" { | ||
// only allow file backend if a directory is specified | ||
allowedBackends = append(allowedBackends, keyring.FileBackend) | ||
} | ||
|
||
prompt := keyring.PromptFunc(func(s string) (string, error) { | ||
cmd.Print(s) | ||
pass, err := term.ReadPassword(int(syscall.Stdin)) | ||
return string(pass), err | ||
}) | ||
|
||
return keyring.Open(keyring.Config{ | ||
AllowedBackends: allowedBackends, | ||
ServiceName: "defradb", | ||
KeychainName: "defradb", | ||
KeychainPasswordFunc: prompt, | ||
FilePasswordFunc: prompt, | ||
FileDir: dir, | ||
KeyCtlScope: "user", | ||
KeyCtlPerm: 0, // TODO | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. todo: Please create a task if this is something that needs to be handled later. Otherwise it will linger. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I will fix before merging. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I ended up replacing this dependency. |
||
KWalletAppID: "defradb", | ||
KWalletFolder: "defradb", | ||
LibSecretCollectionName: "defradb", | ||
PassPrefix: "defradb", | ||
WinCredPrefix: "defradb", | ||
}) | ||
} | ||
|
||
// generateAES256 generates a new random AES-256 bit encryption key. | ||
func generateAES256() ([]byte, error) { | ||
data := make([]byte, 32) | ||
_, err := rand.Read(data) | ||
return data, err | ||
} | ||
|
||
// loadOrGenerateAES256 attempts to load the AES-256 bit key with the given name. | ||
// | ||
// If the key does not exist a new random key is generated and stored in the keyring. | ||
func loadOrGenerateAES256(kr keyring.Keyring, name string) ([]byte, error) { | ||
item, err := kr.Get(name) | ||
if err == nil { | ||
return item.Data, nil | ||
} | ||
if !errors.Is(err, keyring.ErrKeyNotFound) { | ||
return nil, err | ||
} | ||
key, err := generateAES256() | ||
if err != nil { | ||
return nil, err | ||
} | ||
err = kr.Set(keyring.Item{ | ||
Key: name, | ||
Data: key, | ||
}) | ||
if err != nil { | ||
return nil, err | ||
} | ||
return key, nil | ||
} | ||
|
||
// generateEd25519 generates a new random Ed25519 private key. | ||
func generateEd25519() (crypto.PrivKey, error) { | ||
key, _, err := crypto.GenerateKeyPair(crypto.Ed25519, 0) | ||
return key, err | ||
} | ||
|
||
// loadOrGenerateEd25519 attempts to load the Ed25519 private key with the given name. | ||
// | ||
// If the key does not exist a new random key is generated and stored in the keyring. | ||
func loadOrGenerateEd25519(kr keyring.Keyring, name string) (crypto.PrivKey, error) { | ||
item, err := kr.Get(name) | ||
if err == nil { | ||
return crypto.UnmarshalPrivateKey(item.Data) | ||
} | ||
if !errors.Is(err, keyring.ErrKeyNotFound) { | ||
return nil, err | ||
} | ||
key, err := generateEd25519() | ||
if err != nil { | ||
return nil, err | ||
} | ||
data, err := crypto.MarshalPrivateKey(key) | ||
if err != nil { | ||
return nil, err | ||
} | ||
err = kr.Set(keyring.Item{ | ||
Key: name, | ||
Data: data, | ||
}) | ||
if err != nil { | ||
return nil, err | ||
} | ||
return key, nil | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -14,7 +14,6 @@ import ( | |
"fmt" | ||
"os" | ||
"os/signal" | ||
"path/filepath" | ||
"syscall" | ||
|
||
"github.com/libp2p/go-libp2p/core/peer" | ||
|
@@ -84,23 +83,32 @@ func MakeStartCommand() *cobra.Command { | |
} | ||
|
||
if cfg.GetString("datastore.store") != configStoreMemory { | ||
// It would be ideal to not have the key path tied to the datastore. | ||
// Running with memory store mode will always generate a random key. | ||
// Adding support for an ephemeral mode and moving the key to the | ||
// config would solve both of these issues. | ||
rootDir := mustGetContextRootDir(cmd) | ||
key, err := loadOrGeneratePrivateKey(filepath.Join(rootDir, "data", "key")) | ||
if err != nil { | ||
return err | ||
} | ||
netOpts = append(netOpts, net.WithPrivateKey(key)) | ||
|
||
// TODO-ACP: Infuture when we add support for the --no-acp flag when admin signatures are in, | ||
// we can allow starting of db without acp. Currently that can only be done programmatically. | ||
// https://github.com/sourcenetwork/defradb/issues/2271 | ||
dbOpts = append(dbOpts, db.WithACP(rootDir)) | ||
} | ||
|
||
if !cfg.GetBool("keyring.disabled") { | ||
keyring, err := openKeyring(cmd, cfg.GetString("keyring.path")) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
peerKey, err := loadOrGenerateEd25519(keyring, peerKeyName) | ||
if err != nil { | ||
return err | ||
} | ||
netOpts = append(netOpts, net.WithPrivateKey(peerKey)) | ||
|
||
encryptionKey, err := loadOrGenerateAES256(keyring, badgerEncryptionKeyName) | ||
if err != nil { | ||
return err | ||
} | ||
storeOpts = append(storeOpts, node.WithEncryptionKey(encryptionKey)) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. question: This implies that if the keyring is enabled, then we also use use at-rest encryption. Should these technically be separate config options? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good catch! I've added a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Update: I've made the key optional instead of the config option. |
||
} | ||
|
||
opts := []node.NodeOpt{ | ||
node.WithPeers(peers...), | ||
node.WithStoreOpts(storeOpts...), | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -74,5 +74,12 @@ func NewStore(opts ...StoreOpt) (datastore.RootStore, error) { | |
badgerOpts.ValueLogFileSize = options.valueLogFileSize | ||
badgerOpts.EncryptionKey = options.encryptionKey | ||
|
||
if len(options.encryptionKey) > 0 { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. suggestion: I prefer There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think you may have misread the type. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I see, my bad |
||
// Having a cache improves the performance. | ||
// Otherwise, your reads would be very slow while encryption is enabled. | ||
// https://dgraph.io/docs/badger/get-started/#encryption-mode | ||
badgerOpts.IndexCacheSize = 100 << 20 | ||
} | ||
|
||
return badger.NewDatastore(options.path, &badgerOpts) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
suggestion: I wonder if all this logic with generating hashes should be scoped to this cli package.
Maybe it makes sense to extract it in another place so other that other packages can use it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think that would be nice. Would it make sense to have a
crypto
package?