Description:
In some cases, some of Envoy users want to use a object storage like Amazon S3 as a control plane. This is mainly for that simplicity and most of Envoy configurations are almost static in that case. For example, if one wants to use only Envoy's failure recovery features, the one can deliver that configuration to Envoy instances via RDS and CDS. That CDS response can point to a DNS name to central (server-side) load balancer and it's not so dynamic. Also, RDS can response route config with timeouts and retries and they are not updated frequently in this case.
Considerations:
I don't know yet we can have well design and implementation for this feature, so this needs comments and further investigation.
Relevant Links:
@taiki45 is the issue here basically that the v2 xDS REST mappings use POST? Is that the only issue?
@mattklein123 Yes, current v2 xDS REST mappings only support POST requests. This issue is a proposal for having GET requests support along with current POST support.
OK, sure that sounds reasonable to me and should be a really simple change w/ configuration.
Can someone who understands the nuance of REST HTTP verb mapping chime in? Is it valid to use GET if we have stateful changes, my high-level understanding of this topic indicates maybe not? Would we offer a simplified version of the API without the version + ACK/NACK dance?
TBH it really doesn't make any difference, it just angers hard core REST people. The HTTP standard says that GET requests can have a body, etc. IMO we should just add a config flag to switch the method and document why it's there.
This would be really nice, we'd like to use S3 as the control plane too. One potential gotcha might be auth, which I don't think is supported by the REST client? (correct me if I'm wrong). Which means the S3 bucket needs to be publicly readable, which isn't great, especially for secrets. Unless there's a workaround?
@tekumara is this just the ability to add some extra headers with static tokens in the Envoy config for auth? That exists today for gRPC but not REST as you say. Or, do you need something more sophisticated, to deal with token lifetimes, refresh, integration with AWS SDK etc?
To @tekumara point, we need to calculate the value for the Authorization header[1] and it seems it has lifetimes as pointed by @htuch.
Do you think to let envoy somehow to get the value e.g. from file worth to have (map a value of a header entry from an external source)?
[1] The format would be something like: Authorization: <Algorithm> Credential=<Access Key ID/Scope>, SignedHeaders=<SignedHeaders>, Signature=<Signature>, there are some values to be calculated (and I think that can be done outside envoy process).
At some point you want to be as expressive as https://github.com/envoyproxy/envoy/blob/master/api/envoy/api/v2/core/grpc_service.proto#L35, including the ability to write arbitrary extension plugins for credential handling. I'm wondering if we can head towards this incrementally and what the minimum needed would be?
I missed one important point in PR description: I think changing HTTP method of xDS API requests from Envoy is not enough. As far as I know, most of object storage HTTP endpoints don't parse request body. Instead we have to encode Envoy's node identifier and/or xDS API type into HTTP request path like v1 xDS does. Something like: /v2/discovery:clusters/${cluster_name}/${node_id}. In v2 xDS API
we have more node information than cluster name and node id, so we might need other encoding mechanism of node information into request path.
For authorization, it鈥檚 good Envoy has that extensibility, but for now I think we can work-around with source-IP based restrictions (S3 has the feature) or simple HTTP proxy which handles authorization.
@htuch yes as mentioned by @dio for AWS at least, as per https://docs.aws.amazon.com/AmazonS3/latest/API/sig-v4-authenticating-requests.htm, additional authorization headers aren't static and involve generating a signature from elements of the request, using your credentials (which in the case of instance credentials, are fetched from the metadata endpoint)
@taiki45 makes a good point tho, in our case restricting S3 buckets to a specific VPC (or VPC endpoint) is a reasonable workaround.
BTW @taiki45 when using S3 with v1 xDS, did you ever run into issues with S3's eventual consistency guarantees?
@tekumara No, envoy's eventual consistency model covers S3's limitation well.
I'm planning to take this. At first, I'll make a design proposal for URL mapping and request paramter encoding.
This issue has been automatically marked as stale because it has not had activity in the last 30 days. It will be closed in the next 7 days unless it is tagged "help wanted" or other activity occurs. Thank you for your contributions.
Working on this but probably I鈥檒l take another approach.
@taiki45 thought on this other approach?
Sorry for confusing, I'm just wondering.
The another approach I mentioned is a proxy approach. That is, prepare community made proxy and users who want to convert POST to GET use the proxy.
The reason is I'm not sure we can make a well-designed feature for Envoy's standard ranther than current gRPC/POST REST design. In other words, I'm wondering this feature can be worth to be merged into mainline.
Upsides:
Downsides:
core.Node information effectively, but GET version has to use specific core.Node encoding like using only Node.cluster or etc.NOTE: for feasibility, I'm writting a proxy which translate v2 CDS/RDS POST requests to GET requests and it works including xDS version negotiation. So I think it's OK.
@taiki45 why not have Envoy itself be the proxy and configure a loopback for xDS requests? :)
This issue has been automatically marked as stale because it has not had activity in the last 30 days. It will be closed in the next 7 days unless it is tagged "help wanted" or other activity occurs. Thank you for your contributions.
Sorry for being late but I'm still working this.
This issue has been automatically marked as stale because it has not had activity in the last 30 days. It will be closed in the next 7 days unless it is tagged "help wanted" or other activity occurs. Thank you for your contributions.
This issue has been automatically closed because it has not had activity in the last 37 days. If this issue is still valid, please ping a maintainer and ask them to label it as "help wanted". Thank you for your contributions.
Oops, I'd like to comment the design proposal in a few days.
@taiki45 given the work that AWS is doing to have direct AWS signature integration, do you think this is still necessary (https://github.com/envoyproxy/envoy/pull/5580)? cc @lavignes
I think https://github.com/envoyproxy/envoy/pull/5580 is for authentication not for xDS protocol translation described in this issue. Probably, the PR implementation can help building better authentication mechanism in this issue area.
@htuch I'm so sorry for bothering you, but could you remove "no stalebot" label from this issue and add "help wanted" label so that any others can take this issue again? I can't take this issue due to situation change.
Left my design note for this:
a. To enable Envoy to get its xDS config from object storage like Amazon S3, we need some translations to embed current
REST POST request body data (node id, cluster etc..) to the HTTP request URI.
b. In this issue, authentication/authorization extension to xDS REST is out of scope. It's beecause we can handle this with object
storage source-IP restriction etc.
We have some considerations to design REST-GET xDS API.
This design note is beased on xDS POST-GET translation server implemtation: https://github.com/cookpad/itacho
Currently, REST-POST sends below information:
Request URI: /v2/discovery:clusters
Request body
{
"version_info": "...",
"node": "{...}",
"resource_names": [],
"type_url": "...",
"response_nonce": "...",
"error_detail": "{...}"
}
https://www.envoyproxy.io/docs/envoy/v1.9.0/api-v2/api/v2/discovery.proto#envoy-api-msg-discoveryrequest
In REST-GET xDS, we can't use request body in REST-GET xDS, so we have to translate and embed those information into request URI. We can extract specific information like node.cluster or it might be configurable.
Request URI: /v2/discovery/${xds_type}/${node.cluster}
e.g. /v2/discovery/clusters/user
As far as I know, envoy does not use responded xDS version. So we can send responses immidiately to envoy requests even
if the responses don't contain any updates. The downside is increase of traffic.
This version negotiation translation from gRPC to REST is not obvious in case that an envoy instance which already knows latest xDS version sends an xDS request. In that case, gRPC xDS server just waits until xDS contents will be changed, but REST xDS server can't wait because most of REST environments don't support long polling (AFAIK). Current go-control-plane implementation will response internal server error in that situation: https://github.com/envoyproxy/go-control-plane/blob/c15526a457a61287f6ab2561195577b261fcadb8/pkg/server/gateway.go#L83-L86
I think only CDS and RDS should be supported in terms of typical use cases. Other xDS like v2 SDS can be supported but I don't have a specific use case now.
Since this is still being worked out. This is my (a bit dirty) workaround:
WARNING: since it's forwarding to its own listener, the dynamic listener wil start after the 2nd config request.
node:
id: id_1
cluster: test
static_resources:
listeners:
# the following listener will:
# - rewrite a HTTP POST to a HTTP GET
# - forward traffic to some url (currently S3)
- name: config_proxy
address:
socket_address:
address: 127.0.0.1
port_value: 8000
filter_chains:
- filters:
- name: envoy.filters.network.http_connection_manager
config:
stat_prefix: config_proxy
name: local_route
virtual_hosts:
- name: config_proxy
domains: ["*"]
routes:
- match:
prefix: "/"
route:
host_rewrite: <bucket-name>.s3-<zone>.amazonaws.com
cluster: s3
http_filters:
- name: envoy.filters.http.lua
typed_config:
"@type": type.googleapis.com/envoy.config.filter.http.lua.v2.Lua
# this takes a http request object
# and performs a small transformation on it
# everything will be written to use HTTP GET
# headers prefixed with ":" are special case headers
inline_code: |
function envoy_on_request(request_handle)
request_handle:headers():replace(":method", "GET")
end
- name: envoy.router
# this is the actual listener
# it will try to get it's configuration from config_proxy
- name: reverse_proy
address:
socket_address:
address: 0.0.0.0
port_value: 10000
filter_chains:
- filters:
- name: envoy.filters.network.http_connection_manager
config:
access_log:
name: envoy.access_loggers.file
config:
path: /dev/stdout
stat_prefix: ingress_http
rds:
route_config_name: vacansoleil_dynamic_routes
config_source:
initial_fetch_timeout: 5s
api_config_source:
api_type: REST
refresh_delay:
# first request fails because it's pointing to a listener which hasn't started yet
seconds: 5
cluster_names:
- config_proxy
http_filters:
- name: envoy.router
clusters:
- name: config_proxy
connect_timeout: 0.25s
type: LOGICAL_DNS
dns_lookup_family: V4_ONLY
lb_policy: ROUND_ROBIN
hosts:
- socket_address:
address: 127.0.0.1
port_value: 8000
- name: s3
connect_timeout: 0.25s
type: LOGICAL_DNS
dns_lookup_family: V4_ONLY
lb_policy: ROUND_ROBIN
hosts:
- socket_address:
address: <bucket-name>.s3-<zone>.amazonaws.com
port_value: 443
tls_context:
sni: <bucket-name>.s3-<zone>.amazonaws.com
Most helpful comment
I'm planning to take this. At first, I'll make a design proposal for URL mapping and request paramter encoding.