From f452cbefe8579f690f8d0f27dbeb0f6e118b0182 Mon Sep 17 00:00:00 2001 From: David Dollar Date: Wed, 20 Apr 2016 23:11:10 -0400 Subject: [PATCH 1/7] decouple certs from ssl --- api/controllers/certificates.go | 45 +++++++ api/controllers/routes.go | 3 + api/controllers/ssl.go | 16 +-- api/dist/kernel.json | 5 + api/manifest.yml | 2 + api/models/ssl.go | 49 +++----- api/provider/aws/aws.go | 5 + api/provider/aws/certificates.go | 210 +++++++++++++++++++++++++++++++ api/provider/provider.go | 16 +++ api/provider/test.go | 33 +++-- api/structs/certificate.go | 11 ++ client/certificates.go | 48 +++++++ client/client.go | 16 +-- client/ssl.go | 18 ++- cmd/convox/certs.go | 117 +++++++++++++++++ cmd/convox/ssl.go | 102 ++------------- docker-compose.yml | 1 + 17 files changed, 535 insertions(+), 162 deletions(-) create mode 100644 api/controllers/certificates.go create mode 100644 api/provider/aws/certificates.go create mode 100644 api/structs/certificate.go create mode 100644 client/certificates.go create mode 100644 cmd/convox/certs.go diff --git a/api/controllers/certificates.go b/api/controllers/certificates.go new file mode 100644 index 0000000000..da3cfbe06d --- /dev/null +++ b/api/controllers/certificates.go @@ -0,0 +1,45 @@ +package controllers + +import ( + "net/http" + + "github.com/convox/rack/api/httperr" + "github.com/convox/rack/api/provider" + "github.com/gorilla/mux" +) + +func CertificateCreate(rw http.ResponseWriter, r *http.Request) *httperr.Error { + pub := r.FormValue("pub") + key := r.FormValue("key") + chain := r.FormValue("chain") + + cert, err := provider.CertificateCreate(pub, key, chain) + + if err != nil { + return httperr.Server(err) + } + + return RenderJson(rw, cert) +} + +func CertificateDelete(rw http.ResponseWriter, r *http.Request) *httperr.Error { + id := mux.Vars(r)["id"] + + err := provider.CertificateDelete(id) + + if err != nil { + return httperr.Server(err) + } + + return RenderSuccess(rw) +} + +func CertificateList(rw http.ResponseWriter, r *http.Request) *httperr.Error { + certs, err := provider.CertificateList() + + if err != nil { + return httperr.Server(err) + } + + return RenderJson(rw, certs) +} diff --git a/api/controllers/routes.go b/api/controllers/routes.go index 41a172f777..82db6f4976 100644 --- a/api/controllers/routes.go +++ b/api/controllers/routes.go @@ -41,6 +41,9 @@ func NewRouter() (router *mux.Router) { router.HandleFunc("/apps/{app}/ssl", api("ssl.list", SSLList)).Methods("GET") router.HandleFunc("/apps/{app}/ssl/{process}/{port}", api("ssl.update", SSLUpdate)).Methods("PUT") router.HandleFunc("/auth", api("auth", Auth)).Methods("GET") + router.HandleFunc("/certificates", api("certificate.list", CertificateList)).Methods("GET") + router.HandleFunc("/certificates", api("certificate.create", CertificateCreate)).Methods("POST") + router.HandleFunc("/certificates/{id}", api("certificate.delete", CertificateDelete)).Methods("DELETE") router.HandleFunc("/index/diff", api("index.diff", IndexDiff)).Methods("POST") router.HandleFunc("/index/file/{hash}", api("index.upload", IndexUpload)).Methods("POST") router.HandleFunc("/instances", api("instances.get", InstancesList)).Methods("GET") diff --git a/api/controllers/ssl.go b/api/controllers/ssl.go index 6f97582d44..c977a030d6 100644 --- a/api/controllers/ssl.go +++ b/api/controllers/ssl.go @@ -3,7 +3,6 @@ package controllers import ( "net/http" "strconv" - "strings" "github.com/convox/rack/api/httperr" "github.com/convox/rack/api/models" @@ -31,10 +30,7 @@ func SSLUpdate(rw http.ResponseWriter, r *http.Request) *httperr.Error { a := vars["app"] process := vars["process"] port := vars["port"] - arn := GetForm(r, "arn") - chain := GetForm(r, "chain") - body := GetForm(r, "body") - key := GetForm(r, "key") + id := GetForm(r, "id") if process == "" { return httperr.Errorf(403, "must specify a process") @@ -46,11 +42,7 @@ func SSLUpdate(rw http.ResponseWriter, r *http.Request) *httperr.Error { return httperr.Errorf(403, "port must be numeric") } - if (arn != "") && !validateARNFormat(arn) { - return httperr.Errorf(403, "arn must follow the AWS ARN format") - } - - ssl, err := models.UpdateSSL(a, process, portn, arn, body, key, chain) + ssl, err := models.UpdateSSL(a, process, portn, id) if awsError(err) == "ValidationError" { return httperr.Errorf(404, "%s", err) @@ -62,7 +54,3 @@ func SSLUpdate(rw http.ResponseWriter, r *http.Request) *httperr.Error { return RenderJson(rw, ssl) } - -func validateARNFormat(arn string) bool { - return strings.HasPrefix(strings.ToLower(arn), "arn:") -} diff --git a/api/dist/kernel.json b/api/dist/kernel.json index 3d222a4c45..c8c43ffb31 100644 --- a/api/dist/kernel.json +++ b/api/dist/kernel.json @@ -28,6 +28,10 @@ "Condition": "Development", "Value": { "Fn::If": [ "Autoscale", "true", "false" ] } }, + "AwsAccount": { + "Condition": "Development", + "Value": { "Ref": "AWS::AccountId" } + }, "AwsRegion": { "Condition": "Development", "Value": { "Ref": "AWS::Region" } @@ -1397,6 +1401,7 @@ "Command": "api/bin/web", "CPU": "128", "Environment": { + "AWS_ACCOUNT": { "Ref": "AWS::AccountId" }, "AWS_REGION": { "Ref": "AWS::Region" }, "AWS_ACCESS": { "Ref": "KernelAccess" }, "AWS_SECRET": { "Fn::GetAtt": [ "KernelAccess", "SecretAccessKey" ] }, diff --git a/api/manifest.yml b/api/manifest.yml index 4fa9f3a361..00eebc9296 100644 --- a/api/manifest.yml +++ b/api/manifest.yml @@ -915,6 +915,8 @@ definitions: type: string ssl: properties: + certificate: + type: string domain: type: string expiration: diff --git a/api/models/ssl.go b/api/models/ssl.go index f0db428649..8a9ae45f6d 100644 --- a/api/models/ssl.go +++ b/api/models/ssl.go @@ -20,11 +20,12 @@ import ( ) type SSL struct { - Expiration time.Time `json:"expiration"` - Domain string `json:"domain"` - Process string `json:"process"` - Port int `json:"port"` - Secure bool `json:"secure"` + Certificate string `json:"certificate"` + Expiration time.Time `json:"expiration"` + Domain string `json:"domain"` + Process string `json:"process"` + Port int `json:"port"` + Secure bool `json:"secure"` } type SSLs []SSL @@ -68,11 +69,12 @@ func ListSSLs(a string) (SSLs, error) { secure := app.Parameters[fmt.Sprintf("%sPort%sSecure", matches[1], matches[2])] == "Yes" ssls = append(ssls, SSL{ - Domain: c.Subject.CommonName, - Expiration: *resp.ServerCertificate.ServerCertificateMetadata.Expiration, - Port: port, - Process: DashName(matches[1]), - Secure: secure, + Certificate: *resp.ServerCertificate.ServerCertificateMetadata.ServerCertificateName, + Domain: c.Subject.CommonName, + Expiration: *resp.ServerCertificate.ServerCertificateMetadata.Expiration, + Port: port, + Process: DashName(matches[1]), + Secure: secure, }) } } @@ -80,7 +82,7 @@ func ListSSLs(a string) (SSLs, error) { return ssls, nil } -func UpdateSSL(app, process string, port int, arn, body, key, chain string) (*SSL, error) { +func UpdateSSL(app, process string, port int, id string) (*SSL, error) { a, err := GetApp(app) if err != nil { @@ -92,29 +94,19 @@ func UpdateSSL(app, process string, port int, arn, body, key, chain string) (*SS return nil, fmt.Errorf("can not update app with status: %s", a.Status) } - // store old cert name - oldCertName := certName(app, process, port) - - // validate process exists - if oldCertName == "" { - return nil, fmt.Errorf("no certificate configured for %s port %d", process, port) - } - outputs := a.Outputs - balancer := outputs[fmt.Sprintf("%sPort%dBalancerName", UpperName(process), port)] if balancer == "" { - return nil, fmt.Errorf("Balancer ouptut not found. Please redeploy your app and try again.") + return nil, fmt.Errorf("Process and port combination unknown") } - if arn == "" { - // upload new cert - arn, err = uploadCert(a, process, port, body, key, chain) + res, err := IAM().GetServerCertificate(&iam.GetServerCertificateInput{ + ServerCertificateName: aws.String(id), + }) - if err != nil { - return nil, err - } + if err != nil { + return nil, err } // update cloudformation @@ -125,8 +117,7 @@ func UpdateSSL(app, process string, port int, arn, body, key, chain string) (*SS } params := a.Parameters - - params[fmt.Sprintf("%sPort%dCertificate", UpperName(process), port)] = arn + params[fmt.Sprintf("%sPort%dCertificate", UpperName(process), port)] = *res.ServerCertificate.ServerCertificateMetadata.Arn for key, val := range params { req.Parameters = append(req.Parameters, &cloudformation.Parameter{ diff --git a/api/provider/aws/aws.go b/api/provider/aws/aws.go index db92678ca9..2124127a53 100644 --- a/api/provider/aws/aws.go +++ b/api/provider/aws/aws.go @@ -13,6 +13,7 @@ import ( "github.com/aws/aws-sdk-go/service/ec2" "github.com/aws/aws-sdk-go/service/ecr" "github.com/aws/aws-sdk-go/service/ecs" + "github.com/aws/aws-sdk-go/service/iam" "github.com/aws/aws-sdk-go/service/kinesis" "github.com/aws/aws-sdk-go/service/s3" "github.com/aws/aws-sdk-go/service/sns" @@ -88,6 +89,10 @@ func (p *AWSProvider) ecs() *ecs.ECS { return ecs.New(session.New(), p.config()) } +func (p *AWSProvider) iam() *iam.IAM { + return iam.New(session.New(), p.config()) +} + func (p *AWSProvider) kinesis() *kinesis.Kinesis { return kinesis.New(session.New(), p.config()) } diff --git a/api/provider/aws/certificates.go b/api/provider/aws/certificates.go new file mode 100644 index 0000000000..23f9282e77 --- /dev/null +++ b/api/provider/aws/certificates.go @@ -0,0 +1,210 @@ +package aws + +import ( + "bytes" + "crypto/x509" + "encoding/json" + "encoding/pem" + "fmt" + "io/ioutil" + "os" + "os/exec" + "strings" + "time" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/iam" + "github.com/convox/rack/api/structs" +) + +func (p *AWSProvider) CertificateCreate(pub, key, chain string) (*structs.Certificate, error) { + end, _ := pem.Decode([]byte(pub)) + pub = string(pem.EncodeToMemory(end)) + + if chain == "" { + ch, err := resolveCertificateChain(pub) + + if err != nil { + return nil, err + } + + chain = ch + } + + c, err := x509.ParseCertificate(end.Bytes) + + if err != nil { + return nil, err + } + + req := &iam.UploadServerCertificateInput{ + CertificateBody: aws.String(pub), + PrivateKey: aws.String(key), + ServerCertificateName: aws.String(fmt.Sprintf("cert-%d", time.Now().Unix())), + } + + if chain != "" { + req.CertificateChain = aws.String(chain) + } + + res, err := p.iam().UploadServerCertificate(req) + + if err != nil { + return nil, err + } + + parts := strings.Split(*res.ServerCertificateMetadata.Arn, "/") + id := parts[len(parts)-1] + + cert := structs.Certificate{ + Id: id, + Domain: c.Subject.CommonName, + Expiration: *res.ServerCertificateMetadata.Expiration, + } + + return &cert, nil +} + +func (p *AWSProvider) CertificateDelete(id string) error { + _, err := p.iam().DeleteServerCertificate(&iam.DeleteServerCertificateInput{ + ServerCertificateName: aws.String(id), + }) + + return err +} + +func (p *AWSProvider) CertificateList() (structs.Certificates, error) { + res, err := p.iam().ListServerCertificates(nil) + + if err != nil { + return nil, err + } + + certs := structs.Certificates{} + + for _, cert := range res.ServerCertificateMetadataList { + res, err := p.iam().GetServerCertificate(&iam.GetServerCertificateInput{ + ServerCertificateName: cert.ServerCertificateName, + }) + + if err != nil { + return nil, err + } + + pem, _ := pem.Decode([]byte(*res.ServerCertificate.CertificateBody)) + + if err != nil { + return nil, err + } + + c, err := x509.ParseCertificate(pem.Bytes) + + certs = append(certs, structs.Certificate{ + Id: *cert.ServerCertificateName, + Domain: c.Subject.CommonName, + Expiration: *cert.Expiration, + }) + } + + return certs, nil +} + +type CfsslCertificateBundle struct { + Bundle string `json:"bundle"` +} + +// use cfssl bundle to generate the certificate chain +// return the whole list minus the first one +func resolveCertificateChain(body string) (string, error) { + bl, _ := pem.Decode([]byte(body)) + crt, err := x509.ParseCertificate(bl.Bytes) + + if err != nil { + return "", err + } + + // return if this is a self-signed cert + // a cert is self-signed if the issuer and subject are the same + if string(crt.RawIssuer) == string(crt.RawSubject) { + return "", nil + } + + cmd := exec.Command("cfssl", "bundle", "-cert", "-") + + cmd.Stderr = os.Stderr + + stdin, err := cmd.StdinPipe() + + if err != nil { + return "", err + } + + stdout, err := cmd.StdoutPipe() + + if err != nil { + return "", err + } + + err = cmd.Start() + + if err != nil { + return "", err + } + + stdin.Write([]byte(body)) + stdin.Close() + + data, err := ioutil.ReadAll(stdout) + + if err != nil { + return "", err + } + + var bundle CfsslCertificateBundle + + err = json.Unmarshal(data, &bundle) + + if err != nil { + return "", err + } + + err = cmd.Wait() + + if err != nil { + return "", err + } + + certs := []*x509.Certificate{} + + raw := []byte(bundle.Bundle) + + for { + block, rest := pem.Decode(raw) + + if block == nil { + break + } + + raw = rest + + cert, err := x509.ParseCertificate(block.Bytes) + + if err != nil { + return "", nil + } + + certs = append(certs, cert) + } + + var buf bytes.Buffer + + for i := 1; i < len(certs); i++ { + err := pem.Encode(&buf, &pem.Block{Type: "CERTIFICATE", Bytes: certs[i].Raw}) + + if err != nil { + return "", err + } + } + + return buf.String(), nil +} diff --git a/api/provider/provider.go b/api/provider/provider.go index e151407a17..5212d8dd36 100644 --- a/api/provider/provider.go +++ b/api/provider/provider.go @@ -26,6 +26,10 @@ type Provider interface { CapacityGet() (*structs.Capacity, error) + CertificateCreate(pub, key, chain string) (*structs.Certificate, error) + CertificateDelete(id string) error + CertificateList() (structs.Certificates, error) + EventSend(*structs.Event, error) error IndexDiff(*structs.Index) ([]string, error) @@ -115,6 +119,18 @@ func CapacityGet() (*structs.Capacity, error) { return CurrentProvider.CapacityGet() } +func CertificateCreate(pub, key, chain string) (*structs.Certificate, error) { + return CurrentProvider.CertificateCreate(pub, key, chain) +} + +func CertificateDelete(id string) error { + return CurrentProvider.CertificateDelete(id) +} + +func CertificateList() (structs.Certificates, error) { + return CurrentProvider.CertificateList() +} + func EventSend(e *structs.Event, err error) error { return CurrentProvider.EventSend(e, err) } diff --git a/api/provider/test.go b/api/provider/test.go index 59a71b6e31..278b5eee61 100644 --- a/api/provider/test.go +++ b/api/provider/test.go @@ -11,14 +11,16 @@ var TestProvider = &TestProviderRunner{} type TestProviderRunner struct { mock.Mock - App structs.App - Build structs.Build - Builds structs.Builds - Capacity structs.Capacity - Instances structs.Instances - Release structs.Release - Releases structs.Releases - Service structs.Service + App structs.App + Build structs.Build + Builds structs.Builds + Capacity structs.Capacity + Certificate structs.Certificate + Certificates structs.Certificates + Instances structs.Instances + Release structs.Release + Releases structs.Releases + Service structs.Service } func (p *TestProviderRunner) AppGet(name string) (*structs.App, error) { @@ -76,6 +78,21 @@ func (p *TestProviderRunner) CapacityGet() (*structs.Capacity, error) { return &p.Capacity, nil } +func (p *TestProviderRunner) CertificateCreate(pub, key, chain string) (*structs.Certificate, error) { + p.Called(pub, key, chain) + return &p.Certificate, nil +} + +func (p *TestProviderRunner) CertificateDelete(id string) error { + p.Called(id) + return nil +} + +func (p *TestProviderRunner) CertificateList() (structs.Certificates, error) { + p.Called() + return p.Certificates, nil +} + func (p *TestProviderRunner) EventSend(e *structs.Event, err error) error { p.Called(e, err) return nil diff --git a/api/structs/certificate.go b/api/structs/certificate.go new file mode 100644 index 0000000000..5eba6c8f55 --- /dev/null +++ b/api/structs/certificate.go @@ -0,0 +1,11 @@ +package structs + +import "time" + +type Certificate struct { + Id string `json:"id"` + Domain string `json:"domain"` + Expiration time.Time `json:"expiration"` +} + +type Certificates []Certificate diff --git a/client/certificates.go b/client/certificates.go new file mode 100644 index 0000000000..2242537cbe --- /dev/null +++ b/client/certificates.go @@ -0,0 +1,48 @@ +package client + +import ( + "fmt" + "time" +) + +type Certificate struct { + Id string `json:"id"` + Domain string `json:"domain"` + Expiration time.Time `json:"expiration"` +} + +type Certificates []Certificate + +func (c *Client) CreateCertificate(pub, key, chain string) (*Certificate, error) { + var cert Certificate + + params := Params{ + "pub": pub, + "key": key, + "chain": chain, + } + + err := c.Post("/certificates", params, &cert) + + if err != nil { + return nil, err + } + + return &cert, nil +} + +func (c *Client) DeleteCertificate(id string) error { + return c.Delete(fmt.Sprintf("/certificates/%s", id), nil) +} + +func (c *Client) ListCertificates() (Certificates, error) { + var certs Certificates + + err := c.Get("/certificates", &certs) + + if err != nil { + return nil, err + } + + return certs, nil +} diff --git a/client/client.go b/client/client.go index 56a10708c8..ae1150cbdf 100644 --- a/client/client.go +++ b/client/client.go @@ -266,16 +266,18 @@ func (c *Client) DeleteResponse(path string, out interface{}) (*http.Response, e return nil, err } - data, err := ioutil.ReadAll(res.Body) + if out != nil { + data, err := ioutil.ReadAll(res.Body) - if err != nil { - return nil, err - } + if err != nil { + return nil, err + } - err = json.Unmarshal(data, out) + err = json.Unmarshal(data, out) - if err != nil { - return nil, err + if err != nil { + return nil, err + } } return res, nil diff --git a/client/ssl.go b/client/ssl.go index e7d8bd6452..6b4f05ac7f 100644 --- a/client/ssl.go +++ b/client/ssl.go @@ -6,11 +6,12 @@ import ( ) type SSL struct { - Domain string `json:"domain"` - Expiration time.Time `json:"expiration"` - Port int `json:"port"` - Process string `json:"process"` - Secure bool `json:"secure"` + Certificate string `json:"certificate"` + Domain string `json:"domain"` + Expiration time.Time `json:"expiration"` + Port int `json:"port"` + Process string `json:"process"` + Secure bool `json:"secure"` } type SSLs []SSL @@ -27,12 +28,9 @@ func (c *Client) ListSSL(app string) (*SSLs, error) { return &ssls, nil } -func (c *Client) UpdateSSL(app, process, port, arn, body, key, chain string) (*SSL, error) { +func (c *Client) UpdateSSL(app, process, port, id string) (*SSL, error) { params := Params{ - "arn": arn, - "body": body, - "chain": chain, - "key": key, + "id": id, } var ssl SSL diff --git a/cmd/convox/certs.go b/cmd/convox/certs.go new file mode 100644 index 0000000000..cdf809b8da --- /dev/null +++ b/cmd/convox/certs.go @@ -0,0 +1,117 @@ +package main + +import ( + "fmt" + "io/ioutil" + + "github.com/codegangsta/cli" + "github.com/convox/rack/cmd/convox/stdcli" +) + +func init() { + stdcli.RegisterCommand(cli.Command{ + Name: "certs", + Action: cmdCertsList, + Description: "list certificates", + Subcommands: []cli.Command{ + { + Name: "create", + Description: "upload a certificate", + Usage: " ", + Action: cmdCertsCreate, + Flags: []cli.Flag{ + cli.StringFlag{ + Name: "chain", + Usage: "intermediate certificate chain", + }, + }, + }, + { + Name: "delete", + Description: "delete a certificate", + Usage: "", + Action: cmdCertsDelete, + }, + }, + }) +} + +func cmdCertsList(c *cli.Context) { + certs, err := rackClient(c).ListCertificates() + + if err != nil { + stdcli.Error(err) + return + } + + t := stdcli.NewTable("ID", "DOMAIN", "EXPIRES") + + for _, cert := range certs { + t.AddRow(cert.Id, cert.Domain, humanizeTime(cert.Expiration)) + } + + t.Print() +} + +func cmdCertsCreate(c *cli.Context) { + if len(c.Args()) < 2 { + stdcli.Usage(c, "create") + return + } + + pub, err := ioutil.ReadFile(c.Args()[0]) + + if err != nil { + stdcli.Error(err) + return + } + + key, err := ioutil.ReadFile(c.Args()[1]) + + if err != nil { + stdcli.Error(err) + return + } + + chain := "" + + if chainFile := c.String("chain"); chainFile != "" { + data, err := ioutil.ReadFile(chainFile) + + if err != nil { + stdcli.Error(err) + return + } + + chain = string(data) + } + + fmt.Printf("Uploading certificate... ") + + cert, err := rackClient(c).CreateCertificate(string(pub), string(key), chain) + + if err != nil { + stdcli.Error(err) + return + } + + fmt.Printf("OK, %s\n", cert.Id) +} + +func cmdCertsDelete(c *cli.Context) { + if len(c.Args()) < 1 { + stdcli.Usage(c, "delete") + return + } + + fmt.Printf("Removing certificate... ") + + err := rackClient(c).DeleteCertificate(c.Args()[0]) + + if err != nil { + stdcli.Error(err) + return + } + + fmt.Println("OK") +} diff --git a/cmd/convox/ssl.go b/cmd/convox/ssl.go index 75501f1821..46f9e56950 100644 --- a/cmd/convox/ssl.go +++ b/cmd/convox/ssl.go @@ -1,16 +1,8 @@ package main import ( - "crypto/rand" - "crypto/rsa" - "crypto/x509" - "crypto/x509/pkix" - "encoding/pem" "fmt" - "io/ioutil" - "math/big" "strings" - "time" "github.com/codegangsta/cli" "github.com/convox/rack/cmd/convox/stdcli" @@ -28,7 +20,7 @@ func init() { { Name: "update", Description: "upload a replacement ssl certificate", - Usage: " [ |]", + Usage: " ", Action: cmdSSLUpdate, Flags: []cli.Flag{ appFlag, @@ -57,10 +49,10 @@ func cmdSSLList(c *cli.Context) { return } - t := stdcli.NewTable("TARGET", "EXPIRES", "DOMAIN") + t := stdcli.NewTable("TARGET", "CERTIFICATE", "DOMAIN", "EXPIRES") for _, ssl := range *ssls { - t.AddRow(fmt.Sprintf("%s:%d", ssl.Process, ssl.Port), humanizeTime(ssl.Expiration), ssl.Domain) + t.AddRow(fmt.Sprintf("%s:%d", ssl.Process, ssl.Port), ssl.Certificate, ssl.Domain, humanizeTime(ssl.Expiration)) } t.Print() @@ -74,8 +66,8 @@ func cmdSSLUpdate(c *cli.Context) { return } - if len(c.Args()) < 1 { - stdcli.Usage(c, "create") + if len(c.Args()) < 2 { + stdcli.Usage(c, "update") return } @@ -88,92 +80,14 @@ func cmdSSLUpdate(c *cli.Context) { return } - var pub []byte - var key []byte - var arn string - - switch len(c.Args()) { - case 2: - arn = c.Args()[1] - case 3: - pub, err = ioutil.ReadFile(c.Args()[1]) - - if err != nil { - stdcli.Error(err) - return - } - - key, err = ioutil.ReadFile(c.Args()[2]) - - if err != nil { - stdcli.Error(err) - return - } - default: - stdcli.Usage(c, "update") - return - } - - chain := "" - - if chainFile := c.String("chain"); chainFile != "" { - data, err := ioutil.ReadFile(chainFile) - - if err != nil { - stdcli.Error(err) - return - } - - chain = string(data) - } - - fmt.Printf("Updating SSL listener %s... ", target) + fmt.Printf("Updating SSL certificate... ") - _, err = rackClient(c).UpdateSSL(app, parts[0], parts[1], arn, string(pub), string(key), chain) + _, err = rackClient(c).UpdateSSL(app, parts[0], parts[1], c.Args()[1]) if err != nil { stdcli.Error(err) return } - fmt.Println("Done.") -} - -func generateSelfSignedCertificate(app, host string) ([]byte, []byte, error) { - rkey, err := rsa.GenerateKey(rand.Reader, 2048) - - if err != nil { - return nil, nil, err - } - - serial, err := rand.Int(rand.Reader, new(big.Int).Lsh(big.NewInt(1), 128)) - - if err != nil { - return nil, nil, err - } - - template := x509.Certificate{ - SerialNumber: serial, - Subject: pkix.Name{ - CommonName: host, - Organization: []string{app}, - }, - NotBefore: time.Now(), - NotAfter: time.Now().Add(365 * 24 * time.Hour), - KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, - ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, - BasicConstraintsValid: true, - DNSNames: []string{host}, - } - - data, err := x509.CreateCertificate(rand.Reader, &template, &template, &rkey.PublicKey, rkey) - - if err != nil { - return nil, nil, err - } - - pub := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: data}) - key := pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(rkey)}) - - return pub, key, nil + fmt.Println("OK") } diff --git a/docker-compose.yml b/docker-compose.yml index 69e6db733e..91f3ee038b 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -4,6 +4,7 @@ web: environment: - AUTOSCALE=false - DEVELOPMENT=true + - AWS_ACCOUNT - AWS_REGION - AWS_ACCESS - AWS_SECRET From c375ab3c7de3edb08c2fbe10c8ef9892e40dfd01 Mon Sep 17 00:00:00 2001 From: David Dollar Date: Wed, 20 Apr 2016 23:30:26 -0400 Subject: [PATCH 2/7] tweak message --- cmd/convox/ssl.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/convox/ssl.go b/cmd/convox/ssl.go index 46f9e56950..6a672a191a 100644 --- a/cmd/convox/ssl.go +++ b/cmd/convox/ssl.go @@ -80,7 +80,7 @@ func cmdSSLUpdate(c *cli.Context) { return } - fmt.Printf("Updating SSL certificate... ") + fmt.Printf("Updating certificate... ") _, err = rackClient(c).UpdateSSL(app, parts[0], parts[1], c.Args()[1]) From f07d66027401f314212252e179c305b1f3f50df5 Mon Sep 17 00:00:00 2001 From: David Dollar Date: Wed, 20 Apr 2016 23:30:32 -0400 Subject: [PATCH 3/7] standard cert name --- api/models/release.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/api/models/release.go b/api/models/release.go index 90502f33fa..273cc2e59e 100644 --- a/api/models/release.go +++ b/api/models/release.go @@ -213,8 +213,7 @@ func (r *Release) Promote() error { switch app.Parameters[protoParam] { case "https", "tls": if app.Parameters[certParam] == "" { - timestamp := time.Now().Format("20060102150405") - name := fmt.Sprintf("%s%s%s-%s", UpperName(app.StackName()), UpperName(entry.Name), mapping.Balancer, timestamp) + name := fmt.Sprintf("cert-%d", time.Now().Unix()) body, key, err := GenerateSelfSignedCertificate("*.*.elb.amazonaws.com") From e9163db684b67b9e73a75f8da9582bacc24ab262 Mon Sep 17 00:00:00 2001 From: David Dollar Date: Thu, 21 Apr 2016 09:21:17 -0400 Subject: [PATCH 4/7] add to api manifest --- api/controllers/certificates.go | 4 +-- api/manifest.yml | 62 +++++++++++++++++++++++++++++++++ client/certificates.go | 6 ++-- 3 files changed, 67 insertions(+), 5 deletions(-) diff --git a/api/controllers/certificates.go b/api/controllers/certificates.go index da3cfbe06d..59ec94819b 100644 --- a/api/controllers/certificates.go +++ b/api/controllers/certificates.go @@ -9,8 +9,8 @@ import ( ) func CertificateCreate(rw http.ResponseWriter, r *http.Request) *httperr.Error { - pub := r.FormValue("pub") - key := r.FormValue("key") + pub := r.FormValue("public") + key := r.FormValue("private") chain := r.FormValue("chain") cert, err := provider.CertificateCreate(pub, key, chain) diff --git a/api/manifest.yml b/api/manifest.yml index 00eebc9296..b80d154410 100644 --- a/api/manifest.yml +++ b/api/manifest.yml @@ -612,6 +612,61 @@ paths: description: invalid password schema: type: string + /certificates: + get: + description: List certificates + responses: + 200: + description: certificate list + schema: + type: array + items: + $ref: '#/definitions/certificate' + post: + description: Upload a certificate + parameters: + - name: public + description: public key + type: string + in: formData + required: true + - name: private + description: private key + type: string + in: formData + required: true + - name: chain + description: intermediate chain + type: string + in: formData + required: false + responses: + 200: + description: certificate + schema: + $ref: '#/definitions/certificate' + 403: + description: invalid certificate + schema: + $ref: '#/definitions/error' + /certificates/{id}: + delete: + description: Remove a certificate + parameters: + - name: id + description: certificate id + type: string + in: path + required: true + responses: + 200: + description: success + schema: + $ref: '#/definitions/success' + 404: + description: not found + schema: + $ref: '#/definitions/error' /instances: get: description: List instances. @@ -825,6 +880,13 @@ definitions: type: integer process-width: type: integer + certificate: + domain: + type: string + expiration: + type: date + id: + type: string error: properties: error: diff --git a/client/certificates.go b/client/certificates.go index 2242537cbe..08c7eb2b88 100644 --- a/client/certificates.go +++ b/client/certificates.go @@ -17,9 +17,9 @@ func (c *Client) CreateCertificate(pub, key, chain string) (*Certificate, error) var cert Certificate params := Params{ - "pub": pub, - "key": key, - "chain": chain, + "public": pub, + "private": key, + "chain": chain, } err := c.Post("/certificates", params, &cert) From 55c836606de3c14aba1dcfeea265991084802502 Mon Sep 17 00:00:00 2001 From: David Dollar Date: Thu, 21 Apr 2016 10:45:05 -0400 Subject: [PATCH 5/7] allow acm certificate generation --- api/controllers/certificates.go | 16 + api/controllers/routes.go | 1 + api/models/models.go | 5 + api/models/ssl.go | 104 ++- api/provider/aws/aws.go | 5 + api/provider/aws/certificates.go | 71 ++ api/provider/provider.go | 5 + api/provider/test.go | 5 + api/structs/certificate.go | 9 +- client/certificates.go | 17 + cmd/convox/certs.go | 24 + .../aws/aws-sdk-go/aws/awserr/error.go | 56 +- .../aws/aws-sdk-go/aws/awserr/types.go | 85 +- .../aws/aws-sdk-go/aws/awsutil/prettify.go | 4 + .../aws-sdk-go/aws/client/default_retryer.go | 31 +- .../github.com/aws/aws-sdk-go/aws/config.go | 68 ++ .../aws/credentials/chain_provider.go | 27 +- .../aws-sdk-go/aws/credentials/credentials.go | 3 + .../aws/credentials/env_provider.go | 8 +- .../shared_credentials_provider.go | 16 +- .../aws/credentials/static_provider.go | 6 +- .../github.com/aws/aws-sdk-go/aws/logger.go | 14 + .../aws/aws-sdk-go/aws/request/handlers.go | 53 +- .../aws-sdk-go/aws/request/offset_reader.go | 49 ++ .../aws/aws-sdk-go/aws/request/request.go | 93 ++- .../aws/aws-sdk-go/aws/request/retryer.go | 23 +- vendor/github.com/aws/aws-sdk-go/aws/types.go | 30 +- .../github.com/aws/aws-sdk-go/aws/version.go | 2 +- .../private/protocol/idempotency.go | 75 ++ .../private/protocol/json/jsonutil/build.go | 16 +- .../protocol/jsonrpc/build_bench_test.go | 71 ++ .../private/protocol/jsonrpc/jsonrpc.go | 12 + .../aws-sdk-go/private/protocol/rest/build.go | 6 +- .../private/protocol/rest/unmarshal.go | 15 + .../aws-sdk-go/private/protocol/unmarshal.go | 21 + .../private/signer/v4/header_rules.go | 82 ++ .../aws/aws-sdk-go/private/signer/v4/v4.go | 142 +++- .../aws/aws-sdk-go/service/acm/api.go | 739 ++++++++++++++++++ .../aws/aws-sdk-go/service/acm/service.go | 95 +++ 39 files changed, 1964 insertions(+), 140 deletions(-) create mode 100644 vendor/github.com/aws/aws-sdk-go/aws/request/offset_reader.go create mode 100644 vendor/github.com/aws/aws-sdk-go/private/protocol/idempotency.go create mode 100644 vendor/github.com/aws/aws-sdk-go/private/protocol/jsonrpc/build_bench_test.go create mode 100644 vendor/github.com/aws/aws-sdk-go/private/protocol/unmarshal.go create mode 100644 vendor/github.com/aws/aws-sdk-go/private/signer/v4/header_rules.go create mode 100644 vendor/github.com/aws/aws-sdk-go/service/acm/api.go create mode 100644 vendor/github.com/aws/aws-sdk-go/service/acm/service.go diff --git a/api/controllers/certificates.go b/api/controllers/certificates.go index 59ec94819b..0bbab36ccb 100644 --- a/api/controllers/certificates.go +++ b/api/controllers/certificates.go @@ -2,6 +2,8 @@ package controllers import ( "net/http" + "sort" + "strings" "github.com/convox/rack/api/httperr" "github.com/convox/rack/api/provider" @@ -34,6 +36,18 @@ func CertificateDelete(rw http.ResponseWriter, r *http.Request) *httperr.Error { return RenderSuccess(rw) } +func CertificateGenerate(rw http.ResponseWriter, r *http.Request) *httperr.Error { + domains := strings.Split(r.FormValue("domains"), ",") + + cert, err := provider.CertificateGenerate(domains) + + if err != nil { + return httperr.Server(err) + } + + return RenderJson(rw, cert) +} + func CertificateList(rw http.ResponseWriter, r *http.Request) *httperr.Error { certs, err := provider.CertificateList() @@ -41,5 +55,7 @@ func CertificateList(rw http.ResponseWriter, r *http.Request) *httperr.Error { return httperr.Server(err) } + sort.Sort(certs) + return RenderJson(rw, certs) } diff --git a/api/controllers/routes.go b/api/controllers/routes.go index 82db6f4976..8fd415fe11 100644 --- a/api/controllers/routes.go +++ b/api/controllers/routes.go @@ -43,6 +43,7 @@ func NewRouter() (router *mux.Router) { router.HandleFunc("/auth", api("auth", Auth)).Methods("GET") router.HandleFunc("/certificates", api("certificate.list", CertificateList)).Methods("GET") router.HandleFunc("/certificates", api("certificate.create", CertificateCreate)).Methods("POST") + router.HandleFunc("/certificates/generate", api("certificate.generate", CertificateGenerate)).Methods("POST") router.HandleFunc("/certificates/{id}", api("certificate.delete", CertificateDelete)).Methods("DELETE") router.HandleFunc("/index/diff", api("index.diff", IndexDiff)).Methods("POST") router.HandleFunc("/index/file/{hash}", api("index.upload", IndexUpload)).Methods("POST") diff --git a/api/models/models.go b/api/models/models.go index 16cabc17e8..73499cf27c 100644 --- a/api/models/models.go +++ b/api/models/models.go @@ -11,6 +11,7 @@ import ( "github.com/aws/aws-sdk-go/aws/awserr" "github.com/aws/aws-sdk-go/aws/credentials" "github.com/aws/aws-sdk-go/aws/session" + "github.com/aws/aws-sdk-go/service/acm" "github.com/aws/aws-sdk-go/service/autoscaling" "github.com/aws/aws-sdk-go/service/cloudformation" "github.com/aws/aws-sdk-go/service/cloudwatch" @@ -58,6 +59,10 @@ func awsConfig() *aws.Config { return config } +func ACM() *acm.ACM { + return acm.New(session.New(), awsConfig()) +} + func AutoScaling() *autoscaling.AutoScaling { return autoscaling.New(session.New(), awsConfig()) } diff --git a/api/models/ssl.go b/api/models/ssl.go index 8a9ae45f6d..8178e3e199 100644 --- a/api/models/ssl.go +++ b/api/models/ssl.go @@ -15,6 +15,7 @@ import ( "time" "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/acm" "github.com/aws/aws-sdk-go/service/cloudformation" "github.com/aws/aws-sdk-go/service/iam" ) @@ -55,27 +56,57 @@ func ListSSLs(a string) (SSLs, error) { return nil, err } - resp, err := IAM().GetServerCertificate(&iam.GetServerCertificateInput{ - ServerCertificateName: aws.String(certName(app.StackName(), matches[1], port)), - }) - - if err != nil { - return nil, err - } - - pemBlock, _ := pem.Decode([]byte(*resp.ServerCertificate.CertificateBody)) - c, err := x509.ParseCertificate(pemBlock.Bytes) - secure := app.Parameters[fmt.Sprintf("%sPort%sSecure", matches[1], matches[2])] == "Yes" - ssls = append(ssls, SSL{ - Certificate: *resp.ServerCertificate.ServerCertificateMetadata.ServerCertificateName, - Domain: c.Subject.CommonName, - Expiration: *resp.ServerCertificate.ServerCertificateMetadata.Expiration, - Port: port, - Process: DashName(matches[1]), - Secure: secure, - }) + switch prefix := v[8:11]; prefix { + case "acm": + res, err := ACM().DescribeCertificate(&acm.DescribeCertificateInput{ + CertificateArn: aws.String(v), + }) + + if err != nil { + return nil, err + } + + parts := strings.Split(v, "-") + id := fmt.Sprintf("acm-%s", parts[len(parts)-1]) + + ssls = append(ssls, SSL{ + Certificate: id, + Domain: *res.Certificate.DomainName, + Expiration: *res.Certificate.NotAfter, + Port: port, + Process: DashName(matches[1]), + Secure: secure, + }) + case "iam": + res, err := IAM().GetServerCertificate(&iam.GetServerCertificateInput{ + ServerCertificateName: aws.String(certName(app.StackName(), matches[1], port)), + }) + + if err != nil { + return nil, err + } + + pemBlock, _ := pem.Decode([]byte(*res.ServerCertificate.CertificateBody)) + + c, err := x509.ParseCertificate(pemBlock.Bytes) + + if err != nil { + return nil, err + } + + ssls = append(ssls, SSL{ + Certificate: *res.ServerCertificate.ServerCertificateMetadata.ServerCertificateName, + Domain: c.Subject.CommonName, + Expiration: *res.ServerCertificate.ServerCertificateMetadata.Expiration, + Port: port, + Process: DashName(matches[1]), + Secure: secure, + }) + default: + return nil, fmt.Errorf("unknown arn prefix: %s", prefix) + } } } @@ -101,12 +132,35 @@ func UpdateSSL(app, process string, port int, id string) (*SSL, error) { return nil, fmt.Errorf("Process and port combination unknown") } - res, err := IAM().GetServerCertificate(&iam.GetServerCertificateInput{ - ServerCertificateName: aws.String(id), - }) + arn := "" - if err != nil { - return nil, err + if strings.HasPrefix(id, "acm-") { + uuid := id[4:] + + res, err := ACM().ListCertificates(nil) + + if err != nil { + return nil, err + } + + for _, cert := range res.CertificateSummaryList { + parts := strings.Split(*cert.CertificateArn, "-") + + if parts[len(parts)-1] == uuid { + arn = *cert.CertificateArn + break + } + } + } else { + res, err := IAM().GetServerCertificate(&iam.GetServerCertificateInput{ + ServerCertificateName: aws.String(id), + }) + + if err != nil { + return nil, err + } + + arn = *res.ServerCertificate.ServerCertificateMetadata.Arn } // update cloudformation @@ -117,7 +171,7 @@ func UpdateSSL(app, process string, port int, id string) (*SSL, error) { } params := a.Parameters - params[fmt.Sprintf("%sPort%dCertificate", UpperName(process), port)] = *res.ServerCertificate.ServerCertificateMetadata.Arn + params[fmt.Sprintf("%sPort%dCertificate", UpperName(process), port)] = arn for key, val := range params { req.Parameters = append(req.Parameters, &cloudformation.Parameter{ diff --git a/api/provider/aws/aws.go b/api/provider/aws/aws.go index 2124127a53..9e7db4eeaf 100644 --- a/api/provider/aws/aws.go +++ b/api/provider/aws/aws.go @@ -7,6 +7,7 @@ import ( "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/credentials" "github.com/aws/aws-sdk-go/aws/session" + "github.com/aws/aws-sdk-go/service/acm" "github.com/aws/aws-sdk-go/service/cloudformation" "github.com/aws/aws-sdk-go/service/cloudwatchlogs" "github.com/aws/aws-sdk-go/service/dynamodb" @@ -65,6 +66,10 @@ func (p *AWSProvider) config() *aws.Config { return config } +func (p *AWSProvider) acm() *acm.ACM { + return acm.New(session.New(), p.config()) +} + func (p *AWSProvider) cloudformation() *cloudformation.CloudFormation { return cloudformation.New(session.New(), p.config()) } diff --git a/api/provider/aws/certificates.go b/api/provider/aws/certificates.go index 23f9282e77..c1a569697a 100644 --- a/api/provider/aws/certificates.go +++ b/api/provider/aws/certificates.go @@ -13,6 +13,7 @@ import ( "time" "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/acm" "github.com/aws/aws-sdk-go/service/iam" "github.com/convox/rack/api/structs" ) @@ -73,6 +74,42 @@ func (p *AWSProvider) CertificateDelete(id string) error { return err } +func (p *AWSProvider) CertificateGenerate(domains []string) (*structs.Certificate, error) { + if len(domains) < 1 { + return nil, fmt.Errorf("must specify at least one domain") + } + + alts := []*string{} + + for _, domain := range domains[1:] { + alts = append(alts, aws.String(domain)) + } + + req := &acm.RequestCertificateInput{ + DomainName: aws.String(domains[0]), + } + + if len(alts) > 0 { + req.SubjectAlternativeNames = alts + } + + res, err := p.acm().RequestCertificate(req) + + if err != nil { + return nil, err + } + + parts := strings.Split(*res.CertificateArn, "-") + id := fmt.Sprintf("acm-%s", parts[len(parts)-1]) + + cert := structs.Certificate{ + Id: id, + Domain: domains[0], + } + + return &cert, nil +} + func (p *AWSProvider) CertificateList() (structs.Certificates, error) { res, err := p.iam().ListServerCertificates(nil) @@ -99,6 +136,10 @@ func (p *AWSProvider) CertificateList() (structs.Certificates, error) { c, err := x509.ParseCertificate(pem.Bytes) + if err != nil { + return nil, err + } + certs = append(certs, structs.Certificate{ Id: *cert.ServerCertificateName, Domain: c.Subject.CommonName, @@ -106,6 +147,36 @@ func (p *AWSProvider) CertificateList() (structs.Certificates, error) { }) } + ares, err := p.acm().ListCertificates(nil) + + if err != nil { + return nil, err + } + + for _, cert := range ares.CertificateSummaryList { + parts := strings.Split(*cert.CertificateArn, "-") + id := fmt.Sprintf("acm-%s", parts[len(parts)-1]) + + c := structs.Certificate{ + Id: id, + Domain: *cert.DomainName, + } + + res, err := p.acm().DescribeCertificate(&acm.DescribeCertificateInput{ + CertificateArn: cert.CertificateArn, + }) + + if err != nil { + return nil, err + } + + if res.Certificate.NotAfter != nil { + c.Expiration = *res.Certificate.NotAfter + } + + certs = append(certs, c) + } + return certs, nil } diff --git a/api/provider/provider.go b/api/provider/provider.go index 5212d8dd36..5c0fa8ae57 100644 --- a/api/provider/provider.go +++ b/api/provider/provider.go @@ -28,6 +28,7 @@ type Provider interface { CertificateCreate(pub, key, chain string) (*structs.Certificate, error) CertificateDelete(id string) error + CertificateGenerate(domains []string) (*structs.Certificate, error) CertificateList() (structs.Certificates, error) EventSend(*structs.Event, error) error @@ -127,6 +128,10 @@ func CertificateDelete(id string) error { return CurrentProvider.CertificateDelete(id) } +func CertificateGenerate(domains []string) (*structs.Certificate, error) { + return CurrentProvider.CertificateGenerate(domains) +} + func CertificateList() (structs.Certificates, error) { return CurrentProvider.CertificateList() } diff --git a/api/provider/test.go b/api/provider/test.go index 278b5eee61..cc1ff1d712 100644 --- a/api/provider/test.go +++ b/api/provider/test.go @@ -88,6 +88,11 @@ func (p *TestProviderRunner) CertificateDelete(id string) error { return nil } +func (p *TestProviderRunner) CertificateGenerate(domains []string) (*structs.Certificate, error) { + p.Called(domains) + return &p.Certificate, nil +} + func (p *TestProviderRunner) CertificateList() (structs.Certificates, error) { p.Called() return p.Certificates, nil diff --git a/api/structs/certificate.go b/api/structs/certificate.go index 5eba6c8f55..a5d5aaa80d 100644 --- a/api/structs/certificate.go +++ b/api/structs/certificate.go @@ -1,6 +1,9 @@ package structs -import "time" +import ( + "strings" + "time" +) type Certificate struct { Id string `json:"id"` @@ -9,3 +12,7 @@ type Certificate struct { } type Certificates []Certificate + +func (c Certificates) Len() int { return len(c) } +func (c Certificates) Less(i, j int) bool { return strings.ToUpper(c[i].Id) < strings.ToUpper(c[j].Id) } +func (c Certificates) Swap(i, j int) { c[i], c[j] = c[j], c[i] } diff --git a/client/certificates.go b/client/certificates.go index 08c7eb2b88..d99df64acf 100644 --- a/client/certificates.go +++ b/client/certificates.go @@ -2,6 +2,7 @@ package client import ( "fmt" + "strings" "time" ) @@ -35,6 +36,22 @@ func (c *Client) DeleteCertificate(id string) error { return c.Delete(fmt.Sprintf("/certificates/%s", id), nil) } +func (c *Client) GenerateCertificate(domains []string) (*Certificate, error) { + var cert Certificate + + params := Params{ + "domains": strings.Join(domains, ","), + } + + err := c.Post("/certificates/generate", params, &cert) + + if err != nil { + return nil, err + } + + return &cert, nil +} + func (c *Client) ListCertificates() (Certificates, error) { var certs Certificates diff --git a/cmd/convox/certs.go b/cmd/convox/certs.go index cdf809b8da..615d26a09d 100644 --- a/cmd/convox/certs.go +++ b/cmd/convox/certs.go @@ -32,6 +32,12 @@ func init() { Usage: "", Action: cmdCertsDelete, }, + { + Name: "generate", + Description: "generate a certificate", + Usage: " [domain...]", + Action: cmdCertsGenerate, + }, }, }) } @@ -115,3 +121,21 @@ func cmdCertsDelete(c *cli.Context) { fmt.Println("OK") } + +func cmdCertsGenerate(c *cli.Context) { + if len(c.Args()) < 1 { + stdcli.Usage(c, "generate") + return + } + + fmt.Printf("Requesting certificate... ") + + cert, err := rackClient(c).GenerateCertificate(c.Args()) + + if err != nil { + stdcli.Error(err) + return + } + + fmt.Printf("OK, %s\n", cert.Id) +} diff --git a/vendor/github.com/aws/aws-sdk-go/aws/awserr/error.go b/vendor/github.com/aws/aws-sdk-go/aws/awserr/error.go index a52743bef1..e50771f803 100644 --- a/vendor/github.com/aws/aws-sdk-go/aws/awserr/error.go +++ b/vendor/github.com/aws/aws-sdk-go/aws/awserr/error.go @@ -14,13 +14,13 @@ package awserr // if err != nil { // if awsErr, ok := err.(awserr.Error); ok { // // Get error details -// log.Println("Error:", err.Code(), err.Message()) +// log.Println("Error:", awsErr.Code(), awsErr.Message()) // // // Prints out full error message, including original error if there was one. -// log.Println("Error:", err.Error()) +// log.Println("Error:", awsErr.Error()) // // // Get original error -// if origErr := err.Err(); origErr != nil { +// if origErr := awsErr.OrigErr(); origErr != nil { // // operate on original error. // } // } else { @@ -42,15 +42,55 @@ type Error interface { OrigErr() error } +// BatchError is a batch of errors which also wraps lower level errors with +// code, message, and original errors. Calling Error() will include all errors +// that occured in the batch. +// +// Deprecated: Replaced with BatchedErrors. Only defined for backwards +// compatibility. +type BatchError interface { + // Satisfy the generic error interface. + error + + // Returns the short phrase depicting the classification of the error. + Code() string + + // Returns the error details message. + Message() string + + // Returns the original error if one was set. Nil is returned if not set. + OrigErrs() []error +} + +// BatchedErrors is a batch of errors which also wraps lower level errors with +// code, message, and original errors. Calling Error() will include all errors +// that occured in the batch. +// +// Replaces BatchError +type BatchedErrors interface { + // Satisfy the base Error interface. + Error + + // Returns the original error if one was set. Nil is returned if not set. + OrigErrs() []error +} + // New returns an Error object described by the code, message, and origErr. // // If origErr satisfies the Error interface it will not be wrapped within a new // Error object and will instead be returned. func New(code, message string, origErr error) Error { - if e, ok := origErr.(Error); ok && e != nil { - return e + var errs []error + if origErr != nil { + errs = append(errs, origErr) } - return newBaseError(code, message, origErr) + return newBaseError(code, message, errs) +} + +// NewBatchError returns an BatchedErrors with a collection of errors as an +// array of errors. +func NewBatchError(code, message string, errs []error) BatchedErrors { + return newBaseError(code, message, errs) } // A RequestFailure is an interface to extract request failure information from @@ -63,9 +103,9 @@ func New(code, message string, origErr error) Error { // output, err := s3manage.Upload(svc, input, opts) // if err != nil { // if reqerr, ok := err.(RequestFailure); ok { -// log.Printf("Request failed", reqerr.Code(), reqerr.Message(), reqerr.RequestID()) +// log.Println("Request failed", reqerr.Code(), reqerr.Message(), reqerr.RequestID()) // } else { -// log.Printf("Error:", err.Error() +// log.Println("Error:", err.Error()) // } // } // diff --git a/vendor/github.com/aws/aws-sdk-go/aws/awserr/types.go b/vendor/github.com/aws/aws-sdk-go/aws/awserr/types.go index 003a6e8067..e2d333b84b 100644 --- a/vendor/github.com/aws/aws-sdk-go/aws/awserr/types.go +++ b/vendor/github.com/aws/aws-sdk-go/aws/awserr/types.go @@ -31,23 +31,27 @@ type baseError struct { // Optional original error this error is based off of. Allows building // chained errors. - origErr error + errs []error } -// newBaseError returns an error object for the code, message, and err. +// newBaseError returns an error object for the code, message, and errors. // // code is a short no whitespace phrase depicting the classification of // the error that is being created. // -// message is the free flow string containing detailed information about the error. +// message is the free flow string containing detailed information about the +// error. // -// origErr is the error object which will be nested under the new error to be returned. -func newBaseError(code, message string, origErr error) *baseError { - return &baseError{ +// origErrs is the error objects which will be nested under the new errors to +// be returned. +func newBaseError(code, message string, origErrs []error) *baseError { + b := &baseError{ code: code, message: message, - origErr: origErr, + errs: origErrs, } + + return b } // Error returns the string representation of the error. @@ -56,7 +60,12 @@ func newBaseError(code, message string, origErr error) *baseError { // // Satisfies the error interface. func (b baseError) Error() string { - return SprintError(b.code, b.message, "", b.origErr) + size := len(b.errs) + if size > 0 { + return SprintError(b.code, b.message, "", errorList(b.errs)) + } + + return SprintError(b.code, b.message, "", nil) } // String returns the string representation of the error. @@ -75,10 +84,28 @@ func (b baseError) Message() string { return b.message } -// OrigErr returns the original error if one was set. Nil is returned if no error -// was set. +// OrigErr returns the original error if one was set. Nil is returned if no +// error was set. This only returns the first element in the list. If the full +// list is needed, use BatchedErrors. func (b baseError) OrigErr() error { - return b.origErr + switch len(b.errs) { + case 0: + return nil + case 1: + return b.errs[0] + default: + if err, ok := b.errs[0].(Error); ok { + return NewBatchError(err.Code(), err.Message(), b.errs[1:]) + } + return NewBatchError("BatchedErrors", + "multiple errors occured", b.errs) + } +} + +// OrigErrs returns the original errors if one was set. An empty slice is +// returned if no error was set. +func (b baseError) OrigErrs() []error { + return b.errs } // So that the Error interface type can be included as an anonymous field @@ -94,8 +121,8 @@ type requestError struct { requestID string } -// newRequestError returns a wrapped error with additional information for request -// status code, and service requestID. +// newRequestError returns a wrapped error with additional information for +// request status code, and service requestID. // // Should be used to wrap all request which involve service requests. Even if // the request failed without a service response, but had an HTTP status code @@ -133,3 +160,35 @@ func (r requestError) StatusCode() int { func (r requestError) RequestID() string { return r.requestID } + +// OrigErrs returns the original errors if one was set. An empty slice is +// returned if no error was set. +func (r requestError) OrigErrs() []error { + if b, ok := r.awsError.(BatchedErrors); ok { + return b.OrigErrs() + } + return []error{r.OrigErr()} +} + +// An error list that satisfies the golang interface +type errorList []error + +// Error returns the string representation of the error. +// +// Satisfies the error interface. +func (e errorList) Error() string { + msg := "" + // How do we want to handle the array size being zero + if size := len(e); size > 0 { + for i := 0; i < size; i++ { + msg += fmt.Sprintf("%s", e[i].Error()) + // We check the next index to see if it is within the slice. + // If it is, then we append a newline. We do this, because unit tests + // could be broken with the additional '\n' + if i+1 < size { + msg += "\n" + } + } + } + return msg +} diff --git a/vendor/github.com/aws/aws-sdk-go/aws/awsutil/prettify.go b/vendor/github.com/aws/aws-sdk-go/aws/awsutil/prettify.go index 0de3eaa0f6..fc38172fec 100644 --- a/vendor/github.com/aws/aws-sdk-go/aws/awsutil/prettify.go +++ b/vendor/github.com/aws/aws-sdk-go/aws/awsutil/prettify.go @@ -91,6 +91,10 @@ func prettify(v reflect.Value, indent int, buf *bytes.Buffer) { buf.WriteString("\n" + strings.Repeat(" ", indent) + "}") default: + if !v.IsValid() { + fmt.Fprint(buf, "") + return + } format := "%v" switch v.Interface().(type) { case string: diff --git a/vendor/github.com/aws/aws-sdk-go/aws/client/default_retryer.go b/vendor/github.com/aws/aws-sdk-go/aws/client/default_retryer.go index 24d39ce564..f664caf094 100644 --- a/vendor/github.com/aws/aws-sdk-go/aws/client/default_retryer.go +++ b/vendor/github.com/aws/aws-sdk-go/aws/client/default_retryer.go @@ -1,7 +1,6 @@ package client import ( - "math" "math/rand" "time" @@ -32,14 +31,38 @@ func (d DefaultRetryer) MaxRetries() int { // RetryRules returns the delay duration before retrying this request again func (d DefaultRetryer) RetryRules(r *request.Request) time.Duration { - delay := int(math.Pow(2, float64(r.RetryCount))) * (rand.Intn(30) + 30) + // Set the upper limit of delay in retrying at ~five minutes + minTime := 30 + throttle := d.shouldThrottle(r) + if throttle { + minTime = 1000 + } + + retryCount := r.RetryCount + if retryCount > 13 { + retryCount = 13 + } else if throttle && retryCount > 8 { + retryCount = 8 + } + + delay := (1 << uint(retryCount)) * (rand.Intn(30) + minTime) return time.Duration(delay) * time.Millisecond } -// ShouldRetry returns if the request should be retried. +// ShouldRetry returns true if the request should be retried. func (d DefaultRetryer) ShouldRetry(r *request.Request) bool { if r.HTTPResponse.StatusCode >= 500 { return true } - return r.IsErrorRetryable() + return r.IsErrorRetryable() || d.shouldThrottle(r) +} + +// ShouldThrottle returns true if the request should be throttled. +func (d DefaultRetryer) shouldThrottle(r *request.Request) bool { + if r.HTTPResponse.StatusCode == 502 || + r.HTTPResponse.StatusCode == 503 || + r.HTTPResponse.StatusCode == 504 { + return true + } + return r.IsErrorThrottle() } diff --git a/vendor/github.com/aws/aws-sdk-go/aws/config.go b/vendor/github.com/aws/aws-sdk-go/aws/config.go index 75fcc8284b..ade3ebfc0b 100644 --- a/vendor/github.com/aws/aws-sdk-go/aws/config.go +++ b/vendor/github.com/aws/aws-sdk-go/aws/config.go @@ -18,6 +18,11 @@ type RequestRetryer interface{} // A Config provides service configuration for service clients. By default, // all clients will use the {defaults.DefaultConfig} structure. type Config struct { + // Enables verbose error printing of all credential chain errors. + // Should be used when wanting to see all errors while attempting to retreive + // credentials. + CredentialsChainVerboseErrors *bool + // The credentials object to use when signing requests. Defaults to // a chain of credential providers to search for credentials in environment // variables, shared credential file, and EC2 Instance Roles. @@ -95,6 +100,36 @@ type Config struct { // Amazon S3: Virtual Hosting of Buckets S3ForcePathStyle *bool + // Set this to `true` to disable the SDK adding the `Expect: 100-Continue` + // header to PUT requests over 2MB of content. 100-Continue instructs the + // HTTP client not to send the body until the service responds with a + // `continue` status. This is useful to prevent sending the request body + // until after the request is authenticated, and validated. + // + // http://docs.aws.amazon.com/AmazonS3/latest/API/RESTObjectPUT.html + // + // 100-Continue is only enabled for Go 1.6 and above. See `http.Transport`'s + // `ExpectContinueTimeout` for information on adjusting the continue wait timeout. + // https://golang.org/pkg/net/http/#Transport + // + // You should use this flag to disble 100-Continue if you experiance issues + // with proxies or thrid party S3 compatible services. + S3Disable100Continue *bool + + // Set this to `true` to disable the EC2Metadata client from overriding the + // default http.Client's Timeout. This is helpful if you do not want the EC2Metadata + // client to create a new http.Client. This options is only meaningful if you're not + // already using a custom HTTP client with the SDK. Enabled by default. + // + // Must be set and provided to the session.New() in order to disable the EC2Metadata + // overriding the timeout for default credentials chain. + // + // Example: + // sess := session.New(aws.NewConfig().WithEC2MetadataDiableTimeoutOverride(true)) + // svc := s3.New(sess) + // + EC2MetadataDisableTimeoutOverride *bool + SleepDelay func(time.Duration) } @@ -107,6 +142,13 @@ func NewConfig() *Config { return &Config{} } +// WithCredentialsChainVerboseErrors sets a config verbose errors boolean and returning +// a Config pointer. +func (c *Config) WithCredentialsChainVerboseErrors(verboseErrs bool) *Config { + c.CredentialsChainVerboseErrors = &verboseErrs + return c +} + // WithCredentials sets a config Credentials value returning a Config pointer // for chaining. func (c *Config) WithCredentials(creds *credentials.Credentials) *Config { @@ -184,6 +226,20 @@ func (c *Config) WithS3ForcePathStyle(force bool) *Config { return c } +// WithS3Disable100Continue sets a config S3Disable100Continue value returning +// a Config pointer for chaining. +func (c *Config) WithS3Disable100Continue(disable bool) *Config { + c.S3Disable100Continue = &disable + return c +} + +// WithEC2MetadataDisableTimeoutOverride sets a config EC2MetadataDisableTimeoutOverride value +// returning a Config pointer for chaining. +func (c *Config) WithEC2MetadataDisableTimeoutOverride(enable bool) *Config { + c.EC2MetadataDisableTimeoutOverride = &enable + return c +} + // WithSleepDelay overrides the function used to sleep while waiting for the // next retry. Defaults to time.Sleep. func (c *Config) WithSleepDelay(fn func(time.Duration)) *Config { @@ -203,6 +259,10 @@ func mergeInConfig(dst *Config, other *Config) { return } + if other.CredentialsChainVerboseErrors != nil { + dst.CredentialsChainVerboseErrors = other.CredentialsChainVerboseErrors + } + if other.Credentials != nil { dst.Credentials = other.Credentials } @@ -251,6 +311,14 @@ func mergeInConfig(dst *Config, other *Config) { dst.S3ForcePathStyle = other.S3ForcePathStyle } + if other.S3Disable100Continue != nil { + dst.S3Disable100Continue = other.S3Disable100Continue + } + + if other.EC2MetadataDisableTimeoutOverride != nil { + dst.EC2MetadataDisableTimeoutOverride = other.EC2MetadataDisableTimeoutOverride + } + if other.SleepDelay != nil { dst.SleepDelay = other.SleepDelay } diff --git a/vendor/github.com/aws/aws-sdk-go/aws/credentials/chain_provider.go b/vendor/github.com/aws/aws-sdk-go/aws/credentials/chain_provider.go index 115b40739a..857311f64c 100644 --- a/vendor/github.com/aws/aws-sdk-go/aws/credentials/chain_provider.go +++ b/vendor/github.com/aws/aws-sdk-go/aws/credentials/chain_provider.go @@ -8,8 +8,14 @@ var ( // ErrNoValidProvidersFoundInChain Is returned when there are no valid // providers in the ChainProvider. // + // This has been deprecated. For verbose error messaging set + // aws.Config.CredentialsChainVerboseErrors to true + // // @readonly - ErrNoValidProvidersFoundInChain = awserr.New("NoCredentialProviders", "no valid providers in chain", nil) + ErrNoValidProvidersFoundInChain = awserr.New("NoCredentialProviders", + `no valid providers in chain. Deprecated. + For verbose messaging see aws.Config.CredentialsChainVerboseErrors`, + nil) ) // A ChainProvider will search for a provider which returns credentials @@ -45,8 +51,9 @@ var ( // svc := ec2.New(&aws.Config{Credentials: creds}) // type ChainProvider struct { - Providers []Provider - curr Provider + Providers []Provider + curr Provider + VerboseErrors bool } // NewChainCredentials returns a pointer to a new Credentials object @@ -63,17 +70,23 @@ func NewChainCredentials(providers []Provider) *Credentials { // If a provider is found it will be cached and any calls to IsExpired() // will return the expired state of the cached provider. func (c *ChainProvider) Retrieve() (Value, error) { + var errs []error for _, p := range c.Providers { - if creds, err := p.Retrieve(); err == nil { + creds, err := p.Retrieve() + if err == nil { c.curr = p return creds, nil } + errs = append(errs, err) } c.curr = nil - // TODO better error reporting. maybe report error for each failed retrieve? - - return Value{}, ErrNoValidProvidersFoundInChain + var err error + err = ErrNoValidProvidersFoundInChain + if c.VerboseErrors { + err = awserr.NewBatchError("NoCredentialProviders", "no valid providers in chain", errs) + } + return Value{}, err } // IsExpired will returned the expired state of the currently cached provider diff --git a/vendor/github.com/aws/aws-sdk-go/aws/credentials/credentials.go b/vendor/github.com/aws/aws-sdk-go/aws/credentials/credentials.go index 5dd71f02e8..7b8ebf5f9d 100644 --- a/vendor/github.com/aws/aws-sdk-go/aws/credentials/credentials.go +++ b/vendor/github.com/aws/aws-sdk-go/aws/credentials/credentials.go @@ -76,6 +76,9 @@ type Value struct { // AWS Session Token SessionToken string + + // Provider used to get credentials + ProviderName string } // A Provider is the interface for any component which will provide credentials diff --git a/vendor/github.com/aws/aws-sdk-go/aws/credentials/env_provider.go b/vendor/github.com/aws/aws-sdk-go/aws/credentials/env_provider.go index 043e861d6f..96655bc46a 100644 --- a/vendor/github.com/aws/aws-sdk-go/aws/credentials/env_provider.go +++ b/vendor/github.com/aws/aws-sdk-go/aws/credentials/env_provider.go @@ -6,6 +6,9 @@ import ( "github.com/aws/aws-sdk-go/aws/awserr" ) +// EnvProviderName provides a name of Env provider +const EnvProviderName = "EnvProvider" + var ( // ErrAccessKeyIDNotFound is returned when the AWS Access Key ID can't be // found in the process's environment. @@ -52,11 +55,11 @@ func (e *EnvProvider) Retrieve() (Value, error) { } if id == "" { - return Value{}, ErrAccessKeyIDNotFound + return Value{ProviderName: EnvProviderName}, ErrAccessKeyIDNotFound } if secret == "" { - return Value{}, ErrSecretAccessKeyNotFound + return Value{ProviderName: EnvProviderName}, ErrSecretAccessKeyNotFound } e.retrieved = true @@ -64,6 +67,7 @@ func (e *EnvProvider) Retrieve() (Value, error) { AccessKeyID: id, SecretAccessKey: secret, SessionToken: os.Getenv("AWS_SESSION_TOKEN"), + ProviderName: EnvProviderName, }, nil } diff --git a/vendor/github.com/aws/aws-sdk-go/aws/credentials/shared_credentials_provider.go b/vendor/github.com/aws/aws-sdk-go/aws/credentials/shared_credentials_provider.go index 09bd00a950..7fb7cbf0db 100644 --- a/vendor/github.com/aws/aws-sdk-go/aws/credentials/shared_credentials_provider.go +++ b/vendor/github.com/aws/aws-sdk-go/aws/credentials/shared_credentials_provider.go @@ -10,6 +10,9 @@ import ( "github.com/aws/aws-sdk-go/aws/awserr" ) +// SharedCredsProviderName provides a name of SharedCreds provider +const SharedCredsProviderName = "SharedCredentialsProvider" + var ( // ErrSharedCredentialsHomeNotFound is emitted when the user directory cannot be found. // @@ -55,12 +58,12 @@ func (p *SharedCredentialsProvider) Retrieve() (Value, error) { filename, err := p.filename() if err != nil { - return Value{}, err + return Value{ProviderName: SharedCredsProviderName}, err } creds, err := loadProfile(filename, p.profile()) if err != nil { - return Value{}, err + return Value{ProviderName: SharedCredsProviderName}, err } p.retrieved = true @@ -78,23 +81,23 @@ func (p *SharedCredentialsProvider) IsExpired() bool { func loadProfile(filename, profile string) (Value, error) { config, err := ini.Load(filename) if err != nil { - return Value{}, awserr.New("SharedCredsLoad", "failed to load shared credentials file", err) + return Value{ProviderName: SharedCredsProviderName}, awserr.New("SharedCredsLoad", "failed to load shared credentials file", err) } iniProfile, err := config.GetSection(profile) if err != nil { - return Value{}, awserr.New("SharedCredsLoad", "failed to get profile", err) + return Value{ProviderName: SharedCredsProviderName}, awserr.New("SharedCredsLoad", "failed to get profile", err) } id, err := iniProfile.GetKey("aws_access_key_id") if err != nil { - return Value{}, awserr.New("SharedCredsAccessKey", + return Value{ProviderName: SharedCredsProviderName}, awserr.New("SharedCredsAccessKey", fmt.Sprintf("shared credentials %s in %s did not contain aws_access_key_id", profile, filename), err) } secret, err := iniProfile.GetKey("aws_secret_access_key") if err != nil { - return Value{}, awserr.New("SharedCredsSecret", + return Value{ProviderName: SharedCredsProviderName}, awserr.New("SharedCredsSecret", fmt.Sprintf("shared credentials %s in %s did not contain aws_secret_access_key", profile, filename), nil) } @@ -106,6 +109,7 @@ func loadProfile(filename, profile string) (Value, error) { AccessKeyID: id.String(), SecretAccessKey: secret.String(), SessionToken: token.String(), + ProviderName: SharedCredsProviderName, }, nil } diff --git a/vendor/github.com/aws/aws-sdk-go/aws/credentials/static_provider.go b/vendor/github.com/aws/aws-sdk-go/aws/credentials/static_provider.go index 530a9ac2f3..71189e733d 100644 --- a/vendor/github.com/aws/aws-sdk-go/aws/credentials/static_provider.go +++ b/vendor/github.com/aws/aws-sdk-go/aws/credentials/static_provider.go @@ -4,6 +4,9 @@ import ( "github.com/aws/aws-sdk-go/aws/awserr" ) +// StaticProviderName provides a name of Static provider +const StaticProviderName = "StaticProvider" + var ( // ErrStaticCredentialsEmpty is emitted when static credentials are empty. // @@ -30,9 +33,10 @@ func NewStaticCredentials(id, secret, token string) *Credentials { // Retrieve returns the credentials or error if the credentials are invalid. func (s *StaticProvider) Retrieve() (Value, error) { if s.AccessKeyID == "" || s.SecretAccessKey == "" { - return Value{}, ErrStaticCredentialsEmpty + return Value{ProviderName: StaticProviderName}, ErrStaticCredentialsEmpty } + s.Value.ProviderName = StaticProviderName return s.Value, nil } diff --git a/vendor/github.com/aws/aws-sdk-go/aws/logger.go b/vendor/github.com/aws/aws-sdk-go/aws/logger.go index f536948738..db87188e20 100644 --- a/vendor/github.com/aws/aws-sdk-go/aws/logger.go +++ b/vendor/github.com/aws/aws-sdk-go/aws/logger.go @@ -79,6 +79,20 @@ type Logger interface { Log(...interface{}) } +// A LoggerFunc is a convenience type to convert a function taking a variadic +// list of arguments and wrap it so the Logger interface can be used. +// +// Example: +// s3.New(sess, &aws.Config{Logger: aws.LoggerFunc(func(args ...interface{}) { +// fmt.Fprintln(os.Stdout, args...) +// })}) +type LoggerFunc func(...interface{}) + +// Log calls the wrapped function with the arguments provided +func (f LoggerFunc) Log(args ...interface{}) { + f(args...) +} + // NewDefaultLogger returns a Logger which will write log messages to stdout, and // use same formatting runes as the stdlib log.Logger func NewDefaultLogger() Logger { diff --git a/vendor/github.com/aws/aws-sdk-go/aws/request/handlers.go b/vendor/github.com/aws/aws-sdk-go/aws/request/handlers.go index 3e90a7976a..5279c19c09 100644 --- a/vendor/github.com/aws/aws-sdk-go/aws/request/handlers.go +++ b/vendor/github.com/aws/aws-sdk-go/aws/request/handlers.go @@ -50,9 +50,28 @@ func (h *Handlers) Clear() { h.AfterRetry.Clear() } +// A HandlerListRunItem represents an entry in the HandlerList which +// is being run. +type HandlerListRunItem struct { + Index int + Handler NamedHandler + Request *Request +} + // A HandlerList manages zero or more handlers in a list. type HandlerList struct { list []NamedHandler + + // Called after each request handler in the list is called. If set + // and the func returns true the HandlerList will continue to iterate + // over the request handlers. If false is returned the HandlerList + // will stop iterating. + // + // Should be used if extra logic to be performed between each handler + // in the list. This can be used to terminate a list's iteration + // based on a condition such as error like, HandlerListStopOnError. + // Or for logging like HandlerListLogItem. + AfterEachFn func(item HandlerListRunItem) bool } // A NamedHandler is a struct that contains a name and function callback. @@ -63,7 +82,9 @@ type NamedHandler struct { // copy creates a copy of the handler list. func (l *HandlerList) copy() HandlerList { - var n HandlerList + n := HandlerList{ + AfterEachFn: l.AfterEachFn, + } n.list = append([]NamedHandler{}, l.list...) return n } @@ -111,11 +132,37 @@ func (l *HandlerList) Remove(n NamedHandler) { // Run executes all handlers in the list with a given request object. func (l *HandlerList) Run(r *Request) { - for _, f := range l.list { - f.Fn(r) + for i, h := range l.list { + h.Fn(r) + item := HandlerListRunItem{ + Index: i, Handler: h, Request: r, + } + if l.AfterEachFn != nil && !l.AfterEachFn(item) { + return + } } } +// HandlerListLogItem logs the request handler and the state of the +// request's Error value. Always returns true to continue iterating +// request handlers in a HandlerList. +func HandlerListLogItem(item HandlerListRunItem) bool { + if item.Request.Config.Logger == nil { + return true + } + item.Request.Config.Logger.Log("DEBUG: RequestHandler", + item.Index, item.Handler.Name, item.Request.Error) + + return true +} + +// HandlerListStopOnError returns false to stop the HandlerList iterating +// over request handlers if Request.Error is not nil. True otherwise +// to continue iterating. +func HandlerListStopOnError(item HandlerListRunItem) bool { + return item.Request.Error == nil +} + // MakeAddToUserAgentHandler will add the name/version pair to the User-Agent request // header. If the extra parameters are provided they will be added as metadata to the // name/version pair resulting in the following format. diff --git a/vendor/github.com/aws/aws-sdk-go/aws/request/offset_reader.go b/vendor/github.com/aws/aws-sdk-go/aws/request/offset_reader.go new file mode 100644 index 0000000000..da6396d2d9 --- /dev/null +++ b/vendor/github.com/aws/aws-sdk-go/aws/request/offset_reader.go @@ -0,0 +1,49 @@ +package request + +import ( + "io" + "sync" +) + +// offsetReader is a thread-safe io.ReadCloser to prevent racing +// with retrying requests +type offsetReader struct { + buf io.ReadSeeker + lock sync.RWMutex + closed bool +} + +func newOffsetReader(buf io.ReadSeeker, offset int64) *offsetReader { + reader := &offsetReader{} + buf.Seek(offset, 0) + + reader.buf = buf + return reader +} + +// Close is a thread-safe close. Uses the write lock. +func (o *offsetReader) Close() error { + o.lock.Lock() + defer o.lock.Unlock() + o.closed = true + return nil +} + +// Read is a thread-safe read using a read lock. +func (o *offsetReader) Read(p []byte) (int, error) { + o.lock.RLock() + defer o.lock.RUnlock() + + if o.closed { + return 0, io.EOF + } + + return o.buf.Read(p) +} + +// CloseAndCopy will return a new offsetReader with a copy of the old buffer +// and close the old buffer. +func (o *offsetReader) CloseAndCopy(offset int64) *offsetReader { + o.Close() + return newOffsetReader(o.buf, offset) +} diff --git a/vendor/github.com/aws/aws-sdk-go/aws/request/request.go b/vendor/github.com/aws/aws-sdk-go/aws/request/request.go index 3735d7fa53..a252292f00 100644 --- a/vendor/github.com/aws/aws-sdk-go/aws/request/request.go +++ b/vendor/github.com/aws/aws-sdk-go/aws/request/request.go @@ -22,20 +22,22 @@ type Request struct { Handlers Handlers Retryer - Time time.Time - ExpireTime time.Duration - Operation *Operation - HTTPRequest *http.Request - HTTPResponse *http.Response - Body io.ReadSeeker - BodyStart int64 // offset from beginning of Body that the request body starts - Params interface{} - Error error - Data interface{} - RequestID string - RetryCount int - Retryable *bool - RetryDelay time.Duration + Time time.Time + ExpireTime time.Duration + Operation *Operation + HTTPRequest *http.Request + HTTPResponse *http.Response + Body io.ReadSeeker + BodyStart int64 // offset from beginning of Body that the request body starts + Params interface{} + Error error + Data interface{} + RequestID string + RetryCount int + Retryable *bool + RetryDelay time.Duration + NotHoist bool + SignedHeaderVals http.Header built bool } @@ -129,7 +131,7 @@ func (r *Request) SetStringBody(s string) { // SetReaderBody will set the request's body reader. func (r *Request) SetReaderBody(reader io.ReadSeeker) { - r.HTTPRequest.Body = ioutil.NopCloser(reader) + r.HTTPRequest.Body = newOffsetReader(reader, 0) r.Body = reader } @@ -137,6 +139,7 @@ func (r *Request) SetReaderBody(reader io.ReadSeeker) { // if the signing fails. func (r *Request) Presign(expireTime time.Duration) (string, error) { r.ExpireTime = expireTime + r.NotHoist = false r.Sign() if r.Error != nil { return "", r.Error @@ -144,6 +147,18 @@ func (r *Request) Presign(expireTime time.Duration) (string, error) { return r.HTTPRequest.URL.String(), nil } +// PresignRequest behaves just like presign, but hoists all headers and signs them. +// Also returns the signed hash back to the user +func (r *Request) PresignRequest(expireTime time.Duration) (string, http.Header, error) { + r.ExpireTime = expireTime + r.NotHoist = true + r.Sign() + if r.Error != nil { + return "", nil, r.Error + } + return r.HTTPRequest.URL.String(), r.SignedHeaderVals, nil +} + func debugLogReqError(r *Request, stage string, retrying bool, err error) { if !r.Config.LogLevel.Matches(aws.LogDebugWithRequestErrors) { return @@ -177,6 +192,10 @@ func (r *Request) Build() error { return r.Error } r.Handlers.Build.Run(r) + if r.Error != nil { + debugLogReqError(r, "Build Request", false, r.Error) + return r.Error + } r.built = true } @@ -204,22 +223,48 @@ func (r *Request) Sign() error { // be executed in the order they were set. func (r *Request) Send() error { for { - r.Sign() - if r.Error != nil { - return r.Error - } - if aws.BoolValue(r.Retryable) { if r.Config.LogLevel.Matches(aws.LogDebugWithRequestRetries) { r.Config.Logger.Log(fmt.Sprintf("DEBUG: Retrying Request %s/%s, attempt %d", r.ClientInfo.ServiceName, r.Operation.Name, r.RetryCount)) } - // Re-seek the body back to the original point in for a retry so that - // send will send the body's contents again in the upcoming request. - r.Body.Seek(r.BodyStart, 0) - r.HTTPRequest.Body = ioutil.NopCloser(r.Body) + var body io.ReadCloser + if reader, ok := r.HTTPRequest.Body.(*offsetReader); ok { + body = reader.CloseAndCopy(r.BodyStart) + } else { + if r.Config.Logger != nil { + r.Config.Logger.Log("Request body type has been overwritten. May cause race conditions") + } + r.Body.Seek(r.BodyStart, 0) + body = ioutil.NopCloser(r.Body) + } + + r.HTTPRequest = &http.Request{ + URL: r.HTTPRequest.URL, + Header: r.HTTPRequest.Header, + Close: r.HTTPRequest.Close, + Form: r.HTTPRequest.Form, + PostForm: r.HTTPRequest.PostForm, + Body: body, + MultipartForm: r.HTTPRequest.MultipartForm, + Host: r.HTTPRequest.Host, + Method: r.HTTPRequest.Method, + Proto: r.HTTPRequest.Proto, + ContentLength: r.HTTPRequest.ContentLength, + } + if r.HTTPResponse != nil && r.HTTPResponse.Body != nil { + // Closing response body. Since we are setting a new request to send off, this + // response will get squashed and leaked. + r.HTTPResponse.Body.Close() + } + } + + r.Sign() + if r.Error != nil { + return r.Error } + r.Retryable = nil r.Handlers.Send.Run(r) diff --git a/vendor/github.com/aws/aws-sdk-go/aws/request/retryer.go b/vendor/github.com/aws/aws-sdk-go/aws/request/retryer.go index ab6fff5ac8..8cc8b015ae 100644 --- a/vendor/github.com/aws/aws-sdk-go/aws/request/retryer.go +++ b/vendor/github.com/aws/aws-sdk-go/aws/request/retryer.go @@ -26,8 +26,11 @@ func WithRetryer(cfg *aws.Config, retryer Retryer) *aws.Config { // retryableCodes is a collection of service response codes which are retry-able // without any further action. var retryableCodes = map[string]struct{}{ - "RequestError": {}, - "RequestTimeout": {}, + "RequestError": {}, + "RequestTimeout": {}, +} + +var throttleCodes = map[string]struct{}{ "ProvisionedThroughputExceededException": {}, "Throttling": {}, "ThrottlingException": {}, @@ -46,6 +49,11 @@ var credsExpiredCodes = map[string]struct{}{ "RequestExpired": {}, // EC2 Only } +func isCodeThrottle(code string) bool { + _, ok := throttleCodes[code] + return ok +} + func isCodeRetryable(code string) bool { if _, ok := retryableCodes[code]; ok { return true @@ -70,6 +78,17 @@ func (r *Request) IsErrorRetryable() bool { return false } +// IsErrorThrottle returns whether the error is to be throttled based on its code. +// Returns false if the request has no Error set +func (r *Request) IsErrorThrottle() bool { + if r.Error != nil { + if err, ok := r.Error.(awserr.Error); ok { + return isCodeThrottle(err.Code()) + } + } + return false +} + // IsErrorExpired returns whether the error code is a credential expiry error. // Returns false if the request has no Error set. func (r *Request) IsErrorExpired() bool { diff --git a/vendor/github.com/aws/aws-sdk-go/aws/types.go b/vendor/github.com/aws/aws-sdk-go/aws/types.go index 0f067c57f4..fa014b49e1 100644 --- a/vendor/github.com/aws/aws-sdk-go/aws/types.go +++ b/vendor/github.com/aws/aws-sdk-go/aws/types.go @@ -61,23 +61,41 @@ func (r ReaderSeekerCloser) Close() error { type WriteAtBuffer struct { buf []byte m sync.Mutex + + // GrowthCoeff defines the growth rate of the internal buffer. By + // default, the growth rate is 1, where expanding the internal + // buffer will allocate only enough capacity to fit the new expected + // length. + GrowthCoeff float64 +} + +// NewWriteAtBuffer creates a WriteAtBuffer with an internal buffer +// provided by buf. +func NewWriteAtBuffer(buf []byte) *WriteAtBuffer { + return &WriteAtBuffer{buf: buf} } // WriteAt writes a slice of bytes to a buffer starting at the position provided // The number of bytes written will be returned, or error. Can overwrite previous // written slices if the write ats overlap. func (b *WriteAtBuffer) WriteAt(p []byte, pos int64) (n int, err error) { + pLen := len(p) + expLen := pos + int64(pLen) b.m.Lock() defer b.m.Unlock() - - expLen := pos + int64(len(p)) if int64(len(b.buf)) < expLen { - newBuf := make([]byte, expLen) - copy(newBuf, b.buf) - b.buf = newBuf + if int64(cap(b.buf)) < expLen { + if b.GrowthCoeff < 1 { + b.GrowthCoeff = 1 + } + newBuf := make([]byte, expLen, int64(b.GrowthCoeff*float64(expLen))) + copy(newBuf, b.buf) + b.buf = newBuf + } + b.buf = b.buf[:expLen] } copy(b.buf[pos:], p) - return len(p), nil + return pLen, nil } // Bytes returns a slice of bytes written to the buffer. diff --git a/vendor/github.com/aws/aws-sdk-go/aws/version.go b/vendor/github.com/aws/aws-sdk-go/aws/version.go index 42227a114e..d2225f95e9 100644 --- a/vendor/github.com/aws/aws-sdk-go/aws/version.go +++ b/vendor/github.com/aws/aws-sdk-go/aws/version.go @@ -5,4 +5,4 @@ package aws const SDKName = "aws-sdk-go" // SDKVersion is the version of this SDK -const SDKVersion = "1.0.7" +const SDKVersion = "1.1.19" diff --git a/vendor/github.com/aws/aws-sdk-go/private/protocol/idempotency.go b/vendor/github.com/aws/aws-sdk-go/private/protocol/idempotency.go new file mode 100644 index 0000000000..53831dff98 --- /dev/null +++ b/vendor/github.com/aws/aws-sdk-go/private/protocol/idempotency.go @@ -0,0 +1,75 @@ +package protocol + +import ( + "crypto/rand" + "fmt" + "reflect" +) + +// RandReader is the random reader the protocol package will use to read +// random bytes from. This is exported for testing, and should not be used. +var RandReader = rand.Reader + +const idempotencyTokenFillTag = `idempotencyToken` + +// CanSetIdempotencyToken returns true if the struct field should be +// automatically populated with a Idempotency token. +// +// Only *string and string type fields that are tagged with idempotencyToken +// which are not already set can be auto filled. +func CanSetIdempotencyToken(v reflect.Value, f reflect.StructField) bool { + switch u := v.Interface().(type) { + // To auto fill an Idempotency token the field must be a string, + // tagged for auto fill, and have a zero value. + case *string: + return u == nil && len(f.Tag.Get(idempotencyTokenFillTag)) != 0 + case string: + return len(u) == 0 && len(f.Tag.Get(idempotencyTokenFillTag)) != 0 + } + + return false +} + +// GetIdempotencyToken returns a randomly generated idempotency token. +func GetIdempotencyToken() string { + b := make([]byte, 16) + RandReader.Read(b) + + return UUIDVersion4(b) +} + +// SetIdempotencyToken will set the value provided with a Idempotency Token. +// Given that the value can be set. Will panic if value is not setable. +func SetIdempotencyToken(v reflect.Value) { + if v.Kind() == reflect.Ptr { + if v.IsNil() && v.CanSet() { + v.Set(reflect.New(v.Type().Elem())) + } + v = v.Elem() + } + v = reflect.Indirect(v) + + if !v.CanSet() { + panic(fmt.Sprintf("unable to set idempotnecy token %v", v)) + } + + b := make([]byte, 16) + _, err := rand.Read(b) + if err != nil { + // TODO handle error + return + } + + v.Set(reflect.ValueOf(UUIDVersion4(b))) +} + +// UUIDVersion4 returns a Version 4 random UUID from the byte slice provided +func UUIDVersion4(u []byte) string { + // https://en.wikipedia.org/wiki/Universally_unique_identifier#Version_4_.28random.29 + // 13th character is "4" + u[6] = (u[6] | 0x40) & 0x4F + // 17th character is "8", "9", "a", or "b" + u[8] = (u[8] | 0x80) & 0xBF + + return fmt.Sprintf(`%X-%X-%X-%X-%X`, u[0:4], u[4:6], u[6:8], u[8:10], u[10:]) +} diff --git a/vendor/github.com/aws/aws-sdk-go/private/protocol/json/jsonutil/build.go b/vendor/github.com/aws/aws-sdk-go/private/protocol/json/jsonutil/build.go index 870707c6a6..1d552afe45 100644 --- a/vendor/github.com/aws/aws-sdk-go/private/protocol/json/jsonutil/build.go +++ b/vendor/github.com/aws/aws-sdk-go/private/protocol/json/jsonutil/build.go @@ -9,6 +9,8 @@ import ( "sort" "strconv" "time" + + "github.com/aws/aws-sdk-go/private/protocol" ) var timeType = reflect.ValueOf(time.Time{}).Type() @@ -85,11 +87,8 @@ func buildStruct(value reflect.Value, buf *bytes.Buffer, tag reflect.StructTag) first := true for i := 0; i < t.NumField(); i++ { member := value.Field(i) - if (member.Kind() == reflect.Ptr || member.Kind() == reflect.Slice || member.Kind() == reflect.Map) && member.IsNil() { - continue // ignore unset fields - } - field := t.Field(i) + if field.PkgPath != "" { continue // ignore unexported fields } @@ -100,6 +99,15 @@ func buildStruct(value reflect.Value, buf *bytes.Buffer, tag reflect.StructTag) continue // ignore non-body elements } + if protocol.CanSetIdempotencyToken(member, field) { + token := protocol.GetIdempotencyToken() + member = reflect.ValueOf(&token) + } + + if (member.Kind() == reflect.Ptr || member.Kind() == reflect.Slice || member.Kind() == reflect.Map) && member.IsNil() { + continue // ignore unset fields + } + if first { first = false } else { diff --git a/vendor/github.com/aws/aws-sdk-go/private/protocol/jsonrpc/build_bench_test.go b/vendor/github.com/aws/aws-sdk-go/private/protocol/jsonrpc/build_bench_test.go new file mode 100644 index 0000000000..563caa05cf --- /dev/null +++ b/vendor/github.com/aws/aws-sdk-go/private/protocol/jsonrpc/build_bench_test.go @@ -0,0 +1,71 @@ +// +build bench + +package jsonrpc_test + +import ( + "bytes" + "encoding/json" + "testing" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/request" + "github.com/aws/aws-sdk-go/awstesting" + "github.com/aws/aws-sdk-go/private/protocol/json/jsonutil" + "github.com/aws/aws-sdk-go/private/protocol/jsonrpc" + "github.com/aws/aws-sdk-go/service/dynamodb" + "github.com/aws/aws-sdk-go/service/dynamodb/dynamodbattribute" +) + +func BenchmarkJSONRPCBuild_Simple_dynamodbPutItem(b *testing.B) { + svc := awstesting.NewClient() + + params := getDynamodbPutItemParams(b) + + for i := 0; i < b.N; i++ { + r := svc.NewRequest(&request.Operation{Name: "Operation"}, params, nil) + jsonrpc.Build(r) + if r.Error != nil { + b.Fatal("Unexpected error", r.Error) + } + } +} + +func BenchmarkJSONUtilBuild_Simple_dynamodbPutItem(b *testing.B) { + svc := awstesting.NewClient() + + params := getDynamodbPutItemParams(b) + + for i := 0; i < b.N; i++ { + r := svc.NewRequest(&request.Operation{Name: "Operation"}, params, nil) + _, err := jsonutil.BuildJSON(r.Params) + if err != nil { + b.Fatal("Unexpected error", err) + } + } +} + +func BenchmarkEncodingJSONMarshal_Simple_dynamodbPutItem(b *testing.B) { + params := getDynamodbPutItemParams(b) + + for i := 0; i < b.N; i++ { + buf := &bytes.Buffer{} + encoder := json.NewEncoder(buf) + if err := encoder.Encode(params); err != nil { + b.Fatal("Unexpected error", err) + } + } +} + +func getDynamodbPutItemParams(b *testing.B) *dynamodb.PutItemInput { + av, err := dynamodbattribute.ConvertToMap(struct { + Key string + Data string + }{Key: "MyKey", Data: "MyData"}) + if err != nil { + b.Fatal("benchPutItem, expect no ConvertToMap errors", err) + } + return &dynamodb.PutItemInput{ + Item: av, + TableName: aws.String("tablename"), + } +} diff --git a/vendor/github.com/aws/aws-sdk-go/private/protocol/jsonrpc/jsonrpc.go b/vendor/github.com/aws/aws-sdk-go/private/protocol/jsonrpc/jsonrpc.go index c4e71cf487..7aff0e0fa4 100644 --- a/vendor/github.com/aws/aws-sdk-go/private/protocol/jsonrpc/jsonrpc.go +++ b/vendor/github.com/aws/aws-sdk-go/private/protocol/jsonrpc/jsonrpc.go @@ -18,6 +18,18 @@ import ( var emptyJSON = []byte("{}") +// BuildHandler is a named request handler for building jsonrpc protocol requests +var BuildHandler = request.NamedHandler{Name: "awssdk.jsonrpc.Build", Fn: Build} + +// UnmarshalHandler is a named request handler for unmarshaling jsonrpc protocol requests +var UnmarshalHandler = request.NamedHandler{Name: "awssdk.jsonrpc.Unmarshal", Fn: Unmarshal} + +// UnmarshalMetaHandler is a named request handler for unmarshaling jsonrpc protocol request metadata +var UnmarshalMetaHandler = request.NamedHandler{Name: "awssdk.jsonrpc.UnmarshalMeta", Fn: UnmarshalMeta} + +// UnmarshalErrorHandler is a named request handler for unmarshaling jsonrpc protocol request errors +var UnmarshalErrorHandler = request.NamedHandler{Name: "awssdk.jsonrpc.UnmarshalError", Fn: UnmarshalError} + // Build builds a JSON payload for a JSON RPC request. func Build(req *request.Request) { var buf []byte diff --git a/vendor/github.com/aws/aws-sdk-go/private/protocol/rest/build.go b/vendor/github.com/aws/aws-sdk-go/private/protocol/rest/build.go index ed3c2e0395..5f412516dc 100644 --- a/vendor/github.com/aws/aws-sdk-go/private/protocol/rest/build.go +++ b/vendor/github.com/aws/aws-sdk-go/private/protocol/rest/build.go @@ -39,6 +39,9 @@ func init() { } } +// BuildHandler is a named request handler for building rest protocol requests +var BuildHandler = request.NamedHandler{Name: "awssdk.rest.Build", Fn: Build} + // Build builds the REST component of a service request. func Build(r *request.Request) { if r.ParamsFilled() { @@ -219,8 +222,7 @@ func EscapePath(path string, encodeSep bool) string { if noEscape[c] || (c == '/' && !encodeSep) { buf.WriteByte(c) } else { - buf.WriteByte('%') - buf.WriteString(strings.ToUpper(strconv.FormatUint(uint64(c), 16))) + fmt.Fprintf(&buf, "%%%02X", c) } } return buf.String() diff --git a/vendor/github.com/aws/aws-sdk-go/private/protocol/rest/unmarshal.go b/vendor/github.com/aws/aws-sdk-go/private/protocol/rest/unmarshal.go index 27f47b02c7..2cba1d9aa7 100644 --- a/vendor/github.com/aws/aws-sdk-go/private/protocol/rest/unmarshal.go +++ b/vendor/github.com/aws/aws-sdk-go/private/protocol/rest/unmarshal.go @@ -3,6 +3,7 @@ package rest import ( "encoding/base64" "fmt" + "io" "io/ioutil" "net/http" "reflect" @@ -15,6 +16,12 @@ import ( "github.com/aws/aws-sdk-go/aws/request" ) +// UnmarshalHandler is a named request handler for unmarshaling rest protocol requests +var UnmarshalHandler = request.NamedHandler{Name: "awssdk.rest.Unmarshal", Fn: Unmarshal} + +// UnmarshalMetaHandler is a named request handler for unmarshaling rest protocol request metadata +var UnmarshalMetaHandler = request.NamedHandler{Name: "awssdk.rest.UnmarshalMeta", Fn: UnmarshalMeta} + // Unmarshal unmarshals the REST component of a response in a REST service. func Unmarshal(r *request.Request) { if r.DataFilled() { @@ -26,6 +33,10 @@ func Unmarshal(r *request.Request) { // UnmarshalMeta unmarshals the REST metadata of a response in a REST service func UnmarshalMeta(r *request.Request) { r.RequestID = r.HTTPResponse.Header.Get("X-Amzn-Requestid") + if r.RequestID == "" { + // Alternative version of request id in the header + r.RequestID = r.HTTPResponse.Header.Get("X-Amz-Request-Id") + } if r.DataFilled() { v := reflect.Indirect(reflect.ValueOf(r.Data)) unmarshalLocationElements(r, v) @@ -41,6 +52,7 @@ func unmarshalBody(r *request.Request, v reflect.Value) { if payload.IsValid() { switch payload.Interface().(type) { case []byte: + defer r.HTTPResponse.Body.Close() b, err := ioutil.ReadAll(r.HTTPResponse.Body) if err != nil { r.Error = awserr.New("SerializationError", "failed to decode REST response", err) @@ -48,6 +60,7 @@ func unmarshalBody(r *request.Request, v reflect.Value) { payload.Set(reflect.ValueOf(b)) } case *string: + defer r.HTTPResponse.Body.Close() b, err := ioutil.ReadAll(r.HTTPResponse.Body) if err != nil { r.Error = awserr.New("SerializationError", "failed to decode REST response", err) @@ -62,6 +75,8 @@ func unmarshalBody(r *request.Request, v reflect.Value) { case "aws.ReadSeekCloser", "io.ReadCloser": payload.Set(reflect.ValueOf(r.HTTPResponse.Body)) default: + io.Copy(ioutil.Discard, r.HTTPResponse.Body) + defer r.HTTPResponse.Body.Close() r.Error = awserr.New("SerializationError", "failed to decode REST response", fmt.Errorf("unknown payload type %s", payload.Type())) diff --git a/vendor/github.com/aws/aws-sdk-go/private/protocol/unmarshal.go b/vendor/github.com/aws/aws-sdk-go/private/protocol/unmarshal.go new file mode 100644 index 0000000000..da1a68111d --- /dev/null +++ b/vendor/github.com/aws/aws-sdk-go/private/protocol/unmarshal.go @@ -0,0 +1,21 @@ +package protocol + +import ( + "io" + "io/ioutil" + + "github.com/aws/aws-sdk-go/aws/request" +) + +// UnmarshalDiscardBodyHandler is a named request handler to empty and close a response's body +var UnmarshalDiscardBodyHandler = request.NamedHandler{Name: "awssdk.shared.UnmarshalDiscardBody", Fn: UnmarshalDiscardBody} + +// UnmarshalDiscardBody is a request handler to empty a response's body and closing it. +func UnmarshalDiscardBody(r *request.Request) { + if r.HTTPResponse == nil || r.HTTPResponse.Body == nil { + return + } + + io.Copy(ioutil.Discard, r.HTTPResponse.Body) + r.HTTPResponse.Body.Close() +} diff --git a/vendor/github.com/aws/aws-sdk-go/private/signer/v4/header_rules.go b/vendor/github.com/aws/aws-sdk-go/private/signer/v4/header_rules.go new file mode 100644 index 0000000000..244c86da05 --- /dev/null +++ b/vendor/github.com/aws/aws-sdk-go/private/signer/v4/header_rules.go @@ -0,0 +1,82 @@ +package v4 + +import ( + "net/http" + "strings" +) + +// validator houses a set of rule needed for validation of a +// string value +type rules []rule + +// rule interface allows for more flexible rules and just simply +// checks whether or not a value adheres to that rule +type rule interface { + IsValid(value string) bool +} + +// IsValid will iterate through all rules and see if any rules +// apply to the value and supports nested rules +func (r rules) IsValid(value string) bool { + for _, rule := range r { + if rule.IsValid(value) { + return true + } + } + return false +} + +// mapRule generic rule for maps +type mapRule map[string]struct{} + +// IsValid for the map rule satisfies whether it exists in the map +func (m mapRule) IsValid(value string) bool { + _, ok := m[value] + return ok +} + +// whitelist is a generic rule for whitelisting +type whitelist struct { + rule +} + +// IsValid for whitelist checks if the value is within the whitelist +func (w whitelist) IsValid(value string) bool { + return w.rule.IsValid(value) +} + +// blacklist is a generic rule for blacklisting +type blacklist struct { + rule +} + +// IsValid for whitelist checks if the value is within the whitelist +func (b blacklist) IsValid(value string) bool { + return !b.rule.IsValid(value) +} + +type patterns []string + +// IsValid for patterns checks each pattern and returns if a match has +// been found +func (p patterns) IsValid(value string) bool { + for _, pattern := range p { + if strings.HasPrefix(http.CanonicalHeaderKey(value), pattern) { + return true + } + } + return false +} + +// inclusiveRules rules allow for rules to depend on one another +type inclusiveRules []rule + +// IsValid will return true if all rules are true +func (r inclusiveRules) IsValid(value string) bool { + for _, rule := range r { + if !rule.IsValid(value) { + return false + } + } + return true +} diff --git a/vendor/github.com/aws/aws-sdk-go/private/signer/v4/v4.go b/vendor/github.com/aws/aws-sdk-go/private/signer/v4/v4.go index dc176f312b..eb23731212 100644 --- a/vendor/github.com/aws/aws-sdk-go/private/signer/v4/v4.go +++ b/vendor/github.com/aws/aws-sdk-go/private/signer/v4/v4.go @@ -26,11 +26,66 @@ const ( shortTimeFormat = "20060102" ) -var ignoredHeaders = map[string]bool{ - "Authorization": true, - "Content-Type": true, - "Content-Length": true, - "User-Agent": true, +var ignoredHeaders = rules{ + blacklist{ + mapRule{ + "Authorization": struct{}{}, + "User-Agent": struct{}{}, + }, + }, +} + +// requiredSignedHeaders is a whitelist for build canonical headers. +var requiredSignedHeaders = rules{ + whitelist{ + mapRule{ + "Cache-Control": struct{}{}, + "Content-Disposition": struct{}{}, + "Content-Encoding": struct{}{}, + "Content-Language": struct{}{}, + "Content-Md5": struct{}{}, + "Content-Type": struct{}{}, + "Expires": struct{}{}, + "If-Match": struct{}{}, + "If-Modified-Since": struct{}{}, + "If-None-Match": struct{}{}, + "If-Unmodified-Since": struct{}{}, + "Range": struct{}{}, + "X-Amz-Acl": struct{}{}, + "X-Amz-Copy-Source": struct{}{}, + "X-Amz-Copy-Source-If-Match": struct{}{}, + "X-Amz-Copy-Source-If-Modified-Since": struct{}{}, + "X-Amz-Copy-Source-If-None-Match": struct{}{}, + "X-Amz-Copy-Source-If-Unmodified-Since": struct{}{}, + "X-Amz-Copy-Source-Range": struct{}{}, + "X-Amz-Copy-Source-Server-Side-Encryption-Customer-Algorithm": struct{}{}, + "X-Amz-Copy-Source-Server-Side-Encryption-Customer-Key": struct{}{}, + "X-Amz-Copy-Source-Server-Side-Encryption-Customer-Key-Md5": struct{}{}, + "X-Amz-Grant-Full-control": struct{}{}, + "X-Amz-Grant-Read": struct{}{}, + "X-Amz-Grant-Read-Acp": struct{}{}, + "X-Amz-Grant-Write": struct{}{}, + "X-Amz-Grant-Write-Acp": struct{}{}, + "X-Amz-Metadata-Directive": struct{}{}, + "X-Amz-Mfa": struct{}{}, + "X-Amz-Request-Payer": struct{}{}, + "X-Amz-Server-Side-Encryption": struct{}{}, + "X-Amz-Server-Side-Encryption-Aws-Kms-Key-Id": struct{}{}, + "X-Amz-Server-Side-Encryption-Customer-Algorithm": struct{}{}, + "X-Amz-Server-Side-Encryption-Customer-Key": struct{}{}, + "X-Amz-Server-Side-Encryption-Customer-Key-Md5": struct{}{}, + "X-Amz-Storage-Class": struct{}{}, + "X-Amz-Website-Redirect-Location": struct{}{}, + }, + }, + patterns{"X-Amz-Meta-"}, +} + +// allowedHoisting is a whitelist for build query headers. The boolean value +// represents whether or not it is a pattern. +var allowedQueryHoisting = inclusiveRules{ + blacklist{requiredSignedHeaders}, + patterns{"X-Amz-"}, } type signer struct { @@ -57,6 +112,8 @@ type signer struct { stringToSign string signature string authorization string + notHoist bool + signedHeaderVals http.Header } // Sign requests with signature version 4. @@ -92,9 +149,12 @@ func Sign(req *request.Request) { Credentials: req.Config.Credentials, Debug: req.Config.LogLevel.Value(), Logger: req.Config.Logger, + notHoist: req.NotHoist, } req.Error = s.sign() + req.Time = s.Time + req.SignedHeaderVals = s.signedHeaderVals } func (v4 *signer) sign() error { @@ -103,11 +163,12 @@ func (v4 *signer) sign() error { } if v4.isRequestSigned() { - if !v4.Credentials.IsExpired() { + if !v4.Credentials.IsExpired() && time.Now().Before(v4.Time.Add(10*time.Minute)) { // If the request is already signed, and the credentials have not - // expired yet ignore the signing request. + // expired, and the request is not too old ignore the signing request. return nil } + v4.Time = time.Now() // The credentials have expired for this request. The current signing // is invalid, and needs to be request because the request will fail. @@ -165,15 +226,25 @@ func (v4 *signer) logSigningInfo() { } func (v4 *signer) build() { + v4.buildTime() // no depends v4.buildCredentialString() // no depends + + unsignedHeaders := v4.Request.Header if v4.isPresign { - v4.buildQuery() // no depends + if !v4.notHoist { + urlValues := url.Values{} + urlValues, unsignedHeaders = buildQuery(allowedQueryHoisting, unsignedHeaders) // no depends + for k := range urlValues { + v4.Query[k] = urlValues[k] + } + } } - v4.buildCanonicalHeaders() // depends on cred string - v4.buildCanonicalString() // depends on canon headers / signed headers - v4.buildStringToSign() // depends on canon string - v4.buildSignature() // depends on string to sign + + v4.buildCanonicalHeaders(ignoredHeaders, unsignedHeaders) + v4.buildCanonicalString() // depends on canon headers / signed headers + v4.buildStringToSign() // depends on canon string + v4.buildSignature() // depends on string to sign if v4.isPresign { v4.Request.URL.RawQuery += "&X-Amz-Signature=" + v4.signature @@ -213,31 +284,40 @@ func (v4 *signer) buildCredentialString() { } } -func (v4 *signer) buildQuery() { - for k, h := range v4.Request.Header { - if strings.HasPrefix(http.CanonicalHeaderKey(k), "X-Amz-") { - continue // never hoist x-amz-* headers, they must be signed - } - if _, ok := ignoredHeaders[http.CanonicalHeaderKey(k)]; ok { - continue // never hoist ignored headers - } - - v4.Request.Header.Del(k) - v4.Query.Del(k) - for _, v := range h { - v4.Query.Add(k, v) +func buildQuery(r rule, header http.Header) (url.Values, http.Header) { + query := url.Values{} + unsignedHeaders := http.Header{} + for k, h := range header { + if r.IsValid(k) { + query[k] = h + } else { + unsignedHeaders[k] = h } } -} -func (v4 *signer) buildCanonicalHeaders() { + return query, unsignedHeaders +} +func (v4 *signer) buildCanonicalHeaders(r rule, header http.Header) { var headers []string headers = append(headers, "host") - for k := range v4.Request.Header { - if _, ok := ignoredHeaders[http.CanonicalHeaderKey(k)]; ok { + for k, v := range header { + canonicalKey := http.CanonicalHeaderKey(k) + if !r.IsValid(canonicalKey) { continue // ignored header } - headers = append(headers, strings.ToLower(k)) + if v4.signedHeaderVals == nil { + v4.signedHeaderVals = make(http.Header) + } + + lowerCaseKey := strings.ToLower(k) + if _, ok := v4.signedHeaderVals[lowerCaseKey]; ok { + // include additional values + v4.signedHeaderVals[lowerCaseKey] = append(v4.signedHeaderVals[lowerCaseKey], v...) + continue + } + + headers = append(headers, lowerCaseKey) + v4.signedHeaderVals[lowerCaseKey] = v } sort.Strings(headers) @@ -253,7 +333,7 @@ func (v4 *signer) buildCanonicalHeaders() { headerValues[i] = "host:" + v4.Request.URL.Host } else { headerValues[i] = k + ":" + - strings.Join(v4.Request.Header[http.CanonicalHeaderKey(k)], ",") + strings.Join(v4.signedHeaderVals[k], ",") } } diff --git a/vendor/github.com/aws/aws-sdk-go/service/acm/api.go b/vendor/github.com/aws/aws-sdk-go/service/acm/api.go new file mode 100644 index 0000000000..c5232a1f66 --- /dev/null +++ b/vendor/github.com/aws/aws-sdk-go/service/acm/api.go @@ -0,0 +1,739 @@ +// THIS FILE IS AUTOMATICALLY GENERATED. DO NOT EDIT. + +// Package acm provides a client for AWS Certificate Manager. +package acm + +import ( + "time" + + "github.com/aws/aws-sdk-go/aws/awsutil" + "github.com/aws/aws-sdk-go/aws/request" + "github.com/aws/aws-sdk-go/private/protocol" + "github.com/aws/aws-sdk-go/private/protocol/jsonrpc" +) + +const opDeleteCertificate = "DeleteCertificate" + +// DeleteCertificateRequest generates a request for the DeleteCertificate operation. +func (c *ACM) DeleteCertificateRequest(input *DeleteCertificateInput) (req *request.Request, output *DeleteCertificateOutput) { + op := &request.Operation{ + Name: opDeleteCertificate, + HTTPMethod: "POST", + HTTPPath: "/", + } + + if input == nil { + input = &DeleteCertificateInput{} + } + + req = c.newRequest(op, input, output) + req.Handlers.Unmarshal.Remove(jsonrpc.UnmarshalHandler) + req.Handlers.Unmarshal.PushBackNamed(protocol.UnmarshalDiscardBodyHandler) + output = &DeleteCertificateOutput{} + req.Data = output + return +} + +// Deletes an ACM Certificate and its associated private key. If this action +// succeeds, the certificate no longer appears in the list of ACM Certificates +// that can be displayed by calling the ListCertificates action or be retrieved +// by calling the GetCertificate action. The certificate will not be available +// for use by other AWS services. +// +// You cannot delete an ACM Certificate that is being used by another AWS service. +// To delete a certificate that is in use, the certificate association must +// first be removed. +func (c *ACM) DeleteCertificate(input *DeleteCertificateInput) (*DeleteCertificateOutput, error) { + req, out := c.DeleteCertificateRequest(input) + err := req.Send() + return out, err +} + +const opDescribeCertificate = "DescribeCertificate" + +// DescribeCertificateRequest generates a request for the DescribeCertificate operation. +func (c *ACM) DescribeCertificateRequest(input *DescribeCertificateInput) (req *request.Request, output *DescribeCertificateOutput) { + op := &request.Operation{ + Name: opDescribeCertificate, + HTTPMethod: "POST", + HTTPPath: "/", + } + + if input == nil { + input = &DescribeCertificateInput{} + } + + req = c.newRequest(op, input, output) + output = &DescribeCertificateOutput{} + req.Data = output + return +} + +// Returns a list of the fields contained in the specified ACM Certificate. +// For example, this action returns the certificate status, a flag that indicates +// whether the certificate is associated with any other AWS service, and the +// date at which the certificate request was created. The ACM Certificate is +// specified on input by its Amazon Resource Name (ARN). +func (c *ACM) DescribeCertificate(input *DescribeCertificateInput) (*DescribeCertificateOutput, error) { + req, out := c.DescribeCertificateRequest(input) + err := req.Send() + return out, err +} + +const opGetCertificate = "GetCertificate" + +// GetCertificateRequest generates a request for the GetCertificate operation. +func (c *ACM) GetCertificateRequest(input *GetCertificateInput) (req *request.Request, output *GetCertificateOutput) { + op := &request.Operation{ + Name: opGetCertificate, + HTTPMethod: "POST", + HTTPPath: "/", + } + + if input == nil { + input = &GetCertificateInput{} + } + + req = c.newRequest(op, input, output) + output = &GetCertificateOutput{} + req.Data = output + return +} + +// Retrieves an ACM Certificate and certificate chain for the certificate specified +// by an ARN. The chain is an ordered list of certificates that contains the +// root certificate, intermediate certificates of subordinate CAs, and the ACM +// Certificate. The certificate and certificate chain are base64 encoded. If +// you want to decode the certificate chain to see the individual certificate +// fields, you can use OpenSSL. +// +// Currently, ACM Certificates can be used only with Elastic Load Balancing +// and Amazon CloudFront. +func (c *ACM) GetCertificate(input *GetCertificateInput) (*GetCertificateOutput, error) { + req, out := c.GetCertificateRequest(input) + err := req.Send() + return out, err +} + +const opListCertificates = "ListCertificates" + +// ListCertificatesRequest generates a request for the ListCertificates operation. +func (c *ACM) ListCertificatesRequest(input *ListCertificatesInput) (req *request.Request, output *ListCertificatesOutput) { + op := &request.Operation{ + Name: opListCertificates, + HTTPMethod: "POST", + HTTPPath: "/", + Paginator: &request.Paginator{ + InputTokens: []string{"NextToken"}, + OutputTokens: []string{"NextToken"}, + LimitToken: "MaxItems", + TruncationToken: "", + }, + } + + if input == nil { + input = &ListCertificatesInput{} + } + + req = c.newRequest(op, input, output) + output = &ListCertificatesOutput{} + req.Data = output + return +} + +// Retrieves a list of the ACM Certificate ARNs, and the domain name for each +// ARN, owned by the calling account. You can filter the list based on the CertificateStatuses +// parameter, and you can display up to MaxItems certificates at one time. If +// you have more than MaxItems certificates, use the NextToken marker from the +// response object in your next call to the ListCertificates action to retrieve +// the next set of certificate ARNs. +func (c *ACM) ListCertificates(input *ListCertificatesInput) (*ListCertificatesOutput, error) { + req, out := c.ListCertificatesRequest(input) + err := req.Send() + return out, err +} + +func (c *ACM) ListCertificatesPages(input *ListCertificatesInput, fn func(p *ListCertificatesOutput, lastPage bool) (shouldContinue bool)) error { + page, _ := c.ListCertificatesRequest(input) + page.Handlers.Build.PushBack(request.MakeAddToUserAgentFreeFormHandler("Paginator")) + return page.EachPage(func(p interface{}, lastPage bool) bool { + return fn(p.(*ListCertificatesOutput), lastPage) + }) +} + +const opRequestCertificate = "RequestCertificate" + +// RequestCertificateRequest generates a request for the RequestCertificate operation. +func (c *ACM) RequestCertificateRequest(input *RequestCertificateInput) (req *request.Request, output *RequestCertificateOutput) { + op := &request.Operation{ + Name: opRequestCertificate, + HTTPMethod: "POST", + HTTPPath: "/", + } + + if input == nil { + input = &RequestCertificateInput{} + } + + req = c.newRequest(op, input, output) + output = &RequestCertificateOutput{} + req.Data = output + return +} + +// Requests an ACM Certificate for use with other AWS services. To request an +// ACM Certificate, you must specify the fully qualified domain name (FQDN) +// for your site. You can also specify additional FQDNs if users can reach your +// site by using other names. For each domain name you specify, email is sent +// to the domain owner to request approval to issue the certificate. After receiving +// approval from the domain owner, the ACM Certificate is issued. For more information, +// see the AWS Certificate Manager User Guide (http://docs.aws.amazon.com/acm/latest/userguide/overview.html). +func (c *ACM) RequestCertificate(input *RequestCertificateInput) (*RequestCertificateOutput, error) { + req, out := c.RequestCertificateRequest(input) + err := req.Send() + return out, err +} + +const opResendValidationEmail = "ResendValidationEmail" + +// ResendValidationEmailRequest generates a request for the ResendValidationEmail operation. +func (c *ACM) ResendValidationEmailRequest(input *ResendValidationEmailInput) (req *request.Request, output *ResendValidationEmailOutput) { + op := &request.Operation{ + Name: opResendValidationEmail, + HTTPMethod: "POST", + HTTPPath: "/", + } + + if input == nil { + input = &ResendValidationEmailInput{} + } + + req = c.newRequest(op, input, output) + req.Handlers.Unmarshal.Remove(jsonrpc.UnmarshalHandler) + req.Handlers.Unmarshal.PushBackNamed(protocol.UnmarshalDiscardBodyHandler) + output = &ResendValidationEmailOutput{} + req.Data = output + return +} + +// Resends the email that requests domain ownership validation. The domain owner +// or an authorized representative must approve the ACM Certificate before it +// can be issued. The certificate can be approved by clicking a link in the +// mail to navigate to the Amazon certificate approval website and then clicking +// I Approve. However, the validation email can be blocked by spam filters. +// Therefore, if you do not receive the original mail, you can request that +// the mail be resent within 72 hours of requesting the ACM Certificate. If +// more than 72 hours have elapsed since your original request or since your +// last attempt to resend validation mail, you must request a new certificate. +func (c *ACM) ResendValidationEmail(input *ResendValidationEmailInput) (*ResendValidationEmailOutput, error) { + req, out := c.ResendValidationEmailRequest(input) + err := req.Send() + return out, err +} + +// This structure is returned in the response object of the DescribeCertificate +// action. +type CertificateDetail struct { + _ struct{} `type:"structure"` + + // Amazon Resource Name (ARN) of the certificate. This is of the form: + // + // arn:aws:acm:region:123456789012:certificate/12345678-1234-1234-1234-123456789012 + // + // For more information about ARNs, see Amazon Resource Names (ARNs) and AWS + // Service Namespaces (http://docs.aws.amazon.com/general/latest/gr/aws-arns-and-namespaces.html). + CertificateArn *string `min:"20" type:"string"` + + // Time at which the certificate was requested. + CreatedAt *time.Time `type:"timestamp" timestampFormat:"unix"` + + // Fully qualified domain name (FQDN), such as www.example.com or example.com, + // for the certificate. + DomainName *string `min:"1" type:"string"` + + // References a DomainValidation structure that contains the domain name in + // the certificate and the email address that can be used for validation. + DomainValidationOptions []*DomainValidation `min:"1" type:"list"` + + // List that identifies ARNs that are using the certificate. A single ACM Certificate + // can be used by multiple AWS resources. + InUseBy []*string `type:"list"` + + // Time at which the certificate was issued. + IssuedAt *time.Time `type:"timestamp" timestampFormat:"unix"` + + // The X.500 distinguished name of the CA that issued and signed the certificate. + Issuer *string `type:"string"` + + // Asymmetric algorithm used to generate the public and private key pair. Currently + // the only supported value is RSA_2048. + KeyAlgorithm *string `type:"string" enum:"KeyAlgorithm"` + + // Time after which the certificate is not valid. + NotAfter *time.Time `type:"timestamp" timestampFormat:"unix"` + + // Time before which the certificate is not valid. + NotBefore *time.Time `type:"timestamp" timestampFormat:"unix"` + + // A RevocationReason enumeration value that indicates why the certificate was + // revoked. This value exists only if the certificate has been revoked. This + // can be one of the following vales: UNSPECIFIED KEY_COMPROMISE CA_COMPROMISE + // AFFILIATION_CHANGED SUPERCEDED CESSATION_OF_OPERATION CERTIFICATE_HOLD REMOVE_FROM_CRL + // PRIVILEGE_WITHDRAWN A_A_COMPROMISE + RevocationReason *string `type:"string" enum:"RevocationReason"` + + // The time, if any, at which the certificate was revoked. This value exists + // only if the certificate has been revoked. + RevokedAt *time.Time `type:"timestamp" timestampFormat:"unix"` + + // String that contains the serial number of the certificate. + Serial *string `type:"string"` + + // Algorithm used to generate a signature. Currently the only supported value + // is SHA256WITHRSA. + SignatureAlgorithm *string `type:"string"` + + // A CertificateStatus enumeration value that can contain one of the following: + // PENDING_VALIDATION ISSUED INACTIVE EXPIRED REVOKED FAILED VALIDATION_TIMED_OUT + Status *string `type:"string" enum:"CertificateStatus"` + + // The X.500 distinguished name of the entity associated with the public key + // contained in the certificate. + Subject *string `type:"string"` + + // One or more domain names (subject alternative names) included in the certificate + // request. After the certificate is issued, this list includes the domain names + // bound to the public key contained in the certificate. The subject alternative + // names include the canonical domain name (CN) of the certificate and additional + // domain names that can be used to connect to the website. + SubjectAlternativeNames []*string `min:"1" type:"list"` +} + +// String returns the string representation +func (s CertificateDetail) String() string { + return awsutil.Prettify(s) +} + +// GoString returns the string representation +func (s CertificateDetail) GoString() string { + return s.String() +} + +// This structure is returned in the response object of ListCertificates action. +type CertificateSummary struct { + _ struct{} `type:"structure"` + + // Amazon Resource Name (ARN) of the certificate. This is of the form: + // + // arn:aws:acm:region:123456789012:certificate/12345678-1234-1234-1234-123456789012 + // + // For more information about ARNs, see Amazon Resource Names (ARNs) and AWS + // Service Namespaces (http://docs.aws.amazon.com/general/latest/gr/aws-arns-and-namespaces.html). + CertificateArn *string `min:"20" type:"string"` + + // Fully qualified domain name (FQDN), such as www.example.com or example.com, + // for the certificate. + DomainName *string `min:"1" type:"string"` +} + +// String returns the string representation +func (s CertificateSummary) String() string { + return awsutil.Prettify(s) +} + +// GoString returns the string representation +func (s CertificateSummary) GoString() string { + return s.String() +} + +type DeleteCertificateInput struct { + _ struct{} `type:"structure"` + + // String that contains the ARN of the ACM Certificate to be deleted. This must + // be of the form: + // + // arn:aws:acm:region:123456789012:certificate/12345678-1234-1234-1234-123456789012 + // + // For more information about ARNs, see Amazon Resource Names (ARNs) and AWS + // Service Namespaces (http://docs.aws.amazon.com/general/latest/gr/aws-arns-and-namespaces.html). + CertificateArn *string `min:"20" type:"string" required:"true"` +} + +// String returns the string representation +func (s DeleteCertificateInput) String() string { + return awsutil.Prettify(s) +} + +// GoString returns the string representation +func (s DeleteCertificateInput) GoString() string { + return s.String() +} + +type DeleteCertificateOutput struct { + _ struct{} `type:"structure"` +} + +// String returns the string representation +func (s DeleteCertificateOutput) String() string { + return awsutil.Prettify(s) +} + +// GoString returns the string representation +func (s DeleteCertificateOutput) GoString() string { + return s.String() +} + +type DescribeCertificateInput struct { + _ struct{} `type:"structure"` + + // String that contains an ACM Certificate ARN. The ARN must be of the form: + // + // arn:aws:acm:region:123456789012:certificate/12345678-1234-1234-1234-123456789012 + // + // For more information about ARNs, see Amazon Resource Names (ARNs) and AWS + // Service Namespaces (http://docs.aws.amazon.com/general/latest/gr/aws-arns-and-namespaces.html). + CertificateArn *string `min:"20" type:"string" required:"true"` +} + +// String returns the string representation +func (s DescribeCertificateInput) String() string { + return awsutil.Prettify(s) +} + +// GoString returns the string representation +func (s DescribeCertificateInput) GoString() string { + return s.String() +} + +type DescribeCertificateOutput struct { + _ struct{} `type:"structure"` + + // Contains a CertificateDetail structure that lists the fields of an ACM Certificate. + Certificate *CertificateDetail `type:"structure"` +} + +// String returns the string representation +func (s DescribeCertificateOutput) String() string { + return awsutil.Prettify(s) +} + +// GoString returns the string representation +func (s DescribeCertificateOutput) GoString() string { + return s.String() +} + +// Structure that contains the domain name, the base validation domain to which +// validation email is sent, and the email addresses used to validate the domain +// identity. +type DomainValidation struct { + _ struct{} `type:"structure"` + + // Fully Qualified Domain Name (FQDN) of the form www.example.com or example.com + DomainName *string `min:"1" type:"string" required:"true"` + + // The base validation domain that acts as the suffix of the email addresses + // that are used to send the emails. + ValidationDomain *string `min:"1" type:"string"` + + // A list of contact address for the domain registrant. + ValidationEmails []*string `type:"list"` +} + +// String returns the string representation +func (s DomainValidation) String() string { + return awsutil.Prettify(s) +} + +// GoString returns the string representation +func (s DomainValidation) GoString() string { + return s.String() +} + +// This structure is used in the request object of the RequestCertificate action. +type DomainValidationOption struct { + _ struct{} `type:"structure"` + + // Fully Qualified Domain Name (FQDN) of the certificate being requested. + DomainName *string `min:"1" type:"string" required:"true"` + + // The domain to which validation email is sent. This is the base validation + // domain that will act as the suffix of the email addresses. This must be the + // same as the DomainName value or a superdomain of the DomainName value. For + // example, if you requested a certificate for site.subdomain.example.com and + // specify a ValidationDomain of subdomain.example.com, ACM sends email to the + // domain registrant, technical contact, and administrative contact in WHOIS + // for the base domain and the following five addresses: admin@subdomain.example.com + // administrator@subdomain.example.com hostmaster@subdomain.example.com postmaster@subdomain.example.com + // webmaster@subdomain.example.com + ValidationDomain *string `min:"1" type:"string" required:"true"` +} + +// String returns the string representation +func (s DomainValidationOption) String() string { + return awsutil.Prettify(s) +} + +// GoString returns the string representation +func (s DomainValidationOption) GoString() string { + return s.String() +} + +type GetCertificateInput struct { + _ struct{} `type:"structure"` + + // String that contains a certificate ARN in the following format: + // + // arn:aws:acm:region:123456789012:certificate/12345678-1234-1234-1234-123456789012 + // + // For more information about ARNs, see Amazon Resource Names (ARNs) and AWS + // Service Namespaces (http://docs.aws.amazon.com/general/latest/gr/aws-arns-and-namespaces.html). + CertificateArn *string `min:"20" type:"string" required:"true"` +} + +// String returns the string representation +func (s GetCertificateInput) String() string { + return awsutil.Prettify(s) +} + +// GoString returns the string representation +func (s GetCertificateInput) GoString() string { + return s.String() +} + +type GetCertificateOutput struct { + _ struct{} `type:"structure"` + + // String that contains the ACM Certificate represented by the ARN specified + // at input. + Certificate *string `min:"1" type:"string"` + + // The certificate chain that contains the root certificate issued by the certificate + // authority (CA). + CertificateChain *string `min:"1" type:"string"` +} + +// String returns the string representation +func (s GetCertificateOutput) String() string { + return awsutil.Prettify(s) +} + +// GoString returns the string representation +func (s GetCertificateOutput) GoString() string { + return s.String() +} + +type ListCertificatesInput struct { + _ struct{} `type:"structure"` + + // Identifies the statuses of the ACM Certificates for which you want to retrieve + // the ARNs. This can be one or more of the following values: PENDING_VALIDATION + // ISSUED INACTIVE EXPIRED VALIDATION_TIMED_OUT REVOKED FAILED + CertificateStatuses []*string `type:"list"` + + // Specify this parameter when paginating results to indicate the maximum number + // of ACM Certificates that you want to display for each response. If there + // are additional certificates beyond the maximum you specify, use the NextToken + // value in your next call to the ListCertificates action. + MaxItems *int64 `min:"1" type:"integer"` + + // String that contains an opaque marker of the next ACM Certificate ARN to + // be displayed. Use this parameter when paginating results, and only in a subsequent + // request after you've received a response where the results have been truncated. + // Set it to an empty string the first time you call this action, and set it + // to the value of the NextToken element you receive in the response object + // for subsequent calls. + NextToken *string `min:"1" type:"string"` +} + +// String returns the string representation +func (s ListCertificatesInput) String() string { + return awsutil.Prettify(s) +} + +// GoString returns the string representation +func (s ListCertificatesInput) GoString() string { + return s.String() +} + +type ListCertificatesOutput struct { + _ struct{} `type:"structure"` + + // A list of the certificate ARNs. + CertificateSummaryList []*CertificateSummary `type:"list"` + + // If the list has been truncated, this value is present and should be used + // for the NextToken input parameter on your next call to ListCertificates. + NextToken *string `min:"1" type:"string"` +} + +// String returns the string representation +func (s ListCertificatesOutput) String() string { + return awsutil.Prettify(s) +} + +// GoString returns the string representation +func (s ListCertificatesOutput) GoString() string { + return s.String() +} + +type RequestCertificateInput struct { + _ struct{} `type:"structure"` + + // Fully qualified domain name (FQDN), such as www.example.com, of the site + // you want to secure with an ACM Certificate. Use an asterisk (*) to create + // a wildcard certificate that protects several sites in the same domain. For + // example, *.example.com protects www.example.com, site.example.com, and images.example.com. + DomainName *string `min:"1" type:"string" required:"true"` + + // The base validation domain that will act as the suffix of the email addresses + // that are used to send the emails. This must be the same as the Domain value + // or a superdomain of the Domain value. For example, if you requested a certificate + // for test.example.com and specify DomainValidationOptions of example.com, + // ACM sends email to the domain registrant, technical contact, and administrative + // contact in WHOIS and the following five addresses: admin@example.com administrator@example.com + // hostmaster@example.com postmaster@example.com webmaster@example.com + DomainValidationOptions []*DomainValidationOption `min:"1" type:"list"` + + // Customer chosen string that can be used to distinguish between calls to RequestCertificate. + // Idempotency tokens time out after one hour. Therefore, if you call RequestCertificate + // multiple times with the same idempotency token within one hour, ACM recognizes + // that you are requesting only one certificate and will issue only one. If + // you change the idempotency token for each call, ACM recognizes that you are + // requesting multiple certificates. + IdempotencyToken *string `min:"1" type:"string"` + + // Additional FQDNs to be included in the Subject Alternative Name extension + // of the ACM Certificate. For example, add the name www.example.net to a certificate + // for which the DomainName field is www.example.com if users can reach your + // site by using either name. + SubjectAlternativeNames []*string `min:"1" type:"list"` +} + +// String returns the string representation +func (s RequestCertificateInput) String() string { + return awsutil.Prettify(s) +} + +// GoString returns the string representation +func (s RequestCertificateInput) GoString() string { + return s.String() +} + +type RequestCertificateOutput struct { + _ struct{} `type:"structure"` + + // String that contains the ARN of the issued certificate. This must be of the + // form: + // + // arn:aws:acm:us-east-1:123456789012:certificate/12345678-1234-1234-1234-123456789012 + CertificateArn *string `min:"20" type:"string"` +} + +// String returns the string representation +func (s RequestCertificateOutput) String() string { + return awsutil.Prettify(s) +} + +// GoString returns the string representation +func (s RequestCertificateOutput) GoString() string { + return s.String() +} + +type ResendValidationEmailInput struct { + _ struct{} `type:"structure"` + + // String that contains the ARN of the requested certificate. The certificate + // ARN is generated and returned by RequestCertificate as soon as the request + // is made. By default, using this parameter causes email to be sent to all + // top-level domains you specified in the certificate request. + // + // The ARN must be of the form: + // + // arn:aws:acm:us-east-1:123456789012:certificate/12345678-1234-1234-1234-123456789012 + CertificateArn *string `min:"20" type:"string" required:"true"` + + // The Fully Qualified Domain Name (FQDN) of the certificate that needs to be + // validated. + Domain *string `min:"1" type:"string" required:"true"` + + // The base validation domain that will act as the suffix of the email addresses + // that are used to send the emails. This must be the same as the Domain value + // or a superdomain of the Domain value. For example, if you requested a certificate + // for site.subdomain.example.com and specify a ValidationDomain of subdomain.example.com, + // ACM sends email to the domain registrant, technical contact, and administrative + // contact in WHOIS and the following five addresses: admin@subdomain.example.com + // administrator@subdomain.example.com hostmaster@subdomain.example.com postmaster@subdomain.example.com + // webmaster@subdomain.example.com + ValidationDomain *string `min:"1" type:"string" required:"true"` +} + +// String returns the string representation +func (s ResendValidationEmailInput) String() string { + return awsutil.Prettify(s) +} + +// GoString returns the string representation +func (s ResendValidationEmailInput) GoString() string { + return s.String() +} + +type ResendValidationEmailOutput struct { + _ struct{} `type:"structure"` +} + +// String returns the string representation +func (s ResendValidationEmailOutput) String() string { + return awsutil.Prettify(s) +} + +// GoString returns the string representation +func (s ResendValidationEmailOutput) GoString() string { + return s.String() +} + +const ( + // @enum CertificateStatus + CertificateStatusPendingValidation = "PENDING_VALIDATION" + // @enum CertificateStatus + CertificateStatusIssued = "ISSUED" + // @enum CertificateStatus + CertificateStatusInactive = "INACTIVE" + // @enum CertificateStatus + CertificateStatusExpired = "EXPIRED" + // @enum CertificateStatus + CertificateStatusValidationTimedOut = "VALIDATION_TIMED_OUT" + // @enum CertificateStatus + CertificateStatusRevoked = "REVOKED" + // @enum CertificateStatus + CertificateStatusFailed = "FAILED" +) + +const ( + // @enum KeyAlgorithm + KeyAlgorithmRsa2048 = "RSA_2048" + // @enum KeyAlgorithm + KeyAlgorithmEcPrime256v1 = "EC_prime256v1" +) + +const ( + // @enum RevocationReason + RevocationReasonUnspecified = "UNSPECIFIED" + // @enum RevocationReason + RevocationReasonKeyCompromise = "KEY_COMPROMISE" + // @enum RevocationReason + RevocationReasonCaCompromise = "CA_COMPROMISE" + // @enum RevocationReason + RevocationReasonAffiliationChanged = "AFFILIATION_CHANGED" + // @enum RevocationReason + RevocationReasonSuperceded = "SUPERCEDED" + // @enum RevocationReason + RevocationReasonCessationOfOperation = "CESSATION_OF_OPERATION" + // @enum RevocationReason + RevocationReasonCertificateHold = "CERTIFICATE_HOLD" + // @enum RevocationReason + RevocationReasonRemoveFromCrl = "REMOVE_FROM_CRL" + // @enum RevocationReason + RevocationReasonPrivilegeWithdrawn = "PRIVILEGE_WITHDRAWN" + // @enum RevocationReason + RevocationReasonAACompromise = "A_A_COMPROMISE" +) diff --git a/vendor/github.com/aws/aws-sdk-go/service/acm/service.go b/vendor/github.com/aws/aws-sdk-go/service/acm/service.go new file mode 100644 index 0000000000..4659042627 --- /dev/null +++ b/vendor/github.com/aws/aws-sdk-go/service/acm/service.go @@ -0,0 +1,95 @@ +// THIS FILE IS AUTOMATICALLY GENERATED. DO NOT EDIT. + +package acm + +import ( + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/client" + "github.com/aws/aws-sdk-go/aws/client/metadata" + "github.com/aws/aws-sdk-go/aws/request" + "github.com/aws/aws-sdk-go/private/protocol/jsonrpc" + "github.com/aws/aws-sdk-go/private/signer/v4" +) + +// Welcome to the AWS Certificate Manager (ACM) Command Reference. This guide +// provides descriptions, syntax, and usage examples for each ACM command. You +// can use AWS Certificate Manager to request ACM Certificates for your AWS-based +// websites and applications. For general information about using ACM and for +// more information about using the console, see the AWS Certificate Manager +// User Guide (http://docs.aws.amazon.com/acm/latest/userguide/acm-overview.html). +// For more information about using the ACM API, see the AWS Certificate Manager +// API Reference (http://docs.aws.amazon.com/acm/latest/APIReference/Welcome.html). +//The service client's operations are safe to be used concurrently. +// It is not safe to mutate any of the client's properties though. +type ACM struct { + *client.Client +} + +// Used for custom client initialization logic +var initClient func(*client.Client) + +// Used for custom request initialization logic +var initRequest func(*request.Request) + +// A ServiceName is the name of the service the client will make API calls to. +const ServiceName = "acm" + +// New creates a new instance of the ACM client with a session. +// If additional configuration is needed for the client instance use the optional +// aws.Config parameter to add your extra config. +// +// Example: +// // Create a ACM client from just a session. +// svc := acm.New(mySession) +// +// // Create a ACM client with additional configuration +// svc := acm.New(mySession, aws.NewConfig().WithRegion("us-west-2")) +func New(p client.ConfigProvider, cfgs ...*aws.Config) *ACM { + c := p.ClientConfig(ServiceName, cfgs...) + return newClient(*c.Config, c.Handlers, c.Endpoint, c.SigningRegion) +} + +// newClient creates, initializes and returns a new service client instance. +func newClient(cfg aws.Config, handlers request.Handlers, endpoint, signingRegion string) *ACM { + svc := &ACM{ + Client: client.New( + cfg, + metadata.ClientInfo{ + ServiceName: ServiceName, + SigningRegion: signingRegion, + Endpoint: endpoint, + APIVersion: "2015-12-08", + JSONVersion: "1.1", + TargetPrefix: "CertificateManager", + }, + handlers, + ), + } + + // Handlers + svc.Handlers.Sign.PushBack(v4.Sign) + svc.Handlers.Build.PushBackNamed(jsonrpc.BuildHandler) + svc.Handlers.Unmarshal.PushBackNamed(jsonrpc.UnmarshalHandler) + svc.Handlers.UnmarshalMeta.PushBackNamed(jsonrpc.UnmarshalMetaHandler) + svc.Handlers.UnmarshalError.PushBackNamed(jsonrpc.UnmarshalErrorHandler) + + // Run custom client initialization if present + if initClient != nil { + initClient(svc.Client) + } + + return svc +} + +// newRequest creates a new request for a ACM operation and runs any +// custom request initialization. +func (c *ACM) newRequest(op *request.Operation, params, data interface{}) *request.Request { + req := c.NewRequest(op, params, data) + + // Run custom request initialization if present + if initRequest != nil { + initRequest(req) + } + + return req +} From aec039c93e15b89f79b468a68c864ccfb7a72044 Mon Sep 17 00:00:00 2001 From: David Dollar Date: Thu, 21 Apr 2016 10:52:16 -0400 Subject: [PATCH 6/7] dont allow certs that are still pending validation to be used --- api/models/ssl.go | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/api/models/ssl.go b/api/models/ssl.go index 8178e3e199..12bc2c956d 100644 --- a/api/models/ssl.go +++ b/api/models/ssl.go @@ -147,6 +147,18 @@ func UpdateSSL(app, process string, port int, id string) (*SSL, error) { parts := strings.Split(*cert.CertificateArn, "-") if parts[len(parts)-1] == uuid { + res, err := ACM().DescribeCertificate(&acm.DescribeCertificateInput{ + CertificateArn: cert.CertificateArn, + }) + + if err != nil { + return nil, err + } + + if *res.Certificate.Status == "PENDING_VALIDATION" { + return nil, fmt.Errorf("%s is still pending validation", id) + } + arn = *cert.CertificateArn break } From 544255926d381f449f22323be9abd4386cd64a0d Mon Sep 17 00:00:00 2001 From: David Dollar Date: Thu, 21 Apr 2016 10:59:08 -0400 Subject: [PATCH 7/7] add cert generation api to manifest --- api/manifest.yml | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/api/manifest.yml b/api/manifest.yml index b80d154410..b3768fdc24 100644 --- a/api/manifest.yml +++ b/api/manifest.yml @@ -649,6 +649,24 @@ paths: description: invalid certificate schema: $ref: '#/definitions/error' + /certificates/generate: + post: + description: Request a certificate + parameters: + - name: domains + description: public key + type: array + items: + type: string + responses: + 200: + description: certificate + schema: + $ref: '#/definitions/certificate' + 403: + description: invalid domains + schema: + $ref: '#/definitions/error' /certificates/{id}: delete: description: Remove a certificate