Authorization to establish access and authorization to keep using an established resource are not always the same control. PrivateLink endpoint-service permissions decide who may request a new interface endpoint. They do not continuously re-evaluate an endpoint that already reached Available.
An external consumer can therefore create an endpoint during an authorized period and keep the private transport after the provider removes that consumer from the allowed-principal set. AWS documents that removing an allowed principal does not affect previously accepted endpoint connections.[1]
Revoking the principal’s creation permission does not revoke its live private connection.
Technique
The reusable technique exploits stale authorization state. A durable resource is authorized at creation, then continues through its own lifecycle. Later policy changes close the creation path but do not invalidate the live resource.
flowchart LR
A[Temporary authorization]
E[Durable access resource]
R[Authorization revoked]
O[Access remains active]
A -->|creation permitted| E
E --> O
A --> R
R -. no continuous re-evaluation .-> E
class A,R awsResource
class E,O credential
Required properties
- A principal receives temporary authority to establish an access resource.
- The resource reaches a durable usable state before that authority is removed.
- The service does not continuously re-evaluate the original authorization.
- The attacker retains the resource and a usable path to the exposed application.
Implementations
| Implementation | Validation | Creation authorization | Durable state | Revocation gap | Limiting boundary |
|---|---|---|---|---|---|
| PrivateLink endpoint service | Validated | Endpoint-service allowed principals and optional provider acceptance | Consumer-owned interface endpoint in Available |
Removing an allowed principal blocks new requests but leaves accepted connections | Application authentication and provider-side connection rejection remain effective. |
No other AWS service is presented as a validated implementation. Similar creation-time authorization patterns are research leads until their resource lifetime and revocation behavior are tested.
How the implementation works
The service provider publishes a Network Load Balancer-backed endpoint service. A consumer creates an interface endpoint in its own VPC. PrivateLink creates endpoint network interfaces in the consumer subnets and carries service traffic over the AWS network.[2]
The resulting endpoint belongs to the consumer account, but the provider can see the connection through DescribeVpcEndpointConnections. That record exposes the endpoint ID, service ID, owner account, creation time, Region, and state.[3][4]
Connection states and acceptance
| Service setting | Initial endpoint state | Provider action | Usable state |
|---|---|---|---|
AcceptanceRequired=true |
PendingAcceptance |
AcceptVpcEndpointConnections |
Available after processing |
AcceptanceRequired=false |
Pending |
No acceptance API call | Available after processing |
Changing the acceptance setting controls requests to create endpoints. It does not turn the allowed-principal list into continuous authorization for endpoints that are already available.[5][2:1]
The stale connection exposes only the endpoint service, not the provider VPC as a routed network. Its value depends on what the load balancer publishes and what the application accepts. TLS, application authentication, source-aware authorization, and listener configuration remain independent controls.
Execution requirements
The endpoint service must already allow the external account, role, or user. An account principal uses the root ARN form, such as arn:aws:iam::444455556666:root, and covers all principals in that account. Role and user ARNs provide narrower grants. A wildcard grants every AWS principal permission to request a connection.[1:1][6]
The consumer principal needs:
ec2:CreateVpcEndpointin the consumer account.- Permission to use the selected VPC, subnet, and security group resources.
- The endpoint service name and a network path from the intended client to the endpoint network interfaces.
If the endpoint service requires acceptance, the provider must accept the request before the endpoint becomes available. With automatic acceptance, creation proceeds without an AcceptVpcEndpointConnections call.[7][8]
The published listener and application must permit the traffic. PrivateLink establishes transport to the service, but it does not replace the service’s own authentication or authorization.
Execution
This example uses endpoint service vpce-svc-0123456789abcdef0 in provider account 111122223333. The temporary consumer is account 444455556666.
The provider grants that account permission to create an endpoint:
aws ec2 modify-vpc-endpoint-service-permissions \
--service-id vpce-svc-0123456789abcdef0 \
--add-allowed-principals arn:aws:iam::444455556666:root \
--region us-east-1
The service name shared with the consumer is com.amazonaws.vpce.us-east-1.vpce-svc-0123456789abcdef0. A principal in the consumer account creates an interface endpoint in its own VPC:
aws ec2 create-vpc-endpoint \
--vpc-endpoint-type Interface \
--vpc-id vpc-0a111111111111111 \
--service-name com.amazonaws.vpce.us-east-1.vpce-svc-0123456789abcdef0 \
--subnet-ids subnet-0b222222222222222 \
--security-group-ids sg-0c333333333333333 \
--region us-east-1
Assume the response identifies endpoint vpce-0d444444444444444. If manual acceptance is enabled, the provider accepts it:
aws ec2 accept-vpc-endpoint-connections \
--service-id vpce-svc-0123456789abcdef0 \
--vpc-endpoint-ids vpce-0d444444444444444 \
--region us-east-1
No acceptance command occurs when AcceptanceRequired is false. In that case, the endpoint starts in Pending instead of PendingAcceptance and proceeds toward Available automatically.[2:2]
After the connection has been accepted, the provider removes the temporary account from the service permissions:
aws ec2 modify-vpc-endpoint-service-permissions \
--service-id vpce-svc-0123456789abcdef0 \
--remove-allowed-principals arn:aws:iam::444455556666:root \
--region us-east-1
The removal prevents that principal from submitting new endpoint creation requests. It does not change the previously accepted endpoint to Rejected or Deleted. Endpoint vpce-0d444444444444444 remains available until a separate connection lifecycle action changes it.[1:2][2:3]
The provider can reject a connection even after it is available. RejectVpcEndpointConnections is therefore the provider-side revocation primitive for the existing endpoint, distinct from removing the principal’s ability to create another endpoint.[9]
Resilience and failure conditions
The endpoint survives removal of its owner’s account, role, or user from the allowed-principal set because that list governs new creation. It also survives a later switch to manual acceptance. The live connection ends when the provider rejects it, the consumer deletes it, the service or backing application is removed, or application-layer authorization blocks its traffic.
| Defender action | Survives? | Why |
|---|---|---|
| Remove owner from allowed principals | Yes | Existing accepted endpoints are not re-evaluated. |
Set AcceptanceRequired=true |
Yes | The setting governs new requests, not live endpoints. |
| Reject the endpoint connection | No | Provider-side rejection changes the endpoint lifecycle state. |
| Consumer deletes the endpoint | No | The durable resource no longer exists. |
| Remove application authorization | Transport only | PrivateLink connectivity remains, but the application can deny use. |
The interface endpoint is the persistence anchor. Revocation must change that endpoint’s lifecycle or independently deny application access.
Detection
The behavioral invariant is a live endpoint whose owner is absent from the endpoint service’s currently effective allowed-principal set. A permission-removal event is only a trigger for reconciliation, not proof that access ended.
For each endpoint service $s$, collect:
- $A_s$: principals currently allowed by
DescribeVpcEndpointServicePermissions. - $C_s$: endpoint connections in
Available, including eachVpcEndpointOwner.
After expanding account grants and applicable organization scopes, an available endpoint owner with no corresponding current permission is stale authorization state. Track Pending and PendingAcceptance separately as in-flight requests. A wildcard permission matches every account and prevents this comparison from identifying stale owners.
Role- and user-scoped permissions require historical context. DescribeVpcEndpointConnections reports the endpoint owner account, not the IAM role or user that created it. An owner-account match therefore cannot prove that a particular connection corresponds to the currently allowed role. Retain the grant, creation, and approval history or an external approval ledger for that distinction.[10][4:1]
Current-state inventory
List the service’s current allowed principals:
aws ec2 describe-vpc-endpoint-service-permissions \
--service-id vpce-svc-0123456789abcdef0 \
--query 'AllowedPrincipals[].{Type:PrincipalType,Principal:Principal}' \
--region us-east-1
List available endpoint connections with their owner accounts:
aws ec2 describe-vpc-endpoint-connections \
--filters \
Name=service-id,Values=vpce-svc-0123456789abcdef0 \
Name=vpc-endpoint-state,Values=available \
--query 'VpcEndpointConnections[].{Endpoint:VpcEndpointId,Owner:VpcEndpointOwner,State:VpcEndpointState,Created:CreationTimestamp,Region:VpcEndpointRegion}' \
--region us-east-1
The AWS CLI paginates both describe operations. Reconciliation must consume all pages before calculating the difference.[3:1][10:1]
CloudTrail sequence
PrivateLink endpoint-service APIs are Amazon EC2 management events with eventSource set to ec2.amazonaws.com. CloudTrail records the provider-side permission and acceptance actions, while CreateVpcEndpoint is recorded in the consumer account that made the call.[11]
A focused removal event looks like this:
{
"eventTime": "2026-09-01T14:20:00Z",
"eventSource": "ec2.amazonaws.com",
"eventName": "ModifyVpcEndpointServicePermissions",
"awsRegion": "us-east-1",
"recipientAccountId": "111122223333",
"userIdentity": {
"type": "AssumedRole",
"arn": "arn:aws:sts::111122223333:assumed-role/NetworkAdministration/session"
},
"sourceIPAddress": "198.51.100.24",
"requestParameters": {
"serviceId": "vpce-svc-0123456789abcdef0",
"removeAllowedPrincipals": [
"arn:aws:iam::444455556666:root"
]
}
}
The high-value finding is not the removal alone. It is this event followed by an inventory result showing an endpoint owned by 444455556666 still in Available for the same service.
Detection inputs
| Event or state source | Decision-useful fields | Why it matters |
|---|---|---|
ec2.amazonaws.com ModifyVpcEndpointServicePermissions |
requestParameters.serviceId, addAllowedPrincipals, removeAllowedPrincipals, userIdentity.arn |
Defines the beginning and end of the principal’s endpoint-creation window. |
ec2.amazonaws.com CreateVpcEndpoint |
requestParameters.serviceName, vpcId, endpoint type, subnet IDs, security group IDs, responseElements.vpcEndpoint.vpcEndpointId |
Identifies the consumer-side creation. It may be unavailable to a provider monitoring an external account. |
ec2.amazonaws.com ModifyVpcEndpointServiceConfiguration |
requestParameters.serviceId, acceptanceRequired |
Identifies changes between manual and automatic acceptance. |
ec2.amazonaws.com AcceptVpcEndpointConnections |
requestParameters.serviceId, endpoint IDs, userIdentity.arn |
Records explicit provider approval when manual acceptance is enabled. |
ec2.amazonaws.com RejectVpcEndpointConnections |
requestParameters.serviceId, endpoint IDs, unsuccessful items |
Records provider-side revocation of individual connections. |
ec2.amazonaws.com DeleteVpcEndpoints |
endpoint IDs, userIdentity.accountId |
Records consumer-side deletion when that account’s telemetry is available. |
DescribeVpcEndpointServicePermissions state |
principal type and ARN | Provides the current creation allowlist. |
DescribeVpcEndpointConnections state |
VpcEndpointId, VpcEndpointOwner, VpcEndpointState, CreationTimestamp, ServiceId |
Proves whether an endpoint still exists after its owner lost permission. |
| PrivateLink endpoint-service notification | Connect, Accept, Reject, or Delete |
Gives the provider a connection lifecycle signal even when the consumer account’s CloudTrail is unavailable.[12] |
Retain eventTime, recipientAccountId, awsRegion, userIdentity.arn, sourceIPAddress, userAgent, errorCode, and errorMessage across CloudTrail records.
Correlation logic
- Build a current inventory of endpoint services, acceptance settings, allowed principals, and endpoint connections in every enabled Region.
- Normalize account, role, and user ARNs to their account IDs. Expand approved organization or OU grants before comparing them with
VpcEndpointOwner. - Alert when an
Availableendpoint owner’s account has no effective current permission for the service. Treat this as a state violation even when no recent removal event exists. TrackPendingandPendingAcceptanceseparately so removal workflows also resolve in-flight requests. - Join
ModifyVpcEndpointServicePermissionsremovals toAvailableconnections on service ID and removed principal account. Raise confidence when the endpoint creation time precedes the removal and no laterRejectVpcEndpointConnectionsor consumer deletion exists. - For manually accepted services, attach the accepting principal from
AcceptVpcEndpointConnections. For automatically accepted services, do not require an acceptance event. - Continue tracking connection state after a principal removal until every endpoint owned by that principal has reached a non-active state.
Tune against documented long-lived consumer accounts and endpoint IDs, not only the current allowed-principal list. An intentionally grandfathered endpoint is still stale relative to current creation permissions and should remain explicit in the service’s authorization record.
Hardening
Hardening must close both properties: authority to create future endpoints and the independent lifecycle of existing endpoints.
| Control | Implementation | Primitive property removed |
|---|---|---|
| Atomic revocation workflow | Treat principal removal and connection rejection as one change. Remove the principal to stop new requests, enumerate every connection owned by that account, and call RejectVpcEndpointConnections for active endpoints that should lose access. |
Closes both the creation permission and the established transport path. |
| Continuous reconciliation | Compare DescribeVpcEndpointConnections with DescribeVpcEndpointServicePermissions on a schedule and after every permission change. Include every Region and all result pages. |
Detects stale connections even when the original grant and creation events are outside log retention. |
| Manual acceptance | Keep AcceptanceRequired=true for services with external consumers and restrict ec2:AcceptVpcEndpointConnections to a dedicated provider role. |
Adds explicit provider approval, but does not revoke endpoints after approval. |
| Narrow principal grants | Prefer specific account, role, or user ARNs over *. Maintain owner-account and endpoint-ID approval records for every accepted connection. |
Reduces who can establish endpoints and makes state reconciliation actionable. |
| Provider notifications | Configure endpoint-service notifications for Connect, Accept, Reject, and Delete events to a provider-controlled SNS topic.[12:1] |
Preserves lifecycle visibility when consumer CloudTrail is outside the provider’s organization. |
| Application-layer authorization | Authenticate every service request and bind authorization to an identity independent of endpoint connectivity. Limit listeners and operations exposed through the load balancer. | Prevents an available PrivateLink connection from being equivalent to unrestricted application access. |
| IAM separation | Restrict ec2:ModifyVpcEndpointServicePermissions, ec2:ModifyVpcEndpointServiceConfiguration, ec2:AcceptVpcEndpointConnections, and ec2:RejectVpcEndpointConnections to provider network roles and scoped endpoint-service resources.[13] |
Limits who can create temporary trust, enable automatic acceptance, or preserve a connection through approval. |
| Consumer endpoint guardrails | In organization-owned consumer accounts, constrain ec2:CreateVpcEndpoint with ec2:VpceServiceName, ec2:VpceServiceOwner, VPC, subnet, and security-group conditions.[13:1] |
Prevents workloads from creating unapproved PrivateLink paths to arbitrary endpoint services. |
References
AWS PrivateLink Guide, Configure an endpoint service. ↩︎ ↩︎ ↩︎
AWS PrivateLink Guide, AWS PrivateLink concepts. ↩︎ ↩︎ ↩︎ ↩︎
Amazon EC2 API Reference, DescribeVpcEndpointConnections. ↩︎ ↩︎
Amazon EC2 API Reference, VpcEndpointConnection. ↩︎ ↩︎
Amazon EC2 API Reference, ModifyVpcEndpointServiceConfiguration. ↩︎
Amazon EC2 API Reference, ModifyVpcEndpointServicePermissions. ↩︎
Amazon EC2 API Reference, CreateVpcEndpoint. ↩︎
Amazon EC2 API Reference, AcceptVpcEndpointConnections. ↩︎
Amazon EC2 API Reference, RejectVpcEndpointConnections. ↩︎
Amazon EC2 API Reference, DescribeVpcEndpointServicePermissions. ↩︎ ↩︎
Amazon EC2 User Guide, Log Amazon EC2 API calls using AWS CloudTrail. ↩︎
AWS PrivateLink Guide, Receive alerts for endpoint service events. ↩︎ ↩︎
AWS Service Authorization Reference, Actions, resources, and condition keys for Amazon EC2. ↩︎ ↩︎