minor updates
This commit is contained in:
parent
e6a9a5957d
commit
4712533f1d
|
@ -96,7 +96,7 @@ func (b *backend) periodicFunc(req *logical.Request) error {
|
|||
if b.nextTidyTime.IsZero() || !time.Now().Before(b.nextTidyTime) {
|
||||
// safety_buffer defaults to 72h
|
||||
safety_buffer := 259200
|
||||
tidyBlacklistConfigEntry, err := configTidyRoleTags(req.Storage)
|
||||
tidyBlacklistConfigEntry, err := b.configTidyRoleTags(req.Storage)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -117,7 +117,7 @@ func (b *backend) periodicFunc(req *logical.Request) error {
|
|||
|
||||
// reset the safety_buffer to 72h
|
||||
safety_buffer = 259200
|
||||
tidyWhitelistConfigEntry, err := configTidyIdentities(req.Storage)
|
||||
tidyWhitelistConfigEntry, err := b.configTidyIdentities(req.Storage)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -22,7 +22,7 @@ import (
|
|||
// * Instance metadata role
|
||||
func (b *backend) getClientConfig(s logical.Storage, region string) (*aws.Config, error) {
|
||||
// Read the configured secret key and access key
|
||||
config, err := clientConfigEntry(s)
|
||||
config, err := b.clientConfigEntry(s)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
@ -91,11 +91,7 @@ func (b *backend) pathBlacklistRoleTagDelete(
|
|||
return logical.ErrorResponse("missing role_tag"), nil
|
||||
}
|
||||
|
||||
err := req.Storage.Delete("blacklist/roletag/" + base64.StdEncoding.EncodeToString([]byte(tag)))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return nil, nil
|
||||
return nil, req.Storage.Delete("blacklist/roletag/" + base64.StdEncoding.EncodeToString([]byte(tag)))
|
||||
}
|
||||
|
||||
// If the given role tag is blacklisted, returns the details of the blacklist entry.
|
||||
|
@ -225,7 +221,7 @@ Blacklist a role tag so that it cannot be used by an EC2 instance to perform log
|
|||
in the future. This can be used if the role tag is suspected or believed to be possessed
|
||||
by an unintended party.
|
||||
|
||||
By default, a cron task will periodically look for expired entries in the blacklist
|
||||
By default, a cron task will periodically looks for expired entries in the blacklist
|
||||
and delete them. The duration to periodically run this is one hour by default.
|
||||
However, this can be configured using the 'config/tidy/roletags' endpoint. This tidy
|
||||
action can be triggered via the API as well, using the 'tidy/roletags' endpoint.
|
||||
|
|
|
@ -193,15 +193,13 @@ func (b *backend) awsPublicCertificateEntry(s logical.Storage, certName string)
|
|||
func (b *backend) pathConfigCertificateDelete(req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
|
||||
b.configMutex.Lock()
|
||||
defer b.configMutex.Unlock()
|
||||
|
||||
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
|
||||
|
||||
return nil, req.Storage.Delete("config/certificate/" + certName)
|
||||
}
|
||||
|
||||
// pathConfigCertificateRead is used to view the configured AWS Public Key that is
|
||||
|
|
|
@ -39,7 +39,7 @@ func pathConfigClient(b *backend) *framework.Path {
|
|||
// Returning 'true' forces an UpdateOperation, CreateOperation otherwise.
|
||||
func (b *backend) pathConfigClientExistenceCheck(
|
||||
req *logical.Request, data *framework.FieldData) (bool, error) {
|
||||
entry, err := clientConfigEntry(req.Storage)
|
||||
entry, err := b.clientConfigEntry(req.Storage)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
@ -47,7 +47,7 @@ func (b *backend) pathConfigClientExistenceCheck(
|
|||
}
|
||||
|
||||
// Fetch the client configuration required to access the AWS API.
|
||||
func clientConfigEntry(s logical.Storage) (*clientConfig, error) {
|
||||
func (b *backend) clientConfigEntry(s logical.Storage) (*clientConfig, error) {
|
||||
b.configMutex.RLock()
|
||||
defer b.configMutex.RUnlock()
|
||||
|
||||
|
@ -68,7 +68,7 @@ func clientConfigEntry(s logical.Storage) (*clientConfig, error) {
|
|||
|
||||
func (b *backend) pathConfigClientRead(
|
||||
req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
|
||||
clientConfig, err := clientConfigEntry(req.Storage)
|
||||
clientConfig, err := b.clientConfigEntry(req.Storage)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -87,8 +87,7 @@ func (b *backend) pathConfigClientDelete(
|
|||
b.configMutex.Lock()
|
||||
defer b.configMutex.Unlock()
|
||||
|
||||
err := req.Storage.Delete("config/client")
|
||||
if err != nil {
|
||||
if err := req.Storage.Delete("config/client"); err != nil {
|
||||
b.configMutex.Unlock()
|
||||
return nil, err
|
||||
}
|
||||
|
@ -103,7 +102,7 @@ func (b *backend) pathConfigClientDelete(
|
|||
// that can be used to interact with AWS EC2 API.
|
||||
func (b *backend) pathConfigClientCreateUpdate(
|
||||
req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
|
||||
configEntry, err := clientConfigEntry(req.Storage)
|
||||
configEntry, err := b.clientConfigEntry(req.Storage)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
@ -38,14 +38,14 @@ expiration, before it is removed from the backend storage.`,
|
|||
}
|
||||
|
||||
func (b *backend) pathConfigTidyIdentitiesExistenceCheck(req *logical.Request, data *framework.FieldData) (bool, error) {
|
||||
entry, err := configTidyIdentities(req.Storage)
|
||||
entry, err := b.configTidyIdentities(req.Storage)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return entry != nil, nil
|
||||
}
|
||||
|
||||
func configTidyIdentities(s logical.Storage) (*tidyWhitelistIdentityConfig, error) {
|
||||
func (b *backend) configTidyIdentities(s logical.Storage) (*tidyWhitelistIdentityConfig, error) {
|
||||
b.configMutex.RLock()
|
||||
defer b.configMutex.RUnlock()
|
||||
entry, err := s.Get("config/tidy/identities")
|
||||
|
@ -64,7 +64,7 @@ func configTidyIdentities(s logical.Storage) (*tidyWhitelistIdentityConfig, erro
|
|||
}
|
||||
|
||||
func (b *backend) pathConfigTidyIdentitiesCreateUpdate(req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
|
||||
configEntry, err := configTidyIdentities(req.Storage)
|
||||
configEntry, err := b.configTidyIdentities(req.Storage)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -102,7 +102,7 @@ func (b *backend) pathConfigTidyIdentitiesCreateUpdate(req *logical.Request, dat
|
|||
}
|
||||
|
||||
func (b *backend) pathConfigTidyIdentitiesRead(req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
|
||||
clientConfig, err := configTidyIdentities(req.Storage)
|
||||
clientConfig, err := b.configTidyIdentities(req.Storage)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -119,11 +119,7 @@ func (b *backend) pathConfigTidyIdentitiesDelete(req *logical.Request, data *fra
|
|||
b.configMutex.Lock()
|
||||
defer b.configMutex.Unlock()
|
||||
|
||||
if err := req.Storage.Delete("config/tidy/identities"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
return nil, req.Storage.Delete("config/tidy/identities")
|
||||
}
|
||||
|
||||
type tidyWhitelistIdentityConfig struct {
|
||||
|
|
|
@ -38,14 +38,14 @@ expiration, before it is removed from the backend storage.`,
|
|||
}
|
||||
|
||||
func (b *backend) pathConfigTidyRoleTagsExistenceCheck(req *logical.Request, data *framework.FieldData) (bool, error) {
|
||||
entry, err := configTidyRoleTags(req.Storage)
|
||||
entry, err := b.configTidyRoleTags(req.Storage)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return entry != nil, nil
|
||||
}
|
||||
|
||||
func configTidyRoleTags(s logical.Storage) (*tidyBlacklistRoleTagConfig, error) {
|
||||
func (b *backend) configTidyRoleTags(s logical.Storage) (*tidyBlacklistRoleTagConfig, error) {
|
||||
b.configMutex.RLock()
|
||||
defer b.configMutex.RUnlock()
|
||||
|
||||
|
@ -66,7 +66,7 @@ func configTidyRoleTags(s logical.Storage) (*tidyBlacklistRoleTagConfig, error)
|
|||
}
|
||||
|
||||
func (b *backend) pathConfigTidyRoleTagsCreateUpdate(req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
|
||||
configEntry, err := configTidyRoleTags(req.Storage)
|
||||
configEntry, err := b.configTidyRoleTags(req.Storage)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -102,7 +102,7 @@ func (b *backend) pathConfigTidyRoleTagsCreateUpdate(req *logical.Request, data
|
|||
}
|
||||
|
||||
func (b *backend) pathConfigTidyRoleTagsRead(req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
|
||||
clientConfig, err := configTidyRoleTags(req.Storage)
|
||||
clientConfig, err := b.configTidyRoleTags(req.Storage)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -119,11 +119,7 @@ func (b *backend) pathConfigTidyRoleTagsDelete(req *logical.Request, data *frame
|
|||
b.configMutex.Lock()
|
||||
defer b.configMutex.Unlock()
|
||||
|
||||
if err := req.Storage.Delete("config/tidy/roletags"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
return nil, req.Storage.Delete("config/tidy/roletags")
|
||||
}
|
||||
|
||||
type tidyBlacklistRoleTagConfig struct {
|
||||
|
|
|
@ -24,7 +24,7 @@ func pathImage(b *backend) *framework.Path {
|
|||
"role_tag": &framework.FieldSchema{
|
||||
Type: framework.TypeString,
|
||||
Default: "",
|
||||
Description: "If set, enables the RoleTag for this AMI. The value set for this field should be the 'key' of the tag on the EC2 instance using the RoleTag. Defaults to empty string.",
|
||||
Description: "If set, enables the RoleTag for this AMI. The value set for this field should be the 'key' of the tag on the EC2 instance. The 'value' of the tag should be generated using 'image/<ami_id>/roletag' endpoint. Defaults to empty string.",
|
||||
},
|
||||
|
||||
"max_ttl": &framework.FieldSchema{
|
||||
|
@ -111,11 +111,7 @@ func awsImage(s logical.Storage, amiID string) (*awsImageEntry, error) {
|
|||
// pathImageDelete is used to delete the information registered for a given AMI ID.
|
||||
func (b *backend) pathImageDelete(
|
||||
req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
|
||||
err := req.Storage.Delete("image/" + strings.ToLower(data.Get("ami_id").(string)))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return nil, nil
|
||||
return nil, req.Storage.Delete("image/" + strings.ToLower(data.Get("ami_id").(string)))
|
||||
}
|
||||
|
||||
// pathImageList is used to list all the AMI IDs registered with Vault.
|
||||
|
@ -255,15 +251,17 @@ be registered with Vault. After the authentication of the instance, the
|
|||
authorization for the instance to access Vault's resources is determined
|
||||
by the policies that are associated to the AMI through this endpoint.
|
||||
|
||||
In case the AMI is shared by many instances, then a role tag can be created
|
||||
through the endpoint 'image/<ami_id>/tag'. This tag needs to be applied on the
|
||||
instance before it attempts to login to Vault. The policies on the tag should
|
||||
be a subset of policies that are associated to the AMI in this endpoint. In
|
||||
order to enable login using tags, RoleTag needs to be enabled in this endpoint.
|
||||
When the instances share an AMI and when only a subset of policies on the AMI
|
||||
are supposed to be applicable for any instance, then 'role_tag' option on the AMI
|
||||
can be enabled to create a role via the endpoint 'image/<ami_id>/tag'.
|
||||
This tag then needs to be applied on the instance before it attempts to login
|
||||
to Vault. The policies on the tag should be a subset of policies that are
|
||||
associated to the AMI in this endpoint. In order to enable login using tags,
|
||||
RoleTag needs to be enabled in this endpoint.
|
||||
|
||||
Also, a 'max_ttl' can be configured in this endpoint that determines the maximum
|
||||
duration for which a login can be renewed. Note that the 'max_ttl' has a upper
|
||||
limit of the 'max_ttl' value that is applicable to the backend.
|
||||
limit of the 'max_ttl' value that is applicable to the backend's mount.
|
||||
`
|
||||
|
||||
const pathListImagesHelpSyn = `
|
||||
|
|
|
@ -77,6 +77,7 @@ func (b *backend) pathImageTagUpdate(
|
|||
// Remove all other policies if 'root' is present.
|
||||
policies := policyutil.ParsePolicies(data.Get("policies").(string))
|
||||
|
||||
// This is an optional field.
|
||||
disallowReauthentication := data.Get("disallow_reauthentication").(bool)
|
||||
|
||||
// Fetch the image entry corresponding to the AMI ID
|
||||
|
@ -95,11 +96,11 @@ func (b *backend) pathImageTagUpdate(
|
|||
|
||||
// There should be a HMAC key present in the image entry
|
||||
if imageEntry.HMACKey == "" {
|
||||
// Not able to find the HMACKey is an internal error
|
||||
// Not being able to find the HMACKey is an internal error
|
||||
return nil, fmt.Errorf("failed to find the HMAC key")
|
||||
}
|
||||
|
||||
// Create a random nonce
|
||||
// Create a random nonce.
|
||||
nonce, err := createRoleTagNonce()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -158,29 +159,36 @@ func createRoleTagValue(rTag *roleTag, imageEntry *awsImageEntry) (string, error
|
|||
}
|
||||
|
||||
// Attach version, nonce, policies and maxTTL to the role tag value.
|
||||
rTagPlainText, err := prepareRoleTagPlaintextValue(rTag)
|
||||
rTagPlaintext, err := prepareRoleTagPlaintextValue(rTag)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return appendHMAC(rTagPlainText, imageEntry)
|
||||
// Attach HMAC to tag's plaintext and return.
|
||||
return appendHMAC(rTagPlaintext, imageEntry)
|
||||
}
|
||||
|
||||
// Takes in the plaintext part of the role tag, creates a HMAC of it and returns
|
||||
// a role tag value containing both the plaintext part and the HMAC part.
|
||||
func appendHMAC(rTagPlainText string, imageEntry *awsImageEntry) (string, error) {
|
||||
func appendHMAC(rTagPlaintext string, imageEntry *awsImageEntry) (string, error) {
|
||||
if rTagPlaintext == "" {
|
||||
return "", fmt.Errorf("empty role tag plaintext string")
|
||||
}
|
||||
|
||||
if imageEntry == nil {
|
||||
return "", fmt.Errorf("nil image entry")
|
||||
}
|
||||
|
||||
// Create the HMAC of the value
|
||||
hmacB64, err := createRoleTagHMACBase64(imageEntry.HMACKey, rTagPlainText)
|
||||
hmacB64, err := createRoleTagHMACBase64(imageEntry.HMACKey, rTagPlaintext)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// attach the HMAC to the value
|
||||
rTagValue := fmt.Sprintf("%s:%s", rTagPlainText, hmacB64)
|
||||
rTagValue := fmt.Sprintf("%s:%s", rTagPlaintext, hmacB64)
|
||||
|
||||
// This limit of 255 is enforced on the EC2 instance. Hence complying to it here.
|
||||
if len(rTagValue) > 255 {
|
||||
return "", fmt.Errorf("role tag 'value' exceeding the limit of 255 characters")
|
||||
}
|
||||
|
@ -201,16 +209,17 @@ func verifyRoleTagValue(rTag *roleTag, imageEntry *awsImageEntry) (bool, error)
|
|||
}
|
||||
|
||||
// Fetch the plaintext part of role tag
|
||||
rTagPlainText, err := prepareRoleTagPlaintextValue(rTag)
|
||||
rTagPlaintext, err := prepareRoleTagPlaintextValue(rTag)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
// Compute the HMAC of the plaintext
|
||||
hmacB64, err := createRoleTagHMACBase64(imageEntry.HMACKey, rTagPlainText)
|
||||
hmacB64, err := createRoleTagHMACBase64(imageEntry.HMACKey, rTagPlaintext)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return subtle.ConstantTimeCompare([]byte(rTag.HMAC), []byte(hmacB64)) == 1, nil
|
||||
}
|
||||
|
||||
|
@ -229,6 +238,7 @@ func prepareRoleTagPlaintextValue(rTag *roleTag) (string, error) {
|
|||
return "", fmt.Errorf("missing ami_id")
|
||||
}
|
||||
|
||||
// This avoids an empty policy, ":p=:" in the role tag.
|
||||
if rTag.Policies == nil || len(rTag.Policies) == 0 {
|
||||
rTag.Policies = []string{"default"}
|
||||
}
|
||||
|
@ -253,6 +263,7 @@ func prepareRoleTagPlaintextValue(rTag *roleTag) (string, error) {
|
|||
// also verifies the correctness of the parsed role tag.
|
||||
func parseAndVerifyRoleTagValue(s logical.Storage, tag string) (*roleTag, error) {
|
||||
tagItems := strings.Split(tag, ":")
|
||||
|
||||
// Tag must contain version, nonce, policies and HMAC
|
||||
if len(tagItems) < 4 {
|
||||
return nil, fmt.Errorf("invalid tag")
|
||||
|
@ -263,45 +274,45 @@ func parseAndVerifyRoleTagValue(s logical.Storage, tag string) (*roleTag, error)
|
|||
// Cache the HMAC value. The last item in the collection.
|
||||
rTag.HMAC = tagItems[len(tagItems)-1]
|
||||
|
||||
// Delete the HMAC from the list.
|
||||
// Remove the HMAC from the list.
|
||||
tagItems = tagItems[:len(tagItems)-1]
|
||||
|
||||
// Version is the first element.
|
||||
// Version will be the first element.
|
||||
rTag.Version = tagItems[0]
|
||||
if rTag.Version != roleTagVersion {
|
||||
return nil, fmt.Errorf("invalid role tag version")
|
||||
}
|
||||
|
||||
// Nonce is the second element.
|
||||
// Nonce will be the second element.
|
||||
rTag.Nonce = tagItems[1]
|
||||
|
||||
if len(tagItems) > 2 {
|
||||
// Delete the version and nonce from the list.
|
||||
tagItems = tagItems[2:]
|
||||
for _, tagItem := range tagItems {
|
||||
var err error
|
||||
switch {
|
||||
case strings.Contains(tagItem, "i="):
|
||||
rTag.InstanceID = strings.TrimPrefix(tagItem, "i=")
|
||||
case strings.Contains(tagItem, "a="):
|
||||
rTag.AmiID = strings.TrimPrefix(tagItem, "a=")
|
||||
case strings.Contains(tagItem, "p="):
|
||||
rTag.Policies = strings.Split(strings.TrimPrefix(tagItem, "p="), ",")
|
||||
case strings.Contains(tagItem, "d="):
|
||||
rTag.DisallowReauthentication, err = strconv.ParseBool(strings.TrimPrefix(tagItem, "d="))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
case strings.Contains(tagItem, "t="):
|
||||
rTag.MaxTTL, err = time.ParseDuration(strings.TrimPrefix(tagItem, "t="))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
default:
|
||||
return nil, fmt.Errorf("unrecognized item in tag")
|
||||
// Delete the version and nonce from the list.
|
||||
tagItems = tagItems[2:]
|
||||
|
||||
for _, tagItem := range tagItems {
|
||||
var err error
|
||||
switch {
|
||||
case strings.Contains(tagItem, "i="):
|
||||
rTag.InstanceID = strings.TrimPrefix(tagItem, "i=")
|
||||
case strings.Contains(tagItem, "a="):
|
||||
rTag.AmiID = strings.TrimPrefix(tagItem, "a=")
|
||||
case strings.Contains(tagItem, "p="):
|
||||
rTag.Policies = strings.Split(strings.TrimPrefix(tagItem, "p="), ",")
|
||||
case strings.Contains(tagItem, "d="):
|
||||
rTag.DisallowReauthentication, err = strconv.ParseBool(strings.TrimPrefix(tagItem, "d="))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
case strings.Contains(tagItem, "t="):
|
||||
rTag.MaxTTL, err = time.ParseDuration(strings.TrimPrefix(tagItem, "t="))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
default:
|
||||
return nil, fmt.Errorf("unrecognized item %s in tag", tagItem)
|
||||
}
|
||||
}
|
||||
|
||||
if rTag.AmiID == "" {
|
||||
return nil, fmt.Errorf("missing image ID")
|
||||
}
|
||||
|
@ -320,8 +331,9 @@ func parseAndVerifyRoleTagValue(s logical.Storage, tag string) (*roleTag, error)
|
|||
return nil, err
|
||||
}
|
||||
if !verified {
|
||||
return nil, fmt.Errorf("role tag signature mismatch")
|
||||
return nil, fmt.Errorf("role tag signature verification failed")
|
||||
}
|
||||
|
||||
return rTag, nil
|
||||
}
|
||||
|
||||
|
@ -339,11 +351,11 @@ func createRoleTagHMACBase64(key, value string) (string, error) {
|
|||
|
||||
// Creates a base64 encoded random nonce.
|
||||
func createRoleTagNonce() (string, error) {
|
||||
uuidBytes, err := uuid.GenerateRandomBytes(8)
|
||||
if err != nil {
|
||||
if uuidBytes, err := uuid.GenerateRandomBytes(8); err != nil {
|
||||
return "", err
|
||||
} else {
|
||||
return base64.StdEncoding.EncodeToString(uuidBytes), nil
|
||||
}
|
||||
return base64.StdEncoding.EncodeToString(uuidBytes), nil
|
||||
}
|
||||
|
||||
// Struct roleTag represents a role tag in a struc form.
|
||||
|
@ -376,14 +388,14 @@ Create a tag for an EC2 instance.
|
|||
`
|
||||
|
||||
const pathImageTagDesc = `
|
||||
When an AMI is used by more than one EC2 instance, policies to be associated
|
||||
during login are determined by a particular tag on the instance. This tag
|
||||
can be created using this endpoint.
|
||||
When an AMI is used by more than one EC2 instance and there is a need
|
||||
to apply only a subset of AMI's policies on the instance, create a
|
||||
role tag using this endpoint and apply it on the instance.
|
||||
|
||||
A RoleTag setting needs to be enabled in 'image/<ami_id>' endpoint, to be able
|
||||
to create a tag. Also, the policies to be associated with the tag should be
|
||||
a subset of the policies associated with the regisred AMI.
|
||||
|
||||
This endpoint will return both the 'key' and the 'value' to be set for the
|
||||
instance tag.
|
||||
EC2 instance tag.
|
||||
`
|
||||
|
|
|
@ -68,7 +68,7 @@ func (b *backend) validateInstance(s logical.Storage, identityDoc *identityDocum
|
|||
return nil, fmt.Errorf("no instance details found in reservations")
|
||||
}
|
||||
if *status.Reservations[0].Instances[0].InstanceId != identityDoc.InstanceID {
|
||||
return nil, fmt.Errorf("expected instance ID does not match the instance ID in the instance description")
|
||||
return nil, fmt.Errorf("expected instance ID not matching the instance ID in the instance description")
|
||||
}
|
||||
if status.Reservations[0].Instances[0].State == nil {
|
||||
return nil, fmt.Errorf("instance state in instance description is nil")
|
||||
|
@ -77,7 +77,6 @@ func (b *backend) validateInstance(s logical.Storage, identityDoc *identityDocum
|
|||
*status.Reservations[0].Instances[0].State.Name != "running" {
|
||||
return nil, fmt.Errorf("instance is not in 'running' state")
|
||||
}
|
||||
// Validate the instance through InstanceState, InstanceStatus and SystemStatus
|
||||
return status, nil
|
||||
}
|
||||
|
||||
|
@ -107,7 +106,8 @@ func validateMetadata(clientNonce, pendingTime string, storedIdentity *whitelist
|
|||
// instance metadata, which sadly is only updated when an instance is
|
||||
// stopped and started but *not* when the instance is rebooted. If reboot
|
||||
// survivability is needed, either instrumentation to delete the instance
|
||||
// ID is necessary, or the client must durably store the nonce.
|
||||
// ID from the whitelist is necessary, or the client must durably store
|
||||
// the nonce.
|
||||
//
|
||||
// If the `allow_instance_migration` property of the registered AMI is
|
||||
// enabled, then the client nonce mismatch is ignored, as long as the
|
||||
|
@ -126,8 +126,9 @@ func validateMetadata(clientNonce, pendingTime string, storedIdentity *whitelist
|
|||
}
|
||||
}
|
||||
|
||||
// ensure that the 'pendingTime' on the given identity document is not before than the
|
||||
// 'pendingTime' that was used for previous login.
|
||||
// Ensure that the 'pendingTime' on the given identity document is not before than the
|
||||
// 'pendingTime' that was used for previous login. This disallows old metadata documents
|
||||
// from being used to perform login.
|
||||
if givenPendingTime.Before(storedPendingTime) {
|
||||
return fmt.Errorf("instance meta-data is older than the one used for previous login")
|
||||
}
|
||||
|
@ -138,6 +139,7 @@ func validateMetadata(clientNonce, pendingTime string, storedIdentity *whitelist
|
|||
// signature. After verification, extracts the instance identity document from the
|
||||
// signature, parses it and returns it.
|
||||
func (b *backend) parseIdentityDocument(s logical.Storage, pkcs7B64 string) (*identityDocument, error) {
|
||||
// Insert the header and footer for the signature to be able to pem decode it.
|
||||
pkcs7B64 = fmt.Sprintf("-----BEGIN PKCS7-----\n%s\n-----END PKCS7-----", pkcs7B64)
|
||||
|
||||
// Decode the PEM encoded signature.
|
||||
|
@ -153,6 +155,8 @@ func (b *backend) parseIdentityDocument(s logical.Storage, pkcs7B64 string) (*id
|
|||
}
|
||||
|
||||
// Get the public certificate that is used to verify the signature.
|
||||
// This returns a slice of certificates containing the default certificate
|
||||
// and all the registered certificates using 'config/certificate/<cert_name>' endpoint
|
||||
publicCerts, err := b.awsPublicCertificates(s)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -186,8 +190,9 @@ func (b *backend) parseIdentityDocument(s logical.Storage, pkcs7B64 string) (*id
|
|||
}
|
||||
|
||||
// pathLoginUpdate is used to create a Vault token by the EC2 instances
|
||||
// by providing its instance identity document, pkcs7 signature of the document,
|
||||
// and a client created nonce.
|
||||
// by providing the pkcs7 signature of the instance identity document
|
||||
// and a client created nonce. Client nonce is optional if 'disallow_reauthentication'
|
||||
// option is enabled on the registered AMI.
|
||||
func (b *backend) pathLoginUpdate(
|
||||
req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
|
||||
|
||||
|
@ -206,7 +211,9 @@ func (b *backend) pathLoginUpdate(
|
|||
return logical.ErrorResponse("failed to extract instance identity document from PKCS#7 signature"), nil
|
||||
}
|
||||
|
||||
// Validate the instance ID.
|
||||
// Validate the instance ID by making a call to AWS EC2 DescribeInstances API
|
||||
// and fetching the instance description. Validation succeeds only if the
|
||||
// instance is in 'running' state.
|
||||
instanceDesc, err := b.validateInstance(req.Storage, identityDoc)
|
||||
if err != nil {
|
||||
return logical.ErrorResponse(fmt.Sprintf("failed to verify instance ID: %s", err)), nil
|
||||
|
@ -233,7 +240,8 @@ func (b *backend) pathLoginUpdate(
|
|||
if storedIdentity != nil {
|
||||
// Check if the client nonce match the cached nonce and if the pending time
|
||||
// of the identity document is not before the pending time of the document
|
||||
// with which previous login was made.
|
||||
// with which previous login was made. If 'allow_instance_migration' is
|
||||
// enabled on the registered AMI, client nonce requirement is relaxed.
|
||||
if err = validateMetadata(clientNonce, identityDoc.PendingTime, storedIdentity, imageEntry); err != nil {
|
||||
return logical.ErrorResponse(err.Error()), nil
|
||||
}
|
||||
|
@ -257,6 +265,10 @@ func (b *backend) pathLoginUpdate(
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if resp == nil {
|
||||
return logical.ErrorResponse("failed to fetch and verify the role tag"), nil
|
||||
}
|
||||
|
||||
policies = resp.Policies
|
||||
rTagMaxTTL = resp.MaxTTL
|
||||
|
||||
|
@ -267,6 +279,7 @@ func (b *backend) pathLoginUpdate(
|
|||
disallowReauthentication = resp.DisallowReauthentication
|
||||
}
|
||||
|
||||
// Scope the maxTTL to the value set on the role tag.
|
||||
if resp.MaxTTL > time.Duration(0) && resp.MaxTTL < maxTTL {
|
||||
maxTTL = resp.MaxTTL
|
||||
}
|
||||
|
@ -332,7 +345,18 @@ func (b *backend) pathLoginUpdate(
|
|||
// handleRoleTagLogin is used to fetch the role tag of the instance and verifies it to be correct.
|
||||
// Then the policies for the login request will be set off of the role tag, if certain creteria satisfies.
|
||||
func (b *backend) handleRoleTagLogin(s logical.Storage, identityDoc *identityDocument, imageEntry *awsImageEntry, instanceDesc *ec2.DescribeInstancesOutput) (*roleTagLoginResponse, error) {
|
||||
if identityDoc == nil {
|
||||
return nil, fmt.Errorf("nil identityDoc")
|
||||
}
|
||||
if imageEntry == nil {
|
||||
return nil, fmt.Errorf("nil imageEntry")
|
||||
}
|
||||
if instanceDesc == nil {
|
||||
return nil, fmt.Errorf("nil instanceDesc")
|
||||
}
|
||||
|
||||
// Input validation is not performed here considering that it would have been done
|
||||
// in validateInstance method.
|
||||
tags := instanceDesc.Reservations[0].Instances[0].Tags
|
||||
if tags == nil || len(tags) == 0 {
|
||||
return nil, fmt.Errorf("missing tag with key %s on the instance", imageEntry.RoleTag)
|
||||
|
@ -346,6 +370,8 @@ func (b *backend) handleRoleTagLogin(s logical.Storage, identityDoc *identityDoc
|
|||
}
|
||||
}
|
||||
|
||||
// If 'role_tag' is enabled on the AMI, and if a corresponding tag is not found
|
||||
// to be attached to the instance, fail.
|
||||
if rTagValue == "" {
|
||||
return nil, fmt.Errorf("missing tag with key %s on the instance", imageEntry.RoleTag)
|
||||
}
|
||||
|
@ -398,13 +424,16 @@ func (b *backend) pathLoginRenew(
|
|||
|
||||
// For now, rTagMaxTTL is cached in internal data during login and used in renewal for
|
||||
// setting the MaxTTL for the stored login identity entry.
|
||||
// If `instance_id` can be used to fetch the role tag again (through an API), it would be good.
|
||||
// For accessing the max_ttl, storing the entire identity document is too heavy.
|
||||
//
|
||||
// Ideally, the instance ID should be used to query the role tag again.
|
||||
// For now, we only make an API call during login and not during renewal.
|
||||
// If there is a need to do make an API call, this should be changed.
|
||||
rTagMaxTTL, err := time.ParseDuration(req.Auth.Metadata["role_tag_max_ttl"])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Ensure that image entry is not deleted.
|
||||
imageEntry, err := awsImage(req.Storage, storedIdentity.AmiID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -413,6 +442,7 @@ func (b *backend) pathLoginRenew(
|
|||
return logical.ErrorResponse("image entry not found"), nil
|
||||
}
|
||||
|
||||
// Re-evaluate the maxTTL bounds.
|
||||
maxTTL := b.System().MaxLeaseTTL()
|
||||
if imageEntry.MaxTTL > time.Duration(0) && imageEntry.MaxTTL < maxTTL {
|
||||
maxTTL = imageEntry.MaxTTL
|
||||
|
@ -421,7 +451,7 @@ func (b *backend) pathLoginRenew(
|
|||
maxTTL = rTagMaxTTL
|
||||
}
|
||||
|
||||
// Only LastUpdatedTime and ExpirationTime change, none else.
|
||||
// Only LastUpdatedTime and ExpirationTime change and all other fields remain the same.
|
||||
currentTime := time.Now()
|
||||
storedIdentity.LastUpdatedTime = currentTime
|
||||
storedIdentity.ExpirationTime = currentTime.Add(maxTTL)
|
||||
|
@ -453,15 +483,17 @@ Authenticates an EC2 instance with Vault.
|
|||
`
|
||||
|
||||
const pathLoginDesc = `
|
||||
An EC2 instance is authenticated using the instance identity document, the identity document's
|
||||
PKCS#7 signature and a client created nonce. This nonce should be unique and should be used by
|
||||
the instance for all future logins.
|
||||
An EC2 instance is authenticated using the PKCS#7 signature of the instance identity
|
||||
document and a client created nonce. This nonce should be unique and should be used by
|
||||
the instance for all future logins, unless 'allow_instance_migration' option on the
|
||||
registered AMI is enabled, in which case client nonce is optional.
|
||||
|
||||
First login attempt, creates a whitelist entry in Vault associating the instance to the nonce
|
||||
provided. All future logins will succeed only if the client nonce matches the nonce in the
|
||||
whitelisted entry.
|
||||
|
||||
The entries in the whitelist are not automatically deleted. Although, they will have an
|
||||
expiration time set on the entry. There is a separate endpoint 'tidy/identities',
|
||||
that needs to be invoked to clean-up all the expired entries in the whitelist.
|
||||
By default, a cron task will periodically looks for expired entries in the whitelist
|
||||
and delete them. The duration to periodically run this is one hour by default.
|
||||
However, this can be configured using the 'config/tidy/identities' endpoint. This tidy
|
||||
action can be triggered via the API as well, using the 'tidy/identities' endpoint.
|
||||
`
|
||||
|
|
|
@ -92,11 +92,7 @@ func (b *backend) pathWhitelistIdentityDelete(
|
|||
return logical.ErrorResponse("missing instance_id"), nil
|
||||
}
|
||||
|
||||
if err := req.Storage.Delete("whitelist/identity/" + instanceID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
return nil, req.Storage.Delete("whitelist/identity/" + instanceID)
|
||||
}
|
||||
|
||||
// pathWhitelistIdentityRead is used to view an entry in the identity whitelist given an instance ID.
|
||||
|
@ -140,7 +136,7 @@ Each login from an EC2 instance creates/updates an entry in the identity whiteli
|
|||
|
||||
Entries in this list can be viewed or deleted using this endpoint.
|
||||
|
||||
By default, a cron task will periodically look for expired entries in the whitelist
|
||||
By default, a cron task will periodically looks for expired entries in the whitelist
|
||||
and delete them. The duration to periodically run this is one hour by default.
|
||||
However, this can be configured using the 'config/tidy/identities' endpoint. This tidy
|
||||
action can be triggered via the API as well, using the 'tidy/identities' endpoint.
|
||||
|
|
Loading…
Reference in a new issue