├── .dockerignore ├── .gitignore ├── Dockerfile ├── LICENSE ├── README.md ├── default.conf ├── docker-compose.yml ├── docs └── home-page.gif ├── frontend ├── error.svg ├── index.html └── webassembly.svg └── wasm ├── common └── secret.go ├── decoder └── secret.go ├── encoder └── secret.go ├── go.mod ├── go.sum └── main.go /.dockerignore: -------------------------------------------------------------------------------- 1 | kubernetes-secret.wasm 2 | wasm_exec.js 3 | frontend/*.js 4 | frontend/*.wasm -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # If you prefer the allow list template instead of the deny list, see community template: 2 | # https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore 3 | # 4 | # Binaries for programs and plugins 5 | *.exe 6 | *.exe~ 7 | *.dll 8 | *.so 9 | *.dylib 10 | 11 | # Test binary, built with `go test -c` 12 | *.test 13 | 14 | # Output of the go coverage tool, specifically when used with LiteIDE 15 | *.out 16 | 17 | # Dependency directories (remove the comment below to include it) 18 | # vendor/ 19 | 20 | # Go workspace file 21 | go.work 22 | 23 | kubernetes-secret.wasm 24 | wasm_exec.js 25 | *_test.go -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.20.4 AS builder 2 | 3 | ENV GOOS=js 4 | ENV GOARCH=wasm 5 | 6 | COPY wasm/ /go/src/github.com/michaelact/kubernetes-secret-editor/wasm/ 7 | 8 | RUN cd /go/src/github.com/michaelact/kubernetes-secret-editor/wasm/ \ 9 | && cp /usr/local/go/misc/wasm/wasm_exec.js wasm_exec.js \ 10 | && go build -o kubernetes-secret.wasm 11 | 12 | FROM nginx:1.25 AS runtime 13 | 14 | COPY --from=builder /go/src/github.com/michaelact/kubernetes-secret-editor/wasm/wasm_exec.js /usr/share/nginx/html/ 15 | COPY --from=builder /go/src/github.com/michaelact/kubernetes-secret-editor/wasm/kubernetes-secret.wasm /usr/share/nginx/html/ 16 | COPY frontend/* /usr/share/nginx/html/ 17 | 18 | COPY default.conf /etc/nginx/conf.d/default.conf 19 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 michaelact 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Kubernetes Secret Editor 🚀 2 | 3 | Explore it live on https://michael-act.github.io/kubernetes-secret-editor/ . 4 | 5 |

6 | 7 |

8 | 9 | Introducing Kubernetes Secret Editor, the effortless way to edit your Kubernetes secrets directly from your web browser! With the power of web assembly technology, your secret data stays secure, processed entirely on the client side. 10 | 11 | ## Run it Locally in 3 Simple Steps 12 | 13 | ### Prerequisites 14 | 15 | Make sure you have the following installed on your system: 16 | 17 | - [Rancher Desktop](https://rancherdesktop.io/) or [Docker](https://docs.docker.com/engine/install/) 18 | 19 | ### Setup 20 | 21 | 1. **Clone the Repository:** 22 | ```shell 23 | git clone https://github.com/michael-act/kubernetes-secret-editor/ 24 | cd kubernetes-secret-editor/ 25 | ``` 26 | 27 | 2. **Start the Application:** 28 | ```shell 29 | docker compose up -d 30 | ``` 31 | 32 | 3. Access http://localhost:8000 33 | -------------------------------------------------------------------------------- /default.conf: -------------------------------------------------------------------------------- 1 | server{ 2 | listen 8000; 3 | server_name _; 4 | server_tokens off; 5 | 6 | location / { 7 | root /usr/share/nginx/html; 8 | # Activate the next line if you want to list files 9 | # autoindex on; 10 | } 11 | } -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | services: 2 | frontend: 3 | build: 4 | context: . 5 | dockerfile: Dockerfile 6 | image: kubernetes-secret-editor 7 | restart: on-failure:5 8 | ports: 9 | - 127.0.0.1:8000:8000 10 | -------------------------------------------------------------------------------- /docs/home-page.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hateus/kubernetes-secret-editor/85fcc6300c3c96b22f52af5443054721953d5975/docs/home-page.gif -------------------------------------------------------------------------------- /frontend/error.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /frontend/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | Kubernetes Secret Editor 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 66 | 67 | 68 | 100 | 101 | 110 | 119 | 120 |
121 |
122 | 123 |

KUBERNETEST SECRET in YAML format

124 |

125 | 126 |
127 |
128 |
129 |
130 | 131 |

DECODED KUBERNETES SECRET in YAML format

132 |

133 | 134 |

powered by

135 | 136 |
137 |
138 |
139 |
140 | 143 | 144 | 145 | -------------------------------------------------------------------------------- /frontend/webassembly.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /wasm/common/secret.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import ( 4 | "gopkg.in/yaml.v3" 5 | ) 6 | 7 | const ( 8 | DataKey = "data" 9 | ) 10 | 11 | // Secret allows us to read and return the full Kubernetes secret 12 | type Secret map[string]interface{} 13 | 14 | // SecretData extracts out the data portion of a Kubernetes secret 15 | type SecretData struct { 16 | Data map[string]string `json:"data" yaml:"data"` 17 | } 18 | 19 | func GetStringSecret(s *Secret) (string, error) { 20 | secret, err := yaml.Marshal(s) 21 | if err != nil { 22 | return "", err 23 | } 24 | 25 | return string(secret), nil 26 | } 27 | 28 | func GetFullSecret(output []byte, sd *SecretData) (*Secret, error) { 29 | var secret Secret 30 | var err error 31 | 32 | err = yaml.Unmarshal(output, &secret) 33 | if err != nil { 34 | return nil, err 35 | } 36 | 37 | secret[DataKey] = sd.Data 38 | return &secret, nil 39 | } 40 | -------------------------------------------------------------------------------- /wasm/decoder/secret.go: -------------------------------------------------------------------------------- 1 | package decoder 2 | 3 | import ( 4 | "encoding/base64" 5 | "gopkg.in/yaml.v3" 6 | 7 | "github.com/michaelact/kubernetes-secret-editor/wasm/common" 8 | ) 9 | 10 | func GetSecretData(input []byte) (*common.SecretData, error) { 11 | var secretData common.SecretData 12 | var err error 13 | 14 | err = yaml.Unmarshal(input, &secretData) 15 | if err != nil { 16 | return nil, err 17 | } 18 | 19 | err = parseSecretData(&secretData) 20 | if err != nil { 21 | return nil, err 22 | } 23 | 24 | return &secretData, nil 25 | } 26 | 27 | func parseSecretData(s *common.SecretData) error { 28 | var err error 29 | for key, value := range s.Data { 30 | s.Data[key], err = decodeString(value) 31 | if err != nil { 32 | return err 33 | } 34 | } 35 | 36 | return nil 37 | } 38 | 39 | func decodeString(encoded string) (string, error) { 40 | decoded, err := base64.StdEncoding.DecodeString(encoded) 41 | if err != nil { 42 | return "", err 43 | } 44 | 45 | return string(decoded), nil 46 | } 47 | -------------------------------------------------------------------------------- /wasm/encoder/secret.go: -------------------------------------------------------------------------------- 1 | package encoder 2 | 3 | import ( 4 | "encoding/base64" 5 | "gopkg.in/yaml.v3" 6 | 7 | "github.com/michaelact/kubernetes-secret-editor/wasm/common" 8 | ) 9 | 10 | func GetSecretData(input []byte) (*common.SecretData, error) { 11 | var secretData common.SecretData 12 | var err error 13 | 14 | err = yaml.Unmarshal(input, &secretData) 15 | if err != nil { 16 | return nil, err 17 | } 18 | 19 | err = parseSecretData(&secretData) 20 | if err != nil { 21 | return nil, err 22 | } 23 | 24 | return &secretData, nil 25 | } 26 | 27 | func parseSecretData(s *common.SecretData) error { 28 | var err error 29 | for key, value := range s.Data { 30 | s.Data[key], err = encodeString(value) 31 | if err != nil { 32 | return err 33 | } 34 | } 35 | 36 | return nil 37 | } 38 | 39 | func encodeString(decoded string) (string, error) { 40 | encoded := base64.StdEncoding.EncodeToString([]byte(decoded)) 41 | return encoded, nil 42 | } 43 | -------------------------------------------------------------------------------- /wasm/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/michaelact/kubernetes-secret-editor/wasm 2 | 3 | go 1.20 4 | 5 | require gopkg.in/yaml.v3 v3.0.1 // indirect 6 | -------------------------------------------------------------------------------- /wasm/go.sum: -------------------------------------------------------------------------------- 1 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 2 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 3 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 4 | -------------------------------------------------------------------------------- /wasm/main.go: -------------------------------------------------------------------------------- 1 | //go:build wasm 2 | 3 | package main 4 | 5 | import ( 6 | "fmt" 7 | "syscall/js" 8 | 9 | "github.com/michaelact/kubernetes-secret-editor/wasm/common" 10 | "github.com/michaelact/kubernetes-secret-editor/wasm/decoder" 11 | "github.com/michaelact/kubernetes-secret-editor/wasm/encoder" 12 | ) 13 | 14 | func main() { 15 | wait := make(chan struct{}, 0) 16 | 17 | // Set up a callback function that will be called from JavaScript 18 | js.Global().Set("ProcessDecodeSecret", js.FuncOf(func(this js.Value, p []js.Value) interface{} { 19 | return ProcessSecret(decoder.GetSecretData, p) 20 | })) 21 | js.Global().Set("ProcessEncodeSecret", js.FuncOf(func(this js.Value, p []js.Value) interface{} { 22 | return ProcessSecret(encoder.GetSecretData, p) 23 | })) 24 | 25 | // Keep the program running 26 | <-wait 27 | } 28 | 29 | func ProcessSecret(getSecretData func([]byte) (*common.SecretData, error), p []js.Value) interface{} { 30 | input := []byte(p[0].String()) 31 | 32 | secretData, err := getSecretData(input) 33 | if err != nil { 34 | errorStr := fmt.Sprintf("Unable to get secret data. Error %s occurred\n", err) 35 | return map[string]interface{}{ 36 | "errorInput": errorStr, 37 | } 38 | } 39 | 40 | fullSecret, err := common.GetFullSecret(input, secretData) 41 | if err != nil { 42 | errorStr := fmt.Sprintf("Unable to get full secret file. Error %s occurred\n", err) 43 | return map[string]interface{}{ 44 | "errorInput": errorStr, 45 | } 46 | } 47 | 48 | output, err := common.GetStringSecret(fullSecret) 49 | if err != nil { 50 | errorStr := fmt.Sprintf("Unable to convert the secret to string. Error %s occurred\n", err) 51 | return map[string]interface{}{ 52 | "errorInput": errorStr, 53 | } 54 | } 55 | 56 | return map[string]interface{}{ 57 | "output": output, 58 | } 59 | } 60 | --------------------------------------------------------------------------------