Skip to content

Commit 0af40f1

Browse files
committed
Lock credentials file while editing, dont generate new encrypted file if no file changes
1 parent b333270 commit 0af40f1

File tree

6 files changed

+82
-6
lines changed

6 files changed

+82
-6
lines changed

Readme.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
Sicher is a Go implementation of the secret management system that was introduced in Ruby on Rails 6.
44

5-
Sicher is a go package that allows the safe storage of encrypted credentials in a version control system. The credentials can only be decrypted by a key file, and this key file is not added to the source control. The file is edited in a temp file on a local system and destroyed after each edit.
5+
Sicher is a go package that allows the secure storage of encrypted credentials in a version control system. The credentials can only be decrypted by a key file, and this key file is not added to the source control. The file is edited in a temp file on a local system and destroyed after each edit.
66

77
Using sicher in a project creates a set of files
88

dev.enc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
cb3272c8a4437c24b70296e697b551a9aa972e244d682af20fd5d2d1a4f32c9b1a059a48caa0d8c08e52e59c4660f88ae4309d5a0e3e5e198a81974c7e4918fd7fdbfcf32e72f3d9af4a9fc1fe7ea56b9f7d2b315b4fbfe47d9d5fc698510322a9==--==090e8f04181739a0d57068d8
1+
5f990c89f9b5f406e2e6d3c641b6fea7efadd697e759f5b63f1cdfa6265f38d957eeb3==--==25a5bf6099052bf3b862e49f

filelock.go

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
package sicher
2+
3+
import (
4+
"fmt"
5+
"log"
6+
"os"
7+
"time"
8+
9+
"golang.org/x/sys/unix"
10+
)
11+
12+
type fileLock struct {
13+
file *os.File
14+
}
15+
16+
type FileCreator func() (*os.File, error)
17+
18+
func newFileLock(file *os.File) *fileLock {
19+
return &fileLock{file: file}
20+
}
21+
22+
func (l *fileLock) Lock() bool {
23+
if err := unix.Flock(int(l.file.Fd()), unix.LOCK_EX); err != nil {
24+
log.Fatalf("file lock error: %v\n", err)
25+
return false
26+
}
27+
return true
28+
}
29+
30+
func (l *fileLock) Unlock() {
31+
if err := unix.Flock(int(l.file.Fd()), unix.LOCK_UN); err != nil {
32+
log.Fatalf("file unlock error: %v\n", err)
33+
}
34+
}
35+
36+
func (l *fileLock) Create(creator FileCreator) error {
37+
f, err := creator()
38+
if err != nil {
39+
return err
40+
}
41+
defer f.Close()
42+
43+
l.file = f
44+
l.Lock()
45+
return nil
46+
}
47+
48+
func (l *fileLock) LockWithTimeout(timeout time.Duration) {
49+
ch := make(chan bool)
50+
go func() {
51+
ch <- l.Lock()
52+
}()
53+
54+
select {
55+
case <-time.After(timeout):
56+
fmt.Printf("File is being edited in another terminal. Lock timeout after %v\n", timeout)
57+
os.Exit(1)
58+
case <-ch:
59+
break
60+
}
61+
}

go.mod

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
11
module github.com/dsa0x/sicher
22

33
go 1.17
4+
5+
require golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e // indirect

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e h1:fLOSk5Q00efkSvAm+4xcoXD+RRmLmmulPn5I3Y9F2EM=
2+
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=

sicher.go

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import (
1616
"path/filepath"
1717
"reflect"
1818
"regexp"
19+
"time"
1920
)
2021

2122
var delimiter = "==--=="
@@ -177,7 +178,7 @@ func (s *sicher) Edit(editor ...string) error {
177178
if os.Getenv(masterKey) != "" {
178179
key = []byte(os.Getenv(masterKey))
179180
} else {
180-
return fmt.Errorf("encryption key(%s.key) is not available. Create one by running the cli with init flag", s.Environment)
181+
return fmt.Errorf("encryption key(%s.key) is not available. Provide a key file or enter one through the command line", s.Environment)
181182
}
182183
}
183184
strKey := string(key)
@@ -189,6 +190,11 @@ func (s *sicher) Edit(editor ...string) error {
189190
}
190191
defer credFile.Close()
191192

193+
// lock file to enable only one edit at a time
194+
credFileLock := newFileLock(credFile)
195+
credFileLock.LockWithTimeout(time.Second * 10)
196+
defer credFileLock.Unlock()
197+
192198
var buf bytes.Buffer
193199
_, err = io.Copy(&buf, credFile)
194200
if err != nil {
@@ -202,7 +208,6 @@ func (s *sicher) Edit(editor ...string) error {
202208
return fmt.Errorf("error creating temp file %v", err)
203209
}
204210
defer f.Close()
205-
f.Chmod(0600)
206211
filePath := f.Name()
207212
defer cleanUpFile(filePath)
208213

@@ -212,8 +217,9 @@ func (s *sicher) Edit(editor ...string) error {
212217
return fmt.Errorf("error decoding encryption file: %s", err)
213218
}
214219

220+
var plaintext []byte
215221
if nonce != nil && fileText != nil {
216-
plaintext, err := decrypt(strKey, nonce, fileText)
222+
plaintext, err = decrypt(strKey, nonce, fileText)
217223
if err != nil {
218224
return fmt.Errorf("error decrypting file: %s", err)
219225
}
@@ -246,11 +252,16 @@ func (s *sicher) Edit(editor ...string) error {
246252
return fmt.Errorf("error reading credentials file %v ", err)
247253
}
248254

255+
// if no file changes, dont generate new encrypted file
256+
if bytes.Equal(file, plaintext) {
257+
fmt.Fprintf(stdOut, "No changes made.\n")
258+
return nil
259+
}
260+
249261
//encrypt and overwrite credentials file
250262
// the encrypted file is encoded in hexadecimal format
251263
nonce, encrypted, err := encrypt(strKey, file)
252264
if err != nil {
253-
254265
return fmt.Errorf("error encrypting file: %s ", err)
255266
}
256267

0 commit comments

Comments
 (0)