mirror of
https://github.com/mitchell/selfpass.git
synced 2025-12-13 21:07:22 +00:00
Implemented all but update from cli client to server;
solidified encryption; setup deployment mechanism for GCP
This commit is contained in:
parent
cd24f6e848
commit
c5ae0b4ddc
28 changed files with 598 additions and 295 deletions
|
|
@ -5,7 +5,6 @@ import (
|
|||
"crypto/x509"
|
||||
"flag"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
stdlog "log"
|
||||
"net"
|
||||
"os"
|
||||
|
|
@ -28,13 +27,10 @@ var logger log.Logger
|
|||
|
||||
func main() {
|
||||
var (
|
||||
stop = make(chan os.Signal, 1)
|
||||
dev = flag.Bool("dev", false, "enables dev mode logging")
|
||||
port = flag.String("port", "8080", "specify the port to listen on")
|
||||
crtFile = flag.String("cert", "/run/secrets/server_cert", "specify the cert file")
|
||||
keyFile = flag.String("key", "/run/secrets/server_key", "specify the private key file")
|
||||
caFile = flag.String("ca", "/run/secrets/ca_cert", "specify the ca cert file")
|
||||
verbose = flag.Bool("v", false, "be more verbose")
|
||||
stop = make(chan os.Signal, 1)
|
||||
jsonLogs = flag.Bool("json-logs", false, "enables json logging")
|
||||
port = flag.String("port", "8080", "specify the port to listen on")
|
||||
verbose = flag.Bool("v", false, "be more verbose")
|
||||
// tableName = flag.String(
|
||||
// "credential-table-name",
|
||||
// "selfpass-credential",
|
||||
|
|
@ -47,16 +43,13 @@ func main() {
|
|||
signal.Notify(stop, syscall.SIGKILL)
|
||||
signal.Notify(stop, syscall.SIGTERM)
|
||||
|
||||
logger = newLogger(os.Stdout, *dev)
|
||||
logger = newLogger(os.Stdout, *jsonLogs)
|
||||
|
||||
keypair, err := tls.LoadX509KeyPair(*crtFile, *keyFile)
|
||||
check(err)
|
||||
|
||||
ca, err := ioutil.ReadFile(*caFile)
|
||||
keypair, err := tls.X509KeyPair([]byte(cert), []byte(key))
|
||||
check(err)
|
||||
|
||||
caPool := x509.NewCertPool()
|
||||
caPool.AppendCertsFromPEM(ca)
|
||||
caPool.AppendCertsFromPEM([]byte(ca))
|
||||
|
||||
creds := credentials.NewTLS(&tls.Config{
|
||||
Certificates: []tls.Certificate{keypair},
|
||||
|
|
@ -85,7 +78,6 @@ func main() {
|
|||
_ = logger.Log(
|
||||
"message", "serving",
|
||||
"address", addr,
|
||||
"dev", dev,
|
||||
)
|
||||
|
||||
go func() { check(srv.Serve(lis)) }()
|
||||
|
|
@ -95,13 +87,11 @@ func main() {
|
|||
srv.GracefulStop()
|
||||
}
|
||||
|
||||
func newLogger(writer io.Writer, dev bool) log.Logger {
|
||||
var l log.Logger
|
||||
func newLogger(writer io.Writer, jsonLogs bool) log.Logger {
|
||||
writer = log.NewSyncWriter(writer)
|
||||
l := log.NewLogfmtLogger(writer)
|
||||
|
||||
if dev {
|
||||
l = log.NewLogfmtLogger(writer)
|
||||
} else {
|
||||
if jsonLogs {
|
||||
l = log.NewJSONLogger(writer)
|
||||
}
|
||||
l = log.WithPrefix(l, "caller", log.Caller(5), "timestamp", log.DefaultTimestamp)
|
||||
|
|
|
|||
|
|
@ -1,95 +0,0 @@
|
|||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"strings"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/mitchellh/go-homedir"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
"gopkg.in/AlecAivazis/survey.v1"
|
||||
|
||||
"github.com/mitchell/selfpass/credentials/cmds"
|
||||
)
|
||||
|
||||
func makeInitCmd(cfg *viper.Viper) *cobra.Command {
|
||||
initCmd := &cobra.Command{
|
||||
Use: "init",
|
||||
Short: "This command initializes SPC for the first time",
|
||||
Long: `This command initializes SPC for the first time. Writing to the user configuration
|
||||
the users private key, and server certificates. (All of which will be encrypted)`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
var (
|
||||
hasPK bool
|
||||
masterpass string
|
||||
cmasterpass string
|
||||
target string
|
||||
caFile string
|
||||
certFile string
|
||||
keyFile string
|
||||
prompt survey.Prompt
|
||||
privateKey = strings.Replace(uuid.New().String(), "-", "", -1)
|
||||
)
|
||||
|
||||
prompt = &survey.Password{Message: "Master password:"}
|
||||
check(survey.AskOne(prompt, &masterpass, nil))
|
||||
|
||||
prompt = &survey.Password{Message: "Confirm master password:"}
|
||||
check(survey.AskOne(prompt, &cmasterpass, nil))
|
||||
if masterpass != cmasterpass {
|
||||
check(fmt.Errorf("master passwords didn't match"))
|
||||
}
|
||||
|
||||
prompt = &survey.Input{Message: "Selfpass server address:"}
|
||||
check(survey.AskOne(prompt, &target, nil))
|
||||
|
||||
prompt = &survey.Confirm{Message: "Do you have a private key?"}
|
||||
check(survey.AskOne(prompt, &hasPK, nil))
|
||||
|
||||
if hasPK {
|
||||
prompt = &survey.Input{Message: "Private key:"}
|
||||
check(survey.AskOne(prompt, &privateKey, nil))
|
||||
privateKey = strings.Replace(privateKey, "-", "", -1)
|
||||
}
|
||||
|
||||
prompt = &survey.Input{Message: "CA certificate file:"}
|
||||
check(survey.AskOne(prompt, &caFile, nil))
|
||||
ca, err := ioutil.ReadFile(caFile)
|
||||
check(err)
|
||||
|
||||
prompt = &survey.Input{Message: "Client certificate file:"}
|
||||
check(survey.AskOne(prompt, &certFile, nil))
|
||||
cert, err := ioutil.ReadFile(certFile)
|
||||
check(err)
|
||||
|
||||
prompt = &survey.Input{Message: "Client key file:"}
|
||||
check(survey.AskOne(prompt, &keyFile, nil))
|
||||
key, err := ioutil.ReadFile(keyFile)
|
||||
check(err)
|
||||
|
||||
cfg.Set(cmds.KeyConnConfig, map[string]string{
|
||||
"target": target,
|
||||
"ca": string(ca),
|
||||
"cert": string(cert),
|
||||
"key": string(key),
|
||||
})
|
||||
|
||||
cfg.Set(cmds.KeyPrivateKey, privateKey)
|
||||
|
||||
if err := cfg.WriteConfig(); err != nil {
|
||||
home, err := homedir.Dir()
|
||||
check(err)
|
||||
|
||||
check(cfg.WriteConfigAs(home + "/.spc.toml"))
|
||||
cfg.SetConfigFile(home + "/.spc.toml")
|
||||
fmt.Println("Wrote new config to: " + home + "/.spc.toml")
|
||||
}
|
||||
|
||||
encryptConfig(masterpass, cfg)
|
||||
},
|
||||
}
|
||||
|
||||
return initCmd
|
||||
}
|
||||
|
|
@ -1,147 +0,0 @@
|
|||
package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
|
||||
"github.com/mitchellh/go-homedir"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
"gopkg.in/AlecAivazis/survey.v1"
|
||||
|
||||
"github.com/mitchell/selfpass/credentials/cmds"
|
||||
"github.com/mitchell/selfpass/credentials/types"
|
||||
"github.com/mitchell/selfpass/crypto"
|
||||
)
|
||||
|
||||
func Execute(ctx context.Context, initClient types.CredentialClientInit) {
|
||||
rootCmd := &cobra.Command{
|
||||
Use: "spc",
|
||||
Short: "This is the CLI client for Selfpass.",
|
||||
Long: `This is the CLI client for Selfpass, the self-hosted password manager. With this tool you
|
||||
can interact with the entire Selfpass API.`,
|
||||
}
|
||||
|
||||
cfgFile := rootCmd.PersistentFlags().String("config", "", "config file (default is $HOME/.spc.toml)")
|
||||
rootCmd.PersistentFlags().Parse(os.Args)
|
||||
|
||||
decryptCfg := rootCmd.Flags().Bool("decrypt-cfg", false, "unencrypt config file")
|
||||
rootCmd.Flags().Parse(os.Args)
|
||||
|
||||
encryptCfg := !*decryptCfg
|
||||
masterpass, cfg := openConfig(*cfgFile)
|
||||
if encryptCfg && masterpass != "" {
|
||||
defer encryptConfig(masterpass, cfg)
|
||||
}
|
||||
if *decryptCfg {
|
||||
fmt.Println("Decrypting config file. It will auto-encrypt when you next run of spc.")
|
||||
return
|
||||
}
|
||||
|
||||
rootCmd.AddCommand(makeInitCmd(cfg))
|
||||
rootCmd.AddCommand(cmds.MakeListCmd(makeInitClient(cfg, initClient)))
|
||||
rootCmd.AddCommand(cmds.MakeCreateCmd(masterpass, cfg, makeInitClient(cfg, initClient)))
|
||||
rootCmd.AddCommand(cmds.MakeGetCmd(masterpass, cfg, makeInitClient(cfg, initClient)))
|
||||
|
||||
check(rootCmd.Execute())
|
||||
}
|
||||
|
||||
func makeInitClient(cfg *viper.Viper, initClient types.CredentialClientInit) cmds.CredentialClientInit {
|
||||
return func(ctx context.Context) types.CredentialClient {
|
||||
connConfig := cfg.GetStringMapString(cmds.KeyConnConfig)
|
||||
|
||||
client, err := initClient(
|
||||
ctx,
|
||||
connConfig["target"],
|
||||
connConfig["ca"],
|
||||
connConfig["cert"],
|
||||
connConfig["key"],
|
||||
)
|
||||
if err != nil {
|
||||
fmt.Printf("Please run 'init' command before running API commands.\nError Message: %s\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
return client
|
||||
}
|
||||
}
|
||||
|
||||
func openConfig(cfgFile string) (masterpass string, v *viper.Viper) {
|
||||
v = viper.New()
|
||||
v.SetConfigType("toml")
|
||||
|
||||
if cfgFile != "" {
|
||||
// Use config file from the flag.
|
||||
v.SetConfigFile(cfgFile)
|
||||
} else {
|
||||
// Find home directory.
|
||||
home, err := homedir.Dir()
|
||||
check(err)
|
||||
|
||||
// Search config in home directory with name ".spc" (without extension).
|
||||
v.AddConfigPath(home)
|
||||
v.SetConfigName(".spc")
|
||||
|
||||
cfgFile = home + "/.spc.toml"
|
||||
}
|
||||
|
||||
if _, err := os.Open(cfgFile); !os.IsNotExist(err) {
|
||||
prompt := &survey.Password{Message: "Master password:"}
|
||||
check(survey.AskOne(prompt, &masterpass, nil))
|
||||
|
||||
decryptConfig(masterpass, cfgFile)
|
||||
}
|
||||
|
||||
//v.AutomaticEnv() // read in environment variables that match
|
||||
|
||||
// If a config file is found, read it in.
|
||||
if err := v.ReadInConfig(); err == nil {
|
||||
fmt.Println("Using config file:", v.ConfigFileUsed())
|
||||
}
|
||||
|
||||
return masterpass, v
|
||||
}
|
||||
|
||||
func decryptConfig(masterpass string, cfgFile string) {
|
||||
contents, err := ioutil.ReadFile(cfgFile)
|
||||
check(err)
|
||||
|
||||
passkey, err := crypto.GenerateKeyFromPassword([]byte(masterpass))
|
||||
check(err)
|
||||
|
||||
contents, err = crypto.CBCDecrypt(passkey, contents)
|
||||
if err != nil && err.Error() == "Padding incorrect" {
|
||||
fmt.Println("incorrect master password")
|
||||
os.Exit(1)
|
||||
} else if err != nil && err.Error() == "ciphertext is not a multiple of the block size" {
|
||||
fmt.Println("Config wasn't encrypted.")
|
||||
return
|
||||
}
|
||||
check(err)
|
||||
|
||||
check(ioutil.WriteFile(cfgFile, contents, 0600))
|
||||
}
|
||||
|
||||
func encryptConfig(masterpass string, cfg *viper.Viper) {
|
||||
contents, err := ioutil.ReadFile(cfg.ConfigFileUsed())
|
||||
if os.IsNotExist(err) {
|
||||
return
|
||||
}
|
||||
|
||||
keypass, err := crypto.GenerateKeyFromPassword([]byte(masterpass))
|
||||
check(err)
|
||||
|
||||
contents, err = crypto.CBCEncrypt(keypass, contents)
|
||||
check(err)
|
||||
|
||||
check(ioutil.WriteFile(cfg.ConfigFileUsed(), contents, 0600))
|
||||
}
|
||||
|
||||
func check(err error) {
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,13 +1,10 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/mitchell/selfpass/cmd/spc/cmd"
|
||||
"github.com/mitchell/selfpass/cli/commands"
|
||||
"github.com/mitchell/selfpass/credentials/repositories"
|
||||
)
|
||||
|
||||
func main() {
|
||||
ctx := context.Background()
|
||||
cmd.Execute(ctx, repositories.NewCredentialServiceClient)
|
||||
commands.Execute(repositories.NewCredentialServiceClient)
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue