Skip to content

Increase Envoy dataplane efficiency by adding egress policy cache - #2200

Open
yanavlasov wants to merge 23 commits into
agent-substrate:mainfrom
yanavlasov:egress-policy-cache
Open

yanavlasov wants to merge 23 commits into
agent-substrate:mainfrom
yanavlasov:egress-policy-cache

Conversation

@yanavlasov

@yanavlasov yanavlasov commented Oct 5, 2026 •

Copy link
Copy Markdown
Collaborator

This PR implements egress policy cache in Envoy dataplane at CONNECT termination. It reduces number of callouts for policy retrievals from the same actor to the same destination port.

Cache for egress policy enforcement on the inner connection is in the followup PR.


  • Tests pass
  • Appropriate changes to documentation are included in the PR

Signed-off-by: Yan Avlasov <yavlasov@google.com>
Signed-off-by: Yan Avlasov <yavlasov@google.com>
Signed-off-by: Yan Avlasov <yavlasov@google.com>
Signed-off-by: Yan Avlasov <yavlasov@google.com>
Signed-off-by: Yan Avlasov <yavlasov@google.com>
Signed-off-by: Yan Avlasov <yavlasov@google.com>
Signed-off-by: Yan Avlasov <yavlasov@google.com>
Signed-off-by: Yan Avlasov <yavlasov@google.com>
Signed-off-by: Yan Avlasov <yavlasov@google.com>
Signed-off-by: Yan Avlasov <yavlasov@google.com>
Signed-off-by: Yan Avlasov <yavlasov@google.com>
Signed-off-by: Yan Avlasov <yavlasov@google.com>
Signed-off-by: Yan Avlasov <yavlasov@google.com>
~/.cargo/registry/cache
~/.cargo/git/db
cmd/dataplane/envoy/dynamic-modules/*/target
key: cargo-${{ runner.os }}-${{ hashFiles('cmd/dataplane/envoy/dynamic-modules/*/Cargo.lock') }}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just curious - why we moved it a folder?

@yanavlasov yanavlasov Oct 6, 2026 •

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Each extension is in its own directory. Top level folder contains a workspace cargo files with the dynamic modules we build for Substrate. The existing egress-policy dynamic module did not move.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thx yan!

// See the License for the specific language governing permissions and
// limitations under the License.

//! The egress-policy-cache HTTP filter dynamic module.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why "!" at the beginning? is it a rust thing?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, it is for doc generator if we ever need it. Acts as a title.

Comment on lines +39 to +40
// TODO(yanavlasov): this is a temporary workaround of the Rust dynamic module API
// limitation that does not allow storing filter state shared with upstream.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not sure I understand this, what is the workaround? in other words - what would be the ideal thing that we can not do?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated the comment

An Envoy dynamic module, written in Rust, that runs as an HTTP filter on the
egress gateway's outer `CONNECT` listener and caches egress policy SNI rules
per actor certificate in a thread-local LRU cache so repeat `CONNECT` requests
from the same actor can bypass the `ext_proc` sidecar.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can bypass the ext_proc sidecar

I think ext-proc side car is doing more things today (like simple authn), do we need that? Or we are saying if the egress policy is cached authn already happened? If yes, is that secure?

I am not sure we need that authn but just checking

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've updated readme with clarifications. Yes policy is cached after actor and destination port were authorized.

per actor certificate in a thread-local LRU cache so repeat `CONNECT` requests
from the same actor can bypass the `ext_proc` sidecar.

## How it works

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

shuold we store only what we care about for that stage of the module vs caching the whole egress policy? e.g a small list of https_hostnames (meaning you mitm those) and tls_hostnames (meaning you passthrough). We dont need all cred injection here etc,

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right now policy is only SNIs and hostnames for the destination port. However I will add key injection as I will implement timed caching and local enforcement of inner policies, including key injection, in the followup. This should make the inner part more efficient as well and avoid callout per request.

keyed by `<peer_cert_digest>;<destination_port>`, where `<peer_cert_digest>` is
the downstream peer certificate's SHA-256 digest
(`connection.sha256_peer_certificate_digest`) and `<destination_port>` is the
destination port extracted from the `:authority` request header.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

extracted from the :authority request header.

Better to take it from the actual port, not the authority header? See #2228

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comes from CONNECT authority, so it is the actual port. There is no other place for it to come from, since the cache is before ext_proc

(`connection.sha256_peer_certificate_digest`) and `<destination_port>` is the
destination port extracted from the `:authority` request header.

### Request path (`on_request_headers`)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

isnt this section too detailed? can we just replace with a visual?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've edited it a bit. It seems like details are ok.

`shared_with_upstream: ONCE`, making it available to the inner listener's
`egress-policy` listener filter.

### Response path (`on_response_headers`)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same comment. too detailed and easy to get stale

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shortened it a bit.

Comment thread demos/egress/main.go
Comment on lines +137 to +140
if input.DisableKeepAlive {
outbound.Close = true
client.CloseIdleConnections()
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

reason?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've added a comment for this value.

Comment on lines +207 to +209
safe_regex:
google_re2: {}
regex: ".*"

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what is the match for? i.e if this key exists in filter state, skip ext proc? why regex? cant we string match for dev.ate.policy.egress.cached ?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've added a dedicated filter state value to match on to avoid regex here.

Signed-off-by: Yan Avlasov <yavlasov@google.com>
Signed-off-by: Yan Avlasov <yavlasov@google.com>
Signed-off-by: Yan Avlasov <yavlasov@google.com>
Signed-off-by: Yan Avlasov <yavlasov@google.com>
Signed-off-by: Yan Avlasov <yavlasov@google.com>
Signed-off-by: Yan Avlasov <yavlasov@google.com>
Signed-off-by: Yan Avlasov <yavlasov@google.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants