Initial project snapshot
This commit is contained in:
@@ -0,0 +1,132 @@
|
||||
package secrets
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"slices"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrPlaintextResourceCredentials = errors.New("plaintext resource credentials are not allowed in metadata in production")
|
||||
ErrMissingResourceSecretRef = errors.New("secret_ref is required for this resource protocol in production")
|
||||
)
|
||||
|
||||
var credentialKeyFragments = []string{
|
||||
"accesstoken",
|
||||
"clientsecret",
|
||||
"credential",
|
||||
"credentials",
|
||||
"domain",
|
||||
"password",
|
||||
"privatekey",
|
||||
"refreshtoken",
|
||||
"secret",
|
||||
"secrets",
|
||||
"token",
|
||||
"user",
|
||||
"username",
|
||||
}
|
||||
|
||||
var safeReferenceKeys = []string{
|
||||
"certificateverificationmode",
|
||||
"renderqualityprofile",
|
||||
"secretref",
|
||||
"secretreference",
|
||||
"vaultref",
|
||||
}
|
||||
|
||||
func ValidateResourceSecretReadiness(protocol string, secretRef *string, metadata json.RawMessage, appEnv string) error {
|
||||
if !IsProductionEnv(appEnv) {
|
||||
return nil
|
||||
}
|
||||
|
||||
paths, err := PlaintextCredentialMetadataPaths(metadata)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(paths) > 0 {
|
||||
return fmt.Errorf("%w: %s", ErrPlaintextResourceCredentials, strings.Join(paths, ", "))
|
||||
}
|
||||
if ResourceProtocolRequiresSecretRef(protocol) && (secretRef == nil || strings.TrimSpace(*secretRef) == "") {
|
||||
return ErrMissingResourceSecretRef
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func IsProductionEnv(appEnv string) bool {
|
||||
switch strings.ToLower(strings.TrimSpace(appEnv)) {
|
||||
case "prod", "production":
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func ResourceProtocolRequiresSecretRef(protocol string) bool {
|
||||
switch strings.ToLower(strings.TrimSpace(protocol)) {
|
||||
case "rdp", "vnc", "ssh":
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func PlaintextCredentialMetadataPaths(raw json.RawMessage) ([]string, error) {
|
||||
if len(raw) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
var value any
|
||||
if err := json.Unmarshal(raw, &value); err != nil {
|
||||
return nil, errors.New("metadata must be valid json")
|
||||
}
|
||||
metadata, ok := value.(map[string]any)
|
||||
if !ok {
|
||||
return nil, errors.New("metadata must be a json object")
|
||||
}
|
||||
var paths []string
|
||||
collectCredentialPaths(metadata, "", &paths)
|
||||
sort.Strings(paths)
|
||||
return slices.Compact(paths), nil
|
||||
}
|
||||
|
||||
func collectCredentialPaths(value any, prefix string, paths *[]string) {
|
||||
switch typed := value.(type) {
|
||||
case map[string]any:
|
||||
for key, child := range typed {
|
||||
path := key
|
||||
if prefix != "" {
|
||||
path = prefix + "." + key
|
||||
}
|
||||
if isCredentialMetadataKey(key) {
|
||||
*paths = append(*paths, path)
|
||||
}
|
||||
collectCredentialPaths(child, path, paths)
|
||||
}
|
||||
case []any:
|
||||
for index, child := range typed {
|
||||
collectCredentialPaths(child, fmt.Sprintf("%s[%d]", prefix, index), paths)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func isCredentialMetadataKey(key string) bool {
|
||||
normalized := normalizeMetadataKey(key)
|
||||
if slices.Contains(safeReferenceKeys, normalized) {
|
||||
return false
|
||||
}
|
||||
for _, fragment := range credentialKeyFragments {
|
||||
if normalized == fragment || strings.HasSuffix(normalized, fragment) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func normalizeMetadataKey(key string) string {
|
||||
key = strings.ToLower(strings.TrimSpace(key))
|
||||
replacer := strings.NewReplacer("_", "", "-", "", " ", "", ".", "")
|
||||
return replacer.Replace(key)
|
||||
}
|
||||
Reference in New Issue
Block a user