From e2b5cc7e607c9bdc2db30786b5e55bf7ac83900d Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Sat, 20 Apr 2019 21:56:15 +0200 Subject: [PATCH] KV and commands (#8) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - new commands - support for KV Co-authored-by: Stephan Müller --- .dockerignore | 8 + .golangci.toml | 3 + .goreleaser.yml | 6 +- Dockerfile | 11 +- Makefile | 2 +- cmd/boltdb.go | 39 +++++ cmd/consul.go | 37 +++++ cmd/doc.go | 20 +++ cmd/etcd.go | 43 ++++++ cmd/file.go | 25 ++++ cmd/kv.go | 58 ++++++++ cmd/root.go | 164 +++++++++++++++++++++ version.go => cmd/version.go | 17 ++- cmd/zookeeper.go | 33 +++++ docs/traefik-certs-dumper.md | 28 ++++ docs/traefik-certs-dumper_file.md | 36 +++++ docs/traefik-certs-dumper_kv.md | 41 ++++++ docs/traefik-certs-dumper_kv_boltdb.md | 43 ++++++ docs/traefik-certs-dumper_kv_consul.md | 42 ++++++ docs/traefik-certs-dumper_kv_etcd.md | 42 ++++++ docs/traefik-certs-dumper_kv_zookeeper.md | 41 ++++++ docs/traefik-certs-dumper_version.md | 35 +++++ dumper.go | 141 ------------------ dumper/config.go | 9 ++ dumper/dumper.go | 102 +++++++++++++ dumper/file/file.go | 32 ++++ dumper/info.go | 35 +++++ dumper/kv/config.go | 12 ++ dumper/kv/convert.go | 65 +++++++++ dumper/kv/kv.go | 88 +++++++++++ go.mod | 44 +++++- go.sum | 170 ++++++++++++++++++++-- integrationtest/docker-compose.yml | 18 +++ integrationtest/loader.go | 121 +++++++++++++++ integrationtest/readme.md | 42 ++++++ main.go | 122 +--------------- readme.md | 75 +++++----- 37 files changed, 1528 insertions(+), 322 deletions(-) create mode 100644 .dockerignore create mode 100644 cmd/boltdb.go create mode 100644 cmd/consul.go create mode 100644 cmd/doc.go create mode 100644 cmd/etcd.go create mode 100644 cmd/file.go create mode 100644 cmd/kv.go create mode 100644 cmd/root.go rename version.go => cmd/version.go (56%) create mode 100644 cmd/zookeeper.go create mode 100644 docs/traefik-certs-dumper.md create mode 100644 docs/traefik-certs-dumper_file.md create mode 100644 docs/traefik-certs-dumper_kv.md create mode 100644 docs/traefik-certs-dumper_kv_boltdb.md create mode 100644 docs/traefik-certs-dumper_kv_consul.md create mode 100644 docs/traefik-certs-dumper_kv_etcd.md create mode 100644 docs/traefik-certs-dumper_kv_zookeeper.md create mode 100644 docs/traefik-certs-dumper_version.md delete mode 100644 dumper.go create mode 100644 dumper/config.go create mode 100644 dumper/dumper.go create mode 100644 dumper/file/file.go create mode 100644 dumper/info.go create mode 100644 dumper/kv/config.go create mode 100644 dumper/kv/convert.go create mode 100644 dumper/kv/kv.go create mode 100644 integrationtest/docker-compose.yml create mode 100644 integrationtest/loader.go create mode 100644 integrationtest/readme.md diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..0cace7a --- /dev/null +++ b/.dockerignore @@ -0,0 +1,8 @@ +.idea/ +vendor/ +dist/ +dump/ +dumpcerts.sh +acme.json +acme-backup.json +traefik-certs-dumper diff --git a/.golangci.toml b/.golangci.toml index 8a57b92..2a697a9 100644 --- a/.golangci.toml +++ b/.golangci.toml @@ -39,3 +39,6 @@ [[issues.exclude-rules]] path = "version.go" text = "`(version|commit|date)` is a global variable" + [[issues.exclude-rules]] + path = "cmd/" + linters = ["gochecknoglobals", "gochecknoinits"] diff --git a/.goreleaser.yml b/.goreleaser.yml index 830e61c..45d4646 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -2,12 +2,14 @@ project_name: traefik-certs-dumper builds: - binary: traefik-certs-dumper + ldflags: + - -s -w -X github.com/ldez/traefik-certs-dumper/cmd.version={{.Version}} -X github.com/ldez/traefik-certs-dumper/cmd.commit={{.ShortCommit}} -X github.com/ldez/traefik-certs-dumper/cmd.date={{.Date}} env: - GO111MODULE=on goos: - - windows - - darwin - linux + - darwin + - windows - freebsd - openbsd goarch: diff --git a/Dockerfile b/Dockerfile index bfc4763..7d1ac0a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,21 +1,20 @@ FROM golang:1-alpine as builder RUN apk --update upgrade \ -&& apk --no-cache --no-progress add git make gcc musl-dev \ -&& rm -rf /var/cache/apk/* + && apk --no-cache --no-progress add git make gcc musl-dev WORKDIR /go/src/github.com/ldez/traefik-certs-dumper -COPY . . -RUN go get -u github.com/golang/dep/cmd/dep ENV GO111MODULE on +COPY go.mod go.sum ./ RUN go mod download + +COPY . . RUN make build FROM alpine:3.9 RUN apk --update upgrade \ - && apk --no-cache --no-progress add ca-certificates git \ - && rm -rf /var/cache/apk/* + && apk --no-cache --no-progress add ca-certificates COPY --from=builder /go/src/github.com/ldez/traefik-certs-dumper/traefik-certs-dumper /usr/bin/traefik-certs-dumper diff --git a/Makefile b/Makefile index 8f91d03..00be093 100644 --- a/Makefile +++ b/Makefile @@ -16,7 +16,7 @@ clean: build: clean @echo Version: $(VERSION) $(BUILD_DATE) - go build -v -ldflags '-X "main.version=${VERSION}" -X "main.commit=${SHA}" -X "main.date=${BUILD_DATE}"' + go build -v -ldflags '-X "github.com/ldez/traefik-certs-dumper/cmd.version=${VERSION}" -X "github.com/ldez/traefik-certs-dumper/cmd.commit=${SHA}" -X "github.com/ldez/traefik-certs-dumper/cmd.date=${BUILD_DATE}"' checks: golangci-lint run diff --git a/cmd/boltdb.go b/cmd/boltdb.go new file mode 100644 index 0000000..622d665 --- /dev/null +++ b/cmd/boltdb.go @@ -0,0 +1,39 @@ +package cmd + +import ( + "github.com/abronan/valkeyrie/store" + "github.com/abronan/valkeyrie/store/boltdb" + "github.com/ldez/traefik-certs-dumper/dumper" + "github.com/ldez/traefik-certs-dumper/dumper/kv" + "github.com/spf13/cobra" +) + +// boltdbCmd represents the boltdb command +var boltdbCmd = &cobra.Command{ + Use: "boltdb", + Short: "Dump the content of BoltDB.", + Long: `Dump the content of BoltDB.`, + RunE: runE(boltdbRun), +} + +func init() { + kvCmd.AddCommand(boltdbCmd) + + boltdbCmd.Flags().Bool("persist-connection", false, "Persist connection for boltdb.") + boltdbCmd.Flags().String("bucket", "traefik", "Bucket for boltdb.") +} + +func boltdbRun(baseConfig *dumper.BaseConfig, cmd *cobra.Command) error { + config, err := getKvConfig(cmd) + if err != nil { + return err + } + + config.Options.Bucket = cmd.Flag("bucket").Value.String() + config.Options.PersistConnection, _ = cmd.Flags().GetBool("persist-connection") + + config.Backend = store.BOLTDB + boltdb.Register() + + return kv.Dump(config, baseConfig) +} diff --git a/cmd/consul.go b/cmd/consul.go new file mode 100644 index 0000000..9d917e7 --- /dev/null +++ b/cmd/consul.go @@ -0,0 +1,37 @@ +package cmd + +import ( + "github.com/abronan/valkeyrie/store" + "github.com/abronan/valkeyrie/store/consul" + "github.com/ldez/traefik-certs-dumper/dumper" + "github.com/ldez/traefik-certs-dumper/dumper/kv" + "github.com/spf13/cobra" +) + +// consulCmd represents the consul command +var consulCmd = &cobra.Command{ + Use: "consul", + Short: "Dump the content of Consul.", + Long: `Dump the content of Consul.`, + RunE: runE(consulRun), +} + +func init() { + kvCmd.AddCommand(consulCmd) + + consulCmd.Flags().String("token", "", "Token for consul.") +} + +func consulRun(baseConfig *dumper.BaseConfig, cmd *cobra.Command) error { + config, err := getKvConfig(cmd) + if err != nil { + return err + } + + config.Options.Token = cmd.Flag("token").Value.String() + + config.Backend = store.CONSUL + consul.Register() + + return kv.Dump(config, baseConfig) +} diff --git a/cmd/doc.go b/cmd/doc.go new file mode 100644 index 0000000..9218db9 --- /dev/null +++ b/cmd/doc.go @@ -0,0 +1,20 @@ +package cmd + +import ( + "github.com/spf13/cobra" + "github.com/spf13/cobra/doc" +) + +// docCmd represents the doc command +var docCmd = &cobra.Command{ + Use: "doc", + Short: "Generate documentation", + Hidden: true, + RunE: func(cmd *cobra.Command, args []string) error { + return doc.GenMarkdownTree(rootCmd, "./docs") + }, +} + +func init() { + rootCmd.AddCommand(docCmd) +} diff --git a/cmd/etcd.go b/cmd/etcd.go new file mode 100644 index 0000000..ad4d390 --- /dev/null +++ b/cmd/etcd.go @@ -0,0 +1,43 @@ +package cmd + +import ( + "time" + + "github.com/abronan/valkeyrie/store" + "github.com/abronan/valkeyrie/store/etcd/v2" + "github.com/ldez/traefik-certs-dumper/dumper" + "github.com/ldez/traefik-certs-dumper/dumper/kv" + "github.com/spf13/cobra" +) + +// etcdCmd represents the etcd command +var etcdCmd = &cobra.Command{ + Use: "etcd", + Short: "Dump the content of etcd.", + Long: `Dump the content of etcd.`, + RunE: runE(etcdRun), +} + +func init() { + kvCmd.AddCommand(etcdCmd) + + etcdCmd.Flags().Int("sync-period", 0, "Sync period for etcd in seconds.") +} + +func etcdRun(baseConfig *dumper.BaseConfig, cmd *cobra.Command) error { + config, err := getKvConfig(cmd) + if err != nil { + return err + } + + synPeriod, err := cmd.Flags().GetInt("sync-period") + if err != nil { + return err + } + config.Options.SyncPeriod = time.Duration(synPeriod) * time.Second + + config.Backend = store.ETCD + etcd.Register() + + return kv.Dump(config, baseConfig) +} diff --git a/cmd/file.go b/cmd/file.go new file mode 100644 index 0000000..cc19b9c --- /dev/null +++ b/cmd/file.go @@ -0,0 +1,25 @@ +package cmd + +import ( + "github.com/ldez/traefik-certs-dumper/dumper" + "github.com/ldez/traefik-certs-dumper/dumper/file" + "github.com/spf13/cobra" +) + +// fileCmd represents the file command +var fileCmd = &cobra.Command{ + Use: "file", + Short: `Dump the content of the "acme.json" file.`, + Long: `Dump the content of the "acme.json" file from Traefik to certificates.`, + RunE: runE(func(baseConfig *dumper.BaseConfig, cmd *cobra.Command) error { + acmeFile := cmd.Flag("source").Value.String() + + return file.Dump(acmeFile, baseConfig) + }), +} + +func init() { + rootCmd.AddCommand(fileCmd) + + fileCmd.Flags().String("source", "./acme.json", "Path to 'acme.json' file.") +} diff --git a/cmd/kv.go b/cmd/kv.go new file mode 100644 index 0000000..e299152 --- /dev/null +++ b/cmd/kv.go @@ -0,0 +1,58 @@ +package cmd + +import ( + "strconv" + "time" + + "github.com/abronan/valkeyrie/store" + "github.com/ldez/traefik-certs-dumper/dumper/kv" + "github.com/spf13/cobra" +) + +// kvCmd represents the kv command +var kvCmd = &cobra.Command{ + Use: "kv", + Short: `Dump the content of a KV store.`, + Long: `Dump the content of a KV store.`, +} + +func init() { + rootCmd.AddCommand(kvCmd) + + kvCmd.PersistentFlags().StringSlice("endpoints", []string{"localhost:8500"}, "List of endpoints.") + kvCmd.PersistentFlags().Int("connection-timeout", 0, "Connection timeout in seconds.") + kvCmd.PersistentFlags().String("prefix", "traefik", "Prefix used for KV store.") + kvCmd.PersistentFlags().String("password", "", "Password for connection.") + kvCmd.PersistentFlags().String("username", "", "Username for connection.") + kvCmd.PersistentFlags().Bool("watch", false, "Enable watching changes.") + + // FIXME review TLS parts + // kvCmd.PersistentFlags().Bool("tls.enable", false, "Enable TLS encryption.") + // kvCmd.PersistentFlags().Bool("tls.insecureskipverify", false, "Trust unverified certificates if TLS is enabled.") + // kvCmd.PersistentFlags().String("tls.ca-cert-file", "", "Root CA file for certificate verification if TLS is enabled.") +} + +func getKvConfig(cmd *cobra.Command) (*kv.Config, error) { + endpoints, err := cmd.Flags().GetStringSlice("endpoints") + if err != nil { + return nil, err + } + + connectionTimeout, err := cmd.Flags().GetInt("connection-timeout") + if err != nil { + return nil, err + } + + watch, _ := strconv.ParseBool(cmd.Flag("watch").Value.String()) + + return &kv.Config{ + Endpoints: endpoints, + Prefix: cmd.Flag("prefix").Value.String(), + Options: &store.Config{ + ConnectionTimeout: time.Duration(connectionTimeout) * time.Second, + Username: cmd.Flag("password").Value.String(), + Password: cmd.Flag("username").Value.String(), + }, + Watch: watch, + }, nil +} diff --git a/cmd/root.go b/cmd/root.go new file mode 100644 index 0000000..439abd0 --- /dev/null +++ b/cmd/root.go @@ -0,0 +1,164 @@ +package cmd + +import ( + "fmt" + "io/ioutil" + "log" + "os" + "path/filepath" + "strconv" + + "github.com/ldez/traefik-certs-dumper/dumper" + homedir "github.com/mitchellh/go-homedir" + "github.com/spf13/cobra" + "github.com/spf13/viper" +) + +var cfgFile string + +// rootCmd represents the base command when called without any subcommands +var rootCmd = &cobra.Command{ + Use: "traefik-certs-dumper", + Short: "Dump Let's Encrypt certificates from Traefik.", + Long: `Dump Let's Encrypt certificates from Traefik.`, + PersistentPreRunE: func(cmd *cobra.Command, args []string) error { + if cmd.Name() == "version" { + return nil + } + + crtExt := cmd.Flag("crt-ext").Value.String() + keyExt := cmd.Flag("key-ext").Value.String() + + subDir, _ := strconv.ParseBool(cmd.Flag("domain-subdir").Value.String()) + if !subDir { + if crtExt == keyExt { + return fmt.Errorf("--crt-ext (%q) and --key-ext (%q) are identical, in this case --domain-subdir is required", crtExt, keyExt) + } + } + return nil + }, +} + +// Execute adds all child commands to the root command and sets flags appropriately. +// This is called by main.main(). It only needs to happen once to the rootCmd. +func Execute() { + if err := rootCmd.Execute(); err != nil { + log.Println(err) + os.Exit(1) + } +} + +func init() { + cobra.OnInitialize(initConfig) + + rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.traefik-certs-dumper.yaml)") + + rootCmd.PersistentFlags().String("dest", "./dump", "Path to store the dump content.") + rootCmd.PersistentFlags().String("crt-ext", ".crt", "The file extension of the generated certificates.") + rootCmd.PersistentFlags().String("crt-name", "certificate", "The file name (without extension) of the generated certificates.") + rootCmd.PersistentFlags().String("key-ext", ".key", "The file extension of the generated private keys.") + rootCmd.PersistentFlags().String("key-name", "privatekey", "The file name (without extension) of the generated private keys.") + rootCmd.PersistentFlags().Bool("domain-subdir", false, "Use domain as sub-directory.") +} + +// initConfig reads in config file and ENV variables if set. +func initConfig() { + if cfgFile != "" { + // Use config file from the flag. + viper.SetConfigFile(cfgFile) + } else { + // Find home directory. + home, err := homedir.Dir() + if err != nil { + fmt.Println(err) + os.Exit(1) + } + + // Search config in home directory with name ".traefik-certs-dumper" (without extension). + viper.AddConfigPath(home) + viper.SetConfigName(".traefik-certs-dumper") + } + + viper.AutomaticEnv() // read in environment variables that match + + // If a config file is found, read it in. + if err := viper.ReadInConfig(); err == nil { + fmt.Println("Using config file:", viper.ConfigFileUsed()) + } +} + +func getBaseConfig(cmd *cobra.Command) (*dumper.BaseConfig, error) { + subDir, err := strconv.ParseBool(cmd.Flag("domain-subdir").Value.String()) + if err != nil { + return nil, err + } + + return &dumper.BaseConfig{ + DumpPath: cmd.Flag("dest").Value.String(), + CrtInfo: dumper.FileInfo{ + Name: cmd.Flag("crt-name").Value.String(), + Ext: cmd.Flag("crt-ext").Value.String(), + }, + KeyInfo: dumper.FileInfo{ + Name: cmd.Flag("key-name").Value.String(), + Ext: cmd.Flag("key-ext").Value.String(), + }, + DomainSubDir: subDir, + }, nil +} + +func runE(apply func(*dumper.BaseConfig, *cobra.Command) error) func(*cobra.Command, []string) error { + return func(cmd *cobra.Command, _ []string) error { + baseConfig, err := getBaseConfig(cmd) + if err != nil { + return err + } + + err = apply(baseConfig, cmd) + if err != nil { + return err + } + + return tree(baseConfig.DumpPath, "") + } +} + +func tree(root, indent string) error { + fi, err := os.Stat(root) + if err != nil { + return fmt.Errorf("could not stat %s: %v", root, err) + } + + fmt.Println(fi.Name()) + if !fi.IsDir() { + return nil + } + + fis, err := ioutil.ReadDir(root) + if err != nil { + return fmt.Errorf("could not read dir %s: %v", root, err) + } + + var names []string + for _, fi := range fis { + if fi.Name()[0] != '.' { + names = append(names, fi.Name()) + } + } + + for i, name := range names { + add := "│ " + if i == len(names)-1 { + fmt.Printf(indent + "└──") + add = " " + } else { + fmt.Printf(indent + "├──") + } + + if err := tree(filepath.Join(root, name), indent+add); err != nil { + return err + } + } + + return nil +} diff --git a/version.go b/cmd/version.go similarity index 56% rename from version.go rename to cmd/version.go index a101709..2b2b4a8 100644 --- a/version.go +++ b/cmd/version.go @@ -1,8 +1,10 @@ -package main +package cmd import ( "fmt" "runtime" + + "github.com/spf13/cobra" ) var ( @@ -11,6 +13,19 @@ var ( date = "I don't remember exactly" ) +// versionCmd represents the version command +var versionCmd = &cobra.Command{ + Use: "version", + Short: "Display version", + Run: func(cmd *cobra.Command, args []string) { + displayVersion(rootCmd.Name()) + }, +} + +func init() { + rootCmd.AddCommand(versionCmd) +} + func displayVersion(name string) { fmt.Printf(name+`: version : %s diff --git a/cmd/zookeeper.go b/cmd/zookeeper.go new file mode 100644 index 0000000..11cfa31 --- /dev/null +++ b/cmd/zookeeper.go @@ -0,0 +1,33 @@ +package cmd + +import ( + "github.com/abronan/valkeyrie/store" + "github.com/abronan/valkeyrie/store/zookeeper" + "github.com/ldez/traefik-certs-dumper/dumper" + "github.com/ldez/traefik-certs-dumper/dumper/kv" + "github.com/spf13/cobra" +) + +// zookeeperCmd represents the zookeeper command +var zookeeperCmd = &cobra.Command{ + Use: "zookeeper", + Short: "Dump the content of zookeeper.", + Long: `Dump the content of zookeeper.`, + RunE: runE(zookeeperRun), +} + +func init() { + kvCmd.AddCommand(zookeeperCmd) +} + +func zookeeperRun(baseConfig *dumper.BaseConfig, cmd *cobra.Command) error { + config, err := getKvConfig(cmd) + if err != nil { + return err + } + + config.Backend = store.ZK + zookeeper.Register() + + return kv.Dump(config, baseConfig) +} diff --git a/docs/traefik-certs-dumper.md b/docs/traefik-certs-dumper.md new file mode 100644 index 0000000..4790da9 --- /dev/null +++ b/docs/traefik-certs-dumper.md @@ -0,0 +1,28 @@ +## traefik-certs-dumper + +Dump Let's Encrypt certificates from Traefik. + +### Synopsis + +Dump Let's Encrypt certificates from Traefik. + +### Options + +``` + --config string config file (default is $HOME/.traefik-certs-dumper.yaml) + --crt-ext string The file extension of the generated certificates. (default ".crt") + --crt-name string The file name (without extension) of the generated certificates. (default "certificate") + --dest string Path to store the dump content. (default "./dump") + --domain-subdir Use domain as sub-directory. + -h, --help help for traefik-certs-dumper + --key-ext string The file extension of the generated private keys. (default ".key") + --key-name string The file name (without extension) of the generated private keys. (default "privatekey") +``` + +### SEE ALSO + +* [traefik-certs-dumper file](traefik-certs-dumper_file.md) - Dump the content of the "acme.json" file. +* [traefik-certs-dumper kv](traefik-certs-dumper_kv.md) - Dump the content of a KV store. +* [traefik-certs-dumper version](traefik-certs-dumper_version.md) - Display version + +###### Auto generated by spf13/cobra on 20-Apr-2019 diff --git a/docs/traefik-certs-dumper_file.md b/docs/traefik-certs-dumper_file.md new file mode 100644 index 0000000..5c3dc50 --- /dev/null +++ b/docs/traefik-certs-dumper_file.md @@ -0,0 +1,36 @@ +## traefik-certs-dumper file + +Dump the content of the "acme.json" file. + +### Synopsis + +Dump the content of the "acme.json" file from Traefik to certificates. + +``` +traefik-certs-dumper file [flags] +``` + +### Options + +``` + -h, --help help for file + --source string Path to 'acme.json' file. (default "./acme.json") +``` + +### Options inherited from parent commands + +``` + --config string config file (default is $HOME/.traefik-certs-dumper.yaml) + --crt-ext string The file extension of the generated certificates. (default ".crt") + --crt-name string The file name (without extension) of the generated certificates. (default "certificate") + --dest string Path to store the dump content. (default "./dump") + --domain-subdir Use domain as sub-directory. + --key-ext string The file extension of the generated private keys. (default ".key") + --key-name string The file name (without extension) of the generated private keys. (default "privatekey") +``` + +### SEE ALSO + +* [traefik-certs-dumper](traefik-certs-dumper.md) - Dump Let's Encrypt certificates from Traefik. + +###### Auto generated by spf13/cobra on 20-Apr-2019 diff --git a/docs/traefik-certs-dumper_kv.md b/docs/traefik-certs-dumper_kv.md new file mode 100644 index 0000000..3343203 --- /dev/null +++ b/docs/traefik-certs-dumper_kv.md @@ -0,0 +1,41 @@ +## traefik-certs-dumper kv + +Dump the content of a KV store. + +### Synopsis + +Dump the content of a KV store. + +### Options + +``` + --connection-timeout int Connection timeout in seconds. + --endpoints strings List of endpoints. (default [localhost:8500]) + -h, --help help for kv + --password string Password for connection. + --prefix string Prefix used for KV store. (default "traefik") + --username string Username for connection. + --watch Enable watching changes. +``` + +### Options inherited from parent commands + +``` + --config string config file (default is $HOME/.traefik-certs-dumper.yaml) + --crt-ext string The file extension of the generated certificates. (default ".crt") + --crt-name string The file name (without extension) of the generated certificates. (default "certificate") + --dest string Path to store the dump content. (default "./dump") + --domain-subdir Use domain as sub-directory. + --key-ext string The file extension of the generated private keys. (default ".key") + --key-name string The file name (without extension) of the generated private keys. (default "privatekey") +``` + +### SEE ALSO + +* [traefik-certs-dumper](traefik-certs-dumper.md) - Dump Let's Encrypt certificates from Traefik. +* [traefik-certs-dumper kv boltdb](traefik-certs-dumper_kv_boltdb.md) - Dump the content of BoltDB. +* [traefik-certs-dumper kv consul](traefik-certs-dumper_kv_consul.md) - Dump the content of Consul. +* [traefik-certs-dumper kv etcd](traefik-certs-dumper_kv_etcd.md) - Dump the content of etcd. +* [traefik-certs-dumper kv zookeeper](traefik-certs-dumper_kv_zookeeper.md) - Dump the content of zookeeper. + +###### Auto generated by spf13/cobra on 20-Apr-2019 diff --git a/docs/traefik-certs-dumper_kv_boltdb.md b/docs/traefik-certs-dumper_kv_boltdb.md new file mode 100644 index 0000000..d4d1d71 --- /dev/null +++ b/docs/traefik-certs-dumper_kv_boltdb.md @@ -0,0 +1,43 @@ +## traefik-certs-dumper kv boltdb + +Dump the content of BoltDB. + +### Synopsis + +Dump the content of BoltDB. + +``` +traefik-certs-dumper kv boltdb [flags] +``` + +### Options + +``` + --bucket string Bucket for boltdb. (default "traefik") + -h, --help help for boltdb + --persist-connection Persist connection for boltdb. +``` + +### Options inherited from parent commands + +``` + --config string config file (default is $HOME/.traefik-certs-dumper.yaml) + --connection-timeout int Connection timeout in seconds. + --crt-ext string The file extension of the generated certificates. (default ".crt") + --crt-name string The file name (without extension) of the generated certificates. (default "certificate") + --dest string Path to store the dump content. (default "./dump") + --domain-subdir Use domain as sub-directory. + --endpoints strings List of endpoints. (default [localhost:8500]) + --key-ext string The file extension of the generated private keys. (default ".key") + --key-name string The file name (without extension) of the generated private keys. (default "privatekey") + --password string Password for connection. + --prefix string Prefix used for KV store. (default "traefik") + --username string Username for connection. + --watch Enable watching changes. +``` + +### SEE ALSO + +* [traefik-certs-dumper kv](traefik-certs-dumper_kv.md) - Dump the content of a KV store. + +###### Auto generated by spf13/cobra on 20-Apr-2019 diff --git a/docs/traefik-certs-dumper_kv_consul.md b/docs/traefik-certs-dumper_kv_consul.md new file mode 100644 index 0000000..ce9250c --- /dev/null +++ b/docs/traefik-certs-dumper_kv_consul.md @@ -0,0 +1,42 @@ +## traefik-certs-dumper kv consul + +Dump the content of Consul. + +### Synopsis + +Dump the content of Consul. + +``` +traefik-certs-dumper kv consul [flags] +``` + +### Options + +``` + -h, --help help for consul + --token string Token for consul. +``` + +### Options inherited from parent commands + +``` + --config string config file (default is $HOME/.traefik-certs-dumper.yaml) + --connection-timeout int Connection timeout in seconds. + --crt-ext string The file extension of the generated certificates. (default ".crt") + --crt-name string The file name (without extension) of the generated certificates. (default "certificate") + --dest string Path to store the dump content. (default "./dump") + --domain-subdir Use domain as sub-directory. + --endpoints strings List of endpoints. (default [localhost:8500]) + --key-ext string The file extension of the generated private keys. (default ".key") + --key-name string The file name (without extension) of the generated private keys. (default "privatekey") + --password string Password for connection. + --prefix string Prefix used for KV store. (default "traefik") + --username string Username for connection. + --watch Enable watching changes. +``` + +### SEE ALSO + +* [traefik-certs-dumper kv](traefik-certs-dumper_kv.md) - Dump the content of a KV store. + +###### Auto generated by spf13/cobra on 20-Apr-2019 diff --git a/docs/traefik-certs-dumper_kv_etcd.md b/docs/traefik-certs-dumper_kv_etcd.md new file mode 100644 index 0000000..b7077c0 --- /dev/null +++ b/docs/traefik-certs-dumper_kv_etcd.md @@ -0,0 +1,42 @@ +## traefik-certs-dumper kv etcd + +Dump the content of etcd. + +### Synopsis + +Dump the content of etcd. + +``` +traefik-certs-dumper kv etcd [flags] +``` + +### Options + +``` + -h, --help help for etcd + --sync-period int Sync period for etcd in seconds. +``` + +### Options inherited from parent commands + +``` + --config string config file (default is $HOME/.traefik-certs-dumper.yaml) + --connection-timeout int Connection timeout in seconds. + --crt-ext string The file extension of the generated certificates. (default ".crt") + --crt-name string The file name (without extension) of the generated certificates. (default "certificate") + --dest string Path to store the dump content. (default "./dump") + --domain-subdir Use domain as sub-directory. + --endpoints strings List of endpoints. (default [localhost:8500]) + --key-ext string The file extension of the generated private keys. (default ".key") + --key-name string The file name (without extension) of the generated private keys. (default "privatekey") + --password string Password for connection. + --prefix string Prefix used for KV store. (default "traefik") + --username string Username for connection. + --watch Enable watching changes. +``` + +### SEE ALSO + +* [traefik-certs-dumper kv](traefik-certs-dumper_kv.md) - Dump the content of a KV store. + +###### Auto generated by spf13/cobra on 20-Apr-2019 diff --git a/docs/traefik-certs-dumper_kv_zookeeper.md b/docs/traefik-certs-dumper_kv_zookeeper.md new file mode 100644 index 0000000..9e275b6 --- /dev/null +++ b/docs/traefik-certs-dumper_kv_zookeeper.md @@ -0,0 +1,41 @@ +## traefik-certs-dumper kv zookeeper + +Dump the content of zookeeper. + +### Synopsis + +Dump the content of zookeeper. + +``` +traefik-certs-dumper kv zookeeper [flags] +``` + +### Options + +``` + -h, --help help for zookeeper +``` + +### Options inherited from parent commands + +``` + --config string config file (default is $HOME/.traefik-certs-dumper.yaml) + --connection-timeout int Connection timeout in seconds. + --crt-ext string The file extension of the generated certificates. (default ".crt") + --crt-name string The file name (without extension) of the generated certificates. (default "certificate") + --dest string Path to store the dump content. (default "./dump") + --domain-subdir Use domain as sub-directory. + --endpoints strings List of endpoints. (default [localhost:8500]) + --key-ext string The file extension of the generated private keys. (default ".key") + --key-name string The file name (without extension) of the generated private keys. (default "privatekey") + --password string Password for connection. + --prefix string Prefix used for KV store. (default "traefik") + --username string Username for connection. + --watch Enable watching changes. +``` + +### SEE ALSO + +* [traefik-certs-dumper kv](traefik-certs-dumper_kv.md) - Dump the content of a KV store. + +###### Auto generated by spf13/cobra on 20-Apr-2019 diff --git a/docs/traefik-certs-dumper_version.md b/docs/traefik-certs-dumper_version.md new file mode 100644 index 0000000..2a40ad3 --- /dev/null +++ b/docs/traefik-certs-dumper_version.md @@ -0,0 +1,35 @@ +## traefik-certs-dumper version + +Display version + +### Synopsis + +Display version + +``` +traefik-certs-dumper version [flags] +``` + +### Options + +``` + -h, --help help for version +``` + +### Options inherited from parent commands + +``` + --config string config file (default is $HOME/.traefik-certs-dumper.yaml) + --crt-ext string The file extension of the generated certificates. (default ".crt") + --crt-name string The file name (without extension) of the generated certificates. (default "certificate") + --dest string Path to store the dump content. (default "./dump") + --domain-subdir Use domain as sub-directory. + --key-ext string The file extension of the generated private keys. (default ".key") + --key-name string The file name (without extension) of the generated private keys. (default "privatekey") +``` + +### SEE ALSO + +* [traefik-certs-dumper](traefik-certs-dumper.md) - Dump Let's Encrypt certificates from Traefik. + +###### Auto generated by spf13/cobra on 20-Apr-2019 diff --git a/dumper.go b/dumper.go deleted file mode 100644 index be31822..0000000 --- a/dumper.go +++ /dev/null @@ -1,141 +0,0 @@ -package main - -import ( - "encoding/json" - "encoding/pem" - "io/ioutil" - "os" - "path/filepath" - - "github.com/xenolf/lego/certcrypto" - "github.com/xenolf/lego/registration" -) - -const ( - certsSubDir = "certs" - keysSubDir = "private" -) - -// StoredData represents the data managed by the Store -type StoredData struct { - Account *Account - Certificates []*Certificate - HTTPChallenges map[string]map[string][]byte - TLSChallenges map[string]*Certificate -} - -// Certificate is a struct which contains all data needed from an ACME certificate -type Certificate struct { - Domain Domain - Certificate []byte - Key []byte -} - -// Domain holds a domain name with SANs -type Domain struct { - Main string - SANs []string -} - -// Account is used to store lets encrypt registration info -type Account struct { - Email string - Registration *registration.Resource - PrivateKey []byte - KeyType certcrypto.KeyType -} - -type fileInfo struct { - Name string - Ext string -} - -func dump(acmeFile, dumpPath string, crtInfo, keyInfo fileInfo, domainSubDir bool) error { - f, err := os.Open(acmeFile) - if err != nil { - return err - } - - data := StoredData{} - if err = json.NewDecoder(f).Decode(&data); err != nil { - return err - } - - if err = os.RemoveAll(dumpPath); err != nil { - return err - } - - if !domainSubDir { - if err = os.MkdirAll(filepath.Join(dumpPath, certsSubDir), 0755); err != nil { - return err - } - } - - if err = os.MkdirAll(filepath.Join(dumpPath, keysSubDir), 0755); err != nil { - return err - } - - privateKeyPem := extractPEMPrivateKey(data.Account) - err = ioutil.WriteFile(filepath.Join(dumpPath, keysSubDir, "letsencrypt"+keyInfo.Ext), privateKeyPem, 0666) - if err != nil { - return err - } - - for _, cert := range data.Certificates { - err := writeCert(dumpPath, cert, crtInfo, domainSubDir) - if err != nil { - return err - } - - err = writeKey(dumpPath, cert, keyInfo, domainSubDir) - if err != nil { - return err - } - } - - return nil -} - -func writeCert(dumpPath string, cert *Certificate, info fileInfo, domainSubDir bool) error { - certPath := filepath.Join(dumpPath, certsSubDir, cert.Domain.Main+info.Ext) - if domainSubDir { - certPath = filepath.Join(dumpPath, cert.Domain.Main, info.Name+info.Ext) - if err := os.MkdirAll(filepath.Join(dumpPath, cert.Domain.Main), 0755); err != nil { - return err - } - } - - return ioutil.WriteFile(certPath, cert.Certificate, 0666) -} - -func writeKey(dumpPath string, cert *Certificate, info fileInfo, domainSubDir bool) error { - keyPath := filepath.Join(dumpPath, keysSubDir, cert.Domain.Main+info.Ext) - if domainSubDir { - keyPath = filepath.Join(dumpPath, cert.Domain.Main, info.Name+info.Ext) - if err := os.MkdirAll(filepath.Join(dumpPath, cert.Domain.Main), 0755); err != nil { - return err - } - } - - return ioutil.WriteFile(keyPath, cert.Key, 0666) -} - -func extractPEMPrivateKey(account *Account) []byte { - var block *pem.Block - switch account.KeyType { - case certcrypto.RSA2048, certcrypto.RSA4096, certcrypto.RSA8192: - block = &pem.Block{ - Type: "RSA PRIVATE KEY", - Bytes: account.PrivateKey, - } - case certcrypto.EC256, certcrypto.EC384: - block = &pem.Block{ - Type: "EC PRIVATE KEY", - Bytes: account.PrivateKey, - } - default: - panic("unsupported key type") - } - - return pem.EncodeToMemory(block) -} diff --git a/dumper/config.go b/dumper/config.go new file mode 100644 index 0000000..94dc780 --- /dev/null +++ b/dumper/config.go @@ -0,0 +1,9 @@ +package dumper + +// BaseConfig Base dump command configuration. +type BaseConfig struct { + DumpPath string + CrtInfo FileInfo + KeyInfo FileInfo + DomainSubDir bool +} diff --git a/dumper/dumper.go b/dumper/dumper.go new file mode 100644 index 0000000..e523687 --- /dev/null +++ b/dumper/dumper.go @@ -0,0 +1,102 @@ +package dumper + +import ( + "encoding/pem" + "io/ioutil" + "os" + "path/filepath" + + "github.com/go-acme/lego/certcrypto" +) + +const ( + certsSubDir = "certs" + keysSubDir = "private" +) + +// FileInfo File information. +type FileInfo struct { + Name string + Ext string +} + +// Dump Dumps data to certificates. +func Dump(data *StoredData, baseConfig *BaseConfig) error { + if err := os.RemoveAll(baseConfig.DumpPath); err != nil { + return err + } + + if !baseConfig.DomainSubDir { + if err := os.MkdirAll(filepath.Join(baseConfig.DumpPath, certsSubDir), 0755); err != nil { + return err + } + } + + if err := os.MkdirAll(filepath.Join(baseConfig.DumpPath, keysSubDir), 0755); err != nil { + return err + } + + privateKeyPem := extractPEMPrivateKey(data.Account) + err := ioutil.WriteFile(filepath.Join(baseConfig.DumpPath, keysSubDir, "letsencrypt"+baseConfig.KeyInfo.Ext), privateKeyPem, 0666) + if err != nil { + return err + } + + for _, cert := range data.Certificates { + err := writeCert(baseConfig.DumpPath, cert, baseConfig.CrtInfo, baseConfig.DomainSubDir) + if err != nil { + return err + } + + err = writeKey(baseConfig.DumpPath, cert, baseConfig.KeyInfo, baseConfig.DomainSubDir) + if err != nil { + return err + } + } + + return nil +} + +func writeCert(dumpPath string, cert *Certificate, info FileInfo, domainSubDir bool) error { + certPath := filepath.Join(dumpPath, certsSubDir, cert.Domain.Main+info.Ext) + if domainSubDir { + certPath = filepath.Join(dumpPath, cert.Domain.Main, info.Name+info.Ext) + if err := os.MkdirAll(filepath.Join(dumpPath, cert.Domain.Main), 0755); err != nil { + return err + } + } + + return ioutil.WriteFile(certPath, cert.Certificate, 0666) +} + +func writeKey(dumpPath string, cert *Certificate, info FileInfo, domainSubDir bool) error { + keyPath := filepath.Join(dumpPath, keysSubDir, cert.Domain.Main+info.Ext) + if domainSubDir { + keyPath = filepath.Join(dumpPath, cert.Domain.Main, info.Name+info.Ext) + if err := os.MkdirAll(filepath.Join(dumpPath, cert.Domain.Main), 0755); err != nil { + return err + } + } + + return ioutil.WriteFile(keyPath, cert.Key, 0666) +} + +func extractPEMPrivateKey(account *Account) []byte { + var block *pem.Block + switch account.KeyType { + case certcrypto.RSA2048, certcrypto.RSA4096, certcrypto.RSA8192: + block = &pem.Block{ + Type: "RSA PRIVATE KEY", + Bytes: account.PrivateKey, + } + case certcrypto.EC256, certcrypto.EC384: + block = &pem.Block{ + Type: "EC PRIVATE KEY", + Bytes: account.PrivateKey, + } + default: + panic("unsupported key type") + } + + return pem.EncodeToMemory(block) +} diff --git a/dumper/file/file.go b/dumper/file/file.go new file mode 100644 index 0000000..9653b6e --- /dev/null +++ b/dumper/file/file.go @@ -0,0 +1,32 @@ +package file + +import ( + "encoding/json" + "os" + + "github.com/ldez/traefik-certs-dumper/dumper" +) + +// Dump Dumps "acme.json" file to certificates. +func Dump(acmeFile string, baseConfig *dumper.BaseConfig) error { + data, err := readFile(acmeFile) + if err != nil { + return err + } + + return dumper.Dump(data, baseConfig) +} + +func readFile(acmeFile string) (*dumper.StoredData, error) { + source, err := os.Open(acmeFile) + if err != nil { + return nil, err + } + + data := &dumper.StoredData{} + if err = json.NewDecoder(source).Decode(data); err != nil { + return nil, err + } + + return data, nil +} diff --git a/dumper/info.go b/dumper/info.go new file mode 100644 index 0000000..793f1f7 --- /dev/null +++ b/dumper/info.go @@ -0,0 +1,35 @@ +package dumper + +import ( + "github.com/go-acme/lego/certcrypto" + "github.com/go-acme/lego/registration" +) + +// StoredData represents the data managed by the Store +type StoredData struct { + Account *Account + Certificates []*Certificate + HTTPChallenges map[string]map[string][]byte + TLSChallenges map[string]*Certificate +} + +// Certificate is a struct which contains all data needed from an ACME certificate +type Certificate struct { + Domain Domain + Certificate []byte + Key []byte +} + +// Domain holds a domain name with SANs +type Domain struct { + Main string + SANs []string +} + +// Account is used to store lets encrypt registration info +type Account struct { + Email string + Registration *registration.Resource + PrivateKey []byte + KeyType certcrypto.KeyType +} diff --git a/dumper/kv/config.go b/dumper/kv/config.go new file mode 100644 index 0000000..f2f5e10 --- /dev/null +++ b/dumper/kv/config.go @@ -0,0 +1,12 @@ +package kv + +import "github.com/abronan/valkeyrie/store" + +// Config KV configuration. +type Config struct { + Backend store.Backend + Prefix string + Endpoints []string + Watch bool + Options *store.Config +} diff --git a/dumper/kv/convert.go b/dumper/kv/convert.go new file mode 100644 index 0000000..95ce86f --- /dev/null +++ b/dumper/kv/convert.go @@ -0,0 +1,65 @@ +package kv + +import ( + "github.com/go-acme/lego/certcrypto" + "github.com/go-acme/lego/registration" + "github.com/ldez/traefik-certs-dumper/dumper" +) + +// CertificateV1 is used to store certificate info +type CertificateV1 struct { + Domain string + CertURL string + CertStableURL string + PrivateKey []byte + Certificate []byte +} + +// AccountV1 is used to store lets encrypt registration info +type AccountV1 struct { + Email string + Registration *registration.Resource + PrivateKey []byte + KeyType certcrypto.KeyType + DomainsCertificate DomainsCertificates + ChallengeCerts map[string]*ChallengeCert + HTTPChallenge map[string]map[string][]byte +} + +// DomainsCertificates stores a certificate for multiple domains +type DomainsCertificates struct { + Certs []*DomainsCertificate +} + +// ChallengeCert stores a challenge certificate +type ChallengeCert struct { + Certificate []byte + PrivateKey []byte +} + +// DomainsCertificate contains a certificate for multiple domains +type DomainsCertificate struct { + Domains dumper.Domain + Certificate *CertificateV1 +} + +// convertAccountV1ToV2 converts account information from version 1 to 2 +func convertAccountV1ToV2(account *AccountV1) *dumper.StoredData { + storedData := &dumper.StoredData{} + storedData.Account = &dumper.Account{ + PrivateKey: account.PrivateKey, + Registration: account.Registration, + Email: account.Email, + KeyType: account.KeyType, + } + var certs []*dumper.Certificate + for _, oldCert := range account.DomainsCertificate.Certs { + certs = append(certs, &dumper.Certificate{ + Certificate: oldCert.Certificate.Certificate, + Domain: oldCert.Domains, + Key: oldCert.Certificate.PrivateKey, + }) + } + storedData.Certificates = certs + return storedData +} diff --git a/dumper/kv/kv.go b/dumper/kv/kv.go new file mode 100644 index 0000000..c1f6cda --- /dev/null +++ b/dumper/kv/kv.go @@ -0,0 +1,88 @@ +package kv + +import ( + "bytes" + "compress/gzip" + "encoding/json" + "fmt" + "io/ioutil" + "log" + + "github.com/abronan/valkeyrie" + "github.com/abronan/valkeyrie/store" + "github.com/ldez/traefik-certs-dumper/dumper" +) + +const storeKeySuffix = "/acme/account/object" + +// Dump Dumps KV content to certificates. +func Dump(config *Config, baseConfig *dumper.BaseConfig) error { + kvStore, err := valkeyrie.NewStore(config.Backend, config.Endpoints, config.Options) + if err != nil { + return fmt.Errorf("unable to create client of the store: %v", err) + } + + storeKey := config.Prefix + storeKeySuffix + + if config.Watch { + return watch(kvStore, storeKey, baseConfig) + } + + pair, err := kvStore.Get(storeKey, nil) + if err != nil { + return fmt.Errorf("unable to retrieve %s value: %v", storeKey, err) + } + + return dumpPair(pair, baseConfig) +} + +func watch(kvStore store.Store, storeKey string, baseConfig *dumper.BaseConfig) error { + stopCh := make(<-chan struct{}) + + pairs, err := kvStore.Watch(storeKey, stopCh, nil) + if err != nil { + return err + } + + for { + pair := <-pairs + if pair == nil { + return fmt.Errorf("could not fetch Key/Value pair for key %v", storeKey) + } + + err = dumpPair(pair, baseConfig) + if err != nil { + return err + } + + log.Println("Dumped new certificate data.") + } +} + +func dumpPair(pair *store.KVPair, baseConfig *dumper.BaseConfig) error { + data, err := getStoredDataFromGzip(pair) + if err != nil { + return err + } + + return dumper.Dump(data, baseConfig) +} + +func getStoredDataFromGzip(pair *store.KVPair) (*dumper.StoredData, error) { + reader, err := gzip.NewReader(bytes.NewBuffer(pair.Value)) + if err != nil { + return nil, fmt.Errorf("fail to create GZip reader: %v", err) + } + + acmeData, err := ioutil.ReadAll(reader) + if err != nil { + return nil, fmt.Errorf("unable to read the pair content: %v", err) + } + + account := &AccountV1{} + if err := json.Unmarshal(acmeData, &account); err != nil { + return nil, fmt.Errorf("unable marshal AccountV1: %v", err) + } + + return convertAccountV1ToV2(account), nil +} diff --git a/go.mod b/go.mod index 9ef9c76..ba59f6d 100644 --- a/go.mod +++ b/go.mod @@ -1,11 +1,47 @@ module github.com/ldez/traefik-certs-dumper +go 1.12 + require ( + github.com/abronan/valkeyrie v0.0.0-20190419181538-ccf7df650fe4 github.com/cenkalti/backoff v2.1.1+incompatible // indirect + github.com/coreos/bbolt v1.3.2 // indirect + github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e // indirect + github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f // indirect + github.com/cpuguy83/go-md2man v1.0.10 // indirect + github.com/dgrijalva/jwt-go v3.2.0+incompatible // indirect + github.com/go-acme/lego v2.5.0+incompatible + github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef // indirect + github.com/google/btree v1.0.0 // indirect + github.com/gorilla/websocket v1.4.0 // indirect + github.com/grpc-ecosystem/go-grpc-middleware v1.0.0 // indirect + github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 // indirect + github.com/grpc-ecosystem/grpc-gateway v1.8.5 // indirect + github.com/hashicorp/go-msgpack v0.5.4 // indirect + github.com/hashicorp/go-uuid v1.0.1 // indirect + github.com/hashicorp/memberlist v0.1.3 // indirect github.com/inconshreveable/mousetrap v1.0.0 // indirect + github.com/jonboulle/clockwork v0.1.0 // indirect + github.com/mitchellh/go-homedir v1.1.0 + github.com/mitchellh/go-testing-interface v1.0.0 // indirect + github.com/pascaldekloe/goe v0.1.0 // indirect + github.com/pkg/errors v0.8.1 // indirect + github.com/prometheus/client_golang v0.9.2 // indirect + github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90 // indirect + github.com/sirupsen/logrus v1.4.1 // indirect + github.com/soheilhy/cmux v0.1.4 // indirect github.com/spf13/cobra v0.0.3 - github.com/spf13/pflag v1.0.3 // indirect - github.com/xenolf/lego v2.2.0+incompatible - golang.org/x/crypto v0.0.0-20190208162236-193df9c0f06f // indirect - gopkg.in/square/go-jose.v2 v2.2.2 // indirect + github.com/spf13/viper v1.3.2 + github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5 // indirect + github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 // indirect + go.uber.org/atomic v1.3.2 // indirect + go.uber.org/multierr v1.1.0 // indirect + go.uber.org/zap v1.9.1 // indirect + golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 // indirect + gopkg.in/square/go-jose.v2 v2.3.1 // indirect +) + +replace ( + github.com/ugorji/go => github.com/ugorji/go v1.1.2-0.20181022190402-e5e69e061d4f + github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8 => github.com/ugorji/go/codec v1.1.2-0.20181022190402-e5e69e061d4f ) diff --git a/go.sum b/go.sum index 945f965..b2b4fa5 100644 --- a/go.sum +++ b/go.sum @@ -1,21 +1,171 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/abronan/valkeyrie v0.0.0-20190419181538-ccf7df650fe4 h1:DrTAEU8rVfy2tRZObh8Hdjs819By7XfFhoOKh8xqX7Y= +github.com/abronan/valkeyrie v0.0.0-20190419181538-ccf7df650fe4/go.mod h1:NOvlKBjVll/vPwdjPHGLNhKk7VrnLzLGU/VGOVPLiog= +github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= +github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= +github.com/aws/aws-sdk-go v1.16.23/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= +github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/cenkalti/backoff v2.1.1+incompatible h1:tKJnvO2kl0zmb/jA5UKAt4VoEVw1qxKWjE/Bpp46npY= github.com/cenkalti/backoff v2.1.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= -github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= +github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= +github.com/coreos/etcd v3.3.11+incompatible h1:0gCnqKsq7XxMi69JsnbmMc1o+RJH3XH64sV9aiTTYko= +github.com/coreos/etcd v3.3.11+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= +github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= +github.com/coreos/go-semver v0.2.0 h1:3Jm3tLmsgAYcjC+4Up7hJrFBPr+n7rAqYeSw/SZazuY= +github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= +github.com/cpuguy83/go-md2man v1.0.10 h1:BSKMNlYxDvnunlTymqtgONjNnaRV1sTpcovwwjF22jk= +github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= +github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/go-acme/lego v2.5.0+incompatible h1:5fNN9yRQfv8ymH3DSsxla+4aYeQt2IgfZqHKVnK8f0s= +github.com/go-acme/lego v2.5.0+incompatible/go.mod h1:yzMNe9CasVUhkquNvti5nAtPmG94USbYxYrZfTkIn0M= +github.com/gogo/protobuf v1.2.0 h1:xU6/SpYbvkNYiptHJYEDRseDLvYE7wSqhYYNy0QSUzI= +github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= +github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= +github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= +github.com/grpc-ecosystem/grpc-gateway v1.8.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= +github.com/hashicorp/consul v1.4.0 h1:PQTW4xCuAExEiSbhrsFsikzbW5gVBoi74BjUvYFyKHw= +github.com/hashicorp/consul v1.4.0/go.mod h1:mFrjN1mfidgJfYP1xrJCF+AfRhr6Eaqhb2+sfyn/OOI= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-cleanhttp v0.5.0 h1:wvCrVc9TjDls6+YGAF2hAifE1E5U1+b4tH6KdvN3Gig= +github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= +github.com/hashicorp/go-msgpack v0.5.4/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= +github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= +github.com/hashicorp/go-rootcerts v1.0.0 h1:Rqb66Oo1X/eSV1x66xbDccZjhJigjg0+e82kpwzSwCI= +github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= +github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= +github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= +github.com/hashicorp/serf v0.8.1 h1:mYs6SMzu72+90OcPa5wr3nfznA4Dw9UyR791ZFNOIf4= +github.com/hashicorp/serf v0.8.1/go.mod h1:h/Ru6tmZazX7WO/GDmwdpS975F019L4t5ng5IgwbNrE= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= +github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/magiconair/properties v1.8.0 h1:LLgXmsheXeRoUOBOjtwPQCWIYqM/LU1ayDtDePerRcY= +github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= +github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= +github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= +github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= +github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= +github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc= +github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_golang v0.9.2/go.mod h1:OsXs2jCmiKlQ1lTBmv21f2mNfw4xf/QclQDMrYNZzcM= +github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/common v0.0.0-20181126121408-4724e9255275/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= +github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= +github.com/russross/blackfriday v1.5.2 h1:HyvC0ARfnZBqnXwABFeSZHpKvJHJJfPz81GNueLj0oo= +github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= +github.com/samuel/go-zookeeper v0.0.0-20180130194729-c4fab1ac1bec h1:6ncX5ko6B9LntYM0YBRXkiSaZMmLYeZ/NWcmeB43mMY= +github.com/samuel/go-zookeeper v0.0.0-20180130194729-c4fab1ac1bec/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E= +github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= +github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= +github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= +github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI= +github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= +github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8= +github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cobra v0.0.3 h1:ZlrZ4XsMRm04Fr5pSFxBgfND2EBVa1nLpiy1stUsX/8= github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= +github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk= +github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/viper v1.3.2 h1:VUFqw5KcqRf7i70GOzW7N+Q7+gxVBkSSqiXB12+JQ4M= +github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/xenolf/lego v2.2.0+incompatible h1:r4UAcpgPmX3j0aThoVrRM1FFLcvyy08UyGbIwFU4zoQ= -github.com/xenolf/lego v2.2.0+incompatible/go.mod h1:fwiGnfsIjG7OHPfOvgK7Y/Qo6+2Ox0iozjNTkZICKbY= -golang.org/x/crypto v0.0.0-20190208162236-193df9c0f06f h1:ETU2VEl7TnT5bl7IvuKEzTDpplg5wzGYsOCAPhdoEIg= -golang.org/x/crypto v0.0.0-20190208162236-193df9c0f06f/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -gopkg.in/square/go-jose.v2 v2.2.2 h1:orlkJ3myw8CN1nVQHBFfloD+L3egixIa4FvUP6RosSA= -gopkg.in/square/go-jose.v2 v2.2.2/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= +github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= +github.com/ugorji/go v1.1.2-0.20181022190402-e5e69e061d4f h1:E6ip3gLExd3v9o1iiZMMxOaC/XiWk3mPbDTOPLL0eWw= +github.com/ugorji/go v1.1.2-0.20181022190402-e5e69e061d4f/go.mod h1:hnLbHMwcvSihnDhEfx2/BzKp2xb0Y+ErdfYcrs9tkJQ= +github.com/ugorji/go/codec v1.1.2-0.20181022190402-e5e69e061d4f h1:CZG9W9a8rpiPXPmkGcyXoD9sLF+JfLh/x+BpYHGhK+o= +github.com/ugorji/go/codec v1.1.2-0.20181022190402-e5e69e061d4f/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= +github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= +github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= +go.etcd.io/bbolt v1.3.1-etcd.8 h1:6J7QAKqfFBGnU80KRnuQxfjjeE5xAGE/qB810I3FQHQ= +go.etcd.io/bbolt v1.3.1-etcd.8/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= +go.etcd.io/etcd v3.3.11+incompatible h1:AVwRXu9VIzZcvVe1nSirTVkNv7WT3/hwdMRrDVFsf3A= +go.etcd.io/etcd v3.3.11+incompatible/go.mod h1:yaeTdrJi5lOmYerz05bd8+V7KubZs8YSFZfzsF9A6aI= +go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= +go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9 h1:mKdxBk7AujPs8kU4m80U72y/zjbZ3UcXC7dClwKbUI0= +golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181220203305-927f97764cc3 h1:eH6Eip3UpmR+yM/qI9Ijluzb1bNv/cAU/n+6l8tRSis= +golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a h1:1n5lsVfiQW3yfsRGu98756EH1YthsFqr/5mxHduZW2A= +golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8 h1:Nw54tB0rB7hY/N0NQvRW8DG4Yk3Q6T9cu9RcFQDu1tc= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/grpc v1.18.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= +google.golang.org/grpc v1.19.0 h1:cfg4PD8YEdSFnm7qLV4++93WcmhH2nIUhMjhdCvl3j8= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/redis.v5 v5.2.9/go.mod h1:6gtv0/+A4iM08kdRfocWYB3bLX2tebpNtfKlFT6H4mY= +gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= +gopkg.in/square/go-jose.v2 v2.3.1 h1:SK5KegNXmKmqE342YYN2qPHEnUYeoMiXXl1poUlI+o4= +gopkg.in/square/go-jose.v2 v2.3.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= +gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= +gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/integrationtest/docker-compose.yml b/integrationtest/docker-compose.yml new file mode 100644 index 0000000..2a397c6 --- /dev/null +++ b/integrationtest/docker-compose.yml @@ -0,0 +1,18 @@ +version: '3' + +services: + consul-kv: + image: consul + ports: + - "8500:8500" + + zookeeper-kv: + image: zookeeper + ports: + - "2181:2181" + + etcd-kv: + image: quay.io/coreos/etcd:v3.3.12 + command: etcd --listen-client-urls http://0.0.0.0:2379 --advertise-client-urls http://0.0.0.0:2380 + ports: + - "2379:2379" diff --git a/integrationtest/loader.go b/integrationtest/loader.go new file mode 100644 index 0000000..e4dd4b8 --- /dev/null +++ b/integrationtest/loader.go @@ -0,0 +1,121 @@ +package main + +import ( + "bytes" + "compress/gzip" + "io/ioutil" + "log" + "time" + + "github.com/abronan/valkeyrie" + "github.com/abronan/valkeyrie/store" + "github.com/abronan/valkeyrie/store/boltdb" + "github.com/abronan/valkeyrie/store/consul" + etcdv3 "github.com/abronan/valkeyrie/store/etcd/v3" + "github.com/abronan/valkeyrie/store/zookeeper" +) + +const storeKey = "traefik/acme/account/object" + +func main() { + log.SetFlags(log.Lshortfile) + + source := "./acme.json" + err := loadData(source) + if err != nil { + log.Fatal(err) + } +} + +func loadData(source string) error { + content, err := readFile(source) + if err != nil { + return err + } + + // Consul + err = putData(store.CONSUL, []string{"localhost:8500"}, content) + if err != nil { + return err + } + + // ETCD v3 + err = putData(store.ETCDV3, []string{"localhost:2379"}, content) + if err != nil { + return err + } + + // Zookeeper + err = putData(store.ZK, []string{"localhost:2181"}, content) + if err != nil { + return err + } + + // BoltDB + err = putData(store.BOLTDB, []string{"/tmp/test-traefik-certs-dumper.db"}, content) + if err != nil { + return err + } + + return nil +} + +func putData(backend store.Backend, addrs []string, content []byte) error { + storeConfig := &store.Config{ + ConnectionTimeout: 3 * time.Second, + Bucket: "traefik", + } + + switch backend { + case store.CONSUL: + consul.Register() + case store.ETCDV3: + etcdv3.Register() + case store.ZK: + zookeeper.Register() + case store.BOLTDB: + boltdb.Register() + } + + kvStore, err := valkeyrie.NewStore(backend, addrs, storeConfig) + if err != nil { + return err + } + + if err := kvStore.Put(storeKey, content, nil); err != nil { + return err + } + + log.Printf("Successfully updated %s.\n", backend) + return nil +} + +func readFile(source string) ([]byte, error) { + content, err := ioutil.ReadFile(source) + if err != nil { + return nil, err + } + + var b bytes.Buffer + gz := gzip.NewWriter(&b) + + defer func() { + if errC := gz.Close(); errC != nil { + log.Println(errC) + } + }() + + if _, err = gz.Write(content); err != nil { + return nil, err + } + + if err = gz.Flush(); err != nil { + return nil, err + } + + if err := gz.Close(); err != nil { + return nil, err + } + + return b.Bytes(), nil +} diff --git a/integrationtest/readme.md b/integrationtest/readme.md new file mode 100644 index 0000000..a1ba456 --- /dev/null +++ b/integrationtest/readme.md @@ -0,0 +1,42 @@ +# Integration testing + +## Preparation + +- Create valid ACME file `./acme.json` + +- Start backends using docker + +```bash +docker-compose -f integrationtest/docker-compose.yml up +``` + +- Initialize backends + +```bash +go run integrationtest/loader.go +``` + +## Run certs dumper without watching + +```bash +# http://localhost:8500/ui/ +traefik-certs-dumper kv consul --endpoints localhost:8500 + +traefik-certs-dumper kv etcd --endpoints localhost:2379 + +traefik-certs-dumper kv boltdb --endpoints /tmp/test-traefik-certs-dumper.db + +traefik-certs-dumper kv zookeeper --endpoints localhost:2181 +``` + +## Run certs dumper with watching + +While watching is enabled, run `loader.go` again for KV backends or manipulate `/tmp/acme.json` for file backend that change events are triggered. + +```bash +traefik-certs-dumper kv consul --watch --endpoints localhost:8500 + +traefik-certs-dumper kv etcd --watch --endpoints localhost:2379 + +traefik-certs-dumper kv zookeeper --watch --endpoints localhost:2181 +``` diff --git a/main.go b/main.go index b629b69..075f710 100644 --- a/main.go +++ b/main.go @@ -1,126 +1,12 @@ package main import ( - "fmt" - "io/ioutil" - "os" - "path/filepath" - "strconv" + "log" - "github.com/spf13/cobra" + "github.com/ldez/traefik-certs-dumper/cmd" ) func main() { - var rootCmd = &cobra.Command{ - Use: "traefik-certs-dumper", - Short: "Dump Let's Encrypt certificates from Traefik", - Long: `Dump the content of the "acme.json" file from Traefik to certificates.`, - Version: version, - } - - var dumpCmd = &cobra.Command{ - Use: "dump", - Short: "Dump Let's Encrypt certificates from Traefik", - Long: `Dump the content of the "acme.json" file from Traefik to certificates.`, - PreRunE: func(cmd *cobra.Command, args []string) error { - crtExt := cmd.Flag("crt-ext").Value.String() - keyExt := cmd.Flag("key-ext").Value.String() - - subDir, _ := strconv.ParseBool(cmd.Flag("domain-subdir").Value.String()) - if !subDir { - if crtExt == keyExt { - return fmt.Errorf("--crt-ext (%q) and --key-ext (%q) are identical, in this case --domain-subdir is required", crtExt, keyExt) - } - } - - return nil - }, - RunE: func(cmd *cobra.Command, _ []string) error { - acmeFile := cmd.Flag("source").Value.String() - dumpPath := cmd.Flag("dest").Value.String() - - crtInfo := fileInfo{ - Name: cmd.Flag("crt-name").Value.String(), - Ext: cmd.Flag("crt-ext").Value.String(), - } - - keyInfo := fileInfo{ - Name: cmd.Flag("key-name").Value.String(), - Ext: cmd.Flag("key-ext").Value.String(), - } - - subDir, _ := strconv.ParseBool(cmd.Flag("domain-subdir").Value.String()) - - err := dump(acmeFile, dumpPath, crtInfo, keyInfo, subDir) - if err != nil { - return err - } - - return tree(dumpPath, "") - }, - } - - dumpCmd.Flags().String("source", "./acme.json", "Path to 'acme.json' file.") - dumpCmd.Flags().String("dest", "./dump", "Path to store the dump content.") - dumpCmd.Flags().String("crt-ext", ".crt", "The file extension of the generated certificates.") - dumpCmd.Flags().String("crt-name", "certificate", "The file name (without extension) of the generated certificates.") - dumpCmd.Flags().String("key-ext", ".key", "The file extension of the generated private keys.") - dumpCmd.Flags().String("key-name", "privatekey", "The file name (without extension) of the generated private keys.") - dumpCmd.Flags().Bool("domain-subdir", false, "Use domain as sub-directory.") - rootCmd.AddCommand(dumpCmd) - - var versionCmd = &cobra.Command{ - Use: "version", - Short: "Display version", - Run: func(_ *cobra.Command, _ []string) { - displayVersion(rootCmd.Name()) - }, - } - - rootCmd.AddCommand(versionCmd) - - if err := rootCmd.Execute(); err != nil { - fmt.Println(err) - os.Exit(1) - } -} - -func tree(root, indent string) error { - fi, err := os.Stat(root) - if err != nil { - return fmt.Errorf("could not stat %s: %v", root, err) - } - - fmt.Println(fi.Name()) - if !fi.IsDir() { - return nil - } - - fis, err := ioutil.ReadDir(root) - if err != nil { - return fmt.Errorf("could not read dir %s: %v", root, err) - } - - var names []string - for _, fi := range fis { - if fi.Name()[0] != '.' { - names = append(names, fi.Name()) - } - } - - for i, name := range names { - add := "│ " - if i == len(names)-1 { - fmt.Printf(indent + "└──") - add = " " - } else { - fmt.Printf(indent + "├──") - } - - if err := tree(filepath.Join(root, name), indent+add); err != nil { - return err - } - } - - return nil + log.SetFlags(log.LstdFlags | log.Lshortfile) + cmd.Execute() } diff --git a/readme.md b/readme.md index 6a33fa4..967a980 100644 --- a/readme.md +++ b/readme.md @@ -43,47 +43,16 @@ docker run ldez/traefik-certs-dumper: ## Usage -```yaml -Dump the content of the "acme.json" file from Traefik to certificates. - -Usage: - traefik-certs-dumper [command] - -Available Commands: - dump Dump Let's Encrypt certificates from Traefik - help Help about any command - version Display version - -Flags: - -h, --help help for certs-dumper - --version version for certs-dumper - -Use "traefik-certs-dumper [command] --help" for more information about a command. -``` - -```yaml -Dump the content of the "acme.json" file from Traefik to certificates. - -Usage: - traefik-certs-dumper dump [flags] - -Flags: - --crt-ext string The file extension of the generated certificates. (default ".crt") - --crt-name string The file name (without extension) of the generated certificates. (default "certificate") - --dest string Path to store the dump content. (default "./dump") - --domain-subdir Use domain as sub-directory. - -h, --help help for dump - --key-ext string The file extension of the generated private keys. (default ".key") - --key-name string The file name (without extension) of the generated private keys. (default "privatekey") - --source string Path to 'acme.json' file. (default "./acme.json") -``` +- [traefik-certs-dumper](docs/traefik-certs-dumper.md) +- [traefik-certs-dumper file](docs/traefik-certs-dumper_file.md) +- [traefik-certs-dumper kv](docs/traefik-certs-dumper_kv.md) ## Examples ### Simple Dump ```console -$ traefik-certs-dumper dump +$ traefik-certs-dumper file dump ├──certs │ └──my.domain.com.key @@ -96,7 +65,7 @@ dump ### Change source and destination ```console -$ traefik-certs-dumper dump --source ./acme.json --dest ./dump/test +$ traefik-certs-dumper file --source ./acme.json --dest ./dump/test test ├──certs │ └──my.domain.com.key @@ -109,7 +78,7 @@ test ### Use domain as sub-directory ```console -$ traefik-certs-dumper dump --domain-subdir=true +$ traefik-certs-dumper file --domain-subdir=true dump ├──my.domain.com │ ├──certificate.crt @@ -121,7 +90,7 @@ dump #### Change file extension ```console -$ traefik-certs-dumper dump --domain-subdir=true --crt-ext=.pem --key-ext=.pem +$ traefik-certs-dumper file --domain-subdir=true --crt-ext=.pem --key-ext=.pem dump ├──my.domain.com │ ├──certificate.pem @@ -133,7 +102,7 @@ dump #### Change file name ```console -$ traefik-certs-dumper dump --domain-subdir=true --crt-name=fullchain --key-name=privkey +$ traefik-certs-dumper file --domain-subdir=true --crt-name=fullchain --key-name=privkey dump ├──my.domain.com │ ├──fullchain.crt @@ -141,3 +110,31 @@ dump └──private └──letsencrypt.key ``` + +### KV store + +#### Consul + +```console +$ traefik-certs-dumper kv consul --endpoints localhost:8500 +``` + +#### Etcd + +```console +$ traefik-certs-dumper kv etcd --endpoints localhost:2379 +``` + +#### Boltdb + +```console +$ traefik-certs-dumper kv boltdb --endpoints /the/path/to/mydb.db +``` + +#### Zookeeper + +```console +$ traefik-certs-dumper kv zookeeper --endpoints localhost:2181 +``` + +