github.com/zntrio/harp/v2@v2.0.9/pkg/tasks/container/seal.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 container
    19  
    20  import (
    21  	"context"
    22  	"crypto/rand"
    23  	"encoding/base64"
    24  	"encoding/json"
    25  	"errors"
    26  	"fmt"
    27  
    28  	"github.com/awnumar/memguard"
    29  
    30  	"github.com/zntrio/harp/v2/pkg/container"
    31  	"github.com/zntrio/harp/v2/pkg/container/seal"
    32  	sealv1 "github.com/zntrio/harp/v2/pkg/container/seal/v1"
    33  	sealv2 "github.com/zntrio/harp/v2/pkg/container/seal/v2"
    34  	"github.com/zntrio/harp/v2/pkg/sdk/types"
    35  	"github.com/zntrio/harp/v2/pkg/tasks"
    36  )
    37  
    38  // SealTask implements secret container sealing task.
    39  type SealTask struct {
    40  	ContainerReader          tasks.ReaderProvider
    41  	SealedContainerWriter    tasks.WriterProvider
    42  	OutputWriter             tasks.WriterProvider
    43  	PeerPublicKeys           []string
    44  	DCKDMasterKey            string
    45  	DCKDTarget               string
    46  	JSONOutput               bool
    47  	DisableContainerIdentity bool
    48  	SealVersion              uint
    49  	PreSharedKey             *memguard.LockedBuffer
    50  }
    51  
    52  // Run the task.
    53  //
    54  //nolint:funlen,gocyclo // to refactor
    55  func (t *SealTask) Run(ctx context.Context) error {
    56  	// Check arguments
    57  	if types.IsNil(t.ContainerReader) {
    58  		return errors.New("unable to run task with a nil containerReader provider")
    59  	}
    60  	if types.IsNil(t.SealedContainerWriter) {
    61  		return errors.New("unable to run task with a nil sealedContainerWriter provider")
    62  	}
    63  	if types.IsNil(t.OutputWriter) {
    64  		return errors.New("unable to run task with a nil outputWriter provider")
    65  	}
    66  	if len(t.PeerPublicKeys) == 0 {
    67  		return errors.New("at least one public key must be provided for recovery")
    68  	}
    69  
    70  	// Create input reader
    71  	reader, err := t.ContainerReader(ctx)
    72  	if err != nil {
    73  		return fmt.Errorf("unable to open input reader: %w", err)
    74  	}
    75  
    76  	// Load input container
    77  	in, err := container.Load(reader)
    78  	if err != nil {
    79  		return fmt.Errorf("unable to read input container: %w", err)
    80  	}
    81  
    82  	var containerKey string
    83  	if !t.DisableContainerIdentity {
    84  		opts := []seal.GenerateOption{}
    85  
    86  		// Check container sealing master key usage
    87  		if t.DCKDMasterKey != "" {
    88  			// Process target
    89  			if t.DCKDTarget == "" {
    90  				return errors.New("target flag (string) is mandatory for key derivation")
    91  			}
    92  
    93  			// Decode master key
    94  			masterKeyRaw, errDecode := base64.RawURLEncoding.DecodeString(t.DCKDMasterKey)
    95  			if errDecode != nil {
    96  				return fmt.Errorf("unable to decode master key: %w", errDecode)
    97  			}
    98  
    99  			// Enable deterministic container key generation
   100  			opts = append(opts, seal.WithDeterministicKey(memguard.NewBufferFromBytes(masterKeyRaw), t.DCKDTarget))
   101  		}
   102  
   103  		// Initialize seal strategy
   104  		var ss seal.Strategy
   105  		switch t.SealVersion {
   106  		case 1:
   107  			ss = sealv1.New()
   108  		case 2:
   109  			ss = sealv2.New()
   110  		default:
   111  			ss = sealv1.New()
   112  		}
   113  
   114  		// Generate container key
   115  		containerPublicKey, containerSecretKey, errGenerate := ss.GenerateKey(opts...)
   116  		if errGenerate != nil {
   117  			return fmt.Errorf("unable to generate container key: %w", errGenerate)
   118  		}
   119  
   120  		// Append to identities
   121  		t.PeerPublicKeys = append(t.PeerPublicKeys, containerPublicKey)
   122  
   123  		// Assign container key
   124  		containerKey = containerSecretKey
   125  	}
   126  
   127  	// Seal options
   128  	sopts := []container.Option{
   129  		container.WithPeerPublicKeys(t.PeerPublicKeys),
   130  	}
   131  
   132  	// Process pre-shared key
   133  	if t.PreSharedKey != nil {
   134  		// Try to decode preshared key
   135  		psk, errDecode := base64.RawURLEncoding.DecodeString(t.PreSharedKey.String())
   136  		if errDecode != nil {
   137  			return fmt.Errorf("unable to decode pre-shared key: %w", errDecode)
   138  		}
   139  		sopts = append(sopts, container.WithPreSharedKey(memguard.NewBufferFromBytes(psk)))
   140  		t.PreSharedKey.Destroy()
   141  	}
   142  
   143  	// Seal the container
   144  	sealedContainer, err := container.Seal(rand.Reader, in, sopts...)
   145  	if err != nil {
   146  		return fmt.Errorf("unable to seal container: %w", err)
   147  	}
   148  
   149  	// Open output file
   150  	writer, err := t.SealedContainerWriter(ctx)
   151  	if err != nil {
   152  		return fmt.Errorf("unable to create output bundle: %w", err)
   153  	}
   154  
   155  	// Dump to writer
   156  	if err = container.Dump(writer, sealedContainer); err != nil {
   157  		return fmt.Errorf("unable to write sealed container: %w", err)
   158  	}
   159  
   160  	if !t.DisableContainerIdentity {
   161  		// Get output writer
   162  		outputWriter, err := t.OutputWriter(ctx)
   163  		if err != nil {
   164  			return fmt.Errorf("unable to retrieve output writer: %w", err)
   165  		}
   166  
   167  		// Display as json
   168  		if t.JSONOutput {
   169  			if err := json.NewEncoder(outputWriter).Encode(map[string]interface{}{
   170  				"container_key": containerKey,
   171  			}); err != nil {
   172  				return fmt.Errorf("unable to display as json: %w", err)
   173  			}
   174  		} else {
   175  			// Display container key
   176  			if _, err := fmt.Fprintf(outputWriter, "Container key : %s\n", containerKey); err != nil {
   177  				return fmt.Errorf("unable to display result: %w", err)
   178  			}
   179  		}
   180  	}
   181  
   182  	// No error
   183  	return nil
   184  }