Initial import
This commit is contained in:
		
							
								
								
									
										69
									
								
								aws.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										69
									
								
								aws.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,69 @@
 | 
			
		||||
package secret
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
 | 
			
		||||
	"github.com/aws/aws-sdk-go-v2/config"
 | 
			
		||||
	"github.com/aws/aws-sdk-go-v2/service/kms"
 | 
			
		||||
	"github.com/aws/aws-sdk-go-v2/service/ssm"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type awskms struct {
 | 
			
		||||
	service *kms.Client
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// AWSKeyManagement uses AWS KMS for decrypting blobs.
 | 
			
		||||
//
 | 
			
		||||
// The keys passed in GetSecret are the encrypted blobs and will be converted with [ToBinary].
 | 
			
		||||
func AWSKeyManagement(options ...func(*config.LoadOptions) error) (Provider, error) {
 | 
			
		||||
	config, err := config.LoadDefaultConfig(context.TODO(), options...)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return awskms{kms.NewFromConfig(config)}, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (p awskms) GetSecret(key string) (value []byte, err error) {
 | 
			
		||||
	input := new(kms.DecryptInput)
 | 
			
		||||
	input.CiphertextBlob = ToBinary(key)
 | 
			
		||||
 | 
			
		||||
	var output *kms.DecryptOutput
 | 
			
		||||
	if output, err = p.service.Decrypt(context.TODO(), input); err != nil {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return output.Plaintext, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type awsps struct {
 | 
			
		||||
	service *ssm.Client
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// AWSParameterStorage uses AWS Session Manager Parameter Storage for obtaining secrets.
 | 
			
		||||
func AWSParameterStorage(options ...func(*config.LoadOptions) error) (Provider, error) {
 | 
			
		||||
	config, err := config.LoadDefaultConfig(context.TODO(), options...)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return awsps{service: ssm.NewFromConfig(config)}, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (p awsps) GetSecret(key string) (value []byte, err error) {
 | 
			
		||||
	var yesPlease = true
 | 
			
		||||
	input := new(ssm.GetParameterInput)
 | 
			
		||||
	input.Name = &key
 | 
			
		||||
	input.WithDecryption = &yesPlease
 | 
			
		||||
 | 
			
		||||
	var output *ssm.GetParameterOutput
 | 
			
		||||
	if output, err = p.service.GetParameter(context.TODO(), input); err != nil {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if output.Parameter == nil {
 | 
			
		||||
		return nil, NotFound{Key: key}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return []byte(*output.Parameter.Value), nil
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										76
									
								
								decryption.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										76
									
								
								decryption.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,76 @@
 | 
			
		||||
package secret
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"crypto/aes"
 | 
			
		||||
	"crypto/cipher"
 | 
			
		||||
	"errors"
 | 
			
		||||
 | 
			
		||||
	"golang.org/x/crypto/chacha20poly1305"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type aead struct {
 | 
			
		||||
	Provider
 | 
			
		||||
	aead cipher.AEAD
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// WithChaCha20Poly1305 wraps the returned value and decrypts it using ChaCha20-Poly1305 AEAD.
 | 
			
		||||
//
 | 
			
		||||
// The used nonce size is 12 bytes.
 | 
			
		||||
func WithChaCha20Poly1305(p Provider, key []byte) (Provider, error) {
 | 
			
		||||
	cipher, err := chacha20poly1305.New(key)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	return aead{
 | 
			
		||||
		Provider: p,
 | 
			
		||||
		aead:     cipher,
 | 
			
		||||
	}, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// WithChaCha20Poly1305X wraps the returned value and decrypts it using ChaCha20-Poly1305 AEAD.
 | 
			
		||||
//
 | 
			
		||||
// The used nonce size is 24 bytes.
 | 
			
		||||
func WithChaCha20Poly1305X(p Provider, key []byte) (Provider, error) {
 | 
			
		||||
	cipher, err := chacha20poly1305.NewX(key)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	return aead{
 | 
			
		||||
		Provider: p,
 | 
			
		||||
		aead:     cipher,
 | 
			
		||||
	}, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// WithAESGCM wraps the returned value and decrypts it using AES GCM AEAD.
 | 
			
		||||
//
 | 
			
		||||
// The accepted key sizes are 16 bytes for AES-128 and 32 bytes for AES-256.
 | 
			
		||||
func WithAESGCM(p Provider, key []byte) (Provider, error) {
 | 
			
		||||
	block, err := aes.NewCipher(key)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	gcm, err := cipher.NewGCM(block)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return aead{
 | 
			
		||||
		Provider: p,
 | 
			
		||||
		aead:     gcm,
 | 
			
		||||
	}, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (p aead) GetSecret(key string) (value []byte, err error) {
 | 
			
		||||
	if value, err = p.Provider.GetSecret(key); err != nil {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	nonceSize := p.aead.NonceSize()
 | 
			
		||||
	if len(value) < nonceSize {
 | 
			
		||||
		return nil, errors.New("secret: ciphertext is too short")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	nonce, ciphertext := value[:nonceSize], value[nonceSize:]
 | 
			
		||||
	return p.aead.Open(nil, nonce, ciphertext, nil)
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										152
									
								
								decryption_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										152
									
								
								decryption_test.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,152 @@
 | 
			
		||||
package secret
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"bytes"
 | 
			
		||||
	"encoding/hex"
 | 
			
		||||
	"testing"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestWithAES128GCM(t *testing.T) {
 | 
			
		||||
	key := []byte{
 | 
			
		||||
		0x37, 0xe4, 0x62, 0x59, 0xa6, 0xef, 0xe9, 0x96,
 | 
			
		||||
		0xb8, 0x5d, 0x2c, 0x35, 0xb5, 0x33, 0x3e, 0xff,
 | 
			
		||||
	}
 | 
			
		||||
	env := environment{"test": []byte{
 | 
			
		||||
		0x1c, 0x74, 0x37, 0xe9, 0x9e, 0x37, 0xb8, 0x9e,
 | 
			
		||||
		0xf0, 0x21, 0xc0, 0xec, 0xad, 0x9d, 0xdf, 0x67,
 | 
			
		||||
		0x75, 0xfd, 0x00, 0x48, 0x20, 0x46, 0x2e, 0x14,
 | 
			
		||||
		0xfb, 0x9d, 0x17, 0x9a, 0xe7, 0x9d, 0x6f, 0x39,
 | 
			
		||||
		0x19, 0xd5, 0x3f, 0x22, 0xa1, 0xac, 0xdb, 0xff,
 | 
			
		||||
		0x2c, 0x1f, 0x57, 0x0e, 0xbb, 0x5d, 0xff, 0x89,
 | 
			
		||||
		0x62, 0x55, 0x2b, 0x3b, 0x5e, 0xb5, 0x67, 0xb4,
 | 
			
		||||
		0x32, 0x92, 0x68, 0xcc, 0x55, 0x8e, 0xd3, 0xc7,
 | 
			
		||||
		0xce,
 | 
			
		||||
	}}
 | 
			
		||||
 | 
			
		||||
	p, err := WithAESGCM(env, key)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	msg := []byte("Gophers, gophers, gophers everywhere!")
 | 
			
		||||
	v, err := p.GetSecret("test")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	if !bytes.Equal(v, msg) {
 | 
			
		||||
		t.Errorf("expected:\n%s\n\ngot:\n%s", hex.Dump(msg), hex.Dump(v))
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestWithAES256GCM(t *testing.T) {
 | 
			
		||||
	key := []byte{
 | 
			
		||||
		0x50, 0x0a, 0x33, 0xf2, 0xe5, 0x85, 0xc1, 0xd3,
 | 
			
		||||
		0x65, 0x6d, 0x32, 0xbe, 0xea, 0x7b, 0xce, 0x7e,
 | 
			
		||||
		0x12, 0xbf, 0x7a, 0x47, 0x4d, 0x21, 0x09, 0xa0,
 | 
			
		||||
		0x3e, 0x3f, 0x65, 0xc7, 0xae, 0x94, 0x6c, 0xe3,
 | 
			
		||||
	}
 | 
			
		||||
	env := environment{"test": []byte{
 | 
			
		||||
		0x62, 0x97, 0x6b, 0xc1, 0x78, 0xef, 0x41, 0xa0,
 | 
			
		||||
		0xd4, 0xdc, 0x05, 0x05, 0x66, 0xf6, 0x5f, 0x62,
 | 
			
		||||
		0xca, 0x91, 0xae, 0xd7, 0x7c, 0xff, 0xad, 0xc1,
 | 
			
		||||
		0xbf, 0xd5, 0x61, 0xe3, 0x09, 0x8d, 0x9c, 0xce,
 | 
			
		||||
		0xac, 0x88, 0x55, 0x8c, 0x02, 0xd4, 0x30, 0xc9,
 | 
			
		||||
		0x42, 0x38, 0xdf, 0xf9, 0x8a, 0x4c, 0x92, 0x34,
 | 
			
		||||
		0xc7, 0x82, 0x24, 0xb4, 0x9e, 0x9e, 0xdc, 0x10,
 | 
			
		||||
		0x96, 0x60, 0xa2, 0x92, 0x2a, 0x94, 0x9c, 0x3a,
 | 
			
		||||
		0xd8,
 | 
			
		||||
	}}
 | 
			
		||||
 | 
			
		||||
	p, err := WithAESGCM(env, key)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	msg := []byte("Gophers, gophers, gophers everywhere!")
 | 
			
		||||
	v, err := p.GetSecret("test")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	if !bytes.Equal(v, msg) {
 | 
			
		||||
		t.Errorf("expected:\n%s\n\ngot:\n%s", hex.Dump(msg), hex.Dump(v))
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestWithChaCha20Poly1305(t *testing.T) {
 | 
			
		||||
	key := []byte{
 | 
			
		||||
		0x01, 0x94, 0x99, 0xd6, 0x89, 0x82, 0x7f, 0xa1,
 | 
			
		||||
		0x27, 0x82, 0x36, 0x11, 0x78, 0x22, 0xe5, 0x16,
 | 
			
		||||
		0x21, 0xe2, 0xc6, 0x29, 0x0c, 0x14, 0x6c, 0xb8,
 | 
			
		||||
		0x12, 0x46, 0x2f, 0xae, 0x9e, 0xa7, 0x17, 0x09,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	env := environment{"test": []byte{
 | 
			
		||||
		0x76, 0x7c, 0x7d, 0x5d, 0x36, 0xd1, 0x27, 0xca,
 | 
			
		||||
		0x0b, 0x64, 0x28, 0x82, 0xc3, 0x3a, 0xd3, 0x24,
 | 
			
		||||
		0x30, 0x77, 0x8f, 0xef, 0xf1, 0x3a, 0x9f, 0xa7,
 | 
			
		||||
		0x41, 0x49, 0x62, 0x79, 0x9f, 0x65, 0x12, 0x4f,
 | 
			
		||||
		0x29, 0xe6, 0x04, 0x83, 0xab, 0x5c, 0xbc, 0x0b,
 | 
			
		||||
		0xe2, 0x82, 0xcc, 0x3c, 0x47, 0x7d, 0xaa, 0x6d,
 | 
			
		||||
		0x4c, 0x71, 0x6b, 0x24, 0x01, 0xd0, 0xf9, 0x88,
 | 
			
		||||
		0xcf, 0x88, 0xbe, 0xee, 0xe2, 0x77, 0x07, 0x18,
 | 
			
		||||
		0xf1,
 | 
			
		||||
	}}
 | 
			
		||||
 | 
			
		||||
	p, err := WithChaCha20Poly1305(env, key)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	msg := []byte("Gophers, gophers, gophers everywhere!")
 | 
			
		||||
	v, err := p.GetSecret("test")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	if !bytes.Equal(v, msg) {
 | 
			
		||||
		t.Errorf("expected:\n%s\n\ngot:\n%s", hex.Dump(msg), hex.Dump(v))
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestWithChaCha20Poly1305X(t *testing.T) {
 | 
			
		||||
	key := []byte{
 | 
			
		||||
		0x25, 0x20, 0xda, 0x7c, 0x97, 0x60, 0x20, 0xf5,
 | 
			
		||||
		0x09, 0xa7, 0x42, 0x31, 0x08, 0x50, 0x76, 0xf6,
 | 
			
		||||
		0x79, 0xc5, 0x38, 0x5b, 0xfb, 0xf4, 0x98, 0x56,
 | 
			
		||||
		0xa1, 0x92, 0xd1, 0xa0, 0x08, 0x3e, 0xf5, 0xfd,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	env := environment{"test": []byte{
 | 
			
		||||
		0x59, 0xaf, 0xc4, 0x65, 0x60, 0x3f, 0x02, 0xd3,
 | 
			
		||||
		0x87, 0xd3, 0x15, 0xac, 0xf3, 0x6b, 0x4a, 0x4f,
 | 
			
		||||
		0xe8, 0x40, 0xe5, 0x4d, 0x80, 0x30, 0x7b, 0x3d,
 | 
			
		||||
		0x8a, 0xf4, 0x18, 0x96, 0x89, 0x05, 0x4e, 0x31,
 | 
			
		||||
		0x44, 0x8c, 0xb2, 0x84, 0x9d, 0x25, 0xce, 0x3b,
 | 
			
		||||
		0xb4, 0x66, 0x36, 0x0f, 0xd2, 0xad, 0xb3, 0x78,
 | 
			
		||||
		0xf8, 0x02, 0x1e, 0x6c, 0xf9, 0x6c, 0x1f, 0x71,
 | 
			
		||||
		0x3c, 0x2b, 0x59, 0x6f, 0xc7, 0x92, 0x3b, 0x40,
 | 
			
		||||
		0x89, 0x13, 0x93, 0x6b, 0xa0, 0x35, 0x4e, 0x6f,
 | 
			
		||||
		0xd8, 0x31, 0x67, 0xee, 0xa2,
 | 
			
		||||
	}}
 | 
			
		||||
 | 
			
		||||
	p, err := WithChaCha20Poly1305X(env, key)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	msg := []byte("Gophers, gophers, gophers everywhere!")
 | 
			
		||||
	v, err := p.GetSecret("test")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	if !bytes.Equal(v, msg) {
 | 
			
		||||
		t.Errorf("expected:\n%s\n\ngot:\n%s", hex.Dump(msg), hex.Dump(v))
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										8
									
								
								doc.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								doc.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,8 @@
 | 
			
		||||
// Package secret implements secret storage providers.
 | 
			
		||||
//
 | 
			
		||||
// # Providers
 | 
			
		||||
//
 | 
			
		||||
// Providers of secrets accept a string as key and return a byte slice as result
 | 
			
		||||
// for the secret. It is up to the consumers of this package how to interpret
 | 
			
		||||
// the bytes. In most cases, the byte slice can be converted to a string.
 | 
			
		||||
package secret
 | 
			
		||||
							
								
								
									
										93
									
								
								env.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										93
									
								
								env.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,93 @@
 | 
			
		||||
package secret
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"bufio"
 | 
			
		||||
	"io"
 | 
			
		||||
	"os"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"syscall"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type environment map[string][]byte
 | 
			
		||||
 | 
			
		||||
// Environment provides environment variables as secrets.
 | 
			
		||||
func Environment() Provider {
 | 
			
		||||
	return EnvironmentPrefix("")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// EnvironmentPrefix provides environment variables with a prefix as secrets.
 | 
			
		||||
//
 | 
			
		||||
// The prefix is stripped from the final secret key name.
 | 
			
		||||
func EnvironmentPrefix(prefix string) Provider {
 | 
			
		||||
	env := make(environment)
 | 
			
		||||
	for _, line := range syscall.Environ() {
 | 
			
		||||
		if !strings.HasPrefix(line, prefix) {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		kv := strings.SplitN(line[len(prefix):], "=", 2)
 | 
			
		||||
		if len(kv) == 2 {
 | 
			
		||||
			env[kv[0]] = []byte(strings.TrimSpace(kv[1]))
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return env
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// EnvironmentFile reads a file containing key-value pairs.
 | 
			
		||||
//
 | 
			
		||||
// Line starting with `#` or `//` are ignored.
 | 
			
		||||
//
 | 
			
		||||
// Example:
 | 
			
		||||
//
 | 
			
		||||
//	key=value
 | 
			
		||||
//	# This comment is ignored
 | 
			
		||||
func EnvironmentFile(name string) (Provider, error) {
 | 
			
		||||
	f, err := os.Open(name)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	defer func() { _ = f.Close() }()
 | 
			
		||||
	return EnvironmentReader(f)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// EnvironmentReader reads key-value pairs separated by newlines.
 | 
			
		||||
//
 | 
			
		||||
// Line starting with `#` or `//` are ignored.
 | 
			
		||||
//
 | 
			
		||||
// Example:
 | 
			
		||||
//
 | 
			
		||||
//	key=value
 | 
			
		||||
//	# This comment is ignored
 | 
			
		||||
func EnvironmentReader(r io.Reader) (Provider, error) {
 | 
			
		||||
	s := bufio.NewScanner(r)
 | 
			
		||||
	s.Split(bufio.ScanLines)
 | 
			
		||||
 | 
			
		||||
	env := make(environment)
 | 
			
		||||
	for s.Scan() {
 | 
			
		||||
		if err := s.Err(); err != nil {
 | 
			
		||||
			if err == io.EOF {
 | 
			
		||||
				break
 | 
			
		||||
			}
 | 
			
		||||
			return nil, err
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		line := strings.TrimSpace(s.Text())
 | 
			
		||||
		if strings.HasPrefix(line, "#") || strings.HasPrefix(line, "//") {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		kv := strings.SplitN(line, "=", 2)
 | 
			
		||||
		if len(kv) == 2 {
 | 
			
		||||
			env[strings.TrimSpace(kv[0])] = []byte(strings.TrimSpace(kv[1]))
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return env, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (env environment) GetSecret(key string) (value []byte, err error) {
 | 
			
		||||
	var ok bool
 | 
			
		||||
	if value, ok = env[key]; ok {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	return nil, NotFound{key}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										60
									
								
								env_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										60
									
								
								env_test.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,60 @@
 | 
			
		||||
package secret
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"bytes"
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
	"testing"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestEnvironment(t *testing.T) {
 | 
			
		||||
	testProvider(t, Environment(),
 | 
			
		||||
		testProviderCase{
 | 
			
		||||
			Key:  "USER",
 | 
			
		||||
			Test: testNotEmpty,
 | 
			
		||||
		})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestEnvironmentPrefix(t *testing.T) {
 | 
			
		||||
	testProvider(t, EnvironmentPrefix("US"),
 | 
			
		||||
		testProviderCase{
 | 
			
		||||
			Key:  "ER",
 | 
			
		||||
			Test: testNotEmpty,
 | 
			
		||||
		})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var testEnvironmentFileCases = []testProviderCase{
 | 
			
		||||
	{
 | 
			
		||||
		Key:  "test",
 | 
			
		||||
		Test: testEqualString("case"),
 | 
			
		||||
	},
 | 
			
		||||
	{
 | 
			
		||||
		Key:  "spaces",
 | 
			
		||||
		Test: testEqualString("yeah"),
 | 
			
		||||
	},
 | 
			
		||||
	{
 | 
			
		||||
		Key: "ignored",
 | 
			
		||||
		Err: NotFound{"ignored"},
 | 
			
		||||
	},
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestEnvironmentFile(t *testing.T) {
 | 
			
		||||
	p, err := EnvironmentFile(filepath.Join("testdata", "env"))
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	testProvider(t, p, testEnvironmentFileCases...)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestEnvironmentReader(t *testing.T) {
 | 
			
		||||
	p, err := EnvironmentReader(bytes.NewBufferString(`
 | 
			
		||||
#ignored=true
 | 
			
		||||
test=case
 | 
			
		||||
   spaces = yeah
 | 
			
		||||
`))
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	testProvider(t, p, testEnvironmentFileCases...)
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										47
									
								
								go.mod
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								go.mod
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,47 @@
 | 
			
		||||
module git.maze.io/go/secret
 | 
			
		||||
 | 
			
		||||
go 1.24.0
 | 
			
		||||
 | 
			
		||||
require (
 | 
			
		||||
	github.com/aws/aws-sdk-go-v2/config v1.31.6
 | 
			
		||||
	github.com/aws/aws-sdk-go-v2/service/ssm v1.64.2
 | 
			
		||||
	github.com/hashicorp/vault/api v1.20.0
 | 
			
		||||
	golang.org/x/crypto v0.41.0
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
require (
 | 
			
		||||
	github.com/aws/aws-sdk-go-v2 v1.38.3 // indirect
 | 
			
		||||
	github.com/aws/aws-sdk-go-v2/credentials v1.18.10 // indirect
 | 
			
		||||
	github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.6 // indirect
 | 
			
		||||
	github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.6 // indirect
 | 
			
		||||
	github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.6 // indirect
 | 
			
		||||
	github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 // indirect
 | 
			
		||||
	github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.1 // indirect
 | 
			
		||||
	github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.6 // indirect
 | 
			
		||||
	github.com/aws/aws-sdk-go-v2/service/kms v1.45.1 // indirect
 | 
			
		||||
	github.com/aws/aws-sdk-go-v2/service/sso v1.29.1 // indirect
 | 
			
		||||
	github.com/aws/aws-sdk-go-v2/service/ssooidc v1.34.2 // indirect
 | 
			
		||||
	github.com/aws/aws-sdk-go-v2/service/sts v1.38.2 // indirect
 | 
			
		||||
	github.com/aws/smithy-go v1.23.0 // indirect
 | 
			
		||||
	github.com/cenkalti/backoff/v4 v4.3.0 // indirect
 | 
			
		||||
	github.com/danieljoos/wincred v1.2.2 // indirect
 | 
			
		||||
	github.com/go-jose/go-jose/v4 v4.0.5 // indirect
 | 
			
		||||
	github.com/godbus/dbus/v5 v5.1.0 // indirect
 | 
			
		||||
	github.com/hashicorp/errwrap v1.1.0 // indirect
 | 
			
		||||
	github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
 | 
			
		||||
	github.com/hashicorp/go-multierror v1.1.1 // indirect
 | 
			
		||||
	github.com/hashicorp/go-retryablehttp v0.7.7 // indirect
 | 
			
		||||
	github.com/hashicorp/go-rootcerts v1.0.2 // indirect
 | 
			
		||||
	github.com/hashicorp/go-secure-stdlib/parseutil v0.1.6 // indirect
 | 
			
		||||
	github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 // indirect
 | 
			
		||||
	github.com/hashicorp/go-sockaddr v1.0.2 // indirect
 | 
			
		||||
	github.com/hashicorp/hcl v1.0.1-vault-7 // indirect
 | 
			
		||||
	github.com/keybase/go-keychain v0.0.1 // indirect
 | 
			
		||||
	github.com/mitchellh/go-homedir v1.1.0 // indirect
 | 
			
		||||
	github.com/mitchellh/mapstructure v1.5.0 // indirect
 | 
			
		||||
	github.com/ryanuber/go-glob v1.0.0 // indirect
 | 
			
		||||
	golang.org/x/net v0.42.0 // indirect
 | 
			
		||||
	golang.org/x/sys v0.35.0 // indirect
 | 
			
		||||
	golang.org/x/text v0.28.0 // indirect
 | 
			
		||||
	golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1 // indirect
 | 
			
		||||
)
 | 
			
		||||
							
								
								
									
										115
									
								
								go.sum
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										115
									
								
								go.sum
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,115 @@
 | 
			
		||||
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
 | 
			
		||||
github.com/aws/aws-sdk-go-v2 v1.38.3 h1:B6cV4oxnMs45fql4yRH+/Po/YU+597zgWqvDpYMturk=
 | 
			
		||||
github.com/aws/aws-sdk-go-v2 v1.38.3/go.mod h1:sDioUELIUO9Znk23YVmIk86/9DOpkbyyVb1i/gUNFXY=
 | 
			
		||||
github.com/aws/aws-sdk-go-v2/config v1.31.6 h1:a1t8fXY4GT4xjyJExz4knbuoxSCacB5hT/WgtfPyLjo=
 | 
			
		||||
github.com/aws/aws-sdk-go-v2/config v1.31.6/go.mod h1:5ByscNi7R+ztvOGzeUaIu49vkMk2soq5NaH5PYe33MQ=
 | 
			
		||||
github.com/aws/aws-sdk-go-v2/credentials v1.18.10 h1:xdJnXCouCx8Y0NncgoptztUocIYLKeQxrCgN6x9sdhg=
 | 
			
		||||
github.com/aws/aws-sdk-go-v2/credentials v1.18.10/go.mod h1:7tQk08ntj914F/5i9jC4+2HQTAuJirq7m1vZVIhEkWs=
 | 
			
		||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.6 h1:wbjnrrMnKew78/juW7I2BtKQwa1qlf6EjQgS69uYY14=
 | 
			
		||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.6/go.mod h1:AtiqqNrDioJXuUgz3+3T0mBWN7Hro2n9wll2zRUc0ww=
 | 
			
		||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.6 h1:uF68eJA6+S9iVr9WgX1NaRGyQ/6MdIyc4JNUo6TN1FA=
 | 
			
		||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.6/go.mod h1:qlPeVZCGPiobx8wb1ft0GHT5l+dc6ldnwInDFaMvC7Y=
 | 
			
		||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.6 h1:pa1DEC6JoI0zduhZePp3zmhWvk/xxm4NB8Hy/Tlsgos=
 | 
			
		||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.6/go.mod h1:gxEjPebnhWGJoaDdtDkA0JX46VRg1wcTHYe63OfX5pE=
 | 
			
		||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 h1:bIqFDwgGXXN1Kpp99pDOdKMTTb5d2KyU5X/BZxjOkRo=
 | 
			
		||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3/go.mod h1:H5O/EsxDWyU+LP/V8i5sm8cxoZgc2fdNR9bxlOFrQTo=
 | 
			
		||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.1 h1:oegbebPEMA/1Jny7kvwejowCaHz1FWZAQ94WXFNCyTM=
 | 
			
		||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.1/go.mod h1:kemo5Myr9ac0U9JfSjMo9yHLtw+pECEHsFtJ9tqCEI8=
 | 
			
		||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.6 h1:LHS1YAIJXJ4K9zS+1d/xa9JAA9sL2QyXIQCQFQW/X08=
 | 
			
		||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.6/go.mod h1:c9PCiTEuh0wQID5/KqA32J+HAgZxN9tOGXKCiYJjTZI=
 | 
			
		||||
github.com/aws/aws-sdk-go-v2/service/kms v1.45.1 h1:NhkI4kfcZYmcIM34a+q9drh3aMG1BthkyziOr7sRTv4=
 | 
			
		||||
github.com/aws/aws-sdk-go-v2/service/kms v1.45.1/go.mod h1:elyXIFqx79eHvd0cRAzYDYHajeoJEygkBjJto4HJddc=
 | 
			
		||||
github.com/aws/aws-sdk-go-v2/service/ssm v1.64.2 h1:6P4W42RUTZixRG6TgfRB8KlsqNzHtvBhs6sTbkVPZvk=
 | 
			
		||||
github.com/aws/aws-sdk-go-v2/service/ssm v1.64.2/go.mod h1:wtxdacy3oO5sHO03uOtk8HMGfgo1gBHKwuJdYM220i0=
 | 
			
		||||
github.com/aws/aws-sdk-go-v2/service/sso v1.29.1 h1:8OLZnVJPvjnrxEwHFg9hVUof/P4sibH+Ea4KKuqAGSg=
 | 
			
		||||
github.com/aws/aws-sdk-go-v2/service/sso v1.29.1/go.mod h1:27M3BpVi0C02UiQh1w9nsBEit6pLhlaH3NHna6WUbDE=
 | 
			
		||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.34.2 h1:gKWSTnqudpo8dAxqBqZnDoDWCiEh/40FziUjr/mo6uA=
 | 
			
		||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.34.2/go.mod h1:x7+rkNmRoEN1U13A6JE2fXne9EWyJy54o3n6d4mGaXQ=
 | 
			
		||||
github.com/aws/aws-sdk-go-v2/service/sts v1.38.2 h1:YZPjhyaGzhDQEvsffDEcpycq49nl7fiGcfJTIo8BszI=
 | 
			
		||||
github.com/aws/aws-sdk-go-v2/service/sts v1.38.2/go.mod h1:2dIN8qhQfv37BdUYGgEC8Q3tteM3zFxTI1MLO2O3J3c=
 | 
			
		||||
github.com/aws/smithy-go v1.23.0 h1:8n6I3gXzWJB2DxBDnfxgBaSX6oe0d/t10qGz7OKqMCE=
 | 
			
		||||
github.com/aws/smithy-go v1.23.0/go.mod h1:t1ufH5HMublsJYulve2RKmHDC15xu1f26kHCp/HgceI=
 | 
			
		||||
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
 | 
			
		||||
github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=
 | 
			
		||||
github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
 | 
			
		||||
github.com/danieljoos/wincred v1.2.2 h1:774zMFJrqaeYCK2W57BgAem/MLi6mtSE47MB6BOJ0i0=
 | 
			
		||||
github.com/danieljoos/wincred v1.2.2/go.mod h1:w7w4Utbrz8lqeMbDAK0lkNJUv5sAOkFi7nd/ogr0Uh8=
 | 
			
		||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 | 
			
		||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
 | 
			
		||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 | 
			
		||||
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
 | 
			
		||||
github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=
 | 
			
		||||
github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=
 | 
			
		||||
github.com/go-jose/go-jose/v4 v4.0.5 h1:M6T8+mKZl/+fNNuFHvGIzDz7BTLQPIounk/b9dw3AaE=
 | 
			
		||||
github.com/go-jose/go-jose/v4 v4.0.5/go.mod h1:s3P1lRrkT8igV8D9OjyL4WRyHvjB6a4JSllnOrmmBOA=
 | 
			
		||||
github.com/go-test/deep v1.0.2 h1:onZX1rnHT3Wv6cqNgYyFOOlgVKJrksuCMCRvJStbMYw=
 | 
			
		||||
github.com/go-test/deep v1.0.2/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA=
 | 
			
		||||
github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk=
 | 
			
		||||
github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
 | 
			
		||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
 | 
			
		||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
 | 
			
		||||
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
 | 
			
		||||
github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
 | 
			
		||||
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
 | 
			
		||||
github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ=
 | 
			
		||||
github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48=
 | 
			
		||||
github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k=
 | 
			
		||||
github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M=
 | 
			
		||||
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
 | 
			
		||||
github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
 | 
			
		||||
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
 | 
			
		||||
github.com/hashicorp/go-retryablehttp v0.7.7 h1:C8hUCYzor8PIfXHa4UrZkU4VvK8o9ISHxT2Q8+VepXU=
 | 
			
		||||
github.com/hashicorp/go-retryablehttp v0.7.7/go.mod h1:pkQpWZeYWskR+D1tR2O5OcBFOxfA7DoAO6xtkuQnHTk=
 | 
			
		||||
github.com/hashicorp/go-rootcerts v1.0.2 h1:jzhAVGtqPKbwpyCPELlgNWhE1znq+qwJtW5Oi2viEzc=
 | 
			
		||||
github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8=
 | 
			
		||||
github.com/hashicorp/go-secure-stdlib/parseutil v0.1.6 h1:om4Al8Oy7kCm/B86rLCLah4Dt5Aa0Fr5rYBG60OzwHQ=
 | 
			
		||||
github.com/hashicorp/go-secure-stdlib/parseutil v0.1.6/go.mod h1:QmrqtbKuxxSWTN3ETMPuB+VtEiBJ/A9XhoYGv8E1uD8=
 | 
			
		||||
github.com/hashicorp/go-secure-stdlib/strutil v0.1.1/go.mod h1:gKOamz3EwoIoJq7mlMIRBpVTAUn8qPCrEclOKKWhD3U=
 | 
			
		||||
github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 h1:kes8mmyCpxJsI7FTwtzRqEy9CdjCtrXrXGuOpxEA7Ts=
 | 
			
		||||
github.com/hashicorp/go-secure-stdlib/strutil v0.1.2/go.mod h1:Gou2R9+il93BqX25LAKCLuM+y9U2T4hlwvT1yprcna4=
 | 
			
		||||
github.com/hashicorp/go-sockaddr v1.0.2 h1:ztczhD1jLxIRjVejw8gFomI1BQZOe2WoVOu0SyteCQc=
 | 
			
		||||
github.com/hashicorp/go-sockaddr v1.0.2/go.mod h1:rB4wwRAUzs07qva3c5SdrY/NEtAUjGlgmH/UkBUC97A=
 | 
			
		||||
github.com/hashicorp/hcl v1.0.1-vault-7 h1:ag5OxFVy3QYTFTJODRzTKVZ6xvdfLLCA1cy/Y6xGI0I=
 | 
			
		||||
github.com/hashicorp/hcl v1.0.1-vault-7/go.mod h1:XYhtn6ijBSAj6n4YqAaf7RBPS4I06AItNorpy+MoQNM=
 | 
			
		||||
github.com/hashicorp/vault/api v1.20.0 h1:KQMHElgudOsr+IbJgmbjHnCTxEpKs9LnozA1D3nozU4=
 | 
			
		||||
github.com/hashicorp/vault/api v1.20.0/go.mod h1:GZ4pcjfzoOWpkJ3ijHNpEoAxKEsBJnVljyTe3jM2Sms=
 | 
			
		||||
github.com/keybase/go-keychain v0.0.1 h1:way+bWYa6lDppZoZcgMbYsvC7GxljxrskdNInRtuthU=
 | 
			
		||||
github.com/keybase/go-keychain v0.0.1/go.mod h1:PdEILRW3i9D8JcdM+FmY6RwkHGnhHxXwkPPMeUgOK1k=
 | 
			
		||||
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
 | 
			
		||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
 | 
			
		||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
 | 
			
		||||
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
 | 
			
		||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
 | 
			
		||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
 | 
			
		||||
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
 | 
			
		||||
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
 | 
			
		||||
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
 | 
			
		||||
github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo=
 | 
			
		||||
github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
 | 
			
		||||
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
 | 
			
		||||
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
 | 
			
		||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 | 
			
		||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
 | 
			
		||||
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
 | 
			
		||||
github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
 | 
			
		||||
github.com/ryanuber/go-glob v1.0.0 h1:iQh3xXAumdQ+4Ufa5b25cRpC5TYKlno6hsv6Cb3pkBk=
 | 
			
		||||
github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc=
 | 
			
		||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
 | 
			
		||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
 | 
			
		||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
 | 
			
		||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
 | 
			
		||||
golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4=
 | 
			
		||||
golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc=
 | 
			
		||||
golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs=
 | 
			
		||||
golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8=
 | 
			
		||||
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 | 
			
		||||
golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
 | 
			
		||||
golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
 | 
			
		||||
golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng=
 | 
			
		||||
golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU=
 | 
			
		||||
golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1 h1:NusfzzA6yGQ+ua51ck7E3omNUX/JuqbFSaRGqU8CcLI=
 | 
			
		||||
golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 | 
			
		||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 | 
			
		||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 | 
			
		||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
 | 
			
		||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 | 
			
		||||
							
								
								
									
										5
									
								
								keyring.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								keyring.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,5 @@
 | 
			
		||||
package secret
 | 
			
		||||
 | 
			
		||||
func Keyring(service string) (Provider, error) {
 | 
			
		||||
	return keyringProvider(service)
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										22
									
								
								keyring_darwin.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								keyring_darwin.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,22 @@
 | 
			
		||||
package secret
 | 
			
		||||
 | 
			
		||||
import "github.com/keybase/go-keychain"
 | 
			
		||||
 | 
			
		||||
type keyring struct {
 | 
			
		||||
	service string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func keyringProvider(service string) (Provider, error) {
 | 
			
		||||
	return keyring{
 | 
			
		||||
		service: service,
 | 
			
		||||
	}, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (p keyring) GetSecret(key string) (value []byte, err error) {
 | 
			
		||||
	return keychain.GetGenericPassword(
 | 
			
		||||
		p.service, // service
 | 
			
		||||
		key,       // account
 | 
			
		||||
		"",        // label
 | 
			
		||||
		"",        // accessgroup
 | 
			
		||||
	)
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										40
									
								
								keyring_darwin_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								keyring_darwin_test.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,40 @@
 | 
			
		||||
package secret
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"testing"
 | 
			
		||||
 | 
			
		||||
	"github.com/keybase/go-keychain"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	testKeyringService = "io.maze.git.go.secret"
 | 
			
		||||
	testKeyringKey     = "test"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestKeyring(t *testing.T) {
 | 
			
		||||
	item := keychain.NewGenericPassword(testKeyringService, testKeyringKey, "", []byte(testKeyringKey), "")
 | 
			
		||||
	if err := keychain.AddItem(item); err != nil {
 | 
			
		||||
		t.Skip(err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	defer func() {
 | 
			
		||||
		if err := keychain.DeleteGenericPasswordItem(testKeyringService, testKeyringKey); err != nil {
 | 
			
		||||
			t.Error(err)
 | 
			
		||||
		}
 | 
			
		||||
	}()
 | 
			
		||||
 | 
			
		||||
	p, err := Keyring(testKeyringService)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	v, err := p.GetSecret(testKeyringKey)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	if string(v) != testKeyringKey {
 | 
			
		||||
		t.Errorf("expected %q, got %q", testKeyringKey, v)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										20
									
								
								keyring_stub.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								keyring_stub.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,20 @@
 | 
			
		||||
//go:build !darwin && !windows && !(dragonfly && cgo) && !(freebsd && cgo) && !linux && !netbsd && !openbsd
 | 
			
		||||
 | 
			
		||||
package secret
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"errors"
 | 
			
		||||
	"runtime"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var errKeyringNotSupported = errors.New("secret: keyring is not supported on " + runtime.GOOS)
 | 
			
		||||
 | 
			
		||||
type keyring struct{}
 | 
			
		||||
 | 
			
		||||
func keyringProvider(_ string) (Provider, error) {
 | 
			
		||||
	return nil, errKeyringNotSupported
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (keyring) GetSecret(_ string) (_ []byte, err error) {
 | 
			
		||||
	return nil, errKeyringNotSupported
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										177
									
								
								keyring_unix.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										177
									
								
								keyring_unix.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,177 @@
 | 
			
		||||
//go:build (dragonfly && cgo) || (freebsd && cgo) || linux || netbsd || openbsd
 | 
			
		||||
 | 
			
		||||
package secret
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
 | 
			
		||||
	"github.com/godbus/dbus/v5"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	dbusServiceName              = "org.freedesktop.secrets"
 | 
			
		||||
	dbusServicePath              = "/org/freedesktop/secrets"
 | 
			
		||||
	dbusServiceInterface         = "org.freedesktop.Secret.Service"
 | 
			
		||||
	dbusServiceClose             = dbusServiceInterface + ".Close"
 | 
			
		||||
	dbusServiceUnlock            = dbusServiceInterface + ".Unlock"
 | 
			
		||||
	dbusCollectionBasePath       = "/org/freedesktop/secrets/collection/"
 | 
			
		||||
	dbusCollectionLoginPath      = dbusCollectionBasePath + "login"
 | 
			
		||||
	dbusCollectionLoginAliasPath = "/org/freedesktop/secrets/aliases/default"
 | 
			
		||||
	dbusCollectionInterface      = "org.freedesktop.Secret.Collection"
 | 
			
		||||
	dbusCollectionGetSecret      = dbusCollectionInterface + ".GetSecret"
 | 
			
		||||
	dbusCollectionSearchItems    = dbusCollectionInterface + ".SearchItems"
 | 
			
		||||
	dbusPromptInterface          = "org.freedesktop.Secret.Prompt"
 | 
			
		||||
	dbusPrompt                   = dbusPromptInterface + ".Prompt"
 | 
			
		||||
	dbusPromptCompleted          = dbusPromptInterface + ".Completed"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type keyring struct {
 | 
			
		||||
	service string
 | 
			
		||||
	session *dbus.Conn
 | 
			
		||||
	object  dbus.BusObject
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type keyringSecret struct {
 | 
			
		||||
	Session     dbus.ObjectPath
 | 
			
		||||
	Parameters  []byte
 | 
			
		||||
	Value       []byte
 | 
			
		||||
	ContentType string `dbus:"content_type"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func keyringProvider(service string) (Provider, error) {
 | 
			
		||||
	session, err := dbus.SessionBus()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	return &keyring{
 | 
			
		||||
		service: service,
 | 
			
		||||
		session: session,
 | 
			
		||||
		object:  session.Object(dbusServiceName, dbusServicePath),
 | 
			
		||||
	}, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (p *keyring) Close() error {
 | 
			
		||||
	return p.object.Call(dbusServiceClose, 0).Err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (p *keyring) GetSecret(key string) (value []byte, err error) {
 | 
			
		||||
 | 
			
		||||
	collection := p.getLoginCollection()
 | 
			
		||||
	if err = p.unlock(collection.Path()); err != nil {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	query := map[string]string{
 | 
			
		||||
		"service":  p.service,
 | 
			
		||||
		"username": key,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var results []dbus.ObjectPath
 | 
			
		||||
	if results, err = p.search(collection, query); err != nil {
 | 
			
		||||
		return
 | 
			
		||||
	} else if len(results) == 0 {
 | 
			
		||||
		return nil, NotFound{Key: key}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var secret *keyringSecret
 | 
			
		||||
	if secret, err = p.getSecret(results[0]); err != nil {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	return secret.Value, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (p keyring) checkCollectionPath(here dbus.ObjectPath) bool {
 | 
			
		||||
	obj := p.session.Object(dbusServiceName, dbusServicePath)
 | 
			
		||||
	val, err := obj.GetProperty(dbusCollectionInterface)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return false
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	paths := val.Value().([]dbus.ObjectPath)
 | 
			
		||||
	for _, p := range paths {
 | 
			
		||||
		if p == here {
 | 
			
		||||
			return true
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (p keyring) getLoginCollection() dbus.BusObject {
 | 
			
		||||
	here := dbus.ObjectPath(dbusCollectionLoginPath)
 | 
			
		||||
	if !p.checkCollectionPath(here) {
 | 
			
		||||
		here = dbus.ObjectPath(dbusCollectionLoginAliasPath)
 | 
			
		||||
	}
 | 
			
		||||
	return p.session.Object(dbusServiceName, here)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (p *keyring) search(collection dbus.BusObject, query interface{}) (results []dbus.ObjectPath, err error) {
 | 
			
		||||
	err = collection.Call(dbusCollectionSearchItems, 0, query).Store(&results)
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (p *keyring) getSecret(here dbus.ObjectPath) (secret *keyringSecret, err error) {
 | 
			
		||||
	secret = new(keyringSecret)
 | 
			
		||||
	err = p.session.Object(dbusServiceName, here).Call(dbusCollectionGetSecret, 0).Store(secret)
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (p *keyring) unlock(here dbus.ObjectPath) (err error) {
 | 
			
		||||
	var (
 | 
			
		||||
		unlocked []dbus.ObjectPath
 | 
			
		||||
		prompt   dbus.ObjectPath
 | 
			
		||||
	)
 | 
			
		||||
	if err = p.object.Call(dbusServiceUnlock, 0, []dbus.ObjectPath{here}).Store(&unlocked, &prompt); err != nil {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var variant dbus.Variant
 | 
			
		||||
	if _, variant, err = p.handlePrompt(prompt); err != nil {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	collections := variant.Value()
 | 
			
		||||
	switch t := collections.(type) {
 | 
			
		||||
	case []dbus.ObjectPath:
 | 
			
		||||
		unlocked = append(unlocked, t...)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if len(unlocked) != 1 || (here != dbusCollectionLoginAliasPath && unlocked[0] != here) {
 | 
			
		||||
		return fmt.Errorf("secret: failed to unlock keyring collection %q", here)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (s *keyring) handlePrompt(prompt dbus.ObjectPath) (bool, dbus.Variant, error) {
 | 
			
		||||
	if prompt != dbus.ObjectPath("/") {
 | 
			
		||||
		err := s.session.AddMatchSignal(dbus.WithMatchObjectPath(prompt),
 | 
			
		||||
			dbus.WithMatchInterface(dbusPromptInterface),
 | 
			
		||||
		)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return false, dbus.MakeVariant(""), err
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		defer func(s *keyring, options ...dbus.MatchOption) {
 | 
			
		||||
			_ = s.session.RemoveMatchSignal(options...)
 | 
			
		||||
		}(s, dbus.WithMatchObjectPath(prompt), dbus.WithMatchInterface(dbusPromptInterface))
 | 
			
		||||
 | 
			
		||||
		promptSignal := make(chan *dbus.Signal, 1)
 | 
			
		||||
		s.session.Signal(promptSignal)
 | 
			
		||||
 | 
			
		||||
		err = s.session.Object(dbusServiceName, prompt).Call(dbusPrompt, 0, "").Err
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return false, dbus.MakeVariant(""), err
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		signal := <-promptSignal
 | 
			
		||||
		switch signal.Name {
 | 
			
		||||
		case dbusPromptCompleted:
 | 
			
		||||
			dismissed := signal.Body[0].(bool)
 | 
			
		||||
			result := signal.Body[1].(dbus.Variant)
 | 
			
		||||
			return dismissed, result, nil
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return false, dbus.MakeVariant(""), nil
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										104
									
								
								keyring_windows.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										104
									
								
								keyring_windows.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,104 @@
 | 
			
		||||
package secret
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"syscall"
 | 
			
		||||
	"unsafe"
 | 
			
		||||
 | 
			
		||||
	"golang.org/x/sys/windows"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var (
 | 
			
		||||
	modadvapi32          = windows.NewLazySystemDLL("advapi32.dll")
 | 
			
		||||
	procCredRead         = modadvapi32.NewProc("CredReadW")
 | 
			
		||||
	procCredFree winproc = modadvapi32.NewProc("CredFree")
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// Interface for syscall.Proc: helps testing
 | 
			
		||||
type winproc interface {
 | 
			
		||||
	Call(a ...uintptr) (r1, r2 uintptr, lastErr error)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type keyring struct {
 | 
			
		||||
	service string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func keyringProvider(service string) (Provider, error) {
 | 
			
		||||
	return keyring{service}, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (p keyring) GetSecret(key string) (value []byte, err error) {
 | 
			
		||||
	return sysCredRead(p.service+":"+key, sysCRED_TYPE_GENERIC)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// https://docs.microsoft.com/en-us/windows/desktop/api/wincred/ns-wincred-_credentialw
 | 
			
		||||
type sysCREDENTIAL struct {
 | 
			
		||||
	Flags              uint32
 | 
			
		||||
	Type               uint32
 | 
			
		||||
	TargetName         *uint16
 | 
			
		||||
	Comment            *uint16
 | 
			
		||||
	LastWritten        windows.Filetime
 | 
			
		||||
	CredentialBlobSize uint32
 | 
			
		||||
	CredentialBlob     uintptr
 | 
			
		||||
	Persist            uint32
 | 
			
		||||
	AttributeCount     uint32
 | 
			
		||||
	Attributes         uintptr
 | 
			
		||||
	TargetAlias        *uint16
 | 
			
		||||
	UserName           *uint16
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// https://docs.microsoft.com/en-us/windows/desktop/api/wincred/ns-wincred-_credentialw
 | 
			
		||||
type sysCRED_TYPE uint32
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	sysCRED_TYPE_GENERIC                 sysCRED_TYPE = 0x1
 | 
			
		||||
	sysCRED_TYPE_DOMAIN_PASSWORD         sysCRED_TYPE = 0x2
 | 
			
		||||
	sysCRED_TYPE_DOMAIN_CERTIFICATE      sysCRED_TYPE = 0x3
 | 
			
		||||
	sysCRED_TYPE_DOMAIN_VISIBLE_PASSWORD sysCRED_TYPE = 0x4
 | 
			
		||||
	sysCRED_TYPE_GENERIC_CERTIFICATE     sysCRED_TYPE = 0x5
 | 
			
		||||
	sysCRED_TYPE_DOMAIN_EXTENDED         sysCRED_TYPE = 0x6
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// https://docs.microsoft.com/en-us/windows/desktop/api/wincred/nf-wincred-credreadw
 | 
			
		||||
func sysCredRead(targetName string, typ sysCRED_TYPE) ([]byte, error) {
 | 
			
		||||
	var pcred *sysCREDENTIAL
 | 
			
		||||
	targetNamePtr, _ := windows.UTF16PtrFromString(targetName)
 | 
			
		||||
	ret, _, err := syscall.SyscallN(
 | 
			
		||||
		procCredRead.Addr(),
 | 
			
		||||
		uintptr(unsafe.Pointer(targetNamePtr)),
 | 
			
		||||
		uintptr(typ),
 | 
			
		||||
		0,
 | 
			
		||||
		uintptr(unsafe.Pointer(&pcred)),
 | 
			
		||||
	)
 | 
			
		||||
	if ret == 0 {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	defer procCredFree.Call(uintptr(unsafe.Pointer(pcred)))
 | 
			
		||||
 | 
			
		||||
	return goBytes(pcred.CredentialBlob, pcred.CredentialBlobSize), nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// goBytes copies the given C byte array to a Go byte array (see `C.GoBytes`).
 | 
			
		||||
// This function avoids having cgo as dependency.
 | 
			
		||||
func goBytes(src uintptr, len uint32) []byte {
 | 
			
		||||
	if src == uintptr(0) {
 | 
			
		||||
		return []byte{}
 | 
			
		||||
	}
 | 
			
		||||
	rv := make([]byte, len)
 | 
			
		||||
	copy(rv, *(*[]byte)(unsafe.Pointer(&struct {
 | 
			
		||||
		Data unsafe.Pointer
 | 
			
		||||
		Len  int
 | 
			
		||||
		Cap  int
 | 
			
		||||
	}{
 | 
			
		||||
		Data: unsafe.Pointer(src),
 | 
			
		||||
		Len:  int(len),
 | 
			
		||||
		Cap:  int(len),
 | 
			
		||||
	})))
 | 
			
		||||
	/*
 | 
			
		||||
		copy(rv, *(*[]byte)(unsafe.Pointer(&reflect.SliceHeader{
 | 
			
		||||
			Data: src,
 | 
			
		||||
			Len:  int(len),
 | 
			
		||||
			Cap:  int(len),
 | 
			
		||||
		})))
 | 
			
		||||
	*/
 | 
			
		||||
	return rv
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										57
									
								
								provider.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										57
									
								
								provider.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,57 @@
 | 
			
		||||
package secret
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"encoding/base64"
 | 
			
		||||
	"encoding/hex"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"strings"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// Provider for secrets.
 | 
			
		||||
type Provider interface {
 | 
			
		||||
	// GetSecret loads a secret by named key.
 | 
			
		||||
	GetSecret(key string) (value []byte, err error)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// AmbiguousKey is an error incdicating that the secret doesn't resolve to exactly one item.
 | 
			
		||||
type AmbiguousKey struct {
 | 
			
		||||
	Key string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (err AmbiguousKey) Error() string {
 | 
			
		||||
	return fmt.Sprintf("secret: ambigious secret key %q", err.Key)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NotFound is an error indicating the secret can not be found.
 | 
			
		||||
type NotFound struct {
 | 
			
		||||
	Key string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (err NotFound) Error() string {
 | 
			
		||||
	if err.Key == "" {
 | 
			
		||||
		return "secret: not found"
 | 
			
		||||
	}
 | 
			
		||||
	return fmt.Sprintf("secret: %q not found", err.Key)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ToBinary converts a string to []bytes.
 | 
			
		||||
//
 | 
			
		||||
// There are special prefixes for binary encoded formats:
 | 
			
		||||
//   - hex: for hexadecimal encoded strings
 | 
			
		||||
//   - b64: for base-64 encoded strings (raw encoding)
 | 
			
		||||
//
 | 
			
		||||
// If a special prefix is found, the appropriate codec will be used to decode to string to []byte,
 | 
			
		||||
// when there is an error decoding, it may result in an empty value.
 | 
			
		||||
//
 | 
			
		||||
// All other strings will be converted to []byte as-is.
 | 
			
		||||
func ToBinary(s string) (bytes []byte) {
 | 
			
		||||
	switch {
 | 
			
		||||
	case strings.HasPrefix(s, "hex:"):
 | 
			
		||||
		bytes, _ = hex.DecodeString(s[4:])
 | 
			
		||||
	case strings.HasPrefix(s, "b64:"):
 | 
			
		||||
		bytes, _ = base64.RawStdEncoding.DecodeString(s[4:])
 | 
			
		||||
	default:
 | 
			
		||||
		bytes = []byte(s)
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										65
									
								
								provider_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										65
									
								
								provider_test.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,65 @@
 | 
			
		||||
package secret
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"bytes"
 | 
			
		||||
	"errors"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"testing"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type testProviderCase struct {
 | 
			
		||||
	Name string
 | 
			
		||||
	Key  string
 | 
			
		||||
	Err  error
 | 
			
		||||
	Test func([]byte) error
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func testProvider(t *testing.T, p Provider, tests ...testProviderCase) {
 | 
			
		||||
	t.Helper()
 | 
			
		||||
 | 
			
		||||
	for _, test := range tests {
 | 
			
		||||
		if test.Name == "" {
 | 
			
		||||
			test.Name = test.Key
 | 
			
		||||
		}
 | 
			
		||||
		t.Run(test.Name, func(it *testing.T) {
 | 
			
		||||
			v, err := p.GetSecret(test.Key)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				if test.Err == nil {
 | 
			
		||||
					it.Fatalf("unexpected error %q in %T (%#+v)", err, p, p)
 | 
			
		||||
					return
 | 
			
		||||
				} else if !errors.Is(err, test.Err) {
 | 
			
		||||
					it.Fatalf("unexpected error %q, expected error %q", err, test.Err)
 | 
			
		||||
					return
 | 
			
		||||
				}
 | 
			
		||||
				return
 | 
			
		||||
			} else if test.Err != nil {
 | 
			
		||||
				it.Fatalf("expected error %q", test.Err)
 | 
			
		||||
				return
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			if err := test.Test(v); err != nil {
 | 
			
		||||
				it.Error(err)
 | 
			
		||||
			}
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func testNotEmpty(v []byte) error {
 | 
			
		||||
	if len(v) > 0 {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
	return fmt.Errorf("expected empty, got %q", v)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func testEqual(a []byte) func([]byte) error {
 | 
			
		||||
	return func(b []byte) error {
 | 
			
		||||
		if bytes.Equal(a, b) {
 | 
			
		||||
			return nil
 | 
			
		||||
		}
 | 
			
		||||
		return fmt.Errorf("expected %q, got %q", a, b)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func testEqualString(a string) func([]byte) error {
 | 
			
		||||
	return testEqual([]byte(a))
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										4
									
								
								testdata/env
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								testdata/env
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,4 @@
 | 
			
		||||
# This is a test case for the Environment provider
 | 
			
		||||
#ignored=true
 | 
			
		||||
test=case
 | 
			
		||||
   spaces = yeah
 | 
			
		||||
							
								
								
									
										89
									
								
								vault.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										89
									
								
								vault.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,89 @@
 | 
			
		||||
package secret
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	vault "github.com/hashicorp/vault/api"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type vaultProvider struct{}
 | 
			
		||||
 | 
			
		||||
// Vault provider uses Hashicorp Vault for secrets storage.
 | 
			
		||||
//
 | 
			
		||||
// The secretKey is used to
 | 
			
		||||
func Vault() Provider {
 | 
			
		||||
	return vaultProvider{}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (p vaultProvider) GetSecret(key string) (value []byte, err error) {
 | 
			
		||||
	var (
 | 
			
		||||
		config = vault.DefaultConfig()
 | 
			
		||||
		client *vault.Client
 | 
			
		||||
	)
 | 
			
		||||
	if client, err = vault.NewClient(config); err != nil {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var mounts map[string]*vault.MountOutput
 | 
			
		||||
	if mounts, err = client.Sys().ListMounts(); err != nil {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var (
 | 
			
		||||
		mount    *vault.MountOutput
 | 
			
		||||
		location string
 | 
			
		||||
	)
 | 
			
		||||
	if mount, location, err = p.split(key, mounts); err != nil {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	switch mount.Type {
 | 
			
		||||
	case "generic", "kv":
 | 
			
		||||
		// Supported
 | 
			
		||||
	default:
 | 
			
		||||
		return nil, fmt.Errorf("secret: mount type %q is not supported", mount.Type)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var secret *vault.Secret
 | 
			
		||||
	if secret, err = client.Logical().Read(location); err != nil {
 | 
			
		||||
		return
 | 
			
		||||
	} else if secret == nil || len(secret.Data) == 0 {
 | 
			
		||||
		return nil, NotFound{Key: key}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	data, ok := secret.Data["data"].(map[string]interface{})
 | 
			
		||||
	if !ok {
 | 
			
		||||
		return nil, NotFound{Key: key}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if len(data) == 1 {
 | 
			
		||||
		for _, item := range data {
 | 
			
		||||
			switch data := item.(type) {
 | 
			
		||||
			case []byte:
 | 
			
		||||
				return data, nil
 | 
			
		||||
			case string:
 | 
			
		||||
				return []byte(data), nil
 | 
			
		||||
			default:
 | 
			
		||||
				return nil, fmt.Errorf("secret: unexpected return type %T", data)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nil, AmbiguousKey{Key: key}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (p vaultProvider) split(key string, mounts map[string]*vault.MountOutput) (mount *vault.MountOutput, location string, err error) {
 | 
			
		||||
	location = strings.TrimPrefix(key, "/")
 | 
			
		||||
 | 
			
		||||
	var tree string
 | 
			
		||||
	for tree, mount = range mounts {
 | 
			
		||||
		if strings.HasPrefix(location, tree) {
 | 
			
		||||
			if mount.Type == "kv" && mount.Options["version"] == "2" {
 | 
			
		||||
				location = tree + "data/" + location[len(tree):]
 | 
			
		||||
			}
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return nil, "", fmt.Errorf("secret: no Vault mount found for secret path %q", key)
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										25
									
								
								vault_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								vault_test.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,25 @@
 | 
			
		||||
package secret
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"os"
 | 
			
		||||
	"testing"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestVault(t *testing.T) {
 | 
			
		||||
	testKey := os.Getenv("TEST_VAULT_KEY")
 | 
			
		||||
	if testKey == "" {
 | 
			
		||||
		t.Skip("TEST_VAULT_KEY not set, which should contain the path to a secret for testing")
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	testValue := os.Getenv("TEST_VAULT_VALUE")
 | 
			
		||||
	if testValue == "" {
 | 
			
		||||
		t.Skip("TEST_VAULT_VALUE not set, which should contain the secret value for testing")
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	p := Vault()
 | 
			
		||||
	testProvider(t, p, testProviderCase{
 | 
			
		||||
		Key:  testKey,
 | 
			
		||||
		Test: testEqualString(testValue),
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user