Skip to content

Commit

Permalink
Update upgrade code paths, rename configuration files fleet.yml and s…
Browse files Browse the repository at this point in the history
…tate.yml into fleet.enc and state.enc after the encryption
  • Loading branch information
aleksmaus committed May 4, 2022
1 parent 5922e7b commit c53341d
Show file tree
Hide file tree
Showing 6 changed files with 204 additions and 21 deletions.
45 changes: 36 additions & 9 deletions internal/pkg/agent/application/paths/files.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,20 +15,42 @@ import (
// defaultAgentCapabilitiesFile is a name of file used to store agent capabilities
const defaultAgentCapabilitiesFile = "capabilities.yml"

// defaultAgentFleetFile is a name of file used to store agent information
const defaultAgentFleetFile = "fleet.yml"
// defaultAgentFleetYmlFile is a name of file used to store agent information
const defaultAgentFleetYmlFile = "fleet.yml"

// defaultAgentFleetFile is a name of file used to store agent information encrypted
const defaultAgentFleetFile = "fleet.enc"

// defaultAgentEnrollFile is a name of file used to enroll agent on first-start
const defaultAgentEnrollFile = "enroll.yml"

// defaultAgentActionStoreFile is the file that will contain the action that can be replayed after restart.
const defaultAgentActionStoreFile = "action_store.yml"

// defaultAgentStateStoreFile is the file that will contain the action that can be replayed after restart.
const defaultAgentStateStoreFile = "state.yml"
// defaultAgentStateStoreYmlFile is the file that will contain the action that can be replayed after restart.
const defaultAgentStateStoreYmlFile = "state.yml"

// defaultAgentStateStoreFile is the file that will contain the action that can be replayed after restart encrypted.
const defaultAgentStateStoreFile = "state.enc"

// defaultAgentVaultName is keychain item name for mac
const defaultAgentVaultName = "co.elastic.agent"

// defaultAgentVaultPath is the directory for windows and linux where the vault store is located or the
const defaultAgentVaultPath = "vault"

// AgentConfigYmlFile is a name of file used to store agent information
func AgentConfigYmlFile() string {
return filepath.Join(Config(), defaultAgentFleetYmlFile)
}

// defaultAgentVaultPath is the directory for windows and linux where the vault store is located or the keychain item name for mac
const defaultAgentVaultPath = "co.elastic.agent"
// AgentConfigYmlFileLock is a locker for agent config file updates.
func AgentConfigYmlFileLock() *filelock.AppLocker {
return filelock.NewAppLocker(
Config(),
fmt.Sprintf("%s.lock", defaultAgentFleetYmlFile),
)
}

// AgentConfigFile is a name of file used to store agent information
func AgentConfigFile() string {
Expand Down Expand Up @@ -58,15 +80,20 @@ func AgentActionStoreFile() string {
return filepath.Join(Home(), defaultAgentActionStoreFile)
}

// AgentStateStoreFile is the file that contains the persisted state of the agent including the action that can be replayed after restart.
// AgentStateStoreYmlFile is the file that contains the persisted state of the agent including the action that can be replayed after restart.
func AgentStateStoreYmlFile() string {
return filepath.Join(Home(), defaultAgentStateStoreYmlFile)
}

// AgentStateStoreFile is the file that contains the persisted state of the agent including the action that can be replayed after restart encrypted.
func AgentStateStoreFile() string {
return filepath.Join(Home(), defaultAgentStateStoreFile)
}

// AgentVaultPath is the directory that contains all the files for the value for windows and linux
func AgentVaultPath() string {
if runtime.GOOS == "darwin" {
return defaultAgentVaultPath
return defaultAgentVaultName
}
return filepath.Join(Home(), "vault", defaultAgentVaultPath)
return filepath.Join(Home(), defaultAgentVaultPath)
}
105 changes: 100 additions & 5 deletions internal/pkg/agent/application/upgrade/upgrade.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,13 @@
package upgrade

import (
"bytes"
"context"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"runtime"
"strings"

"github.com/otiai10/copy"
Expand All @@ -18,8 +20,10 @@ import (
"github.com/elastic/elastic-agent/internal/pkg/agent/application/info"
"github.com/elastic/elastic-agent/internal/pkg/agent/application/paths"
"github.com/elastic/elastic-agent/internal/pkg/agent/application/reexec"
"github.com/elastic/elastic-agent/internal/pkg/agent/application/secret"
"github.com/elastic/elastic-agent/internal/pkg/agent/errors"
"github.com/elastic/elastic-agent/internal/pkg/agent/program"
"github.com/elastic/elastic-agent/internal/pkg/agent/storage"
"github.com/elastic/elastic-agent/internal/pkg/artifact"
"github.com/elastic/elastic-agent/internal/pkg/capabilities"
"github.com/elastic/elastic-agent/internal/pkg/core/state"
Expand Down Expand Up @@ -158,12 +162,17 @@ func (u *Upgrader) Upgrade(ctx context.Context, a Action, reexecNow bool) (_ ree
return nil, nil
}

// Copy vault directory for linux/windows only
if err := copyVault(newHash); err != nil {
return nil, errors.New(err, "failed to copy vault")
}

if err := copyActionStore(newHash); err != nil {
return nil, errors.New(err, "failed to copy action store")
}

if err := copyVault(newHash); err != nil {
return nil, errors.New(err, "failed to copy vault")
if err := encryptConfigIfNeeded(newHash); err != nil {
return nil, errors.New(err, "failed to encrypt the configuration")
}

if err := ChangeSymlink(ctx, newHash); err != nil {
Expand Down Expand Up @@ -270,7 +279,8 @@ func rollbackInstall(ctx context.Context, hash string) {
}

func copyActionStore(newHash string) error {
storePaths := []string{paths.AgentActionStoreFile(), paths.AgentStateStoreFile()}
// copies legacy action_store.yml, state.yml and state.enc encrypted file if exists
storePaths := []string{paths.AgentActionStoreFile(), paths.AgentStateStoreYmlFile(), paths.AgentStateStoreFile()}

for _, currentActionStorePath := range storePaths {
newHome := filepath.Join(filepath.Dir(paths.Home()), fmt.Sprintf("%s-%s", agentName, newHash))
Expand All @@ -293,12 +303,97 @@ func copyActionStore(newHash string) error {
return nil
}

func getVaultPath(newHash string) string {
vaultPath := paths.AgentVaultPath()
if runtime.GOOS == "darwin" {
return vaultPath
}
newHome := filepath.Join(filepath.Dir(paths.Home()), fmt.Sprintf("%s-%s", agentName, newHash))
return filepath.Join(newHome, filepath.Base(vaultPath))
}

// Copies the vault files for windows and linux
func copyVault(newHash string) error {
//nolint:godox // TODO
// TODO (AM): Implement copy vault for windows and linux
// No vault files to copy on darwin
if runtime.GOOS == "darwin" {
return nil
}

vaultPath := paths.AgentVaultPath()
newVaultPath := getVaultPath(newHash)

err := copyDir(vaultPath, newVaultPath)
if err != nil {
if os.IsNotExist(err) {
return nil
}
return err
}

return nil
}

// Create the key if it doesn't exists and encrypt the fleet.yml and state.yml
func encryptConfigIfNeeded(newHash string) (err error) {
vaultPath := getVaultPath(newHash)

err = secret.CreateAgentSecret(secret.WithVaultPath(vaultPath))

if err != nil {
return err
}

newHome := filepath.Join(filepath.Dir(paths.Home()), fmt.Sprintf("%s-%s", agentName, newHash))
ymlActionStorePath := filepath.Join(newHome, filepath.Base(paths.AgentStateStoreYmlFile()))
actionStorePath := filepath.Join(newHome, filepath.Base(paths.AgentStateStoreFile()))

files := []struct {
Src string
Dst string
}{
{
Src: ymlActionStorePath,
Dst: actionStorePath,
},
{
Src: paths.AgentConfigYmlFile(),
Dst: paths.AgentConfigFile(),
},
}
for _, f := range files {
b, err := ioutil.ReadFile(f.Src)
if err != nil {
if os.IsNotExist(err) {
continue
}
return err
}

// Encrypt yml file
store := storage.NewEncryptedDiskStore(f.Dst, storage.WithVaultPath(vaultPath))
err = store.Save(bytes.NewReader(b))
if err != nil {
return err
}

// Remove yml file if no errors
defer func(fp string) {
if err != nil {
return
}
_ = os.Remove(fp)
}(f.Src)
}

// Remove fleet.yml.lock file if no errors
if err != nil {
return
}
_ = os.Remove(paths.AgentConfigYmlFile() + ".lock")

return err
}

// shutdownCallback returns a callback function to be executing during shutdown once all processes are closed.
// this goes through runtime directory of agent and copies all the state files created by processes to new versioned
// home directory with updated process name to match new version.
Expand Down
41 changes: 41 additions & 0 deletions internal/pkg/agent/application/upgrade/upgrade_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ import (

"github.com/stretchr/testify/require"

"github.com/elastic/elastic-agent/internal/pkg/agent/application/paths"
"github.com/elastic/elastic-agent/internal/pkg/agent/errors"
"github.com/elastic/elastic-agent/internal/pkg/release"
"github.com/elastic/elastic-agent/pkg/core/logger"
)
Expand Down Expand Up @@ -54,3 +56,42 @@ func TestShutdownCallback(t *testing.T) {
require.NoError(t, err, "reading file failed")
require.Equal(t, content, newContent, "contents are not equal")
}

func TestCopyFiles(t *testing.T) {
const home = "/home/amaus/elastic/elastic-agent/build/distributions/main/elastic-agent-8.3.0-SNAPSHOT-linux-x86_64"
paths.SetTop(home)
paths.SetConfig(home)
storePath := paths.AgentStateStoreFile()
fmt.Println("store path:", storePath)

configPath := paths.AgentConfigFile()
fmt.Println("config path:", configPath)

vaultPath := paths.AgentVaultPath()
fmt.Println("vault path:", vaultPath)

newHash := "5922e7"
newPath := "/home/amaus/elastic/elastic-agent/build/distributions/main/elastic-agent-8.3.0-SNAPSHOT-linux-x86_64/data/elastic-agent-" + newHash
os.RemoveAll(newPath)
os.MkdirAll(newPath, 0750)
err := upgradeForTest(newHash)
if err != nil {
t.Fatal(err)
}
}

func upgradeForTest(newHash string) error {
if err := copyVault(newHash); err != nil {
return nil
}

if err := copyActionStore(newHash); err != nil {
return errors.New(err, "failed to copy action store")
}

if err := encryptConfigIfNeeded(newHash); err != nil {
return errors.New(err, "failed to encrypt the configuration")
}

return nil
}
26 changes: 22 additions & 4 deletions internal/pkg/agent/storage/encrypted_disk_store.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,37 @@ import (
"fmt"
"io"
"os"
"runtime"

"github.com/elastic/elastic-agent-libs/file"
"github.com/elastic/elastic-agent/internal/pkg/agent/application/paths"
"github.com/elastic/elastic-agent/internal/pkg/agent/application/secret"
"github.com/elastic/elastic-agent/internal/pkg/agent/errors"
"github.com/elastic/elastic-agent/internal/pkg/crypto"
"github.com/hectane/go-acl"
)

type OptionFunc func(s *EncryptedDiskStore)

// NewEncryptedDiskStore creates an encrypted disk store.
// Drop-in replacement for NewDiskStorage
func NewEncryptedDiskStore(target string) *EncryptedDiskStore {
return &EncryptedDiskStore{
target: target,
func NewEncryptedDiskStore(target string, opts ...OptionFunc) *EncryptedDiskStore {
s := &EncryptedDiskStore{
target: target,
vaultPath: paths.AgentVaultPath(),
}
for _, opt := range opts {
opt(s)
}
return s
}

func WithVaultPath(vaultPath string) OptionFunc {
return func(s *EncryptedDiskStore) {
if runtime.GOOS == "darwin" {
return
}
s.vaultPath = vaultPath
}
}

Expand All @@ -37,7 +55,7 @@ func (d *EncryptedDiskStore) Exists() (bool, error) {

func (d *EncryptedDiskStore) ensureKey() error {
if d.key == nil {
key, err := secret.GetAgentSecret()
key, err := secret.GetAgentSecret(secret.WithVaultPath(d.vaultPath))
if err != nil {
return err
}
Expand Down
5 changes: 3 additions & 2 deletions internal/pkg/agent/storage/storage.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ type DiskStore struct {
}

type EncryptedDiskStore struct {
target string
key []byte
target string
vaultPath string
key []byte
}
3 changes: 2 additions & 1 deletion internal/pkg/agent/vault/seed.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,10 @@ func getSeed(path string) ([]byte, error) {

isOwnerRoot, err := isFileOwnerRoot(fp)
if err != nil {
if !errors.Is(err, os.ErrNotExist) {
if !os.IsNotExist(err) {
return nil, err
}
isOwnerRoot = true
}

if !isOwnerRoot {
Expand Down

0 comments on commit c53341d

Please sign in to comment.