mirror of
https://github.com/getsops/sops.git
synced 2026-02-05 12:45:21 +01:00
Encrypt and decrypt time.Time objects.
Signed-off-by: Felix Fontein <felix@fontein.de>
This commit is contained in:
@@ -11,6 +11,7 @@ import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/getsops/sops/v3"
|
||||
"github.com/getsops/sops/v3/logging"
|
||||
@@ -110,6 +111,10 @@ func (c Cipher) Decrypt(ciphertext string, key []byte, additionalData string) (p
|
||||
plaintext = decryptedBytes
|
||||
case "bool":
|
||||
plaintext, err = strconv.ParseBool(decryptedValue)
|
||||
case "time":
|
||||
var value time.Time
|
||||
err = value.UnmarshalText(decryptedBytes)
|
||||
plaintext = value
|
||||
case "comment":
|
||||
plaintext = sops.Comment{Value: decryptedValue}
|
||||
default:
|
||||
@@ -176,6 +181,12 @@ func (c Cipher) Encrypt(plaintext interface{}, key []byte, additionalData string
|
||||
} else {
|
||||
plainBytes = []byte("False")
|
||||
}
|
||||
case time.Time:
|
||||
encryptedType = "time"
|
||||
plainBytes, err = value.MarshalText()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("Error marshaling timestamp %q: %w", value, err)
|
||||
}
|
||||
case sops.Comment:
|
||||
encryptedType = "comment"
|
||||
plainBytes = []byte(value.Value)
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
package aes
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/rand"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
"testing/quick"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/getsops/sops/v3"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestDecrypt(t *testing.T) {
|
||||
@@ -108,6 +111,36 @@ func TestRoundtripBool(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestRoundtripTime(t *testing.T) {
|
||||
key := []byte(strings.Repeat("f", 32))
|
||||
parsedTime, err := time.Parse(time.RFC3339, "2006-01-02T15:04:05+07:00")
|
||||
assert.Nil(t, err)
|
||||
loc := time.FixedZone("", 12300) // offset must be divisible by 60, otherwise won't survive a round-trip
|
||||
values := []time.Time{
|
||||
time.UnixMilli(0).In(time.UTC),
|
||||
time.UnixMilli(123456).In(time.UTC),
|
||||
time.UnixMilli(123456).In(loc),
|
||||
time.UnixMilli(123456789).In(time.UTC),
|
||||
time.UnixMilli(123456789).In(loc),
|
||||
time.UnixMilli(1234567890).In(time.UTC),
|
||||
time.UnixMilli(1234567890).In(loc),
|
||||
parsedTime,
|
||||
}
|
||||
for _, value := range values {
|
||||
s, err := NewCipher().Encrypt(value, key, "foo")
|
||||
assert.Nil(t, err)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
d, err := NewCipher().Decrypt(s, key, "foo")
|
||||
assert.Nil(t, err)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
assert.Equal(t, value, d)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEncryptEmptyComment(t *testing.T) {
|
||||
key := []byte(strings.Repeat("f", 32))
|
||||
s, err := NewCipher().Encrypt(sops.Comment{}, key, "")
|
||||
@@ -121,3 +154,61 @@ func TestDecryptEmptyValue(t *testing.T) {
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, "", s)
|
||||
}
|
||||
|
||||
// This test would belong more in sops_test.go, but from there we cannot access
|
||||
// the aes package to get a cipher which can actually handle time.Time objects.
|
||||
func TestTimestamps(t *testing.T) {
|
||||
unixTime := time.UnixMilli(123456789).In(time.UTC)
|
||||
parsedTime, err := time.Parse(time.RFC3339, "2006-01-02T15:04:05+07:00")
|
||||
assert.Nil(t, err)
|
||||
branches := sops.TreeBranches{
|
||||
sops.TreeBranch{
|
||||
sops.TreeItem{
|
||||
Key: "foo",
|
||||
Value: unixTime,
|
||||
},
|
||||
sops.TreeItem{
|
||||
Key: "bar",
|
||||
Value: sops.TreeBranch{
|
||||
sops.TreeItem{
|
||||
Key: "foo",
|
||||
Value: parsedTime,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
tree := sops.Tree{Branches: branches, Metadata: sops.Metadata{UnencryptedSuffix: "_unencrypted"}}
|
||||
expected := sops.TreeBranch{
|
||||
sops.TreeItem{
|
||||
Key: "foo",
|
||||
Value: unixTime,
|
||||
},
|
||||
sops.TreeItem{
|
||||
Key: "bar",
|
||||
Value: sops.TreeBranch{
|
||||
sops.TreeItem{
|
||||
Key: "foo",
|
||||
Value: parsedTime,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
cipher := NewCipher()
|
||||
_, err = tree.Encrypt(bytes.Repeat([]byte("f"), 32), cipher)
|
||||
if err != nil {
|
||||
t.Errorf("Encrypting the tree failed: %s", err)
|
||||
}
|
||||
if reflect.DeepEqual(tree.Branches[0], expected) {
|
||||
t.Errorf("Trees do match: \ngot \t\t%+v,\n not expected \t\t%+v", tree.Branches[0], expected)
|
||||
}
|
||||
_, err = tree.Decrypt(bytes.Repeat([]byte("f"), 32), cipher)
|
||||
if err != nil {
|
||||
t.Errorf("Decrypting the tree failed: %s", err)
|
||||
}
|
||||
assert.Equal(t, tree.Branches[0][0].Value, unixTime)
|
||||
assert.Equal(t, tree.Branches[0], expected)
|
||||
if !reflect.DeepEqual(tree.Branches[0], expected) {
|
||||
t.Errorf("Trees don't match: \ngot\t\t\t%+v,\nexpected\t\t%+v", tree.Branches[0], expected)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -659,6 +659,50 @@ b: ba"#
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_yaml_time() {
|
||||
let file_path = prepare_temp_file(
|
||||
"test_time.yaml",
|
||||
r#"a: 2024-01-01
|
||||
b: 2006-01-02T15:04:05+07:06"#
|
||||
.as_bytes(),
|
||||
);
|
||||
assert!(
|
||||
Command::new(SOPS_BINARY_PATH)
|
||||
.arg("encrypt")
|
||||
.arg("-i")
|
||||
.arg(file_path.clone())
|
||||
.output()
|
||||
.expect("Error running sops")
|
||||
.status
|
||||
.success(),
|
||||
"sops didn't exit successfully"
|
||||
);
|
||||
let output = Command::new(SOPS_BINARY_PATH)
|
||||
.arg("decrypt")
|
||||
.arg("-i")
|
||||
.arg(file_path.clone())
|
||||
.output()
|
||||
.expect("Error running sops");
|
||||
println!(
|
||||
"stdout: {}, stderr: {}",
|
||||
String::from_utf8_lossy(&output.stdout),
|
||||
String::from_utf8_lossy(&output.stderr)
|
||||
);
|
||||
assert!(output.status.success(), "sops didn't exit successfully");
|
||||
let mut s = String::new();
|
||||
File::open(file_path)
|
||||
.unwrap()
|
||||
.read_to_string(&mut s)
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
s,
|
||||
r#"a: 2024-01-01T00:00:00Z
|
||||
b: 2006-01-02T15:04:05+07:06
|
||||
"#
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unset_json_file() {
|
||||
// Test removal of tree branch
|
||||
|
||||
4
sops.go
4
sops.go
@@ -347,6 +347,8 @@ func (branch TreeBranch) walkValue(in interface{}, path []string, commentsStack
|
||||
return onLeaves(in, path, commentsStack)
|
||||
case float64:
|
||||
return onLeaves(in, path, commentsStack)
|
||||
case time.Time:
|
||||
return onLeaves(in, path, commentsStack)
|
||||
case Comment:
|
||||
return onLeaves(in, path, commentsStack)
|
||||
case TreeBranch:
|
||||
@@ -968,6 +970,8 @@ func ToBytes(in interface{}) ([]byte, error) {
|
||||
return boolB, nil
|
||||
case []byte:
|
||||
return in, nil
|
||||
case time.Time:
|
||||
return in.MarshalText()
|
||||
case Comment:
|
||||
return ToBytes(in.Value)
|
||||
default:
|
||||
|
||||
Reference in New Issue
Block a user