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

Add AWS_CONTAINER_AUTHORIZATION_TOKEN support #916

Merged
merged 1 commit into from
Jun 27, 2024
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
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
Loading