2017-09-12 20:01:12 -07:00
/ *
Package config provides a way to find and load SOPS configuration files
* /
2019-11-18 10:06:58 -08:00
package config //import "go.mozilla.org/sops/v3/config"
2016-08-25 17:51:55 -07:00
import (
"fmt"
"io/ioutil"
"os"
"path"
"regexp"
2016-10-22 08:19:34 -07:00
2016-10-27 19:13:01 +02:00
"github.com/mozilla-services/yaml"
2018-03-27 17:36:45 -04:00
"github.com/sirupsen/logrus"
2019-11-18 10:06:58 -08:00
"go.mozilla.org/sops/v3"
"go.mozilla.org/sops/v3/azkv"
"go.mozilla.org/sops/v3/gcpkms"
2020-05-05 00:57:51 +05:30
"go.mozilla.org/sops/v3/hcvault"
2019-11-18 10:06:58 -08:00
"go.mozilla.org/sops/v3/kms"
"go.mozilla.org/sops/v3/logging"
"go.mozilla.org/sops/v3/pgp"
"go.mozilla.org/sops/v3/publish"
2016-08-25 17:51:55 -07:00
)
2018-03-27 17:36:45 -04:00
var log * logrus . Logger
func init ( ) {
log = logging . NewLogger ( "CONFIG" )
}
2016-08-25 17:51:55 -07:00
type fileSystem interface {
Stat ( name string ) ( os . FileInfo , error )
}
type osFS struct {
stat func ( string ) ( os . FileInfo , error )
}
func ( fs osFS ) Stat ( name string ) ( os . FileInfo , error ) {
return fs . stat ( name )
}
var fs fileSystem = osFS { stat : os . Stat }
const (
maxDepth = 100
configFileName = ".sops.yaml"
)
// FindConfigFile looks for a sops config file in the current working directory and on parent directories, up to the limit defined by the maxDepth constant.
func FindConfigFile ( start string ) ( string , error ) {
filepath := path . Dir ( start )
for i := 0 ; i < maxDepth ; i ++ {
_ , err := fs . Stat ( path . Join ( filepath , configFileName ) )
if err != nil {
filepath = path . Join ( filepath , ".." )
} else {
return path . Join ( filepath , configFileName ) , nil
}
}
return "" , fmt . Errorf ( "Config file not found" )
}
type configFile struct {
2019-06-27 16:48:54 +00:00
CreationRules [ ] creationRule ` yaml:"creation_rules" `
DestinationRules [ ] destinationRule ` yaml:"destination_rules" `
2016-08-25 17:51:55 -07:00
}
2017-08-24 17:24:04 -07:00
type keyGroup struct {
2018-06-17 22:50:30 +02:00
KMS [ ] kmsKey
GCPKMS [ ] gcpKmsKey ` yaml:"gcp_kms" `
AzureKV [ ] azureKVKey ` yaml:"azure_keyvault" `
2020-05-05 00:57:51 +05:30
Vault [ ] string ` yaml:"hc_vault" `
2018-06-17 22:50:30 +02:00
PGP [ ] string
2017-09-15 14:27:04 -07:00
}
type gcpKmsKey struct {
ResourceID string ` yaml:"resource_id" `
2017-09-12 11:38:03 -07:00
}
type kmsKey struct {
2019-01-25 12:42:41 +00:00
Arn string ` yaml:"arn" `
Role string ` yaml:"role,omitempty" `
Context map [ string ] * string ` yaml:"context" `
AwsProfile string ` yaml:"aws_profile" `
2017-08-24 17:24:04 -07:00
}
2018-06-17 22:50:30 +02:00
type azureKVKey struct {
VaultURL string ` yaml:"vaultUrl" `
Key string ` yaml:"key" `
Version string ` yaml:"version" `
}
2019-06-27 16:48:54 +00:00
type destinationRule struct {
2019-08-30 13:44:04 -07:00
PathRegex string ` yaml:"path_regex" `
S3Bucket string ` yaml:"s3_bucket" `
S3Prefix string ` yaml:"s3_prefix" `
GCSBucket string ` yaml:"gcs_bucket" `
GCSPrefix string ` yaml:"gcs_prefix" `
VaultPath string ` yaml:"vault_path" `
VaultAddress string ` yaml:"vault_address" `
VaultKVMountName string ` yaml:"vault_kv_mount_name" `
VaultKVVersion int ` yaml:"vault_kv_version" `
RecreationRule creationRule ` yaml:"recreation_rule,omitempty" `
2020-01-09 09:18:51 +06:00
OmitExtensions bool ` yaml:"omit_extensions" `
2019-06-27 16:48:54 +00:00
}
2016-08-25 17:51:55 -07:00
type creationRule struct {
2018-04-09 11:50:47 +03:00
PathRegex string ` yaml:"path_regex" `
KMS string
2019-01-25 12:42:41 +00:00
AwsProfile string ` yaml:"aws_profile" `
2018-04-09 11:50:47 +03:00
PGP string
GCPKMS string ` yaml:"gcp_kms" `
2018-06-17 22:50:30 +02:00
AzureKeyVault string ` yaml:"azure_keyvault" `
2020-05-05 00:57:51 +05:30
VaultURI string ` yaml:"hc_vault_transit_uri" `
2018-04-09 11:50:47 +03:00
KeyGroups [ ] keyGroup ` yaml:"key_groups" `
ShamirThreshold int ` yaml:"shamir_threshold" `
UnencryptedSuffix string ` yaml:"unencrypted_suffix" `
EncryptedSuffix string ` yaml:"encrypted_suffix" `
2020-09-02 13:15:50 -04:00
UnencryptedRegex string ` yaml:"unencrypted_regex" `
2019-08-14 15:39:21 -04:00
EncryptedRegex string ` yaml:"encrypted_regex" `
2016-08-25 17:51:55 -07:00
}
// Load loads a sops config file into a temporary struct
func ( f * configFile ) load ( bytes [ ] byte ) error {
err := yaml . Unmarshal ( bytes , f )
if err != nil {
return fmt . Errorf ( "Could not unmarshal config file: %s" , err )
}
return nil
}
2017-09-12 13:53:21 -07:00
// Config is the configuration for a given SOPS file
type Config struct {
2018-04-09 11:50:47 +03:00
KeyGroups [ ] sops . KeyGroup
ShamirThreshold int
UnencryptedSuffix string
EncryptedSuffix string
2020-09-02 13:15:50 -04:00
UnencryptedRegex string
2019-08-14 15:39:21 -04:00
EncryptedRegex string
2019-06-27 16:48:54 +00:00
Destination publish . Destination
2020-01-09 09:18:51 +06:00
OmitExtensions bool
2017-09-12 13:53:21 -07:00
}
2019-06-27 16:48:54 +00:00
func getKeyGroupsFromCreationRule ( cRule * creationRule , kmsEncryptionContext map [ string ] * string ) ( [ ] sops . KeyGroup , error ) {
2017-08-24 17:24:04 -07:00
var groups [ ] sops . KeyGroup
2019-06-27 16:48:54 +00:00
if len ( cRule . KeyGroups ) > 0 {
for _ , group := range cRule . KeyGroups {
2017-09-12 13:53:21 -07:00
var keyGroup sops . KeyGroup
for _ , k := range group . PGP {
keyGroup = append ( keyGroup , pgp . NewMasterKeyFromFingerprint ( k ) )
}
for _ , k := range group . KMS {
keyGroup = append ( keyGroup , kms . NewMasterKey ( k . Arn , k . Role , k . Context ) )
2017-08-24 17:24:04 -07:00
}
2017-09-15 14:27:04 -07:00
for _ , k := range group . GCPKMS {
2017-09-15 14:40:57 -07:00
keyGroup = append ( keyGroup , gcpkms . NewMasterKeyFromResourceID ( k . ResourceID ) )
2017-09-15 14:27:04 -07:00
}
2019-08-02 15:22:17 -04:00
for _ , k := range group . AzureKV {
keyGroup = append ( keyGroup , azkv . NewMasterKey ( k . VaultURL , k . Key , k . Version ) )
}
2020-05-05 00:57:51 +05:30
for _ , k := range group . Vault {
if masterKey , err := hcvault . NewMasterKeyFromURI ( k ) ; err == nil {
keyGroup = append ( keyGroup , masterKey )
} else {
return nil , err
}
}
2017-09-12 13:53:21 -07:00
groups = append ( groups , keyGroup )
}
} else {
var keyGroup sops . KeyGroup
2019-06-27 16:48:54 +00:00
for _ , k := range pgp . MasterKeysFromFingerprintString ( cRule . PGP ) {
2017-09-12 13:53:21 -07:00
keyGroup = append ( keyGroup , k )
}
2019-06-27 16:48:54 +00:00
for _ , k := range kms . MasterKeysFromArnString ( cRule . KMS , kmsEncryptionContext , cRule . AwsProfile ) {
2017-09-12 13:53:21 -07:00
keyGroup = append ( keyGroup , k )
2016-08-25 17:51:55 -07:00
}
2019-06-27 16:48:54 +00:00
for _ , k := range gcpkms . MasterKeysFromResourceIDString ( cRule . GCPKMS ) {
2017-09-15 14:27:04 -07:00
keyGroup = append ( keyGroup , k )
}
2019-06-27 16:48:54 +00:00
azureKeys , err := azkv . MasterKeysFromURLs ( cRule . AzureKeyVault )
2018-06-18 16:03:24 +02:00
if err != nil {
return nil , err
}
for _ , k := range azureKeys {
2018-06-17 22:50:30 +02:00
keyGroup = append ( keyGroup , k )
}
2020-05-05 00:57:51 +05:30
vaultKeys , err := hcvault . NewMasterKeysFromURIs ( cRule . VaultURI )
if err != nil {
return nil , err
}
for _ , k := range vaultKeys {
keyGroup = append ( keyGroup , k )
}
2017-09-12 13:53:21 -07:00
groups = append ( groups , keyGroup )
}
2019-06-27 16:48:54 +00:00
return groups , nil
}
func loadConfigFile ( confPath string ) ( * configFile , error ) {
confBytes , err := ioutil . ReadFile ( confPath )
if err != nil {
return nil , fmt . Errorf ( "could not read config file: %s" , err )
}
conf := & configFile { }
err = conf . load ( confBytes )
if err != nil {
return nil , fmt . Errorf ( "error loading config: %s" , err )
}
return conf , nil
}
func configFromRule ( rule * creationRule , kmsEncryptionContext map [ string ] * string ) ( * Config , error ) {
2019-08-14 15:39:21 -04:00
cryptRuleCount := 0
if rule . UnencryptedSuffix != "" {
cryptRuleCount ++
}
if rule . EncryptedSuffix != "" {
cryptRuleCount ++
}
if rule . EncryptedRegex != "" {
cryptRuleCount ++
}
if cryptRuleCount > 1 {
return nil , fmt . Errorf ( "error loading config: cannot use more than one of encrypted_suffix, unencrypted_suffix, or encrypted_regex for the same rule" )
2019-06-27 16:48:54 +00:00
}
groups , err := getKeyGroupsFromCreationRule ( rule , kmsEncryptionContext )
if err != nil {
return nil , err
}
2017-09-12 13:53:21 -07:00
return & Config {
2018-04-09 11:50:47 +03:00
KeyGroups : groups ,
ShamirThreshold : rule . ShamirThreshold ,
UnencryptedSuffix : rule . UnencryptedSuffix ,
EncryptedSuffix : rule . EncryptedSuffix ,
2020-09-02 13:15:50 -04:00
UnencryptedRegex : rule . UnencryptedRegex ,
2019-08-14 15:39:21 -04:00
EncryptedRegex : rule . EncryptedRegex ,
2017-09-12 13:53:21 -07:00
} , nil
}
2019-06-27 16:48:54 +00:00
func parseDestinationRuleForFile ( conf * configFile , filePath string , kmsEncryptionContext map [ string ] * string ) ( * Config , error ) {
var rule * creationRule
var dRule * destinationRule
if len ( conf . DestinationRules ) > 0 {
for _ , r := range conf . DestinationRules {
if r . PathRegex == "" {
dRule = & r
rule = & dRule . RecreationRule
break
}
if r . PathRegex != "" {
if match , _ := regexp . MatchString ( r . PathRegex , filePath ) ; match {
dRule = & r
rule = & dRule . RecreationRule
break
}
}
}
}
if dRule == nil {
return nil , fmt . Errorf ( "error loading config: no matching destination found in config" )
}
var dest publish . Destination
if dRule != nil {
2019-07-16 14:33:59 -07:00
if dRule . S3Bucket != "" && dRule . GCSBucket != "" && dRule . VaultPath != "" {
2020-05-05 00:57:51 +05:30
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" )
2019-06-27 16:48:54 +00:00
}
if dRule . S3Bucket != "" {
dest = publish . NewS3Destination ( dRule . S3Bucket , dRule . S3Prefix )
}
if dRule . GCSBucket != "" {
dest = publish . NewGCSDestination ( dRule . GCSBucket , dRule . GCSPrefix )
}
2019-07-16 14:33:59 -07:00
if dRule . VaultPath != "" {
2019-08-30 13:44:04 -07:00
dest = publish . NewVaultDestination ( dRule . VaultAddress , dRule . VaultPath , dRule . VaultKVMountName , dRule . VaultKVVersion )
2019-07-16 14:33:59 -07:00
}
2019-06-27 16:48:54 +00:00
}
config , err := configFromRule ( rule , kmsEncryptionContext )
if err != nil {
return nil , err
}
config . Destination = dest
2020-01-09 09:18:51 +06:00
config . OmitExtensions = dRule . OmitExtensions
2019-06-27 16:48:54 +00:00
return config , nil
}
func parseCreationRuleForFile ( conf * configFile , filePath string , kmsEncryptionContext map [ string ] * string ) ( * Config , error ) {
2020-04-24 23:54:06 +02:00
// If config file doesn't contain CreationRules (it's empty or only contains DestionationRules), assume it does not exist
if conf . CreationRules == nil {
return nil , nil
}
2019-06-27 16:48:54 +00:00
var rule * creationRule
for _ , r := range conf . CreationRules {
if r . PathRegex == "" {
rule = & r
break
}
if r . PathRegex != "" {
if match , _ := regexp . MatchString ( r . PathRegex , filePath ) ; match {
rule = & r
break
}
}
}
if rule == nil {
return nil , fmt . Errorf ( "error loading config: no matching creation rules found" )
}
config , err := configFromRule ( rule , kmsEncryptionContext )
if err != nil {
return nil , err
}
return config , nil
}
2020-05-04 22:58:45 +02:00
// LoadCreationRuleForFile load the configuration for a given SOPS file from the config file at confPath. A kmsEncryptionContext
2017-09-12 13:53:21 -07:00
// should be provided for configurations that do not contain key groups, as there's no way to specify context inside
// a SOPS config file outside of key groups.
2020-05-04 22:58:45 +02:00
func LoadCreationRuleForFile ( confPath string , filePath string , kmsEncryptionContext map [ string ] * string ) ( * Config , error ) {
2019-06-27 16:48:54 +00:00
conf , err := loadConfigFile ( confPath )
2017-09-12 13:53:21 -07:00
if err != nil {
2019-06-27 16:48:54 +00:00
return nil , err
}
return parseCreationRuleForFile ( conf , filePath , kmsEncryptionContext )
}
2020-05-04 22:58:45 +02:00
// LoadDestinationRuleForFile works the same as LoadCreationRuleForFile, but gets the "creation_rule" from the matching destination_rule's
2019-06-27 16:48:54 +00:00
// "recreation_rule".
func LoadDestinationRuleForFile ( confPath string , filePath string , kmsEncryptionContext map [ string ] * string ) ( * Config , error ) {
conf , err := loadConfigFile ( confPath )
if err != nil {
return nil , err
2016-08-25 17:51:55 -07:00
}
2019-06-27 16:48:54 +00:00
return parseDestinationRuleForFile ( conf , filePath , kmsEncryptionContext )
2016-08-25 17:51:55 -07:00
}