Skip to content

Commit 92b6728

Browse files
authored
Merge branch 'main' into allow-azure-keyvault-empty-version
2 parents b9ee238 + 193c269 commit 92b6728

File tree

13 files changed

+251
-27
lines changed

13 files changed

+251
-27
lines changed

.github/workflows/cli.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ jobs:
2929
VAULT_ADDR: "http://127.0.0.1:8200"
3030
steps:
3131
- name: Set up Go ${{ matrix.go-version }}
32-
uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0
32+
uses: actions/setup-go@44694675825211faa026b3c33043df3e48a5fa00 # v6.0.0
3333
with:
3434
go-version: ${{ matrix.go-version }}
3535
id: go

.github/workflows/codeql.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ jobs:
3535

3636
# Initializes the CodeQL tools for scanning.
3737
- name: Initialize CodeQL
38-
uses: github/codeql-action/init@3c3833e0f8c1c83d449a7478aa59c036a9165498 # v3.29.5
38+
uses: github/codeql-action/init@f1f6e5f6af878fb37288ce1c627459e94dbf7d01 # v3.29.5
3939
with:
4040
languages: go
4141
# xref: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
@@ -52,6 +52,6 @@ jobs:
5252
make install
5353
5454
- name: Perform CodeQL Analysis
55-
uses: github/codeql-action/analyze@3c3833e0f8c1c83d449a7478aa59c036a9165498 # v3.29.5
55+
uses: github/codeql-action/analyze@f1f6e5f6af878fb37288ce1c627459e94dbf7d01 # v3.29.5
5656
with:
5757
category: "/language:go"

.github/workflows/release.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ jobs:
3131
persist-credentials: false
3232

3333
- name: Setup Go
34-
uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v4.0.1
34+
uses: actions/setup-go@44694675825211faa026b3c33043df3e48a5fa00 # v4.0.1
3535
with:
3636
go-version: 1.24
3737
cache: false

audit/audit.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import (
1414

1515
"github.com/getsops/sops/v3/logging"
1616
"github.com/sirupsen/logrus"
17-
"gopkg.in/yaml.v3"
17+
"go.yaml.in/yaml/v3"
1818
)
1919

2020
var log *logrus.Logger

config/config.go

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ import (
1919
"github.com/getsops/sops/v3/kms"
2020
"github.com/getsops/sops/v3/pgp"
2121
"github.com/getsops/sops/v3/publish"
22-
"gopkg.in/yaml.v3"
22+
"go.yaml.in/yaml/v3"
2323
)
2424

