Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Docker ecs server #935

Merged
merged 1 commit into from
Jul 5, 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
34 changes: 34 additions & 0 deletions .github/workflows/docker-hub.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
name: Publish Docker image

on:
release:
types: [published]

jobs:
push_to_registry:
name: Push Docker image to Docker Hub
runs-on: ubuntu-latest
steps:
- name: Check out the repo
uses: actions/checkout@v4

- name: Log in to Docker Hub
uses: docker/#-action@v3
with:
username: synfinatic
password: ${{ secrets.DOCKER_HUB_SECRET }}

- name: Extract metadata (tags, labels) for Docker
id: meta
uses: docker/metadata-action@v5
with:
images: synfinatic/aws-sso-cli-ecs-server

- name: Build and push Docker image
uses: docker/build-push-action@v4
with:
context: .
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}

1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

* Add support for HTTP Auth/`AWS_CONTAINER_AUTHORIZATION_TOKEN` env variable #516
* Add support for HTTPS #518
* Add Docker container support #569

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

Expand Down
22 changes: 22 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Stage 1: Build stage
FROM golang:1.21-alpine AS builder
RUN apk --no-cache add git gcc musl-dev make
WORKDIR /app

# Copy the source code into the container
COPY . .

# Build the Go application
RUN make

# Stage 2: Final stage
FROM alpine:latest

WORKDIR /app
# Copy the built binary from the previous stage
COPY --from=builder /app/dist/aws-sso .

# Set the entrypoint for the container
EXPOSE 4144

ENTRYPOINT ["./aws-sso", "ecs", "run", "--docker"]
11 changes: 8 additions & 3 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
PROJECT_VERSION := 1.17.0
DOCKER_REPO := synfinatic
PROJECT_NAME := aws-sso
PROJECT_VERSION := 1.17.0
DOCKER_REPO := synfinatic
PROJECT_NAME := aws-sso
DOCKER_PROJECT_NAME := aws-sso-cli-ecs-server

DIST_DIR ?= dist/
GOOS ?= $(shell uname -s | tr "[:upper:]" "[:lower:]")
Expand Down Expand Up @@ -267,3 +268,7 @@ serve-docs: ## Run mkdocs server on localhost:8000
-v $$(pwd):/docs \
-p 8000:8000 \
synfinatic/mkdocs-material:latest

docker: ## Build docker image
docker build -t $(DOCKER_REPO)/$(DOCKER_PROJECT_NAME):$(PROJECT_VERSION) \
-t $(DOCKER_REPO)/$(DOCKER_PROJECT_NAME):latest .
59 changes: 20 additions & 39 deletions cmd/aws-sso/ecs_client_cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,9 @@ import (
"github.com/synfinatic/gotable"
)

type EcsListCmd struct{}
type EcsListCmd struct {
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
}

type EcsLoadCmd struct {
// AWS Params
Expand Down Expand Up @@ -69,15 +71,7 @@ func (cc *EcsLoadCmd) Run(ctx *RunContext) error {
}

func (cc *EcsProfileCmd) Run(ctx *RunContext) error {
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)
c := newClient(ctx.Cli.Ecs.Profile.Port, ctx)

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

func (cc *EcsUnloadCmd) Run(ctx *RunContext) error {
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)
c := newClient(ctx.Cli.Ecs.Unload.Port, ctx)

return c.Delete(ctx.Cli.Ecs.Unload.Profile)
}

// Loads our AWS API creds into the ECS Server
func ecsLoadCmd(ctx *RunContext, awssso *sso.AWSSSO, accountId int64, role string) error {
c := newClient(ctx.Cli.Ecs.Load.Port, ctx)

creds := GetRoleCredentials(ctx, awssso, accountId, role)

cache := ctx.Settings.Cache.GetSSO() // ctx.Settings.Cache.Refresh(awssso, ssoConfig, ctx.Cli.SSO)
Expand All @@ -130,31 +118,12 @@ func ecsLoadCmd(ctx *RunContext, awssso *sso.AWSSSO, accountId int64, role strin
log.WithError(err).Warnf("Unable to update cache")
}

// do something
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 {
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)
c := newClient(ctx.Cli.Ecs.Profile.Port, ctx)

profiles, err := c.ListProfiles()
if err != nil {
Expand Down Expand Up @@ -187,3 +156,15 @@ func listProfiles(profiles []ecs.ListProfilesResponse) error {

return err
}

func newClient(port int, ctx *RunContext) *client.ECSClient {
certChain, err := ctx.Store.GetEcsSslCert()
if err != nil {
log.Fatalf("Unable to get ECS SSL cert: %s", err)
}
bearerToken, err := ctx.Store.GetEcsBearerToken()
if err != nil {
log.Fatalf("Unable to get ECS bearer token: %s", err)
}
return client.NewECSClient(port, bearerToken, certChain)
}
130 changes: 96 additions & 34 deletions cmd/aws-sso/ecs_cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import (
"strings"

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

Expand All @@ -34,9 +35,10 @@ const (
)

type EcsCmd struct {
Run EcsRunCmd `kong:"cmd,help='Run the ECS Server'"`
Run EcsRunCmd `kong:"cmd,help='Run the ECS Server locally'"`
BearerToken EcsBearerTokenCmd `kong:"cmd,help='Configure the ECS Server/AWS Client bearer token'"`
Cert EcsCertCmd `kong:"cmd,help='Configure the ECS Server SSL certificate'"`
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'"`
Expand All @@ -45,35 +47,68 @@ type EcsCmd struct {

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 {
l, err := net.Listen("tcp", fmt.Sprintf("127.0.0.1:%d", ctx.Cli.Ecs.Run.Port))
if err != nil {
return err
// Start the ECS Server
ip := "127.0.0.1"
if ctx.Cli.Ecs.Run.Docker {
ip = "0.0.0.0"
}

token, err := ctx.Store.GetEcsBearerToken()
l, err := net.Listen("tcp", fmt.Sprintf("%s:%d", ip, ctx.Cli.Ecs.Run.Port))
if err != nil {
return err
}
if token == "" {
log.Warnf("No authentication token set, use 'aws-sso ecs bearer-token' to set one")
}
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()

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
}
log.Infof("Running ECS Server with SSL/TLS enabled")

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.Infof("Running ECS Server without SSL/TLS")
log.Info("HTTP Auth: enabled")
}
s, err := server.NewEcsServer(context.TODO(), token, l, 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
}
Expand Down Expand Up @@ -102,31 +137,58 @@ func (cc *EcsBearerTokenCmd) Run(ctx *RunContext) error {
}

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'"`
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'"`
}

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()
}
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'"`
}

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")
}
type EcsCertDeleteCmd struct{}

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

type EcsCertPrintCmd struct{}

// Else, save the key pair
privateKey, err := os.ReadFile(ctx.Cli.Ecs.Cert.PrivateKey)
func (cc *EcsCertPrintCmd) Run(ctx *RunContext) error {
cert, err := ctx.Store.GetEcsSslCert()
if err != nil {
return fmt.Errorf("failed to read private key file: %w", err)
return err
}
if cert == "" {
return fmt.Errorf("no certificate found")
}
fmt.Println(cert)
return nil
}

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

if !ctx.Cli.Ecs.Cert.Load.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.CertChain)
certChain, err = os.ReadFile(ctx.Cli.Ecs.Cert.Load.CertChain)
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 err != nil {
return fmt.Errorf("failed to read private key file: %w", err)
}
}

return ctx.Store.SaveEcsSslKeyPair(privateKey, certChain)
}
Loading
Loading