Skip to content

Commit

Permalink
[CM] Gracefully handle TLS options when enrolling a beats (#9752)
Browse files Browse the repository at this point in the history
The `enroll` subcommand allow to specify the CA and other SSL options when
enrolling. Any TLS options will be persisted to the file and will be
used when fetching the configuration.

```
./filebeat enroll https://localhost:5601 d2eec88904f546f2816ce58061781c3d  -E "management.kibana.ssl.certificate_authorities=/tmp/myca.crt"
```
Fixes: #9129
  • Loading branch information
ph authored Jan 11, 2019
1 parent 4bdd0a8 commit 0a914fa
Show file tree
Hide file tree
Showing 13 changed files with 173 additions and 135 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.next.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ https://github.com/elastic/beats/compare/v7.0.0-alpha2...master[Check the HEAD d

- Enforce validation for the Central Management access token. {issue}9621[9621]
- Fix config appender registration. {pull}9873[9873]
- Gracefully handle TLS options when enrolling a Beat. {issue}9129[9129]

*Auditbeat*

Expand Down
1 change: 1 addition & 0 deletions libbeat/common/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,7 @@ func (c *Config) PrintDebugf(msg string, params ...interface{}) {
}
}

// Enabled return the configured enabled value or true by default.
func (c *Config) Enabled() bool {
testEnabled := struct {
Enabled bool `config:"enabled"`
Expand Down
10 changes: 5 additions & 5 deletions libbeat/common/transport/tlscommon/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,13 @@ import (

// Config defines the user configurable options in the yaml file.
type Config struct {
Enabled *bool `config:"enabled" yaml:"enabled"`
Enabled *bool `config:"enabled" yaml:"enabled,omitempty"`
VerificationMode TLSVerificationMode `config:"verification_mode" yaml:"verification_mode"` // one of 'none', 'full'
Versions []TLSVersion `config:"supported_protocols" yaml:"supported_protocols"`
CipherSuites []tlsCipherSuite `config:"cipher_suites" yaml:"cipher_suites"`
CAs []string `config:"certificate_authorities" yaml:"certificate_authorities"`
Versions []TLSVersion `config:"supported_protocols" yaml:"supported_protocols,omitempty"`
CipherSuites []tlsCipherSuite `config:"cipher_suites" yaml:"cipher_suites,omitempty"`
CAs []string `config:"certificate_authorities" yaml:"certificate_authorities,omitempty"`
Certificate CertificateConfig `config:",inline" yaml:",inline"`
CurveTypes []tlsCurveType `config:"curve_types" yaml:"curve_types"`
CurveTypes []tlsCurveType `config:"curve_types" yaml:"curve_types,omitempty"`
Renegotiation tlsRenegotiationSupport `config:"renegotiation" yaml:"renegotation"`
}

Expand Down
40 changes: 30 additions & 10 deletions libbeat/common/transport/tlscommon/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,12 +56,22 @@ var tlsCipherSuites = map[string]tlsCipherSuite{
}

var tlsCipherSuitesInverse = make(map[tlsCipherSuite]string, len(tlsCipherSuites))
var tlsRenegotiationSupportTypesInverse = make(map[tlsRenegotiationSupport]string, len(tlsRenegotiationSupportTypes))
var tlsVerificationModesInverse = make(map[TLSVerificationMode]string, len(tlsVerificationModes))

// Init creates a inverse representation of the values mapping.
func init() {
for cipherName, i := range tlsCipherSuites {
tlsCipherSuitesInverse[i] = cipherName
}

for name, t := range tlsRenegotiationSupportTypes {
tlsRenegotiationSupportTypesInverse[t] = name
}

for name, t := range tlsVerificationModes {
tlsVerificationModesInverse[t] = name
}
}

var tlsCurveTypes = map[string]tlsCurveType{
Expand Down Expand Up @@ -166,18 +176,20 @@ var tlsVerificationModes = map[string]TLSVerificationMode{
}

func (m TLSVerificationMode) String() string {
modes := map[TLSVerificationMode]string{
VerifyFull: "full",
// VerifyCertificate: "certificate",
VerifyNone: "none",
}

if s, ok := modes[m]; ok {
if s, ok := tlsVerificationModesInverse[m]; ok {
return s
}
return "unknown"
}

// MarshalText marshal the verification mode into a human readable value.
func (m TLSVerificationMode) MarshalText() ([]byte, error) {
if s, ok := tlsVerificationModesInverse[m]; ok {
return []byte(s), nil
}
return nil, fmt.Errorf("could not marshal '%+v' to text", m)
}

// Unpack unpacks the string into constants.
func (m *TLSVerificationMode) Unpack(in interface{}) error {
if in == nil {
Expand Down Expand Up @@ -262,11 +274,19 @@ func (r *tlsRenegotiationSupport) Unpack(s string) error {
return nil
}

func (r tlsRenegotiationSupport) MarshalText() ([]byte, error) {
if t, found := tlsRenegotiationSupportTypesInverse[r]; found {
return []byte(t), nil
}

return nil, fmt.Errorf("could not marshal '%+v' to text", r)
}

// CertificateConfig define a common set of fields for a certificate.
type CertificateConfig struct {
Certificate string `config:"certificate" yaml:"certificate"`
Key string `config:"key" yaml:"key"`
Passphrase string `config:"key_passphrase" yaml:"key_passphrase"`
Certificate string `config:"certificate" yaml:"certificate,omitempty"`
Key string `config:"key" yaml:"key,omitempty"`
Passphrase string `config:"key_passphrase" yaml:"key_passphrase,omitempty"`
}

// Validate validates the CertificateConfig
Expand Down
12 changes: 6 additions & 6 deletions libbeat/kibana/client_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,12 @@ import (

// ClientConfig to connect to Kibana
type ClientConfig struct {
Protocol string `config:"protocol" yaml:"protocol"`
Host string `config:"host" yaml:"host"`
Path string `config:"path" yaml:"path"`
SpaceID string `config:"space.id" yaml:"space.id"`
Username string `config:"username" yaml:"username"`
Password string `config:"password" yaml:"password"`
Protocol string `config:"protocol" yaml:"protocol,omitempty"`
Host string `config:"host" yaml:"host,omitempty"`
Path string `config:"path" yaml:"path,omitempty"`
SpaceID string `config:"space.id" yaml:"space.id,omitempty"`
Username string `config:"username" yaml:"username,omitempty"`
Password string `config:"password" yaml:"password,omitempty"`
TLS *tlscommon.Config `config:"ssl" yaml:"ssl"`
Timeout time.Duration `config:"timeout" yaml:"timeout"`
IgnoreVersion bool
Expand Down
101 changes: 59 additions & 42 deletions x-pack/libbeat/cmd/enroll.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"github.com/spf13/cobra"

"github.com/elastic/beats/libbeat/cmd/instance"
"github.com/elastic/beats/libbeat/common"
"github.com/elastic/beats/libbeat/common/cli"
"github.com/elastic/beats/x-pack/libbeat/management"
"github.com/elastic/beats/x-pack/libbeat/management/api"
Expand Down Expand Up @@ -40,62 +41,67 @@ func genEnrollCmd(name, version string) *cobra.Command {
Long: `This will enroll in Kibana Beats Central Management. If you pass an enrollment token
it will be used. You can also enroll using a username and password combination.`,
Args: cobra.RangeArgs(1, 2),
Run: cli.RunWith(func(cmd *cobra.Command, args []string) error {
beat, err := getBeat(name, version)
if err != nil {
return err
}

kibanaURL := args[0]

if username == "" && len(args) == 1 {
return errors.New("You should pass either an enrollment token or use --username flag")
}

var enrollmentToken string
if len(args) == 2 {
// use given enrollment token
enrollmentToken = args[1]
Run: cli.RunWith(
func(cmd *cobra.Command, args []string) error {
beat, err := getBeat(name, version)
if err != nil {
return err
}
} else {
// retrieve an enrollment token using username/password
config, err := api.ConfigFromURL(kibanaURL)
if err != nil {
return err

kibanaURL := args[0]

if username == "" && len(args) == 1 {
return errors.New("You should pass either an enrollment token or use --username flag")
}

// pass username/password
config.IgnoreVersion = true
config.Username = username
config.Password, err = cli.ReadPassword(password)
// Retrieve any available configuration avaible for Kibana, either
// from the configuration file or using `-E`.
kibanaRaw, err := kibanaConfig(beat.Config.Management)
if err != nil {
return err
}

client, err := api.NewClient(config)
// retrieve an enrollment token using username/password
config, err := api.ConfigFromURL(kibanaURL, kibanaRaw)
if err != nil {
return err
}
enrollmentToken, err = client.CreateEnrollmentToken()

var enrollmentToken string
if len(args) == 2 {
// use given enrollment token
enrollmentToken = args[1]
} else {
// pass username/password
config.IgnoreVersion = true
config.Username = username
config.Password, err = cli.ReadPassword(password)
if err != nil {
return err
}

client, err := api.NewClient(config)
if err != nil {
return err
}
enrollmentToken, err = client.CreateEnrollmentToken()
if err != nil {
return errors.Wrap(err, "Error creating a new enrollment token")
}
}

enrolled, err := management.Enroll(beat, config, enrollmentToken, force)
if err != nil {
return errors.Wrap(err, "Error creating a new enrollment token")
return errors.Wrap(err, "Error while enrolling")
}

if enrolled {
fmt.Println("Enrolled and ready to retrieve settings from Kibana")
} else {
fmt.Println("Enrollment was canceled by the user")
}
}

enrolled, err := management.Enroll(beat, kibanaURL, enrollmentToken, force)
if err != nil {
return errors.Wrap(err, "Error while enrolling")
}

if enrolled {
fmt.Println("Enrolled and ready to retrieve settings from Kibana")
} else {
fmt.Println("Enrollment was canceled by the user")
}
return nil
}),
return nil
}),
}

enrollCmd.Flags().StringVar(&username, "username", "elastic", "Username to use when enrolling without token")
Expand All @@ -104,3 +110,14 @@ func genEnrollCmd(name, version string) *cobra.Command {

return &enrollCmd
}

func kibanaConfig(config *common.Config) (*common.Config, error) {
if config != nil && config.HasField("kibana") {
sub, err := config.Child("kibana", -1)
if err != nil {
return nil, err
}
return sub, nil
}
return common.NewConfig(), nil
}
25 changes: 16 additions & 9 deletions x-pack/libbeat/management/api/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ type Client struct {
}

// ConfigFromURL generates a full kibana client config from an URL
func ConfigFromURL(kibanaURL string) (*kibana.ClientConfig, error) {
func ConfigFromURL(kibanaURL string, config *common.Config) (*kibana.ClientConfig, error) {
data, err := url.Parse(kibanaURL)
if err != nil {
return nil, err
Expand All @@ -37,14 +37,21 @@ func ConfigFromURL(kibanaURL string) (*kibana.ClientConfig, error) {
password, _ = data.User.Password()
}

return &kibana.ClientConfig{
Protocol: data.Scheme,
Host: data.Host,
Path: data.Path,
Username: username,
Password: password,
Timeout: defaultTimeout,
}, nil
// Lets pick up any configuration from either the YAML or from the -E flags.
// and merge it with the provided URL.
kibana := kibana.ClientConfig{}
if err := config.Unpack(&kibana); err != nil {
return nil, err
}

kibana.Protocol = data.Scheme
kibana.Host = data.Host
kibana.Path = data.Path
kibana.Username = username
kibana.Password = password
kibana.Timeout = defaultTimeout

return &kibana, nil
}

// NewClient creates and returns a kibana client
Expand Down
4 changes: 3 additions & 1 deletion x-pack/libbeat/management/api/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import (
"net/http"
"net/http/httptest"
"testing"

"github.com/elastic/beats/libbeat/common"
)

func newServerClientPair(t *testing.T, handler http.HandlerFunc) (*httptest.Server, *Client) {
Expand All @@ -19,7 +21,7 @@ func newServerClientPair(t *testing.T, handler http.HandlerFunc) (*httptest.Serv

server := httptest.NewServer(mux)

config, err := ConfigFromURL(server.URL)
config, err := ConfigFromURL(server.URL, common.NewConfig())
if err != nil {
t.Fatal(err)
}
Expand Down
11 changes: 0 additions & 11 deletions x-pack/libbeat/management/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
package management

import (
"errors"
"io"
"text/template"
"time"
Expand Down Expand Up @@ -67,8 +66,6 @@ const ManagedConfigTemplate = `
#xpack.monitoring.elasticsearch:
`

var errEmptyAccessToken = errors.New("access_token is empty, you must reenroll your Beat")

// Config for central management
type Config struct {
// true when enrolled
Expand All @@ -84,14 +81,6 @@ type Config struct {
Blacklist ConfigBlacklistSettings `config:"blacklist" yaml:"blacklist"`
}

// Validate validates the fields in the config.
func (c *Config) Validate() error {
if len(c.AccessToken) == 0 {
return errEmptyAccessToken
}
return nil
}

func defaultConfig() *Config {
return &Config{
Period: 60 * time.Second,
Expand Down
41 changes: 0 additions & 41 deletions x-pack/libbeat/management/config_test.go

This file was deleted.

Loading

0 comments on commit 0a914fa

Please sign in to comment.