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

Refactor keystore interface into 3 different interfaces #16365

Merged
merged 11 commits into from
Apr 2, 2020
35 changes: 27 additions & 8 deletions libbeat/cmd/keystore.go
Original file line number Diff line number Diff line change
Expand Up @@ -130,10 +130,15 @@ func createKeystore(settings instance.Settings, force bool) error {
return err
}

wKeystore, ok := store.(keystore.WritableKeystore)
if !ok {
return fmt.Errorf("the configured keystore is not writable")
}
urso marked this conversation as resolved.
Show resolved Hide resolved

if store.IsPersisted() == true && force == false {
response := terminal.PromptYesNo("A keystore already exists, Overwrite?", false)
if response == true {
err := store.Create(true)
err := wKeystore.Create(true)
if err != nil {
return fmt.Errorf("error creating the keystore: %s", err)
}
Expand All @@ -142,7 +147,7 @@ func createKeystore(settings instance.Settings, force bool) error {
return nil
}
} else {
err := store.Create(true)
err := wKeystore.Create(true)
if err != nil {
return fmt.Errorf("Error creating the keystore: %s", err)
}
Expand All @@ -160,14 +165,19 @@ func addKey(store keystore.Keystore, keys []string, force, stdin bool) error {
return fmt.Errorf("could not create secret for: %s, you can only provide one key per invocation", keys)
}

wKeystore, ok := store.(keystore.WritableKeystore)
if !ok {
return fmt.Errorf("the configured keystore is not writable")
}

if store.IsPersisted() == false {
if force == false {
answer := terminal.PromptYesNo("The keystore does not exist. Do you want to create it?", false)
if answer == false {
return errors.New("exiting without creating keystore")
}
}
err := store.Create(true)
err := wKeystore.Create(true)
if err != nil {
return fmt.Errorf("could not create keystore, error: %s", err)
}
Expand Down Expand Up @@ -202,10 +212,10 @@ func addKey(store keystore.Keystore, keys []string, force, stdin bool) error {
return fmt.Errorf("could not read value from the input, error: %s", err)
}
}
if err = store.Store(key, keyValue); err != nil {
if err = wKeystore.Store(key, keyValue); err != nil {
return fmt.Errorf("could not add the key in the keystore, error: %s", err)
}
if err = store.Save(); err != nil {
if err = wKeystore.Save(); err != nil {
return fmt.Errorf("fail to save the keystore: %s", err)
} else {
fmt.Println("Successfully updated the keystore")
Expand All @@ -218,6 +228,11 @@ func removeKey(store keystore.Keystore, keys []string) error {
return errors.New("you must supply at least one key to remove")
}

wKeystore, ok := store.(keystore.WritableKeystore)
if !ok {
return fmt.Errorf("the configured keystore is not writable")
}

if store.IsPersisted() == false {
return errors.New("the keystore doesn't exist. Use the 'create' command to create one")
}
Expand All @@ -229,8 +244,8 @@ func removeKey(store keystore.Keystore, keys []string) error {
return fmt.Errorf("could not find key '%v' in the keystore", key)
}

store.Delete(key)
err = store.Save()
wKeystore.Delete(key)
err = wKeystore.Save()
if err != nil {
return fmt.Errorf("could not update the keystore with the changes, key: %s, error: %v", key, err)
}
Expand All @@ -240,7 +255,11 @@ func removeKey(store keystore.Keystore, keys []string) error {
}

func list(store keystore.Keystore) error {
keys, err := store.List()
lKeystore, ok := store.(keystore.ListingKeystore)
if !ok {
return fmt.Errorf("the configured keystore is not writable")
ChrsMark marked this conversation as resolved.
Show resolved Hide resolved
}
keys, err := lKeystore.List()
if err != nil {
return fmt.Errorf("could not read values from the keystore, error: %s", err)
}
Expand Down
42 changes: 38 additions & 4 deletions libbeat/keystore/file_keystore.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,19 @@ const (
// Version of the keystore format, will be added at the beginning of the file.
var version = []byte("v1")

// Packager defines a keystore that we can read the raw bytes and be packaged in an artifact.
type Packager interface {
Package() ([]byte, error)
ConfiguredPath() string
}

// FileKeystoreIn defines a keystore implements Keystore, WritableKeystore, ListingKeystore.
type FileKeystoreIn interface {
Keystore
WritableKeystore
ListingKeystore
}
ChrsMark marked this conversation as resolved.
Show resolved Hide resolved

// FileKeystore Allows to store key / secrets pair securely into an encrypted local file.
type FileKeystore struct {
sync.RWMutex
Expand All @@ -66,16 +79,37 @@ type serializableSecureString struct {
Value []byte `json:"value"`
}

// Factory Create the right keystore with the configured options.
func Factory(cfg *common.Config, defaultPath string) (Keystore, error) {
config := defaultConfig

if cfg == nil {
cfg = common.NewConfig()
}
err := cfg.Unpack(&config)

if err != nil {
return nil, fmt.Errorf("could not read keystore configuration, err: %v", err)
}

if config.Path == "" {
config.Path = defaultPath
}

keystore, err := NewFileKeystore(config.Path)
return &keystore, err
}

// NewFileKeystore returns an new File based keystore or an error, currently users cannot set their
// own password on the keystore, the default password will be an empty string. When the keystore
// is initialized the secrets are automatically loaded into memory.
func NewFileKeystore(keystoreFile string) (Keystore, error) {
func NewFileKeystore(keystoreFile string) (FileKeystore, error) {
return NewFileKeystoreWithPassword(keystoreFile, NewSecureString([]byte("")))
}

// NewFileKeystoreWithPassword return a new File based keystore or an error, allow to define what
// password to use to create the keystore.
func NewFileKeystoreWithPassword(keystoreFile string, password *SecureString) (Keystore, error) {
func NewFileKeystoreWithPassword(keystoreFile string, password *SecureString) (FileKeystore, error) {
ChrsMark marked this conversation as resolved.
Show resolved Hide resolved
keystore := FileKeystore{
Path: keystoreFile,
dirty: false,
Expand All @@ -85,10 +119,10 @@ func NewFileKeystoreWithPassword(keystoreFile string, password *SecureString) (K

err := keystore.load()
if err != nil {
return nil, err
return FileKeystore{}, err
}

return &keystore, nil
return keystore, nil
}

// Retrieve return a SecureString instance that will contains both the key and the secret.
Expand Down
3 changes: 2 additions & 1 deletion libbeat/keystore/file_keystore_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ func TestCanCreateAKeyStore(t *testing.T) {

keystore, err := NewFileKeystore(path)
assert.NoError(t, err)

assert.Nil(t, keystore.Store(keyValue, secretValue))
assert.Nil(t, keystore.Save())
}
Expand Down Expand Up @@ -289,7 +290,7 @@ func createAndReadKeystoreWithPassword(t *testing.T, password []byte) {

// CreateAnExistingKeystore creates a keystore with an existing key
/// `output.elasticsearch.password` with the value `secret`.
func CreateAnExistingKeystore(path string) Keystore {
func CreateAnExistingKeystore(path string) FileKeystore {
keystore, err := NewFileKeystore(path)
// Fail fast in the test suite
if err != nil {
Expand Down
61 changes: 13 additions & 48 deletions libbeat/keystore/keystore.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,9 @@ package keystore

import (
"errors"
"fmt"

"github.com/elastic/beats/libbeat/common"
ucfg "github.com/elastic/go-ucfg"
"github.com/elastic/go-ucfg"
"github.com/elastic/go-ucfg/parse"
)

Expand All @@ -39,67 +38,33 @@ var (
// to that concept, so we can deal with tokens that has a limited duration or can be revoked by a
// remote keystore.
type Keystore interface {
// Store add keys to the keystore, wont be persisted until we save.
Store(key string, secret []byte) error

// Retrieve returns a SecureString instance of the searched key or an error.
Retrieve(key string) (*SecureString, error)

// Delete removes a specific key from the keystore.
Delete(key string) error

// List returns the list of keys in the keystore, return an empty list if none is found.
List() ([]string, error)

// GetConfig returns the key value pair in the config format to be merged with other configuration.
GetConfig() (*common.Config, error)

// Create Allow to create an empty keystore.
Create(override bool) error

// IsPersisted check if the current keystore is persisted.
IsPersisted() bool

// Save persist the changes to the keystore.
Save() error
}

// Packager defines a keystore that we can read the raw bytes and be packaged in an artifact.
type Packager interface {
Package() ([]byte, error)
ConfiguredPath() string
}

// Factory Create the right keystore with the configured options.
func Factory(cfg *common.Config, defaultPath string) (Keystore, error) {
config := defaultConfig

if cfg == nil {
cfg = common.NewConfig()
}
err := cfg.Unpack(&config)
type WritableKeystore interface {
// Store add keys to the keystore, wont be persisted until we save.
Store(key string, secret []byte) error

if err != nil {
return nil, fmt.Errorf("could not read keystore configuration, err: %v", err)
}
// Delete removes a specific key from the keystore.
Delete(key string) error

if config.Path == "" {
config.Path = defaultPath
}
// Create Allow to create an empty keystore.
Create(override bool) error

keystore, err := NewFileKeystore(config.Path)
return keystore, err
// Save persist the changes to the keystore.
Save() error
}

// ResolverFromConfig create a resolver from a configuration.
func ResolverFromConfig(cfg *common.Config, dataPath string) (func(string) (string, parse.Config, error), error) {
keystore, err := Factory(cfg, dataPath)

if err != nil {
return nil, err
}

return ResolverWrap(keystore), nil
type ListingKeystore interface {
// List returns the list of keys in the keystore, return an empty list if none is found.
List() ([]string, error)
}

// ResolverWrap wrap a config resolver around an existing keystore.
Expand Down
20 changes: 14 additions & 6 deletions x-pack/libbeat/management/enroll.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import (
"os"
"time"

"github.com/elastic/beats/libbeat/keystore"

"github.com/pkg/errors"

"github.com/elastic/beats/libbeat/cfgfile"
Expand Down Expand Up @@ -87,16 +89,22 @@ func Enroll(
}

func storeAccessToken(beat *instance.Beat, accessToken string) error {
keystore := beat.Keystore()
if !keystore.IsPersisted() {
if err := keystore.Create(false); err != nil {
keyStore := beat.Keystore()

wKeystore, ok := keyStore.(keystore.WritableKeystore)
if !ok {
return fmt.Errorf("the configured keystore is not writable")
}

if !keyStore.IsPersisted() {

if err := wKeystore.Create(false); err != nil {
return errors.Wrap(err, "error creating keystore")
}
}

if err := keystore.Store(accessTokenKey, []byte(accessToken)); err != nil {
if err := wKeystore.Store(accessTokenKey, []byte(accessToken)); err != nil {
return errors.Wrap(err, "error storing the access token")
}

return keystore.Save()
return wKeystore.Save()
}