Skip to content

Commit

Permalink
Add AWS_CONTAINER_AUTHORIZATION_TOKEN support
Browse files Browse the repository at this point in the history
- remove -race flag from unit tests since it was generating warnings

fixes: #516
  • Loading branch information
synfinatic committed Jun 27, 2024
1 parent 3f23df2 commit 046b0b6
Show file tree
Hide file tree
Showing 8 changed files with 382 additions and 47 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

## [Unreleased]

### New Features

* Add support for the `AWS_CONTAINER_AUTHORIZATION_TOKEN` env variable #516

## [v1.16.1] - 2024-06-13

### Bugs
Expand Down
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ debug: .prepare ## Run debug in dlv

.PHONY: unittest
unittest: ## Run go unit tests
go test -race -ldflags='$(LDFLAGS)' -covermode=atomic -coverprofile=coverage.out ./...
go test -ldflags='$(LDFLAGS)' -covermode=atomic -coverprofile=coverage.out ./...

.PHONY: test-race
test-race: ## Run `go test -race` on the code
Expand Down
8 changes: 4 additions & 4 deletions cmd/aws-sso/ecs_client_cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ func (cc *EcsLoadCmd) Run(ctx *RunContext) error {
}

func (cc *EcsProfileCmd) Run(ctx *RunContext) error {
c := client.NewECSClient(ctx.Cli.Ecs.Profile.Port)
c := client.NewECSClient(ctx.Cli.Ecs.Profile.Port, ctx.Cli.Ecs.SecurityToken)

profile, err := c.GetProfile()
if err != nil {
Expand All @@ -87,7 +87,7 @@ func (cc *EcsProfileCmd) Run(ctx *RunContext) error {
}

func (cc *EcsUnloadCmd) Run(ctx *RunContext) error {
c := client.NewECSClient(ctx.Cli.Ecs.Unload.Port)
c := client.NewECSClient(ctx.Cli.Ecs.Unload.Port, ctx.Cli.Ecs.SecurityToken)

return c.Delete(ctx.Cli.Ecs.Unload.Profile)
}
Expand Down Expand Up @@ -115,14 +115,14 @@ func ecsLoadCmd(ctx *RunContext, awssso *sso.AWSSSO, accountId int64, role strin
}

// do something
c := client.NewECSClient(ctx.Cli.Ecs.Load.Port)
c := client.NewECSClient(ctx.Cli.Ecs.Load.Port, ctx.Cli.Ecs.SecurityToken)

log.Debugf("%s", spew.Sdump(rFlat))
return c.SubmitCreds(creds, rFlat.Profile, ctx.Cli.Ecs.Load.Slotted)
}

func (cc *EcsListCmd) Run(ctx *RunContext) error {
c := client.NewECSClient(ctx.Cli.Ecs.Profile.Port)
c := client.NewECSClient(ctx.Cli.Ecs.Profile.Port, ctx.Cli.Ecs.SecurityToken)

profiles, err := c.ListProfiles()
if err != nil {
Expand Down
13 changes: 7 additions & 6 deletions cmd/aws-sso/ecs_cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,12 @@ const (
)

type EcsCmd struct {
Run EcsRunCmd `kong:"cmd,help='Run the ECS Server'"`
List EcsListCmd `kong:"cmd,help='List profiles loaded in the ECS Server'"`
Load EcsLoadCmd `kong:"cmd,help='Load new IAM Role credentials into the ECS Server'"`
Unload EcsUnloadCmd `kong:"cmd,help='Unload the current IAM Role credentials from the ECS Server'"`
Profile EcsProfileCmd `kong:"cmd,help='Get the current role profile name in the default slot'"`
Run EcsRunCmd `kong:"cmd,help='Run the ECS Server'"`
List EcsListCmd `kong:"cmd,help='List profiles loaded in the ECS Server'"`
Load EcsLoadCmd `kong:"cmd,help='Load new IAM Role credentials into the ECS Server'"`
Unload EcsUnloadCmd `kong:"cmd,help='Unload the current IAM Role credentials from the ECS Server'"`
Profile EcsProfileCmd `kong:"cmd,help='Get the current role profile name in the default slot'"`
SecurityToken string `kong:"help='Security Token to use for authentication',env='AWS_CONTAINER_AUTHORIZATION_TOKEN'"`
}

type EcsRunCmd struct {
Expand All @@ -48,7 +49,7 @@ func (cc *EcsRunCmd) Run(ctx *RunContext) error {
if err != nil {
return err
}
s, err := server.NewEcsServer(context.TODO(), "", l)
s, err := server.NewEcsServer(context.TODO(), ctx.Cli.Ecs.SecurityToken, l)
if err != nil {
return err
}
Expand Down
4 changes: 2 additions & 2 deletions cmd/aws-sso/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,7 @@ func main() {
switch ctx.Command() {
case "version":
if err = ctx.Run(&runCtx); err != nil {
log.Fatalf("Error running command: %s", err.Error())
log.Fatalf("%s", err.Error())
}
return
}
Expand Down Expand Up @@ -219,7 +219,7 @@ func main() {

err = ctx.Run(&runCtx)
if err != nil {
log.Fatalf("Error running command: %s", err.Error())
log.Fatalf("%s", err.Error())
}
}

Expand Down
23 changes: 19 additions & 4 deletions docs/ecs-server.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,12 @@ It is important to _not_ set `AWS_CONTAINER_CREDENTIALS_RELATIVE_URI`
as that takes precidence for `AWS_CONTAINER_CREDENTIALS_FULL_URI` and it is not
compatible with `aws-sso`.

### AWS\_CONTAINER\_AUTHORIZATION\_TOKEN

Specify the HTTP Authentication token used to authenticate communication between the
ECS Server and clients (aws-sso and AWS SDK/CLI). Typically the value should be specified
in the format of `Bearer <auth token value>`.

## Selecting a role via ECS Server

Before you can assume a role, you must select an IAM role for the aws-sso ecs
Expand Down Expand Up @@ -100,8 +106,8 @@ Accessing the individual credentials is done via the `profile` query parameter:

`export AWS_CONTAINER_CREDENTIALS_FULL_URI=http://localhost:4144/slot/ExampleProfileName`

Would utilize the `ExampleProfileName` role. Note that the `profile` parameter
value must be URL Escaped.
Would utilize the `ExampleProfileName` role. Note that the `profile` value
value in the URL must be [URL Escaped](https://www.w3schools.com/tags/ref_urlencode.ASP).

### Unloading

Expand All @@ -124,8 +130,8 @@ The ECS Server API endpoint generates errors with the following JSON format:
## Authentication

Support for the [AWS\_CONTAINER\_AUTHORIZATION\_TOKEN](
https://github.com/synfinatic/aws-sso-cli/issues/516) is TBD. Please vote for
this feature if you want it!
https://docs.aws.amazon.com/sdkref/latest/guide/feature-container-credentials.html) environment
variable is supported.

## HTTPS Transport

Expand All @@ -137,6 +143,7 @@ is TBD. Please vote for this feature if you want it!
### Default credentials

#### GET /

Fetch default credentials.

```json
Expand All @@ -150,6 +157,7 @@ Fetch default credentials.
```

#### GET /profile

Fetch profile name of the default credentials.

```json
Expand All @@ -163,6 +171,7 @@ Fetch profile name of the default credentials.
```

#### PUT /

Upload default credentials.

```json
Expand All @@ -173,6 +182,7 @@ Upload default credentials.
```

#### DELETE /

Delete default credentials.

```json
Expand All @@ -185,6 +195,7 @@ Delete default credentials.
### Slotted credentials

#### GET /slot

Fetch list of default credentials.

```json
Expand All @@ -201,6 +212,7 @@ Fetch list of default credentials.
```

#### GET /slot/&lt;profile&gt;

Fetch credentials of the named profile.

```json
Expand All @@ -214,6 +226,7 @@ Fetch credentials of the named profile.
```

#### PUT /slot/&lt;profile&gt;

Upload credentials of the named profile.

```json
Expand All @@ -224,6 +237,7 @@ Upload credentials of the named profile.
```

#### DELETE /slot/&lt;profile&gt;

Delete credentials of the named profile.

```json
Expand All @@ -234,6 +248,7 @@ Delete credentials of the named profile.
```

#### DELETE /slot

Delete all named credentials.

```json
Expand Down
73 changes: 43 additions & 30 deletions internal/ecs/client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,28 +32,50 @@ import (
)

type ECSClient struct {
port int
port int
authToken string
loadUrl string
loadSlotUrl string
profileUrl string
listUrl string
}

func NewECSClient(port int) *ECSClient {
func NewECSClient(port int, authToken string) *ECSClient {
return &ECSClient{
port: port,
port: port,
authToken: authToken,
loadUrl: fmt.Sprintf("http://localhost:%d/", port),
loadSlotUrl: fmt.Sprintf("http://localhost:%d%s", port, ecs.SLOT_ROUTE),
profileUrl: fmt.Sprintf("http://localhost:%d%s", port, ecs.PROFILE_ROUTE),
listUrl: fmt.Sprintf("http://localhost:%d%s", port, ecs.SLOT_ROUTE),
}
}

func (c *ECSClient) LoadUrl(profile string) string {
if profile == "" {
return fmt.Sprintf("http://localhost:%d/", c.port)
return c.loadUrl
}
return fmt.Sprintf("http://localhost:%d%s/%s", c.port, ecs.SLOT_ROUTE, url.QueryEscape(profile))
return c.loadSlotUrl + "/" + url.PathEscape(profile)
}

func (c *ECSClient) ProfileUrl() string {
return fmt.Sprintf("http://localhost:%d%s", c.port, ecs.PROFILE_ROUTE)
return c.profileUrl
}

func (c *ECSClient) ListUrl() string {
return fmt.Sprintf("http://localhost:%d%s", c.port, ecs.SLOT_ROUTE)
return c.listUrl
}

func (c *ECSClient) newRequest(method, url string, body io.Reader) (*http.Request, error) {
req, err := http.NewRequest(method, url, body)
if err != nil {
return nil, err
}
req.Header.Set("Content-Type", ecs.CHARSET_JSON)
if c.authToken != "" {
req.Header.Set("Authorization", c.authToken)
}
return req, nil
}

func (c *ECSClient) SubmitCreds(creds *storage.RoleCredentials, profile string, slotted bool) error {
Expand All @@ -62,34 +84,31 @@ func (c *ECSClient) SubmitCreds(creds *storage.RoleCredentials, profile string,
Creds: creds,
ProfileName: profile,
}
j, err := json.Marshal(cr)
if err != nil {
return err
}
j, _ := json.Marshal(cr)

var path string
if slotted {
path = profile
}
req, err := http.NewRequest(http.MethodPut, c.LoadUrl(path), bytes.NewBuffer(j))
if err != nil {
return err
}
req.Header.Set("Content-Type", ecs.CHARSET_JSON)

req, _ := c.newRequest(http.MethodPut, c.LoadUrl(path), bytes.NewBuffer(j))
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
return err
}
return CheckDoResponse(resp)
return checkDoResponse(resp)
}

func (c *ECSClient) GetProfile() (ecs.ListProfilesResponse, error) {
lpr := ecs.ListProfilesResponse{}
req, _ := c.newRequest(http.MethodGet, c.ProfileUrl(), nil)
client := &http.Client{}
resp, err := client.Get(c.ProfileUrl())
resp, err := client.Do(req)
if err != nil {
return lpr, err
}

defer resp.Body.Close()

body, err := io.ReadAll(resp.Body)
Expand All @@ -108,8 +127,9 @@ func (c *ECSClient) GetProfile() (ecs.ListProfilesResponse, error) {
// ListProfiles returns a list of profiles that are loaded into slots
func (c *ECSClient) ListProfiles() ([]ecs.ListProfilesResponse, error) {
lpr := []ecs.ListProfilesResponse{}
req, _ := c.newRequest(http.MethodGet, c.ListUrl(), nil)
client := &http.Client{}
resp, err := client.Get(c.ListUrl())
resp, err := client.Do(req)
if err != nil {
return lpr, err
}
Expand All @@ -129,26 +149,19 @@ func (c *ECSClient) ListProfiles() ([]ecs.ListProfilesResponse, error) {
}

func (c *ECSClient) Delete(profile string) error {
req, err := http.NewRequest(http.MethodDelete, c.LoadUrl(profile), bytes.NewBuffer([]byte("")))
if err != nil {
return err
}
req, _ := c.newRequest(http.MethodDelete, c.LoadUrl(profile), bytes.NewBuffer([]byte("")))

client := &http.Client{}
req.Header.Set("Content-Type", ecs.CHARSET_JSON)
if err != nil {
return err
}
resp, err := client.Do(req)
if err != nil {
return err
}
return CheckDoResponse(resp)
return checkDoResponse(resp)
}

func CheckDoResponse(resp *http.Response) error {
func checkDoResponse(resp *http.Response) error {
if resp.StatusCode < 200 || resp.StatusCode > 200 {
return fmt.Errorf("HTTP Error %d", resp.StatusCode)
return fmt.Errorf("ECS Server HTTP error: %s", resp.Status)
}
return nil
}
Loading

0 comments on commit 046b0b6

Please sign in to comment.