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  }