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

     1  package secret
     2  
     3  import (
     4  	"fmt"
     5  	"sort"
     6  	"strings"
     7  
     8  	"github.com/caos/orbos/pkg/helper"
     9  
    10  	"github.com/caos/orbos/pkg/git"
    11  
    12  	v1 "k8s.io/api/core/v1"
    13  	mach "k8s.io/apimachinery/pkg/apis/meta/v1"
    14  
    15  	macherrs "k8s.io/apimachinery/pkg/api/errors"
    16  
    17  	"github.com/caos/orbos/pkg/kubernetes"
    18  
    19  	"github.com/AlecAivazis/survey/v2"
    20  
    21  	"github.com/caos/orbos/pkg/tree"
    22  
    23  	"github.com/caos/orbos/mntr"
    24  )
    25  
    26  type PushFuncs func(trees map[string]*tree.Tree, path string) error
    27  type GetFuncs func() (map[string]*Secret, map[string]*Existing, map[string]*tree.Tree, error)
    28  
    29  func Read(
    30  	k8sClient kubernetes.ClientInt,
    31  	path string,
    32  	getFunc GetFuncs,
    33  ) (
    34  	val string,
    35  	err error,
    36  ) {
    37  
    38  	defer func() {
    39  		if err != nil {
    40  			err = fmt.Errorf("reading secret failed: %w", err)
    41  		}
    42  	}()
    43  
    44  	allSecrets, allExisting, _, err := getFunc()
    45  	if err != nil {
    46  		return "", err
    47  	}
    48  	if helper.IsNil(k8sClient) {
    49  		allExisting = make(map[string]*Existing)
    50  	}
    51  
    52  	/*
    53  		if allSecrets == nil || len(allSecrets) == 0 {
    54  			return "", errors.New("no secrets found")
    55  		}
    56  	*/
    57  
    58  	secret, err := findSecret(allSecrets, allExisting, &path, false)
    59  	if err != nil {
    60  		return "", err
    61  	}
    62  
    63  	switch secretType := secret.(type) {
    64  	case *Secret:
    65  		if secretType.Value == "" {
    66  			return "", fmt.Errorf("secret %s is empty", path)
    67  		}
    68  		return secretType.Value, nil
    69  	case *Existing:
    70  		if secretType.Name == "" {
    71  			return "", fmt.Errorf("secret %s has no name specified", path)
    72  		}
    73  		if secretType.Key == "" {
    74  			return "", fmt.Errorf("secret %s has no key specified", path)
    75  		}
    76  		k8sSecret, err := k8sClient.GetSecret(existingSecretsNamespace, secretType.Name)
    77  		if err != nil {
    78  			return "", err
    79  		}
    80  		bytes, ok := k8sSecret.Data[secretType.Key]
    81  		if !ok || len(bytes) == 0 {
    82  			return "", fmt.Errorf("Kubernetes secret is empty at key %s", secretType.Key)
    83  		}
    84  		return string(bytes), nil
    85  	}
    86  	panic(fmt.Errorf("unknown secret of type %T", secret))
    87  }
    88  
    89  func Rewrite(
    90  	newMasterKey string,
    91  	pushFunc func() error,
    92  ) error {
    93  	oldMasterKey := Masterkey
    94  	Masterkey = newMasterKey
    95  	defer func() {
    96  		Masterkey = oldMasterKey
    97  	}()
    98  
    99  	return pushFunc()
   100  }
   101  
   102  func Write(
   103  	monitor mntr.Monitor,
   104  	k8sClient kubernetes.ClientInt,
   105  	path,
   106  	value,
   107  	writtenByCLI,
   108  	writtenByVersion string,
   109  	getFunc GetFuncs,
   110  	pushFunc PushFuncs,
   111  ) error {
   112  	allSecrets, allExisting, allTrees, err := getFunc()
   113  	if err != nil {
   114  		return err
   115  	}
   116  
   117  	if helper.IsNil(k8sClient) {
   118  		allExisting = make(map[string]*Existing)
   119  	}
   120  
   121  	secret, err := findSecret(allSecrets, allExisting, &path, true)
   122  	if err != nil {
   123  		return err
   124  	}
   125  
   126  	switch secretType := secret.(type) {
   127  	case *Secret:
   128  		if secretType.Value == value {
   129  			monitor.Info("Value is unchanged")
   130  			return nil
   131  		}
   132  		secretType.Value = value
   133  	case *Existing:
   134  		var refChanged bool
   135  		if secretType.Name == "" {
   136  			secretType.Name = strings.ReplaceAll(path, ".", "-")
   137  			refChanged = true
   138  		}
   139  
   140  		if secretType.Key == "" {
   141  			secretType.Key = "default"
   142  			refChanged = true
   143  		}
   144  
   145  		k8sSecret, err := k8sClient.GetSecret(existingSecretsNamespace, secretType.Name)
   146  		if macherrs.IsNotFound(err) {
   147  			err = nil
   148  			k8sSecret = &v1.Secret{
   149  				ObjectMeta: mach.ObjectMeta{
   150  					Name:      secretType.Name,
   151  					Namespace: existingSecretsNamespace,
   152  					Labels: map[string]string{
   153  						"cli":     writtenByCLI,
   154  						"version": writtenByVersion,
   155  					},
   156  				},
   157  				Immutable: boolPtr(false),
   158  				Type:      v1.SecretTypeOpaque,
   159  			}
   160  		}
   161  		if err != nil {
   162  			return err
   163  		}
   164  
   165  		if k8sSecret.Data == nil {
   166  			k8sSecret.Data = make(map[string][]byte)
   167  		}
   168  		k8sSecret.Data[secretType.Key] = []byte(value)
   169  		if err := k8sClient.ApplySecret(k8sSecret); err != nil {
   170  			return err
   171  		}
   172  		if !refChanged {
   173  			return nil
   174  		}
   175  	}
   176  
   177  	return pushFunc(allTrees, path)
   178  }
   179  
   180  func GetOperatorSecrets(
   181  	monitor mntr.Monitor,
   182  	printLogs,
   183  	gitops bool,
   184  	gitClient *git.Client,
   185  	desiredFile git.DesiredFile,
   186  	allTrees map[string]*tree.Tree,
   187  	allSecrets map[string]*Secret,
   188  	allExistingSecrets map[string]*Existing,
   189  	treeFromCRD func() (*tree.Tree, error),
   190  	getOperatorSpecifics func(*tree.Tree) (map[string]*Secret, map[string]*Existing, bool, error),
   191  ) error {
   192  
   193  	operator := strings.Split(string(desiredFile), ".")[0]
   194  
   195  	if gitops {
   196  		if !gitClient.Exists(desiredFile) {
   197  			if printLogs {
   198  				monitor.Info(fmt.Sprintf("file %s not found", desiredFile))
   199  			}
   200  			return nil
   201  		}
   202  
   203  		operatorTree, err := gitClient.ReadTree(desiredFile)
   204  		if err != nil {
   205  			return err
   206  		}
   207  		allTrees[operator] = operatorTree
   208  	} else {
   209  		operatorTree, err := treeFromCRD()
   210  		if operatorTree == nil {
   211  			return err
   212  		}
   213  		allTrees[operator] = operatorTree
   214  	}
   215  
   216  	secrets, existing, migrate, err := getOperatorSpecifics(allTrees[operator])
   217  	if err != nil {
   218  		return err
   219  	}
   220  
   221  	if migrate {
   222  		return fmt.Errorf("please use the api command to migrate to the latest %s api first", operator)
   223  	}
   224  
   225  	if !gitops {
   226  		secrets = nil
   227  	}
   228  
   229  	suffixedSecrets := make(map[string]*Secret, len(secrets))
   230  	suffixedExisting := make(map[string]*Existing, len(existing))
   231  	for k, v := range secrets {
   232  		suffixedSecrets[k+".encrypted"] = v
   233  	}
   234  	for k, v := range existing {
   235  		suffixedExisting[k+".existing"] = v
   236  	}
   237  
   238  	AppendSecrets(operator, allSecrets, suffixedSecrets, allExistingSecrets, suffixedExisting)
   239  
   240  	return nil
   241  }
   242  
   243  func secretsListToSlice(
   244  	secrets map[string]*Secret,
   245  	existing map[string]*Existing,
   246  	includeEmpty bool,
   247  ) []string {
   248  	items := make([]string, 0, len(secrets)+len(existing))
   249  	for key, value := range secrets {
   250  		if includeEmpty || (value != nil && value.Value != "") {
   251  			items = append(items, key)
   252  		}
   253  	}
   254  	for key, value := range existing {
   255  		if includeEmpty || (value != nil && value.Name != "" && value.Key != "") {
   256  			items = append(items, key)
   257  		}
   258  	}
   259  	return items
   260  }
   261  
   262  func findSecret(
   263  	allSecrets map[string]*Secret,
   264  	allExisting map[string]*Existing,
   265  	path *string,
   266  	includeEmpty bool,
   267  ) (
   268  	interface{},
   269  	error,
   270  ) {
   271  	if *path != "" {
   272  		secret, err := exactSecret(allSecrets, allExisting, *path)
   273  		return secret, mntr.ToUserError(err)
   274  	}
   275  
   276  	selectItems := secretsListToSlice(allSecrets, allExisting, includeEmpty)
   277  
   278  	sort.Slice(selectItems, func(i, j int) bool {
   279  		iDots := strings.Count(selectItems[i], ".")
   280  		jDots := strings.Count(selectItems[j], ".")
   281  		return iDots < jDots || iDots == jDots && selectItems[i] < selectItems[j]
   282  	})
   283  
   284  	var result string
   285  	if err := survey.AskOne(&survey.Select{
   286  		Message: "Select a secret:",
   287  		Options: selectItems,
   288  	}, &result, survey.WithValidator(survey.Required)); err != nil {
   289  		return nil, err
   290  	}
   291  	*path = result
   292  
   293  	secret, err := exactSecret(allSecrets, allExisting, *path)
   294  	if err != nil {
   295  		panic(err)
   296  	}
   297  	return secret, nil
   298  }
   299  
   300  func exactSecret(
   301  	secrets map[string]*Secret,
   302  	existings map[string]*Existing,
   303  	path string,
   304  ) (
   305  	interface{},
   306  	error,
   307  ) {
   308  	secret, ok := secrets[path]
   309  	if ok {
   310  		return secret, nil
   311  	}
   312  
   313  	existing, ok := existings[path]
   314  	if ok {
   315  		return existing, nil
   316  	}
   317  
   318  	return nil, fmt.Errorf("no secret found at %s", path)
   319  }
   320  
   321  func boolPtr(b bool) *bool { return &b }