Modern applications rely on authorizing user's access to their application. One of the best practice is to perform a OAuth2 authorization for the endpoint exposed by an application. These applications when broken down into smaller micro-services expose many endpoints as such, protecting all of them becomes a continuous and tedious job. A general pattern is to embed a OAuth2 client/library into each micro service to help protect these exposed endpoints. If these micro-services are being written in different languages, it adds additional complexity of keeping all libraries to its latest approved version and vulnerability free.
In this blog, we will look at a sidecar pattern which would help achieve the above stated drawbacks/overhead and additionally provide a uniformity among all your micro-services with respect to protecting endpoint with OAuth2 workflow. This pattern demonstrates how can you configure the application sidecar to generate and validate the minted JWT for each micro service without a single line of code change in your application in an easy maintainable fashion.
In a cloud native world, a sidecar is a well known pattern where functionalities of an application are segregated into a separate process to provide isolation and encapsulation. Important point here to note is that it is assumed that the sidecar and the application are residing in an encapsulated environment which is trusted. Thus, all the communications between an application and a sidecar are trusted.
This provides an opportunity to offload the authorization and authentication with the sidecar. The sidecar will let the incoming connection pass through the application only after a successful authorization and authentication. This pattern greatly helps a polyglot application.
For establishing the pattern, we will have an unprotected hello-world application with envoyproxy as the choice of sidecar. Envoy will be configured to protect the application.
The sidecar will be configured for:
1. Log in to the Linux machine
2. Write a hello world application which you want to protect. This can be written in your favorite language no restriction.
a. Open a new terminal.
mkdir -p /tmp/123 ; cd /tmp/123; echo "$HOSTNAME" > index.html ; python3 -m http.server --bind 127.0.0.1 8001
3. For IDA server, we are going to use Okta as our Identity server. Its very easy to setup. a. If you don’t have an Okta account, go ahead and create one. Once you have signed up, go through the following steps:
b. Make a note of the below inputs needed to setup the envoy sidecar
4. Envoyproxy binary: Pick the latest binary for your machine. Use func-e to download and install the binary.
a. Get basic understanding on envoy filters and their usage. We are going to use the below filters in this example:
b. Envoy static configuration: This is a bootstrap file used by envoy binary to load its configuration. Copy paste the content into a file and save as 'envoy.yml'.
static_resources:
secrets:
- name: token
generic_secret:
secret:
inline_string: <Your token secret here>
- name: hmac
generic_secret:
secret:
inline_string: <Your token secret here>
listeners:
- address:
socket_address:
address: 0.0.0.0
port_value: 8000
filter_chains:
- filters:
- name: envoy.filters.network.http_connection_manager
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
codec_type: AUTO
stat_prefix: ingress_http
route_config:
name: local_route
virtual_hosts:
- name: upstream
domains:
- "*"
routes:
- match:
prefix: "/"
route:
cluster: upstream-service
http_filters:
- name: envoy.filters.http.oauth2
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.oauth2.v3.OAuth2
config:
token_endpoint:
cluster: oauth
uri: "https://<server>/oauth2/default/v1/token"
timeout: 5s
authorization_endpoint: "https://<server>/oauth2/default/v1/authorize"
redirect_uri: "http://localhost:8000/callback"
redirect_path_matcher:
path:
exact: /callback
signout_path:
path:
exact: /signout
forward_bearer_token: true
credentials:
client_id: <client id>
token_secret:
name: token
hmac_secret:
name: hmac
auth_scopes:
- openid
- name: envoy.filters.http.jwt_authn
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.jwt_authn.v3.JwtAuthentication
providers:
provider1:
remote_jwks:
http_uri:
uri: "https://<server>/oauth2/default/v1/keys"
cluster: oauth
timeout: 5s
cache_duration: 600s
rules:
- match:
prefix: /
requires:
provider_name: provider1
- name: envoy.filters.http.router
typed_config: {}
clusters:
- name: upstream-service
connect_timeout: 2s
type: STRICT_DNS
lb_policy: ROUND_ROBIN
load_assignment:
cluster_name: upstream-service
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address:
address: 127.0.0.1
port_value: 8001
- name: oauth
connect_timeout: 2s
type: STRICT_DNS
lb_policy: ROUND_ROBIN
load_assignment:
cluster_name: oauth
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address:
address: <server>
port_value: 443
transport_socket:
name: envoy.transport_sockets.tls
typed_config:
"@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext
sni: <server>
c. Edit the envoy.yml and fill the missing configuration items. They are marked with <> brackets.
d. Start the envoy binary. It will start listening on localhost:8000
envoy -c envoy.yml
5. Open a browser on the VM and hit 'localhost:8000'
a. you would reach envoy proxy
b. envoy should redirect you to Okta server which will ask you to authenticate yourself. In case you are already logged in, this step will happen automatically and it will not prompt you.
c. upon successful authentication, control will be passed to the back-end application and application will display its content on the browser.
You can now try to extend the above example by
This setup can be very easily replicated in a Kubernetes platform where envoy and application container can reside in a pod.