fix: enable env vars in setup steps (and deprecate admin subcommand) (#3871)

* fix: enable env vars in setup steps (and deprecate admin subcommand)

* fix tests and error text
This commit is contained in:
Livio Spring
2022-06-27 12:32:34 +02:00
committed by GitHub
parent 30f553dea1
commit 12d4d3ea0b
53 changed files with 44 additions and 31 deletions

132
cmd/key/key.go Normal file
View File

@@ -0,0 +1,132 @@
package key
import (
"io"
"os"
"strings"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"sigs.k8s.io/yaml"
caos_errs "github.com/zitadel/zitadel/internal/errors"
"github.com/zitadel/zitadel/internal/crypto"
cryptoDB "github.com/zitadel/zitadel/internal/crypto/database"
"github.com/zitadel/zitadel/internal/database"
)
const (
flagKeyFile = "file"
)
type Config struct {
Database database.Config
}
func New() *cobra.Command {
cmd := &cobra.Command{
Use: "keys",
Short: "manage encryption keys",
}
AddMasterKeyFlag(cmd)
cmd.AddCommand(newKey())
return cmd
}
func newKey() *cobra.Command {
cmd := &cobra.Command{
Use: "new [keyID=key]... [-f file]",
Short: "create new encryption key(s)",
Long: `create new encryption key(s) (encrypted by the provided master key)
provide key(s) by YAML file and/or by argument
Requirements:
- cockroachdb`,
Example: `new -f keys.yaml
new key1=somekey key2=anotherkey
new -f keys.yaml key2=anotherkey`,
RunE: func(cmd *cobra.Command, args []string) error {
keys, err := keysFromArgs(args)
if err != nil {
return err
}
filePath, _ := cmd.Flags().GetString(flagKeyFile)
if filePath != "" {
file, err := openFile(filePath)
if err != nil {
return err
}
yamlKeys, err := keysFromYAML(file)
if err != nil {
return err
}
keys = append(keys, yamlKeys...)
}
config := new(Config)
if err := viper.Unmarshal(config); err != nil {
return err
}
masterKey, err := MasterKey(cmd)
if err != nil {
return err
}
storage, err := keyStorage(config.Database, masterKey)
if err != nil {
return err
}
return storage.CreateKeys(keys...)
},
}
cmd.PersistentFlags().StringP(flagKeyFile, "f", "", "path to keys file")
return cmd
}
func keysFromArgs(args []string) ([]*crypto.Key, error) {
keys := make([]*crypto.Key, len(args))
for i, arg := range args {
key := strings.Split(arg, "=")
if len(key) != 2 {
return nil, caos_errs.ThrowInternal(nil, "KEY-JKd82", "argument is not in the valid format [keyID=key]")
}
keys[i] = &crypto.Key{
ID: key[0],
Value: key[1],
}
}
return keys, nil
}
func keysFromYAML(file io.Reader) ([]*crypto.Key, error) {
data, err := io.ReadAll(file)
if err != nil {
return nil, caos_errs.ThrowInternal(err, "KEY-ajGFr", "unable to extract keys from file")
}
keysYAML := make(map[string]string)
if err = yaml.Unmarshal(data, &keysYAML); err != nil {
return nil, caos_errs.ThrowInternal(err, "KEY-sd34K", "unable to extract keys from file")
}
keys := make([]*crypto.Key, 0, len(keysYAML))
for id, key := range keysYAML {
keys = append(keys, &crypto.Key{
ID: id,
Value: key,
})
}
return keys, nil
}
func openFile(fileName string) (io.Reader, error) {
file, err := os.Open(fileName)
if err != nil {
return nil, caos_errs.ThrowInternalf(err, "KEY-asGr2", "failed to open file: %s", fileName)
}
return file, nil
}
func keyStorage(config database.Config, masterKey string) (crypto.KeyStorage, error) {
db, err := database.Connect(config)
if err != nil {
return nil, err
}
return cryptoDB.NewKeyStorage(db, masterKey)
}

161
cmd/key/key_test.go Normal file
View File

@@ -0,0 +1,161 @@
package key
import (
"bytes"
"io"
"reflect"
"testing"
"github.com/stretchr/testify/assert"
caos_errors "github.com/zitadel/zitadel/internal/errors"
"github.com/zitadel/zitadel/internal/crypto"
)
func Test_keysFromArgs(t *testing.T) {
type args struct {
args []string
}
type res struct {
keys []*crypto.Key
err func(error) bool
}
tests := []struct {
name string
args args
res res
}{
{
"no args",
args{},
res{
keys: []*crypto.Key{},
},
},
{
"invalid arg",
args{
args: []string{"keyID", "value"},
},
res{
err: caos_errors.IsInternal,
},
},
{
"single arg",
args{
args: []string{"keyID=value"},
},
res{
keys: []*crypto.Key{
{
ID: "keyID",
Value: "value",
},
},
},
},
{
"multiple args",
args{
args: []string{"keyID=value", "keyID2=value2"},
},
res{
keys: []*crypto.Key{
{
ID: "keyID",
Value: "value",
},
{
ID: "keyID2",
Value: "value2",
},
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := keysFromArgs(tt.args.args)
if tt.res.err == nil {
assert.NoError(t, err)
}
if tt.res.err != nil && !tt.res.err(err) {
t.Errorf("got wrong err: %v ", err)
}
if !reflect.DeepEqual(got, tt.res.keys) {
t.Errorf("keysFromArgs() got = %v, want %v", got, tt.res.keys)
}
})
}
}
func Test_keysFromYAML(t *testing.T) {
type args struct {
file io.Reader
}
type res struct {
keys []*crypto.Key
err func(error) bool
}
tests := []struct {
name string
args args
res res
}{
{
"invalid yaml",
args{
file: bytes.NewReader([]byte("keyID=ds")),
},
res{
err: caos_errors.IsInternal,
},
},
{
"single key",
args{
file: bytes.NewReader([]byte("keyID: value")),
},
res{
keys: []*crypto.Key{
{
ID: "keyID",
Value: "value",
},
},
},
},
{
"multiple keys",
args{
file: bytes.NewReader([]byte("keyID: value\nkeyID2: value2")),
},
res{
keys: []*crypto.Key{
{
ID: "keyID",
Value: "value",
},
{
ID: "keyID2",
Value: "value2",
},
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := keysFromYAML(tt.args.file)
if tt.res.err == nil {
assert.NoError(t, err)
}
if tt.res.err != nil && !tt.res.err(err) {
t.Errorf("got wrong err: %v ", err)
}
assert.ElementsMatch(t, got, tt.res.keys)
})
}
}

67
cmd/key/masterkey.go Normal file
View File

@@ -0,0 +1,67 @@
package key
import (
"errors"
"io/ioutil"
"os"
"github.com/spf13/cobra"
)
const (
flagMasterKey = "masterkeyFile"
flagMasterKeyShort = "m"
flagMasterKeyArg = "masterkey"
flagMasterKeyEnv = "masterkeyFromEnv"
envMasterKey = "ZITADEL_MASTERKEY"
)
var (
ErrNotSingleFlag = errors.New("masterkey must either be provided by file path, value or environment variable")
)
func AddMasterKeyFlag(cmd *cobra.Command) {
if cmd.PersistentFlags().Lookup(flagMasterKey) != nil {
return
}
cmd.PersistentFlags().StringP(flagMasterKey, flagMasterKeyShort, "", "path to the masterkey for en/decryption keys")
cmd.PersistentFlags().String(flagMasterKeyArg, "", "masterkey as argument for en/decryption keys")
cmd.PersistentFlags().Bool(flagMasterKeyEnv, false, "read masterkey for en/decryption keys from environment variable (ZITADEL_MASTERKEY)")
}
func MasterKey(cmd *cobra.Command) (string, error) {
masterKeyFile, _ := cmd.Flags().GetString(flagMasterKey)
masterKeyFromArg, _ := cmd.Flags().GetString(flagMasterKeyArg)
masterKeyFromEnv, _ := cmd.Flags().GetBool(flagMasterKeyEnv)
if err := checkSingleFlag(masterKeyFile, masterKeyFromArg, masterKeyFromEnv); err != nil {
return "", err
}
if masterKeyFromArg != "" {
return masterKeyFromArg, nil
}
if masterKeyFromEnv {
return os.Getenv(envMasterKey), nil
}
data, err := ioutil.ReadFile(masterKeyFile)
if err != nil {
return "", err
}
return string(data), nil
}
func checkSingleFlag(masterKeyFile, masterKeyFromArg string, masterKeyFromEnv bool) error {
var flags int
if masterKeyFile != "" {
flags++
}
if masterKeyFromArg != "" {
flags++
}
if masterKeyFromEnv {
flags++
}
if flags != 1 {
return ErrNotSingleFlag
}
return nil
}

72
cmd/key/masterkey_test.go Normal file
View File

@@ -0,0 +1,72 @@
package key
import (
"fmt"
"testing"
"github.com/stretchr/testify/assert"
)
func Test_checkSingleFlag(t *testing.T) {
type args struct {
masterKeyFile string
masterKeyFromArg string
masterKeyFromEnv bool
}
tests := []struct {
name string
args args
wantErr assert.ErrorAssertionFunc
}{
{
"no values, error",
args{
masterKeyFile: "",
masterKeyFromArg: "",
masterKeyFromEnv: false,
},
assert.Error,
},
{
"multiple values, error",
args{
masterKeyFile: "file",
masterKeyFromArg: "masterkey",
masterKeyFromEnv: true,
},
assert.Error,
},
{
"only file, ok",
args{
masterKeyFile: "file",
masterKeyFromArg: "",
masterKeyFromEnv: false,
},
assert.NoError,
},
{
"only argument, ok",
args{
masterKeyFile: "",
masterKeyFromArg: "masterkey",
masterKeyFromEnv: false,
},
assert.NoError,
},
{
"only env, ok",
args{
masterKeyFile: "",
masterKeyFromArg: "",
masterKeyFromEnv: true,
},
assert.NoError,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
tt.wantErr(t, checkSingleFlag(tt.args.masterKeyFile, tt.args.masterKeyFromArg, tt.args.masterKeyFromEnv), fmt.Sprintf("checkSingleFlag(%v, %v)", tt.args.masterKeyFile, tt.args.masterKeyFromArg))
})
}
}