Skip to content

Commit

Permalink
work on refactoring and documentation of ECS
Browse files Browse the repository at this point in the history
Fixes: #938
  • Loading branch information
synfinatic committed Jul 7, 2024
1 parent 3bb3185 commit 9c02e64
Show file tree
Hide file tree
Showing 10 changed files with 292 additions and 119 deletions.
7 changes: 7 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
Dockerfile*
dist/*
*.crt
*.key
tags
coverage.out
aws-ssl-test
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,4 @@ COPY --from=builder /app/dist/aws-sso .
# Set the entrypoint for the container
EXPOSE 4144

ENTRYPOINT ["./aws-sso", "ecs", "run", "--docker"]
ENTRYPOINT ["./aws-sso", "ecs", "server", "--docker"]
141 changes: 33 additions & 108 deletions cmd/aws-sso/ecs_cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,144 +19,69 @@ package main
*/

import (
"context"
"fmt"
"net"
"os"
"strings"

// "github.com/davecgh/go-spew/spew"
"github.com/synfinatic/aws-sso-cli/internal/ecs"
"github.com/synfinatic/aws-sso-cli/internal/ecs/server"
)

const (
ECS_PORT = 4144
)

type EcsCmd struct {
Run EcsRunCmd `kong:"cmd,help='Run the ECS Server locally'"`
BearerToken EcsBearerTokenCmd `kong:"cmd,help='Configure the ECS Server/AWS Client bearer token'"`
Docker EcsDockerCmd `kong:"cmd,help='Start the ECS Server in a Docker container'"`
Cert EcsCertCmd `kong:"cmd,help='Configure the ECS Server SSL certificate/private key'"`
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'"`
}

type EcsRunCmd struct {
Port int `kong:"help='TCP port to listen on',env='AWS_SSO_ECS_PORT',default=4144"`
// hidden flags are for internal use only when running in a docker container
Docker bool `kong:"hidden,help='Enable Docker support for ECS Server'"`
}

func (cc *EcsRunCmd) Run(ctx *RunContext) error {
// Start the ECS Server
ip := "127.0.0.1"
if ctx.Cli.Ecs.Run.Docker {
ip = "0.0.0.0"
}
l, err := net.Listen("tcp", fmt.Sprintf("%s:%d", ip, ctx.Cli.Ecs.Run.Port))
if err != nil {
return err
}

var bearerToken, privateKey, certChain string
if ctx.Cli.Ecs.Run.Docker {
// fetch the creds from our temporary file mounted in the docker container
f, err := ecs.OpenSecurityFile(ecs.READ_ONLY)
if err != nil {
log.Warnf("Failed to open ECS credentials file: %s", err.Error())
} else {
creds, err := ecs.ReadSecurityConfig(f)
if err != nil {
return err
}
// have to manually close since defer won't work in this case
f.Close()
os.Remove(f.Name())

bearerToken = creds.BearerToken
privateKey = creds.PrivateKey
certChain = creds.CertChain
}
} else {
if bearerToken, err = ctx.Store.GetEcsBearerToken(); err != nil {
return err
}

if privateKey, err = ctx.Store.GetEcsSslKey(); err != nil {
return err
} else if privateKey != "" {
// only get the certificate if the private key is set
if certChain, err = ctx.Store.GetEcsSslCert(); err != nil {
return err
}
}
}

if bearerToken == "" {
log.Warnf("HTTP Auth: disabled. Use 'aws-sso ecs bearer-token' to enable")
} else {
log.Info("HTTP Auth: enabled")
}

if privateKey != "" && certChain != "" {
log.Infof("SSL/TLS: enabled")
} else {
log.Warnf("SSL/TLS: disabled. Use 'aws-sso ecs cert' to enable")
}

s, err := server.NewEcsServer(context.TODO(), bearerToken, l, privateKey, certChain)
if err != nil {
return err
}
return s.Serve()
Auth EcsAuthCmd `kong:"cmd,help='Manage the ECS Server/AWS Client authentication'"`
SSL EcsSSLCmd `kong:"cmd,help='Manage the ECS Server SSL configuration'"`
Server EcsServerCmd `kong:"cmd,help='Run the ECS Server locally'"`
Docker EcsDockerCmd `kong:"cmd,help='Start the ECS Server in a Docker container'"`
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'"`
}

type EcsBearerTokenCmd struct {
Token string `kong:"short=t,help='Bearer token value to use for ECS Server',xor='flag'"`
Delete bool `kong:"short=d,help='Delete the current bearer token',xor='flag'"`
type EcsAuthCmd struct {
BearerToken string `kong:"short=t,help='Bearer token value to use for ECS Server',xor='flag'"`
Delete bool `kong:"short=d,help='Delete the current bearer token',xor='flag'"`
}

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

// Or store the token in the SecureStore
if ctx.Cli.Ecs.BearerToken.Token == "" {
if ctx.Cli.Ecs.Auth.BearerToken == "" {
return fmt.Errorf("no token provided")
}
if !strings.HasPrefix(ctx.Cli.Ecs.BearerToken.Token, "Bearer ") {
if !strings.HasPrefix(ctx.Cli.Ecs.Auth.BearerToken, "Bearer ") {
return fmt.Errorf("token should start with 'Bearer '")
}
return ctx.Store.SaveEcsBearerToken(ctx.Cli.Ecs.BearerToken.Token)
return ctx.Store.SaveEcsBearerToken(ctx.Cli.Ecs.Auth.BearerToken)
}

type EcsCertCmd struct {
Load EcsCertLoadCmd `kong:"cmd,help='Load a new SSL certificate/private key into the ECS Server'"`
Delete EcsCertDeleteCmd `kong:"cmd,help='Delete the current SSL certificate/private key'"`
Print EcsCertPrintCmd `kong:"cmd,help='Print the current SSL certificate'"`
type EcsSSLCmd struct {
Save EcsSSLSaveCmd `kong:"cmd,help='Save a new SSL certificate/private key'"`
Delete EcsSSLDeleteCmd `kong:"cmd,help='Delete the current SSL certificate/private key'"`
Print EcsSSLPrintCmd `kong:"cmd,help='Print the current SSL certificate'"`
}

type EcsCertLoadCmd struct {
CertChain string `kong:"short=c,type='existingfile',help='Path to certificate chain PEM file',predictor='allFiles',required"`
PrivateKey string `kong:"short=p,type='existingfile',help='Path to private key file PEM file',predictor='allFiles'"`
Force bool `kong:"hidden,help='Force loading the certificate'"`
type EcsSSLSaveCmd struct {
Certificate string `kong:"short=c,type='existingfile',help='Path to certificate chain PEM file',predictor='allFiles',required"`
PrivateKey string `kong:"short=p,type='existingfile',help='Path to private key file PEM file',predictor='allFiles'"`
Force bool `kong:"hidden,help='Force loading the certificate'"`
}

type EcsCertDeleteCmd struct{}
type EcsSSLDeleteCmd struct{}

func (cc *EcsCertDeleteCmd) Run(ctx *RunContext) error {
func (cc *EcsSSLDeleteCmd) Run(ctx *RunContext) error {
return ctx.Store.DeleteEcsSslKeyPair()
}

type EcsCertPrintCmd struct{}
type EcsSSLPrintCmd struct{}

func (cc *EcsCertPrintCmd) Run(ctx *RunContext) error {
func (cc *EcsSSLPrintCmd) Run(ctx *RunContext) error {
cert, err := ctx.Store.GetEcsSslCert()
if err != nil {
return err
Expand All @@ -168,23 +93,23 @@ func (cc *EcsCertPrintCmd) Run(ctx *RunContext) error {
return nil
}

func (cc *EcsCertLoadCmd) Run(ctx *RunContext) error {
func (cc *EcsSSLSaveCmd) Run(ctx *RunContext) error {
var privateKey, certChain []byte
var err error

if !ctx.Cli.Ecs.Cert.Load.Force {
if !ctx.Cli.Ecs.SSL.Save.Force {
log.Warn("This feature is experimental and may not work as expected.")
log.Warn("Please read https://github.com/synfinatic/aws-sso-cli/issues/936 before contiuing.")
log.Fatal("Use `--force` to continue anyways.")
}

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

if ctx.Cli.Ecs.Cert.Load.PrivateKey != "" {
privateKey, err = os.ReadFile(ctx.Cli.Ecs.Cert.Load.PrivateKey)
if ctx.Cli.Ecs.SSL.Save.PrivateKey != "" {
privateKey, err = os.ReadFile(ctx.Cli.Ecs.SSL.Save.PrivateKey)
if err != nil {
return fmt.Errorf("failed to read private key file: %w", err)
}
Expand Down
8 changes: 4 additions & 4 deletions cmd/aws-sso/ecs_docker_cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,9 @@ type EcsDockerCmd struct {
}

type EcsDockerStartCmd struct {
BindIP string `kong:"help='Host IP address to bind to the ECS Server',default='127.0.0.1'"`
BindPort string `kong:"help='Host port to bind to the ECS Server',default='4144'"`
Version string `kong:"help='ECS Server docker image version',default='${VERSION}'"`
BindIP string `kong:"help='Host IP address to bind to the ECS Server',default='127.0.0.1'"`
Port string `kong:"help='Host port to bind to the ECS Server',default='4144'"`
Version string `kong:"help='ECS Server docker image version',default='${VERSION}'"`
}

func (cc *EcsDockerStartCmd) Run(ctx *RunContext) error {
Expand Down Expand Up @@ -93,7 +93,7 @@ func (cc *EcsDockerStartCmd) Run(ctx *RunContext) error {

portBinding := nat.PortBinding{
HostIP: ctx.Cli.Ecs.Docker.Start.BindIP,
HostPort: ctx.Cli.Ecs.Docker.Start.BindPort,
HostPort: ctx.Cli.Ecs.Docker.Start.Port,
}

hostConfig := &container.HostConfig{
Expand Down
108 changes: 108 additions & 0 deletions cmd/aws-sso/ecs_server_cmd.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
package main

/*
* AWS SSO CLI
* Copyright (c) 2021-2024 Aaron Turner <synfinatic at gmail dot com>
*
* This program is free software: you can redistribute it
* and/or modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or with the authors permission any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

import (
"context"
"fmt"
"net"
"os"

"github.com/synfinatic/aws-sso-cli/internal/ecs"
"github.com/synfinatic/aws-sso-cli/internal/ecs/server"
)

type EcsServerCmd struct {
BindIP string `kong:"help='Bind address for ECS Server',env='AWS_SSO_ECS_BIND',default='127.0.0.1'"`
Port int `kong:"help='TCP port to listen on',env='AWS_SSO_ECS_PORT',default=4144"`
// hidden flags are for internal use only when running in a docker container
Docker bool `kong:"hidden"`
DisableSSL bool `kong:"help='Disable SSL/TLS for the ECS Server'"`
}

func (cc *EcsServerCmd) Run(ctx *RunContext) error {
// Start the ECS Server
bindIP := ctx.Cli.Ecs.Server.BindIP
if ctx.Cli.Ecs.Server.Docker {
// if running in a docker container, bind to all interfaces
bindIP = "0.0.0.0"
}
l, err := net.Listen("tcp", fmt.Sprintf("%s:%d", bindIP, ctx.Cli.Ecs.Server.Port))
if err != nil {
return err
}

var bearerToken, privateKey, certChain string
if ctx.Cli.Ecs.Server.Docker {
// fetch the creds from our temporary file mounted in the docker container
f, err := ecs.OpenSecurityFile(ecs.READ_ONLY)
if err != nil {
log.Warnf("Failed to open ECS credentials file: %s", err.Error())
} else {
creds, err := ecs.ReadSecurityConfig(f)
if err != nil {
return err
}
// have to manually close since defer won't work in this case
f.Close()
os.Remove(f.Name())

bearerToken = creds.BearerToken
privateKey = creds.PrivateKey
certChain = creds.CertChain
}
} else {
if bearerToken, err = ctx.Store.GetEcsBearerToken(); err != nil {
return err
}

if privateKey, err = ctx.Store.GetEcsSslKey(); err != nil {
return err
} else if privateKey != "" {
// only get the certificate if the private key is set
if certChain, err = ctx.Store.GetEcsSslCert(); err != nil {
return err
}
}
}

if bearerToken == "" {
log.Warnf("HTTP Auth: disabled. Use 'aws-sso ecs bearer-token' to enable")
} else {
log.Info("HTTP Auth: enabled")
}

// Disable SSL, even if configure
if ctx.Cli.Ecs.Server.DisableSSL {
privateKey = ""
certChain = ""
}

if privateKey != "" && certChain != "" {
log.Infof("SSL/TLS: enabled")
} else {
log.Warnf("SSL/TLS: disabled. Use 'aws-sso ecs cert' to enable")
}

s, err := server.NewEcsServer(context.TODO(), bearerToken, l, privateKey, certChain)
if err != nil {
return err
}
return s.Serve()
}
6 changes: 3 additions & 3 deletions cmd/aws-sso/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ type CLI struct {
Completions CompleteCmd `kong:"cmd,help='Manage shell completions'"`
ConfigProfiles ConfigProfilesCmd `kong:"cmd,help='Update ~/.aws/config with AWS SSO profiles from the cache'"`
Config ConfigCmd `kong:"cmd,help='Run the configuration wizard'"`
Ecs EcsCmd `kong:"cmd,help='ECS Server commands'"`
Ecs EcsCmd `kong:"cmd,help='ECS server/client commands'"`
Version VersionCmd `kong:"cmd,help='Print version and exit'"`
}

Expand Down Expand Up @@ -170,7 +170,7 @@ func main() {
log.Fatalf("%s", err.Error())
}
return
case "ecs run":
case "ecs server":
// side-step the rest of the setup...
if err = ctx.Run(&runCtx); err != nil {
log.Fatalf("%s", err.Error())
Expand Down Expand Up @@ -351,5 +351,5 @@ func logLevelValidate(level string) error {
if utils.StrListContains(level, VALID_LOG_LEVELS) || level == "" {
return nil
}
return fmt.Errorf("Invalid value for --level: %s", level)
return fmt.Errorf("invalid value for --level: %s", level)
}
6 changes: 6 additions & 0 deletions docs/FAQ.md
Original file line number Diff line number Diff line change
Expand Up @@ -398,6 +398,12 @@ set [CacheRefresh](config.md#CacheRefresh) to a higher value
(cache roles for longer) or you can completely disable the auto-refresh of
the cache by setting `CacheRefresh` to `0`.

### Error response from daemon: client version 1.46 is too new. Maximum supported API version is 1.45

The version of the Docker Go SDK used by `aws-sso` is higher than the version supported
by your Docker daemon. Run `export DOCKER_API_VERSION=1.45` (set the version
appropriate based on the error message) to fix.

## Misc

### What is the story with Homebrew support?
Expand Down
Loading

0 comments on commit 9c02e64

Please sign in to comment.