This repository has been archived by the owner on Dec 8, 2020. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #9 from puppetlabs/tasks/add-secret-value-encoding…
…-interface New: Add secrets/encoding package to standardize secret value encoding for storage
- Loading branch information
Showing
5 changed files
with
294 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
/* | ||
Package transfer provides an interface for encoding and decoding values | ||
for storage. The utility in this package is transparent to the user | ||
and it is used to maintain byte integrity on values used in workflows. | ||
Basic use when encoding a value: | ||
encoder := transfer.Encoders[transfer.DefaultEncodingType]() | ||
result, err := encoder.EncodeForTransfer([]byte("super secret token")) | ||
if err != nil { | ||
// handle error | ||
} | ||
Basic use when decoding a value: | ||
encodingType, value := transfer.ParseEncodedValue("base64:c3VwZXIgc2VjcmV0IHRva2Vu") | ||
encoder := transfer.Encoders[encoderType]() | ||
result, err := encoder.DecodeFromTransfer(value) | ||
if err != nil { | ||
// handle error | ||
} | ||
*/ | ||
package transfer |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,110 @@ | ||
package transfer | ||
|
||
import ( | ||
"encoding/base64" | ||
"fmt" | ||
"strings" | ||
) | ||
|
||
type encodingType string | ||
|
||
func (p encodingType) String() string { | ||
return string(p) | ||
} | ||
|
||
const ( | ||
Base64EncodingType encodingType = "base64" | ||
NoEncodingType encodingType = "" | ||
) | ||
|
||
// DefaultEncodingType is the default encodingType. This makes it easier to use this | ||
// package as the caller won't need to make any desisions around what encoder to use | ||
// unless they really need to. | ||
const DefaultEncodingType = Base64EncodingType | ||
|
||
// encodingTypeMap is an internal map used to get the encodingType type from a string | ||
var encodingTypeMap = map[string]encodingType{ | ||
"base64": Base64EncodingType, | ||
} | ||
|
||
// ParseEncodedValue will attempt to split on : and extract an encoding identifer | ||
// from the prefix of the string. It then returns the discovered encodingType and the | ||
// value without the encodingType prefixed. | ||
func ParseEncodedValue(value string) (encodingType, string) { | ||
parts := strings.SplitN(value, ":", 2) | ||
|
||
if len(parts) < 2 { | ||
return NoEncodingType, value | ||
} | ||
|
||
t, ok := encodingTypeMap[parts[0]] | ||
if !ok { | ||
return NoEncodingType, value | ||
} | ||
|
||
return t, parts[1] | ||
} | ||
|
||
// Encoders maps encoding algorithms to their respective EncodeDecoder types. | ||
// Example: | ||
// | ||
// ed := transfer.Encoders[Base64EncodingType]() | ||
// encodedValue, err := ed.EncodeForTransfer("my super secret value") | ||
var Encoders = map[encodingType]func() EncodeDecoder{ | ||
Base64EncodingType: func() EncodeDecoder { | ||
return Base64Encoding{} | ||
}, | ||
NoEncodingType: func() EncodeDecoder { | ||
return NoEncoding{} | ||
}, | ||
} | ||
|
||
// Base64Encoding handles the encoding and decoding of values using base64. | ||
// All encoded values will be prefixed with "base64:" | ||
type Base64Encoding struct{} | ||
|
||
// EncodeForTransfer takes a byte slice and returns it encoded as a base64 string. | ||
// No error is ever returned. | ||
func (e Base64Encoding) EncodeForTransfer(value []byte) (string, error) { | ||
s := base64.StdEncoding.EncodeToString(value) | ||
|
||
return fmt.Sprintf("%s:%s", Base64EncodingType, s), nil | ||
} | ||
|
||
// DecodeFromTransfer takes a string and attempts to decode using a base64 decoder. | ||
// If an error is returned, it will originate from the Go encoding/base64 package. | ||
func (e Base64Encoding) DecodeFromTransfer(value string) ([]byte, error) { | ||
return base64.StdEncoding.DecodeString(value) | ||
} | ||
|
||
// NoEncoding just returns the values without encoding them. This is used when there | ||
// is no encoding type algorithm prefix on the value. | ||
type NoEncoding struct{} | ||
|
||
// EncodeForTransfer takes a byte slice and casts it to a string. No error is ever | ||
// returned. | ||
func (e NoEncoding) EncodeForTransfer(value []byte) (string, error) { | ||
return string(value), nil | ||
} | ||
|
||
// DecodeFromTransfer takes a string and casts it to a byte slice. No error is ever | ||
// returned. | ||
func (e NoEncoding) DecodeFromTransfer(value string) ([]byte, error) { | ||
return []byte(value), nil | ||
} | ||
|
||
// EncodeForTransfer uses the DefaultEncodingType to encode value. | ||
func EncodeForTransfer(value []byte) (string, error) { | ||
encoder := Encoders[DefaultEncodingType]() | ||
|
||
return encoder.EncodeForTransfer(value) | ||
} | ||
|
||
// DecodeFromTransfer uses ParseEncodedValue to find the right encoder then | ||
// decodes value with it. | ||
func DecodeFromTransfer(value string) ([]byte, error) { | ||
t, val := ParseEncodedValue(value) | ||
encoder := Encoders[t]() | ||
|
||
return encoder.DecodeFromTransfer(val) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,133 @@ | ||
package transfer | ||
|
||
import ( | ||
"encoding/base64" | ||
"fmt" | ||
"testing" | ||
|
||
"github.com/stretchr/testify/require" | ||
) | ||
|
||
func TestEncoding(t *testing.T) { | ||
var cases = []struct { | ||
description string | ||
value string | ||
encodingType encodingType | ||
expected string | ||
customResultTest func(t *testing.T, encoded string, decoded []byte) | ||
}{ | ||
{ | ||
description: "base64 encoding succeeds", | ||
value: "super secret token", | ||
encodingType: Base64EncodingType, | ||
expected: "base64:c3VwZXIgc2VjcmV0IHRva2Vu", | ||
}, | ||
{ | ||
description: "no encoding succeeds", | ||
value: "super secret token", | ||
encodingType: NoEncodingType, | ||
expected: "super secret token", | ||
}, | ||
{ | ||
description: "base64 complex values don't loose integrity", | ||
value: "super: secret token:12:49:wheel", | ||
encodingType: Base64EncodingType, | ||
expected: "base64:c3VwZXI6IHNlY3JldCB0b2tlbjoxMjo0OTp3aGVlbA==", | ||
}, | ||
{ | ||
description: "no encoding complex values don't loose integrity", | ||
value: "super: secret token:12:49:wheel", | ||
encodingType: NoEncodingType, | ||
expected: "super: secret token:12:49:wheel", | ||
}, | ||
{ | ||
description: "begins with :", | ||
value: ":fun time at the park", | ||
encodingType: NoEncodingType, | ||
expected: ":fun time at the park", | ||
}, | ||
{ | ||
description: "user encoded base64", | ||
value: "c3VwZXIgc2VjcmV0IHRva2Vu", | ||
encodingType: NoEncodingType, | ||
expected: "c3VwZXIgc2VjcmV0IHRva2Vu", | ||
}, | ||
{ | ||
description: "user encoded base64 wrapped with our base64 encoder", | ||
// "super secret token" encoded as base64 | ||
value: "c3VwZXIgc2VjcmV0IHRva2Vu", | ||
encodingType: Base64EncodingType, | ||
expected: "base64:YzNWd1pYSWdjMlZqY21WMElIUnZhMlZ1", | ||
customResultTest: func(t *testing.T, encoded string, decoded []byte) { | ||
// tests that a user can encode their own values, have our system wrap it in our own | ||
// encoding, then when they try to unwrap their encoding, they get the expected value. | ||
result, err := base64.StdEncoding.DecodeString(string(decoded)) | ||
require.NoError(t, err) | ||
|
||
require.Equal(t, "super secret token", string(result)) | ||
}, | ||
}, | ||
} | ||
|
||
for _, c := range cases { | ||
t.Run(c.description, func(t *testing.T) { | ||
ed := Encoders[c.encodingType]() | ||
|
||
encoded, err := ed.EncodeForTransfer([]byte(c.value)) | ||
require.NoError(t, err) | ||
require.Equal(t, c.expected, encoded, fmt.Sprintf("encoding result was malformed: %s", encoded)) | ||
|
||
typ, value := ParseEncodedValue(encoded) | ||
require.Equal(t, c.encodingType, typ) | ||
|
||
newED := Encoders[typ]() | ||
|
||
var decoded []byte | ||
|
||
decoded, err = newED.DecodeFromTransfer(value) | ||
require.NoError(t, err) | ||
require.Equal(t, c.value, string(decoded)) | ||
|
||
if c.customResultTest != nil { | ||
t.Run("custom result test", func(t *testing.T) { | ||
c.customResultTest(t, encoded, decoded) | ||
}) | ||
} | ||
}) | ||
} | ||
} | ||
|
||
func TestHelperFuncs(t *testing.T) { | ||
var cases = []struct { | ||
description string | ||
value string | ||
expected string | ||
}{ | ||
{ | ||
description: "base64 encoding succeeds", | ||
value: "super secret token", | ||
expected: "base64:c3VwZXIgc2VjcmV0IHRva2Vu", | ||
}, | ||
{ | ||
description: "user encoded base64 wrapped with our base64 encoder", | ||
// "super secret token" encoded as base64 | ||
value: "c3VwZXIgc2VjcmV0IHRva2Vu", | ||
expected: "base64:YzNWd1pYSWdjMlZqY21WMElIUnZhMlZ1", | ||
}, | ||
} | ||
|
||
for _, c := range cases { | ||
t.Run(c.description, func(t *testing.T) { | ||
encoded, err := EncodeForTransfer([]byte(c.value)) | ||
require.NoError(t, err) | ||
|
||
require.Equal(t, c.expected, encoded) | ||
|
||
var decoded []byte | ||
|
||
decoded, err = DecodeFromTransfer(encoded) | ||
require.NoError(t, err) | ||
require.Equal(t, c.value, string(decoded)) | ||
}) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
package transfer | ||
|
||
// Encoder encodes a byte slice and returns a string with the encoding type prefixed | ||
type Encoder interface { | ||
EncodeForTransfer([]byte) (string, error) | ||
} | ||
|
||
// Decoder takes a string and decodes it, returning a byte slice or an error | ||
type Decoder interface { | ||
DecodeFromTransfer(string) ([]byte, error) | ||
} | ||
|
||
// EncodeDecoder groups Encoder and Decoder to form a type that can both encode and decode values. | ||
type EncodeDecoder interface { | ||
Encoder | ||
Decoder | ||
} |