From c5ea09d8e17206d536aa848cc86c9b9c75b87d4e Mon Sep 17 00:00:00 2001 From: Lee Brown Date: Sat, 13 Jul 2019 03:03:30 -0800 Subject: [PATCH] completed autocert implimentation for web-api --- .../cmd/web-api/ecs-task-definition.json | 4 +- example-project/cmd/web-api/main.go | 74 +++++++++++-------- example-project/cmd/web-app/main.go | 2 +- example-project/internal/mid/redirect.go | 20 +++-- .../internal/platform/devops/autocert.go | 73 ++++++++++++------ .../internal/platform/devops/config_sync.go | 13 ++-- .../internal/platform/devops/ecs_service.go | 21 +++--- .../internal/platform/web/request.go | 2 +- .../tools/truss/cmd/devops/models.go | 16 ++-- .../tools/truss/cmd/devops/service_deploy.go | 68 ++++++++--------- 10 files changed, 164 insertions(+), 129 deletions(-) diff --git a/example-project/cmd/web-api/ecs-task-definition.json b/example-project/cmd/web-api/ecs-task-definition.json index 703dec5..52e7bf7 100644 --- a/example-project/cmd/web-api/ecs-task-definition.json +++ b/example-project/cmd/web-api/ecs-task-definition.json @@ -36,8 +36,8 @@ {"name": "WEB_API_HTTPS_HOST", "value": "{HTTPS_HOST}"}, {"name": "WEB_API_APP_PROJECT", "value": "{APP_PROJECT}"}, {"name": "WEB_API_APP_BASE_URL", "value": "{APP_BASE_URL}"}, - {"name": "WEB_API_HOST_PRIMARY", "value": "{HOST_PRIMARY}"}, - {"name": "WEB_API_HOST_NAMES", "value": "{HOST_NAMES}"}, + {"name": "WEB_API_APP_HOST_PRIMARY", "value": "{HOST_PRIMARY}"}, + {"name": "WEB_API_APP_HOST_NAMES", "value": "{HOST_NAMES}"}, {"name": "WEB_API_REDIS_HOST", "value": "{CACHE_HOST}"}, {"name": "WEB_API_DB_HOST", "value": "{DB_HOST}"}, {"name": "WEB_API_DB_USER", "value": "{DB_USER}"}, diff --git a/example-project/cmd/web-api/main.go b/example-project/cmd/web-api/main.go index a155179..994dd26 100644 --- a/example-project/cmd/web-api/main.go +++ b/example-project/cmd/web-api/main.go @@ -30,6 +30,7 @@ import ( "github.com/go-redis/redis" "github.com/kelseyhightower/envconfig" "github.com/lib/pq" + "golang.org/x/crypto/acme" "golang.org/x/crypto/acme/autocert" awstrace "gopkg.in/DataDog/dd-trace-go.v1/contrib/aws/aws-sdk-go/aws" sqltrace "gopkg.in/DataDog/dd-trace-go.v1/contrib/database/sql" @@ -84,13 +85,14 @@ func main() { Host string `default:"" envconfig:"HOST"` ReadTimeout time.Duration `default:"5s" envconfig:"READ_TIMEOUT"` WriteTimeout time.Duration `default:"5s" envconfig:"WRITE_TIMEOUT"` + DisableHTTP2 bool `default:"false" envconfig:"DISABLE_HTTP2"` } App struct { Name string `default:"web-api" envconfig:"NAME"` - Project string `default:"" envconfig:"PROJECT"` + Project string `default:"" envconfig:"PROJECT"` BaseUrl string `default:"" envconfig:"BASE_URL" example:"http://example-project.com"` - HostPrimary string `envconfig:"HOST_PRIMARY" example:"example-project.com"` - HostNames []string `envconfig:"HOST_NAMES" example:"subdomain.example-project.com"` + HostPrimary string `envconfig:"HOST_PRIMARY" example:"example-project.com"` + HostNames []string `envconfig:"HOST_NAMES" example:"subdomain.example-project.com"` TemplateDir string `default:"./templates" envconfig:"TEMPLATE_DIR"` DebugHost string `default:"0.0.0.0:4000" envconfig:"DEBUG_HOST"` ShutdownTimeout time.Duration `default:"5s" envconfig:"SHUTDOWN_TIMEOUT"` @@ -116,12 +118,12 @@ func main() { AnalyticsRate float64 `default:"0.10" envconfig:"ANALYTICS_RATE"` } Aws struct { - AccessKeyID string `envconfig:"AWS_ACCESS_KEY_ID"` // WEB_API_AWS_AWS_ACCESS_KEY_ID or AWS_ACCESS_KEY_ID - SecretAccessKey string `envconfig:"AWS_SECRET_ACCESS_KEY" json:"-"` // don't print - Region string `default:"us-east-1" envconfig:"AWS_REGION"` - S3BucketPrivate string `envconfig:"S3_BUCKET_PRIVATE"` - S3BucketPublic string `envconfig:"S3_BUCKET_PUBLIC"` - SecretsManagerConfigPrefix string `default:"" envconfig:"SECRETS_MANAGER_CONFIG_PREFIX"` + AccessKeyID string `envconfig:"AWS_ACCESS_KEY_ID"` // WEB_API_AWS_AWS_ACCESS_KEY_ID or AWS_ACCESS_KEY_ID + SecretAccessKey string `envconfig:"AWS_SECRET_ACCESS_KEY" json:"-"` // don't print + Region string `default:"us-east-1" envconfig:"AWS_REGION"` + S3BucketPrivate string `envconfig:"S3_BUCKET_PRIVATE"` + S3BucketPublic string `envconfig:"S3_BUCKET_PUBLIC"` + SecretsManagerConfigPrefix string `default:"" envconfig:"SECRETS_MANAGER_CONFIG_PREFIX"` // Get an AWS session from an implicit source if no explicit // configuration is provided. This is useful for taking advantage of @@ -179,7 +181,6 @@ func main() { cfg.Aws.SecretsManagerConfigPrefix = filepath.Join(pts...) } - // If base URL is empty, set the default value from the HTTP Host if cfg.App.BaseUrl == "" { baseUrl := cfg.HTTP.Host @@ -196,7 +197,6 @@ func main() { cfg.App.BaseUrl = baseUrl } - // ========================================================================= // Log App Info @@ -239,7 +239,6 @@ func main() { awsSession = awstrace.WrapSession(awsSession) } - // ========================================================================= // Start Redis // Ensure the eviction policy on the redis cluster is set correctly. @@ -310,7 +309,6 @@ func main() { } defer masterDb.Close() - // ========================================================================= // Init new Authenticator var authenticator *auth.Authenticator @@ -324,30 +322,41 @@ func main() { log.Fatalf("main : Constructing authenticator : %+v", err) } - // ========================================================================= // Init redirect middleware to ensure all requests go to the primary domain. - baseSiteUrl, err := url.Parse(cfg.App.BaseUrl) - if err != nil { - log.Fatalf("main : Parse App Base URL : %s : %+v", cfg.App.BaseUrl, err) - } + primaryDomain := cfg.App.HostPrimary - var primaryDomain string - if strings.Contains(baseSiteUrl.Host, ":") { - primaryDomain, _, err = net.SplitHostPort(baseSiteUrl.Host) + // When primary host is not set, we can parse host from the base app URL. + if primaryDomain == "" { + baseSiteUrl, err := url.Parse(cfg.App.BaseUrl) if err != nil { - log.Fatalf("main : SplitHostPort : %s : %+v", baseSiteUrl.Host, err) + log.Fatalf("main : Parse App Base URL : %s : %+v", cfg.App.BaseUrl, err) + } + + if strings.Contains(baseSiteUrl.Host, ":") { + primaryDomain, _, err = net.SplitHostPort(baseSiteUrl.Host) + if err != nil { + log.Fatalf("main : SplitHostPort : %s : %+v", baseSiteUrl.Host, err) + } + } else { + primaryDomain = baseSiteUrl.Host } - } else { - primaryDomain = baseSiteUrl.Host } redirect := mid.DomainNameRedirect(mid.DomainNameRedirectConfig{ - DomainName: primaryDomain, + RedirectConfig: mid.RedirectConfig{ + Code: http.StatusMovedPermanently, + Skipper: func(ctx context.Context, w http.ResponseWriter, r *http.Request, params map[string]string) bool { + if r.URL.Path == "/ping" { + return true + } + return false + }, + }, + DomainName: primaryDomain, HTTPSEnabled: (cfg.HTTPS.Host != ""), }) - // ========================================================================= // Start Tracing Support th := fmt.Sprintf("%s:%d", cfg.Trace.Host, cfg.Trace.Port) @@ -369,7 +378,6 @@ func main() { }() } - // ========================================================================= // ECS Task registration for services that don't use an AWS Elastic Load Balancer. err = devops.EcsServiceTaskInit(log, awsSession) @@ -377,7 +385,6 @@ func main() { log.Fatalf("main : Ecs Service Task init : %+v", err) } - // ========================================================================= // Start API Service @@ -448,7 +455,10 @@ func main() { // Enable autocert to store certs via Secret Manager. secretPrefix := filepath.Join(cfg.Aws.SecretsManagerConfigPrefix, "autocert") - cache, err := devops.NewSecretManagerAutocertCache(log, awsSession, secretPrefix) + // Local file cache to reduce requests hitting Secret Manager. + localCache := autocert.DirCache(os.TempDir()) + + cache, err := devops.NewSecretManagerAutocertCache(log, awsSession, secretPrefix, localCache) if err != nil { log.Fatalf("main : HTTPS : %+v", err) } @@ -459,11 +469,15 @@ func main() { Cache: cache, } api.TLSConfig = &tls.Config{GetCertificate: m.GetCertificate} + api.TLSConfig.NextProtos = append(api.TLSConfig.NextProtos, acme.ALPNProto) + if !cfg.HTTPS.DisableHTTP2 { + api.TLSConfig.NextProtos = append(api.TLSConfig.NextProtos, "h2") + } httpServers = append(httpServers, api) go func() { - log.Printf("main : API Listening %s", cfg.HTTPS.Host) + log.Printf("main : API Listening %s with SSL cert for hosts %s", cfg.HTTPS.Host, strings.Join(hosts, ", ")) serverErrors <- api.ListenAndServeTLS("", "") }() } diff --git a/example-project/cmd/web-app/main.go b/example-project/cmd/web-app/main.go index fb09a1d..79e4940 100644 --- a/example-project/cmd/web-app/main.go +++ b/example-project/cmd/web-app/main.go @@ -306,7 +306,7 @@ func main() { } redirect := mid.DomainNameRedirect(mid.DomainNameRedirectConfig{ - DomainName: primaryDomain, + DomainName: primaryDomain, HTTPSEnabled: (cfg.HTTPS.Host != ""), }) diff --git a/example-project/internal/mid/redirect.go b/example-project/internal/mid/redirect.go index 645bcfd..0b392ee 100644 --- a/example-project/internal/mid/redirect.go +++ b/example-project/internal/mid/redirect.go @@ -2,9 +2,10 @@ package mid import ( "context" + "net/http" + "geeks-accelerator/oss/saas-starter-kit/example-project/internal/platform/web" "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer" - "net/http" ) type ( @@ -25,7 +26,7 @@ type ( // DomainNameRedirectConfig defines the details needed to apply redirects based on domain names. DomainNameRedirectConfig struct { RedirectConfig - DomainName string + DomainName string HTTPSEnabled bool } @@ -50,14 +51,12 @@ func DefaultSkipper(ctx context.Context, w http.ResponseWriter, r *http.Request, // HTTPSRedirectWithConfig returns an HTTPSRedirect middleware with config. // See `HTTPSRedirect()`. -func DomainNameRedirect(config DomainNameRedirectConfig) web.Middleware { +func DomainNameRedirect(config DomainNameRedirectConfig) web.Middleware { return redirect(config.RedirectConfig, func(scheme, host, uri string) (ok bool, url string) { // Redirects http requests to https. if config.HTTPSEnabled { if ok = scheme != "https"; ok { - url = "https://" + host + uri - scheme = "https" } } @@ -65,16 +64,15 @@ func DomainNameRedirect(config DomainNameRedirectConfig) web.Middleware { // Redirects all domain name alternatives to the primary hostname. if host != config.DomainName { host = config.DomainName + ok = true } - url = scheme + "://" + host + uri return }) } - // HTTPSRedirect redirects http requests to https. // For example, http://geeksinthewoods.com will be redirect to https://geeksinthewoods.com. func HTTPSRedirect() web.Middleware { @@ -83,7 +81,7 @@ func HTTPSRedirect() web.Middleware { // HTTPSRedirectWithConfig returns an HTTPSRedirect middleware with config. // See `HTTPSRedirect()`. -func HTTPSRedirectWithConfig(config RedirectConfig)web.Middleware { +func HTTPSRedirectWithConfig(config RedirectConfig) web.Middleware { return redirect(config, func(scheme, host, uri string) (ok bool, url string) { if ok = scheme != "https"; ok { url = "https://" + host + uri @@ -100,7 +98,7 @@ func HTTPSWWWRedirect() web.Middleware { // HTTPSWWWRedirectWithConfig returns an HTTPSRedirect middleware with config. // See `HTTPSWWWRedirect()`. -func HTTPSWWWRedirectWithConfig(config RedirectConfig) web.Middleware { +func HTTPSWWWRedirectWithConfig(config RedirectConfig) web.Middleware { return redirect(config, func(scheme, host, uri string) (ok bool, url string) { if ok = scheme != "https" && host[:3] != www; ok { url = "https://www." + host + uri @@ -117,7 +115,7 @@ func HTTPSNonWWWRedirect() web.Middleware { // HTTPSNonWWWRedirectWithConfig returns an HTTPSRedirect middleware with config. // See `HTTPSNonWWWRedirect()`. -func HTTPSNonWWWRedirectWithConfig(config RedirectConfig) web.Middleware { +func HTTPSNonWWWRedirectWithConfig(config RedirectConfig) web.Middleware { return redirect(config, func(scheme, host, uri string) (ok bool, url string) { if ok = scheme != "https"; ok { if host[:3] == www { @@ -154,7 +152,7 @@ func NonWWWRedirect() web.Middleware { // NonWWWRedirectWithConfig returns an HTTPSRedirect middleware with config. // See `NonWWWRedirect()`. -func NonWWWRedirectWithConfig(config RedirectConfig) web.Middleware { +func NonWWWRedirectWithConfig(config RedirectConfig) web.Middleware { return redirect(config, func(scheme, host, uri string) (ok bool, url string) { if ok = host[:3] == www; ok { url = scheme + "://" + host[4:] + uri diff --git a/example-project/internal/platform/devops/autocert.go b/example-project/internal/platform/devops/autocert.go index 9e52073..eb28c0b 100644 --- a/example-project/internal/platform/devops/autocert.go +++ b/example-project/internal/platform/devops/autocert.go @@ -7,26 +7,28 @@ import ( "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/awserr" + "github.com/aws/aws-sdk-go/aws/session" "github.com/aws/aws-sdk-go/service/secretsmanager" "github.com/pkg/errors" "golang.org/x/crypto/acme/autocert" - "github.com/aws/aws-sdk-go/aws/session" ) // SecretManagerAutocertCache implements the autocert.Cache interface for AWS Secrets Manager that is used by Manager // to store and retrieve previously obtained certificates and other account data as opaque blobs. -type SecretManagerAutocertCache struct { - awsSession *session.Session - log *log.Logger +type SecretManagerAutocertCache struct { + awsSession *session.Session + log *log.Logger secretPrefix string + cache autocert.Cache } // NewSecretManagerAutocertCache provides the functionality to keep config files sync'd between running tasks and across deployments. -func NewSecretManagerAutocertCache(log *log.Logger, awsSession *session.Session, secretPrefix string ) (*SecretManagerAutocertCache, error) { +func NewSecretManagerAutocertCache(log *log.Logger, awsSession *session.Session, secretPrefix string, cache autocert.Cache) (*SecretManagerAutocertCache, error) { return &SecretManagerAutocertCache{ awsSession, log, secretPrefix, + cache, }, nil } @@ -34,6 +36,16 @@ func NewSecretManagerAutocertCache(log *log.Logger, awsSession *session.Session, // If there's no such key, Get returns ErrCacheMiss. func (c *SecretManagerAutocertCache) Get(ctx context.Context, key string) ([]byte, error) { + // Check short term cache. + if c.cache != nil { + v, err := c.cache.Get(ctx, key) + if err != nil && err != autocert.ErrCacheMiss { + return nil, errors.WithStack(err) + } else if len(v) > 0 { + return v, nil + } + } + svc := secretsmanager.New(c.awsSession) secretID := filepath.Join(c.secretPrefix, key) @@ -43,7 +55,7 @@ func (c *SecretManagerAutocertCache) Get(ctx context.Context, key string) ([]byt SecretId: aws.String(secretID), }) if err != nil { - if aerr, ok := err.(awserr.Error); ok && aerr.Code() == secretsmanager.ErrCodeResourceNotFoundException { + if aerr, ok := err.(awserr.Error); ok && (aerr.Code() == secretsmanager.ErrCodeResourceNotFoundException || aerr.Code() == secretsmanager.ErrCodeInvalidRequestException) { return nil, autocert.ErrCacheMiss } @@ -52,7 +64,7 @@ func (c *SecretManagerAutocertCache) Get(ctx context.Context, key string) ([]byt log.Printf("AWS Secrets Manager : Secret %s found", secretID) - return res.SecretBinary, nil + return []byte(*res.SecretString), nil } // Put stores the data in the cache under the specified key. @@ -70,28 +82,27 @@ func (c *SecretManagerAutocertCache) Put(ctx context.Context, key string, data [ SecretString: aws.String(string(data)), }) if err != nil { - if aerr, ok := err.(awserr.Error); !ok { + aerr, ok := err.(awserr.Error) - if aerr.Code() == secretsmanager.ErrCodeInvalidRequestException { - // InvalidRequestException: You can't create this secret because a secret with this - // name is already scheduled for deletion. + if ok && aerr.Code() == secretsmanager.ErrCodeInvalidRequestException { + // InvalidRequestException: You can't create this secret because a secret with this + // name is already scheduled for deletion. - // Restore secret after it was already previously deleted. - _, err = svc.RestoreSecret(&secretsmanager.RestoreSecretInput{ - SecretId: aws.String(secretID), - }) - if err != nil { - return errors.Wrapf(err, "autocert failed to restore secret %s", secretID) - } - - } else if aerr.Code() != secretsmanager.ErrCodeResourceExistsException { - return errors.Wrapf(err, "autocert failed to create secret %s", secretID) + // Restore secret after it was already previously deleted. + _, err = svc.RestoreSecret(&secretsmanager.RestoreSecretInput{ + SecretId: aws.String(secretID), + }) + if err != nil { + return errors.Wrapf(err, "autocert failed to restore secret %s", secretID) } + + } else if !ok || aerr.Code() != secretsmanager.ErrCodeResourceExistsException { + return errors.Wrapf(err, "autocert failed to create secret %s", secretID) } // If where was a resource exists error for create, then need to update the secret instead. _, err = svc.UpdateSecret(&secretsmanager.UpdateSecretInput{ - SecretId: aws.String(secretID), + SecretId: aws.String(secretID), SecretString: aws.String(string(data)), }) if err != nil { @@ -103,6 +114,13 @@ func (c *SecretManagerAutocertCache) Put(ctx context.Context, key string, data [ log.Printf("AWS Secrets Manager : Secret %s created", secretID) } + if c.cache != nil { + err = c.cache.Put(ctx, key, data) + if err != nil { + return errors.WithStack(err) + } + } + return nil } @@ -116,7 +134,7 @@ func (c *SecretManagerAutocertCache) Delete(ctx context.Context, key string) err // Create the new entry in AWS Secret Manager for the file. _, err := svc.DeleteSecret(&secretsmanager.DeleteSecretInput{ - SecretId: aws.String(secretID), + SecretId: aws.String(secretID), // (Optional) Specifies that the secret is to be deleted without any recovery // window. You can't use both this parameter and the RecoveryWindowInDays parameter @@ -145,7 +163,14 @@ func (c *SecretManagerAutocertCache) Delete(ctx context.Context, key string) err return errors.Wrapf(err, "autocert failed to delete secret %s", secretID) } - log.Printf("AWS Secrets Manager : Secret %s deleted for %s", secretID) + log.Printf("AWS Secrets Manager : Secret %s deleted", secretID) + + if c.cache != nil { + err = c.cache.Delete(ctx, key) + if err != nil { + return errors.WithStack(err) + } + } return nil } diff --git a/example-project/internal/platform/devops/config_sync.go b/example-project/internal/platform/devops/config_sync.go index c380f68..833dcd6 100644 --- a/example-project/internal/platform/devops/config_sync.go +++ b/example-project/internal/platform/devops/config_sync.go @@ -22,7 +22,7 @@ func SyncCfgInit(log *log.Logger, awsSession *session.Session, secretPrefix, wat localfiles := make(map[string]time.Time) // Do the initial sync before starting file watch to download any existing configs. - err := SyncCfgDir(log, awsSession, secretPrefix, watchDir, localfiles) + err := SyncCfgDir(log, awsSession, secretPrefix, watchDir, localfiles) if err != nil { return nil, err } @@ -41,7 +41,6 @@ func SyncCfgInit(log *log.Logger, awsSession *session.Session, secretPrefix, wat // Init the watch to wait for sync local files to Secret Manager. WatchCfgDir(log, awsSession, secretPrefix, watchDir, watcher, localfiles) - // Init ticker to sync remote files from Secret Manager locally at the defined interval. if syncInterval.Seconds() > 0 { ticker := time.NewTicker(syncInterval) @@ -52,7 +51,7 @@ func SyncCfgInit(log *log.Logger, awsSession *session.Session, secretPrefix, wat log.Println("AWS Secrets Manager : Checking for remote updates") // Do the initial sync before starting file watch to download any existing configs. - err := SyncCfgDir(log, awsSession, secretPrefix, watchDir, localfiles) + err := SyncCfgDir(log, awsSession, secretPrefix, watchDir, localfiles) if err != nil { log.Printf("AWS Secrets Manager : Remote sync error - %+v", err) } @@ -82,7 +81,7 @@ func SyncCfgDir(log *log.Logger, awsSession *session.Session, secretPrefix, watc for _, s := range res.SecretList { // Skip any secret that does not have a matching prefix. - if !strings.HasPrefix(*s.Name, secretPrefix) { + if !strings.HasPrefix(*s.Name, secretPrefix) { continue } @@ -192,7 +191,7 @@ func handleWatchCfgEvent(log *log.Logger, awsSession *session.Session, secretPre // Restore secret after it was already previously deleted. _, err = svc.RestoreSecret(&secretsmanager.RestoreSecretInput{ - SecretId: aws.String(secretID), + SecretId: aws.String(secretID), }) if err != nil { return errors.Wrapf(err, "file watcher failed to restore secret %s for %s", secretID, event.Name) @@ -205,7 +204,7 @@ func handleWatchCfgEvent(log *log.Logger, awsSession *session.Session, secretPre // If where was a resource exists error for create, then need to update the secret instead. _, err = svc.UpdateSecret(&secretsmanager.UpdateSecretInput{ - SecretId: aws.String(secretID), + SecretId: aws.String(secretID), SecretString: aws.String(string(dat)), }) if err != nil { @@ -225,7 +224,7 @@ func handleWatchCfgEvent(log *log.Logger, awsSession *session.Session, secretPre // Create the new entry in AWS Secret Manager for the file. _, err := svc.DeleteSecret(&secretsmanager.DeleteSecretInput{ - SecretId: aws.String(secretID), + SecretId: aws.String(secretID), // (Optional) Specifies that the secret is to be deleted without any recovery // window. You can't use both this parameter and the RecoveryWindowInDays parameter diff --git a/example-project/internal/platform/devops/ecs_service.go b/example-project/internal/platform/devops/ecs_service.go index 563d795..2297e6d 100644 --- a/example-project/internal/platform/devops/ecs_service.go +++ b/example-project/internal/platform/devops/ecs_service.go @@ -3,9 +3,6 @@ package devops import ( "encoding/base64" "encoding/json" - "fmt" - "github.com/sethgrid/pester" - "io/ioutil" "log" "os" "strconv" @@ -33,15 +30,6 @@ func EcsServiceTaskInit(log *log.Logger, awsSession *session.Session) error { return nil } - res, err := pester.Get("http://169.254.170.2/v2/metadata") - if err != nil { - fmt.Println("http://169.254.170.2/v2/metadata failed", err.Error()) - } else { - dat, _ := ioutil.ReadAll(res.Body) - res.Body.Close() - fmt.Println("http://169.254.170.2/v2/metadata, OK", string(dat)) - } - var zoneArecNames = map[string][]string{} if v := os.Getenv("ROUTE53_ZONES"); v != "" { dat, err := base64.RawURLEncoding.DecodeString(v) @@ -241,6 +229,15 @@ func RegisterEcsServiceTasksRoute53(log *log.Logger, awsSession *session.Session } /* +res, err := pester.Get("http://169.254.170.2/v2/metadata") +if err != nil { + fmt.Println("http://169.254.170.2/v2/metadata failed", err.Error()) +} else { + dat, _ := ioutil.ReadAll(res.Body) + res.Body.Close() + fmt.Println("http://169.254.170.2/v2/metadata, OK", string(dat)) +} + http://169.254.170.2/v2/metadata, { diff --git a/example-project/internal/platform/web/request.go b/example-project/internal/platform/web/request.go index 1a7097e..c567800 100644 --- a/example-project/internal/platform/web/request.go +++ b/example-project/internal/platform/web/request.go @@ -19,7 +19,7 @@ import ( // Headers const ( - HeaderUpgrade = "Upgrade" + HeaderUpgrade = "Upgrade" HeaderXForwardedFor = "X-Forwarded-For" HeaderXForwardedProto = "X-Forwarded-Proto" HeaderXForwardedProtocol = "X-Forwarded-Protocol" diff --git a/example-project/tools/truss/cmd/devops/models.go b/example-project/tools/truss/cmd/devops/models.go index 416fbb7..1a9863b 100644 --- a/example-project/tools/truss/cmd/devops/models.go +++ b/example-project/tools/truss/cmd/devops/models.go @@ -27,11 +27,11 @@ type ServiceDeployFlags struct { Env string `validate:"oneof=dev stage prod" example:"dev"` // Optional flags. - EnableHTTPS bool `validate:"omitempty" example:"false"` - ServiceHostPrimary string `validate:"omitempty" example:"example-project.com"` - ServiceHostNames cli.StringSlice `validate:"omitempty" example:"subdomain.example-project.com"` - S3BucketPrivateName string `validate:"omitempty" example:"saas-example-project-private"` - S3BucketPublicName string `validate:"omitempty" example:"saas-example-project-public"` + EnableHTTPS bool `validate:"omitempty" example:"false"` + ServiceHostPrimary string `validate:"omitempty" example:"example-project.com"` + ServiceHostNames cli.StringSlice `validate:"omitempty" example:"subdomain.example-project.com"` + S3BucketPrivateName string `validate:"omitempty" example:"saas-example-project-private"` + S3BucketPublicName string `validate:"omitempty" example:"saas-example-project-public"` ProjectRoot string `validate:"omitempty" example:"."` ProjectName string ` validate:"omitempty" example:"example-project"` @@ -56,9 +56,9 @@ type serviceDeployRequest struct { GoModFile string `validate:"required"` GoModName string `validate:"required"` - EnableHTTPS bool `validate:"omitempty"` - ServiceHostPrimary string `validate:"omitempty,required_with=EnableHTTPS,fqdn"` - ServiceHostNames []string `validate:"omitempty,dive,fqdn"` + EnableHTTPS bool `validate:"omitempty"` + ServiceHostPrimary string `validate:"omitempty,required_with=EnableHTTPS,fqdn"` + ServiceHostNames []string `validate:"omitempty,dive,fqdn"` AwsCreds awsCredentials `validate:"required,dive,required"` diff --git a/example-project/tools/truss/cmd/devops/service_deploy.go b/example-project/tools/truss/cmd/devops/service_deploy.go index f0ffe70..ea9ca3d 100644 --- a/example-project/tools/truss/cmd/devops/service_deploy.go +++ b/example-project/tools/truss/cmd/devops/service_deploy.go @@ -80,21 +80,21 @@ func NewServiceDeployRequest(log *log.Logger, flags ServiceDeployFlags) (*servic AwsCreds: awsCreds, // Optional flags. - ProjectRoot: flags.ProjectRoot, - ProjectName: flags.ProjectName, - DockerFile: flags.DockerFile, - EnableHTTPS: flags.EnableHTTPS, - ServiceHostPrimary: flags.ServiceHostPrimary, - ServiceHostNames: flags.ServiceHostNames, - S3BucketPrivateName: flags.S3BucketPrivateName, - S3BucketPublicName: flags.S3BucketPublicName, - EnableLambdaVPC: flags.EnableLambdaVPC, - EnableEcsElb: flags.EnableEcsElb, - NoBuild: flags.NoBuild, - NoDeploy: flags.NoDeploy, - NoCache: flags.NoCache, - NoPush: flags.NoPush, - RecreateService: flags.RecreateService, + ProjectRoot: flags.ProjectRoot, + ProjectName: flags.ProjectName, + DockerFile: flags.DockerFile, + EnableHTTPS: flags.EnableHTTPS, + ServiceHostPrimary: flags.ServiceHostPrimary, + ServiceHostNames: flags.ServiceHostNames, + S3BucketPrivateName: flags.S3BucketPrivateName, + S3BucketPublicName: flags.S3BucketPublicName, + EnableLambdaVPC: flags.EnableLambdaVPC, + EnableEcsElb: flags.EnableEcsElb, + NoBuild: flags.NoBuild, + NoDeploy: flags.NoDeploy, + NoCache: flags.NoCache, + NoPush: flags.NoPush, + RecreateService: flags.RecreateService, flags: flags, } @@ -403,6 +403,8 @@ func NewServiceDeployRequest(log *log.Logger, flags ServiceDeployFlags) (*servic "secretsmanager:GetSecretValue", "secretsmanager:CreateSecret", "secretsmanager:UpdateSecret", + "secretsmanager:RestoreSecret", + "secretsmanager:DeleteSecret", }, Resource: "*", }, @@ -1780,7 +1782,7 @@ func ServiceDeploy(log *log.Logger, req *serviceDeployRequest) error { // Route 53 zone lookup when hostname is set. Supports both top level domains or sub domains. var zoneArecNames = map[string][]string{} - if req.ServiceHostPrimary != "" { + if req.ServiceHostPrimary != "" { log.Println("Route 53 - Get or create hosted zones.") svc := route53.New(req.awsSession()) @@ -2554,24 +2556,24 @@ func ServiceDeploy(log *log.Logger, req *serviceDeployRequest) error { // List of placeholders that can be used in task definition and replaced on deployment. placeholders := map[string]string{ - "{SERVICE}": req.ServiceName, - "{RELEASE_IMAGE}": req.ReleaseImage, - "{ECS_CLUSTER}": req.EcsClusterName, - "{ECS_SERVICE}": req.EcsServiceName, - "{AWS_REGION}": req.AwsCreds.Region, - "{AWS_LOGS_GROUP}": req.CloudWatchLogGroupName, - "{AWS_AWS_S3_BUCKET_PRIVATE}": req.S3BucketPrivateName, - "{S3_BUCKET_PUBLIC}": req.S3BucketPublicName, - "{ENV}": req.Env, - "{DATADOG_APIKEY}": datadogApiKey, - "{DATADOG_ESSENTIAL}": "true", - "{HTTP_HOST}": "0.0.0.0:80", - "{HTTPS_HOST}": "", // Not enabled by default + "{SERVICE}": req.ServiceName, + "{RELEASE_IMAGE}": req.ReleaseImage, + "{ECS_CLUSTER}": req.EcsClusterName, + "{ECS_SERVICE}": req.EcsServiceName, + "{AWS_REGION}": req.AwsCreds.Region, + "{AWS_LOGS_GROUP}": req.CloudWatchLogGroupName, + "{AWS_S3_BUCKET_PRIVATE}": req.S3BucketPrivateName, + "{AWS_S3_BUCKET_PUBLIC}": req.S3BucketPublicName, + "{ENV}": req.Env, + "{DATADOG_APIKEY}": datadogApiKey, + "{DATADOG_ESSENTIAL}": "true", + "{HTTP_HOST}": "0.0.0.0:80", + "{HTTPS_HOST}": "", // Not enabled by default - "{APP_PROJECT}": req.ProjectName, - "{APP_BASE_URL}": "", // Not set by default, requires a hostname to be defined. - "{HOST_PRIMARY}": req.ServiceHostPrimary, - "{HOST_NAMES}": strings.Join(req.ServiceHostNames, ","), + "{APP_PROJECT}": req.ProjectName, + "{APP_BASE_URL}": "", // Not set by default, requires a hostname to be defined. + "{HOST_PRIMARY}": req.ServiceHostPrimary, + "{HOST_NAMES}": strings.Join(req.ServiceHostNames, ","), "{CACHE_HOST}": "", // Not enabled by default