Skip to content

Commit

Permalink
Merge pull request #925 from synfinatic/ssl-support
Browse files Browse the repository at this point in the history
Ssl support
  • Loading branch information
synfinatic authored Jun 30, 2024
2 parents 8363d9d + 0693c6a commit 42feabc
Show file tree
Hide file tree
Showing 23 changed files with 667 additions and 54 deletions.
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@

### New Features

* Add support for the `AWS_CONTAINER_AUTHORIZATION_TOKEN` env variable #516
* Add support for HTTP Auth/`AWS_CONTAINER_AUTHORIZATION_TOKEN` env variable #516
* Add support for HTTPS #518

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

Expand Down
55 changes: 41 additions & 14 deletions cmd/aws-sso/ecs_client_cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,7 @@ import (
"github.com/synfinatic/gotable"
)

type EcsListCmd struct {
Auth string `kong:"help='Full HTTP Authorization token to use for ECS Server',env='AWS_CONTAINER_AUTHORIZATION_TOKEN'"`
}
type EcsListCmd struct{}

type EcsLoadCmd struct {
// AWS Params
Expand All @@ -44,18 +42,15 @@ type EcsLoadCmd struct {
Profile string `kong:"short='p',help='Name of AWS Profile to assume',predictor='profile',xor='account,role'"`

// Other params
Auth string `kong:"help='Full HTTP Authorization token to use for ECS Server',env='AWS_CONTAINER_AUTHORIZATION_TOKEN'"`
Port int `kong:"help='TCP port of aws-sso ECS Server',env='AWS_SSO_ECS_PORT',default=4144"` // SEE ECS_PORT in ecs_cmd.go
Slotted bool `kong:"short='s',help='Load credentials in a unique slot using the ProfileName as the key'"`
Port int `kong:"help='TCP port of aws-sso ECS Server',env='AWS_SSO_ECS_PORT',default=4144"` // SEE ECS_PORT in ecs_cmd.go
Slotted bool `kong:"short='s',help='Load credentials in a unique slot using the ProfileName as the key'"`
}

type EcsProfileCmd struct {
Auth string `kong:"help='Full HTTP Authorization token to use for ECS Server',env='AWS_CONTAINER_AUTHORIZATION_TOKEN'"`
Port int `kong:"help='TCP port of aws-sso ECS Server',env='AWS_SSO_ECS_PORT',default=4144"`
Port int `kong:"help='TCP port of aws-sso ECS Server',env='AWS_SSO_ECS_PORT',default=4144"`
}

type EcsUnloadCmd struct {
Auth string `kong:"help='Full HTTP Authorization token to use for ECS Server',env='AWS_CONTAINER_AUTHORIZATION_TOKEN'"`
Port int `kong:"help='TCP port of aws-sso ECS Server',env='AWS_SSO_ECS_PORT',default=4144"`
Profile string `kong:"short='p',help='Name of AWS Profile to unload',predictor='profile'"`
}
Expand All @@ -74,15 +69,23 @@ func (cc *EcsLoadCmd) Run(ctx *RunContext) error {
}

func (cc *EcsProfileCmd) Run(ctx *RunContext) error {
c := client.NewECSClient(ctx.Cli.Ecs.Profile.Port, ctx.Cli.Ecs.Profile.Auth)
clientCert, err := ctx.Store.GetEcsSslCert()
if err != nil {
return err
}
bearerToken, err := ctx.Store.GetEcsBearerToken()
if err != nil {
return err
}
c := client.NewECSClient(ctx.Cli.Ecs.Profile.Port, bearerToken, clientCert)

profile, err := c.GetProfile()
if err != nil {
return err
}

if profile.ProfileName == "" {
return fmt.Errorf("No profile loaded in ECS Server.")
return fmt.Errorf("no profile loaded in ECS Server")
}

profiles := []ecs.ListProfilesResponse{
Expand All @@ -92,7 +95,15 @@ func (cc *EcsProfileCmd) Run(ctx *RunContext) error {
}

func (cc *EcsUnloadCmd) Run(ctx *RunContext) error {
c := client.NewECSClient(ctx.Cli.Ecs.Unload.Port, ctx.Cli.Ecs.Unload.Auth)
clientCert, err := ctx.Store.GetEcsSslCert()
if err != nil {
return err
}
bearerToken, err := ctx.Store.GetEcsBearerToken()
if err != nil {
return err
}
c := client.NewECSClient(ctx.Cli.Ecs.Unload.Port, bearerToken, clientCert)

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

// do something
c := client.NewECSClient(ctx.Cli.Ecs.Load.Port, ctx.Cli.Ecs.Load.Auth)
clientCert, err := ctx.Store.GetEcsSslCert()
if err != nil {
return err
}
bearerToken, err := ctx.Store.GetEcsBearerToken()
if err != nil {
return err
}
c := client.NewECSClient(ctx.Cli.Ecs.Load.Port, bearerToken, clientCert)

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, ctx.Cli.Ecs.List.Auth)
clientCert, err := ctx.Store.GetEcsSslCert()
if err != nil {
return err
}
bearerToken, err := ctx.Store.GetEcsBearerToken()
if err != nil {
return err
}
c := client.NewECSClient(ctx.Cli.Ecs.Profile.Port, bearerToken, clientCert)

profiles, err := c.ListProfiles()
if err != nil {
Expand Down
54 changes: 50 additions & 4 deletions cmd/aws-sso/ecs_cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import (
"context"
"fmt"
"net"
"os"
"strings"

// "github.com/davecgh/go-spew/spew"
Expand All @@ -34,7 +35,8 @@ const (

type EcsCmd struct {
Run EcsRunCmd `kong:"cmd,help='Run the ECS Server'"`
BearerToken EcsBearerTokenCmd `kong:"cmd,help='Configure the ECS Server bearer token'"`
BearerToken EcsBearerTokenCmd `kong:"cmd,help='Configure the ECS Server/AWS Client bearer token'"`
Cert EcsCertCmd `kong:"cmd,help='Configure the ECS Server SSL certificate'"`
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'"`
Expand All @@ -58,8 +60,20 @@ func (cc *EcsRunCmd) Run(ctx *RunContext) error {
if token == "" {
log.Warnf("No authentication token set, use 'aws-sso ecs bearer-token' to set one")
}

s, err := server.NewEcsServer(context.TODO(), token, l)
var privateKey, certChain string
if privateKey, err = ctx.Store.GetEcsSslKey(); err != nil {
return err
} else if privateKey != "" {
// only get the certificate if the private key is set
certChain, err = ctx.Store.GetEcsSslCert()
if err != nil {
return err
}
log.Infof("Running ECS Server with SSL/TLS enabled")
} else {
log.Infof("Running ECS Server without SSL/TLS")
}
s, err := server.NewEcsServer(context.TODO(), token, l, privateKey, certChain)
if err != nil {
return err
}
Expand All @@ -72,10 +86,12 @@ type EcsBearerTokenCmd struct {
}

func (cc *EcsBearerTokenCmd) Run(ctx *RunContext) error {
// Store the token in the SecureStore
// Delete the token
if ctx.Cli.Ecs.BearerToken.Delete {
return ctx.Store.DeleteEcsBearerToken()
}

// Or store the token in the SecureStore
if ctx.Cli.Ecs.BearerToken.Token == "" {
return fmt.Errorf("no token provided")
}
Expand All @@ -84,3 +100,33 @@ func (cc *EcsBearerTokenCmd) Run(ctx *RunContext) error {
}
return ctx.Store.SaveEcsBearerToken(ctx.Cli.Ecs.BearerToken.Token)
}

type EcsCertCmd struct {
CertChain string `kong:"short=c,type='existingfile',help='Path to certificate chain PEM file',predictor='allFiles',xor='key'"`
PrivateKey string `kong:"short=p,type='existingfile',help='Path to private key file PEM file',predictor='allFiles',xor='cert'"`
Delete bool `kong:"short=d,help='Delete the current SSL certificate key pair',xor='key,cert'"`
}

func (cc *EcsCertCmd) Run(ctx *RunContext) error {
// If delete flag is set, delete the key pair
if ctx.Cli.Ecs.Cert.Delete {
return ctx.Store.DeleteEcsSslKeyPair()
}

if ctx.Cli.Ecs.Cert.CertChain == "" && ctx.Cli.Ecs.Cert.PrivateKey != "" {
return fmt.Errorf("if --private-key is set, --cert-chain must also be set")
}

// Else, save the key pair
privateKey, err := os.ReadFile(ctx.Cli.Ecs.Cert.PrivateKey)
if err != nil {
return fmt.Errorf("failed to read private key file: %w", err)
}

certChain, err := os.ReadFile(ctx.Cli.Ecs.Cert.CertChain)
if err != nil {
return fmt.Errorf("failed to read certificate chain file: %w", err)
}

return ctx.Store.SaveEcsSslKeyPair(privateKey, certChain)
}
2 changes: 1 addition & 1 deletion cmd/aws-sso/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ var DEFAULT_CONFIG map[string]interface{} = map[string]interface{}{
type CLI struct {
// Common Arguments
Browser string `kong:"short='b',help='Path to browser to open URLs with',env='AWS_SSO_BROWSER'"`
ConfigFile string `kong:"name='config',default='${CONFIG_FILE}',help='Config file',env='AWS_SSO_CONFIG'"`
ConfigFile string `kong:"name='config',default='${CONFIG_FILE}',help='Config file',env='AWS_SSO_CONFIG',predict='allFiles'"`
LogLevel string `kong:"short='L',name='level',help='Logging level [error|warn|info|debug|trace] (default: warn)'"`
Lines bool `kong:"help='Print line number in logs'"`
UrlAction string `kong:"short='u',help='How to handle URLs [clip|exec|open|print|printurl|granted-containers|open-url-in-container] (default: open)'"`
Expand Down
1 change: 0 additions & 1 deletion docs/FAQ.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@ have to wait a few minutes.

You can see what the AWS ListAccountRoles API is returning via `aws-sso cache -L debug`


## Advanced Features

### Does AWS SSO CLI support role chaining?
Expand Down
56 changes: 56 additions & 0 deletions docs/ecs-server.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,56 @@ Will start the service on `localhost:4144`. For security purposes, the `aws-ss
ECS Server will _only_ run on localhost/127.0.0.1. You may select an alternative
port via the `--port` flag or setting the `AWS_SSO_ECS_PORT` environment variable.

### ECS Server security

The ECS Server supports both SSL/TLS encryption as well as HTTP Authentication.
Together, they allow using the `aws-sso` ECS Server on multi-user systems in a
secure manner.

**Important:** Failure to configure HTTP Authentication _and_ SSL/TLS encryption
risks any user on the system running the `aws-sso` ECS Server access to your
AWS IAM authentication tokens.

You will need to create an SSL certificate/key pair in PKCS#8/PEM format. Typically,
this will be a self-signed certificate which can be generated thusly:

```bash
cat <<-EOF > config.ssl
[dn]
CN=localhost
[req]
distinguished_name = dn
[EXT]
subjectAltName=DNS:localhost,IP:127.0.0.1
keyUsage=digitalSignature
extendedKeyUsage=serverAuth
EOF

openssl req -x509 -out localhost.crt -keyout localhost.key \
-newkey rsa:2048 -nodes -sha256 -subj '/CN=localhost' -extensions EXT -config config.ssl

rm config.ssl
```

Once you have your certificate and private key, you will need to save them into the
`aws-sso` secure store:

```bash
aws-sso ecs cert --private-key localhost.key --cert-chain localhost.crt
```

**Important:** At this point, you should delete the private key file `localhost.key` for security.

The `localhost.crt` file will be automatically trusted by the `aws-sso` client if it
uses the same secure store. Otherwise, you will need to copy the `localhost.crt` file
to the other host and either add it to the local secure store, or add it to the
appropriate SSL CA trust store for your Python, Java, GoLang, etc AWS SDK.

```bash
# add the certificate to another aws-sso secure store
aws-sso ecs cert --cert-chain localhost.crt
```

## Environment variables

### AWS\_CONTAINER\_CREDENTIALS\_FULL\_URI
Expand All @@ -30,6 +80,8 @@ AWS clients and `aws-sso` should use:

`AWS_CONTAINER_CREDENTIALS_FULL_URI=http://localhost:4144/`

**Note:** If you have configured an SSL certificate as described above, use `https://localhost:4144`.

### AWS\_CONTAINER\_CREDENTIALS\_RELATIVE\_URI

It is important to _not_ set `AWS_CONTAINER_CREDENTIALS_RELATIVE_URI`
Expand Down Expand Up @@ -61,6 +113,8 @@ Ensure you have exported the following shell ENV variable:

`export AWS_CONTAINER_CREDENTIALS_FULL_URI=http://localhost:4144/creds`

**Note:** If you have configured an SSL certificate as described above, use `https://localhost:4144/creds`.

Then just:

`aws sts get-caller-identity`
Expand Down Expand Up @@ -106,6 +160,8 @@ Accessing the individual credentials is done via the `profile` query parameter:

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

**Note:** If you have configured an SSL certificate as described above, use `httpss://localhost:4144/slot/ExampleProfileName`.

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

Expand Down
6 changes: 4 additions & 2 deletions docs/remote-ssh.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,7 @@ encrypted over ssh.

**Note:** The root user or anyone with [CAP_NET_RAW or CAP_NET_ADMIN](https://man7.org/linux/man-pages/man7/capabilities.7.html)
will be able to intercept the HTTP traffic on either endpoint and obtain the bearer token
and/or any IAM Credentials stored in the ECS Server. As of this time, `aws-sso` does
[not support HTTPS](https://github.com/synfinatic/aws-sso-cli/issues/518) for full end-to-end encryption.
and/or any IAM Credentials stored in the ECS Server if you have not [enabled SSL](ecs-server.md#ecs-server-security).

## On your local system

Expand All @@ -45,6 +44,9 @@ or [tmux](https://hamvocke.com/blog/a-quick-and-easy-guide-to-tmux/) session):
1. Verify everything works:
`aws sts get-caller-identity`

**Note:** If you have [loaded an SSL certificate/private](ecs-server.md#ecs-server-security)
key into `aws-sso` use: `export AWS_CONTAINER_CREDENTIALS_FULL_URI=https://localhost:4144/` instead.

**Important:** You must choose a strong secret value for your bearer token secret! This is
what prevents anyone else from using your IAM credentials without your permission. Your bearer
token should be long and random enough to prevent bruteforce attacks.
Expand Down
Loading

0 comments on commit 42feabc

Please sign in to comment.