Skip to content

Commit 3885b12

Browse files
felixfonteinarunbhagyanath
authored andcommitted
Add support for OVH KMS in SOPS
Signed-off-by: Arun Bhagyanath <[email protected]>
2 parents 7d77e8a + df09e2c commit 3885b12

File tree

14 files changed

+970
-108
lines changed

14 files changed

+970
-108
lines changed

README.rst

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -515,6 +515,50 @@ To easily deploy Vault locally: (DO NOT DO THIS FOR PRODUCTION!!!)
515515
516516
$ sops encrypt --verbose prod/raw.yaml > prod/encrypted.yaml
517517
518+
Encrypting using OVH Key Management Service
519+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
520+
521+
To use OVH KMS with SOPS, you need to:
522+
523+
1. Have an OVH account with KMS service enabled
524+
2. Create a key in OVH KMS that will be used for encryption/decryption
525+
3. Obtain an access certificate and private key for authentication
526+
527+
You can use OVH KMS in your `.sops.yaml` file like this:
528+
529+
.. code:: yaml
530+
531+
creation_rules:
532+
- path_regex: path/to/files/*.yaml
533+
ovh_kms: <kms-rest-endpoint>/<your-key-id>
534+
535+
Or with key groups:
536+
537+
.. code:: yaml
538+
539+
creation_rules:
540+
- path_regex: path/to/files/*.yaml
541+
key_groups:
542+
- ovh_kms:
543+
- key_id: <kms-rest-endpoint>/<your-key-id>
544+
545+
Usage
546+
547+
After configuration, you can use SOPS normally and it will automatically use OVH KMS for encryption/decryption:
548+
549+
.. code:: sh
550+
551+
# Set required environment variables
552+
export OVH_CERTIFICATE_PATH=/path/to/certificate.pem
553+
export OVH_CERTIFICATE_KEY_PATH=/path/to/private-key.pem
554+
555+
# Encrypt a file
556+
sops -e -i secrets.yaml
557+
558+
# Decrypt a file
559+
sops -d secrets.yaml
560+
561+
518562
Adding and removing keys
519563
~~~~~~~~~~~~~~~~~~~~~~~~
520564

cmd/sops/main.go

Lines changed: 49 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"context"
55
encodingjson "encoding/json"
66
"fmt"
7+
78
"net"
89
"net/url"
910
"os"
@@ -38,6 +39,7 @@ import (
3839
"github.com/getsops/sops/v3/keyservice"
3940
"github.com/getsops/sops/v3/kms"
4041
"github.com/getsops/sops/v3/logging"
42+
"github.com/getsops/sops/v3/ovhkms"
4143
"github.com/getsops/sops/v3/pgp"
4244
"github.com/getsops/sops/v3/stores/dotenv"
4345
"github.com/getsops/sops/v3/stores/json"
@@ -559,6 +561,7 @@ func main() {
559561
pgpFps := c.StringSlice("pgp")
560562
kmsArns := c.StringSlice("kms")
561563
gcpKmses := c.StringSlice("gcp-kms")
564+
ovhKmses := c.StringSlice("ovh-kms")
562565
vaultURIs := c.StringSlice("hc-vault-transit")
563566
azkvs := c.StringSlice("azure-kv")
564567
ageRecipients := c.StringSlice("age")
@@ -575,6 +578,14 @@ func main() {
575578
for _, kms := range gcpKmses {
576579
group = append(group, gcpkms.NewMasterKeyFromResourceID(kms))
577580
}
581+
for _, kms := range ovhKmses {
582+
k, err := ovhkms.NewMasterKeyFromKeyID(kms)
583+
if err != nil {
584+
log.WithError(err).Error("Failed to add key")
585+
continue
586+
}
587+
group = append(group, k)
588+
}
578589
for _, uri := range vaultURIs {
579590
k, err := hcvault.NewMasterKeyFromURI(uri)
580591
if err != nil {
@@ -1118,6 +1129,14 @@ func main() {
11181129
Name: "rm-pgp",
11191130
Usage: "remove the provided comma-separated list of PGP fingerprints from the list of master keys on the given file",
11201131
},
1132+
cli.StringFlag{
1133+
Name: "add-ovh-kms",
1134+
Usage: "add the provided comma-separated list of OVH KMS key resource IDs from the list of master keys on the given file",
1135+
},
1136+
cli.StringFlag{
1137+
Name: "rm-ovh-kms",
1138+
Usage: "remove the provided comma-separated list of OVH KMS key resource IDs from the list of master keys on the given file",
1139+
},
11211140
cli.StringFlag{
11221141
Name: "filename-override",
11231142
Usage: "Use this filename instead of the provided argument for loading configuration, and for determining input type and output type",
@@ -1144,8 +1163,8 @@ func main() {
11441163
return toExitError(err)
11451164
}
11461165
if _, err := os.Stat(fileName); os.IsNotExist(err) {
1147-
if c.String("add-kms") != "" || c.String("add-pgp") != "" || c.String("add-gcp-kms") != "" || c.String("add-hc-vault-transit") != "" || c.String("add-azure-kv") != "" || c.String("add-age") != "" ||
1148-
c.String("rm-kms") != "" || c.String("rm-pgp") != "" || c.String("rm-gcp-kms") != "" || c.String("rm-hc-vault-transit") != "" || c.String("rm-azure-kv") != "" || c.String("rm-age") != "" {
1166+
if c.String("add-kms") != "" || c.String("add-pgp") != "" || c.String("add-gcp-kms") != "" || c.String("add-hc-vault-transit") != "" || c.String("add-azure-kv") != "" || c.String("add-age") != "" || c.String("add-ovh-kms") != "" ||
1167+
c.String("rm-kms") != "" || c.String("rm-pgp") != "" || c.String("rm-gcp-kms") != "" || c.String("rm-hc-vault-transit") != "" || c.String("rm-azure-kv") != "" || c.String("rm-age") != "" || c.String("rm-ovh-kms") != "" {
11491168
return common.NewExitError(fmt.Sprintf("Error: cannot add or remove keys on non-existent file %q, use the `edit` subcommand instead.", fileName), codes.CannotChangeKeysFromNonExistentFile)
11501169
}
11511170
}
@@ -1620,6 +1639,11 @@ func main() {
16201639
Usage: "comma separated list of GCP KMS resource IDs",
16211640
EnvVar: "SOPS_GCP_KMS_IDS",
16221641
},
1642+
cli.StringFlag{
1643+
Name: "ovh-kms",
1644+
Usage: "comma separated list of OVH KMS Key IDs with endpoint (egs: eu-west-sbg.okms.ovh.net/12345678-1234-1234-1234-123456789012)",
1645+
EnvVar: "SOPS_OVH_KMS_IDS",
1646+
},
16231647
cli.StringFlag{
16241648
Name: "azure-kv",
16251649
Usage: "comma separated list of Azure Key Vault URLs",
@@ -1802,8 +1826,8 @@ func main() {
18021826
return toExitError(err)
18031827
}
18041828
if _, err := os.Stat(fileName); os.IsNotExist(err) {
1805-
if c.String("add-kms") != "" || c.String("add-pgp") != "" || c.String("add-gcp-kms") != "" || c.String("add-hc-vault-transit") != "" || c.String("add-azure-kv") != "" || c.String("add-age") != "" ||
1806-
c.String("rm-kms") != "" || c.String("rm-pgp") != "" || c.String("rm-gcp-kms") != "" || c.String("rm-hc-vault-transit") != "" || c.String("rm-azure-kv") != "" || c.String("rm-age") != "" {
1829+
if c.String("add-kms") != "" || c.String("add-pgp") != "" || c.String("add-gcp-kms") != "" || c.String("add-hc-vault-transit") != "" || c.String("add-azure-kv") != "" || c.String("add-age") != "" || c.String("add-ovh-kms") != "" ||
1830+
c.String("rm-kms") != "" || c.String("rm-pgp") != "" || c.String("rm-gcp-kms") != "" || c.String("rm-hc-vault-transit") != "" || c.String("rm-azure-kv") != "" || c.String("rm-age") != "" || c.String("rm-ovh-kms") != "" {
18071831
return common.NewExitError(fmt.Sprintf("Error: cannot add or remove keys on non-existent file %q, use `--kms` and `--pgp` instead.", fileName), codes.CannotChangeKeysFromNonExistentFile)
18081832
}
18091833
if isEncryptMode || isDecryptMode || isRotateMode {
@@ -2096,7 +2120,7 @@ func getEncryptConfig(c *cli.Context, fileName string) (encryptConfig, error) {
20962120
}, nil
20972121
}
20982122

2099-
func getMasterKeys(c *cli.Context, kmsEncryptionContext map[string]*string, kmsOptionName string, pgpOptionName string, gcpKmsOptionName string, azureKvOptionName string, hcVaultTransitOptionName string, ageOptionName string) ([]keys.MasterKey, error) {
2123+
func getMasterKeys(c *cli.Context, kmsEncryptionContext map[string]*string, kmsOptionName string, pgpOptionName string, gcpKmsOptionName string, azureKvOptionName string, hcVaultTransitOptionName string, ageOptionName string, ovhKmsOptionName string) ([]keys.MasterKey, error) {
21002124
var masterKeys []keys.MasterKey
21012125
for _, k := range kms.MasterKeysFromArnString(c.String(kmsOptionName), kmsEncryptionContext, c.String("aws-profile")) {
21022126
masterKeys = append(masterKeys, k)
@@ -2128,16 +2152,23 @@ func getMasterKeys(c *cli.Context, kmsEncryptionContext map[string]*string, kmsO
21282152
for _, k := range ageKeys {
21292153
masterKeys = append(masterKeys, k)
21302154
}
2155+
ovhKeys, err := ovhkms.MasterKeysFromResourceIDString(c.String(ovhKmsOptionName))
2156+
if err != nil {
2157+
return nil, err
2158+
}
2159+
for _, k := range ovhKeys {
2160+
masterKeys = append(masterKeys, k)
2161+
}
21312162
return masterKeys, nil
21322163
}
21332164

21342165
func getRotateOpts(c *cli.Context, fileName string, inputStore common.Store, outputStore common.Store, svcs []keyservice.KeyServiceClient, decryptionOrder []string) (rotateOpts, error) {
21352166
kmsEncryptionContext := kms.ParseKMSContext(c.String("encryption-context"))
2136-
addMasterKeys, err := getMasterKeys(c, kmsEncryptionContext, "add-kms", "add-pgp", "add-gcp-kms", "add-azure-kv", "add-hc-vault-transit", "add-age")
2167+
addMasterKeys, err := getMasterKeys(c, kmsEncryptionContext, "add-kms", "add-pgp", "add-gcp-kms", "add-azure-kv", "add-hc-vault-transit", "add-age", "add-ovh-kms")
21372168
if err != nil {
21382169
return rotateOpts{}, err
21392170
}
2140-
rmMasterKeys, err := getMasterKeys(c, kmsEncryptionContext, "rm-kms", "rm-pgp", "rm-gcp-kms", "rm-azure-kv", "rm-hc-vault-transit", "rm-age")
2171+
rmMasterKeys, err := getMasterKeys(c, kmsEncryptionContext, "rm-kms", "rm-pgp", "rm-gcp-kms", "rm-azure-kv", "rm-hc-vault-transit", "rm-age", "rm-ovh-kms")
21412172
if err != nil {
21422173
return rotateOpts{}, err
21432174
}
@@ -2282,6 +2313,7 @@ func keyGroups(c *cli.Context, file string) ([]sops.KeyGroup, error) {
22822313
var azkvKeys []keys.MasterKey
22832314
var hcVaultMkKeys []keys.MasterKey
22842315
var ageMasterKeys []keys.MasterKey
2316+
var ovhKmsKeys []keys.MasterKey
22852317
kmsEncryptionContext := kms.ParseKMSContext(c.String("encryption-context"))
22862318
if c.String("encryption-context") != "" && kmsEncryptionContext == nil {
22872319
return nil, common.NewExitError("Invalid KMS encryption context format", codes.ErrorInvalidKMSEncryptionContextFormat)
@@ -2328,6 +2360,15 @@ func keyGroups(c *cli.Context, file string) ([]sops.KeyGroup, error) {
23282360
ageMasterKeys = append(ageMasterKeys, k)
23292361
}
23302362
}
2363+
if c.String("ovh-kms") != "" {
2364+
ovhKeys, err := ovhkms.MasterKeysFromResourceIDString(c.String("ovh-kms"))
2365+
if err != nil {
2366+
return nil, err
2367+
}
2368+
for _, k := range ovhKeys {
2369+
pgpKeys = append(pgpKeys, k)
2370+
}
2371+
}
23312372
if c.String("kms") == "" && c.String("pgp") == "" && c.String("gcp-kms") == "" && c.String("azure-kv") == "" && c.String("hc-vault-transit") == "" && c.String("age") == "" {
23322373
conf, err := loadConfig(c, file, kmsEncryptionContext)
23332374
// config file might just not be supplied, without any error
@@ -2347,6 +2388,7 @@ func keyGroups(c *cli.Context, file string) ([]sops.KeyGroup, error) {
23472388
group = append(group, pgpKeys...)
23482389
group = append(group, hcVaultMkKeys...)
23492390
group = append(group, ageMasterKeys...)
2391+
group = append(group, ovhKmsKeys...)
23502392
log.Debugf("Master keys available: %+v", group)
23512393
return []sops.KeyGroup{group}, nil
23522394
}

config/config.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import (
1717
"github.com/getsops/sops/v3/gcpkms"
1818
"github.com/getsops/sops/v3/hcvault"
1919
"github.com/getsops/sops/v3/kms"
20+
"github.com/getsops/sops/v3/ovhkms"
2021
"github.com/getsops/sops/v3/pgp"
2122
"github.com/getsops/sops/v3/publish"
2223
"gopkg.in/yaml.v3"
@@ -133,6 +134,7 @@ type keyGroup struct {
133134
KMS []kmsKey
134135
GCPKMS []gcpKmsKey `yaml:"gcp_kms"`
135136
AzureKV []azureKVKey `yaml:"azure_keyvault"`
137+
OVHKMS []ovhKmsKey `yaml:"ovh_kms"`
136138
Vault []string `yaml:"hc_vault"`
137139
Age []string `yaml:"age"`
138140
PGP []string
@@ -142,6 +144,10 @@ type gcpKmsKey struct {
142144
ResourceID string `yaml:"resource_id"`
143145
}
144146

147+
type ovhKmsKey struct {
148+
KeyID string `yaml:"key_id"`
149+
}
150+
145151
type kmsKey struct {
146152
Arn string `yaml:"arn"`
147153
Role string `yaml:"role,omitempty"`
@@ -176,6 +182,7 @@ type creationRule struct {
176182
Age string `yaml:"age"`
177183
PGP string
178184
GCPKMS string `yaml:"gcp_kms"`
185+
OVHKMS string `yaml:"ovh_kms"`
179186
AzureKeyVault string `yaml:"azure_keyvault"`
180187
VaultURI string `yaml:"hc_vault_transit_uri"`
181188
KeyGroups []keyGroup `yaml:"key_groups"`
@@ -269,6 +276,13 @@ func extractMasterKeys(group keyGroup) (sops.KeyGroup, error) {
269276
for _, k := range group.AzureKV {
270277
keyGroup = append(keyGroup, azkv.NewMasterKey(k.VaultURL, k.Key, k.Version))
271278
}
279+
for _, k := range group.OVHKMS {
280+
if masterKey, err := ovhkms.NewMasterKeyFromKeyID(k.KeyID); err == nil {
281+
keyGroup = append(keyGroup, masterKey)
282+
} else {
283+
return nil, err
284+
}
285+
}
272286
for _, k := range group.Vault {
273287
if masterKey, err := hcvault.NewMasterKeyFromURI(k); err == nil {
274288
keyGroup = append(keyGroup, masterKey)
@@ -310,6 +324,13 @@ func getKeyGroupsFromCreationRule(cRule *creationRule, kmsEncryptionContext map[
310324
for _, k := range gcpkms.MasterKeysFromResourceIDString(cRule.GCPKMS) {
311325
keyGroup = append(keyGroup, k)
312326
}
327+
ovhKeys, err := ovhkms.MasterKeysFromResourceIDString(cRule.OVHKMS)
328+
if err != nil {
329+
return nil, err
330+
}
331+
for _, k := range ovhKeys {
332+
keyGroup = append(keyGroup, k)
333+
}
313334
azureKeys, err := azkv.MasterKeysFromURLs(cRule.AzureKeyVault)
314335
if err != nil {
315336
return nil, err

go.mod

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,15 @@ require (
2222
github.com/getsops/gopgagent v0.0.0-20241224165529-7044f28e491e
2323
github.com/google/go-cmp v0.7.0
2424
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510
25+
github.com/google/uuid v1.6.0
2526
github.com/goware/prefixer v0.0.0-20160118172347-395022866408
2627
github.com/hashicorp/go-cleanhttp v0.5.2
2728
github.com/hashicorp/vault/api v1.20.0
2829
github.com/lib/pq v1.10.9
2930
github.com/mitchellh/go-homedir v1.1.0
3031
github.com/mitchellh/go-wordwrap v1.0.1
3132
github.com/ory/dockertest/v3 v3.12.0
33+
github.com/ovh/okms-sdk-go v0.4.3
3234
github.com/pkg/errors v0.9.1
3335
github.com/sirupsen/logrus v1.9.3
3436
github.com/stretchr/testify v1.10.0
@@ -66,6 +68,7 @@ require (
6668
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.51.0 // indirect
6769
github.com/Microsoft/go-winio v0.6.2 // indirect
6870
github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 // indirect
71+
github.com/apapsch/go-jsonmerge/v2 v2.0.0 // indirect
6972
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.11 // indirect
7073
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.32 // indirect
7174
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.36 // indirect
@@ -100,12 +103,11 @@ require (
100103
github.com/gogo/protobuf v1.3.2 // indirect
101104
github.com/golang-jwt/jwt/v5 v5.2.2 // indirect
102105
github.com/google/s2a-go v0.1.9 // indirect
103-
github.com/google/uuid v1.6.0 // indirect
104106
github.com/googleapis/enterprise-certificate-proxy v0.3.6 // indirect
105107
github.com/googleapis/gax-go/v2 v2.14.2 // indirect
106108
github.com/hashicorp/errwrap v1.1.0 // indirect
107109
github.com/hashicorp/go-multierror v1.1.1 // indirect
108-
github.com/hashicorp/go-retryablehttp v0.7.7 // indirect
110+
github.com/hashicorp/go-retryablehttp v0.7.8 // indirect
109111
github.com/hashicorp/go-rootcerts v1.0.2 // indirect
110112
github.com/hashicorp/go-secure-stdlib/parseutil v0.2.0 // indirect
111113
github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 // indirect
@@ -118,6 +120,7 @@ require (
118120
github.com/moby/docker-image-spec v1.3.1 // indirect
119121
github.com/moby/sys/user v0.3.0 // indirect
120122
github.com/moby/term v0.5.2 // indirect
123+
github.com/oapi-codegen/runtime v1.1.1 // indirect
121124
github.com/opencontainers/go-digest v1.0.0 // indirect
122125
github.com/opencontainers/image-spec v1.1.1 // indirect
123126
github.com/opencontainers/runc v1.2.6 // indirect

0 commit comments

Comments
 (0)