diff options
| author | Shulhan <ms@kilabit.info> | 2023-09-22 02:43:21 +0700 |
|---|---|---|
| committer | Shulhan <ms@kilabit.info> | 2023-09-22 02:43:21 +0700 |
| commit | f720644d0baaaf91e41b2e0374437d5d1e70dd9a (patch) | |
| tree | 2edf2364800cba565e0c71f1d44a709ae3fbc75d | |
| parent | 0d0217abcd91f9c597eaf44cb639191e42290b5d (diff) | |
| download | awwan-f720644d0baaaf91e41b2e0374437d5d1e70dd9a.tar.xz | |
all: implement reading encrypted awwan environment ".awwan.env.vault"
Upon executing "local" or "play" comman, awwan now read the encrypted
environment file .awwan.env.vault.
The encrypted environment file is generated using "awwan encrypt" command.
| -rw-r--r-- | awwan.go | 7 | ||||
| -rw-r--r-- | awwan_test.go | 64 | ||||
| -rw-r--r-- | script_example_test.go | 2 | ||||
| -rw-r--r-- | session.go | 73 | ||||
| -rw-r--r-- | testdata/encrypt/.awwan.env.plain | 2 | ||||
| -rw-r--r-- | testdata/encrypt/.awwan.env.vault | bin | 0 -> 384 bytes | |||
| l--------- | testdata/encrypt/.awwan.key | 1 | ||||
| -rw-r--r-- | testdata/encrypt/.ssh/empty | 0 | ||||
| -rw-r--r-- | testdata/encrypt/local.aww | 1 | ||||
| -rw-r--r-- | testdata/encrypt/sub/.awwan.env.plain | 2 | ||||
| -rw-r--r-- | testdata/encrypt/sub/.awwan.env.vault | bin | 0 -> 384 bytes | |||
| -rw-r--r-- | testdata/encrypt/sub/local.aww | 1 | ||||
| -rw-r--r-- | testdata/encrypt/test.data | 9 |
13 files changed, 137 insertions, 25 deletions
@@ -49,6 +49,9 @@ const ( // defEncryptExt default file extension for encrypted file. const defEncryptExt = `.vault` +// defFileEnvVault default awwan environment file name that is encrypted. +const defFileEnvVault = `.awwan.env.vault` + // defFilePrivateKey define the default private key file name. const defFilePrivateKey = `.awwan.key` @@ -225,7 +228,7 @@ func (aww *Awwan) Local(req *Request) (err error) { sessionDir = filepath.Dir(req.scriptPath) - ses, err = NewSession(aww.BaseDir, sessionDir) + ses, err = NewSession(aww, sessionDir) if err != nil { return fmt.Errorf("%s: %w", logp, err) } @@ -294,7 +297,7 @@ func (aww *Awwan) Play(req *Request) (err error) { sessionDir = filepath.Dir(req.scriptPath) - ses, err = NewSession(aww.BaseDir, sessionDir) + ses, err = NewSession(aww, sessionDir) if err != nil { return fmt.Errorf("%s: %w", logp, err) } diff --git a/awwan_test.go b/awwan_test.go index 64f6ec0..4e2a17a 100644 --- a/awwan_test.go +++ b/awwan_test.go @@ -1,6 +1,7 @@ package awwan import ( + "bytes" "os" "path/filepath" "testing" @@ -142,3 +143,66 @@ func TestAwwanEncrypt(t *testing.T) { } } } + +func TestAwwanLocal_withEncryption(t *testing.T) { + type testCase struct { + script string + lineRange string + tdataOut string + } + + var ( + tdata *test.Data + err error + ) + + tdata, err = test.LoadData(`testdata/encrypt/test.data`) + if err != nil { + t.Fatal(err) + } + + var ( + basedir = filepath.Join(`testdata`, `encrypt`) + mockout = bytes.Buffer{} + mockerr = bytes.Buffer{} + mockrw = mock.ReadWriter{} + aww = Awwan{} + ) + + // Mock terminal to read passphrase for private key. + mockrw.BufRead.WriteString("s3cret\r") + aww.termrw = &mockrw + + err = aww.init(basedir) + if err != nil { + t.Fatal(err) + } + + var cases = []testCase{{ + script: filepath.Join(basedir, `local.aww`), + lineRange: `1`, + tdataOut: `local.aww:1`, + }, { + script: filepath.Join(basedir, `sub`, `local.aww`), + lineRange: `1`, + tdataOut: `sub/local.aww:1`, + }} + + var c testCase + + for _, c = range cases { + var req = NewRequest(CommandModeLocal, c.script, c.lineRange) + + mockout.Reset() + mockerr.Reset() + req.stdout = &mockout + req.stderr = &mockerr + + err = aww.Local(req) + if err != nil { + t.Fatal(err) + } + + test.Assert(t, `stdout`, string(tdata.Output[c.tdataOut]), mockout.String()) + } +} diff --git a/script_example_test.go b/script_example_test.go index 724dafd..def544a 100644 --- a/script_example_test.go +++ b/script_example_test.go @@ -28,7 +28,7 @@ end; stmt []byte ) - err = ses.loadEnvFromBytes([]byte(envContent)) + err = ses.loadRawEnv([]byte(envContent)) if err != nil { log.Fatal(err) } @@ -4,6 +4,7 @@ package awwan import ( + "crypto/rsa" "fmt" "os" "os/exec" @@ -21,9 +22,11 @@ import ( // Session manage and cache SSH client and list of scripts. // One session have one SSH client, but may contains more than one script. type Session struct { - sftpc *sftp.Client - sshClient *ssh.Client - vars *ini.Ini + privateKey *rsa.PrivateKey + sftpc *sftp.Client + sshClient *ssh.Client + + vars ini.Ini BaseDir string ScriptDir string @@ -40,7 +43,7 @@ type Session struct { // NewSession create and initialize the new session based on Awwan base // directory and the session directory. -func NewSession(baseDir, sessionDir string) (ses *Session, err error) { +func NewSession(aww *Awwan, sessionDir string) (ses *Session, err error) { var ( logp = "newSession" @@ -48,7 +51,9 @@ func NewSession(baseDir, sessionDir string) (ses *Session, err error) { ) ses = &Session{ - BaseDir: baseDir, + privateKey: aww.privateKey, + + BaseDir: aww.BaseDir, ScriptDir: sessionDir, hostname: filepath.Base(sessionDir), } @@ -394,7 +399,7 @@ func (ses *Session) executeScriptOnLocal(req *Request, pos linePosition) { continue } - fmt.Fprintf(req.stdout, "\n>>> local: %3d: %s\n", x, stmt.raw) + fmt.Fprintf(req.stdout, "\n--> local: %3d: %s\n", x, stmt.raw) var err error switch stmt.kind { @@ -437,7 +442,7 @@ func (ses *Session) executeScriptOnRemote(req *Request, pos linePosition) { continue } - fmt.Fprintf(req.stdout, "\n>>> %s: %3d: %s %s\n", + fmt.Fprintf(req.stdout, "\n--> %s: %3d: %s %s\n", ses.sshClient, x, stmt.cmd, stmt.args) var err error @@ -535,42 +540,66 @@ func (ses *Session) loadEnvFromPaths() (err error) { path string awwanEnv string - content []byte ) for _, path = range ses.paths { + // Load unencrypted "awwan.env". awwanEnv = filepath.Join(path, defEnvFileName) - content, err = os.ReadFile(awwanEnv) + err = ses.loadFileEnv(awwanEnv, false) if err != nil { - if os.IsNotExist(err) { - continue - } - return fmt.Errorf("%s: %s: %w", logp, awwanEnv, err) + return fmt.Errorf(`%s: %w`, logp, err) } - fmt.Printf(">>> loading %q ...\n", awwanEnv) - err = ses.loadEnvFromBytes(content) + // Load encrypted ".awwan.env.vault". + awwanEnv = filepath.Join(path, defFileEnvVault) + + err = ses.loadFileEnv(awwanEnv, true) if err != nil { - return fmt.Errorf("%s: %w", logp, err) + return fmt.Errorf(`%s: %w`, logp, err) } } return nil } -func (ses *Session) loadEnvFromBytes(content []byte) (err error) { - in, err := ini.Parse(content) +func (ses *Session) loadFileEnv(awwanEnv string, isVault bool) (err error) { + var content []byte + + content, err = os.ReadFile(awwanEnv) + if err != nil { + if os.IsNotExist(err) { + return nil + } + return fmt.Errorf(`%s: %w`, awwanEnv, err) + } + + fmt.Printf("--- loading %q ...\n", awwanEnv) + + if isVault { + content, err = decrypt(ses.privateKey, content) + if err != nil { + return err + } + } + + err = ses.loadRawEnv(content) if err != nil { return err } - in.Prune() + return nil +} - if ses.vars == nil { - ses.vars = in - return nil +func (ses *Session) loadRawEnv(content []byte) (err error) { + var in *ini.Ini + + in, err = ini.Parse(content) + if err != nil { + return err } + in.Prune() ses.vars.Rebase(in) + return nil } diff --git a/testdata/encrypt/.awwan.env.plain b/testdata/encrypt/.awwan.env.plain new file mode 100644 index 0000000..26ed285 --- /dev/null +++ b/testdata/encrypt/.awwan.env.plain @@ -0,0 +1,2 @@ +[secret] +pass = this_is_a_secret diff --git a/testdata/encrypt/.awwan.env.vault b/testdata/encrypt/.awwan.env.vault Binary files differnew file mode 100644 index 0000000..a4e4bdd --- /dev/null +++ b/testdata/encrypt/.awwan.env.vault diff --git a/testdata/encrypt/.awwan.key b/testdata/encrypt/.awwan.key new file mode 120000 index 0000000..aa99eff --- /dev/null +++ b/testdata/encrypt/.awwan.key @@ -0,0 +1 @@ +../encrypt-with-passphrase/.awwan.key
\ No newline at end of file diff --git a/testdata/encrypt/.ssh/empty b/testdata/encrypt/.ssh/empty new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/testdata/encrypt/.ssh/empty diff --git a/testdata/encrypt/local.aww b/testdata/encrypt/local.aww new file mode 100644 index 0000000..d9127db --- /dev/null +++ b/testdata/encrypt/local.aww @@ -0,0 +1 @@ +echo {{.Val "secret::pass"}} diff --git a/testdata/encrypt/sub/.awwan.env.plain b/testdata/encrypt/sub/.awwan.env.plain new file mode 100644 index 0000000..02b2ae0 --- /dev/null +++ b/testdata/encrypt/sub/.awwan.env.plain @@ -0,0 +1,2 @@ +[secret] +pass = this_is_a_secret_in_sub diff --git a/testdata/encrypt/sub/.awwan.env.vault b/testdata/encrypt/sub/.awwan.env.vault Binary files differnew file mode 100644 index 0000000..21aa5b1 --- /dev/null +++ b/testdata/encrypt/sub/.awwan.env.vault diff --git a/testdata/encrypt/sub/local.aww b/testdata/encrypt/sub/local.aww new file mode 100644 index 0000000..d9127db --- /dev/null +++ b/testdata/encrypt/sub/local.aww @@ -0,0 +1 @@ +echo {{.Val "secret::pass"}} diff --git a/testdata/encrypt/test.data b/testdata/encrypt/test.data new file mode 100644 index 0000000..9ecdb9f --- /dev/null +++ b/testdata/encrypt/test.data @@ -0,0 +1,9 @@ +<<< local.aww:1 + +--> local: 1: echo this_is_a_secret +this_is_a_secret + +<<< sub/local.aww:1 + +--> local: 1: echo this_is_a_secret_in_sub +this_is_a_secret_in_sub |
