diff --git a/keystore/file_keystore_fips.go b/keystore/file_keystore_fips.go new file mode 100644 index 00000000..d77089fc --- /dev/null +++ b/keystore/file_keystore_fips.go @@ -0,0 +1,84 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +//go:build requirefips + +package keystore + +import ( + "github.com/elastic/elastic-agent-libs/config" +) + +// NopKeystore is used in FIPS mode to a beats keystore. +type NopKeystore struct{} + +// NewFileKeystoreWithPasswordAndStrictPerms returns a NopKeystore. +// +// It is using an exising, exported function call name so callers do not need to change the function call, just the build tag. +func NewFileKeystoreWithPasswordAndStrictPerms(keystoreFile string, password *SecureString, strictPerms bool) (Keystore, error) { + return &NopKeystore{}, nil +} + +// Retrieve returns ErrKeyDoesntExists +func (*NopKeystore) Retrieve(key string) (*SecureString, error) { + return nil, ErrKeyDoesntExists +} + +// Store returns ErrKeystoreDisabled +func (*NopKeystore) Store(key string, value []byte) error { + return nil, ErrKeystoreDisabled +} + +// Delete is a nop that returns nil +func (*NopKeystore) Delete(key string) error { + return nil +} + +// Save returns ErrKeystoreDisabled +func (*NopKeystore) Save() error { + return ErrKeystoreDisabled +} + +// List is a nop that returns a 0 length list of strings +func (*NopKeystore) List() ([]string, error) { + return []string{}, nil +} + +// GetConfig returns is a nop that returns a new config.C +func (*NopKeystore) GetConfig() (*config.C, error) { + return config.NewConfig(), nil +} + +// Create returns ErrKeystoreDisabled +func (*NopKeystore) Create(override bool) error { + return ErrKeystoreDisabled +} + +// IsPersisted always retuns false +func (*NopKeystore) IsPersisted() bool { + return false +} + +// Package is a nop that always returns nil +func (*NopKeystore) Package() ([]byte, error) { + return nil, nil +} + +// ConfiguredPath retuns an empty string +func (*NopKeystore) ConfiguredPath() string { + return "" +} diff --git a/keystore/file_keystore.go b/keystore/file_keystore_nofips.go similarity index 79% rename from keystore/file_keystore.go rename to keystore/file_keystore_nofips.go index 41236e5c..2c9a3987 100644 --- a/keystore/file_keystore.go +++ b/keystore/file_keystore_nofips.go @@ -15,13 +15,14 @@ // specific language governing permissions and limitations // under the License. +//go:build !requirefips + package keystore import ( "bytes" "crypto/aes" "crypto/cipher" - "crypto/rand" "crypto/sha512" "encoding/base64" "encoding/json" @@ -38,25 +39,6 @@ import ( "github.com/elastic/elastic-agent-libs/file" ) -const ( - filePermission = 0600 - - // Encryption Related constants - iVLength = 12 - saltLength = 64 - iterationsCount = 10000 - keyLength = 32 -) - -// 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 -} - // FileKeystore Allows to store key / secrets pair securely into an encrypted local file. type FileKeystore struct { sync.RWMutex @@ -67,48 +49,7 @@ type FileKeystore struct { isStrictPerms bool } -// Allow the original SecureString type to be correctly serialized to json. -type serializableSecureString struct { - *SecureString - Value []byte `json:"value"` -} - -// Factory Create the right keystore with the configured options. -func Factory(c *config.C, defaultPath string, strictPerms bool) (Keystore, error) { - cfg := defaultConfig() - - if c == nil { - c = config.NewConfig() - } - err := c.Unpack(&cfg) - - if err != nil { - return nil, fmt.Errorf("could not read keystore configuration, err: %w", err) - } - - if cfg.Path == "" { - cfg.Path = defaultPath - } - - keystore, err := NewFileKeystoreWithStrictPerms(cfg.Path, strictPerms) - 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) { - return NewFileKeystoreWithStrictPerms(keystoreFile, false) -} - -// 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 NewFileKeystoreWithStrictPerms(keystoreFile string, strictPerms bool) (Keystore, error) { - return NewFileKeystoreWithPasswordAndStrictPerms(keystoreFile, NewSecureString([]byte("")), strictPerms) -} - -// NewFileKeystoreWithPassword return a new File based keystore or an error, allow to define what +// NewFileKeystoreWithPasswordAndStrictPerms return a new File based keystore or an error, allow to define what // password to use to create the keystore. func NewFileKeystoreWithPasswordAndStrictPerms(keystoreFile string, password *SecureString, strictPerms bool) (Keystore, error) { keystore := FileKeystore{ @@ -127,12 +68,6 @@ func NewFileKeystoreWithPasswordAndStrictPerms(keystoreFile string, password *Se return &keystore, nil } -// 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) { - return NewFileKeystoreWithPasswordAndStrictPerms(keystoreFile, password, false) -} - // Retrieve return a SecureString instance that will contains both the key and the secret. func (k *FileKeystore) Retrieve(key string) (*SecureString, error) { k.RLock() @@ -459,15 +394,3 @@ func (k *FileKeystore) ConfiguredPath() string { func (k *FileKeystore) hashPassword(password, salt []byte) []byte { return pbkdf2.Key(password, salt, iterationsCount, keyLength, sha512.New) } - -// randomBytes return a slice of random bytes of the defined length -func randomBytes(length int) ([]byte, error) { - r := make([]byte, length) - _, err := rand.Read(r) - - if err != nil { - return nil, err - } - - return r, nil -} diff --git a/keystore/file_keystore_test.go b/keystore/file_keystore_nofips_test.go similarity index 100% rename from keystore/file_keystore_test.go rename to keystore/file_keystore_nofips_test.go diff --git a/keystore/keystore.go b/keystore/keystore.go index 491a1162..05a02f0d 100644 --- a/keystore/keystore.go +++ b/keystore/keystore.go @@ -18,7 +18,9 @@ package keystore import ( + "crypto/rand" "errors" + "fmt" "github.com/elastic/elastic-agent-libs/config" "github.com/elastic/go-ucfg" @@ -37,8 +39,24 @@ var ( // ErrNotWritable is returned when the keystore is not writable ErrNotListing = errors.New("the configured keystore is not listing") + + // ErrKeystoreDisabled is returned on create, save, and store operations if built with the requirefips flag + ErrKeystoreDisabled = fmt.Errorf("keystore disabled in FIPS mode: %w", errors.ErrUnsupported) +) + +const ( + filePermission = 0600 + + // Encryption Related constants + iVLength = 12 + saltLength = 64 + iterationsCount = 10000 + keyLength = 32 ) +// Version of the keystore format, will be added at the beginning of the file. +var version = []byte("v1") + // Keystore implement a way to securely saves and retrieves secrets to be used in the configuration // Currently all credentials are loaded upfront and are not lazy retrieved, we will eventually move // to that concept, so we can deal with tokens that has a limited duration or can be revoked by a @@ -73,6 +91,12 @@ type ListingKeystore interface { List() ([]string, 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 +} + // Use parse.NoopConfig to disable interpreting all parser characters when loading secrets. var parseConfig = parse.NoopConfig @@ -118,3 +142,56 @@ func AsListingKeystore(store Keystore) (ListingKeystore, error) { } return w, nil } + +// Factory Create the right keystore with the configured options. +func Factory(c *config.C, defaultPath string, strictPerms bool) (Keystore, error) { + cfg := defaultConfig() + + if c == nil { + c = config.NewConfig() + } + err := c.Unpack(&cfg) + + if err != nil { + return nil, fmt.Errorf("could not read keystore configuration, err: %w", err) + } + + if cfg.Path == "" { + cfg.Path = defaultPath + } + + keystore, err := NewFileKeystoreWithStrictPerms(cfg.Path, strictPerms) + 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) { + return NewFileKeystoreWithStrictPerms(keystoreFile, false) +} + +// 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 NewFileKeystoreWithStrictPerms(keystoreFile string, strictPerms bool) (Keystore, error) { + return NewFileKeystoreWithPasswordAndStrictPerms(keystoreFile, NewSecureString([]byte("")), strictPerms) +} + +// 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) { + return NewFileKeystoreWithPasswordAndStrictPerms(keystoreFile, password, false) +} + +// randomBytes return a slice of random bytes of the defined length +func randomBytes(length int) ([]byte, error) { + r := make([]byte, length) + _, err := rand.Read(r) + + if err != nil { + return nil, err + } + + return r, nil +} diff --git a/keystore/secure_string.go b/keystore/secure_string.go index d6f2ce9b..c38b56fe 100644 --- a/keystore/secure_string.go +++ b/keystore/secure_string.go @@ -26,6 +26,12 @@ type SecureString struct { value []byte } +// Allow the original SecureString type to be correctly serialized to json. +type serializableSecureString struct { + *SecureString + Value []byte `json:"value"` +} + // NewSecureString return a struct representing a secrets string. func NewSecureString(value []byte) *SecureString { return &SecureString{