pkg/vault/kv/v2_service.go (152 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 kv import ( "context" "errors" "fmt" "github.com/hashicorp/vault/api" "github.com/elastic/harp/pkg/vault/logical" vpath "github.com/elastic/harp/pkg/vault/path" ) type kvv2Backend struct { logical logical.Logical mountPath string customMetadataEnabled bool } // V2 returns a K/V v2 backend service instance. func V2(l logical.Logical, mountPath string, customMetadataEnabled bool) Service { return &kvv2Backend{ logical: l, mountPath: mountPath, customMetadataEnabled: customMetadataEnabled, } } //nolint:revive // refactor use of ctx func (s *kvv2Backend) List(ctx context.Context, path string) ([]string, error) { // Check arguments secretPath := vpath.SanitizePath(path) if secretPath == "" { return nil, fmt.Errorf("unable to query with empty path") } // Create logical client secret, err := s.logical.List(vpath.AddPrefixToVKVPath(secretPath, s.mountPath, "metadata")) if err != nil { return nil, fmt.Errorf("unable to list secret keys: %w", err) } if secret == nil { // Path is a leaf return nil, nil } if secret.Data == nil { return nil, fmt.Errorf("invalid secret response") } // Check required property k, ok := secret.Data["keys"] if !ok || k == nil { return nil, fmt.Errorf("invalid response missing 'keys' property") } // Check value type r, ok := k.([]interface{}) if !ok { return nil, fmt.Errorf("invalid response 'keys' is not a list (%T)", k) } // Convert list of interface to list of string out := make([]string, len(r)) for i := range r { out[i] = fmt.Sprintf("%v", r[i]) } // No error return out, nil } func (s *kvv2Backend) Read(ctx context.Context, path string) (SecretData, SecretMetadata, error) { return s.ReadVersion(ctx, path, 0) } //nolint:revive // refactor use of ctx func (s *kvv2Backend) ReadVersion(ctx context.Context, path string, version uint32) (SecretData, SecretMetadata, error) { // Clean path first secretPath := vpath.SanitizePath(path) if secretPath == "" { return nil, nil, fmt.Errorf("unable to query with empty path") } var ( secret *api.Secret err error ) // Create a logical client if version > 0 { // Prepare params versionParam := map[string][]string{ "version": {fmt.Sprintf("%d", version)}, } secret, err = s.logical.ReadWithData(vpath.AddPrefixToVKVPath(secretPath, s.mountPath, "data"), versionParam) } else { secret, err = s.logical.Read(vpath.AddPrefixToVKVPath(secretPath, s.mountPath, "data")) } if err != nil { return nil, nil, fmt.Errorf("unable to retrieve secret for path '%s': %w", path, err) } if secret == nil { return nil, nil, fmt.Errorf("unable to retrieve secret for path '%s': %w", path, ErrPathNotFound) } if secret.Data == nil { return nil, nil, fmt.Errorf("unable to retrieve secret for path '%s': %w", path, ErrNoData) } // Check v2 backend data, ok := secret.Data["data"] if !ok { return nil, nil, fmt.Errorf("unable to extract values for path '%s', secret backend supposed to be a v2 but it's not", path) } metadata, ok := secret.Data["metadata"].(map[string]interface{}) if !ok { return nil, nil, fmt.Errorf("unable to extract metadata for path '%s', secret backend supposed to be a v2 but it's not", path) } // Check data if data == nil { return nil, nil, ErrNoData } // Custom metadata enabled => retrieve secret meatadata. if s.customMetadataEnabled { rawMeta, errMeta := s.logical.Read(vpath.AddPrefixToVKVPath(secretPath, s.mountPath, "metadata")) if errMeta != nil { return nil, nil, fmt.Errorf("unable to extract secret metadata for path '%s': %w", path, errMeta) } if rawMeta == nil { return nil, nil, fmt.Errorf("unable to retrieve secret metadata for path '%s': %w", path, ErrPathNotFound) } if rawMeta.Data == nil { return nil, nil, fmt.Errorf("unable to retrieve secret metadata for path '%s': %w", path, ErrNoData) } // Check if response contains custom_metadata if rawCustomMeta, ok := rawMeta.Data["custom_metadata"]; ok { if customMeta, ok := rawCustomMeta.(map[string]interface{}); ok { metadata["custom_metadata"] = customMeta } } } // Return secret value and no error return data.(map[string]interface{}), metadata, err } func (s *kvv2Backend) Write(ctx context.Context, path string, data SecretData) error { return s.WriteWithMeta(ctx, path, data, nil) } //nolint:revive // refactor use of ctx func (s *kvv2Backend) WriteWithMeta(ctx context.Context, path string, data SecretData, meta SecretMetadata) error { // Clean path first secretPath := vpath.SanitizePath(path) if secretPath == "" { return fmt.Errorf("unable to query with empty path") } // Custom metadata not enabled => store metadata as secret data. if s.customMetadataEnabled { // Validate metadata if len(meta) > CustomMetadataKeyLimit { return errors.New("unable to store more than 64 custom metadata keys") } // Check key and value constraints for k, v := range meta { if len(k) > CustomMetadataKeySizeLimit { return fmt.Errorf("custom meta '%s' could not be stored, it must be less than 128 bytes", k) } raw, ok := v.(string) if !ok { return fmt.Errorf("custom meta '%s' must be a string", k) } if len(raw) > CustomMetadataValueSizeLimit { return fmt.Errorf("custom meta '%s' value is too large (%d), it must be less than 512 bytes", k, len(raw)) } } } else if len(meta) > 0 { // Add metadata to data data[VaultMetadataDataKey] = meta } // Write data _, err := s.logical.Write(vpath.AddPrefixToVKVPath(secretPath, s.mountPath, "data"), map[string]interface{}{ "data": data, }) if err != nil { return fmt.Errorf("unable to write secret data for path '%s': %w", path, err) } // Write metadata if s.customMetadataEnabled && len(meta) > 0 { _, err := s.logical.Write(vpath.AddPrefixToVKVPath(secretPath, s.mountPath, "metadata"), map[string]interface{}{ "custom_metadata": meta, }) if err != nil { return fmt.Errorf("unable to write secret metadata for path '%s': %w", path, err) } } // No error return nil }