github.com/caos/orbos@v1.5.14-0.20221103111702-e6cd0cea7ad4/pkg/secret/secret.go (about)

     1  package secret
     2  
     3  import (
     4  	"crypto/aes"
     5  	"crypto/cipher"
     6  	"crypto/rand"
     7  	"encoding/base64"
     8  	"errors"
     9  	"io"
    10  	"strings"
    11  	"unicode/utf8"
    12  
    13  	"github.com/caos/orbos/internal/utils/clientgo"
    14  	"gopkg.in/yaml.v3"
    15  )
    16  
    17  var Masterkey = "empty"
    18  
    19  // Secret: Secret handled with orbctl so no manual changes are required
    20  type Secret struct {
    21  	//Encryption algorithm used for the secret
    22  	Encryption string `json:"encryption,omitempty" yaml:"encryption,omitempty"`
    23  	//Encoding algorithm used for the secret
    24  	Encoding string `json:"encoding,omitempty" yaml:"encoding,omitempty"`
    25  	//Encrypted and encoded Value
    26  	Value string `json:"value,omitempty" yaml:"value,omitempty"`
    27  }
    28  type secretAlias Secret
    29  
    30  const existingSecretsNamespace = "caos-system"
    31  
    32  // Existing: Used secret that has to be already existing in the cluster
    33  type Existing struct {
    34  	//Name of the Secret
    35  	Name string `json:"name" yaml:"name"`
    36  	//Key in the secret from where the value should be used
    37  	Key string `json:"key" yaml:"key"`
    38  	//Name which should be used internally, should be unique for the volume and volumemounts
    39  	InternalName string `json:"internalName,omitempty" yaml:"internalName,omitempty"`
    40  }
    41  
    42  func (s *Existing) IsZero() bool {
    43  	if s.Name == "" && s.Key == "" {
    44  		return true
    45  	}
    46  	return false
    47  }
    48  
    49  // Existing: Used secret that has to be already existing in the cluster and should contain id/username and secret/password
    50  type ExistingIDSecret struct {
    51  	//Name of the Secret
    52  	Name string `json:"name" yaml:"name"`
    53  	//Key in the secret which contains the ID
    54  	IDKey string `json:"idKey" yaml:"idKey"`
    55  	//Key in the secret which contains the secret
    56  	SecretKey string `json:"secretKey" yaml:"secretKey"`
    57  	//Name which should be used internally, should be unique for the volume and volumemounts
    58  	InternalName string `json:"internalName,omitempty" yaml:"internalName,omitempty"`
    59  }
    60  
    61  func (s *Secret) IsZero() bool {
    62  	if s.Value == "" {
    63  		return true
    64  	}
    65  	return false
    66  }
    67  
    68  func (s *Secret) UnmarshalYAMLWithExisting(node *yaml.Node, existing *Existing) error {
    69  	if err := s.UnmarshalYAML(node); err != nil {
    70  		return err
    71  	}
    72  
    73  	if s.Value == "" {
    74  		if existing != nil && existing.Name != "" && existing.Key != "" {
    75  			secret, err := clientgo.GetSecret(existing.Name, "caos-system")
    76  			if err != nil {
    77  				return errors.New("Error while reading existing secret")
    78  			}
    79  
    80  			value, found := secret.Data[existing.Key]
    81  			if !found {
    82  				return errors.New("Error while reading existing secret, key non-existent")
    83  			}
    84  			s.Value = string(value)
    85  		}
    86  	}
    87  
    88  	return nil
    89  }
    90  
    91  func unmarshal(s *Secret) (string, error) {
    92  	if s.Value == "" {
    93  		return "", nil
    94  	}
    95  
    96  	cipherText, err := base64.URLEncoding.DecodeString(s.Value)
    97  	if err != nil {
    98  		return "", err
    99  	}
   100  
   101  	if len(Masterkey) < 1 || len(Masterkey) > 32 {
   102  		return "", nil
   103  		//return errors.New("Master key size must be between 1 and 32 characters")
   104  	}
   105  
   106  	masterKeyLocal := make([]byte, 32)
   107  	for idx, char := range []byte(strings.Trim(Masterkey, "\n")) {
   108  		masterKeyLocal[idx] = char
   109  	}
   110  
   111  	block, err := aes.NewCipher(masterKeyLocal)
   112  	if err != nil {
   113  		return "", err
   114  	}
   115  
   116  	if len(cipherText) < aes.BlockSize {
   117  		return "", errors.New("Ciphertext block size is too short")
   118  	}
   119  
   120  	//IV needs to be unique, but doesn't have to be secure.
   121  	//It's common to put it at the beginning of the ciphertext.
   122  	iv := cipherText[:aes.BlockSize]
   123  	cipherText = cipherText[aes.BlockSize:]
   124  
   125  	stream := cipher.NewCFBDecrypter(block, iv)
   126  	// XORKeyStream can work in-place if the two arguments are the same.
   127  	stream.XORKeyStream(cipherText, cipherText)
   128  
   129  	if !utf8.Valid(cipherText) {
   130  		return "", errors.New("Decryption failed")
   131  	}
   132  	//	s.monitor.Info("Decoded and decrypted secret")
   133  	return string(cipherText), nil
   134  }
   135  
   136  func (s *Secret) Unmarshal(masterkey string) error {
   137  	Masterkey = masterkey
   138  
   139  	unm, err := unmarshal(s)
   140  	if err != nil {
   141  		return err
   142  	}
   143  
   144  	s.Value = unm
   145  	return nil
   146  }
   147  
   148  func (s *Secret) UnmarshalYAML(node *yaml.Node) error {
   149  	alias := new(secretAlias)
   150  	err := node.Decode(alias)
   151  
   152  	s.Encoding = alias.Encoding
   153  	s.Encryption = alias.Encryption
   154  	s.Value = alias.Value
   155  
   156  	if alias.Value == "" {
   157  		return nil
   158  	}
   159  
   160  	if len(Masterkey) < 1 || len(Masterkey) > 32 {
   161  		return nil
   162  		//return errors.New("Master key size must be between 1 and 32 characters")
   163  	}
   164  
   165  	unmarshalled, err := unmarshal(s)
   166  	if err != nil {
   167  		return err
   168  	}
   169  
   170  	//	s.monitor.Info("Decoded and decrypted secret")
   171  	s.Encoding = alias.Encoding
   172  	s.Encryption = alias.Encryption
   173  	s.Value = unmarshalled
   174  	return nil
   175  }
   176  
   177  func (s *Secret) MarshalYAML() (interface{}, error) {
   178  
   179  	if s.Value == "" {
   180  		return nil, nil
   181  	}
   182  
   183  	if len(Masterkey) < 1 || len(Masterkey) > 32 {
   184  		return nil, errors.New("Master key size must be between 1 and 32 characters")
   185  	}
   186  
   187  	masterKey := make([]byte, 32)
   188  	for idx, char := range []byte(strings.Trim(Masterkey, "\n")) {
   189  		masterKey[idx] = char
   190  	}
   191  
   192  	c, err := aes.NewCipher(masterKey)
   193  	if err != nil {
   194  		return nil, err
   195  	}
   196  
   197  	cipherText := make([]byte, aes.BlockSize+len(s.Value))
   198  	iv := cipherText[:aes.BlockSize]
   199  	if _, err = io.ReadFull(rand.Reader, iv); err != nil {
   200  		return nil, err
   201  	}
   202  
   203  	stream := cipher.NewCFBEncrypter(c, iv)
   204  	stream.XORKeyStream(cipherText[aes.BlockSize:], []byte(s.Value))
   205  
   206  	return &secretAlias{Encryption: "AES256", Encoding: "Base64", Value: base64.URLEncoding.EncodeToString(cipherText)}, nil
   207  }
   208  
   209  func InitIfNil(sec *Secret) *Secret {
   210  	if sec == nil {
   211  		return &Secret{}
   212  	}
   213  	return sec
   214  }
   215  
   216  func AppendSecrets(prefix string, intoSecrets, addSecrets map[string]*Secret, intoExisting, addExisting map[string]*Existing) {
   217  	for key, secret := range addSecrets {
   218  		name := key
   219  		if prefix != "" {
   220  			name = prefix + "." + name
   221  		}
   222  		intoSecrets[name] = secret
   223  	}
   224  	for key, existing := range addExisting {
   225  		name := key
   226  		if prefix != "" {
   227  			name = prefix + "." + name
   228  		}
   229  		intoExisting[name] = existing
   230  	}
   231  }
   232  
   233  func ValidateSecret(secret *Secret, existing *Existing) error {
   234  	if secret == nil || existing == nil {
   235  		return errors.New("secret not specified")
   236  	}
   237  
   238  	if secret.Value == "" && (existing.Name == "" || existing.Key == "") {
   239  		return errors.New("secret has no encrypted value or no valid reference")
   240  	}
   241  	return nil
   242  }