2525
type fileSystem interface {
@@ -508,7 +508,18 @@ func parseDestinationRuleForFile(conf *configFile, filePath string, kmsEncryptio
508508
}
509509

510510
var dest publish.Destination
511-
if dRule.S3Bucket != "" && dRule.GCSBucket != "" && dRule.VaultPath != "" {
511+
destinationCount := 0
512+
if dRule.S3Bucket != "" {
513+
destinationCount++
514+
}
515+
if dRule.GCSBucket != "" {
516+
destinationCount++
517+
}
518+
if dRule.VaultPath != "" {
519+
destinationCount++
520+
}
521+
522+
if destinationCount > 1 {
512523
return nil, fmt.Errorf("error loading config: more than one destinations were found in a single destination rule, you can only use one per rule")
513524
}
514525
if dRule.S3Bucket != "" {

config/config_test.go

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -755,3 +755,127 @@ creation_rules:
755755
assert.Equal(t, 1, keyTypeCounts["gcp_kms"])
756756
assert.Equal(t, 1, keyTypeCounts["hc_vault"])
757757
}
758+
759+
// Test configurations with multiple destinations should fail
760+
var sampleConfigWithS3GCSConflict = []byte(`
761+
destination_rules:
762+
- path_regex: '^test/.*'
763+
s3_bucket: 'my-s3-bucket'
764+
s3_prefix: 'sops/'
765+
gcs_bucket: 'my-gcs-bucket'
766+
gcs_prefix: 'sops/'
767+
recreation_rule:
768+
kms: 'arn:aws:kms:us-east-1:123456789012:key/12345678-1234-1234-1234-123456789012'
769+
`)
770+
771+
var sampleConfigWithS3VaultConflict = []byte(`
772+
destination_rules:
773+
- path_regex: '^test/.*'
774+
s3_bucket: 'my-s3-bucket'
775+
s3_prefix: 'sops/'
776+
vault_path: 'secret/sops'
777+
vault_address: 'https://vault.example.com'
778+
recreation_rule:
779+
kms: 'arn:aws:kms:us-east-1:123456789012:key/12345678-1234-1234-1234-123456789012'
780+
`)
781+
782+
var sampleConfigWithGCSVaultConflict = []byte(`
783+
destination_rules:
784+
- path_regex: '^test/.*'
785+
gcs_bucket: 'my-gcs-bucket'
786+
gcs_prefix: 'sops/'
787+
vault_path: 'secret/sops'
788+
vault_address: 'https://vault.example.com'
789+
recreation_rule:
790+
kms: 'arn:aws:kms:us-east-1:123456789012:key/12345678-1234-1234-1234-123456789012'
791+
`)
792+
793+
var sampleConfigWithAllThreeDestinations = []byte(`
794+
destination_rules:
795+
- path_regex: '^test/.*'
796+
s3_bucket: 'my-s3-bucket'
797+
s3_prefix: 'sops/'
798+
gcs_bucket: 'my-gcs-bucket'
799+
gcs_prefix: 'sops/'
800+
vault_path: 'secret/sops'
801+
vault_address: 'https://vault.example.com'
802+
recreation_rule:
803+
kms: 'arn:aws:kms:us-east-1:123456789012:key/12345678-1234-1234-1234-123456789012'
804+
`)
805+
806+
func TestDestinationValidationS3GCSConflict(t *testing.T) {
807+
_, err := parseDestinationRuleForFile(parseConfigFile(sampleConfigWithS3GCSConflict, t), "test/secrets.yaml", nil)
808+
assert.NotNil(t, err, "Expected error when both S3 and GCS destinations are specified")
809+
if err != nil {
810+
assert.Contains(t, err.Error(), "more than one destinations were found")
811+
}
812+
}
813+
814+
func TestDestinationValidationS3VaultConflict(t *testing.T) {
815+
_, err := parseDestinationRuleForFile(parseConfigFile(sampleConfigWithS3VaultConflict, t), "test/secrets.yaml", nil)
816+
assert.NotNil(t, err, "Expected error when both S3 and Vault destinations are specified")
817+
if err != nil {
818+
assert.Contains(t, err.Error(), "more than one destinations were found")
819+
}
820+
}
821+
822+
func TestDestinationValidationGCSVaultConflict(t *testing.T) {
823+
_, err := parseDestinationRuleForFile(parseConfigFile(sampleConfigWithGCSVaultConflict, t), "test/secrets.yaml", nil)
824+
assert.NotNil(t, err, "Expected error when both GCS and Vault destinations are specified")
825+
if err != nil {
826+
assert.Contains(t, err.Error(), "more than one destinations were found")
827+
}
828+
}
829+
830+
func TestDestinationValidationAllThreeDestinationsConflict(t *testing.T) {
831+
_, err := parseDestinationRuleForFile(parseConfigFile(sampleConfigWithAllThreeDestinations, t), "test/secrets.yaml", nil)
832+
assert.NotNil(t, err, "Expected error when all three destinations are specified")
833+
if err != nil {
834+
assert.Contains(t, err.Error(), "more than one destinations were found")
835+
}
836+
}
837+
838+
func TestDestinationValidationSingleS3Destination(t *testing.T) {
839+
validS3Config := []byte(`
840+
destination_rules:
841+
- path_regex: '^test/.*'
842+
s3_bucket: 'my-s3-bucket'
843+
s3_prefix: 'sops/'
844+
recreation_rule:
845+
kms: 'arn:aws:kms:us-east-1:123456789012:key/12345678-1234-1234-1234-123456789012'
846+
`)
847+
conf, err := parseDestinationRuleForFile(parseConfigFile(validS3Config, t), "test/secrets.yaml", nil)
848+
assert.Nil(t, err)
849+
assert.NotNil(t, conf.Destination)
850+
assert.Contains(t, conf.Destination.Path("secrets.yaml"), "s3://my-s3-bucket/sops/secrets.yaml")
851+
}
852+
853+
func TestDestinationValidationSingleGCSDestination(t *testing.T) {
854+
validGCSConfig := []byte(`
855+
destination_rules:
856+
- path_regex: '^test/.*'
857+
gcs_bucket: 'my-gcs-bucket'
858+
gcs_prefix: 'sops/'
859+
recreation_rule:
860+
kms: 'arn:aws:kms:us-east-1:123456789012:key/12345678-1234-1234-1234-123456789012'
861+
`)
862+
conf, err := parseDestinationRuleForFile(parseConfigFile(validGCSConfig, t), "test/secrets.yaml", nil)
863+
assert.Nil(t, err)
864+
assert.NotNil(t, conf.Destination)
865+
assert.Contains(t, conf.Destination.Path("secrets.yaml"), "gcs://my-gcs-bucket/sops/secrets.yaml")
866+
}
867+
868+
func TestDestinationValidationSingleVaultDestination(t *testing.T) {
869+
validVaultConfig := []byte(`
870+
destination_rules:
871+
- path_regex: '^test/.*'
872+
vault_path: 'secret/sops'
873+
vault_address: 'https://vault.example.com'
874+
recreation_rule:
875+
kms: 'arn:aws:kms:us-east-1:123456789012:key/12345678-1234-1234-1234-123456789012'
876+
`)
877+
conf, err := parseDestinationRuleForFile(parseConfigFile(validVaultConfig, t), "test/secrets.yaml", nil)
878+
assert.Nil(t, err)
879+
assert.NotNil(t, conf.Destination)
880+
assert.Contains(t, conf.Destination.Path("secrets.yaml"), "https://vault.example.com/v1/secret/data/secret/sops/secrets.yaml")
881+
}

functional-tests/src/lib.rs

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -265,6 +265,57 @@ bar: baz
265265
}
266266
}
267267

