Support providing multiple certificates.
Append all the certificates to the PKCS#7 parser during signature verification.
This commit is contained in:
parent
fd977bb478
commit
58c485f519
|
@ -43,6 +43,7 @@ func Backend(conf *logical.BackendConfig) (*framework.Backend, error) {
|
|||
pathImageTag(&b),
|
||||
pathConfigClient(&b),
|
||||
pathConfigCertificate(&b),
|
||||
pathListCertificates(&b),
|
||||
pathBlacklistRoleTag(&b),
|
||||
pathListBlacklistRoleTags(&b),
|
||||
pathBlacklistRoleTagTidy(&b),
|
||||
|
|
|
@ -21,7 +21,7 @@ type dsaSignature struct {
|
|||
// US West (Oregon), US West (N. California), EU (Ireland), EU (Frankfurt),
|
||||
// Asia Pacific (Tokyo), Asia Pacific (Seoul), Asia Pacific (Singapore),
|
||||
// Asia Pacific (Sydney), and South America (Sao Paulo)
|
||||
const defaultAWSPublicCert = `-----BEGIN CERTIFICATE-----
|
||||
const genericAWSPublicCertificate = `-----BEGIN CERTIFICATE-----
|
||||
MIIC7TCCAq0CCQCWukjZ5V4aZzAJBgcqhkjOOAQDMFwxCzAJBgNVBAYTAlVTMRkw
|
||||
FwYDVQQIExBXYXNoaW5ndG9uIFN0YXRlMRAwDgYDVQQHEwdTZWF0dGxlMSAwHgYD
|
||||
VQQKExdBbWF6b24gV2ViIFNlcnZpY2VzIExMQzAeFw0xMjAxMDUxMjU2MTJaFw0z
|
||||
|
@ -41,14 +41,51 @@ vSeDCOUMYQR7R9LINYwouHIziqQYMAkGByqGSM44BAMDLwAwLAIUWXBlk40xTwSw
|
|||
-----END CERTIFICATE-----
|
||||
`
|
||||
|
||||
const govCloudAWSPublicCertificate = `-----BEGIN CERTIFICATE-----
|
||||
MIIC7TCCAq0CCQCWukjZ5V4aZzAJBgcqhkjOOAQDMFwxCzAJBgNVBAYTAlVTMRkw
|
||||
FwYDVQQIExBXYXNoaW5ndG9uIFN0YXRlMRAwDgYDVQQHEwdTZWF0dGxlMSAwHgYD
|
||||
VQQKExdBbWF6b24gV2ViIFNlcnZpY2VzIExMQzAeFw0xMjAxMDUxMjU2MTJaFw0z
|
||||
ODAxMDUxMjU2MTJaMFwxCzAJBgNVBAYTAlVTMRkwFwYDVQQIExBXYXNoaW5ndG9u
|
||||
IFN0YXRlMRAwDgYDVQQHEwdTZWF0dGxlMSAwHgYDVQQKExdBbWF6b24gV2ViIFNl
|
||||
cnZpY2VzIExMQzCCAbcwggEsBgcqhkjOOAQBMIIBHwKBgQCjkvcS2bb1VQ4yt/5e
|
||||
ih5OO6kK/n1Lzllr7D8ZwtQP8fOEpp5E2ng+D6Ud1Z1gYipr58Kj3nssSNpI6bX3
|
||||
VyIQzK7wLclnd/YozqNNmgIyZecN7EglK9ITHJLP+x8FtUpt3QbyYXJdmVMegN6P
|
||||
hviYt5JH/nYl4hh3Pa1HJdskgQIVALVJ3ER11+Ko4tP6nwvHwh6+ERYRAoGBAI1j
|
||||
k+tkqMVHuAFcvAGKocTgsjJem6/5qomzJuKDmbJNu9Qxw3rAotXau8Qe+MBcJl/U
|
||||
hhy1KHVpCGl9fueQ2s6IL0CaO/buycU1CiYQk40KNHCcHfNiZbdlx1E9rpUp7bnF
|
||||
lRa2v1ntMX3caRVDdbtPEWmdxSCYsYFDk4mZrOLBA4GEAAKBgEbmeve5f8LIE/Gf
|
||||
MNmP9CM5eovQOGx5ho8WqD+aTebs+k2tn92BBPqeZqpWRa5P/+jrdKml1qx4llHW
|
||||
MXrs3IgIb6+hUIB+S8dz8/mmO0bpr76RoZVCXYab2CZedFut7qc3WUH9+EUAH5mw
|
||||
vSeDCOUMYQR7R9LINYwouHIziqQYMAkGByqGSM44BAMDLwAwLAIUWXBlk40xTwSw
|
||||
7HX32MxXYruse9ACFBNGmdX2ZBrVNGrN9N2f6ROk0k9K
|
||||
-----END CERTIFICATE-----`
|
||||
|
||||
// pathListCertificates creates a path that enables listing of all
|
||||
// the AWS public certificates registered with Vault.
|
||||
func pathListCertificates(b *backend) *framework.Path {
|
||||
return &framework.Path{
|
||||
Pattern: "config/certificates/?",
|
||||
|
||||
Callbacks: map[logical.Operation]framework.OperationFunc{
|
||||
logical.ListOperation: b.pathCertificatesList,
|
||||
},
|
||||
|
||||
HelpSynopsis: pathListCertificatesHelpSyn,
|
||||
HelpDescription: pathListCertificatesHelpDesc,
|
||||
}
|
||||
}
|
||||
|
||||
func pathConfigCertificate(b *backend) *framework.Path {
|
||||
return &framework.Path{
|
||||
Pattern: "config/certificate$",
|
||||
Pattern: "config/certificate/" + framework.GenericNameRegex("cert_name"),
|
||||
Fields: map[string]*framework.FieldSchema{
|
||||
"cert_name": &framework.FieldSchema{
|
||||
Type: framework.TypeString,
|
||||
Description: "Name of the certificate.",
|
||||
},
|
||||
"aws_public_cert": &framework.FieldSchema{
|
||||
Type: framework.TypeString,
|
||||
Default: defaultAWSPublicCert,
|
||||
Description: "AWS Public key required to verify PKCS7 signature of the EC2 instance metadata.",
|
||||
Description: "AWS Public cert required to verify PKCS7 signature of the EC2 instance metadata.",
|
||||
},
|
||||
},
|
||||
|
||||
|
@ -58,6 +95,7 @@ func pathConfigCertificate(b *backend) *framework.Path {
|
|||
logical.CreateOperation: b.pathConfigCertificateCreateUpdate,
|
||||
logical.UpdateOperation: b.pathConfigCertificateCreateUpdate,
|
||||
logical.ReadOperation: b.pathConfigCertificateRead,
|
||||
logical.DeleteOperation: b.pathConfigCertificateDelete,
|
||||
},
|
||||
|
||||
HelpSynopsis: pathConfigCertificateSyn,
|
||||
|
@ -68,13 +106,27 @@ func pathConfigCertificate(b *backend) *framework.Path {
|
|||
// Establishes dichotomy of request operation between CreateOperation and UpdateOperation.
|
||||
// Returning 'true' forces an UpdateOperation, CreateOperation otherwise.
|
||||
func (b *backend) pathConfigCertificateExistenceCheck(req *logical.Request, data *framework.FieldData) (bool, error) {
|
||||
entry, err := awsPublicCertificateEntry(req.Storage)
|
||||
certName := data.Get("cert_name").(string)
|
||||
if certName == "" {
|
||||
return false, fmt.Errorf("missing cert_name")
|
||||
}
|
||||
entry, err := awsPublicCertificateEntry(req.Storage, certName)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return entry != nil, nil
|
||||
}
|
||||
|
||||
// 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) {
|
||||
certs, err := req.Storage.List("config/certificate/")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return logical.ListResponse(certs), nil
|
||||
}
|
||||
|
||||
// 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.
|
||||
|
@ -94,23 +146,58 @@ func decodePEMAndParseCertificate(certificate string) (*x509.Certificate, error)
|
|||
return publicCert, nil
|
||||
}
|
||||
|
||||
// awsPublicCertificateParsed will fetch the storage entry for the certificate,
|
||||
// decodes it and returns the parsed certificate.
|
||||
func awsPublicCertificateParsed(s logical.Storage) (*x509.Certificate, error) {
|
||||
certEntry, err := awsPublicCertificateEntry(s)
|
||||
// awsPublicCertificates returns a slice of all the parsed AWS public
|
||||
// certificates, that were registered using `config/certificate/<cert_name>` endpoint.
|
||||
// This method will also append two default certificates to the slice.
|
||||
func awsPublicCertificates(s logical.Storage) ([]*x509.Certificate, error) {
|
||||
|
||||
// Get the list `cert_name`s of all the registered certificates.
|
||||
registeredCerts, err := s.List("config/certificate/")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if certEntry == nil {
|
||||
return decodePEMAndParseCertificate(defaultAWSPublicCert)
|
||||
|
||||
var certs []*x509.Certificate
|
||||
|
||||
// Iterate through each certificate, parse and append it to a slice.
|
||||
for _, cert := range registeredCerts {
|
||||
certEntry, err := awsPublicCertificateEntry(s, cert)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if certEntry == nil {
|
||||
return nil, fmt.Errorf("certificate storage has a nil entry under the name:%s\n", cert)
|
||||
}
|
||||
decodedCert, err := decodePEMAndParseCertificate(certEntry.AWSPublicCert)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
certs = append(certs, decodedCert)
|
||||
}
|
||||
return decodePEMAndParseCertificate(certEntry.AWSPublicCert)
|
||||
|
||||
// Append the two public certs provided in the AWS documentation.
|
||||
|
||||
// Append the generic certificate provided in the documentation.
|
||||
decodedCert, err := decodePEMAndParseCertificate(genericAWSPublicCertificate)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
certs = append(certs, decodedCert)
|
||||
|
||||
// Append the govCloud certificate provided in the documentation.
|
||||
decodedCert, err = decodePEMAndParseCertificate(govCloudAWSPublicCertificate)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
certs = append(certs, decodedCert)
|
||||
|
||||
return certs, nil
|
||||
}
|
||||
|
||||
// awsPublicCertificate is used to get the configured AWS Public Key that is used
|
||||
// to verify the PKCS#7 signature of the instance identity document.
|
||||
func awsPublicCertificateEntry(s logical.Storage) (*awsPublicCert, error) {
|
||||
entry, err := s.Get("config/certificate")
|
||||
func awsPublicCertificateEntry(s logical.Storage, certName string) (*awsPublicCert, error) {
|
||||
entry, err := s.Get("config/certificate/" + certName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -126,11 +213,31 @@ func awsPublicCertificateEntry(s logical.Storage) (*awsPublicCert, error) {
|
|||
return &result, 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.
|
||||
func (b *backend) pathConfigCertificateDelete(req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
|
||||
certName := data.Get("cert_name").(string)
|
||||
if certName == "" {
|
||||
return logical.ErrorResponse("missing cert_name"), nil
|
||||
}
|
||||
err := req.Storage.Delete("config/certificate/" + certName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// 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) {
|
||||
certificateEntry, err := awsPublicCertificateEntry(req.Storage)
|
||||
|
||||
certName := data.Get("cert_name").(string)
|
||||
if certName == "" {
|
||||
return logical.ErrorResponse("missing cert_name"), nil
|
||||
}
|
||||
|
||||
certificateEntry, err := awsPublicCertificateEntry(req.Storage, certName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -150,8 +257,13 @@ func (b *backend) pathConfigCertificateRead(
|
|||
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
|
||||
}
|
||||
|
||||
// Check if there is already a certificate entry registered.
|
||||
certEntry, err := awsPublicCertificateEntry(req.Storage)
|
||||
certEntry, err := awsPublicCertificateEntry(req.Storage, certName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -166,15 +278,16 @@ func (b *backend) pathConfigCertificateCreateUpdate(
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
certEntry.AWSPublicCert = string(certBytes)
|
||||
} else if req.Operation == logical.CreateOperation {
|
||||
certEntry.AWSPublicCert = data.Get("aws_public_cert").(string)
|
||||
} else {
|
||||
// aws_public_cert should be supplied for both create and update operations.
|
||||
// If it is not provided, throw an error.
|
||||
return logical.ErrorResponse("missing aws_public_cert"), nil
|
||||
}
|
||||
|
||||
// If explicitly set to empty string, error out.
|
||||
if certEntry.AWSPublicCert == "" {
|
||||
return logical.ErrorResponse("missing aws_public_cert"), nil
|
||||
return logical.ErrorResponse("invalid aws_public_cert"), nil
|
||||
}
|
||||
|
||||
// Verify the certificate by decoding it and parsing it.
|
||||
|
@ -187,7 +300,7 @@ func (b *backend) pathConfigCertificateCreateUpdate(
|
|||
}
|
||||
|
||||
// If none of the checks fail, save the provided certificate.
|
||||
entry, err := logical.StorageEntryJSON("config/certificate", certEntry)
|
||||
entry, err := logical.StorageEntryJSON("config/certificate/"+certName, certEntry)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -217,3 +330,9 @@ US East (N. Virginia), US West (Oregon), US West (N. California), EU (Ireland),
|
|||
EU (Frankfurt), Asia Pacific (Tokyo), Asia Pacific (Seoul), Asia Pacific (Singapore),
|
||||
Asia Pacific (Sydney), and South America (Sao Paulo).
|
||||
`
|
||||
const pathListCertificatesHelpSyn = `
|
||||
Lists all the AWS public certificates that are registered with Vault.
|
||||
`
|
||||
const pathListCertificatesHelpDesc = `
|
||||
Certificates will be listed by their respective names that were used during registration.
|
||||
`
|
||||
|
|
|
@ -64,7 +64,7 @@ func pathImage(b *backend) *framework.Path {
|
|||
}
|
||||
}
|
||||
|
||||
// pathListImages createa a path that enables listing of all the AMIs that are
|
||||
// pathListImages creates a path that enables listing of all the AMIs that are
|
||||
// registered with Vault.
|
||||
func pathListImages(b *backend) *framework.Path {
|
||||
return &framework.Path{
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
package aws
|
||||
|
||||
import (
|
||||
"crypto/x509"
|
||||
"encoding/json"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
|
@ -133,17 +132,17 @@ func parseIdentityDocument(s logical.Storage, pkcs7B64 string) (*identityDocumen
|
|||
}
|
||||
|
||||
// Get the public certificate that is used to verify the signature.
|
||||
publicCert, err := awsPublicCertificateParsed(s)
|
||||
publicCerts, err := awsPublicCertificates(s)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if publicCert == nil {
|
||||
return nil, fmt.Errorf("certificate to verify the signature is not found")
|
||||
if publicCerts == nil || len(publicCerts) == 0 {
|
||||
return nil, fmt.Errorf("certificates to verify the signature are not found")
|
||||
}
|
||||
|
||||
// Before calling Verify() on the PKCS#7 struct, set the certificate to be used
|
||||
// to verify the contents in the signer information.
|
||||
pkcs7Data.Certificates = []*x509.Certificate{publicCert}
|
||||
pkcs7Data.Certificates = publicCerts
|
||||
|
||||
// Verify extracts the authenticated attributes in the PKCS#7 signature, and verifies
|
||||
// the authenticity of the content using 'dsa.PublicKey' embedded in the public certificate.
|
||||
|
|
|
@ -12,7 +12,7 @@ The AWS EC2 auth backend provides a secure introduction mechanism for AWS EC2
|
|||
instances, allowing automated retrieval of a Vault token. Unlike most Vault
|
||||
authentication backends, this backend does not require first deploying or
|
||||
provisioning security-sensitive credentials (tokens, username/password, client
|
||||
certificates, etc.) Instead, it treats AWS as a Trusted Third Party and uses
|
||||
certificates, etc). Instead, it treats AWS as a Trusted Third Party and uses
|
||||
the cryptographically signed dynamic metadata information that uniquely
|
||||
represents each EC2 instance.
|
||||
|
||||
|
@ -59,7 +59,7 @@ has been found to be distributed outside of its intended set of machines.
|
|||
|
||||
## Client Nonce
|
||||
|
||||
If an unintended party gains access to the PKCS#7 version of the identity
|
||||
If an unintended party gains access to the PKCS#7 signature of the identity
|
||||
document (which by default is available to every process and user that gains
|
||||
access to an EC2 instance), it can impersonate that instance and fetch a Vault
|
||||
token. The backend addresses this problem by using a Trust On First Use (TOFU)
|
||||
|
@ -143,8 +143,7 @@ client, etc.), subsequent login attempts will not succeed. If the client nonce
|
|||
is lost, normally the only option is to delete the entry corresponding to the
|
||||
instance ID from the identity `whitelist` in the backend. This can be done via
|
||||
the `auth/aws/whitelist/identity/<instance_id>` endpoint. This allows a new
|
||||
client nonce to be accepted by the backend during
|
||||
the next login request.
|
||||
client nonce to be accepted by the backend during the next login request.
|
||||
|
||||
Under certain circumstances there is another useful setting. When the instance
|
||||
is placed onto a host upon creation, it is given a `pendingTime` value in the
|
||||
|
@ -154,7 +153,7 @@ is updated (this does not apply to reboots, however).
|
|||
|
||||
The backend can take advantage of this via the `allow_instance_migration`
|
||||
option, which is set per-AMI. When this option is enabled, if the client nonce
|
||||
does not matched the saved nonce, the `pendingTime` value in the instance
|
||||
does not match the saved nonce, the `pendingTime` value in the instance
|
||||
identity document will be checked; if it is newer than the stored `pendingTime`
|
||||
value, the backend assumes that the client was stopped/started and allows the
|
||||
client to log in successfully, storing the new nonce as the valid nonce for
|
||||
|
@ -218,18 +217,16 @@ dictated by the safety buffer in order to actually remove the entry.
|
|||
### Varying Public Certificates
|
||||
|
||||
The AWS public certificate which contains the public key used to verify the
|
||||
PKCS#7 signature varies by region. The default public certificate provided with
|
||||
the backend is applicable for all regions except AWS GovCloud (US); however,
|
||||
users of GovCloud may need to install a different public certificate, which can
|
||||
be found at
|
||||
[here](http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/instance-identity-documents.html),
|
||||
PKCS#7 signature varies for groups of regions. The default public certificate
|
||||
provided with the backend is applicable for all regions except AWS GovCloud (US);
|
||||
however, users of GovCloud may need to install a different public certificate, which can
|
||||
be found at [here](http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/instance-identity-documents.html),
|
||||
via the `auth/aws/config/certificate` endpoint.
|
||||
|
||||
If the instances that are using this backend require more than one certificate
|
||||
due to being spread across normal AWS and GovCloud, this backend needs to be
|
||||
mounted at as many paths as there are certificates. The clients should then use
|
||||
an appropriate mount of the backend which can verify its
|
||||
PKCS#7 signature.
|
||||
an appropriate mount of the backend which can verify its PKCS#7 signature.
|
||||
|
||||
## Authentication
|
||||
|
||||
|
|
Loading…
Reference in a new issue