aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorXe Iaso <me@xeiaso.net>2024-05-17 07:20:23 -0500
committerXe Iaso <me@xeiaso.net>2024-05-17 07:28:19 -0500
commit7ce28bfc8ac4f306196bacbc4ff06671e8a58654 (patch)
treeac0b266643e83333257e9bf5c04df6e425b0445d
parentf22aa000db6e91f0fb59f4728bdbda0aa40f475d (diff)
downloadx-7ce28bfc8ac4f306196bacbc4ff06671e8a58654.tar.xz
x-7ce28bfc8ac4f306196bacbc4ff06671e8a58654.zip
internal: add package flagfolder to populate FlagSets with a secret mount
Signed-off-by: Xe Iaso <me@xeiaso.net>
-rw-r--r--.go.mod.sri2
-rw-r--r--flagfolder/flagfolder.go110
-rw-r--r--flagfolder/flagfolder_test.go44
-rw-r--r--flagfolder/testdata/FOO1
-rw-r--r--flagfolder/testdata/WHAT_IS_COMPUTER1
-rw-r--r--flagfolder/testdata/bar1
-rw-r--r--flagfolder/testdata/something_here1
-rw-r--r--flagfolder/testpod.yml31
-rw-r--r--flake.nix2
-rw-r--r--go.mod1
-rw-r--r--go.sum2
-rw-r--r--internal/internal.go5
12 files changed, 198 insertions, 3 deletions
diff --git a/.go.mod.sri b/.go.mod.sri
index 56dd64a..29942dc 100644
--- a/.go.mod.sri
+++ b/.go.mod.sri
@@ -1 +1 @@
-sha256-hbKmwwxDWQqwJpym2wFOTJCc5ByZTM7WBQM2l5yXuDw=
+sha256-YARBn+bGzZ3FNAfyMdsWFeRDiOKp9XShbtIIAFIchlM=
diff --git a/flagfolder/flagfolder.go b/flagfolder/flagfolder.go
new file mode 100644
index 0000000..cb0d249
--- /dev/null
+++ b/flagfolder/flagfolder.go
@@ -0,0 +1,110 @@
+// Package flagfolder parses a folder on the disk as if each file in it had the contents of a command line flag.
+//
+// This is mainly intended to be used with environments like Kubernetes where you have your secrets mounted as a filesystem.
+package flagfolder
+
+import (
+ "flag"
+ "fmt"
+ "log/slog"
+ "os"
+ "path/filepath"
+
+ "github.com/stoewer/go-strcase"
+)
+
+// ParseSet parses secrets in a single folder into the given *flag.FlagSet.
+//
+// By default this will attempt to correct for several styles of naming for files:
+//
+// * kebab-case (the default)
+// * SHOUTING-KEBAB-CASE
+// * snake_case
+// * SHOUTING_SNAKE_CASE
+// * camelCase
+// * HammerCase
+func ParseSet(secretLocation string, set *flag.FlagSet) error {
+ var (
+ data []byte
+ err error
+ )
+
+ set.VisitAll(func(f *flag.Flag) {
+ if err != nil {
+ return
+ }
+
+ for _, fname := range []string{
+ filepath.Join(secretLocation, f.Name),
+ filepath.Join(secretLocation, strcase.UpperKebabCase(f.Name)),
+ filepath.Join(secretLocation, strcase.LowerCamelCase(f.Name)),
+ filepath.Join(secretLocation, strcase.UpperCamelCase(f.Name)),
+ filepath.Join(secretLocation, strcase.SnakeCase(f.Name)),
+ filepath.Join(secretLocation, strcase.UpperSnakeCase(f.Name)),
+ } {
+ data, err = os.ReadFile(fname)
+ if err != nil {
+ slog.Debug("can't read", "fname", fname, "err", err)
+ if os.IsNotExist(err) {
+ err = nil
+ continue
+ }
+ continue
+ }
+ }
+
+ if ferr := f.Value.Set(string(data)); ferr != nil {
+ err = fmt.Errorf("flagfolder: failed to set flag %q with value %q", f.Name, string(data))
+ }
+ })
+
+ return err
+}
+
+// Parse parses all files in every folder under /run/secrets as if they were command-line flags.
+//
+// This is most useful when you are using environments like Kubernetes where the path of least resistance
+// is to mount your secrets as a filesystem. Mount all your secrets into the pod and then let it figure
+// itself out!
+//
+// To use this effectively, ensure that your Pods and Deployments mount secrets as volumes like this:
+//
+// volumes:
+// - name: secret-volume
+// secret:
+// secretName: shell
+// containers:
+// - name: shell
+// image: ubuntu:latest
+// volumeMounts:
+// - name: secret-volume
+// readOnly: true
+// mountPath: "/run/secrets/shell"
+//
+// By default this will attempt to correct for several styles of naming for files:
+//
+// * kebab-case (the default)
+// * SHOUTING-KEBAB-CASE
+// * snake_case
+// * SHOUTING_SNAKE_CASE
+// * camelCase
+// * HammerCase
+func Parse() {
+ stats, err := os.ReadDir("/run/secrets")
+ if err != nil {
+ slog.Debug("can't read from /run/secrets", "err", err)
+ return
+ }
+
+ for _, stat := range stats {
+ if !stat.IsDir() {
+ continue
+ }
+
+ loc := filepath.Join("/run/secrets", stat.Name())
+
+ if err := ParseSet(loc, flag.CommandLine); err != nil {
+ slog.Error("can't parse folder", "folder", loc, "err", err)
+ }
+ }
+}
diff --git a/flagfolder/flagfolder_test.go b/flagfolder/flagfolder_test.go
new file mode 100644
index 0000000..d85e023
--- /dev/null
+++ b/flagfolder/flagfolder_test.go
@@ -0,0 +1,44 @@
+package flagfolder
+
+import (
+ "flag"
+ "testing"
+)
+
+func TestFlagFolderSimple(t *testing.T) {
+ for _, cs := range []struct {
+ flagName string
+ wantValue string
+ }{
+ {
+ flagName: "foo",
+ wantValue: "foo",
+ },
+ {
+ flagName: "bar",
+ wantValue: "bar",
+ },
+ {
+ flagName: "something-here",
+ wantValue: "something here",
+ },
+ {
+ flagName: "what-is-computer",
+ wantValue: "what is computer",
+ },
+ } {
+ t.Run(cs.flagName, func(t *testing.T) {
+ fs := flag.NewFlagSet("flagfolder_test", flag.PanicOnError)
+
+ f := fs.String(cs.flagName, "fail", "help for "+cs.flagName)
+
+ if err := ParseSet("./testdata", fs); err != nil {
+ t.Errorf("can't parse ./testdata: %v", err)
+ }
+
+ if *f != cs.wantValue {
+ t.Errorf("wanted --%s to be %q, got: %q", cs.flagName, cs.wantValue, *f)
+ }
+ })
+ }
+}
diff --git a/flagfolder/testdata/FOO b/flagfolder/testdata/FOO
new file mode 100644
index 0000000..1910281
--- /dev/null
+++ b/flagfolder/testdata/FOO
@@ -0,0 +1 @@
+foo \ No newline at end of file
diff --git a/flagfolder/testdata/WHAT_IS_COMPUTER b/flagfolder/testdata/WHAT_IS_COMPUTER
new file mode 100644
index 0000000..aae2638
--- /dev/null
+++ b/flagfolder/testdata/WHAT_IS_COMPUTER
@@ -0,0 +1 @@
+what is computer \ No newline at end of file
diff --git a/flagfolder/testdata/bar b/flagfolder/testdata/bar
new file mode 100644
index 0000000..ba0e162
--- /dev/null
+++ b/flagfolder/testdata/bar
@@ -0,0 +1 @@
+bar \ No newline at end of file
diff --git a/flagfolder/testdata/something_here b/flagfolder/testdata/something_here
new file mode 100644
index 0000000..709c317
--- /dev/null
+++ b/flagfolder/testdata/something_here
@@ -0,0 +1 @@
+something here \ No newline at end of file
diff --git a/flagfolder/testpod.yml b/flagfolder/testpod.yml
new file mode 100644
index 0000000..392d629
--- /dev/null
+++ b/flagfolder/testpod.yml
@@ -0,0 +1,31 @@
+apiVersion: v1
+kind: Secret
+metadata:
+ name: shell
+ namespace: default
+data:
+ foo: Zm9v
+ bar: YmFy
+---
+apiVersion: v1
+kind: Pod
+metadata:
+ name: shell
+ namespace: default
+spec:
+ volumes:
+ - name: secret-volume
+ secret:
+ secretName: shell
+ containers:
+ - name: shell
+ image: ubuntu:latest
+ command:
+ - sleep
+ - "infinity"
+ imagePullPolicy: IfNotPresent
+ volumeMounts:
+ - name: secret-volume
+ readOnly: true
+ mountPath: "/run/secrets/shell"
+ restartPolicy: Always
diff --git a/flake.nix b/flake.nix
index 7260811..998fa0a 100644
--- a/flake.nix
+++ b/flake.nix
@@ -1,4 +1,4 @@
-# nix-direnv cache busting line: sha256-hbKmwwxDWQqwJpym2wFOTJCc5ByZTM7WBQM2l5yXuDw=
+# nix-direnv cache busting line: sha256-YARBn+bGzZ3FNAfyMdsWFeRDiOKp9XShbtIIAFIchlM=
{
description = "/x/perimental code";
diff --git a/go.mod b/go.mod
index f4011b7..5af8cca 100644
--- a/go.mod
+++ b/go.mod
@@ -45,6 +45,7 @@ require (
github.com/rogpeppe/go-internal v1.12.0
github.com/rs/cors v1.11.0
github.com/sebest/xff v0.0.0-20210106013422-671bd2870b3a
+ github.com/stoewer/go-strcase v1.3.0
github.com/tetratelabs/wazero v1.7.0
github.com/thoj/go-ircevent v0.0.0-20210723090443-73e444401d64
github.com/tmc/scp v0.0.0-20170824174625-f7b48647feef
diff --git a/go.sum b/go.sum
index fa517ec..6eb395f 100644
--- a/go.sum
+++ b/go.sum
@@ -748,6 +748,8 @@ github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0=
github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf h1:pvbZ0lM0XWPBqUKqFU8cmavspvIl9nulOYwdy6IFRRo=
github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf/go.mod h1:RJID2RhlZKId02nZ62WenDCkgHFerpIOmW0iT7GKmXM=
+github.com/stoewer/go-strcase v1.3.0 h1:g0eASXYtp+yvN9fK8sH94oCIk0fau9uV1/ZdJ0AVEzs=
+github.com/stoewer/go-strcase v1.3.0/go.mod h1:fAH5hQ5pehh+j3nZfvwdk2RgEgQjAoM8wodgtPmh1xo=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
diff --git a/internal/internal.go b/internal/internal.go
index b9c1962..671360f 100644
--- a/internal/internal.go
+++ b/internal/internal.go
@@ -10,6 +10,7 @@ import (
"github.com/posener/complete"
"go4.org/legal"
+ "within.website/x/flagfolder"
"within.website/x/internal/confyg/flagconfyg"
"within.website/x/internal/flagenv"
"within.website/x/internal/manpage"
@@ -48,14 +49,16 @@ func configFileLocation() string {
//
// - command line flags (to get -config)
// - environment variables
+// - any secrets mounted to /run/secrets
// - configuration file (if -config is set)
// - command line flags
//
// This is done this way to ensure that command line flags always are the deciding
-// factor as an escape hatch.
+// factor as an escape hatch, at the cost of potentially evaluating flags twice.
func HandleStartup() {
flag.Parse()
flagenv.Parse()
+ flagfolder.Parse()
ctx := context.Background()