268+
#[test]
269+
fn test_ini_values_as_strings() {
270+
let file_path = prepare_temp_file(
271+
"test_ini_values_as_strings.yaml",
272+
b"the_section:
273+
int: 123
274+
float: 1.23
275+
bool: true
276+
date: 2025-01-02
277+
timestamp: 2025-01-02 03:04:05
278+
utc_timestamp: 2025-01-02T03:04:05Z
279+
string: this is a string",
280+
);
281+
assert!(
282+
Command::new(SOPS_BINARY_PATH)
283+
.arg("encrypt")
284+
.arg("-i")
285+
.arg(file_path.clone())
286+
.output()
287+
.expect("Error running sops")
288+
.status
289+
.success(),
290+
"sops didn't exit successfully"
291+
);
292+
let output = Command::new(SOPS_BINARY_PATH)
293+
.arg("decrypt")
294+
.arg("--output-type")
295+
.arg("ini")
296+
.arg(file_path.clone())
297+
.output()
298+
.expect("Error running sops");
299+
println!(
300+
"stdout: {}, stderr: {}",
301+
String::from_utf8_lossy(&output.stdout),
302+
String::from_utf8_lossy(&output.stderr)
303+
);
304+
assert!(output.status.success(), "sops didn't exit successfully");
305+
let data = &String::from_utf8_lossy(&output.stdout);
306+
assert!(
307+
data == "[the_section]
308+
int = 123
309+
float = 1.23
310+
bool = true
311+
date = 2025-01-02T00:00:00Z
312+
timestamp = 2025-01-02T03:04:05Z
313+
utc_timestamp = 2025-01-02T03:04:05Z
314+
string = this is a string
315+
"
316+
);
317+
}
318+
268319
#[test]
269320
fn encrypt_yaml_file() {
270321
let file_path = prepare_temp_file(

go.mod

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ require (
3333
github.com/sirupsen/logrus v1.9.3
3434
github.com/stretchr/testify v1.11.0
3535
github.com/urfave/cli v1.22.17
36+
go.yaml.in/yaml/v3 v3.0.4
3637
golang.org/x/crypto v0.41.0
3738
golang.org/x/net v0.43.0
3839
golang.org/x/oauth2 v0.30.0
@@ -43,7 +44,6 @@ require (
4344
google.golang.org/grpc v1.75.0
4445
google.golang.org/protobuf v1.36.8
4546
gopkg.in/ini.v1 v1.67.0
46-
gopkg.in/yaml.v3 v3.0.1
4747
)
4848

4949
require (
@@ -145,4 +145,5 @@ require (
145145
golang.org/x/time v0.12.0 // indirect
146146
google.golang.org/genproto v0.0.0-20250603155806-513f23925822 // indirect
147147
google.golang.org/genproto/googleapis/api v0.0.0-20250818200422-3122310a409c // indirect
148+
gopkg.in/yaml.v3 v3.0.1 // indirect
148149
)

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -303,6 +303,8 @@ go.opentelemetry.io/otel/sdk/metric v1.37.0 h1:90lI228XrB9jCMuSdA0673aubgRobVZFh
303303
go.opentelemetry.io/otel/sdk/metric v1.37.0/go.mod h1:cNen4ZWfiD37l5NhS+Keb5RXVWZWpRE+9WyVCpbo5ps=
304304
go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4=
305305
go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0=
306+
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
307+
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
306308
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
307309
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
308310
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=

stores/ini/store.go

Lines changed: 1 addition & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,6 @@ import (
44
"bytes"
55
"encoding/json"
66
"fmt"
7-
8-
"strconv"
97
"strings"
108

119
"github.com/getsops/sops/v3"
@@ -56,7 +54,7 @@ func (store Store) encodeTree(branches sops.TreeBranches) ([]byte, error) {
5654
lastItem.Comment = comment.Value
5755
}
5856
} else {
59-
lastItem, err = section.NewKey(keyVal.Key.(string), store.valToString(keyVal.Value))
57+
lastItem, err = section.NewKey(keyVal.Key.(string), stores.ValToString(keyVal.Value))
6058
if err != nil {
6159
return nil, fmt.Errorf("Error encoding key: %s", err)
6260
}
@@ -78,19 +76,6 @@ func (store Store) stripCommentChar(comment string) string {
7876
return comment
7977
}
8078

81-
func (store Store) valToString(v interface{}) string {
82-
switch v := v.(type) {
83-
case fmt.Stringer:
84-
return v.String()
85-
case float64:
86-
return strconv.FormatFloat(v, 'f', 6, 64)
87-
case bool:
88-
return strconv.FormatBool(v)
89-
default:
90-
return fmt.Sprintf("%s", v)
91-
}
92-
}
93-
9479
func (store Store) iniFromTreeBranches(branches sops.TreeBranches) ([]byte, error) {
9580
return store.encodeTree(branches)
9681
}

0 commit comments

Comments
 (0)