-
-
Notifications
You must be signed in to change notification settings - Fork 604
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
9 changed files
with
524 additions
and
100 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
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
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,116 @@ | ||
package configtypes | ||
|
||
import ( | ||
"encoding/base64" | ||
"encoding/json" | ||
"encoding/pem" | ||
"errors" | ||
"fmt" | ||
"reflect" | ||
|
||
"github.com/go-viper/mapstructure/v2" | ||
) | ||
|
||
// PEMData represents a flexible PEM-encoded source. | ||
// The order sources checked is the following: | ||
// 1. Raw PEM content | ||
// 2. Base64 encoded PEM content | ||
// 3. Path to file with PEM content | ||
type PEMData string | ||
|
||
// String converts PEMData to a string. | ||
func (p PEMData) String() string { | ||
return string(p) | ||
} | ||
|
||
// MarshalJSON converts PEMData to JSON. | ||
func (p PEMData) MarshalJSON() ([]byte, error) { | ||
return json.Marshal(string(p)) | ||
} | ||
|
||
// UnmarshalJSON parses PEMData from JSON. | ||
func (p *PEMData) UnmarshalJSON(data []byte) error { | ||
var str string | ||
if err := json.Unmarshal(data, &str); err != nil { | ||
return err | ||
} | ||
*p = PEMData(str) | ||
return nil | ||
} | ||
|
||
// MarshalText converts PEMData to text for TOML. | ||
func (p PEMData) MarshalText() ([]byte, error) { | ||
return []byte(p.String()), nil | ||
} | ||
|
||
// UnmarshalText parses PEMData from text (used in TOML). | ||
func (p *PEMData) UnmarshalText(text []byte) error { | ||
*p = PEMData(text) | ||
return nil | ||
} | ||
|
||
// MarshalYAML converts PEMData to a YAML-compatible format. | ||
func (p PEMData) MarshalYAML() (interface{}, error) { | ||
return p.String(), nil | ||
} | ||
|
||
// UnmarshalYAML parses PEMData from YAML. | ||
func (p *PEMData) UnmarshalYAML(unmarshal func(interface{}) error) error { | ||
var str string | ||
if err := unmarshal(&str); err != nil { | ||
return err | ||
} | ||
*p = PEMData(str) | ||
return nil | ||
} | ||
|
||
// StringToPEMDataHookFunc for mapstructure to decode PEMData from strings. | ||
func StringToPEMDataHookFunc() mapstructure.DecodeHookFunc { | ||
return func( | ||
f reflect.Type, | ||
t reflect.Type, | ||
data interface{}, | ||
) (interface{}, error) { | ||
if f.Kind() != reflect.String { | ||
return data, nil | ||
} | ||
if t != reflect.TypeOf(PEMData("")) { | ||
return data, nil | ||
} | ||
|
||
return PEMData(data.(string)), nil | ||
} | ||
} | ||
|
||
// isValidPEM validates if the input string is a valid PEM block. | ||
func isValidPEM(pemData string) bool { | ||
// Decode the PEM data | ||
block, _ := pem.Decode([]byte(pemData)) | ||
return block != nil | ||
} | ||
|
||
// Load detects if PEMData is a file path, base64 string, or raw PEM string and loads the content. | ||
func (p PEMData) Load(statFile StatFileFunc, readFile ReadFileFunc) ([]byte, string, error) { | ||
value := string(p) | ||
if isValidPEM(value) { | ||
return []byte(value), "raw pem", nil | ||
} | ||
// Check if it's base64 encoded. | ||
if decodedValue, err := base64.StdEncoding.DecodeString(value); err == nil { | ||
if isValidPEM(string(decodedValue)) { | ||
return decodedValue, "base64 pem", nil | ||
} | ||
} | ||
// Check if it's a file path by verifying if the file exists. | ||
if _, err := statFile(value); err == nil { | ||
content, err := readFile(value) | ||
if err != nil { | ||
return nil, "", fmt.Errorf("error reading file: %w", err) | ||
} | ||
if !isValidPEM(string(content)) { | ||
return nil, "", fmt.Errorf("file \"%s\" contains invalid PEM data", value) | ||
} | ||
return content, "pem file path", nil | ||
} | ||
return nil, "", errors.New("invalid PEM data: not a valid file path, base64-encoded PEM content, or raw PEM content") | ||
} |
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,108 @@ | ||
package configtypes | ||
|
||
import ( | ||
"encoding/base64" | ||
"os" | ||
"reflect" | ||
"testing" | ||
|
||
"github.com/stretchr/testify/assert" | ||
|
||
"github.com/stretchr/testify/require" | ||
) | ||
|
||
func TestPEMData_Load(t *testing.T) { | ||
mockStatFile := func(name string) (os.FileInfo, error) { | ||
if name != "test-file" { | ||
return nil, os.ErrNotExist | ||
} | ||
return nil, nil | ||
} | ||
mockReadFile := func(name string) ([]byte, error) { | ||
if name != "test-file" { | ||
return nil, os.ErrNotExist | ||
} | ||
return []byte(testPEMCert), nil | ||
} | ||
|
||
tests := []struct { | ||
name string | ||
data PEMData | ||
expected string | ||
}{ | ||
{"File Path", PEMData("test-file"), testPEMCert}, | ||
{"Base64", PEMData(base64.StdEncoding.EncodeToString([]byte(testPEMCert))), testPEMCert}, | ||
{"Raw PEM", PEMData(testPEMCert), testPEMCert}, | ||
} | ||
|
||
for _, tt := range tests { | ||
t.Run(tt.name, func(t *testing.T) { | ||
data, _, err := tt.data.Load(mockStatFile, mockReadFile) | ||
require.NoError(t, err) | ||
require.Equal(t, tt.expected, string(data)) | ||
}) | ||
} | ||
} | ||
|
||
func TestStringToPEMDataHookFunc(t *testing.T) { | ||
hook := StringToPEMDataHookFunc().(func( | ||
f reflect.Type, | ||
t reflect.Type, | ||
data interface{}, | ||
) (interface{}, error)) | ||
result, err := hook(reflect.TypeOf(""), reflect.TypeOf(PEMData("")), "test-data") | ||
require.NoError(t, err) | ||
require.Equal(t, PEMData("test-data"), result) | ||
} | ||
|
||
const testPEMCert = ` | ||
-- GlobalSign Root R2, valid until Dec 15, 2021 | ||
-----BEGIN CERTIFICATE----- | ||
MIIDujCCAqKgAwIBAgILBAAAAAABD4Ym5g0wDQYJKoZIhvcNAQEFBQAwTDEgMB4G | ||
A1UECxMXR2xvYmFsU2lnbiBSb290IENBIC0gUjIxEzARBgNVBAoTCkdsb2JhbFNp | ||
Z24xEzARBgNVBAMTCkdsb2JhbFNpZ24wHhcNMDYxMjE1MDgwMDAwWhcNMjExMjE1 | ||
MDgwMDAwWjBMMSAwHgYDVQQLExdHbG9iYWxTaWduIFJvb3QgQ0EgLSBSMjETMBEG | ||
A1UEChMKR2xvYmFsU2lnbjETMBEGA1UEAxMKR2xvYmFsU2lnbjCCASIwDQYJKoZI | ||
hvcNAQEBBQADggEPADCCAQoCggEBAKbPJA6+Lm8omUVCxKs+IVSbC9N/hHD6ErPL | ||
v4dfxn+G07IwXNb9rfF73OX4YJYJkhD10FPe+3t+c4isUoh7SqbKSaZeqKeMWhG8 | ||
eoLrvozps6yWJQeXSpkqBy+0Hne/ig+1AnwblrjFuTosvNYSuetZfeLQBoZfXklq | ||
tTleiDTsvHgMCJiEbKjNS7SgfQx5TfC4LcshytVsW33hoCmEofnTlEnLJGKRILzd | ||
C9XZzPnqJworc5HGnRusyMvo4KD0L5CLTfuwNhv2GXqF4G3yYROIXJ/gkwpRl4pa | ||
zq+r1feqCapgvdzZX99yqWATXgAByUr6P6TqBwMhAo6CygPCm48CAwEAAaOBnDCB | ||
mTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUm+IH | ||
V2ccHsBqBt5ZtJot39wZhi4wNgYDVR0fBC8wLTAroCmgJ4YlaHR0cDovL2NybC5n | ||
bG9iYWxzaWduLm5ldC9yb290LXIyLmNybDAfBgNVHSMEGDAWgBSb4gdXZxwewGoG | ||
3lm0mi3f3BmGLjANBgkqhkiG9w0BAQUFAAOCAQEAmYFThxxol4aR7OBKuEQLq4Gs | ||
J0/WwbgcQ3izDJr86iw8bmEbTUsp9Z8FHSbBuOmDAGJFtqkIk7mpM0sYmsL4h4hO | ||
291xNBrBVNpGP+DTKqttVCL1OmLNIG+6KYnX3ZHu01yiPqFbQfXf5WRDLenVOavS | ||
ot+3i9DAgBkcRcAtjOj4LaR0VknFBbVPFd5uRHg5h6h+u/N5GJG79G+dwfCMNYxd | ||
AfvDbbnvRG15RjF+Cv6pgsH/76tuIMRQyV+dTZsXjAzlAcmgQWpzU/qlULRuJQ/7 | ||
TBj0/VLZjmmx6BEP3ojY+x1J96relc8geMJgEtslQIxq/H5COEBkEveegeGTLg== | ||
-----END CERTIFICATE-----` | ||
|
||
func Test_isValidPEM(t *testing.T) { | ||
tests := []struct { | ||
name string | ||
data string | ||
expected bool | ||
}{ | ||
{"Valid PEM", testPEMCert, true}, | ||
{"Invalid PEM", "/etc/passwd", false}, | ||
} | ||
for _, tt := range tests { | ||
t.Run(tt.name, func(t *testing.T) { | ||
result := isValidPEM(tt.data) | ||
require.Equal(t, tt.expected, result) | ||
}) | ||
} | ||
} | ||
|
||
const invalidPEM = "invalid PEM content" | ||
|
||
func TestPEMData_Load_Invalid(t *testing.T) { | ||
pem := PEMData(invalidPEM) | ||
|
||
_, _, err := pem.Load(mockStatFileSuccess, mockReadFileInvalid) | ||
require.Error(t, err) | ||
assert.Contains(t, err.Error(), "invalid PEM data") | ||
} |
Oops, something went wrong.