get.porter.sh/porter@v1.3.0/pkg/storage/sanitizer.go (about)

     1  package storage
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  
     7  	"get.porter.sh/porter/pkg/cnab"
     8  	"get.porter.sh/porter/pkg/secrets"
     9  	"github.com/cnabio/cnab-go/secrets/host"
    10  )
    11  
    12  // Sanitizer identifies sensitive data in a database record, and replaces it with
    13  // a reference to a secret created by the service in an external secret store.
    14  type Sanitizer struct {
    15  	parameter ParameterSetProvider
    16  	secrets   secrets.Store
    17  }
    18  
    19  // NewSanitizer creates a new service for sanitizing sensitive data and save them
    20  // to a secret store.
    21  func NewSanitizer(parameterstore ParameterSetProvider, secretstore secrets.Store) *Sanitizer {
    22  	return &Sanitizer{
    23  		parameter: parameterstore,
    24  		secrets:   secretstore,
    25  	}
    26  }
    27  
    28  // CleanRawParameters clears out sensitive data in raw parameter values (resolved parameter values stored on a Run) before
    29  // transform the raw value into secret strategies.
    30  // The id argument is used to associate the reference key with the corresponding
    31  // run or installation record in porter's database.
    32  func (s *Sanitizer) CleanRawParameters(ctx context.Context, params map[string]interface{}, bun cnab.ExtendedBundle, id string) ([]secrets.SourceMap, error) {
    33  	strategies := make([]secrets.SourceMap, 0, len(params))
    34  	for name, value := range params {
    35  		stringVal, err := bun.WriteParameterToString(name, value)
    36  		if err != nil {
    37  			return nil, err
    38  		}
    39  		strategy := ValueStrategy(name, stringVal)
    40  		strategies = append(strategies, strategy)
    41  	}
    42  
    43  	strategies, err := s.CleanParameters(ctx, strategies, bun, id)
    44  	if err != nil {
    45  		return nil, err
    46  	}
    47  
    48  	return strategies, nil
    49  
    50  }
    51  
    52  // CleanParameters clears out sensitive data in strategized parameter data (overrides provided by the user on an Installation record) and return
    53  // Sanitized value after saving sensitive data to secrets store.
    54  // The id argument is used to associate the reference key with the corresponding
    55  // run or installation record in porter's database.
    56  func (s *Sanitizer) CleanParameters(ctx context.Context, dirtyParams []secrets.SourceMap, bun cnab.ExtendedBundle, id string) ([]secrets.SourceMap, error) {
    57  	cleanedParams := make([]secrets.SourceMap, 0, len(dirtyParams))
    58  	for _, param := range dirtyParams {
    59  		// Store sensitive hard-coded values in a secret store
    60  		if param.Source.Strategy == host.SourceValue && bun.IsSensitiveParameter(param.Name) {
    61  			cleaned := sanitizedParam(param, id)
    62  			err := s.secrets.Create(ctx, cleaned.Source.Strategy, cleaned.Source.Hint, cleaned.ResolvedValue)
    63  			if err != nil {
    64  				return nil, fmt.Errorf("failed to save sensitive param to secrete store: %w", err)
    65  			}
    66  
    67  			cleanedParams = append(cleanedParams, cleaned)
    68  		} else { // All other parameters are safe to use without cleaning
    69  			cleanedParams = append(cleanedParams, param)
    70  		}
    71  	}
    72  
    73  	if len(cleanedParams) == 0 {
    74  		return nil, nil
    75  	}
    76  
    77  	return cleanedParams, nil
    78  
    79  }
    80  
    81  // LinkSensitiveParametersToSecrets creates a reference key for sensitive data
    82  // and replace the sensitive value with the reference key.
    83  // The id argument is used to associate the reference key with the corresponding
    84  // run or installation record in porter's database.
    85  func LinkSensitiveParametersToSecrets(pset ParameterSet, bun cnab.ExtendedBundle, id string) ParameterSet {
    86  	for i, param := range pset.Parameters {
    87  		if !bun.IsSensitiveParameter(param.Name) {
    88  			continue
    89  		}
    90  		pset.Parameters[i] = sanitizedParam(param, id)
    91  	}
    92  
    93  	return pset
    94  }
    95  
    96  func sanitizedParam(param secrets.SourceMap, id string) secrets.SourceMap {
    97  	param.Source.Strategy = secrets.SourceSecret
    98  	param.Source.Hint = id + "-" + param.Name
    99  	return param
   100  }
   101  
   102  // RestoreParameterSet resolves the raw parameter data from a secrets store.
   103  func (s *Sanitizer) RestoreParameterSet(ctx context.Context, pset ParameterSet, bun cnab.ExtendedBundle) (map[string]interface{}, error) {
   104  	params, err := s.parameter.ResolveAll(ctx, pset, pset.Keys())
   105  	if err != nil {
   106  		return nil, err
   107  	}
   108  
   109  	resolved := make(map[string]interface{})
   110  	for name, value := range params {
   111  		paramValue, err := bun.ConvertParameterValue(name, value)
   112  		if err != nil {
   113  			paramValue = value
   114  		}
   115  
   116  		resolved[name] = paramValue
   117  
   118  	}
   119  	return resolved, nil
   120  
   121  }
   122  
   123  // CleanOutput clears data that's defined as sensitive on the bundle definition
   124  // by storing the raw data into a secret store and store it's reference key onto
   125  // the output record.
   126  func (s *Sanitizer) CleanOutput(ctx context.Context, output Output, bun cnab.ExtendedBundle) (Output, error) {
   127  	// Skip outputs not defined in the bundle, e.g. io.cnab.outputs.invocationImageLogs
   128  	_, ok := output.GetSchema(bun)
   129  	if !ok {
   130  		return output, nil
   131  	}
   132  
   133  	sensitive, err := bun.IsOutputSensitive(output.Name)
   134  	if err != nil {
   135  		output.Value = nil
   136  		return output, err
   137  	}
   138  
   139  	if !sensitive {
   140  		return output, nil
   141  
   142  	}
   143  
   144  	secretOt := sanitizedOutput(output)
   145  
   146  	err = s.secrets.Create(ctx, secrets.SourceSecret, secretOt.Key, string(output.Value))
   147  	if err != nil {
   148  		return secretOt, err
   149  	}
   150  
   151  	return secretOt, nil
   152  }
   153  
   154  func sanitizedOutput(output Output) Output {
   155  	output.Key = output.RunID + "-" + output.Name
   156  	output.Value = nil
   157  	return output
   158  
   159  }
   160  
   161  // RestoreOutputs retrieves all raw output value and return the restored outputs
   162  // record.
   163  func (s *Sanitizer) RestoreOutputs(ctx context.Context, o Outputs) (Outputs, error) {
   164  	resolved := make([]Output, 0, o.Len())
   165  	for _, ot := range o.Value() {
   166  		r, err := s.RestoreOutput(ctx, ot)
   167  		if err != nil {
   168  			return o, fmt.Errorf("failed to resolve output %q using key %q: %w", ot.Name, ot.Key, err)
   169  		}
   170  		resolved = append(resolved, r)
   171  	}
   172  
   173  	return NewOutputs(resolved), nil
   174  }
   175  
   176  // RestoreOutput retrieves the raw output value and return the restored output
   177  // record.
   178  func (s *Sanitizer) RestoreOutput(ctx context.Context, output Output) (Output, error) {
   179  	if output.Key == "" {
   180  		return output, nil
   181  	}
   182  	resolved, err := s.secrets.Resolve(ctx, secrets.SourceSecret, string(output.Key))
   183  	if err != nil {
   184  		return output, err
   185  	}
   186  
   187  	output.Value = []byte(resolved)
   188  	return output, nil
   189  }