github.com/zntrio/harp/v2@v2.0.9/pkg/bundle/template/visitor/secretbuilder/helpers.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 secretbuilder 19 20 import ( 21 "encoding/json" 22 "errors" 23 "fmt" 24 "time" 25 26 bundlev1 "github.com/zntrio/harp/v2/api/gen/go/harp/bundle/v1" 27 "github.com/zntrio/harp/v2/pkg/bundle/secret" 28 "github.com/zntrio/harp/v2/pkg/sdk/types" 29 "github.com/zntrio/harp/v2/pkg/template/engine" 30 ) 31 32 func parseSecretTemplate(templateContext engine.Context, secretPath string, item *bundlev1.SecretSuffix, data interface{}) (*bundlev1.Package, error) { 33 // Prepare secret chain 34 chain, err := buildSecretChain(templateContext, secretPath, item, data) 35 if err != nil { 36 return nil, fmt.Errorf("unable to build secret chain for path %q: %w", secretPath, err) 37 } 38 39 // No error 40 return buildPackage(templateContext, secretPath, chain, item) 41 } 42 43 func buildSecretChain(templateContext engine.Context, secretPath string, item *bundlev1.SecretSuffix, data interface{}) (*bundlev1.SecretChain, error) { 44 // Check arguments 45 if types.IsNil(templateContext) { 46 return nil, errors.New("unable to process with nil context") 47 } 48 if secretPath == "" { 49 return nil, errors.New("unable to process with blank secret path") 50 } 51 if item == nil { 52 return nil, errors.New("unable to process with nil secret suffix") 53 } 54 55 // Extract generated secret value 56 kv, err := renderSuffix(templateContext, secretPath, item, data) 57 if err != nil { 58 return nil, fmt.Errorf("unable to render secret suffix (path:%s suffix:%s): %w", secretPath, item.Suffix, err) 59 } 60 61 // Prepare secret list 62 chain := &bundlev1.SecretChain{ 63 Version: uint32(0), 64 Labels: map[string]string{ 65 "generated": "true", 66 }, 67 Annotations: map[string]string{ 68 "creationDate": fmt.Sprintf("%d", time.Now().UTC().Unix()), 69 "description": item.Description, 70 "template": item.Template, 71 }, 72 Data: make([]*bundlev1.KV, 0), 73 NextVersion: nil, 74 PreviousVersion: nil, 75 } 76 77 // Check vendor status 78 if item.Vendor { 79 chain.Labels["vendor"] = "true" 80 } 81 82 // Iterate over K/V 83 for key, value := range kv { 84 // Skip empty key 85 if key == "" { 86 continue 87 } 88 89 // Pack secret value 90 secretBody, err := secret.Pack(value) 91 if err != nil { 92 return nil, fmt.Errorf("unable to pack secret value for path %q: %w", secretPath, err) 93 } 94 95 // Add secret to package 96 chain.Data = append(chain.Data, &bundlev1.KV{ 97 Key: key, 98 Type: fmt.Sprintf("%T", value), 99 Value: secretBody, 100 }) 101 } 102 103 // No error 104 return chain, nil 105 } 106 107 // suffix is a function used for suffix template compiler. 108 func renderSuffix(templateContext engine.Context, secretPath string, item *bundlev1.SecretSuffix, data interface{}) (map[string]interface{}, error) { 109 // Check input 110 if types.IsNil(templateContext) { 111 return nil, errors.New("unable to process with nil context") 112 } 113 if item == nil { 114 return nil, fmt.Errorf("unable to process nil item") 115 } 116 if len(item.Content) == 0 && item.Template == "" { 117 return nil, fmt.Errorf("content or template property must be defined") 118 } 119 120 kv := map[string]interface{}{} 121 122 if item.Template != "" { 123 payload, err := engine.RenderContextWithData(templateContext, item.Template, data) 124 if err != nil { 125 return nil, fmt.Errorf("unable to render suffix template: %w", err) 126 } 127 128 // Parse generated JSON 129 if !json.Valid([]byte(payload)) { 130 return nil, fmt.Errorf("unable to validate generated json for secret path %q: %s", secretPath, payload) 131 } 132 133 // Extract payload as K/V 134 if err := json.Unmarshal([]byte(payload), &kv); err != nil { 135 return nil, fmt.Errorf("unable to assemble secret package for secret path %q: %w", secretPath, err) 136 } 137 } 138 139 if len(item.Content) > 0 { 140 for filename, content := range item.Content { 141 // Render filename 142 renderedFilename, err := engine.RenderContextWithData(templateContext, filename, data) 143 if err != nil { 144 return nil, fmt.Errorf("unable to render filename template: %w", err) 145 } 146 147 // Render content 148 payload, err := engine.RenderContextWithData(templateContext, content, data) 149 if err != nil { 150 return nil, fmt.Errorf("unable to render file content template: %w", err) 151 } 152 153 // Assign result 154 kv[renderedFilename] = payload 155 } 156 } 157 158 // No error 159 return kv, nil 160 } 161 162 func buildPackage(templateContext engine.Context, secretPath string, chain *bundlev1.SecretChain, item *bundlev1.SecretSuffix) (*bundlev1.Package, error) { 163 // Check arguments 164 if types.IsNil(templateContext) { 165 return nil, errors.New("unable to process with nil context") 166 } 167 if secretPath == "" { 168 return nil, errors.New("unable to process with blank secret path") 169 } 170 if chain == nil { 171 return nil, errors.New("unable to process with nil secret chain") 172 } 173 if item == nil { 174 return nil, errors.New("unable to process with nil secret suffix") 175 } 176 177 // Evaluate annotation values 178 if item.Annotations != nil { 179 for k, v := range item.Annotations { 180 // Evaluate using template engine 181 renderedValue, err := engine.RenderContext(templateContext, v) 182 if err != nil { 183 return nil, fmt.Errorf("unable to render annotations value %q of %q: %w", k, secretPath, err) 184 } 185 186 item.Annotations[k] = renderedValue 187 } 188 } 189 190 // Evaluate labels values 191 if item.Labels != nil { 192 for k, v := range item.Labels { 193 // Evaluate using template engine 194 renderedValue, err := engine.RenderContext(templateContext, v) 195 if err != nil { 196 return nil, fmt.Errorf("unable to render label value %q of %q: %w", k, secretPath, err) 197 } 198 199 item.Labels[k] = renderedValue 200 } 201 } 202 203 // Assemble final secret package 204 return &bundlev1.Package{ 205 Name: secretPath, 206 Secrets: chain, 207 Annotations: item.GetAnnotations(), 208 Labels: item.GetLabels(), 209 }, nil 210 }