pkg/bundle/template/visitor/secretbuilder/helpers.go (141 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 secretbuilder import ( "encoding/json" "errors" "fmt" "time" bundlev1 "github.com/elastic/harp/api/gen/go/harp/bundle/v1" "github.com/elastic/harp/pkg/bundle/secret" "github.com/elastic/harp/pkg/sdk/types" "github.com/elastic/harp/pkg/template/engine" ) func parseSecretTemplate(templateContext engine.Context, secretPath string, item *bundlev1.SecretSuffix, data interface{}) (*bundlev1.Package, error) { // Prepare secret chain chain, err := buildSecretChain(templateContext, secretPath, item, data) if err != nil { return nil, fmt.Errorf("unable to build secret chain for path '%s': %w", secretPath, err) } // No error return buildPackage(templateContext, secretPath, chain, item) } func buildSecretChain(templateContext engine.Context, secretPath string, item *bundlev1.SecretSuffix, data interface{}) (*bundlev1.SecretChain, error) { // Check arguments if types.IsNil(templateContext) { return nil, errors.New("unable to process with nil context") } if secretPath == "" { return nil, errors.New("unable to process with blank secret path") } if item == nil { return nil, errors.New("unable to process with nil secret suffix") } // Extract generated secret value kv, err := renderSuffix(templateContext, secretPath, item, data) if err != nil { return nil, fmt.Errorf("unable to render secret suffix (path:%s suffix:%s): %w", secretPath, item.Suffix, err) } // Prepare secret list chain := &bundlev1.SecretChain{ Version: uint32(0), Labels: map[string]string{ "generated": "true", }, Annotations: map[string]string{ "creationDate": fmt.Sprintf("%d", time.Now().UTC().Unix()), "description": item.Description, "template": item.Template, }, Data: make([]*bundlev1.KV, 0), NextVersion: nil, PreviousVersion: nil, } // Check vendor status if item.Vendor { chain.Labels["vendor"] = "true" } // Iterate over K/V for key, value := range kv { // Skip empty key if key == "" { continue } // Pack secret value secretBody, err := secret.Pack(value) if err != nil { return nil, fmt.Errorf("unable to pack secret value for path '%s': %w", secretPath, err) } // Add secret to package chain.Data = append(chain.Data, &bundlev1.KV{ Key: key, Type: fmt.Sprintf("%T", value), Value: secretBody, }) } // No error return chain, nil } // suffix is a function used for suffix template compiler. func renderSuffix(templateContext engine.Context, secretPath string, item *bundlev1.SecretSuffix, data interface{}) (map[string]interface{}, error) { // Check input if types.IsNil(templateContext) { return nil, errors.New("unable to process with nil context") } if item == nil { return nil, fmt.Errorf("unable to process nil item") } if len(item.Content) == 0 && item.Template == "" { return nil, fmt.Errorf("content or template property must be defined") } kv := map[string]interface{}{} if item.Template != "" { payload, err := engine.RenderContextWithData(templateContext, item.Template, data) if err != nil { return nil, fmt.Errorf("unable to render suffix template: %w", err) } // Parse generated JSON if !json.Valid([]byte(payload)) { return nil, fmt.Errorf("unable to validate generated json for secret path '%s': %s", secretPath, payload) } // Extract payload as K/V if err := json.Unmarshal([]byte(payload), &kv); err != nil { return nil, fmt.Errorf("unable to assemble secret package for secret path '%s': %w", secretPath, err) } } if len(item.Content) > 0 { for filename, content := range item.Content { // Render filename renderedFilename, err := engine.RenderContextWithData(templateContext, filename, data) if err != nil { return nil, fmt.Errorf("unable to render filename template: %w", err) } // Render content payload, err := engine.RenderContextWithData(templateContext, content, data) if err != nil { return nil, fmt.Errorf("unable to render file content template: %w", err) } // Assign result kv[renderedFilename] = payload } } // No error return kv, nil } func buildPackage(templateContext engine.Context, secretPath string, chain *bundlev1.SecretChain, item *bundlev1.SecretSuffix) (*bundlev1.Package, error) { // Check arguments if types.IsNil(templateContext) { return nil, errors.New("unable to process with nil context") } if secretPath == "" { return nil, errors.New("unable to process with blank secret path") } if chain == nil { return nil, errors.New("unable to process with nil secret chain") } if item == nil { return nil, errors.New("unable to process with nil secret suffix") } // Evaluate annotation values if item.Annotations != nil { for k, v := range item.Annotations { // Evaluate using template engine renderedValue, err := engine.RenderContext(templateContext, v) if err != nil { return nil, fmt.Errorf("unable to render annotations value '%s' of '%s': %w", k, secretPath, err) } item.Annotations[k] = renderedValue } } // Evaluate labels values if item.Labels != nil { for k, v := range item.Labels { // Evaluate using template engine renderedValue, err := engine.RenderContext(templateContext, v) if err != nil { return nil, fmt.Errorf("unable to render label value '%s' of '%s': %w", k, secretPath, err) } item.Labels[k] = renderedValue } } // Assemble final secret package return &bundlev1.Package{ Name: secretPath, Secrets: chain, Annotations: item.GetAnnotations(), Labels: item.GetLabels(), }, nil }