Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 44 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -515,6 +515,50 @@ To easily deploy Vault locally: (DO NOT DO THIS FOR PRODUCTION!!!)

$ sops encrypt --verbose prod/raw.yaml > prod/encrypted.yaml

Encrypting using OVH Key Management Service
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

To use OVH KMS with SOPS, you need to:

1. Have an OVH account with KMS service enabled
2. Create a key in OVH KMS that will be used for encryption/decryption
3. Obtain an access certificate and private key for authentication

You can use OVH KMS in your `.sops.yaml` file like this:

.. code:: yaml

creation_rules:
- path_regex: path/to/files/*.yaml
ovh_kms: <kms-rest-endpoint>/<your-key-id>

Or with key groups:

.. code:: yaml

creation_rules:
- path_regex: path/to/files/*.yaml
key_groups:
- ovh_kms:
- key_id: <kms-rest-endpoint>/<your-key-id>

Usage

After configuration, you can use SOPS normally and it will automatically use OVH KMS for encryption/decryption:

.. code:: sh

# Set required environment variables
export OVH_CERTIFICATE_PATH=/path/to/certificate.pem
export OVH_CERTIFICATE_KEY_PATH=/path/to/private-key.pem

# Encrypt a file
sops -e -i secrets.yaml

# Decrypt a file
sops -d secrets.yaml


Adding and removing keys
~~~~~~~~~~~~~~~~~~~~~~~~

Expand Down
52 changes: 45 additions & 7 deletions cmd/sops/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import (
"github.com/getsops/sops/v3/keyservice"
"github.com/getsops/sops/v3/kms"
"github.com/getsops/sops/v3/logging"
"github.com/getsops/sops/v3/ovhkms"
"github.com/getsops/sops/v3/pgp"
"github.com/getsops/sops/v3/stores/dotenv"
"github.com/getsops/sops/v3/stores/json"
Expand Down Expand Up @@ -584,6 +585,7 @@ func main() {
vaultURIs := c.StringSlice("hc-vault-transit")
azkvs := c.StringSlice("azure-kv")
ageRecipients := c.StringSlice("age")
ovhKmses := c.StringSlice("ovh-kms")
if c.NArg() != 0 {
return common.NewExitError(fmt.Errorf("error: no positional arguments allowed"), codes.ErrorGeneric)
}
Expand All @@ -597,6 +599,14 @@ func main() {
for _, kms := range gcpKmses {
group = append(group, gcpkms.NewMasterKeyFromResourceID(kms))
}
for _, kms := range ovhKmses {
k, err := ovhkms.NewMasterKeyFromKeyID(kms)
if err != nil {
log.WithError(err).Error("Failed to add key")
continue
}
group = append(group, k)
}
for _, uri := range vaultURIs {
k, err := hcvault.NewMasterKeyFromURI(uri)
if err != nil {
Expand Down Expand Up @@ -915,6 +925,11 @@ func main() {
Usage: "comma separated list of GCP KMS resource IDs",
EnvVar: "SOPS_GCP_KMS_IDS",
},
cli.StringFlag{
Name: "ovh-kms",
Usage: "comma separated list of OVH KMS key IDs",
EnvVar: "SOPS_OVH_KMS_IDS",
},
cli.StringFlag{
Name: "azure-kv",
Usage: "comma separated list of Azure Key Vault URLs",
Expand Down Expand Up @@ -1166,8 +1181,8 @@ func main() {
return toExitError(err)
}
if _, err := os.Stat(fileName); os.IsNotExist(err) {
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("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") != "" {
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") != "" ||
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") != "" {
return common.NewExitError(fmt.Sprintf("Error: cannot add or remove keys on non-existent file %q, use the `edit` subcommand instead.", fileName), codes.CannotChangeKeysFromNonExistentFile)
}
}
Expand Down Expand Up @@ -1661,6 +1676,11 @@ func main() {
Usage: "comma separated list of GCP KMS resource IDs",
EnvVar: "SOPS_GCP_KMS_IDS",
},
cli.StringFlag{
Name: "ovh-kms",
Usage: "comma separated list of OVH KMS Key IDs with endpoint (egs: eu-west-sbg.okms.ovh.net/12345678-1234-1234-1234-123456789012)",
EnvVar: "SOPS_OVH_KMS_IDS",
},
cli.StringFlag{
Name: "azure-kv",
Usage: "comma separated list of Azure Key Vault URLs",
Expand Down Expand Up @@ -1843,8 +1863,8 @@ func main() {
return toExitError(err)
}
if _, err := os.Stat(fileName); os.IsNotExist(err) {
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("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") != "" {
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") != "" ||
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") != "" {
return common.NewExitError(fmt.Sprintf("Error: cannot add or remove keys on non-existent file %q, use `--kms` and `--pgp` instead.", fileName), codes.CannotChangeKeysFromNonExistentFile)
}
if isEncryptMode || isDecryptMode || isRotateMode {
Expand Down Expand Up @@ -2137,7 +2157,7 @@ func getEncryptConfig(c *cli.Context, fileName string) (encryptConfig, error) {
}, nil
}

func getMasterKeys(c *cli.Context, kmsEncryptionContext map[string]*string, kmsOptionName string, pgpOptionName string, gcpKmsOptionName string, azureKvOptionName string, hcVaultTransitOptionName string, ageOptionName string) ([]keys.MasterKey, error) {
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) {
var masterKeys []keys.MasterKey
for _, k := range kms.MasterKeysFromArnString(c.String(kmsOptionName), kmsEncryptionContext, c.String("aws-profile")) {
masterKeys = append(masterKeys, k)
Expand Down Expand Up @@ -2169,16 +2189,23 @@ func getMasterKeys(c *cli.Context, kmsEncryptionContext map[string]*string, kmsO
for _, k := range ageKeys {
masterKeys = append(masterKeys, k)
}
ovhKeys, err := ovhkms.MasterKeysFromResourceIDString(c.String(ovhKmsOptionName))
if err != nil {
return nil, err
}
for _, k := range ovhKeys {
masterKeys = append(masterKeys, k)
}
return masterKeys, nil
}

func getRotateOpts(c *cli.Context, fileName string, inputStore common.Store, outputStore common.Store, svcs []keyservice.KeyServiceClient, decryptionOrder []string) (rotateOpts, error) {
kmsEncryptionContext := kms.ParseKMSContext(c.String("encryption-context"))
addMasterKeys, err := getMasterKeys(c, kmsEncryptionContext, "add-kms", "add-pgp", "add-gcp-kms", "add-azure-kv", "add-hc-vault-transit", "add-age")
addMasterKeys, err := getMasterKeys(c, kmsEncryptionContext, "add-kms", "add-pgp", "add-gcp-kms", "add-azure-kv", "add-hc-vault-transit", "add-age", "add-ovh-kms")
if err != nil {
return rotateOpts{}, err
}
rmMasterKeys, err := getMasterKeys(c, kmsEncryptionContext, "rm-kms", "rm-pgp", "rm-gcp-kms", "rm-azure-kv", "rm-hc-vault-transit", "rm-age")
rmMasterKeys, err := getMasterKeys(c, kmsEncryptionContext, "rm-kms", "rm-pgp", "rm-gcp-kms", "rm-azure-kv", "rm-hc-vault-transit", "rm-age", "rm-ovh-kms")
if err != nil {
return rotateOpts{}, err
}
Expand Down Expand Up @@ -2323,6 +2350,7 @@ func keyGroups(c *cli.Context, file string) ([]sops.KeyGroup, error) {
var azkvKeys []keys.MasterKey
var hcVaultMkKeys []keys.MasterKey
var ageMasterKeys []keys.MasterKey
var ovhKmsKeys []keys.MasterKey
kmsEncryptionContext := kms.ParseKMSContext(c.String("encryption-context"))
if c.String("encryption-context") != "" && kmsEncryptionContext == nil {
return nil, common.NewExitError("Invalid KMS encryption context format", codes.ErrorInvalidKMSEncryptionContextFormat)
Expand Down Expand Up @@ -2369,6 +2397,15 @@ func keyGroups(c *cli.Context, file string) ([]sops.KeyGroup, error) {
ageMasterKeys = append(ageMasterKeys, k)
}
}
if c.String("ovh-kms") != "" {
ovhKeys, err := ovhkms.MasterKeysFromResourceIDString(c.String("ovh-kms"))
if err != nil {
return nil, err
}
for _, k := range ovhKeys {
pgpKeys = append(pgpKeys, k)
}
}
if c.String("kms") == "" && c.String("pgp") == "" && c.String("gcp-kms") == "" && c.String("azure-kv") == "" && c.String("hc-vault-transit") == "" && c.String("age") == "" {
conf, err := loadConfig(c, file, kmsEncryptionContext)
// config file might just not be supplied, without any error
Expand All @@ -2388,6 +2425,7 @@ func keyGroups(c *cli.Context, file string) ([]sops.KeyGroup, error) {
group = append(group, pgpKeys...)
group = append(group, hcVaultMkKeys...)
group = append(group, ageMasterKeys...)
group = append(group, ovhKmsKeys...)
log.Debugf("Master keys available: %+v", group)
return []sops.KeyGroup{group}, nil
}
Expand Down
21 changes: 21 additions & 0 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import (
"github.com/getsops/sops/v3/gcpkms"
"github.com/getsops/sops/v3/hcvault"
"github.com/getsops/sops/v3/kms"
"github.com/getsops/sops/v3/ovhkms"
"github.com/getsops/sops/v3/pgp"
"github.com/getsops/sops/v3/publish"
"gopkg.in/yaml.v3"
Expand Down Expand Up @@ -133,6 +134,7 @@ type keyGroup struct {
KMS []kmsKey
GCPKMS []gcpKmsKey `yaml:"gcp_kms"`
AzureKV []azureKVKey `yaml:"azure_keyvault"`
OVHKMS []ovhKmsKey `yaml:"ovh_kms"`
Vault []string `yaml:"hc_vault"`
Age []string `yaml:"age"`
PGP []string
Expand All @@ -142,6 +144,10 @@ type gcpKmsKey struct {
ResourceID string `yaml:"resource_id"`
}

type ovhKmsKey struct {
KeyID string `yaml:"key_id"`
}

type kmsKey struct {
Arn string `yaml:"arn"`
Role string `yaml:"role,omitempty"`
Expand Down Expand Up @@ -178,6 +184,7 @@ type creationRule struct {
GCPKMS interface{} `yaml:"gcp_kms"` // string or []string
AzureKeyVault interface{} `yaml:"azure_keyvault"` // string or []string
VaultURI interface{} `yaml:"hc_vault_transit_uri"` // string or []string
OVHKMS string `yaml:"ovh_kms"`
KeyGroups []keyGroup `yaml:"key_groups"`
ShamirThreshold int `yaml:"shamir_threshold"`
UnencryptedSuffix string `yaml:"unencrypted_suffix"`
Expand Down Expand Up @@ -332,6 +339,13 @@ func extractMasterKeys(group keyGroup) (sops.KeyGroup, error) {
for _, k := range group.AzureKV {
keyGroup = append(keyGroup, azkv.NewMasterKey(k.VaultURL, k.Key, k.Version))
}
for _, k := range group.OVHKMS {
if masterKey, err := ovhkms.NewMasterKeyFromKeyID(k.KeyID); err == nil {
keyGroup = append(keyGroup, masterKey)
} else {
return nil, err
}
}
for _, k := range group.Vault {
if masterKey, err := hcvault.NewMasterKeyFromURI(k); err == nil {
keyGroup = append(keyGroup, masterKey)
Expand Down Expand Up @@ -420,6 +434,13 @@ func getKeyGroupsFromCreationRule(cRule *creationRule, kmsEncryptionContext map[
for _, k := range vaultKeys {
keyGroup = append(keyGroup, k)
}
ovhKeys, err := ovhkms.MasterKeysFromResourceIDString(cRule.OVHKMS)
if err != nil {
return nil, err
}
for _, k := range ovhKeys {
keyGroup = append(keyGroup, k)
}
groups = append(groups, keyGroup)
}
return groups, nil
Expand Down
7 changes: 5 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,15 @@ require (
github.com/getsops/gopgagent v0.0.0-20241224165529-7044f28e491e
github.com/google/go-cmp v0.7.0
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510
github.com/google/uuid v1.6.0
github.com/goware/prefixer v0.0.0-20160118172347-395022866408
github.com/hashicorp/go-cleanhttp v0.5.2
github.com/hashicorp/vault/api v1.20.0
github.com/lib/pq v1.10.9
github.com/mitchellh/go-homedir v1.1.0
github.com/mitchellh/go-wordwrap v1.0.1
github.com/ory/dockertest/v3 v3.12.0
github.com/ovh/okms-sdk-go v0.4.3
github.com/pkg/errors v0.9.1
github.com/sirupsen/logrus v1.9.3
github.com/stretchr/testify v1.10.0
Expand Down Expand Up @@ -66,6 +68,7 @@ require (
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.53.0 // indirect
github.com/Microsoft/go-winio v0.6.2 // indirect
github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 // indirect
github.com/apapsch/go-jsonmerge/v2 v2.0.0 // indirect
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.0 // indirect
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.2 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.2 // indirect
Expand Down Expand Up @@ -100,12 +103,11 @@ require (
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang-jwt/jwt/v5 v5.3.0 // indirect
github.com/google/s2a-go v0.1.9 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.3.6 // indirect
github.com/googleapis/gax-go/v2 v2.15.0 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/go-multierror v1.1.1 // indirect
github.com/hashicorp/go-retryablehttp v0.7.7 // indirect
github.com/hashicorp/go-retryablehttp v0.7.8 // indirect
github.com/hashicorp/go-rootcerts v1.0.2 // indirect
github.com/hashicorp/go-secure-stdlib/parseutil v0.2.0 // indirect
github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 // indirect
Expand All @@ -118,6 +120,7 @@ require (
github.com/moby/docker-image-spec v1.3.1 // indirect
github.com/moby/sys/user v0.3.0 // indirect
github.com/moby/term v0.5.2 // indirect
github.com/oapi-codegen/runtime v1.1.1 // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/opencontainers/image-spec v1.1.1 // indirect
github.com/opencontainers/runc v1.2.6 // indirect
Expand Down
Loading
Loading