diff --git a/builtin/credential/aws-ec2/backend_test.go b/builtin/credential/aws-ec2/backend_test.go index 827ba7eece..309d1f95fb 100644 --- a/builtin/credential/aws-ec2/backend_test.go +++ b/builtin/credential/aws-ec2/backend_test.go @@ -562,6 +562,7 @@ func TestBackend_pathConfigCertificate(t *testing.T) { } data := map[string]interface{}{ + "type": "pkcs7", "aws_public_cert": `LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUM3VENDQXEwQ0NRQ1d1a2paNVY0YVp6QUpC Z2NxaGtqT09BUURNRnd4Q3pBSkJnTlZCQVlUQWxWVE1Sa3cKRndZRFZRUUlFeEJYWVhOb2FXNW5k Rzl1SUZOMFlYUmxNUkF3RGdZRFZRUUhFd2RUWldGMGRHeGxNU0F3SGdZRApWUVFLRXhkQmJXRjZi @@ -586,9 +587,9 @@ MlpCclZOR3JOOU4yZjZST2swazlLCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K certReq.Data = data // test create operation - _, err = b.HandleRequest(certReq) - if err != nil { - t.Fatal(err) + resp, err := b.HandleRequest(certReq) + if err != nil || (resp != nil && resp.IsError()) { + t.Fatalf("resp: %#v, err: %v", resp, err) } certReq.Data = nil @@ -606,7 +607,7 @@ MlpCclZOR3JOOU4yZjZST2swazlLCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K certReq.Operation = logical.ReadOperation // test read operation - resp, err := b.HandleRequest(certReq) + resp, err = b.HandleRequest(certReq) expectedCert := `-----BEGIN CERTIFICATE----- MIIC7TCCAq0CCQCWukjZ5V4aZzAJBgcqhkjOOAQDMFwxCzAJBgNVBAYTAlVTMRkw FwYDVQQIExBXYXNoaW5ndG9uIFN0YXRlMRAwDgYDVQQHEwdTZWF0dGxlMSAwHgYD diff --git a/builtin/credential/aws-ec2/path_config_certificate.go b/builtin/credential/aws-ec2/path_config_certificate.go index 787728c27e..85dc330195 100644 --- a/builtin/credential/aws-ec2/path_config_certificate.go +++ b/builtin/credential/aws-ec2/path_config_certificate.go @@ -6,6 +6,7 @@ import ( "encoding/pem" "fmt" "math/big" + "strings" "github.com/fatih/structs" "github.com/hashicorp/vault/logical" @@ -87,14 +88,21 @@ func pathConfigCertificate(b *backend) *framework.Path { return &framework.Path{ Pattern: "config/certificate/" + framework.GenericNameRegex("cert_name"), Fields: map[string]*framework.FieldSchema{ - "cert_name": &framework.FieldSchema{ + "cert_name": { Type: framework.TypeString, Description: "Name of the certificate.", }, - "aws_public_cert": &framework.FieldSchema{ + "aws_public_cert": { Type: framework.TypeString, Description: "AWS Public cert required to verify PKCS7 signature of the EC2 instance metadata.", }, + "type": { + Type: framework.TypeString, + Description: `Takes the value of either "pkcs7" or "identity", indicating the type of +document which the given certificate should be used to verify. Note that the +PKCS#7 document will have a DSA digest and the identity signature will have an +RSA signature.`, + }, }, ExistenceCheck: b.pathConfigCertificateExistenceCheck, @@ -126,7 +134,7 @@ func (b *backend) pathConfigCertificateExistenceCheck(req *logical.Request, data return entry != nil, nil } -// pathCertificatesList is used to list all the AWS public certificates registered with Vault. +// pathCertificatesList is used to list all the AWS public certificates registered with Vault func (b *backend) pathCertificatesList( req *logical.Request, data *framework.FieldData) (*logical.Response, error) { b.configMutex.RLock() @@ -139,15 +147,15 @@ func (b *backend) pathCertificatesList( return logical.ListResponse(certs), nil } -// Decodes the PEM encoded certiticate and parses it into a x509 cert. +// Decodes the PEM encoded certiticate and parses it into a x509 cert func decodePEMAndParseCertificate(certificate string) (*x509.Certificate, error) { - // Decode the PEM block and error out if a block is not detected in the first attempt. + // Decode the PEM block and error out if a block is not detected in the first attempt decodedPublicCert, rest := pem.Decode([]byte(certificate)) if len(rest) != 0 { return nil, fmt.Errorf("invalid certificate; should be one PEM block only") } - // Check if the certificate can be parsed. + // Check if the certificate can be parsed publicCert, err := x509.ParseCertificate(decodedPublicCert.Bytes) if err != nil { return nil, err @@ -176,20 +184,20 @@ func (b *backend) awsPublicCertificates(s logical.Storage, isPkcs bool) ([]*x509 defaultCert = genericAWSPublicCertificatePkcs7 } - // Append the generic certificate provided in the AWS EC2 instance metadata documentation. + // Append the generic certificate provided in the AWS EC2 instance metadata documentation decodedCert, err := decodePEMAndParseCertificate(defaultCert) if err != nil { return nil, err } certs = append(certs, decodedCert) - // Get the list of all the registered certificates. + // Get the list of all the registered certificates registeredCerts, err := s.List("config/certificate/") if err != nil { return nil, err } - // Iterate through each certificate, parse and append it to a slice. + // Iterate through each certificate, parse and append it to a slice for _, cert := range registeredCerts { certEntry, err := b.nonLockedAWSPublicCertificateEntry(s, cert) if err != nil { @@ -208,8 +216,50 @@ func (b *backend) awsPublicCertificates(s logical.Storage, isPkcs bool) ([]*x509 return certs, nil } -// lockedAWSPublicCertificateEntry is used to get the configured AWS Public Key that is used -// to verify the PKCS#7 signature of the instance identity document. +// lockedSetAWSPublicCertificateEntry is used to store the AWS public key in +// the storage. This method acquires lock before creating or updating a storage +// entry. +func (b *backend) lockedSetAWSPublicCertificateEntry(s logical.Storage, certName string, certEntry *awsPublicCert) error { + if certName == "" { + return fmt.Errorf("missing certificate name") + } + + if certEntry == nil { + return fmt.Errorf("nil AWS public key certificate") + } + + b.configMutex.Lock() + defer b.configMutex.Unlock() + + return b.nonLockedSetAWSPublicCertificateEntry(s, certName, certEntry) +} + +// nonLockedSetAWSPublicCertificateEntry is used to store the AWS public key in +// the storage. This method does not acquire lock before reading the storage. +// If locking is desired, use lockedSetAWSPublicCertificateEntry instead. +func (b *backend) nonLockedSetAWSPublicCertificateEntry(s logical.Storage, certName string, certEntry *awsPublicCert) error { + if certName == "" { + return fmt.Errorf("missing certificate name") + } + + if certEntry == nil { + return fmt.Errorf("nil AWS public key certificate") + } + + entry, err := logical.StorageEntryJSON("config/certificate/"+certName, certEntry) + if err != nil { + return err + } + if entry == nil { + return fmt.Errorf("failed to create storage entry for AWS public key certificate") + } + + return s.Put(entry) +} + +// lockedAWSPublicCertificateEntry is used to get the configured AWS Public Key +// that is used to verify the PKCS#7 signature of the instance identity +// document. func (b *backend) lockedAWSPublicCertificateEntry(s logical.Storage, certName string) (*awsPublicCert, error) { b.configMutex.RLock() defer b.configMutex.RUnlock() @@ -217,7 +267,9 @@ func (b *backend) lockedAWSPublicCertificateEntry(s logical.Storage, certName st return b.nonLockedAWSPublicCertificateEntry(s, certName) } -// Internal version of the above that does no locking +// nonLockedAWSPublicCertificateEntry reads the certificate information from +// the storage. This method does not acquire lock before reading the storage. +// If locking is desired, use lockedAWSPublicCertificateEntry instead. func (b *backend) nonLockedAWSPublicCertificateEntry(s logical.Storage, certName string) (*awsPublicCert, error) { entry, err := s.Get("config/certificate/" + certName) if err != nil { @@ -226,16 +278,30 @@ func (b *backend) nonLockedAWSPublicCertificateEntry(s logical.Storage, certName if entry == nil { return nil, nil } - - var result awsPublicCert - if err := entry.DecodeJSON(&result); err != nil { + var certEntry awsPublicCert + if err := entry.DecodeJSON(&certEntry); err != nil { return nil, err } - return &result, nil + + // Handle upgrade for certificate type + persistNeeded := false + if certEntry.Type == "" { + certEntry.Type = "pkcs7" + persistNeeded = true + } + + if persistNeeded { + if err := b.nonLockedSetAWSPublicCertificateEntry(s, certName, &certEntry); err != nil { + return nil, err + } + } + + return &certEntry, nil } -// pathConfigCertificateDelete is used to delete the previously configured AWS Public Key -// that is used to verify the PKCS#7 signature of the instance identity document. +// pathConfigCertificateDelete is used to delete the previously configured AWS +// Public Key that is used to verify the PKCS#7 signature of the instance +// identity document. func (b *backend) pathConfigCertificateDelete(req *logical.Request, data *framework.FieldData) (*logical.Response, error) { b.configMutex.Lock() defer b.configMutex.Unlock() @@ -248,8 +314,8 @@ func (b *backend) pathConfigCertificateDelete(req *logical.Request, data *framew return nil, req.Storage.Delete("config/certificate/" + certName) } -// pathConfigCertificateRead is used to view the configured AWS Public Key that is -// used to verify the PKCS#7 signature of the instance identity document. +// pathConfigCertificateRead is used to view the configured AWS Public Key that +// is used to verify the PKCS#7 signature of the instance identity document. func (b *backend) pathConfigCertificateRead( req *logical.Request, data *framework.FieldData) (*logical.Response, error) { certName := data.Get("cert_name").(string) @@ -270,19 +336,31 @@ func (b *backend) pathConfigCertificateRead( }, nil } -// pathConfigCertificateCreateUpdate is used to register an AWS Public Key that is -// used to verify the PKCS#7 signature of the instance identity document. +// pathConfigCertificateCreateUpdate is used to register an AWS Public Key that +// is used to verify the PKCS#7 signature of the instance identity document. func (b *backend) pathConfigCertificateCreateUpdate( req *logical.Request, data *framework.FieldData) (*logical.Response, error) { certName := data.Get("cert_name").(string) if certName == "" { - return logical.ErrorResponse("missing cert_name"), nil + return logical.ErrorResponse("missing certificate name"), nil + } + + certType := data.Get("type").(string) + if certType == "" { + return logical.ErrorResponse("missing certificate type"), nil + } + + switch strings.ToLower(certType) { + case "pkcs7": + case "identity": + default: + return logical.ErrorResponse("invalid certificate type"), nil } b.configMutex.Lock() defer b.configMutex.Unlock() - // Check if there is already a certificate entry registered. + // Check if there is already a certificate entry registered certEntry, err := b.nonLockedAWSPublicCertificateEntry(req.Storage, certName) if err != nil { return nil, err @@ -291,7 +369,7 @@ func (b *backend) pathConfigCertificateCreateUpdate( certEntry = &awsPublicCert{} } - // Check if the value is provided by the client. + // Check if the value is provided by the client certStrData, ok := data.GetOk("aws_public_cert") if ok { if certBytes, err := base64.StdEncoding.DecodeString(certStrData.(string)); err == nil { @@ -305,12 +383,12 @@ func (b *backend) pathConfigCertificateCreateUpdate( return logical.ErrorResponse("missing aws_public_cert"), nil } - // If explicitly set to empty string, error out. + // If explicitly set to empty string, error out if certEntry.AWSPublicCert == "" { return logical.ErrorResponse("invalid aws_public_cert"), nil } - // Verify the certificate by decoding it and parsing it. + // Verify the certificate by decoding it and parsing it publicCert, err := decodePEMAndParseCertificate(certEntry.AWSPublicCert) if err != nil { return nil, err @@ -319,14 +397,11 @@ func (b *backend) pathConfigCertificateCreateUpdate( return logical.ErrorResponse("invalid certificate; failed to decode and parse certificate"), nil } - // Ensure that we have not - // If none of the checks fail, save the provided certificate. - entry, err := logical.StorageEntryJSON("config/certificate/"+certName, certEntry) - if err != nil { - return nil, err - } + // Add the certificate type to the storage entry + certEntry.Type = certType - if err := req.Storage.Put(entry); err != nil { + // If none of the checks fail, save the provided certificate + if err := b.nonLockedSetAWSPublicCertificateEntry(req.Storage, certName, certEntry); err != nil { return nil, err } @@ -337,6 +412,7 @@ func (b *backend) pathConfigCertificateCreateUpdate( // of the instnace identity document. type awsPublicCert struct { AWSPublicCert string `json:"aws_public_cert" structs:"aws_public_cert" mapstructure:"aws_public_cert"` + Type string `json:"type" structs:"type" mapstructure:"type"` } const pathConfigCertificateSyn = ` diff --git a/builtin/credential/aws-ec2/path_login.go b/builtin/credential/aws-ec2/path_login.go index 410875580c..317bddb7a9 100644 --- a/builtin/credential/aws-ec2/path_login.go +++ b/builtin/credential/aws-ec2/path_login.go @@ -55,12 +55,14 @@ reauthentication. Note that, when 'disallow_reauthentication' option is enabled on either the role or the role tag, the 'nonce' holds no significance.`, }, "identity": { - Type: framework.TypeString, - Description: `Base64 encoded EC2 instance identity document.`, + Type: framework.TypeString, + Description: `Base64 encoded EC2 instance identity document. This needs to be supplied along +with 'signature' parameter.`, }, "signature": { - Type: framework.TypeString, - Description: `Base64 encoded SHA256 RSA signature of the instance identity document.`, + Type: framework.TypeString, + Description: `Base64 encoded SHA256 RSA signature of the instance identity document. This +needs to be supplied along with 'identity' parameter.`, }, }, diff --git a/website/source/docs/auth/aws-ec2.html.md b/website/source/docs/auth/aws-ec2.html.md index 8a7a4de9ad..7184f627d5 100644 --- a/website/source/docs/auth/aws-ec2.html.md +++ b/website/source/docs/auth/aws-ec2.html.md @@ -512,6 +512,16 @@ The response will be in JSON. For example: AWS Public key required to verify PKCS7 signature of the EC2 instance metadata. +
Returns
@@ -1131,9 +1141,11 @@ instance can be allowed to gain in a worst-case scenario.
Description
- Fetch a token. This endpoint verifies the pkcs#7 signature of the instance identity document. - Verifies that the instance is actually in a running state. Cross checks the constraints defined - on the role with which the login is being performed. +Fetch a token. This endpoint verifies the pkcs7 signature of the instance +identity document. Verifies that the instance is actually in a running state. +Cross checks the constraints defined on the role with which the login is being +performed. As an alternative to pkcs7 signature, the identity document along +with its RSA digest can be supplied to this endpoint.
Method
@@ -1154,11 +1166,29 @@ instance can be allowed to gain in a worst-case scenario. If a matching role is not found, login fails. + +