Skip to content

Commit

Permalink
Allow user to locally obfuscate secret in a keystore (#5687)
Browse files Browse the repository at this point in the history
* Feature: Local Keystore to obfuscate sensitive information on disk

This PR allow users to define sensitive information into an obfuscated data store on disk instead of having them defined in plaintext in the yaml configuration.

This add a few users facing commands:

beat keystore create

beat keystore add output.elasticsearch.password

beat keystore remove output.elasticsearch.password

beat keystore list
The current implementation doesn't allow user to configure the secret with a custom password, this will come in future improvements of this feature.

* Changelog
  • Loading branch information
ph authored and tsg committed Dec 22, 2017
1 parent 226c3a7 commit 0f09311
Show file tree
Hide file tree
Showing 22 changed files with 1,577 additions and 0 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
coverage.out
.python-version
beat.db
*.keystore

# Editor swap files
*.swp
Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ https://github.com/elastic/beats/compare/v6.0.0-beta2...master[Check the HEAD di
- Addition and update of the following libraries: pbkdf2, terminal, windows, unix {pull}5735[5735]
- Update the command line library cobra and add support for zsh completion {pull}5761[5761]
- The log format may differ due to logging library changes. {pull}5901[5901]
- Adding a local keystore to allow user to obfuscate password {pull}5687[5687]

*Auditbeat*

Expand Down
5 changes: 5 additions & 0 deletions filebeat/tests/system/config/filebeat.yml.j2
Original file line number Diff line number Diff line change
Expand Up @@ -212,3 +212,8 @@ output.file:
path:
data: {{path_data}}
{% endif %}

{% if keystore_path %}
#================================ keystore =====================================
keystore.path: {{keystore_path}}
{% endif %}
74 changes: 74 additions & 0 deletions filebeat/tests/system/test_keystore.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import os
from os import path

from filebeat import BaseTest
from beat.beat import Proc


class TestKeystore(BaseTest):
"""
Test Keystore variable replacement
"""

def setUp(self):
super(BaseTest, self).setUp()
self.keystore_path = self.working_dir + "/data/keystore"

def test_keystore_with_present_key(self):
"""
Test that we correctly do string replacement with values from the keystore
"""

key = "elasticsearch_host"
secret = "myeleasticsearchsecrethost"

self.render_config_template(keystore_path=self.keystore_path, elasticsearch={
'host': "${%s}:9200" % key
})

exit_code = self.run_beat(extra_args=["keystore", "create"])
assert exit_code == 0

self.add_secret(key, secret, True)
proc = self.start_beat()
self.wait_until(lambda: self.log_contains("myeleasticsearchsecrethost"))
assert self.log_contains(secret)
proc.kill_and_wait()

def test_keystore_with_key_not_present(self):
"""
Test that we return the template key when the key doesn't exist
"""
key = "do_not_exist_elasticsearch_host"

self.render_config_template(keystore_path=self.keystore_path, elasticsearch={
'host': "${%s}:9200" % key
})

exit_code = self.run_beat()
assert self.log_contains(
"missing field accessing 'output.elasticsearch.hosts.0'")
assert exit_code == 1

def add_secret(self, key, value="hello world\n", force=False):
"""
Add new secret using the --stdin option
"""
args = [self.test_binary,
"-systemTest",
"-test.coverprofile",
os.path.join(self.working_dir, "coverage.cov"),
"-c", os.path.join(self.working_dir, self.beat_name + ".yml"),
"-e", "-v", "-d", "*",
"keystore", "add", key, "--stdin",
]

if force:
args.append("--force")

proc = Proc(args, os.path.join(self.working_dir, self.beat_name + ".log"))

os.write(proc.stdin_write, value)
os.close(proc.stdin_write)

return proc.start().wait()
22 changes: 22 additions & 0 deletions libbeat/cmd/instance/beat.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"math/big"
"math/rand"
"os"
"path/filepath"
"runtime"
"strings"
"time"
Expand All @@ -23,6 +24,7 @@ import (
"github.com/elastic/beats/libbeat/common/cfgwarn"
"github.com/elastic/beats/libbeat/common/file"
"github.com/elastic/beats/libbeat/dashboards"
"github.com/elastic/beats/libbeat/keystore"
"github.com/elastic/beats/libbeat/logp"
"github.com/elastic/beats/libbeat/logp/configure"
"github.com/elastic/beats/libbeat/monitoring"
Expand Down Expand Up @@ -59,6 +61,7 @@ type Beat struct {

Config beatConfig
RawConfig *common.Config // Raw config that can be unpacked to get Beat specific config data.
keystore keystore.Keystore
}

type beatConfig struct {
Expand All @@ -75,6 +78,7 @@ type beatConfig struct {
Path paths.Path `config:"path"`
Logging *common.Config `config:"logging"`
MetricLogging *common.Config `config:"logging.metrics"`
Keystore *common.Config `config:"keystore"`

// output/publishing related configurations
Pipeline pipeline.Config `config:",inline"`
Expand Down Expand Up @@ -192,6 +196,11 @@ func (b *Beat) BeatConfig() (*common.Config, error) {
return common.NewConfig(), nil
}

// Keystore return the configured keystore for this beat
func (b *Beat) Keystore() keystore.Keystore {
return b.keystore
}

// create and return the beater, this method also initializes all needed items,
// including template registering, publisher, xpack monitoring
func (b *Beat) createBeater(bt beat.Creator) (beat.Beater, error) {
Expand Down Expand Up @@ -405,6 +414,19 @@ func (b *Beat) configure() error {
return fmt.Errorf("error loading config file: %v", err)
}

// We have to initialize the keystore before any unpack or merging the cloud
// options.
keystoreCfg, _ := cfg.Child("keystore", -1)
defaultPathConfig, _ := cfg.String("path.config", -1)
defaultPathConfig = filepath.Join(defaultPathConfig, fmt.Sprintf("%s.keystore", b.Info.Beat))
store, err := keystore.Factory(keystoreCfg, defaultPathConfig)
if err != nil {
return fmt.Errorf("could not initialize the keystore: %v", err)
}

// TODO: Allow the options to be more flexible for dynamic changes
common.OverwriteConfigOpts(keystore.ConfigOpts(store))
b.keystore = store
err = cloudid.OverwriteSettings(cfg)
if err != nil {
return err
Expand Down
Loading

0 comments on commit 0f09311

Please sign in to comment.