From 2baf8f179c6a435ceb4416386a3c73de9fb76a70 Mon Sep 17 00:00:00 2001 From: Patrick Sanders Date: Fri, 16 Oct 2020 08:30:38 -0700 Subject: [PATCH] Switch to Cobra for CLI (#3) * Switch to Cobra for CLI * fix metadata command * use arg for role name instead of flag, clean up pkger before build * go fmt * iron out cli a little, add support for fish exports * update readme, bump version --- Makefile | 2 +- README.md | 61 ++++--- challenge/challenge.go | 11 +- challenge/types.go | 16 ++ cmd/export.go | 62 +++++++ cmd/list.go | 33 ++++ cmd/metadata.go | 82 ++++++++++ cmd/root.go | 103 ++++++++++++ cmd/setup.go | 20 +++ main_darwin.go => cmd/setup_darwin.go | 2 +- main_linux.go => cmd/setup_linux.go | 2 +- main_windows.go => cmd/setup_windows.go | 2 +- cmd/version.go | 19 +++ consoleme/consoleme.go | 38 +++++ consoleme/types.go | 14 -- go.mod | 2 +- go.sum | 8 + main.go | 206 +----------------------- scripts/build.sh | 9 +- 19 files changed, 447 insertions(+), 245 deletions(-) create mode 100644 challenge/types.go create mode 100644 cmd/export.go create mode 100644 cmd/list.go create mode 100644 cmd/metadata.go create mode 100644 cmd/root.go create mode 100644 cmd/setup.go rename main_darwin.go => cmd/setup_darwin.go (96%) rename main_linux.go => cmd/setup_linux.go (95%) rename main_windows.go => cmd/setup_windows.go (91%) create mode 100644 cmd/version.go diff --git a/Makefile b/Makefile index 46f5750..c94cee8 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ GOFMT_FILES?=$$(find . -name '*.go' | grep -v vendor) BINARY_NAME=weep -VERSION=0.0.9 +VERSION=0.1.0 REGISTRY=$(REGISTRY) BRANCH=$(shell git rev-parse --abbrev-ref HEAD) diff --git a/README.md b/README.md index 91e6aad..15f1c98 100644 --- a/README.md +++ b/README.md @@ -13,18 +13,10 @@ Make a weep configuration file in one of the following locations: - `~/.weep.yaml` - `~/.config/weep/.weep.yaml` -### Embedding mTLS configuration - -`weep` binaries can be shipped with an embedded mutual TLS (mTLS) configuration to -avoid making users set this configuration. An example of such a configuration is included -in [mtls/mtls_paths.yaml](mtls/mtls_paths.yaml). - -To compile with an embedded config, set the `MTLS_CONFIG_FILE` environment variable at -build time. The value of this variable MUST be the **absolute path** of the configuration -file **relative to the root of the module**: +You can also specify a config file as a CLI arg: -```bash -MTLS_CONFIG_FILE=/mtls/mtls_paths.yaml make build +``` +weep --config somethingdifferent.yaml list ``` ## Routing traffic @@ -73,28 +65,59 @@ Enable the rules by running the following: sudo /sbin/iptables-restore < .txt ## Usage + ### Metadata Proxy -```$ weep --meta-data --role arn:aws:iam::123456789012:role/exampleInstanceProfile -INFO[0000] Starting weep meta-data service... -INFO[0000] Server started on: 127.0.0.1:9090 + +```bash +# You can use a full ARN +weep metadata arn:aws:iam::123456789012:role/exampleRole + +# ...or just the role name +weep metadata exampleRole ``` + run `aws sts get-caller-identity` to confirm that your DNAT rules are correctly configured. ### Credential export -```$ eval $(weep -export -role arn:aws:iam::123456789012:role/fullOrPartialRoleName)``` -run `aws sts get-caller-identity` to confirm that your credentials work properly. +```bash +eval $(weep export arn:aws:iam::123456789012:role/fullOrPartialRoleName) + +# this one also works with just the role name! +eval $(weep export fullOrPartialRoleName) +``` + +Then run `aws sts get-caller-identity` to confirm that your credentials work properly. + +## Building + +In most cases, `weep` can be built by running the `make` command in the repository root. `make release` (requires +[`upx`](https://upx.github.io/)) will build and compress the binary for distribution. + +### Embedding mTLS configuration + +`weep` binaries can be shipped with an embedded mutual TLS (mTLS) configuration to +avoid making users set this configuration. An example of such a configuration is included +in [mtls/mtls_paths.yaml](mtls/mtls_paths.yaml). + +To compile with an embedded config, set the `MTLS_CONFIG_FILE` environment variable at +build time. The value of this variable MUST be the **absolute path** of the configuration +file **relative to the root of the module**: + +```bash +MTLS_CONFIG_FILE=/mtls/mtls_paths.yaml make +``` -## Docker +### Docker -### Building and Running +#### Building and Running ``` make build-docker docker run -v ~: --rm weep --meta-data --role ``` -### Publishing a Docker image +#### Publishing a Docker image To publish a Docker image, you can invoke `make docker`, which runs `make build-docker` and `make publish-docker`. When run from any branch other than `master`, the image is tagged with the version number and branch name. On the `master` branch the image is tagged with only the version number. diff --git a/challenge/challenge.go b/challenge/challenge.go index db3815e..a9119f9 100644 --- a/challenge/challenge.go +++ b/challenge/challenge.go @@ -6,7 +6,6 @@ import ( "fmt" "github.com/golang/glog" "github.com/netflix/weep/config" - "github.com/netflix/weep/consoleme" log "github.com/sirupsen/logrus" "io/ioutil" "net/http" @@ -25,7 +24,7 @@ func NewHTTPClient(consolemeUrl string) (*http.Client, error) { if !HasValidJwt() { return nil, errors.New("Your authentication to ConsoleMe has expired. Please restart weep.") } - var challenge consoleme.ConsolemeChallengeResponse + var challenge ConsolemeChallengeResponse jar, err := cookiejar.New(&cookiejar.Options{}) if err != nil { return nil, err } credentialsPath, err := getCredentialsPath() @@ -70,8 +69,8 @@ func isWSL() bool { return false } -func poll(pollingUrl string) (*consoleme.ConsolemeChallengeResponse, error) { - var pollResponse consoleme.ConsolemeChallengeResponse +func poll(pollingUrl string) (*ConsolemeChallengeResponse, error) { + var pollResponse ConsolemeChallengeResponse var pollResponseBody []byte timeout := time.After(2 * time.Minute) tick := time.Tick(3 * time.Second) @@ -115,7 +114,7 @@ func getCredentialsPath() (string, error) { } func HasValidJwt() bool { - var challenge consoleme.ConsolemeChallengeResponse + var challenge ConsolemeChallengeResponse credentialPath, err := getCredentialsPath() if err != nil { return false @@ -153,7 +152,7 @@ func RefreshChallenge() error { config.Config.ConsoleMeUrl, config.Config.ChallengeSettings.User, ) - var challenge consoleme.ConsolemeChallenge + var challenge ConsolemeChallenge req, err := http.NewRequest("GET", consoleMeChallengeGeneratorEndpoint, nil) req.Header.Set("Content-Type", "application/json") client := &http.Client{} diff --git a/challenge/types.go b/challenge/types.go new file mode 100644 index 0000000..99da549 --- /dev/null +++ b/challenge/types.go @@ -0,0 +1,16 @@ +package challenge + +type ConsolemeChallenge struct { + ChallengeURL string `json:"challenge_url"` + PollingUrl string `json:"polling_url"` +} + +type ConsolemeChallengeResponse struct { + Status string `json:"status"` + EncodedJwt string `json:"encoded_jwt"` + CookieName string `json:"cookie_name"` + WantSecure bool `json:"secure"` + WantHttpOnly bool `json:"http_only"` + SameSite int `json:"same_site"` + Expires int64 `json:"expiration"` +} diff --git a/cmd/export.go b/cmd/export.go new file mode 100644 index 0000000..2340cee --- /dev/null +++ b/cmd/export.go @@ -0,0 +1,62 @@ +package cmd + +import ( + "fmt" + "github.com/netflix/weep/consoleme" + "github.com/spf13/cobra" + "os" + "strings" +) + +var ( + exportRole string + exportNoIPRestrict bool +) + +func init() { + exportCmd.PersistentFlags().BoolVarP(&exportNoIPRestrict, "no-ip", "n", false, "remove IP restrictions") + rootCmd.AddCommand(exportCmd) +} + +var exportCmd = &cobra.Command{ + Use: "export [role_name]", + Short: "Retrieve credentials to be exported as environment variables", + Args: cobra.ExactArgs(1), + RunE: runExport, +} + +func runExport(cmd *cobra.Command, args []string) error { + exportRole = args[0] + client, err := consoleme.GetClient() + if err != nil { + return err + } + creds, err := client.GetRoleCredentials(exportRole, exportNoIPRestrict) + if err != nil { + return err + } + printExport(creds) + return nil +} + +// isFish will try its best to identify if we're running in fish shell +func isFish() bool { + shellVar := os.Getenv("SHELL") + + if strings.Contains(shellVar, "fish") { + return true + } else { + return false + } +} + +func printExport(creds consoleme.AwsCredentials) { + if isFish() { + // fish has a different way of setting variables than bash/zsh and others + fmt.Printf("set -x AWS_ACCESS_KEY_ID %s && set -x AWS_SECRET_ACCESS_KEY %s && set -x AWS_SESSION_TOKEN %s\n", + creds.AccessKeyId, creds.SecretAccessKey, creds.SessionToken) + } else { + fmt.Printf("export AWS_ACCESS_KEY_ID=%s && export AWS_SECRET_ACCESS_KEY=%s && export AWS_SESSION_TOKEN=%s\n", + creds.AccessKeyId, creds.SecretAccessKey, creds.SessionToken) + } +} diff --git a/cmd/list.go b/cmd/list.go new file mode 100644 index 0000000..79e1ef2 --- /dev/null +++ b/cmd/list.go @@ -0,0 +1,33 @@ +package cmd + +import ( + "fmt" + "github.com/netflix/weep/consoleme" + "github.com/spf13/cobra" +) + +func init() { + rootCmd.AddCommand(listCmd) +} + +var listCmd = &cobra.Command{ + Use: "list", + Short: "List available roles", + RunE: runList, +} + +func runList(cmd *cobra.Command, args []string) error { + client, err := consoleme.GetClient() + if err != nil { + return err + } + roles, err := client.Roles() + if err != nil { + return err + } + fmt.Println("Roles:") + for i := range roles { + fmt.Println(" ", roles[i]) + } + return nil +} diff --git a/cmd/metadata.go b/cmd/metadata.go new file mode 100644 index 0000000..8b4f939 --- /dev/null +++ b/cmd/metadata.go @@ -0,0 +1,82 @@ +package cmd + +import ( + "fmt" + "github.com/gorilla/mux" + "github.com/netflix/weep/consoleme" + "github.com/netflix/weep/handlers" + "github.com/netflix/weep/metadata" + log "github.com/sirupsen/logrus" + "github.com/spf13/cobra" + "net" + "net/http" + "os" + "os/signal" + "syscall" +) + +var ( + metadataRole string + metadataRegion string + metadataListenAddr string + metadataListenPort int +) + +func init() { + metadataCmd.PersistentFlags().StringVarP(&metadataRegion, "region", "r", "us-east-1", "region of metadata service") + metadataCmd.PersistentFlags().StringVarP(&metadataListenAddr, "listen-address", "a", "127.0.0.1", "IP address for metadata service to listen on") + metadataCmd.PersistentFlags().IntVarP(&metadataListenPort, "port", "p", 9090, "port for metadata service to listen on") + rootCmd.AddCommand(metadataCmd) +} + +var metadataCmd = &cobra.Command{ + Use: "metadata [role_name]", + Short: "Run a local Instance Metadata Service (IMDS) endpoint that serves credentials", + Args: cobra.ExactArgs(1), + RunE: runMetadata, +} + +func runMetadata(cmd *cobra.Command, args []string) error { + metadataRole = args[0] + metadata.Role = metadataRole + metadata.MetadataRegion = metadataRegion + client, err := consoleme.GetClient() + if err != nil { + return err + } + ipaddress := net.ParseIP(metadataListenAddr) + + if ipaddress == nil { + fmt.Println("Invalid IP: ", metadataListenAddr) + os.Exit(1) + } + + listenAddr := fmt.Sprintf("%s:%d", ipaddress, metadataListenPort) + + router := mux.NewRouter() + router.HandleFunc("/{version}/", handlers.MetaDataServiceMiddleware(handlers.BaseVersionHandler)) + router.HandleFunc("/{version}/api/token", handlers.MetaDataServiceMiddleware(handlers.TokenHandler)).Methods("PUT") + router.HandleFunc("/{version}/meta-data", handlers.MetaDataServiceMiddleware(handlers.BaseHandler)) + router.HandleFunc("/{version}/meta-data/", handlers.MetaDataServiceMiddleware(handlers.BaseHandler)) + router.HandleFunc("/{version}/meta-data/iam/info", handlers.MetaDataServiceMiddleware(handlers.IamInfoHandler)) + router.HandleFunc("/{version}/meta-data/iam/security-credentials/", handlers.MetaDataServiceMiddleware(handlers.RoleHandler)) + router.HandleFunc("/{version}/meta-data/iam/security-credentials/{role}", handlers.MetaDataServiceMiddleware(handlers.CredentialsHandler)) + router.HandleFunc("/{version}/dynamic/instance-identity/document", handlers.MetaDataServiceMiddleware(handlers.InstanceIdentityDocumentHandler)) + router.HandleFunc("/{path:.*}", handlers.MetaDataServiceMiddleware(handlers.CustomHandler)) + + go metadata.StartMetaDataRefresh(client) + + go func() { + log.Info("Starting weep meta-data service...") + log.Info("Server started on: ", listenAddr) + log.Info(http.ListenAndServe(listenAddr, router)) + }() + + // Check for interrupt signal and exit cleanly + quit := make(chan os.Signal, 1) + signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM) + <-quit + log.Print("Shutdown signal received, exiting weep meta-data service...") + + return nil +} diff --git a/cmd/root.go b/cmd/root.go new file mode 100644 index 0000000..e353656 --- /dev/null +++ b/cmd/root.go @@ -0,0 +1,103 @@ +package cmd + +import ( + "fmt" + "github.com/mitchellh/go-homedir" + "github.com/netflix/weep/config" + log "github.com/sirupsen/logrus" + "github.com/spf13/cobra" + "github.com/spf13/viper" + "os" + "path" + "runtime" + "strings" +) + +var ( + cfgFile string + logLevel string + logFormat string + + rootCmd = &cobra.Command{ + Use: "weep", + Short: "weep helps you get the most out of ConsoleMe credentials", + Long: "TBD", + } +) + +func init() { + cobra.OnInitialize(initConfig) + cobra.OnInitialize(initLogging) + + rootCmd.PersistentFlags().StringVarP(&cfgFile, "config", "c", "", "config file (default is $HOME/.weep.yaml)") + rootCmd.PersistentFlags().StringVar(&cfgFile, "log-format", "", "log format (json or tty)") + rootCmd.PersistentFlags().StringVar(&cfgFile, "log-level", "", "log level (debug, info, warn)") + +} + +func Execute() { + if err := rootCmd.Execute(); err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } +} + +func initConfig() { + + if cfgFile != "" { + viper.SetConfigFile(cfgFile) + } else { + home, err := homedir.Dir() + if err != nil { + fmt.Println(err) + os.Exit(1) + } + viper.SetConfigType("yaml") + viper.SetConfigName(".weep") + viper.AddConfigPath(".") + viper.AddConfigPath(home) + viper.AddConfigPath(home + "/.config/weep/") + } + + err := viper.ReadInConfig() + if err == nil { + log.Debug("Found config") + err = viper.Unmarshal(&config.Config) + if err != nil { + log.Fatalf("unable to decode into struct, %v", err) + } + } +} + +func initLogging() { + // Set the log format. Default to Text + if logFormat == "json" { + log.SetFormatter(&log.JSONFormatter{ + CallerPrettyfier: func(f *runtime.Frame) (string, string) { + s := strings.Split(f.Function, ".") + funcName := s[len(s)-1] + return funcName, fmt.Sprintf("%s:%d", path.Base(f.File), f.Line) + }, + }) + } else { + log.SetFormatter(&log.TextFormatter{ + CallerPrettyfier: func(f *runtime.Frame) (string, string) { + s := strings.Split(f.Function, ".") + funcName := s[len(s)-1] + return funcName, fmt.Sprintf("%s:%d", path.Base(f.File), f.Line) + }, + }) + } + + // Set the log level and default to INFO + switch logLevel { + case "error": + log.SetLevel(log.ErrorLevel) + case "warn": + log.SetLevel(log.WarnLevel) + case "debug": + log.SetLevel(log.DebugLevel) + default: + log.SetLevel(log.InfoLevel) + } +} diff --git a/cmd/setup.go b/cmd/setup.go new file mode 100644 index 0000000..df851a7 --- /dev/null +++ b/cmd/setup.go @@ -0,0 +1,20 @@ +package cmd + +import ( + "github.com/spf13/cobra" +) + +func init() { + rootCmd.AddCommand(setupCmd) +} + +var setupCmd = &cobra.Command{ + Use: "setup", + Short: "Print setup information for Weep", + Run: func(cmd *cobra.Command, args []string) { + PrintSetup() + }, +} + +var fishSetup = `set +` diff --git a/main_darwin.go b/cmd/setup_darwin.go similarity index 96% rename from main_darwin.go rename to cmd/setup_darwin.go index cbbd84f..15d2b92 100644 --- a/main_darwin.go +++ b/cmd/setup_darwin.go @@ -1,6 +1,6 @@ // +build darwin -package main +package cmd import ( "fmt" diff --git a/main_linux.go b/cmd/setup_linux.go similarity index 95% rename from main_linux.go rename to cmd/setup_linux.go index adba1d5..68a61ed 100644 --- a/main_linux.go +++ b/cmd/setup_linux.go @@ -1,6 +1,6 @@ // +build linux -package main +package cmd import ( "fmt" diff --git a/main_windows.go b/cmd/setup_windows.go similarity index 91% rename from main_windows.go rename to cmd/setup_windows.go index b54307b..d9fe012 100644 --- a/main_windows.go +++ b/cmd/setup_windows.go @@ -1,6 +1,6 @@ // +build windows -package main +package cmd import ( "fmt" diff --git a/cmd/version.go b/cmd/version.go new file mode 100644 index 0000000..074b97b --- /dev/null +++ b/cmd/version.go @@ -0,0 +1,19 @@ +package cmd + +import ( + "fmt" + "github.com/netflix/weep/version" + "github.com/spf13/cobra" +) + +func init() { + rootCmd.AddCommand(versionCmd) +} + +var versionCmd = &cobra.Command{ + Use: "version", + Short: "Print the version number of Weep", + Run: func(cmd *cobra.Command, args []string) { + fmt.Println(version.GetVersion()) + }, +} diff --git a/consoleme/consoleme.go b/consoleme/consoleme.go index 2756f5e..8e5f196 100644 --- a/consoleme/consoleme.go +++ b/consoleme/consoleme.go @@ -4,6 +4,9 @@ import ( "bytes" "encoding/json" "fmt" + "github.com/netflix/weep/challenge" + "github.com/netflix/weep/config" + "github.com/netflix/weep/mtls" log "github.com/sirupsen/logrus" "io" "io/ioutil" @@ -37,6 +40,41 @@ type Client struct { host string } +// GetClient creates an authenticated ConsoleMe client +func GetClient() (*Client, error) { + var client *Client + consoleMeUrl := config.Config.ConsoleMeUrl + authenticationMethod := config.Config.AuthenticationMethod + + if authenticationMethod == "mtls" { + mtlsClient, err := mtls.NewHTTPClient() + if err != nil { + return client, err + } + client, err = NewClientWithMtls(consoleMeUrl, mtlsClient) + if err != nil { + return client, err + } + } else if authenticationMethod == "challenge" { + err := challenge.RefreshChallenge() + if err != nil { + return client, err + } + httpClient, err := challenge.NewHTTPClient(consoleMeUrl) + if err != nil { + return client, err + } + client, err = NewClientWithJwtAuth(consoleMeUrl, httpClient) + if err != nil { + return client, err + } + } else { + log.Fatal("Authentication method unsupported or not provided.") + } + + return client, nil +} + // NewClientWithMtls takes a ConsoleMe hostname and *http.Client, and returns a // ConsoleMe client that will talk to that ConsoleMe instance for AWS Credentials. func NewClientWithMtls(hostname string, httpc HTTPClient) (*Client, error) { diff --git a/consoleme/types.go b/consoleme/types.go index fa85565..15355a6 100644 --- a/consoleme/types.go +++ b/consoleme/types.go @@ -25,17 +25,3 @@ type ConsolemeCredentialErrorMessageType struct { } -type ConsolemeChallenge struct { - ChallengeURL string `json:"challenge_url"` - PollingUrl string `json:"polling_url"` -} - -type ConsolemeChallengeResponse struct { - Status string `json:"status"` - EncodedJwt string `json:"encoded_jwt"` - CookieName string `json:"cookie_name"` - WantSecure bool `json:"secure"` - WantHttpOnly bool `json:"http_only"` - SameSite int `json:"same_site"` - Expires int64 `json:"expiration"` -} diff --git a/go.mod b/go.mod index 019bd2f..4d0d3fd 100644 --- a/go.mod +++ b/go.mod @@ -15,8 +15,8 @@ require ( github.com/sirupsen/logrus v1.6.0 github.com/spf13/afero v1.3.4 // indirect github.com/spf13/cast v1.3.1 // indirect + github.com/spf13/cobra v1.1.0 github.com/spf13/jwalterweatherman v1.1.0 // indirect - github.com/spf13/pflag v1.0.5 // indirect github.com/spf13/viper v1.7.1 golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc // indirect golang.org/x/sys v0.0.0-20200828081204-131dc92a58d5 // indirect diff --git a/go.sum b/go.sum index e5c2ed0..f528250 100644 --- a/go.sum +++ b/go.sum @@ -33,6 +33,7 @@ github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc github.com/coreos/go-semver v0.3.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/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -103,6 +104,8 @@ github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= +github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= +github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/jmespath/go-jmespath v0.3.0 h1:OS12ieG61fsCg5+qLJ+SsW9NicxNkg3b25OyT2yCeUc= github.com/jmespath/go-jmespath v0.3.0/go.mod h1:9QtRXoHjLGCJ5IBSaohpXITPlowMeeYCZ7fLUTSywik= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= @@ -174,8 +177,10 @@ github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7z github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= +github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.6.0 h1:UBcNElsrwanuuMsnGSlYmtmgbb23qDR5dG+6X6Oo89I= github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= @@ -193,6 +198,8 @@ 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/cast v1.3.1 h1:nFm6S0SMdyzrzcmThSipiEubIDy8WEXKNZ0UOgiRpng= github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cobra v1.1.0 h1:aq3wCKjTPmzcNWLVGnsFVN4rflK7Uzn10F8/aw8MhdQ= +github.com/spf13/cobra v1.1.0/go.mod h1:yk5b0mALVusDL5fMM6Rd1wgnoO5jUPhwsQ6LQAJTidQ= 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/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= @@ -201,6 +208,7 @@ github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= github.com/spf13/viper v1.7.1 h1:pM5oEahlgWv/WnHXpgbKz7iLIxRf65tye2Ci+XFK5sk= github.com/spf13/viper v1.7.1/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= diff --git a/main.go b/main.go index 9db9e78..573c9d2 100644 --- a/main.go +++ b/main.go @@ -1,211 +1,17 @@ package main import ( - "flag" - "fmt" - "net" - "net/http" - "os" - "os/signal" - "path" - "runtime" - "github.com/netflix/weep/challenge" - "github.com/netflix/weep/mtls" - "strings" - "syscall" - - "github.com/gorilla/mux" - homedir "github.com/mitchellh/go-homedir" + "github.com/netflix/weep/cmd" log "github.com/sirupsen/logrus" - "github.com/spf13/viper" - "github.com/netflix/weep/config" - "github.com/netflix/weep/consoleme" - "github.com/netflix/weep/handlers" - "github.com/netflix/weep/metadata" - "github.com/netflix/weep/util" - "github.com/netflix/weep/version" -) - -var ( - logFormat string - logLevel string - port int - listenAddr string - configPath string - - mtlsClient *http.Client + "os" ) -func main() { - versionPtr := flag.Bool("version", false, "Prints version") - metadataSvcPtr := flag.Bool("meta-data", false, "Starts the Meta-data Service") - exportPtr := flag.Bool("export", false, "Triggers printing out credentials to stdout") - listPtr := flag.Bool("list", false, "List Eligible Roles") - setupPtr := flag.Bool("setup", false, "Print out the commands you should run to get routing setup") - flag.StringVar(&metadata.Role, "role", "", "Role ARN") - flag.StringVar(&logFormat, "log_fmt", "tty", "Log Format - json or tty") - flag.StringVar(&logLevel, "log_level", "info", "Log Level - info, debug, warn") - flag.StringVar(&listenAddr, "listen_ip", "127.0.0.1", "IP Address to listen on") - flag.StringVar(&configPath, "config", "", "Config file (yml)") - flag.BoolVar(&metadata.NoIpRestrict, "no_ip", false, "removes VPN IP restrictions (PAGES SECOPS)") - flag.StringVar(&metadata.MetadataRegion, "region", "us-east-1", "Region for Metadata service") - flag.IntVar(&port, "port", 9090, "Listening Port") - flag.Parse() - - if *versionPtr { - fmt.Println(version.GetVersion()) - os.Exit(0) - } - - if *setupPtr { - // use os-specific routine - PrintSetup() - os.Exit(0) - } - +func init() { // Output to stdout instead of the default stderr log.SetOutput(os.Stdout) log.SetReportCaller(true) +} - // Set the log format. Default to Text - if logFormat == "json" { - log.SetFormatter(&log.JSONFormatter{ - CallerPrettyfier: func(f *runtime.Frame) (string, string) { - s := strings.Split(f.Function, ".") - funcName := s[len(s)-1] - return funcName, fmt.Sprintf("%s:%d", path.Base(f.File), f.Line) - }, - }) - } else { - log.SetFormatter(&log.TextFormatter{ - CallerPrettyfier: func(f *runtime.Frame) (string, string) { - s := strings.Split(f.Function, ".") - funcName := s[len(s)-1] - return funcName, fmt.Sprintf("%s:%d", path.Base(f.File), f.Line) - }, - }) - } - - // Set the log level and default to INFO - switch logLevel { - case "error": - log.SetLevel(log.ErrorLevel) - case "warn": - log.SetLevel(log.WarnLevel) - case "debug": - log.SetLevel(log.DebugLevel) - default: - log.SetLevel(log.InfoLevel) - } - - home, err := homedir.Dir() - if err != nil { - fmt.Println(err) - os.Exit(1) - } - - viper.SetConfigType("yaml") - viper.SetConfigName(".weep") - viper.AddConfigPath(".") - viper.AddConfigPath(home) - viper.AddConfigPath(home + "/.config/weep/") - err = viper.ReadInConfig() - if err == nil { - log.Debug("Found config") - err = viper.Unmarshal(&config.Config) - if err != nil { - log.Fatalf("unable to decode into struct, %v", err) - } - } - consoleMeUrl := config.Config.ConsoleMeUrl - - authenticationMethod := config.Config.AuthenticationMethod - - var client *consoleme.Client - - if authenticationMethod == "mtls" { - mtlsClient, err := mtls.NewHTTPClient() - util.CheckError(err) - client, err = consoleme.NewClientWithMtls(consoleMeUrl, mtlsClient) - util.CheckError(err) - } else if authenticationMethod == "challenge" { - err = challenge.RefreshChallenge() - util.CheckError(err) - httpClient, err := challenge.NewHTTPClient(consoleMeUrl) - util.CheckError(err) - client, err = consoleme.NewClientWithJwtAuth(consoleMeUrl, httpClient) - util.CheckError(err) - } else { - log.Fatal("Authentication method unsupported or not provided.") - } - - - if *listPtr { - roles, err := client.Roles() - util.CheckError(err) - - fmt.Println("Roles:") - for i := range roles { - fmt.Println(" ", roles[i]) - } - os.Exit(0) - } - - if !*versionPtr && !*metadataSvcPtr && !*exportPtr { - flag.PrintDefaults() - os.Exit(0) - } - - if *metadataSvcPtr && metadata.NoIpRestrict { - log.Fatal("You cannot have non IP-restricted credentials in the metadata service due to potential Duo lockout") - } - - if len(metadata.Role) < 1 { - log.Error("Please provide a Role via the --role command line flag") - os.Exit(1) - } - - if *exportPtr { - creds, err := client.GetRoleCredentials(metadata.Role, metadata.NoIpRestrict) - util.CheckError(err) - fmt.Printf("export AWS_ACCESS_KEY_ID=%s && export AWS_SECRET_ACCESS_KEY=%s && export AWS_SESSION_TOKEN=%s\n", - creds.AccessKeyId, creds.SecretAccessKey, creds.SessionToken) - os.Exit(0) - } - - ipaddress := net.ParseIP(listenAddr) - - if ipaddress == nil { - fmt.Println("Invalid IP: ", listenAddr) - os.Exit(1) - } - - listener_addr := fmt.Sprintf("%s:%d", ipaddress, port) - - if *metadataSvcPtr { - router := mux.NewRouter() - router.HandleFunc("/{version}/", handlers.MetaDataServiceMiddleware(handlers.BaseVersionHandler)) - router.HandleFunc("/{version}/api/token", handlers.MetaDataServiceMiddleware(handlers.TokenHandler)).Methods("PUT") - router.HandleFunc("/{version}/meta-data", handlers.MetaDataServiceMiddleware(handlers.BaseHandler)) - router.HandleFunc("/{version}/meta-data/", handlers.MetaDataServiceMiddleware(handlers.BaseHandler)) - router.HandleFunc("/{version}/meta-data/iam/info", handlers.MetaDataServiceMiddleware(handlers.IamInfoHandler)) - router.HandleFunc("/{version}/meta-data/iam/security-credentials/", handlers.MetaDataServiceMiddleware(handlers.RoleHandler)) - router.HandleFunc("/{version}/meta-data/iam/security-credentials/{role}", handlers.MetaDataServiceMiddleware(handlers.CredentialsHandler)) - router.HandleFunc("/{version}/dynamic/instance-identity/document", handlers.MetaDataServiceMiddleware(handlers.InstanceIdentityDocumentHandler)) - router.HandleFunc("/{path:.*}", handlers.MetaDataServiceMiddleware(handlers.CustomHandler)) - - go metadata.StartMetaDataRefresh(client) - - go func() { - log.Info("Starting weep meta-data service...") - log.Info("Server started on: ", listener_addr) - log.Info(http.ListenAndServe(listener_addr, router)) - }() - } - - // Check for interrupt signal and exit cleanly - quit := make(chan os.Signal, 1) - signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM) - <-quit - log.Print("Shutdown signal received, exiting weep meta-data service...") +func main() { + cmd.Execute() } diff --git a/scripts/build.sh b/scripts/build.sh index f8b2d1b..8d2cae1 100755 --- a/scripts/build.sh +++ b/scripts/build.sh @@ -17,8 +17,15 @@ BUILD_TAGS="${BUILD_TAGS:-"weep"}" GIT_COMMIT="$(git rev-parse HEAD)" GIT_DIRTY="$(test -n "`git status --porcelain`" && echo "+CHANGES" || true)" +rm pkger.go 2&> /dev/null || true + echo "=> Building..." -pkger -include "${MTLS_CONFIG_FILE}" +if [[ ! -z $MTLS_CONFIG_FILE ]]; then + echo "Bundling mTLS config" + pkger -include "${MTLS_CONFIG_FILE}" +else + echo "Not bundling mTLS config" +fi go build \ -ldflags "${LD_FLAGS} \ -X github.com/netflix/weep/mtls.EmbeddedConfigFile=${MTLS_CONFIG_FILE} \