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 }