Skip to content

Commit

Permalink
add config file support and allow configurable filters
Browse files Browse the repository at this point in the history
  • Loading branch information
bakito committed Sep 15, 2021
1 parent 8215692 commit 96c46bd
Show file tree
Hide file tree
Showing 19 changed files with 355 additions and 149 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -120,3 +120,5 @@ bin
# idea
/.idea
/dist

gomock*
56 changes: 23 additions & 33 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,16 @@ import (
"bytes"
"embed"
_ "embed"
"flag"
"fmt"
"html/template"
"io/fs"
"io/ioutil"
"log"
"net/http"
"os"
"strings"

"github.com/bakito/sealed-secrets-web/pkg/config"
"github.com/bakito/sealed-secrets-web/pkg/handler"
"github.com/bakito/sealed-secrets-web/pkg/marshal"
"github.com/bakito/sealed-secrets-web/pkg/seal"
"github.com/bakito/sealed-secrets-web/pkg/secrets"
"github.com/bakito/sealed-secrets-web/pkg/version"
Expand All @@ -39,14 +37,6 @@ var (
staticFS, _ = fs.Sub(static, "static")

clientConfig clientcmd.ClientConfig

disableLoadSecrets = flag.Bool("disable-load-secrets", false, "Disable the loading of existing secrets")
kubesealArgs = flag.String("kubeseal-arguments", "", "Arguments which are passed to kubeseal")
outputFormat = flag.String("format", "json", "Output format for sealed secret. Either json or yaml")
initialSecretFile = flag.String("initial-secret-file", "", "Define a file with the initial secret to be displayed. If empty, defaults are used.")
webExternalUrl = flag.String("web-external-url", "", "The URL under which the Sealed Secrets Web Interface is externally reachable (for example, if it is served via a reverse proxy).")
printVersion = flag.Bool("version", false, "Print version information and exit")
port = flag.Int("port", 8080, "Define the port to run the application on. (default: 8080)")
)

func init() {
Expand All @@ -58,41 +48,45 @@ func init() {
overrides := clientcmd.ConfigOverrides{}
clientConfig = clientcmd.NewInteractiveDeferredLoadingClientConfig(loadingRules, &overrides, os.Stdin)
}

func main() {

flag.Parse()
cfg, err := config.Parse()
if err != nil {
log.Fatalf("Could not read the config: %s", err.Error())
}

if *printVersion {
if cfg.PrintVersion {
fmt.Println(version.Print("sealed secrets web"))
return
}

coreClient, ssClient, err := secrets.BuildClients(clientConfig, *disableLoadSecrets)
coreClient, ssClient, err := secrets.BuildClients(clientConfig, cfg.DisableLoadSecrets)
if err != nil {
log.Fatalf("Could build k8s clients:%v", err.Error())
}

log.Printf("Running sealed secrets web (%s) on port %d", version.Version, *port)
_ = setupRouter(coreClient, ssClient).Run(fmt.Sprintf(":%d", *port))
log.Printf("Running sealed secrets web (%s) on port %d", version.Version, cfg.Web.Port)
_ = setupRouter(coreClient, ssClient, cfg).Run(fmt.Sprintf(":%d", cfg.Web.Port))
}

func setupRouter(coreClient corev1.CoreV1Interface, ssClient ssClient.BitnamiV1alpha1Interface) *gin.Engine {
m := marshal.For(*outputFormat)
sealer := seal.New(*kubesealArgs)
func setupRouter(coreClient corev1.CoreV1Interface, ssClient ssClient.BitnamiV1alpha1Interface, cfg *config.Config) *gin.Engine {

sealer := seal.New(cfg.KubesealArgs)

indexHTML, err := renderIndexHTML(*outputFormat, *disableLoadSecrets, *webExternalUrl)
indexHTML, err := renderIndexHTML(cfg)
if err != nil {
log.Fatalf("Could not render the index html template: %s", err.Error())
}

sHandler, err := secrets.NewHandler(coreClient, ssClient, *outputFormat, *disableLoadSecrets)
sHandler, err := secrets.NewHandler(coreClient, ssClient, cfg)
if err != nil {
log.Fatalf("Could not initialize secrets handler: %s", err.Error())
}

r := gin.New()
r.Use(gin.Recovery())
h := handler.New(indexHTML, m, sealer)
h := handler.New(indexHTML, sealer, cfg)

r.GET("/", h.Index)
r.StaticFS("/static", http.FS(staticFS))
Expand All @@ -113,23 +107,19 @@ func setupRouter(coreClient corev1.CoreV1Interface, ssClient ssClient.BitnamiV1a
return r
}

func renderIndexHTML(outputFormat string, disableLoadSecrets bool, webExternalUrl string) (string, error) {
func renderIndexHTML(cfg *config.Config) (string, error) {
indexTmpl := template.Must(template.New("index.html").Parse(indexTemplate))
initialSecret := initialSecretJSON
if initialSecretFile != nil && strings.TrimSpace(*initialSecretFile) != "" {
b, err := ioutil.ReadFile(*initialSecretFile)
if err != nil {
return "", err
}
initialSecret = string(b)
} else if strings.EqualFold(outputFormat, "yaml") {
if cfg.InitialSecret != "" {
initialSecret = cfg.InitialSecret
} else if strings.EqualFold(cfg.OutputFormat, "yaml") {
initialSecret = initialSecretYAML
}

data := map[string]interface{}{
"OutputFormat": outputFormat,
"DisableLoadSecrets": disableLoadSecrets,
"WebExternalUrl": webExternalUrl,
"OutputFormat": cfg.OutputFormat,
"DisableLoadSecrets": cfg.DisableLoadSecrets,
"WebExternalUrl": cfg.Web.ExternalUrl,
"InitialSecret": initialSecret,
"Version": version.Version,
}
Expand Down
17 changes: 11 additions & 6 deletions main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import (
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"

"github.com/bakito/sealed-secrets-web/pkg/config"
"github.com/bakito/sealed-secrets-web/pkg/marshal"
"github.com/bakito/sealed-secrets-web/pkg/mocks/core"
"github.com/bakito/sealed-secrets-web/pkg/mocks/ssclient"
"github.com/bitnami-labs/sealed-secrets/pkg/apis/sealed-secrets/v1alpha1"
Expand All @@ -32,20 +34,24 @@ var _ = Describe("Main", func() {
ssClient *ssclient.MockSealedSecretInterface
coreClient *core.MockCoreV1Interface
secrets *core.MockSecretInterface
cfg *config.Config
)

BeforeEach(func() {
cfg = &config.Config{
OutputFormat: "yaml",
FieldFilter: &config.FieldFilter{},
Marshaller: marshal.For("yaml"),
}
name = uuid.NewString()
namespace = uuid.NewString()
w = httptest.NewRecorder()
format := "yaml"
outputFormat = &format
mock = gomock.NewController(GinkgoT())
alpha1Client = ssclient.NewMockBitnamiV1alpha1Interface(mock)
ssClient = ssclient.NewMockSealedSecretInterface(mock)
coreClient = core.NewMockCoreV1Interface(mock)
secrets = core.NewMockSecretInterface(mock)
router = setupRouter(coreClient, alpha1Client)
router = setupRouter(coreClient, alpha1Client, cfg)
})
It("return OK on health", func() {
req, _ := http.NewRequest("GET", "/_health", nil)
Expand Down Expand Up @@ -115,9 +121,8 @@ var _ = Describe("Main", func() {
})

It("secrets endpoints are disabled", func() {
disabled := true
disableLoadSecrets = &disabled
router = setupRouter(coreClient, alpha1Client)
cfg.DisableLoadSecrets = true
router = setupRouter(coreClient, alpha1Client, cfg)
req, _ := http.NewRequest("GET", "/api/secrets", nil)
router.ServeHTTP(w, req)
Ω(w.Code).Should(Equal(403))
Expand Down
13 changes: 13 additions & 0 deletions pkg/config/config_suite_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package config_test

import (
"testing"

. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)

func TestHandler(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "Config Suite")
}
30 changes: 30 additions & 0 deletions pkg/config/filter.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package config

import "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"

type FieldFilter struct {
Skip [][]string `yaml:"skip"`
SkipIfNil [][]string `yaml:"skipIfNil"`
}

func (ff *FieldFilter) Apply(sec map[string]interface{}) {
for _, fieldPath := range ff.Skip {
unstructured.RemoveNestedField(sec, fieldPath...)
}

for _, fieldPath := range ff.SkipIfNil {
removeFieldIfNull(sec, fieldPath...)
}
}

func removeFieldIfNull(sec map[string]interface{}, fields ...string) {
path := fields[:len(fields)-1]
name := fields[len(fields)-1]
if m, ok, _ := unstructured.NestedMap(sec, path...); ok {
f := m[name]
if f == nil {
delete(m, name)
_ = unstructured.SetNestedMap(sec, m, path...)
}
}
}
92 changes: 92 additions & 0 deletions pkg/config/filter_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
package config

import (
. "github.com/bakito/sealed-secrets-web/pkg/test"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)

var (
testConfigFile = "../../testdata/config.yaml"
)

var _ = Describe("Filter", func() {
Context("removeNullFields", func() {
var ff *FieldFilter
BeforeEach(func() {
cfg, err := Parse()
Ω(err).ShouldNot(HaveOccurred())
ff = cfg.FieldFilter
})
It("should remove nil fields", func() {
secretData := map[string]interface{}{
"spec": map[string]interface{}{
"template": map[string]interface{}{
"data": nil,
"metadata": map[string]interface{}{
"creationTimestamp": nil,
},
},
},
}

ff.Apply(secretData)

Ω(SubMap(secretData, "spec", "template")).ShouldNot(HaveKey("data"))
Ω(SubMap(secretData, "spec", "template", "metadata")).ShouldNot(HaveKey("creationTimestamp"))
})
It("should keep non nil fields", func() {
secretData := map[string]interface{}{
"metadata": map[string]interface{}{
"creationTimestamp": "00:00",
},
"spec": map[string]interface{}{
"template": map[string]interface{}{
"data": map[string]interface{}{},
"metadata": map[string]interface{}{
"creationTimestamp": "00:00",
},
},
},
}

ff.Apply(secretData)

Ω(secretData["metadata"]).Should(HaveKey("creationTimestamp"))
Ω(SubMap(secretData, "spec", "template")).Should(HaveKey("data"))
})
})
Context("removeRuntimeFields", func() {
var ff *FieldFilter
BeforeEach(func() {
config = &testConfigFile
cfg, err := Parse()
Ω(err).ShouldNot(HaveOccurred())
ff = cfg.FieldFilter
})
It("should remove the fields", func() {
secretData := map[string]interface{}{
"metadata": map[string]interface{}{
"creationTimestamp": "foo",
"managedFields": "foo",
"resourceVersion": "foo",
"selfLink": "foo",
"uid": "foo",
"annotations": map[string]interface{}{
"kubectl.kubernetes.io/last-applied-configuration": "foo",
"foo": "bar",
},
},
}

ff.Apply(secretData)
Ω(secretData["metadata"]).ShouldNot(HaveKey("creationTimestamp"))
Ω(secretData["metadata"]).ShouldNot(HaveKey("managedFields"))
Ω(secretData["metadata"]).ShouldNot(HaveKey("resourceVersion"))
Ω(secretData["metadata"]).ShouldNot(HaveKey("selfLink"))
Ω(secretData["metadata"]).ShouldNot(HaveKey("uid"))
Ω(SubMap(secretData, "metadata", "annotations")).ShouldNot(HaveKey("kubectl.kubernetes.io/last-applied-configuration"))
Ω(SubMap(secretData, "metadata", "annotations")).Should(HaveKey("foo"))
})
})
})
Loading

0 comments on commit 96c46bd

Please sign in to comment.