github.com/zntrio/harp/v2@v2.0.9/pkg/bundle/codec.go (about)

     1  // Licensed to Elasticsearch B.V. under one or more contributor
     2  // license agreements. See the NOTICE file distributed with
     3  // this work for additional information regarding copyright
     4  // ownership. Elasticsearch B.V. licenses this file to you under
     5  // the Apache License, Version 2.0 (the "License"); you may
     6  // not use this file except in compliance with the License.
     7  // You may obtain a copy of the License at
     8  //
     9  //     http://www.apache.org/licenses/LICENSE-2.0
    10  //
    11  // Unless required by applicable law or agreed to in writing,
    12  // software distributed under the License is distributed on an
    13  // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
    14  // KIND, either express or implied.  See the License for the
    15  // specific language governing permissions and limitations
    16  // under the License.
    17  
    18  package bundle
    19  
    20  import (
    21  	"bytes"
    22  	"encoding/json"
    23  	"fmt"
    24  	"io"
    25  	"sort"
    26  	"strings"
    27  
    28  	"google.golang.org/protobuf/encoding/protojson"
    29  	"google.golang.org/protobuf/proto"
    30  
    31  	bundlev1 "github.com/zntrio/harp/v2/api/gen/go/harp/bundle/v1"
    32  	"github.com/zntrio/harp/v2/pkg/bundle/secret"
    33  	"github.com/zntrio/harp/v2/pkg/sdk/ioutil"
    34  	"github.com/zntrio/harp/v2/pkg/sdk/security"
    35  	"github.com/zntrio/harp/v2/pkg/sdk/types"
    36  )
    37  
    38  const (
    39  	maxBundleSize = 100 * 1024 * 1024 // 100MB
    40  )
    41  
    42  // Load a file bundle from the buffer.
    43  func Load(r io.Reader) (*bundlev1.Bundle, error) {
    44  	// Check parameters
    45  	if types.IsNil(r) {
    46  		return nil, fmt.Errorf("unable to process nil reader")
    47  	}
    48  
    49  	var err error
    50  
    51  	// Use buffered copy
    52  	decoded := &bytes.Buffer{}
    53  	if err = ioutil.Copy(maxBundleSize, decoded, r); err != nil {
    54  		return nil, fmt.Errorf("unable to load bundle content")
    55  	}
    56  
    57  	// Deserialize protobuf payload
    58  	bundle := &bundlev1.Bundle{}
    59  	if err = proto.Unmarshal(decoded.Bytes(), bundle); err != nil {
    60  		return nil, fmt.Errorf("unable to decode bundle content")
    61  	}
    62  
    63  	// Compute merkle tree root
    64  	tree, _, err := Tree(bundle)
    65  	if err != nil {
    66  		return nil, fmt.Errorf("unable to compute merkle tree of bundle content: %w", err)
    67  	}
    68  
    69  	// Check if root match
    70  	if !security.SecureCompare(bundle.MerkleTreeRoot, tree.Root()) {
    71  		return nil, fmt.Errorf("invalid merkle tree root, bundle is corrupted")
    72  	}
    73  
    74  	// No error
    75  	return bundle, nil
    76  }
    77  
    78  // Dump a file bundle to the writer.
    79  func Dump(w io.Writer, b *bundlev1.Bundle) error {
    80  	// Check parameters
    81  	if types.IsNil(w) {
    82  		return fmt.Errorf("unable to process nil writer")
    83  	}
    84  	if b == nil {
    85  		return fmt.Errorf("unable to process nil bundle")
    86  	}
    87  
    88  	// Sort packages
    89  	sort.SliceStable(b.Packages, func(i, j int) bool {
    90  		return b.Packages[i].Name < b.Packages[j].Name
    91  	})
    92  
    93  	// Compute merkle tree
    94  	tree, _, err := Tree(b)
    95  	if err != nil {
    96  		return fmt.Errorf("unable to compute merkle tree of bundle content: %w", err)
    97  	}
    98  
    99  	// Assign to bundle
   100  	b.MerkleTreeRoot = tree.Root()
   101  
   102  	// Serialize protobuf payload
   103  	payload, err := proto.Marshal(b)
   104  	if err != nil {
   105  		return fmt.Errorf("unable to encode bundle content: %w", err)
   106  	}
   107  
   108  	// WWrite to writer
   109  	if _, err = w.Write(payload); err != nil {
   110  		return fmt.Errorf("unable to write serialized Bundle: %w", err)
   111  	}
   112  
   113  	// No error
   114  	return nil
   115  }
   116  
   117  // Read a secret located at secretPath from the given bundle.
   118  func Read(b *bundlev1.Bundle, secretPath string) (map[string]interface{}, error) {
   119  	// Check bundle
   120  	if b == nil {
   121  		return nil, fmt.Errorf("unable to process nil bundle")
   122  	}
   123  	if secretPath == "" {
   124  		return nil, fmt.Errorf("unable to process with empty path")
   125  	}
   126  
   127  	// Lookup secret package
   128  	var found *bundlev1.Package
   129  	for _, item := range b.Packages {
   130  		if strings.EqualFold(item.Name, secretPath) {
   131  			found = item
   132  			break
   133  		}
   134  	}
   135  	if found == nil {
   136  		return nil, fmt.Errorf("unable to lookup secret with path %q", secretPath)
   137  	}
   138  
   139  	// Transform secret value
   140  	result := map[string]interface{}{}
   141  	for _, s := range found.Secrets.Data {
   142  		// Unpack secret value
   143  		var obj interface{}
   144  		if err := secret.Unpack(s.Value, &obj); err != nil {
   145  			return nil, fmt.Errorf("unable to unpack secret value for path %q: %w", secretPath, err)
   146  		}
   147  
   148  		// Add to result
   149  		result[s.Key] = obj
   150  	}
   151  
   152  	// No error
   153  	return result, nil
   154  }
   155  
   156  // AsProtoJSON export given bundle as a JSON representation.
   157  //
   158  //nolint:interfacer // Tighly coupled with type
   159  func AsProtoJSON(w io.Writer, b *bundlev1.Bundle) error {
   160  	// Check parameters
   161  	if types.IsNil(w) {
   162  		return fmt.Errorf("unable to process nil writer")
   163  	}
   164  	if b == nil {
   165  		return fmt.Errorf("unable to process nil bundle")
   166  	}
   167  
   168  	// Clone bundle (we don't want to modify input bundle)
   169  	cloned, ok := proto.Clone(b).(*bundlev1.Bundle)
   170  	if !ok {
   171  		return fmt.Errorf("the cloned bundle does not have a correct type: %T", cloned)
   172  	}
   173  
   174  	// Initialize marshaller
   175  	m := &protojson.MarshalOptions{}
   176  
   177  	// Decode packed values
   178  	for _, p := range cloned.Packages {
   179  		for _, s := range p.Secrets.Data {
   180  			// Unpack secret value
   181  			var data interface{}
   182  			if err := secret.Unpack(s.Value, &data); err != nil {
   183  				return fmt.Errorf("unable to unpack %q - %q secret value: %w", p.Name, s.Key, err)
   184  			}
   185  
   186  			// Re-encode as json
   187  			payload, err := json.Marshal(data)
   188  			if err != nil {
   189  				return fmt.Errorf("unable to encode %q - %q secret value as json: %w", p.Name, s.Key, err)
   190  			}
   191  
   192  			// Replace current packed secret value by json encoded one.
   193  			s.Value = payload
   194  		}
   195  	}
   196  
   197  	// Marshal bundle
   198  	out, err := m.Marshal(cloned)
   199  	if err != nil {
   200  		return fmt.Errorf("unable to produce JSON from bundle object: %w", err)
   201  	}
   202  
   203  	// Write to writer
   204  	if _, err := fmt.Fprintf(w, "%s", string(out)); err != nil {
   205  		return fmt.Errorf("unable to write JSON bundle: %w", err)
   206  	}
   207  
   208  	// No error
   209  	return nil
   210  }
   211  
   212  // AsMap returns a bundle as map.
   213  func AsMap(b *bundlev1.Bundle) (KV, error) {
   214  	// Check input
   215  	if b == nil {
   216  		return nil, fmt.Errorf("unable to process nil bundle")
   217  	}
   218  
   219  	res := KV{}
   220  	for _, p := range b.Packages {
   221  		// Check if secret is locked
   222  		if p.Secrets.Locked != nil {
   223  			// Encode value
   224  			res[p.Name] = KV{
   225  				"@type": packageEncryptedValueType,
   226  				"value": p.Secrets.Locked.Value,
   227  			}
   228  			continue
   229  		}
   230  
   231  		// Map package secrets
   232  		secrets, err := AsSecretMap(p)
   233  		if err != nil {
   234  			return nil, fmt.Errorf("unable to pack secrets as a map: %w", err)
   235  		}
   236  
   237  		// Assign result
   238  		res[p.Name] = secrets
   239  	}
   240  
   241  	// No error
   242  	return res, nil
   243  }
   244  
   245  // AsMetadataMap exports given bundle metadata as a map.
   246  func AsMetadataMap(b *bundlev1.Bundle) (KV, error) {
   247  	// Check input
   248  	if b == nil {
   249  		return nil, fmt.Errorf("unable to process nil bundle")
   250  	}
   251  
   252  	metaMap := KV{}
   253  
   254  	// Check if bundle as metadata
   255  	if len(b.Annotations) > 0 {
   256  		metaMap[bundleAnnotationsKey] = b.Annotations
   257  	}
   258  	if len(b.Labels) > 0 {
   259  		metaMap[bundleLabelsKey] = b.Labels
   260  	}
   261  
   262  	// Export bundle metadata
   263  	for _, p := range b.Packages {
   264  		metadata := KV{}
   265  		// Has annotations
   266  		if len(p.Annotations) > 0 {
   267  			// Assign json
   268  			metadata[packageAnnotations] = p.Annotations
   269  		}
   270  		// Has labels
   271  		if len(p.Labels) > 0 {
   272  			// Assign json
   273  			metadata[packageLabels] = p.Labels
   274  		}
   275  
   276  		// Assign to package
   277  		metaMap[p.Name] = metadata
   278  	}
   279  
   280  	// No error
   281  	return metaMap, nil
   282  }