pkg/bundle/codec.go (164 lines of code) (raw):

// Licensed to Elasticsearch B.V. under one or more contributor // license agreements. See the NOTICE file distributed with // this work for additional information regarding copyright // ownership. Elasticsearch B.V. licenses this file to you under // the Apache License, Version 2.0 (the "License"); you may // not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, // software distributed under the License is distributed on an // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY // KIND, either express or implied. See the License for the // specific language governing permissions and limitations // under the License. package bundle import ( "encoding/json" "fmt" "io" "sort" "strings" "google.golang.org/protobuf/encoding/protojson" "google.golang.org/protobuf/proto" bundlev1 "github.com/elastic/harp/api/gen/go/harp/bundle/v1" "github.com/elastic/harp/pkg/bundle/secret" "github.com/elastic/harp/pkg/sdk/security" "github.com/elastic/harp/pkg/sdk/types" ) // Load a file bundle from the buffer. func Load(r io.Reader) (*bundlev1.Bundle, error) { // Check parameters if types.IsNil(r) { return nil, fmt.Errorf("unable to process nil reader") } decoded, err := io.ReadAll(r) if err != nil { return nil, fmt.Errorf("unable to decompress bundle content") } // Deserialize protobuf payload bundle := &bundlev1.Bundle{} if err = proto.Unmarshal(decoded, bundle); err != nil { return nil, fmt.Errorf("unable to decode bundle content") } // Compute merkle tree root tree, _, err := Tree(bundle) if err != nil { return nil, fmt.Errorf("unable to compute merkle tree of bundle content: %w", err) } // Check if root match if !security.SecureCompare(bundle.MerkleTreeRoot, tree.Root()) { return nil, fmt.Errorf("invalid merkle tree root, bundle is corrupted") } // No error return bundle, nil } // Dump a file bundle to the writer. func Dump(w io.Writer, b *bundlev1.Bundle) error { // Check parameters if types.IsNil(w) { return fmt.Errorf("unable to process nil writer") } if b == nil { return fmt.Errorf("unable to process nil bundle") } // Sort packages sort.SliceStable(b.Packages, func(i, j int) bool { return b.Packages[i].Name < b.Packages[j].Name }) // Compute merkle tree tree, _, err := Tree(b) if err != nil { return fmt.Errorf("unable to compute merkle tree of bundle content: %w", err) } // Assign to bundle b.MerkleTreeRoot = tree.Root() // Serialize protobuf payload payload, err := proto.Marshal(b) if err != nil { return fmt.Errorf("unable to encode bundle content: %w", err) } // WWrite to writer if _, err = w.Write(payload); err != nil { return fmt.Errorf("unable to write serialized Bundle: %w", err) } // No error return nil } // Read a secret located at secretPath from the given bundle. func Read(b *bundlev1.Bundle, secretPath string) (map[string]interface{}, error) { // Check bundle if b == nil { return nil, fmt.Errorf("unable to process nil bundle") } if secretPath == "" { return nil, fmt.Errorf("unable to process with empty path") } // Lookup secret package var found *bundlev1.Package for _, item := range b.Packages { if strings.EqualFold(item.Name, secretPath) { found = item break } } if found == nil { return nil, fmt.Errorf("unable to lookup secret with path '%s'", secretPath) } // Transform secret value result := map[string]interface{}{} for _, s := range found.Secrets.Data { // Unpack secret value var obj interface{} if err := secret.Unpack(s.Value, &obj); err != nil { return nil, fmt.Errorf("unable to unpack secret value for path '%s': %w", secretPath, err) } // Add to result result[s.Key] = obj } // No error return result, nil } // AsProtoJSON export given bundle as a JSON representation. // //nolint:interfacer // Tighly coupled with type func AsProtoJSON(w io.Writer, b *bundlev1.Bundle) error { // Check parameters if types.IsNil(w) { return fmt.Errorf("unable to process nil writer") } if b == nil { return fmt.Errorf("unable to process nil bundle") } // Clone bundle (we don't want to modify input bundle) cloned, ok := proto.Clone(b).(*bundlev1.Bundle) if !ok { return fmt.Errorf("the cloned bundle does not have a correct type: %T", cloned) } // Initialize marshaller m := &protojson.MarshalOptions{} // Decode packed values for _, p := range cloned.Packages { for _, s := range p.Secrets.Data { // Unpack secret value var data interface{} if err := secret.Unpack(s.Value, &data); err != nil { return fmt.Errorf("unable to unpack '%s' - '%s' secret value: %w", p.Name, s.Key, err) } // Re-encode as json payload, err := json.Marshal(data) if err != nil { return fmt.Errorf("unable to encode '%s' - '%s' secret value as json: %w", p.Name, s.Key, err) } // Replace current packed secret value by json encoded one. s.Value = payload } } // Marshal bundle out, err := m.Marshal(cloned) if err != nil { return fmt.Errorf("unable to produce JSON from bundle object: %w", err) } // Write to writer if _, err := fmt.Fprintf(w, "%s", string(out)); err != nil { return fmt.Errorf("unable to write JSON bundle: %w", err) } // No error return nil } // AsMap returns a bundle as map func AsMap(b *bundlev1.Bundle) (KV, error) { // Check input if b == nil { return nil, fmt.Errorf("unable to process nil bundle") } res := KV{} for _, p := range b.Packages { // Check if secret is locked if p.Secrets.Locked != nil { // Encode value res[p.Name] = KV{ "@type": packageEncryptedValueType, "value": p.Secrets.Locked.Value, } continue } // Map package secrets secrets, err := AsSecretMap(p) if err != nil { return nil, fmt.Errorf("unable to pack secrets as a map: %w", err) } // Assign result res[p.Name] = secrets } // No error return res, nil } // AsMetadataMap exports given bundle metadata as a map. func AsMetadataMap(b *bundlev1.Bundle) (KV, error) { // Check input if b == nil { return nil, fmt.Errorf("unable to process nil bundle") } metaMap := KV{} // Check if bundle as metadata if len(b.Annotations) > 0 { metaMap[bundleAnnotationsKey] = b.Annotations } if len(b.Labels) > 0 { metaMap[bundleLabelsKey] = b.Labels } // Export bundle metadata for _, p := range b.Packages { metadata := KV{} // Has annotations if len(p.Annotations) > 0 { // Assign json metadata[packageAnnotations] = p.Annotations } // Has labels if len(p.Labels) > 0 { // Assign json metadata[packageLabels] = p.Labels } // Assign to package metaMap[p.Name] = metadata } // No error return metaMap, nil }