|
|
@@ -7,72 +7,109 @@ import (
|
|
|
"os"
|
|
|
"path/filepath"
|
|
|
"strings"
|
|
|
+
|
|
|
+ "github.com/pkg/errors"
|
|
|
)
|
|
|
|
|
|
-// return codes:
|
|
|
-// 1: failed to read secret from env
|
|
|
-// 2: failed to parse hierarchical secret
|
|
|
-// 3: failed to write secret content into file
|
|
|
+type secret struct {
|
|
|
+ name string
|
|
|
+ keys []string
|
|
|
+}
|
|
|
+
|
|
|
+const secretsFolder = "/run/secrets"
|
|
|
+
|
|
|
func main() {
|
|
|
- for _, name := range os.Args[1:] {
|
|
|
- i := strings.Index(name, ":")
|
|
|
+ secrets := parseInput(os.Args[1:])
|
|
|
+
|
|
|
+ for _, secret := range secrets {
|
|
|
+ err := createSecretFiles(secret, secretsFolder)
|
|
|
+ if err != nil {
|
|
|
+ fmt.Fprintf(os.Stderr, err.Error())
|
|
|
+ os.Exit(1)
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+func createSecretFiles(secret secret, path string) error {
|
|
|
+ value, ok := os.LookupEnv(secret.name)
|
|
|
+ if !ok {
|
|
|
+ return fmt.Errorf("%q variable not set", secret.name)
|
|
|
+ }
|
|
|
+
|
|
|
+ secrets := filepath.Join(path, secret.name)
|
|
|
+
|
|
|
+ if len(secret.keys) == 0 {
|
|
|
+ // raw secret
|
|
|
+ fmt.Printf("inject secret %q info %s\n", secret.name, secrets)
|
|
|
+ return ioutil.WriteFile(secrets, []byte(value), 0444)
|
|
|
+ }
|
|
|
+
|
|
|
+ var unmarshalled interface{}
|
|
|
+ err := json.Unmarshal([]byte(value), &unmarshalled)
|
|
|
+ if err != nil {
|
|
|
+ return errors.Wrapf(err, "%q secret is not a valid JSON document", secret.name)
|
|
|
+ }
|
|
|
+
|
|
|
+ dict, ok := unmarshalled.(map[string]interface{})
|
|
|
+ if !ok {
|
|
|
+ return errors.Wrapf(err, "%q secret is not a JSON dictionary", secret.name)
|
|
|
+ }
|
|
|
+ err = os.MkdirAll(secrets, 0755)
|
|
|
+ if err != nil {
|
|
|
+ return err
|
|
|
+ }
|
|
|
+
|
|
|
+ if contains(secret.keys, "*") {
|
|
|
var keys []string
|
|
|
- if i > 0 {
|
|
|
- keys = strings.Split(name[i+1:], ",")
|
|
|
- name = name[:i]
|
|
|
+ for k := range dict {
|
|
|
+ keys = append(keys, k)
|
|
|
}
|
|
|
- value, ok := os.LookupEnv(name)
|
|
|
+ secret.keys = keys
|
|
|
+ }
|
|
|
+
|
|
|
+ for _, k := range secret.keys {
|
|
|
+ path := filepath.Join(secrets, k)
|
|
|
+ fmt.Printf("inject secret %q info %s\n", k, path)
|
|
|
+
|
|
|
+ v, ok := dict[k]
|
|
|
if !ok {
|
|
|
- fmt.Fprintf(os.Stderr, "%q variable not set", name)
|
|
|
- os.Exit(1)
|
|
|
+ return fmt.Errorf("%q secret has no %q key", secret.name, k)
|
|
|
}
|
|
|
|
|
|
- secrets := filepath.Join("/run/secrets", name)
|
|
|
-
|
|
|
- if len(keys) == 0 {
|
|
|
- // raw secret
|
|
|
- fmt.Printf("inject secret %q info %s\n", name, secrets)
|
|
|
- err := ioutil.WriteFile(secrets, []byte(value), 0444)
|
|
|
+ var raw []byte
|
|
|
+ if s, ok := v.(string); ok {
|
|
|
+ raw = []byte(s)
|
|
|
+ } else {
|
|
|
+ raw, err = json.Marshal(v)
|
|
|
if err != nil {
|
|
|
- fmt.Fprintf(os.Stderr, err.Error())
|
|
|
- os.Exit(3)
|
|
|
+ return err
|
|
|
}
|
|
|
- os.Exit(0)
|
|
|
}
|
|
|
|
|
|
- var unmarshalled interface{}
|
|
|
- err := json.Unmarshal([]byte(value), &unmarshalled)
|
|
|
- if err == nil {
|
|
|
- if dict, ok := unmarshalled.(map[string]interface{}); ok {
|
|
|
- os.MkdirAll(secrets, 0555)
|
|
|
- for k, v := range dict {
|
|
|
- if !contains(keys, k) && !contains(keys, "*") {
|
|
|
- continue
|
|
|
- }
|
|
|
- path := filepath.Join(secrets, k)
|
|
|
- fmt.Printf("inject secret %q info %s\n", k, path)
|
|
|
-
|
|
|
- var raw []byte
|
|
|
- if s, ok := v.(string); ok {
|
|
|
- raw = []byte(s)
|
|
|
- } else {
|
|
|
- raw, err = json.Marshal(v)
|
|
|
- if err != nil {
|
|
|
- fmt.Fprintf(os.Stderr, err.Error())
|
|
|
- os.Exit(2)
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- err = ioutil.WriteFile(path, raw, 0444)
|
|
|
- if err != nil {
|
|
|
- fmt.Fprintf(os.Stderr, err.Error())
|
|
|
- os.Exit(3)
|
|
|
- }
|
|
|
- }
|
|
|
- os.Exit(0)
|
|
|
- }
|
|
|
+ err = ioutil.WriteFile(path, raw, 0444)
|
|
|
+ if err != nil {
|
|
|
+ return err
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return nil
|
|
|
+}
|
|
|
+
|
|
|
+// parseInput parse secret to be dumped into secret files with syntax `VARIABLE_NAME[:COMA_SEPARATED_KEYS]`
|
|
|
+func parseInput(input []string) []secret {
|
|
|
+ var secrets []secret
|
|
|
+ for _, name := range input {
|
|
|
+ i := strings.Index(name, ":")
|
|
|
+ var keys []string
|
|
|
+ if i > 0 {
|
|
|
+ keys = strings.Split(name[i+1:], ",")
|
|
|
+ name = name[:i]
|
|
|
}
|
|
|
+ secrets = append(secrets, secret{
|
|
|
+ name: name,
|
|
|
+ keys: keys,
|
|
|
+ })
|
|
|
}
|
|
|
+ return secrets
|
|
|
}
|
|
|
|
|
|
func contains(keys []string, s string) bool {
|