Skip to content

Commit

Permalink
Update aws node attestor plugin to include new selectors (#3640)
Browse files Browse the repository at this point in the history
* Update aws node attestor plugin to include new selectors from the instance identity document

Signed-off-by: Guilherme Carvalho <guilhermbrsp@gmail.com>
  • Loading branch information
guilhermocc committed Dec 9, 2022
1 parent 314a6d6 commit a7a95a1
Show file tree
Hide file tree
Showing 3 changed files with 92 additions and 23 deletions.
6 changes: 5 additions & 1 deletion doc/plugin_server_nodeattestor_aws_iid.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,10 +81,14 @@ This plugin generates the following selectors related to the instance where the

| Selector | Example | Description |
|---------------------|-------------------------------------------------------|------------------------------------------------------------------|
| Availability Zone | `aws_iid:az:us-west-2b` | The Availability Zone in which the instance is running. |
| IAM role | `aws_iid:iamrole:arn:aws:iam::123456789012:role/Blog` | An IAM role within the instance profile for the instance |
| Image ID | `aws_iid:image:id:ami-5fb8c835` | The ID of the AMI used to launch the instance. |
| Instance ID | `aws_iid:instance:id:i-0b22a22eec53b9321` | The ID of the instance. |
| Instance Tag | `aws_iid:tag:name:blog` | The key (e.g. `name`) and value (e.g. `blog`) of an instance tag |
| Region | `aws_iid:region:us-west-2` | The Region in which the instance is running. |
| Security Group ID | `aws_iid:sg:id:sg-01234567` | The id of the security group the instance belongs to |
| Security Group Name | `aws_iid:sg:name:blog` | The name of the security group the instance belongs to |
| IAM role | `aws_iid:iamrole:arn:aws:iam::123456789012:role/Blog` | An IAM role within the instance profile for the instance |

All of the selectors have the type `aws_iid`.

Expand Down
31 changes: 24 additions & 7 deletions pkg/server/plugin/nodeattestor/awsiid/iid.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,15 @@ const (
// accessKeyIDVarName env var name for AWS access key ID
accessKeyIDVarName = "AWS_ACCESS_KEY_ID"
// secretAccessKeyVarName env car name for AWS secret access key
secretAccessKeyVarName = "AWS_SECRET_ACCESS_KEY" //nolint: gosec // false positive
secretAccessKeyVarName = "AWS_SECRET_ACCESS_KEY" //nolint: gosec // false positive
azSelectorPrefix = "az"
imageIDSelectorPrefix = "image:id"
instanceIDSelectorPrefix = "instance:id"
regionSelectorPrefix = "region"
sgIDSelectorPrefix = "sg:id"
sgNameSelectorPrefix = "sg:name"
tagSelectorPrefix = "tag"
iamRoleSelectorPrefix = "iamrole"
)

// BuiltIn creates a new built-in plugin
Expand Down Expand Up @@ -192,7 +200,7 @@ func (p *IIDAttestorPlugin) Attest(stream nodeattestorv1.NodeAttestor_AttestServ
return err
}

selectorValues, err := p.resolveSelectors(stream.Context(), instancesDesc, awsClient)
selectorValues, err := p.resolveSelectors(stream.Context(), instancesDesc, attestationData, awsClient)
if err != nil {
return err
}
Expand Down Expand Up @@ -352,7 +360,7 @@ func unmarshalAndValidateIdentityDocument(data []byte, pubKey *rsa.PublicKey) (i
return doc, nil
}

func (p *IIDAttestorPlugin) resolveSelectors(parent context.Context, instancesDesc *ec2.DescribeInstancesOutput, client Client) ([]string, error) {
func (p *IIDAttestorPlugin) resolveSelectors(parent context.Context, instancesDesc *ec2.DescribeInstancesOutput, iiDoc imds.InstanceIdentityDocument, client Client) ([]string, error) {
selectorSet := map[string]bool{}
addSelectors := func(values []string) {
for _, value := range values {
Expand Down Expand Up @@ -386,6 +394,8 @@ func (p *IIDAttestorPlugin) resolveSelectors(parent context.Context, instancesDe
}
}

resolveIIDocSelectors(selectorSet, iiDoc)

// build and sort selectors
selectors := []string{}
for value := range selectorSet {
Expand All @@ -396,10 +406,17 @@ func (p *IIDAttestorPlugin) resolveSelectors(parent context.Context, instancesDe
return selectors, nil
}

func resolveIIDocSelectors(selectorSet map[string]bool, iiDoc imds.InstanceIdentityDocument) {
selectorSet[fmt.Sprintf("%s:%s", imageIDSelectorPrefix, iiDoc.ImageID)] = true
selectorSet[fmt.Sprintf("%s:%s", instanceIDSelectorPrefix, iiDoc.InstanceID)] = true
selectorSet[fmt.Sprintf("%s:%s", regionSelectorPrefix, iiDoc.Region)] = true
selectorSet[fmt.Sprintf("%s:%s", azSelectorPrefix, iiDoc.AvailabilityZone)] = true
}

func resolveTags(tags []ec2types.Tag) []string {
values := make([]string, 0, len(tags))
for _, tag := range tags {
values = append(values, fmt.Sprintf("tag:%s:%s", aws.ToString(tag.Key), aws.ToString(tag.Value)))
values = append(values, fmt.Sprintf("%s:%s:%s", tagSelectorPrefix, aws.ToString(tag.Key), aws.ToString(tag.Value)))
}
return values
}
Expand All @@ -408,8 +425,8 @@ func resolveSecurityGroups(sgs []ec2types.GroupIdentifier) []string {
values := make([]string, 0, len(sgs)*2)
for _, sg := range sgs {
values = append(values,
fmt.Sprintf("sg:id:%s", aws.ToString(sg.GroupId)),
fmt.Sprintf("sg:name:%s", aws.ToString(sg.GroupName)),
fmt.Sprintf("%s:%s", sgIDSelectorPrefix, aws.ToString(sg.GroupId)),
fmt.Sprintf("%s:%s", sgNameSelectorPrefix, aws.ToString(sg.GroupName)),
)
}
return values
Expand All @@ -422,7 +439,7 @@ func resolveInstanceProfile(instanceProfile *iamtypes.InstanceProfile) []string
values := make([]string, 0, len(instanceProfile.Roles))
for _, role := range instanceProfile.Roles {
if role.Arn != nil {
values = append(values, fmt.Sprintf("iamrole:%s", aws.ToString(role.Arn)))
values = append(values, fmt.Sprintf("%s:%s", iamRoleSelectorPrefix, aws.ToString(role.Arn)))
}
}
return values
Expand Down
78 changes: 63 additions & 15 deletions pkg/server/plugin/nodeattestor/awsiid/iid_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,15 +43,17 @@ const (
)

var (
testAWSCAKey = testkey.MustRSA2048()
testInstance = "test-instance"
testAccount = "test-account"
testRegion = "test-region"
testProfile = "test-profile"
zeroDeviceIndex = int32(0)
nonzeroDeviceIndex = int32(1)
instanceStoreType = ec2types.DeviceTypeInstanceStore
ebsType = ec2types.DeviceTypeEbs
testAWSCAKey = testkey.MustRSA2048()
testInstance = "test-instance"
testAccount = "test-account"
testRegion = "test-region"
testAvailabilityZone = "test-az"
testImageID = "test-image-id"
testProfile = "test-profile"
zeroDeviceIndex = int32(0)
nonzeroDeviceIndex = int32(1)
instanceStoreType = ec2types.DeviceTypeInstanceStore
ebsType = ec2types.DeviceTypeEbs
)

func TestAttest(t *testing.T) {
Expand Down Expand Up @@ -141,6 +143,12 @@ func TestAttest(t *testing.T) {
{
name: "success with zero device index",
expectID: "spiffe://example.org/spire/agent/aws_iid/test-account/test-region/test-instance",
expectSelectors: []*common.Selector{
{Type: caws.PluginName, Value: "az:test-az"},
{Type: caws.PluginName, Value: "image:id:test-image-id"},
{Type: caws.PluginName, Value: "instance:id:test-instance"},
{Type: caws.PluginName, Value: "region:test-region"},
},
},
{
name: "success with non-zero device index when check is disabled",
Expand All @@ -149,6 +157,12 @@ func TestAttest(t *testing.T) {
output.Reservations[0].Instances[0].NetworkInterfaces[0].Attachment.DeviceIndex = &nonzeroDeviceIndex
},
expectID: "spiffe://example.org/spire/agent/aws_iid/test-account/test-region/test-instance",
expectSelectors: []*common.Selector{
{Type: caws.PluginName, Value: "az:test-az"},
{Type: caws.PluginName, Value: "image:id:test-image-id"},
{Type: caws.PluginName, Value: "instance:id:test-instance"},
{Type: caws.PluginName, Value: "region:test-region"},
},
},
{
name: "success with non-zero device index when local account is allow-listed",
Expand All @@ -157,6 +171,12 @@ func TestAttest(t *testing.T) {
output.Reservations[0].Instances[0].NetworkInterfaces[0].Attachment.DeviceIndex = &nonzeroDeviceIndex
},
expectID: "spiffe://example.org/spire/agent/aws_iid/test-account/test-region/test-instance",
expectSelectors: []*common.Selector{
{Type: caws.PluginName, Value: "az:test-az"},
{Type: caws.PluginName, Value: "image:id:test-image-id"},
{Type: caws.PluginName, Value: "instance:id:test-instance"},
{Type: caws.PluginName, Value: "region:test-region"},
},
},
{
name: "block device anti-tampering check rejects non-zero network device index",
Expand Down Expand Up @@ -215,11 +235,23 @@ func TestAttest(t *testing.T) {
output.Reservations[0].Instances[0].NetworkInterfaces[0].Attachment.AttachTime = aws.Time(interfaceAttachTime)
},
expectID: "spiffe://example.org/spire/agent/aws_iid/test-account/test-region/test-instance",
expectSelectors: []*common.Selector{
{Type: caws.PluginName, Value: "az:test-az"},
{Type: caws.PluginName, Value: "image:id:test-image-id"},
{Type: caws.PluginName, Value: "instance:id:test-instance"},
{Type: caws.PluginName, Value: "region:test-region"},
},
},
{
name: "success with agent_path_template",
config: `agent_path_template = "/{{ .PluginName }}/custom/{{ .AccountID }}/{{ .Region }}/{{ .InstanceID }}"`,
expectID: "spiffe://example.org/spire/agent/aws_iid/custom/test-account/test-region/test-instance",
expectSelectors: []*common.Selector{
{Type: caws.PluginName, Value: "az:test-az"},
{Type: caws.PluginName, Value: "image:id:test-image-id"},
{Type: caws.PluginName, Value: "instance:id:test-instance"},
{Type: caws.PluginName, Value: "region:test-region"},
},
},
{
name: "success with tags in template",
Expand All @@ -231,9 +263,15 @@ func TestAttest(t *testing.T) {
},
}
},
config: `agent_path_template = "/{{ .PluginName }}/zone1/{{ .Tags.Hostname }}"`,
expectID: "spiffe://example.org/spire/agent/aws_iid/zone1/host1",
expectSelectors: []*common.Selector{{Type: "aws_iid", Value: "tag:Hostname:host1"}},
config: `agent_path_template = "/{{ .PluginName }}/zone1/{{ .Tags.Hostname }}"`,
expectID: "spiffe://example.org/spire/agent/aws_iid/zone1/host1",
expectSelectors: []*common.Selector{
{Type: caws.PluginName, Value: "az:test-az"},
{Type: caws.PluginName, Value: "image:id:test-image-id"},
{Type: caws.PluginName, Value: "instance:id:test-instance"},
{Type: caws.PluginName, Value: "region:test-region"},
{Type: caws.PluginName, Value: "tag:Hostname:host1"},
},
},
{
name: "fails with missing tags in template",
Expand Down Expand Up @@ -270,8 +308,12 @@ func TestAttest(t *testing.T) {
},
expectID: "spiffe://example.org/spire/agent/aws_iid/test-account/test-region/test-instance",
expectSelectors: []*common.Selector{
{Type: caws.PluginName, Value: "az:test-az"},
{Type: caws.PluginName, Value: "iamrole:role1"},
{Type: caws.PluginName, Value: "iamrole:role2"},
{Type: caws.PluginName, Value: "image:id:test-image-id"},
{Type: caws.PluginName, Value: "instance:id:test-instance"},
{Type: caws.PluginName, Value: "region:test-region"},
{Type: caws.PluginName, Value: "sg:id:TestGroup"},
{Type: caws.PluginName, Value: "sg:name:Test Group Name"},
{Type: caws.PluginName, Value: "tag:Hostname:host1"},
Expand Down Expand Up @@ -307,6 +349,10 @@ func TestAttest(t *testing.T) {
},
expectID: "spiffe://example.org/spire/agent/aws_iid/test-account/test-region/test-instance",
expectSelectors: []*common.Selector{
{Type: caws.PluginName, Value: "az:test-az"},
{Type: caws.PluginName, Value: "image:id:test-image-id"},
{Type: caws.PluginName, Value: "instance:id:test-instance"},
{Type: caws.PluginName, Value: "region:test-region"},
{Type: caws.PluginName, Value: "sg:id:TestGroup"},
{Type: caws.PluginName, Value: "sg:name:Test Group Name"},
{Type: caws.PluginName, Value: "tag:Hostname:host1"},
Expand Down Expand Up @@ -525,9 +571,11 @@ func (c *fakeClient) GetInstanceProfile(ctx context.Context, input *iam.GetInsta
func buildAttestationData(t *testing.T) caws.IIDAttestationData {
// doc body
doc := imds.InstanceIdentityDocument{
AccountID: testAccount,
InstanceID: testInstance,
Region: testRegion,
AccountID: testAccount,
InstanceID: testInstance,
Region: testRegion,
AvailabilityZone: testAvailabilityZone,
ImageID: testImageID,
}
docBytes, err := json.Marshal(doc)
require.NoError(t, err)
Expand Down

0 comments on commit a7a95a1

Please sign in to comment.