diff --git a/doc/plugin_server_nodeattestor_aws_iid.md b/doc/plugin_server_nodeattestor_aws_iid.md index 5090676734..c2f83924f0 100644 --- a/doc/plugin_server_nodeattestor_aws_iid.md +++ b/doc/plugin_server_nodeattestor_aws_iid.md @@ -14,6 +14,7 @@ this plugin resolves the agent's AWS IID-based SPIFFE ID into a set of selectors | `access_key_id` | AWS access key id | Value of `AWS_ACCESS_KEY_ID` environment variable | | `secret_access_key` | AWS secret access key | Value of `AWS_SECRET_ACCESS_KEY` environment variable | | `skip_block_device` | Skip anti-tampering mechanism which checks to make sure that the underlying root volume has not been detached prior to attestation. | false | +| `disable_instance_profile_selectors` | Disables retrieving the attesting instance profile information that is used in the selectors. Useful in cases where the server cannot reach iam.amazonaws.com | false | A sample configuration: @@ -25,6 +26,12 @@ A sample configuration: } } ``` + +## Disabling Instance Profile Selectors +In cases where spire-server is running in a location with no public internet access available, setting `disable_instance_profile_selectors = true` will prevent the server from making requests to `iam.amazonaws.com`. This is needed as spire-server will fail to attest nodes as it cannot retrieve the metadata information. + +When this is enabled, `IAM Role` selector information will no longer be available for use. + ## AWS IAM Permissions The user or role identified by the configured credentials must have permissions for `ec2:DescribeInstances`. @@ -58,7 +65,7 @@ This plugin generates the following selectors related to the instance where the All of the selectors have the type `aws_iid`. -The `IAM role` selector is included in the generated set of selectors only if the instance has an IAM Instance Profile associated. +The `IAM role` selector is included in the generated set of selectors only if the instance has an IAM Instance Profile associated and `disable_instance_profile_selectors = false` ## Security Considerations The AWS Instance Identity Document, which this attestor leverages to prove node identity, is available to any process running on the node by default. As a result, it is possible for non-agent code running on a node to attest to the SPIRE Server, allowing it to obtain any workload identity that the node is authorized to run. diff --git a/pkg/server/plugin/nodeattestor/aws/iid.go b/pkg/server/plugin/nodeattestor/aws/iid.go index 2ced9c4ecf..887a817015 100644 --- a/pkg/server/plugin/nodeattestor/aws/iid.go +++ b/pkg/server/plugin/nodeattestor/aws/iid.go @@ -104,13 +104,14 @@ type IIDAttestorPlugin struct { // IIDAttestorConfig holds hcl configuration for IID attestor plugin type IIDAttestorConfig struct { - SessionConfig `hcl:",squash"` - SkipBlockDevice bool `hcl:"skip_block_device"` - LocalValidAcctIDs []string `hcl:"account_ids_for_local_validation"` - AgentPathTemplate string `hcl:"agent_path_template"` - pathTemplate *template.Template - trustDomain string - awsCaCertPublicKey *rsa.PublicKey + SessionConfig `hcl:",squash"` + SkipBlockDevice bool `hcl:"skip_block_device"` + DisableInstanceProfileSelectors bool `hcl:"disable_instance_profile_selectors"` + LocalValidAcctIDs []string `hcl:"account_ids_for_local_validation"` + AgentPathTemplate string `hcl:"agent_path_template"` + pathTemplate *template.Template + trustDomain string + awsCaCertPublicKey *rsa.PublicKey } // New creates a new IIDAttestorPlugin. @@ -400,11 +401,16 @@ func (p *IIDAttestorPlugin) resolveSelectors(parent context.Context, instancesDe } } + c, err := p.getConfig() + if err != nil { + return nil, err + } + for _, reservation := range instancesDesc.Reservations { for _, instance := range reservation.Instances { addSelectors(resolveTags(instance.Tags)) addSelectors(resolveSecurityGroups(instance.SecurityGroups)) - if instance.IamInstanceProfile != nil && instance.IamInstanceProfile.Arn != nil { + if !c.DisableInstanceProfileSelectors && instance.IamInstanceProfile != nil && instance.IamInstanceProfile.Arn != nil { instanceProfileName, err := instanceProfileNameFromArn(*instance.IamInstanceProfile.Arn) if err != nil { return nil, err diff --git a/pkg/server/plugin/nodeattestor/aws/iid_test.go b/pkg/server/plugin/nodeattestor/aws/iid_test.go index 2eaf849e6d..104e9d83be 100644 --- a/pkg/server/plugin/nodeattestor/aws/iid_test.go +++ b/pkg/server/plugin/nodeattestor/aws/iid_test.go @@ -225,15 +225,16 @@ func (s *IIDAttestorSuite) TestErrorOnNoSignature() { func (s *IIDAttestorSuite) TestClientAndIDReturns() { tests := []struct { - desc string - mockExpect func(mock *mock_aws.MockClient) - expectID string - expectSelectors []*common.Selector - expectErr string - replacementTemplate string - allowList []string - skipBlockDev bool - skipEC2Block bool + desc string + mockExpect func(mock *mock_aws.MockClient) + expectID string + expectSelectors []*common.Selector + expectErr string + replacementTemplate string + allowList []string + skipBlockDev bool + skipEC2Block bool + disableInstanceProfileSelectors bool }{ { desc: "error on call", @@ -409,6 +410,47 @@ func (s *IIDAttestorSuite) TestClientAndIDReturns() { replacementTemplate: "{{ .PluginName}}/zone1/{{ .Tags.Hostname }}", expectID: "spiffe://example.org/spire/agent/aws_iid/zone1/%3Cno%20value%3E", }, + { + desc: "success, ignore instance profile selectors", + disableInstanceProfileSelectors: true, + mockExpect: func(mock *mock_aws.MockClient) { + output := getDefaultDescribeInstancesOutput() + output.Reservations[0].Instances[0].Tags = []*ec2.Tag{ + { + Key: aws.String("Hostname"), + Value: aws.String("host1"), + }, + } + output.Reservations[0].Instances[0].SecurityGroups = []*ec2.GroupIdentifier{ + { + GroupId: aws.String("TestGroup"), + GroupName: aws.String("Test Group Name"), + }, + } + output.Reservations[0].Instances[0].IamInstanceProfile = &ec2.IamInstanceProfile{ + Arn: aws.String("arn:aws::::instance-profile/" + testProfile), + } + output.Reservations[0].Instances[0].RootDeviceType = &instanceStoreType + output.Reservations[0].Instances[0].NetworkInterfaces[0].Attachment.DeviceIndex = &zeroDeviceIndex + setAttestExpectations(mock, output, nil) + gipo := &iam.GetInstanceProfileOutput{ + InstanceProfile: &iam.InstanceProfile{ + Roles: []*iam.Role{ + {Arn: aws.String("role1")}, + {Arn: aws.String("role2")}, + }, + }, + } + setResolveSelectorsExpectations(mock, gipo) + }, + replacementTemplate: "{{ .PluginName}}/zone1/{{ .Tags.Hostname }}", + expectSelectors: []*common.Selector{ + {Type: caws.PluginName, Value: "sg:id:TestGroup"}, + {Type: caws.PluginName, Value: "sg:name:Test Group Name"}, + {Type: caws.PluginName, Value: "tag:Hostname:host1"}, + }, + expectID: "spiffe://example.org/spire/agent/aws_iid/zone1/host1", + }, } for _, tt := range tests { @@ -446,6 +488,10 @@ func (s *IIDAttestorSuite) TestClientAndIDReturns() { configStr += "\nskip_ec2_attest_calling = true" } + if tt.disableInstanceProfileSelectors { + configStr += "\ndisable_instance_profile_selectors = true" + } + _, err := s.p.Configure(context.Background(), &plugin.ConfigureRequest{ Configuration: configStr, GlobalConfig: &plugin.ConfigureRequest_GlobalConfig{TrustDomain: "example.org"},