Skip to content

Commit fe0125f

Browse files
committed
add: initial goseal functionality
1 parent 0e61604 commit fe0125f

File tree

6 files changed

+328
-1
lines changed

6 files changed

+328
-1
lines changed

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,7 @@
1313

1414
# Dependency directories (remove the comment below to include it)
1515
# vendor/
16+
17+
# editors
18+
.idea
19+
.vscode

README.md

Lines changed: 67 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,67 @@
1-
# easy-kubeseal
1+
# goseal
2+
3+
goseal is a simple CLI tool to easily create kubernetes secrets using kubectl and kubeseal (optional).
4+
5+
## Prerequisites
6+
7+
[kubectl](https://kubernetes.io/docs/reference/kubectl/kubectl/)
8+
9+
[kubeseal](https://fluxcd.io/docs/guides/sealed-secrets/)
10+
11+
## Installation
12+
13+
```sh
14+
go install github.com/MaxBreida/goseal
15+
```
16+
17+
## Usage
18+
19+
### Available commands
20+
21+
```text
22+
NAME:
23+
goseal - Used to automatically generate kubernetes secret files (and optionally seal them)
24+
25+
USAGE:
26+
goseal [global options] command [command options] [arguments...]
27+
28+
COMMANDS:
29+
yaml, y Creates a secret file from yaml input
30+
file Creates a secret file from file input
31+
help, h Shows a list of commands or help for one command
32+
33+
GLOBAL OPTIONS:
34+
--help, -h show help (default: false)
35+
```
36+
37+
### Create secret from yaml key-value pairs
38+
39+
```text
40+
NAME:
41+
yaml - Creates a secret file from yaml input
42+
43+
USAGE:
44+
yaml [command options] [arguments...]
45+
46+
DESCRIPTION:
47+
creates a sealed secret from yaml input
48+
49+
OPTIONS:
50+
--help, -h show help (default: false)
51+
--namespace value, --nsp value the namespace of the secret
52+
--file value, -f value the input file in yaml format
53+
--name value, -n value the secret name
54+
--cert value, -c value if set, will run kubeseal with given cert
55+
```
56+
57+
To create an unsealed kubernetes, run:
58+
59+
```sh
60+
goseal yaml -f my-file.yaml -nsp my-namespace -n my-secret > output.yaml
61+
```
62+
63+
To seal the secret, simply add the --cert or -c flag to the command:
64+
65+
```sh
66+
goseal yaml -f my-file.yaml -nsp my-namespace -n my-secret -c path/to/my/cert.pem > output.yaml
67+
```

go.mod

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
module github.com/MaxBreida/goseal
2+
3+
go 1.16
4+
5+
require (
6+
github.com/cpuguy83/go-md2man/v2 v2.0.1 // indirect
7+
github.com/urfave/cli/v2 v2.3.0
8+
gopkg.in/yaml.v2 v2.4.0
9+
)

go.sum

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
2+
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
3+
github.com/cpuguy83/go-md2man/v2 v2.0.1 h1:r/myEWzV9lfsM1tFLgDyu0atFtJ1fXn261LKYj/3DxU=
4+
github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
5+
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
6+
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
7+
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
8+
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
9+
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
10+
github.com/urfave/cli/v2 v2.3.0 h1:qph92Y649prgesehzOrQjdWyxFOp/QVM+6imKHad91M=
11+
github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI=
12+
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
13+
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
14+
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
15+
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
16+
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=

goseal.go

Lines changed: 227 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,227 @@
1+
package main
2+
3+
import (
4+
"bytes"
5+
"errors"
6+
"fmt"
7+
"log"
8+
"os"
9+
"os/exec"
10+
"strings"
11+
12+
"github.com/urfave/cli/v2"
13+
"gopkg.in/yaml.v2"
14+
)
15+
16+
func main() {
17+
app := &cli.App{
18+
Name: "goseal",
19+
Usage: "Used to automatically generate kubernetes secret files (and optionally seal them)",
20+
Commands: []*cli.Command{
21+
{
22+
Name: "yaml",
23+
HelpName: "yaml",
24+
Description: "creates a sealed secret from yaml input",
25+
Usage: "Creates a secret file from yaml input",
26+
Aliases: []string{"y"},
27+
Flags: getStandardFlags(),
28+
Action: Yaml,
29+
},
30+
{
31+
Name: "file",
32+
HelpName: "file",
33+
Description: "creates a sealed secret with a file as secret value",
34+
Usage: "Creates a secret file from file input",
35+
Flags: append(getStandardFlags(), &cli.StringFlag{
36+
Name: "cert",
37+
Usage: "if set, will run kubeseal with given cert",
38+
Aliases: []string{"c"},
39+
}),
40+
Action: File,
41+
},
42+
},
43+
}
44+
45+
app.EnableBashCompletion = true
46+
47+
if err := app.Run(os.Args); err != nil {
48+
log.Fatal(err)
49+
}
50+
}
51+
52+
func getStandardFlags() []cli.Flag {
53+
return []cli.Flag{
54+
cli.BashCompletionFlag,
55+
cli.HelpFlag,
56+
&cli.StringFlag{
57+
Name: "namespace",
58+
Usage: "the namespace of the secret",
59+
Required: true,
60+
Aliases: []string{"nsp"},
61+
},
62+
&cli.StringFlag{
63+
Name: "file",
64+
Usage: "the input file in yaml format",
65+
Required: true,
66+
Aliases: []string{"f"},
67+
},
68+
&cli.StringFlag{
69+
Name: "name",
70+
Usage: "the secret name",
71+
Required: true,
72+
Aliases: []string{"n"},
73+
},
74+
&cli.StringFlag{
75+
Name: "cert",
76+
Usage: "if set, will run kubeseal with given cert",
77+
Aliases: []string{"c"},
78+
},
79+
}
80+
}
81+
82+
// Yaml is a cli command
83+
func Yaml(c *cli.Context) error {
84+
filePath := c.String("file")
85+
namespace := c.String("namespace")
86+
secretName := c.String("name")
87+
certPath := c.String("cert")
88+
89+
file, err := os.ReadFile(filePath)
90+
if err != nil {
91+
return err
92+
}
93+
94+
var secrets map[string]string
95+
96+
if err := yaml.Unmarshal(file, &secrets); err != nil {
97+
return err
98+
}
99+
100+
if certPath != "" {
101+
return sealSecret(secrets, secretName, namespace, certPath)
102+
}
103+
104+
return createSecret(secrets, secretName, namespace)
105+
}
106+
107+
// File is a cli command
108+
func File(c *cli.Context) error {
109+
filePath := c.String("file")
110+
secretKey := c.String("key")
111+
namespace := c.String("namespace")
112+
secretName := c.String("name")
113+
certPath := c.String("cert")
114+
115+
file, err := os.ReadFile(filePath)
116+
if err != nil {
117+
return err
118+
}
119+
120+
secrets := map[string]string{secretKey: string(file)}
121+
122+
if certPath != "" {
123+
return sealSecret(secrets, secretName, namespace, certPath)
124+
}
125+
126+
return createSecret(secrets, secretName, namespace)
127+
}
128+
129+
// runs the kubectl create secret command and prints the output to stdout.
130+
func createSecret(secrets map[string]string, secretName, namespace string) error {
131+
kubectlCreateSecret := getCreateSecretFileCmd(secrets, secretName, namespace)
132+
133+
var stdout bytes.Buffer
134+
kubectlCreateSecret.Stdout = &stdout
135+
136+
if err := runCommand(kubectlCreateSecret); err != nil {
137+
return err
138+
}
139+
140+
fmt.Println(stdout.String())
141+
142+
return nil
143+
}
144+
145+
// runs the kubectl create secret command, pipes the output to the kubeseal command and prints the output to stdout.
146+
func sealSecret(secrets map[string]string, secretName, namespace, certPath string) error {
147+
kubectlCreateSecret := getCreateSecretFileCmd(secrets, secretName, namespace)
148+
kubeseal := exec.Command("kubeseal", "--format", "yaml", "--cert", certPath)
149+
150+
var (
151+
err error
152+
stdout, stderr bytes.Buffer
153+
)
154+
155+
kubeseal.Stdout = &stdout
156+
kubeseal.Stderr = &stderr
157+
158+
// Get stdout of first command and attach it to stdin of second command.
159+
kubeseal.Stdin, err = kubectlCreateSecret.StdoutPipe()
160+
if err != nil {
161+
return err
162+
}
163+
164+
if err := kubeseal.Start(); err != nil {
165+
return err
166+
}
167+
168+
if err = runCommand(kubectlCreateSecret); err != nil {
169+
return err
170+
}
171+
172+
if err = kubeseal.Wait(); err != nil {
173+
return errors.New(getErrText(err, kubeseal.Args, stderr.String()))
174+
}
175+
176+
fmt.Println(stdout.String())
177+
178+
return nil
179+
}
180+
181+
// retrieve a printable error text from cmd errors
182+
func getErrText(err error, cmdArgs []string, stdErr string) string {
183+
text := fmt.Sprintf(
184+
"command '%s' failed: %s",
185+
strings.Join(cmdArgs, " "),
186+
err.Error(),
187+
)
188+
189+
errText := strings.TrimSpace(stdErr)
190+
if len(errText) > 0 {
191+
text += "\n" + errText
192+
}
193+
194+
return text
195+
}
196+
197+
func runCommand(cmd *exec.Cmd) error {
198+
var stderr bytes.Buffer
199+
cmd.Stderr = &stderr
200+
201+
if err := cmd.Run(); err != nil {
202+
return errors.New(getErrText(err, cmd.Args, stderr.String()))
203+
}
204+
205+
return nil
206+
}
207+
208+
// creates
209+
func getCreateSecretFileCmd(secrets map[string]string, secretName, namespace string) *exec.Cmd {
210+
args := []string{
211+
"create",
212+
"secret",
213+
"generic",
214+
secretName,
215+
"-n",
216+
namespace,
217+
"--dry-run",
218+
"-o",
219+
"yaml",
220+
}
221+
222+
for k, v := range secrets {
223+
args = append(args, fmt.Sprintf("--from-literal=%s=%s", k, v))
224+
}
225+
226+
return exec.Command("kubectl", args...)
227+
}

testdata/test-input.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
secret: my-secret
2+
password: really secret
3+
cert: qwertzuiop
4+
5+
redis-secret.yaml: 1234567890123456789

0 commit comments

Comments
 (0)