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.
New: Add secrets/encoding package to standardize secret value encodin…
…g for storage To maintain secret value integrity, we need to encode the value bytes in a common encoding for storage. We also need to make it transparent to the user so we don't force them to encode their values before adding them to the secret store. This package also helps maintain backwards compatibility with the current system that doesn't have any consistent value encoding.
- Loading branch information
Showing
5 changed files
with
205 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 encoding provides an interface for encoding and decoding secret | ||
values for storage. The utility in this package is transparent to the user | ||
and it is used to maintain byte integrity on secret values used in workflows. | ||
Basic use when encoding a value: | ||
encoder := encoding.Encoders[encoding.DefaultEncodingType]() | ||
result, err := encoder.EncodeSecretValue([]byte("super secret token")) | ||
if err != nil { | ||
// handle error | ||
} | ||
Basic use when decoding a value: | ||
encodingType, value := encoding.ParseEncodedValue("base64:c3VwZXIgc2VjcmV0IHRva2Vu") | ||
encoder := encoding.Encoders[encoderType]() | ||
result, err := encoder.DecodeSecretValue(value) | ||
if err != nil { | ||
// handle error | ||
} | ||
*/ | ||
package encoding |
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,90 @@ | ||
package encoding | ||
|
||
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 | ||
|
||
var encodingTypeMap = map[string]encodingType{ | ||
"base64": Base64EncodingType, | ||
"": NoEncodingType, | ||
} | ||
|
||
// 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.Split(value, ":") | ||
|
||
t, ok := encodingTypeMap[parts[0]] | ||
if !ok { | ||
return NoEncodingType, value | ||
} | ||
|
||
return t, strings.Join(parts[1:], ":") | ||
} | ||
|
||
// Encoders maps encoding algorithms to their respective EncodeDecoder types. | ||
// Example: | ||
// | ||
// ed := encoding.Encoders[Base64EncodingType]() | ||
// encodedValue, err := ed.EncodeSecretValue("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 secret values using base64. | ||
// All encoded values will be prefixed with "base64:" | ||
type Base64Encoding struct{} | ||
|
||
// EncodeSecretValue takes a byte slice and returns it encoded as a base64 string. | ||
// No error is ever returned. | ||
func (e Base64Encoding) EncodeSecretValue(value []byte) (string, error) { | ||
s := base64.StdEncoding.EncodeToString(value) | ||
|
||
return fmt.Sprintf("%s:%s", Base64EncodingType, s), nil | ||
} | ||
|
||
// DecodeSecretValue 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) DecodeSecretValue(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{} | ||
|
||
// EncodeSecretValue takes a byte slice and casts it to a string. No error is ever | ||
// returned. | ||
func (e NoEncoding) EncodeSecretValue(value []byte) (string, error) { | ||
return string(value), nil | ||
} | ||
|
||
// DecodeSecretValue takes a string and casts it to a byte slice. No error is ever | ||
// returned. | ||
func (e NoEncoding) DecodeSecretValue(value string) ([]byte, error) { | ||
return []byte(value), nil | ||
} |
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,63 @@ | ||
package encoding | ||
|
||
import ( | ||
"fmt" | ||
"testing" | ||
|
||
"github.com/stretchr/testify/require" | ||
) | ||
|
||
func TestEncoding(t *testing.T) { | ||
var cases = []struct { | ||
description string | ||
value string | ||
encodingType encodingType | ||
expected string | ||
}{ | ||
{ | ||
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", | ||
}, | ||
} | ||
|
||
for _, c := range cases { | ||
t.Run(c.description, func(t *testing.T) { | ||
ed := Encoders[c.encodingType]() | ||
|
||
result, err := ed.EncodeSecretValue([]byte(c.value)) | ||
require.NoError(t, err) | ||
require.Equal(t, c.expected, result, fmt.Sprintf("result was malformed: %s", result)) | ||
|
||
typ, value := ParseEncodedValue(result) | ||
require.Equal(t, c.encodingType, typ) | ||
|
||
newED := Encoders[typ]() | ||
|
||
{ | ||
newResult, err := newED.DecodeSecretValue(value) | ||
require.NoError(t, err) | ||
require.Equal(t, c.value, string(newResult)) | ||
} | ||
}) | ||
} | ||
} |
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,18 @@ | ||
package encoding | ||
|
||
// Encoder encodes a byte slice and returns a string with the encoding type prefixed | ||
type Encoder interface { | ||
EncodeSecretValue([]byte) (string, error) | ||
} | ||
|
||
// Decoder takes a string and decodes it, returning a byte slice or an error | ||
type Decoder interface { | ||
DecodeSecretValue(string) ([]byte, error) | ||
} | ||
|
||
// EncodeDecoder groups Encoder and Decoder to form a type that can both encode and decode | ||
// secret values. | ||
type EncodeDecoder interface { | ||
Encoder | ||
Decoder | ||
} |