I am trying to create custom attribute tags for my go program. These tags will be used with fields, that will pull their values from vault, for e.g. Password string \
vault:“password”``. The functions described below should crawl through all the struct’s fields, including nested structs, and record all the tags in TagParser.ParsedTagMap, with pointer to the destination value. The ParseTags func will recieve ServerConfiguration struct, with some values filled in.
Custom struct is looking like this:
type ServerConfiguration struct {
Server1 EndpointConfiguration `yaml:"server1"`
Server2 EndpointConfiguration `yaml:"server2"`
}
type EndpointConfiguration struct {
Rest RestEndpoint `yaml:"rest"`
Login LoginCredentials `yaml:"login"`
}
type LoginCredentials struct {
Username string `vault:"keycloak_username"`
Password string `vault:"keycloak_password"`
}
The code that should do the parsing of custom structs:
type TagParser struct {
SourceStruct interface{}
ParsedTagMap map[string]interface{}
}
func ParseTags(tagName string, data interface{}) map[string]interface{} {
var tagParser TagParser
tagParser.ParsedTagMap = make(map[string]interface{})
tagParser.GetNestedTag(tagName, data)
slog.Debug("TagParser[" + tagName + "]: Parsed " + strconv.Itoa(len(tagParser.ParsedTagMap)) + " tags")
return tagParser.ParsedTagMap
}
func (tp *TagParser) GetNestedTag(tagName string, data interface{}) {
val := reflect.ValueOf(data)
if val.Kind() == reflect.Ptr {
val = val.Elem()
}
if val.Kind() != reflect.Struct {
return
}
for i := 0; i < val.NumField(); i++ {
field := val.Field(i)
tagValue := val.Type().Field(i).Tag.Get(tagName)
if tagValue != "" {
tp.ParsedTagMap[tagValue] = field.Interface()
}
if field.Kind() == reflect.Struct || (field.Kind() == reflect.Ptr && field.Elem().Kind() == reflect.Struct) {
tp.GetNestedTag(tagName, field.Interface())
}
}
}
After the fields are extracted, a separate function will call vault REST API, and retrieve their respective values (with value assertion);
// var secret *vault.KVSecret retrieved before
for key, value := range vaultSecretMapping {
secretValue, ok := secret.Data["data"].(map[string]interface{})[key].(string)
if !ok {
panic("Unable to parse " + key + " from vault")
}
switch p := value.(type) {
case *[]byte:
*p = []byte(secretValue)
case *string:
*p = secretValue
default: // Should never happen
panic("Unknown type for secret value")
}
}
I need the parsing function to be dynamic (not just strings), as i will have to use other data types in the future (sych as byte).
I tried fiddling around with go’s reflection library, without much luck. In TagParser.ParsedTagMap
, with current setup, i need to have something like: {"password": *interface{metadata:{type: string}, data: {*"pointer to the proper ServerConfiguration nested struct"}}}
I can achieve this result manually: TagParser.ParsedTagMap\["password"\]= &ServerConfiguration.Server1.LoginCredentials.Password
But using reflection obfuscates debugging, and go’s pass-by-value dereferences reflect.Value passing in-between the functions (i think). Calling field.Addr() yields panic.