Skip to content

Commit

Permalink
Add support for etcd password auth
Browse files Browse the repository at this point in the history
This commit adds support for etcd password authentication,
it makes client cert auth optional.

Here is an example:

```yaml
storage:
    type: etcd
    peers: ['https://example.com:30983']
    username: 'username'
    password_file: '/mnt/secrets/etcd-pass'
    tls_ca_file: '/mnt/secrets/etcd-ca.pem'
```
  • Loading branch information
klizhentas authored and fspmarshall committed Jan 3, 2020
1 parent 0da8cd3 commit d2d4248
Show file tree
Hide file tree
Showing 3 changed files with 66 additions and 31 deletions.
89 changes: 58 additions & 31 deletions lib/backend/etcdbk/etcd.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import (
"encoding/base64"
"io/ioutil"
"sort"
"strings"
"time"

"github.com/gravitational/teleport/lib/backend"
Expand Down Expand Up @@ -142,17 +143,30 @@ type EtcdBackend struct {

// Config represents JSON config for etcd backend
type Config struct {
Nodes []string `json:"peers,omitempty"`
Key string `json:"prefix,omitempty"`
TLSKeyFile string `json:"tls_key_file,omitempty"`
TLSCertFile string `json:"tls_cert_file,omitempty"`
TLSCAFile string `json:"tls_ca_file,omitempty"`
Insecure bool `json:"insecure,omitempty"`
// Nodes is a list of nodes
Nodes []string `json:"peers,omitempty"`
// Key is an optional prefix for etcd
Key string `json:"prefix,omitempty"`
// TLSKeyFile is a private key, implies mTLS client authentication
TLSKeyFile string `json:"tls_key_file,omitempty"`
// TLSCertFile is a client certificate implies mTLS client authentication
TLSCertFile string `json:"tls_cert_file,omitempty"`
// TLSCAFile is a trusted certificate authority certificate
TLSCAFile string `json:"tls_ca_file,omitempty"`
// Insecure turns off TLS
Insecure bool `json:"insecure,omitempty"`
// BufferSize is a default buffer size
// used to pull events
BufferSize int `json:"buffer_size,omitempty"`
// DialTimeout specifies dial timeout
DialTimeout time.Duration `json:"dial_timeout,omitempty"`
// Username is an optional username for HTTPS basic authentication
Username string `json:"username,omitempty"`
// Password is initalized from password file, and is not read from the config
Password string `json:"-"`
// PasswordFile is an optional password file for HTTPS basic authentication,
// expects path to a file
PasswordFile string `json:"password_file,omitempty"`
}

// GetName returns the name of etcd backend as it appears in 'storage/type' section
Expand Down Expand Up @@ -209,20 +223,14 @@ func New(ctx context.Context, params backend.Params) (*EtcdBackend, error) {
// Validate checks if all the parameters are present/valid
func (cfg *Config) Validate() error {
if len(cfg.Key) == 0 {
return trace.BadParameter(`etcd: missing "prefix" setting`)
return trace.BadParameter(`etcd: missing "prefix" parameter`)
}
if len(cfg.Nodes) == 0 {
return trace.BadParameter(`etcd: missing "peers" setting`)
return trace.BadParameter(`etcd: missing "peers" parameter`)
}
if cfg.Insecure == false {
if cfg.TLSKeyFile == "" {
return trace.BadParameter(`etcd: missing "tls_key_file" setting`)
}
if cfg.TLSCertFile == "" {
return trace.BadParameter(`etcd: missing "tls_cert_file" setting`)
}
if cfg.TLSCAFile == "" {
return trace.BadParameter(`etcd: missing "tls_ca_file" setting`)
return trace.BadParameter(`etcd: missing "tls_ca_file" parameter`)
}
}
if cfg.BufferSize == 0 {
Expand All @@ -231,6 +239,14 @@ func (cfg *Config) Validate() error {
if cfg.DialTimeout == 0 {
cfg.DialTimeout = defaults.DefaultDialTimeout
}
if cfg.PasswordFile != "" {
out, err := ioutil.ReadFile(cfg.PasswordFile)
if err != nil {
return trace.ConvertSystemError(err)
}
// trim newlines as passwords in files tend to have newlines
cfg.Password = strings.TrimSpace(string(out))
}
return nil
}

Expand All @@ -251,38 +267,49 @@ func (b *EtcdBackend) CloseWatchers() {
}

func (b *EtcdBackend) reconnect() error {
clientCertPEM, err := ioutil.ReadFile(b.cfg.TLSCertFile)
if err != nil {
return trace.ConvertSystemError(err)
}
clientKeyPEM, err := ioutil.ReadFile(b.cfg.TLSKeyFile)
if err != nil {
return trace.ConvertSystemError(err)
}
caCertPEM, err := ioutil.ReadFile(b.cfg.TLSCAFile)
if err != nil {
return trace.ConvertSystemError(err)
}
tlsConfig := utils.TLSConfig(nil)
tlsCert, err := tls.X509KeyPair(clientCertPEM, clientKeyPEM)
if err != nil {
return trace.BadParameter("failed to parse private key: %v", err)

if b.cfg.TLSCertFile != "" {
clientCertPEM, err := ioutil.ReadFile(b.cfg.TLSCertFile)
if err != nil {
return trace.ConvertSystemError(err)
}
clientKeyPEM, err := ioutil.ReadFile(b.cfg.TLSKeyFile)
if err != nil {
return trace.ConvertSystemError(err)
}
tlsCert, err := tls.X509KeyPair(clientCertPEM, clientKeyPEM)
if err != nil {
return trace.BadParameter("failed to parse private key: %v", err)
}
tlsConfig.Certificates = []tls.Certificate{tlsCert}
}

var caCertPEM []byte
if b.cfg.TLSCAFile != "" {
var err error
caCertPEM, err = ioutil.ReadFile(b.cfg.TLSCAFile)
if err != nil {
return trace.ConvertSystemError(err)
}
}

certPool := x509.NewCertPool()
parsedCert, err := tlsca.ParseCertificatePEM(caCertPEM)
if err != nil {
return trace.Wrap(err, "failed to parse CA certificate")
}
certPool.AddCert(parsedCert)

tlsConfig.Certificates = []tls.Certificate{tlsCert}
tlsConfig.RootCAs = certPool
tlsConfig.ClientCAs = certPool

clt, err := clientv3.New(clientv3.Config{
Endpoints: b.nodes,
TLS: tlsConfig,
DialTimeout: b.cfg.DialTimeout,
Username: b.cfg.Username,
Password: b.cfg.Password,
})
if err != nil {
return trace.Wrap(err)
Expand Down
6 changes: 6 additions & 0 deletions lib/events/s3sessions/s3handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import (

"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/awserr"
"github.com/aws/aws-sdk-go/aws/credentials"
awssession "github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/s3"
"github.com/aws/aws-sdk-go/service/s3/s3manager"
Expand All @@ -55,6 +56,8 @@ type Config struct {
DisableServerSideEncryption bool
// Session is an optional existing AWS client session
Session *awssession.Session
// Credentials if supplied are used in tests
Credentials *credentials.Credentials
}

// SetFromURL sets values on the Config from the supplied URI
Expand Down Expand Up @@ -112,6 +115,9 @@ func (s *Config) CheckAndSetDefaults() error {
if s.Insecure {
sess.Config.DisableSSL = aws.Bool(s.Insecure)
}
if s.Credentials != nil {
sess.Config.Credentials = s.Credentials
}
s.Session = sess
}
return nil
Expand Down
2 changes: 2 additions & 0 deletions lib/events/s3sessions/s3handler_thirdparty_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import (
"github.com/gravitational/teleport/lib/events/test"
"github.com/gravitational/teleport/lib/utils"

"github.com/aws/aws-sdk-go/aws/credentials"
"github.com/gravitational/trace"
"github.com/johannesboyne/gofakes3"
"github.com/johannesboyne/gofakes3/backend/s3mem"
Expand Down Expand Up @@ -55,6 +56,7 @@ func (s *S3ThirdPartySuite) SetUpSuite(c *check.C) {

var err error
s.HandlerSuite.Handler, err = NewHandler(Config{
Credentials: credentials.NewStaticCredentials("YOUR-ACCESSKEYID", "YOUR-SECRETACCESSKEY", ""),
Region: "us-west-1",
Path: "/test/",
Bucket: fmt.Sprintf("teleport-test-%v", uuid.New()),
Expand Down

0 comments on commit d2d4248

Please sign in to comment.