Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Switch to Cobra for CLI #3

Merged
merged 6 commits into from
Oct 16, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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