mirror of
https://github.com/hashicorp/vault.git
synced 2026-04-01 15:15:01 -04:00
aws-ec2 config endpoints support type option to distinguish certs
This commit is contained in:
parent
1317753f18
commit
437ddeaadc
4 changed files with 155 additions and 46 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 = `
|
||||
|
|
|
|||
|
|
@ -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.`,
|
||||
},
|
||||
},
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
</li>
|
||||
</ul>
|
||||
<ul>
|
||||
<li>
|
||||
<span class="param">type</span>
|
||||
<span class="param-flags">required</span>
|
||||
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.
|
||||
</li>
|
||||
</ul>
|
||||
</dd>
|
||||
|
||||
<dt>Returns</dt>
|
||||
|
|
@ -1131,9 +1141,11 @@ instance can be allowed to gain in a worst-case scenario.
|
|||
<dl class="api">
|
||||
<dt>Description</dt>
|
||||
<dd>
|
||||
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.
|
||||
</dd>
|
||||
|
||||
<dt>Method</dt>
|
||||
|
|
@ -1154,11 +1166,29 @@ instance can be allowed to gain in a worst-case scenario.
|
|||
If a matching role is not found, login fails.
|
||||
</li>
|
||||
</ul>
|
||||
<ul>
|
||||
<li>
|
||||
<span class="param">identity</span>
|
||||
<span class="param-flags">required</span>
|
||||
Base64 encoded EC2 instance identity document. This needs to be supplied along
|
||||
with 'signature' parameter.
|
||||
</li>
|
||||
</ul>
|
||||
<ul>
|
||||
<li>
|
||||
<span class="param">signature</span>
|
||||
<span class="param-flags">required</span>
|
||||
Base64 encoded SHA256 RSA signature of the instance identity document. This
|
||||
needs to be supplied along with 'identity' parameter.
|
||||
</li>
|
||||
</ul>
|
||||
<ul>
|
||||
<li>
|
||||
<span class="param">pkcs7</span>
|
||||
<span class="param-flags">required</span>
|
||||
PKCS7 signature of the identity document with all `\n` characters removed.
|
||||
PKCS7 signature of the identity document with all `\n` characters removed.
|
||||
Either this needs to be set *OR* both `identity` and `signature` needs to be
|
||||
set.
|
||||
</li>
|
||||
</ul>
|
||||
<ul>
|
||||
|
|
|
|||
Loading…
Reference in a new issue