diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 00000000..e89b2d9e --- /dev/null +++ b/.dockerignore @@ -0,0 +1,7 @@ +Dockerfile* +dist/* +*.crt +*.key +tags +coverage.out +aws-ssl-test diff --git a/Dockerfile b/Dockerfile index ca13bfe9..2fb58dac 100644 --- a/Dockerfile +++ b/Dockerfile @@ -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"] \ No newline at end of file +ENTRYPOINT ["./aws-sso", "ecs", "server", "--docker"] \ No newline at end of file diff --git a/cmd/aws-sso/ecs_cmd.go b/cmd/aws-sso/ecs_cmd.go index 1052d442..eb89ebda 100644 --- a/cmd/aws-sso/ecs_cmd.go +++ b/cmd/aws-sso/ecs_cmd.go @@ -19,15 +19,10 @@ 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 ( @@ -35,128 +30,58 @@ const ( ) 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 @@ -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) } diff --git a/cmd/aws-sso/ecs_docker_cmd.go b/cmd/aws-sso/ecs_docker_cmd.go index ad925fe1..fd0826d0 100644 --- a/cmd/aws-sso/ecs_docker_cmd.go +++ b/cmd/aws-sso/ecs_docker_cmd.go @@ -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 { @@ -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{ diff --git a/cmd/aws-sso/ecs_server_cmd.go b/cmd/aws-sso/ecs_server_cmd.go new file mode 100644 index 00000000..2c5a1d52 --- /dev/null +++ b/cmd/aws-sso/ecs_server_cmd.go @@ -0,0 +1,108 @@ +package main + +/* + * AWS SSO CLI + * Copyright (c) 2021-2024 Aaron Turner + * + * 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 . + */ + +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() +} diff --git a/cmd/aws-sso/main.go b/cmd/aws-sso/main.go index d5204a92..37ab4865 100644 --- a/cmd/aws-sso/main.go +++ b/cmd/aws-sso/main.go @@ -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'"` } @@ -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()) @@ -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) } diff --git a/docs/FAQ.md b/docs/FAQ.md index 43cbad8a..3bc997a0 100644 --- a/docs/FAQ.md +++ b/docs/FAQ.md @@ -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? diff --git a/docs/commands-ecs.md b/docs/commands-ecs.md new file mode 100644 index 00000000..8b72affa --- /dev/null +++ b/docs/commands-ecs.md @@ -0,0 +1,125 @@ +# ECS Commands + +For information about the ECS Server functionality, see the [ecs-server](ecs-server.md) page. + +## Commands + +### ecs auth + +Configures the HTTP Authentication BearerToken. Once set, all future client +requests to the ECS Server will need to provide the correct credentials. +`aws-sso` utilizing the same SecureStore as the ECS Server will automatically +provide the necessary HTTP Auth header, but other AWS clients utilizing the +AWS SDK will require [AWS_CONTAINER_AUTHORIZATION_TOKEN](https://docs.aws.amazon.com/sdkref/latest/guide/feature-container-credentials.html) to be set. + +Flags: + + * `--bearer-token` -- Specify the bearer token secret. + * `--delete` -- Delete the bearer token and disable authentication. + +--- + +### ecs ssl save + + Configures the SSL Certificate and Private Key to enable SSL/TLS. Saves the + SSL certificate and private key to the SecureStore. + + **Note:** At this time, this feature is not recommended due to a bug + in the [AWS SDK](https://github.com/boto/boto3/issues/4188). + + Flags: + + * `--certificate` -- Path to SSL certificate file in PEM format + * `--private-key` -- Path to SSL private key in PEM format + +--- + +### ecs ssl delete + +Delete the SSL certificate and private key from the Secure Store and disables +SSL/TLS for the ECS Server. + +--- + +### ecs ssl print + +Prints the SSL certificate stored in the SecureStore. + +--- + +### ecs server + +Starts the ECS Server in the foreground. + +Flags: + + * `--disable-ssl` -- Disables SSL/TLS, even if a certificate and private key are available. + +--- + +### ecs docker start + +Starts the ECS Server in a Docker container. + +Flags: + + * `--bind-ip` -- IP address to bind the service to. (default 127.0.0.1) + * `--port` -- Port to listen on. (default 4144) + +--- + +### ecs docker stop + +Stops the ECS Server Docker container. + +--- + +### ecs list + +List the AWS Profiles stored in the ECS Server. + +Flags: + + * `--server` -- host:port of the ECS Server (default localhost:4144) + +--- + +### ecs load + +Load the AWS IAM Role credentials into the ECS Server for clients to use. + +Flags: + + * `--arn `, `-a` -- ARN of role to assume (`$AWS_SSO_ROLE_ARN`) + * `--account `, `-A` -- AWS AccountID of role to assume (`$AWS_SSO_ACCOUNT_ID`) + * `--role `, `-R` -- Name of AWS Role to assume (requires `--account`) (`$AWS_SSO_ROLE_NAME`) + * `--profile `, `-p` -- Name of AWS Profile to assume + * `--account` -- AWS AccountID of the IAM Role to load. + * `--server` -- host:port of the ECS Server (default localhost:4144) + * `--slotted` -- Load the IAM credentials into a unique slot using the ProfileName as the key + +--- + +### ecs unload + +Removes the AWS IAM Role credentials from the ECS Server and makes them unavailable to any clients to use. + +Flags: + + * `--arn `, `-a` -- ARN of role to assume (`$AWS_SSO_ROLE_ARN`) + * `--account `, `-A` -- AWS AccountID of role to assume (`$AWS_SSO_ACCOUNT_ID`) + * `--role `, `-R` -- Name of AWS Role to assume (requires `--account`) (`$AWS_SSO_ROLE_NAME`) + * `--profile `, `-p` -- Name of AWS Profile to assume + * `--account` -- AWS AccountID of the IAM Role to load. + * `--server` -- host:port of the ECS Server (default localhost:4144) + * `--slotted` -- Load the IAM credentials into a unique slot using the ProfileName as the key + +--- + +### ecs profile + +Fetches the ProfileName of the role stored in the default slot of the ECS Server. + +Flags: + + * `--slotted` -- Load the IAM credentials into a unique slot using the ProfileName as the key \ No newline at end of file diff --git a/docs/commands.md b/docs/commands.md index 2da04dd3..e9f23010 100644 --- a/docs/commands.md +++ b/docs/commands.md @@ -1,4 +1,4 @@ -# aws-sso commands +# aws-sso Commands ## Common Flags @@ -142,7 +142,7 @@ _strongly_ discouraged that users set this to `~/.aws/credentials`, but use a te ### ecs -For information about the ECS Server functionality, see the [ecs-server](ecs-server.md) page. +[ecs commands](ecs-commands.md) --- diff --git a/mkdocs.yml b/mkdocs.yml index a027dc2a..4daa2daa 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -26,6 +26,8 @@ nav: - 'Configuration': config.md - 'Commands & Usage': commands.md - 'Frequently Asked Questions': FAQ.md + - 'Advanced Topics': + - 'ECS Server Commands': ecs-commands.md - 'ECS Server Mode': ecs-server.md - 'Running On Remote Hosts': remote-ssh.md - 'About': @@ -37,4 +39,4 @@ nav: - 'Release Signing Key': code-sign-key.asc.md - 'Commit Signing Key': commit-sign-key.asc.md - 'Developer Notes': - - 'Release New Version': release.md + - 'Release New Version': release.md \ No newline at end of file