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
}