Skip to content

Commit

Permalink
Merge pull request #145 from rowi1de/main
Browse files Browse the repository at this point in the history
Validation functionality (#132)
fixes #127
  • Loading branch information
bakito committed May 13, 2023
2 parents 50eaa61 + 29cf555 commit 796709f
Show file tree
Hide file tree
Showing 16 changed files with 284 additions and 58 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/e2e.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ jobs:
# continue on error to show logs
continue-on-error: true
working-directory: testdata/e2e
run: ./runTests.sh
run: ./runTests.sh "skip-validate"

- name: 🔐 Print logs Cert-URL
run: |
Expand Down
3 changes: 3 additions & 0 deletions .github/workflows/publish.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ on:
types:
- published

permissions:
packages: write

jobs:
build-images:
runs-on: ubuntu-latest
Expand Down
41 changes: 10 additions & 31 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
- **Decode:** Base64 decodes each key in the `data` field in a secret.
- **Secrets:** Returns a list of all Sealed Secrets in all namespaces. With a click on the Sealed Secret the decrypted Kubernetes secret is loaded.
- **Seal:** Encrypt a Kubernetes secret and creates the Sealed Secret.
- **Validate:** Validate a Sealed Secret.

## Installation

Expand Down Expand Up @@ -89,42 +90,20 @@ curl -request POST 'https://<SEALED_SECRETS_WEB_BASE_URL>/api/raw' \
--data '{ "name": "mysecretname", "namespace": "mysecretnamespace", "value": "value to seal" }'
```

### Validate sealed secret
> **_NOTE:_** Validate is only available when using cluster internal api (e.g. certURL not set) see [bitnami-labs/sealed-secrets](https://github.com/bitnami-labs/sealed-secrets/issues/1208)
```bash
curl --request POST 'https://<SEALED_SECRETS_WEB_BASE_URL>/api/validate' \
--header 'Accept: application/x-yaml' \
--data-binary '@stringData.yaml'
```

## Development

For development, we are using a local Kubernetes cluster using kind. When the cluster is created we install **Sealed Secrets** using Helm:

```sh
# install registry
docker run -d --restart=always -p "127.0.0.1:5001:5000" --name kind-registry registry:2

# startup kind
curl -L https://raw.githubusercontent.com/bakito/kind-with-registry-action/main/kind-config.yaml -o testdata/e2e/kind-config.yaml
kind create cluster --config=testdata/e2e/kind-config.yaml

# setup registry
docker network connect kind kind-registry
kubectl apply -f https://raw.githubusercontent.com/bakito/kind-with-registry-action/main/configmap-registry.yaml

# setup ingress
kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/main/deploy/static/provider/kind/deploy.yaml
kubectl wait --namespace ingress-nginx \
--for=condition=ready pod \
--selector=app.kubernetes.io/component=controller \
--timeout=90s

# build image
./testdata/e2e/buildImage.sh

# install sealed secrets
helm repo add sealed-secrets https://bitnami-labs.github.io/sealed-secrets
helm install sealed-secrets sealed-secrets/sealed-secrets \
--namespace sealed-secrets \
--create-namespace \
--atomic

# install sealed secrets web
./testdata/e2e/installSealedSecretsWebChart.sh yaml

./run_local.sh
```

Access the interface via http://localhost/ssw
9 changes: 8 additions & 1 deletion chart/templates/NOTES.txt
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,13 @@ sealedSecrets:
# -- Name of the sealed secrets service
serviceName: sealed-secrets
# -- URL sealed secrets certificate (required if sealed secrets is not reachable with in cluster service)
certURL: ""
certURL: "" # this will disable validate api
---------------------------------------------
{{- end }}

{{- if ne .Values.sealedSecrets.certURL "" }}
*************************************
** ATTENTION!! **
*************************************
- Using sealedSecrets.certURL will disable the validate functionality, as it is only available via cluster internal api
{{- end }}
10 changes: 6 additions & 4 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ func setupRouter(coreClient corev1.CoreV1Interface, ssClient ssClient.BitnamiV1a
api.GET("/certificate", h.Certificate)
api.POST("/kubeseal", h.KubeSeal)
api.POST("/dencode", h.Dencode)
api.POST("/validate", h.Validate)

api.GET("/secret/:namespace/:name", sHandler.Secret)
api.GET("/secrets", sHandler.AllSecrets)
Expand All @@ -111,10 +112,11 @@ func renderIndexHTML(cfg *config.Config) (string, error) {
}

data := map[string]interface{}{
"DisableLoadSecrets": cfg.DisableLoadSecrets,
"WebContext": cfg.Web.Context,
"InitialSecret": initialSecret,
"Version": version.Version,
"DisableLoadSecrets": cfg.DisableLoadSecrets,
"DisableValidateSecrets": cfg.SealedSecrets.CertURL != "",
"WebContext": cfg.Web.Context,
"InitialSecret": initialSecret,
"Version": version.Version,
}

var tpl bytes.Buffer
Expand Down
14 changes: 7 additions & 7 deletions pkg/config/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,14 +36,14 @@ func parse(f *flags) (*Config, error) {

if *f.sealedSecretsCertURL != "" {
cfg.SealedSecrets.CertURL = *f.sealedSecretsCertURL
} else {
if *f.sealedSecretsServiceName != "" {
cfg.SealedSecrets.Service = *f.sealedSecretsServiceName
}
if *f.sealedSecretsServiceNamespace != "" {
cfg.SealedSecrets.Namespace = *f.sealedSecretsServiceNamespace
}
}
if *f.sealedSecretsServiceName != "" {
cfg.SealedSecrets.Service = *f.sealedSecretsServiceName
}
if *f.sealedSecretsServiceNamespace != "" {
cfg.SealedSecrets.Namespace = *f.sealedSecretsServiceNamespace
}

if *f.includeNamespaces != "" {
cfg.IncludeNamespaces = strings.Split(*f.includeNamespaces, " ")
}
Expand Down
4 changes: 2 additions & 2 deletions pkg/config/types_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,8 @@ var _ = Describe("Types", func() {
f.sealedSecretsCertURL = ptr("cert.url")
cfg, err = parse(f)
Ω(cfg.SealedSecrets.CertURL).Should(Equal("cert.url"))
Ω(cfg.SealedSecrets.Namespace).Should(BeEmpty())
Ω(cfg.SealedSecrets.Service).Should(BeEmpty())
Ω(cfg.SealedSecrets.Namespace).Should(Equal("sealed-secrets"))
Ω(cfg.SealedSecrets.Service).Should(Equal("sealed-secrets"))
})
It("should set the service namespace and name", func() {
f.sealedSecretsServiceName = ptr("name")
Expand Down
2 changes: 2 additions & 0 deletions pkg/handler/index.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,14 @@ type Handler struct {
sealer seal.Sealer
indexHTML string
filter *config.FieldFilter
cfg *config.Config
}

func New(indexHTML string, sealer seal.Sealer, cfg *config.Config) *Handler {
return &Handler{
sealer: sealer,
indexHTML: indexHTML,
cfg: cfg,
filter: cfg.FieldFilter,
}
}
Expand Down
25 changes: 25 additions & 0 deletions pkg/handler/validate.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package handler

import (
"fmt"
"log"
"net/http"

"github.com/gin-gonic/gin"
)

func (h *Handler) Validate(c *gin.Context) {
if h.cfg.SealedSecrets.CertURL != "" {
configError := fmt.Errorf("validate can't be used with CertURL (%s)", h.cfg.SealedSecrets.CertURL)
c.Data(http.StatusConflict, "text/plain", []byte(configError.Error()))
return
}
err := h.sealer.Validate(c, c.Request.Body)

if err != nil {
log.Printf("Error in %s: %v\n", Sanitize(c.Request.URL.Path), err)
c.Data(http.StatusBadRequest, "text/plain", []byte(err.Error()))
} else {
c.Data(http.StatusOK, "text/plain", []byte("OK"))
}
}
78 changes: 78 additions & 0 deletions pkg/handler/validate_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package handler

import (
"bytes"
"errors"
"net/http"
"net/http/httptest"

"github.com/bakito/sealed-secrets-web/pkg/config"
"github.com/bakito/sealed-secrets-web/pkg/mocks/seal"
"github.com/gin-gonic/gin"
"github.com/golang/mock/gomock"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
)

var _ = Describe("Handler ", func() {
Context("Validate", func() {
var (
recorder *httptest.ResponseRecorder
c *gin.Context
mock *gomock.Controller
sealer *seal.MockSealer
h *Handler
cfg *config.Config
)
BeforeEach(func() {
gin.SetMode(gin.ReleaseMode)
recorder = httptest.NewRecorder()
c, _ = gin.CreateTestContext(recorder)
mock = gomock.NewController(GinkgoT())
sealer = seal.NewMockSealer(mock)
cfg = &config.Config{}
h = &Handler{
sealer: sealer,
cfg: cfg,
}
})

It("should return success if validation succeeds", func() {
c.Request, _ = http.NewRequest("POST", "/v1/validate", bytes.NewReader([]byte(stringDataAsYAML)))
c.Request.Header.Set("Content-Type", "application/x-yaml")

sealer.EXPECT().Validate(gomock.Any(), gomock.Any()).Return(nil)

h.Validate(c)

Ω(recorder.Code).Should(Equal(http.StatusOK))
Ω(recorder.Body.String()).Should(Equal("OK"))
Ω(recorder.Header().Get("Content-Type")).Should(Equal("text/plain"))
})

It("should return an error if validation fails", func() {
c.Request, _ = http.NewRequest("POST", "/v1/validate", bytes.NewReader([]byte(stringDataAsYAML)))
c.Request.Header.Set("Content-Type", "application/x-yaml")

sealer.EXPECT().Validate(gomock.Any(), gomock.Any()).Return(errors.New("Validation failed"))

h.Validate(c)

Ω(recorder.Code).Should(Equal(http.StatusBadRequest))
Ω(recorder.Body.String()).Should(Equal("Validation failed"))
Ω(recorder.Header().Get("Content-Type")).Should(Equal("text/plain"))
})

It("should return an error if certURL is used", func() {
cfg.SealedSecrets.CertURL = "http://sealed-secrets/v1/cert.pem"
c.Request, _ = http.NewRequest("POST", "/v1/validate", bytes.NewReader([]byte(stringDataAsYAML)))
c.Request.Header.Set("Content-Type", "application/x-yaml")

h.Validate(c)

Ω(recorder.Code).Should(Equal(http.StatusConflict))
Ω(recorder.Body.String()).Should(Equal("validate can't be used with CertURL (http://sealed-secrets/v1/cert.pem)"))
Ω(recorder.Header().Get("Content-Type")).Should(Equal("text/plain"))
})
})
})
14 changes: 14 additions & 0 deletions pkg/mocks/seal/mock.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 11 additions & 0 deletions pkg/seal/seal.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ type Sealer interface {
Raw(data Raw) ([]byte, error)
Certificate(ctx context.Context) ([]byte, error)
Seal(outputFormat string, secret io.Reader) ([]byte, error)
Validate(ctx context.Context, secret io.Reader) error
}

var _ Sealer = &apiSealer{}
Expand Down Expand Up @@ -100,6 +101,16 @@ func (a *apiSealer) Raw(data Raw) ([]byte, error) {
return buf.Bytes(), nil
}

func (a *apiSealer) Validate(ctx context.Context, secret io.Reader) error {
return kubeseal.ValidateSealedSecret(
ctx,
a.clientConfig,
a.ss.Namespace,
a.ss.Service,
secret,
)
}

type Raw struct {
Value string `json:"value"`
Name string `json:"name"`
Expand Down
34 changes: 34 additions & 0 deletions run_local.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
#!/bin/sh

set -eo pipefail

# install registry
docker run -d --restart=always -p "127.0.0.1:5001:5000" --name kind-registry registry:2

# startup kind
curl -L https://raw.githubusercontent.com/bakito/kind-with-registry-action/main/kind-config.yaml -o testdata/e2e/kind-config.yaml
kind create cluster --config=testdata/e2e/kind-config.yaml

# setup registry
docker network connect kind kind-registry
kubectl apply -f https://raw.githubusercontent.com/bakito/kind-with-registry-action/main/configmap-registry.yaml

# setup ingress
kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/main/deploy/static/provider/kind/deploy.yaml
kubectl wait --namespace ingress-nginx \
--for=condition=ready pod \
--selector=app.kubernetes.io/component=controller \
--timeout=90s

# build image
./testdata/e2e/buildImage.sh

# install sealed secrets
helm repo add sealed-secrets https://bitnami-labs.github.io/sealed-secrets
helm install sealed-secrets sealed-secrets/sealed-secrets \
--namespace sealed-secrets \
--create-namespace \
--atomic

# install sealed secrets web
./testdata/e2e/installSealedSecretsWebChart.sh yaml
Loading

0 comments on commit 796709f

Please sign in to comment.