Skip to content

Commit

Permalink
Switch to Cobra for CLI (#3)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
patricksanders committed Oct 16, 2020
1 parent acb6af8 commit 2baf8f1
Show file tree
Hide file tree
Showing 19 changed files with 447 additions and 245 deletions.
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -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)

Expand Down
61 changes: 42 additions & 19 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -73,28 +65,59 @@ Enable the rules by running the following:
sudo /sbin/iptables-restore < <path_to_file>.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 ~</optional/path/to/your/mtls/certs>:</optional/path/to/your/mtls/certs> --rm weep --meta-data --role <roleArn>
```

### 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.

Expand Down
11 changes: 5 additions & 6 deletions challenge/challenge.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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()
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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{}
Expand Down
16 changes: 16 additions & 0 deletions challenge/types.go
Original file line number Diff line number Diff line change
@@ -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"`
}
62 changes: 62 additions & 0 deletions cmd/export.go
Original file line number Diff line number Diff line change
@@ -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)
}
}
33 changes: 33 additions & 0 deletions cmd/list.go
Original file line number Diff line number Diff line change
@@ -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
}
82 changes: 82 additions & 0 deletions cmd/metadata.go
Original file line number Diff line number Diff line change
@@ -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
}
Loading

0 comments on commit 2baf8f1

Please sign in to comment.