Last active
July 4, 2024 19:43
-
-
Save sandromello/1d6f6aa0209c63d5a118004d328c4f25 to your computer and use it in GitHub Desktop.
Hoop Target to Connection
This file contains hidden or 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
package main | |
import ( | |
"bytes" | |
"encoding/json" | |
"flag" | |
"fmt" | |
"math/rand" | |
"os" | |
"strings" | |
"text/template" | |
"time" | |
) | |
func PrintErrorAndExit(format string, args ...any) { | |
fmt.Println(fmt.Sprintf(format, args...)) | |
os.Exit(1) | |
} | |
var createConnTmpl = `hoop admin create connection {{ .name }} --agent {{ .tags }} \ | |
--type {{ .type }} \ | |
{{- range $key, $val := .plugins }} | |
--plugin '{{ $key }}{{ $val }}' \ | |
{{- end }} | |
{{- range $key, $val := .secret }} | |
--env {{ $key }}={{ $val }} \ | |
{{- end }} | |
--overwrite {{- with .command }} -- {{ . }} {{- end -}} | |
` | |
func parseTemplate(tgt *RunopsTarget) string { | |
cmdInfo, err := parseEnvVarByType(tgt) | |
if err != nil { | |
PrintErrorAndExit(err.Error()) | |
} | |
cmdType := cmdInfo.Type | |
if cmdInfo.Subtype != "" { | |
cmdType = strings.Join([]string{cmdInfo.Type, cmdInfo.Subtype}, "/") | |
} | |
connectionBody := map[string]any{ | |
"name": tgt.Name, | |
"type": cmdType, | |
"secret": cmdInfo.EnvVars, | |
"tags": tgt.Tags, | |
} | |
plugins := map[string]string{"audit": ""} | |
if tgt.getSlackChannel() != "" { | |
plugins["slack:"] = tgt.getSlackChannel() | |
} | |
if len(tgt.Groups) > 0 { | |
plugins["access_control:"] = strings.Join(tgt.Groups, ";") | |
} | |
if tgt.enableReview() && tgt.ReviewGroups != "" { | |
plugins["review:"] = strings.ReplaceAll(tgt.ReviewGroups, ",", ";") | |
} | |
if tgt.enableDLP() { | |
plugins["dlp"] = "" | |
} | |
connectionBody["plugins"] = plugins | |
connectionBody["command"] = strings.Join(cmdInfo.CmdList, " ") | |
return execGoTemplate(createConnTmpl, connectionBody) | |
} | |
type ConnectionCmd struct { | |
Type string | |
Subtype string | |
CmdList []string | |
EnvVars map[string]string | |
} | |
func parseEnvVarByType(t *RunopsTarget) (*ConnectionCmd, error) { | |
cmd := &ConnectionCmd{Type: "database"} | |
// if t.CustomCommand != nil { | |
// return nil, fmt.Errorf("target has custom command [%v], not implemented, target=%v", *t.CustomCommand, t.Name) | |
// } | |
envVar := map[string]string{} | |
secretKeyFn := func(key string) string { | |
switch t.SecretProvider { | |
case "aws": | |
return fmt.Sprintf("_aws:%s:%s", t.SecretPath, t.secretKey(key)) | |
case "env-var": | |
return fmt.Sprintf("_envjson:%s:%s", t.SecretPath, t.secretKey(key)) | |
case "runops": | |
secretVal := t.secretKey(key) | |
if secretVal == "" { | |
PrintErrorAndExit("provider=runops - secret value is empty for %q, target=%v", key, t.Name) | |
} | |
return secretVal | |
case "": | |
// https://github.com/runopsio/agent/blob/213a2dab6de316a850a641f16836eecad0d74575/src/agent/secrets.clj#L46 | |
return "" | |
} | |
PrintErrorAndExit("secret provider %q not implemented", t.SecretProvider) | |
return "" | |
} | |
switch t.Type { | |
case "mysql", "mysql-csv": | |
cmd.Subtype = "mysql" | |
envVar["HOST"] = secretKeyFn("MYSQL_HOST") | |
envVar["USER"] = secretKeyFn("MYSQL_USER") | |
envVar["PASS"] = secretKeyFn("MYSQL_PASS") | |
envVar["DB"] = secretKeyFn("MYSQL_DB") | |
envVar["PORT"] = "3306" | |
case "postgres", "postgres-csv": | |
// TODO: add ssl options | |
cmd.Subtype = "postgres" | |
envVar["HOST"] = secretKeyFn("PG_HOST") | |
envVar["USER"] = secretKeyFn("PG_USER") | |
envVar["PASS"] = secretKeyFn("PG_PASS") | |
envVar["DB"] = secretKeyFn("PG_DB") | |
envVar["PORT"] = "5432" | |
case "sql-server": | |
cmd.Subtype = "mssql" | |
envVar["HOST"] = secretKeyFn("MSSQL_CONNECTION_URI") | |
envVar["USER"] = secretKeyFn("MSSQL_USER") | |
envVar["PASS"] = secretKeyFn("MSSQL_PASS") | |
envVar["PORT"] = "1433" | |
envVar["DB"] = secretKeyFn("MSSQL_DB") | |
case "mongodb": | |
cmd.Type = "custom" | |
envVar["MONGO_CONNECTION_URI"] = secretKeyFn("MONGO_CONNECTION_URI") | |
cmd.CmdList = []string{"mongo", "--quiet", "'$MONGO_CONNECTION_URI'"} | |
case "python": | |
cmd.Type = "custom" | |
cmd.CmdList = []string{"python3"} | |
case "node": | |
cmd.Type = "custom" | |
cmd.CmdList = []string{"node"} | |
case "k8s": | |
envVar["b64-filesystem:KUBECONFIG"] = secretKeyFn("KUBE_CONFIG_DATA") | |
cmd.Type = "custom" | |
cmd.CmdList = []string{"kubectl"} | |
case "bash": | |
cmd.Type = "custom" | |
cmd.CmdList = []string{"bash"} | |
if t.CustomCommand != nil { | |
cc := *t.CustomCommand | |
cc = strings.ReplaceAll(strings.ReplaceAll(cc, "[[", "'${"), "]]", "}'") | |
cmd.CmdList = strings.Split(cc, " ") | |
} | |
default: | |
return nil, fmt.Errorf("target %q not supported", t.Type) | |
} | |
cmd.EnvVars = envVar | |
return cmd, nil | |
} | |
type RunopsTarget struct { | |
Name string `json:"name"` | |
Status string `json:"status"` | |
Type string `json:"type"` | |
Tags string `json:"tags"` | |
SecretProvider string `json:"secret_provider"` | |
SecretPath string `json:"secret_path"` | |
Config map[string]string `json:"config"` | |
SecretMapping map[string]string `json:"secret_mapping"` | |
Redact string `json:"redact"` | |
ReviewType string `json:"review_type"` | |
ReviewGroups string `json:"reviewers"` | |
SlackChannel *string `json:"channel_name"` | |
Groups []string `json:"groups"` | |
// TODO | |
CustomCommand *string `json:"custom_command"` | |
secretMapping map[string]string | |
} | |
func (t *RunopsTarget) getSlackChannel() string { | |
if t.SlackChannel != nil { | |
return *t.SlackChannel | |
} | |
return "" | |
} | |
func (t *RunopsTarget) enableReview() bool { return t.ReviewType != "none" } | |
func (t *RunopsTarget) enableDLP() bool { return t.Redact == "all" } | |
func (t *RunopsTarget) agentName() string { | |
if t.Tags == "" { | |
return "main" | |
} | |
return t.Tags | |
} | |
// secretKey do a best effort to fetch the secret from the mapping, if it doesn't | |
// exits returns the same key | |
func (t *RunopsTarget) secretKey(key string) string { | |
if t.SecretProvider == "runops" { | |
return t.Config[key] | |
} | |
secretKeyVal, ok := t.secretMapping[key] | |
if !ok { | |
secretKeyVal = key | |
} | |
return secretKeyVal | |
} | |
func parseRunopsTargets(filePath string) []RunopsTarget { | |
targetJsonData, err := os.ReadFile(filePath) | |
if err != nil { | |
PrintErrorAndExit("failed fetching targets from file, reason=%v", err) | |
} | |
items := []RunopsTarget{} | |
if err := json.Unmarshal(targetJsonData, &items); err != nil { | |
PrintErrorAndExit("failed decoding targets, reason=%v", err) | |
} | |
return items | |
} | |
func execGoTemplate(tmpl string, data any) string { | |
t, err := template.New("").Parse(tmpl) | |
if err != nil { | |
PrintErrorAndExit("failed parsing template, err=%v", err) | |
} | |
buf := bytes.NewBufferString("") | |
if err := t.Execute(buf, data); err != nil { | |
PrintErrorAndExit("failed executing template: %v", err) | |
} | |
return buf.String() | |
} | |
func main() { | |
hosts := []string{"host01:27017", "host02:27017", "host03:27017"} | |
for i := 0; i < 3; i++ { | |
randS := rand.NewSource(time.Now().Unix()) | |
index := rand.New(randS).Intn(len(hosts)) | |
fmt.Println("GOT", hosts[index]) | |
} | |
os.Exit(0) | |
var statusFlag string | |
flag.StringVar(&statusFlag, "status", "active", "parse depending on the status: active|inactive") | |
flag.Parse() | |
if len(flag.Args()) == 0 { | |
PrintErrorAndExit("missing targets file argument, e.g.: ./target-to-connection /tmp/targets.json") | |
} | |
args := flag.Args() | |
targetList := parseRunopsTargets(args[0]) | |
for _, tgt := range targetList { | |
if statusFlag != tgt.Status { | |
continue | |
} | |
tmpl := parseTemplate(&tgt) | |
fmt.Println(tmpl) | |
fmt.Println("") | |
} | |
} |
This file contains hidden or 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
[ | |
{ | |
"name": "myapp-dev", | |
"tags": "main", | |
"type": "postgres", | |
"config": null, | |
"groups": [ | |
"dev" | |
], | |
"redact": "all", | |
"status": "active", | |
"reviewers": null, | |
"review_type": "none", | |
"secret_path": "my-secret-path", | |
"channel_name": null, | |
"custom_command": null, | |
"secret_mapping": { | |
"PG_HOST": "host", | |
"PG_USER": "username", | |
"PG_PASS": "password", | |
"PG_DB": "dbname", | |
"PG_PORT": "port" | |
}, | |
"secret_provider": "aws" | |
} | |
] |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment