From eaf8d7ff8fecc8e0d5b9d7c549ff0c969e90f2cd Mon Sep 17 00:00:00 2001 From: mo khan Date: Mon, 26 May 2025 12:02:43 -0600 Subject: docs: write notes on Envoy --- share/man/ENVOY.md | 84 +++++++++++++++++++++++++++++++++++++++++++++++++++++ share/man/README.md | 4 +++ 2 files changed, 88 insertions(+) create mode 100644 share/man/ENVOY.md create mode 100644 share/man/README.md diff --git a/share/man/ENVOY.md b/share/man/ENVOY.md new file mode 100644 index 0000000..3f610df --- /dev/null +++ b/share/man/ENVOY.md @@ -0,0 +1,84 @@ +# Envoy + +Envoy Proxy is described as an edge and service proxy. What this means is that +Envoy can take care of managing inbound and outbound networks requests to and +from your application. This allows your application to not to have to worry +about managing key material like OAuth Client secrets, JSON Web Tokens (JWTs), +and other sensitive information. + +Envoy provides a plugin system that allows application developers to use built +in plugins to handle things like: + +* Redirecting to an Identity Provider +* Doing an OAuth handshake with an OAuth Authorization Server + * Performing an Authorization Code Grant Exchange + * Exchanging a refresh token for a new access token +* Validating incoming JSON Web Tokens +* Connecting to a policy decision point to authorize request before forwarding + them to your application. + +Envoy can be run in multiple ways and seems to work best when working as a +sidecar process to your application. The idea behind this is that you would +expose envoy to the externally and use it to reverse proxy requests to your +application that is only accessible via envoy. This is typically configured +using a loopback address for tcp connections. Envoy can speak gRPC and HTTP +quite fluently and the Envoy documentation is fairly extensive. + +You can configure Envoy to receive its configuration from a static YAML file or +dynamically by giving it the location of a control plane for it to connect to +and receive its configuration from. Envoy Gateway and Istio are popular control +planes that allow you to manage a fleet of envoy proxies through a central +management point. + +In this document I'm going to go over how to configure Envoy in a standalone +mode using static configuration. This configuration is written in YAML and is +provided to the Envoy program as a command line option during startup. + +In order to adequately understand what Envoy is providing I will start with +going over the following primitives: + +1. Authentication + * Public Key Cryptography + * Public Key Infrastructure + * Digital Signing +1. Authorization + * Access Control Models + * DAC + * RBAC + * ABAC + +After this brief overview I will dive into how to configure Envoy to provide +the bare necessities for booting up a new service with authentication +and authorization delegated to Envoy. + +1. Authentication + * OpenID Connect Provider using `envoy.filters.http.oauth2` + * JSON Web Token Validation using `envoy.filters.http.jwt_authn` +1. Authorization + * External policy decision point (PDP) using `envoy.filters.http.ext_authz` + +## Pre-requisite Concepts + +Authentication is the act of prooving you are who you claim to be. +Authorization is the act of prooving that you are allowed to do what +you're trying to do. The distinction between the two is important because the +context determines which elements are necessary. + +An example of this is the difference between commuting via municipal transit +versus commuting via an airplane. The security context between the two modes of +transportation are different therefore the level or rigor applied to +authenticating versus authorizing access to the resource differ. To board a bus +you must present a bus token/ticket to the bus driver before you are able to +board the bus. The bus driver does not require you to verify who you are. +Instead, they are only interested in verifying that you have a valid bus ticket +that has not expired, is for the bus that they operate and is issued from a +legitimate authority (the transit authority). TO ride an airplane you must +provide both your passport and your boarding pass in order to board the plane. +The passport is used to verify that you are who you say you are and the boarding +pass is used to ensure that you have a valid seat on the plane. The passport is +used to authenticate the passenger and the bus ticket/boarding pass is used to +authorize the passenger. The bus and plane are protected resources like an API +and the operator of the API understand the security context the best. They +understand whether a rigorous authentication and authorization check is +warranted or not. The passenger is responsible for obtaining a passport, +boarding pass, bus ticket from trusted and reputable authorities. diff --git a/share/man/README.md b/share/man/README.md new file mode 100644 index 0000000..e74a961 --- /dev/null +++ b/share/man/README.md @@ -0,0 +1,4 @@ +# Documentation + +* [Developer Docs](./DEVELOPMENT.md) + * [Envoy](./ENVOY.md) -- cgit v1.2.3 From d27a02c53b244c84dfaacb42f03d3fc61209bb29 Mon Sep 17 00:00:00 2001 From: mo khan Date: Mon, 26 May 2025 12:14:31 -0600 Subject: docs: add a diagram of boarding a bus --- share/man/ENVOY.md | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/share/man/ENVOY.md b/share/man/ENVOY.md index 3f610df..c158f4d 100644 --- a/share/man/ENVOY.md +++ b/share/man/ENVOY.md @@ -82,3 +82,37 @@ and the operator of the API understand the security context the best. They understand whether a rigorous authentication and authorization check is warranted or not. The passenger is responsible for obtaining a passport, boarding pass, bus ticket from trusted and reputable authorities. + +``` + +-----------+ +------------+ +-----+ + | Passenger | | Bus Driver | | Bus | + +-----------+ +------------+ +-----+ + | | | + |-- request access --> | + | | | + |<- request ticket --| | + | | | + |-- present ticket --> authorize (bus #, expiration, fake/legit?) + | | | + |<--- grant access --| | + | | | + |--- board bus ---------------------------->| + +-------------------------------------------------------- + |<--- deny access --| +``` + +The Bus # indicates the canonical identifier for the resource and +this is similar to accessing a resource exposed via a REST/GraphQL +API. The expiration check ensures that the same token cannot be re-used +indefinitely and that the access granted by the ticket is limited in +scope to prevent abuse of the resource and this is similar to ensuring +that a JWT cannot be used indefinitely. The check to make sure that the +ticket is legitimate and issued from a trusted authority is similar to +a digital signature check. In this example, the bus driver does not need to +authenticate the passenger by verifying that they are who they say they are. The +bus driver does not care. The bus driver only cares about whether or not they +carry a token that awards them access to the resource. In this scenario the +passenger could give the token to someone else (for example a child) so that +they can access the resource. The security context of this resource does not +warrant the need for authentication and only requires authorization. -- cgit v1.2.3 From b95799e623511e85032c2ca4f11962e7800a9e2a Mon Sep 17 00:00:00 2001 From: mo khan Date: Mon, 26 May 2025 12:37:42 -0600 Subject: docs: add diagram for boarding a plane --- share/man/ENVOY.md | 44 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 43 insertions(+), 1 deletion(-) diff --git a/share/man/ENVOY.md b/share/man/ENVOY.md index c158f4d..c0b6d02 100644 --- a/share/man/ENVOY.md +++ b/share/man/ENVOY.md @@ -83,7 +83,7 @@ understand whether a rigorous authentication and authorization check is warranted or not. The passenger is responsible for obtaining a passport, boarding pass, bus ticket from trusted and reputable authorities. -``` +```sequence +-----------+ +------------+ +-----+ | Passenger | | Bus Driver | | Bus | +-----------+ +------------+ +-----+ @@ -116,3 +116,45 @@ carry a token that awards them access to the resource. In this scenario the passenger could give the token to someone else (for example a child) so that they can access the resource. The security context of this resource does not warrant the need for authentication and only requires authorization. + +```uml ++-----------+ +----------------+ +----------------+ +-------+ +| Passenger | | Security Agent | | Boarding Agent | | Plane | ++-----------+ +----------------+ +----------------+ +-------+ + | | | | + |-- request access to gate -->| | | + |<--- request boarding pass ---| | | + | | | | + |-- present boarding pass ---->| | | + | |-> validate pass | | + |<-- allow access to gate -----| | | + | | | | + |-- request access to board plane ----------------->| | + |<--- request passport -----------------------------| | + |-- present passport ------------------------------>| | + |<--- request boarding pass ------------------------| | + |-- present boarding pass ------------------------->| | + |<----- allow access to board plane | | + | | | | + |--- board plane ------------------------------------------------->| +``` + +To board a plane you must pass through more security checks before you can +access the airplane. That is because flying in an airplane is a high security +context that requires additional checks to ensure the safety of everyone and the +risk of allowing access to a bad actor has more severe consequences. To board +the airplane you must pass through the security checkpoint by presenting a valid +boarding pass for a flight. This check ensures that we do not allow people into +the gate that do not have a valid pass. A valid pass is one that hasn't already +been used, is for a flight that is set to take off in the future and is for a +known and registered airline. Depending on whether the flight is a domestic or +international flight the gate may require other forms of proof of access. Once +the passenger has made it to the gate they are required to provide a passport +and boarding pass to an airline agent before they are allowed to board the +aircraft. This ensures that everyone who is aboard the airplane is known ahead +of time and that known bad actors are not allowed to board the aircraft. The +airline agent performs an authentication AND authorization check. The airplane +is a metaphor for a high security context that the operators of the airplane +understand. The credit card company and each intermediate authority that was +used to ensure entry do not determine the access controls for gaining entry into +the plane. -- cgit v1.2.3 From b658136f417558b20ade9ffcb94c4f545555fa7c Mon Sep 17 00:00:00 2001 From: mo khan Date: Mon, 26 May 2025 12:59:19 -0600 Subject: docs: write up notes on PKI --- share/man/ENVOY.md | 66 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) diff --git a/share/man/ENVOY.md b/share/man/ENVOY.md index c0b6d02..3635b52 100644 --- a/share/man/ENVOY.md +++ b/share/man/ENVOY.md @@ -158,3 +158,69 @@ is a metaphor for a high security context that the operators of the airplane understand. The credit card company and each intermediate authority that was used to ensure entry do not determine the access controls for gaining entry into the plane. + +### Authentication + +Authentication is the act of verifying that an entity is who they say they are. + +How do we do this on the internet? To accomplish this we depend on public key +cryptography which is a form of asymmetic crypto. In this style of crypto each +party has a public and private key. Entities distribute their public keys while +keeping their private keys private. The interesting property of the +public/private key relationship is that messages that are encrypted by either +the public or private key can only be decrypted by the other corresponding key. + +#### Confidentiality + +So if I give you my public key then you can encrypt a message with my public key +and send that message to me. Only I can decrypt that message using my private +key. This ensures confidentiality so that the ciphertext produced can be snooped +by anyone but only the recipient can convert the ciphertext back into plaintext. + +### Authenticity + +To ensure that a message originated from the entity that claims to have sent the +message an additional signature can be appended to the message. The signature +can contain any arbitrary text but is usually a hash (e.g. SHA256) of the +original plaintext message and encrypted using the private key of the sender. +I'll explain below why a hash is used below. If the recipient has the public key of the sender +then they can decrypt the signature using the public key of the sender. If +signature can be decrypted without an error then we can trust that the message +did in fact originate from the sender. This authenticates the message. + +### Integrity + +When a recipient receives a message from a sender the recipient also needs to +verify that the message wasn't altered. If the signature of the message includes +an encrypted hash then the recipient can compute a hash of the plaintext message +and compare it with the hash in the encrypted signature. This ensures that the +message hasn't been tampered with. + +In order for us to be able to trust JSON Web Tokens we need public/private key +pairs that we can use to validate the authenticity and integrity of the token. +It is also possible to encrypt the JWT body but this isn't necessary and this is +why storing sensitive information like personally identifiable information in a +JWT claim is not recommended. + +The problem of sharing and distributing public keys is solved using Public Key +Infrastructure (PKI). PKI provides a mechanism for distributing X.509 +certificates that include metadata and the public keys for different entities. +X.509 certificates can include a digital signature from other authorities +provides a chain of trust. Each X.509 certificate stores in the CA trust store +on your computer is a self signed certificate that is considered trusted. +Intermediate certificates can be traced back to a root certificate and provides +a web of trust. i.e. I trust this JWT because it was signed by a public key that +I found in this intermediate certificate that was signed by a public key found +in this root certificate that is in my operating systems root certificate +authority trust store. Typically, organizations will operate an internal +certificate authority that can sign intermediate certificates that can be +installed in the trust store for internal services. This makes it easier to +issue internal certificates without need direct access to private keys that can +be abused by bad actors. I apologize for the weak summary of this but a cursory +knowledge of how this works is important for understanding authentication. + +When a service federates user authentication to an Identity Provider (.e.g. SAML +IdP, OIDC Provider) the transaction between the service (i.e. SAML Service +Provider, OIDC Relaying Party) depends on an exchange of public key information +ahead of time (AoT). Without this pre-prequisite, none of the downstream +assumptions about user authentication is valid. -- cgit v1.2.3 From 1880ebcc4a59adfc8f16378cc10955bbbb52ed73 Mon Sep 17 00:00:00 2001 From: mo khan Date: Mon, 26 May 2025 13:13:24 -0600 Subject: docs: add notes on authorization --- share/man/ENVOY.md | 44 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/share/man/ENVOY.md b/share/man/ENVOY.md index 3635b52..cbbf8ff 100644 --- a/share/man/ENVOY.md +++ b/share/man/ENVOY.md @@ -224,3 +224,47 @@ IdP, OIDC Provider) the transaction between the service (i.e. SAML Service Provider, OIDC Relaying Party) depends on an exchange of public key information ahead of time (AoT). Without this pre-prequisite, none of the downstream assumptions about user authentication is valid. + +The `id_token` in the OpenID Connect (OIDC) workflow represents the authentication context. +This _DOES NOT_ represent an authorization context. + +### Authorization + +Authorization is the act of verifying that a party is allowed to perform a +specific action against a resource. This is separate from Authentication because +in many cases the Resource Server providing access to the Resource does not need +to know who the party is. This creates a decoupling that allows an API to +determine just how much information it actually needs to know about the party +making the request. See the Bus vis Airplane example above for an explanation. + +OAuth was designed as a protocol for delegating authorization to an intermediate +entity so that this entity could access resources on behalf of a user without +needing full access to everything that the user has access too. By adhering to +the OAuth2 protocol flow we can ensure that requests made on behalf of end users +do not operate at the highest level of privilege available to them. We can +ensure that requests that are made on behalf of end users use the lowest level +of privilege necessary for the service (OAuth client) to perform their desired +function. + +The OAuth2 `access_token` represents the authorization context and this is +distinct from the `id_token` which represents the authentication context. The +`id_token` tells us who the currently logged in user is but it _should not_ be +used to make authorization decisions. Authorization decisions should be made +using the `access_token` because this represents the delegated authorization +access granted to the service that the token was minted for. In general, most +API's that receive a request should make authorization decisions based on the +privileges granted to the `access_token`. + +The authorization server that generates the `access_token` should only grant +just enough privileges to this token that is required by the service (OAuth +client) that this token is intended for. + +The separation of the authentication context from the authorization context is +incredibly important. This ensures that services cannot access resources based +on the full scope of access that a user has but rather the delegated authorized +access that is granted to an access token. The access token represents the +low-privilege session for a specific service. A single `id_token` can be used +across services to allow the service to know who is logged in but each service +should have its own access token for each user based on the permissions that the +service declares that it needs and the permissions that the user agrees to give +it. I need to say this again because understanding this is crucial! -- cgit v1.2.3 From 33a50904d80028fa06915987570800c5957e3167 Mon Sep 17 00:00:00 2001 From: mo khan Date: Mon, 26 May 2025 13:25:15 -0600 Subject: docs: add a sequence diagram of the envoy architecture --- share/man/ENVOY.md | 77 ++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 75 insertions(+), 2 deletions(-) diff --git a/share/man/ENVOY.md b/share/man/ENVOY.md index cbbf8ff..2eaa141 100644 --- a/share/man/ENVOY.md +++ b/share/man/ENVOY.md @@ -177,7 +177,7 @@ and send that message to me. Only I can decrypt that message using my private key. This ensures confidentiality so that the ciphertext produced can be snooped by anyone but only the recipient can convert the ciphertext back into plaintext. -### Authenticity +#### Authenticity To ensure that a message originated from the entity that claims to have sent the message an additional signature can be appended to the message. The signature @@ -188,7 +188,7 @@ then they can decrypt the signature using the public key of the sender. If signature can be decrypted without an error then we can trust that the message did in fact originate from the sender. This authenticates the message. -### Integrity +#### Integrity When a recipient receives a message from a sender the recipient also needs to verify that the message wasn't altered. If the signature of the message includes @@ -268,3 +268,76 @@ across services to allow the service to know who is logged in but each service should have its own access token for each user based on the permissions that the service declares that it needs and the permissions that the user agrees to give it. I need to say this again because understanding this is crucial! + +## Envoy Architecture + +Given all the concerns listed above this is where Envoy shines. It can be used +to take care of Authentication via an OpenID Connect transaction by slightly +abusing the built-in `envoy.filters.http.oauth2` HTTP filter. It can also be +used to validate any incoming JWTs via the `envoy.filters.http.jwt_authn` HTTP +filter. Finally, we can use the `envoy.filters.http.ext_authz` HTTP filter to +delegate authorization decisions to an external policy decision point (PDP). + +I wrote Sparkle as a proof-of-concept to model these ideas using Envoy. Before +we dive into the configuration I want to quickly go over the high level +architecture of how these pieces work together. + +```mermaid +sequenceDiagram + participant User + participant Envoy + participant authzd + participant sparkled + participant OIDC Provider + + Note over User,OIDC Provider: Initial Authentication Flow + User->>Envoy: GET /dashboard (no auth) + Envoy->>Envoy: OAuth2 filter detects no auth + Envoy->>User: Redirect to OIDC Provider + User->>OIDC Provider: Login + OIDC Provider->>User: Redirect to /callback with code + User->>Envoy: GET /callback?code=... + Envoy->>OIDC Provider: Exchange code for tokens + OIDC Provider->>Envoy: Return tokens (ID, access, refresh) + Envoy->>User: Set cookies & redirect to /dashboard + + Note over User,sparkled: Authenticated Request Flow + User->>Envoy: GET /dashboard (with cookies) + Envoy->>Envoy: Extract ID token from cookie + Envoy->>Envoy: JWT filter validates & extracts claims + Note right of Envoy: Sets headers:
x-jwt-payload
x-jwt-claim-sub + + Envoy->>authzd: Check authorization (gRPC) + Note right of authzd: Request includes:
- Method & Path
- Headers (inc. cookies)
- JWT claims + authzd->>authzd: Evaluate authorization rules + authzd->>Envoy: Return OK/Denied decision + + alt Authorization OK + Envoy->>sparkled: Forward request with JWT headers + sparkled->>sparkled: Extract user from x-jwt-claim-sub + sparkled->>User: Return dashboard content + else Authorization Denied + Envoy->>User: Return 401 Unauthorized + end + + Note over User,sparkled: Public Endpoint Flow + User->>Envoy: GET /health + Envoy->>authzd: Check authorization (gRPC) + authzd->>authzd: Recognize public endpoint + authzd->>Envoy: Return OK (no auth required) + Envoy->>sparkled: Forward request + sparkled->>User: Return health status +``` + +Request Flow Summary + +1. Initial Authentication: Unauthenticated users are redirected to the OIDC provider +1. Token Validation: Envoy validates ID tokens and extracts claims as headers +1. Authorization Check: Every request goes through `authzd` for authorization decisions +1. Application Layer: `sparkled` receives pre-authorized requests with user information in headers + +This architecture ensures that authorization decisions are made consistently at the edge before requests reach the application. + +## Envoy Configuration + +Let's dive into the envoy configuration. -- cgit v1.2.3 From 3989ad13fa19ea567e4f97ad8fc200232676b674 Mon Sep 17 00:00:00 2001 From: mo khan Date: Mon, 26 May 2025 13:50:14 -0600 Subject: docs: describe authn and jwt validation via envoy --- share/man/ENVOY.md | 109 ++++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 88 insertions(+), 21 deletions(-) diff --git a/share/man/ENVOY.md b/share/man/ENVOY.md index 2eaa141..0eca644 100644 --- a/share/man/ENVOY.md +++ b/share/man/ENVOY.md @@ -228,6 +228,8 @@ assumptions about user authentication is valid. The `id_token` in the OpenID Connect (OIDC) workflow represents the authentication context. This _DOES NOT_ represent an authorization context. +TODO:: Describe the sections of a JWT and the schema of the `id_token`. + ### Authorization Authorization is the act of verifying that a party is allowed to perform a @@ -282,15 +284,20 @@ I wrote Sparkle as a proof-of-concept to model these ideas using Envoy. Before we dive into the configuration I want to quickly go over the high level architecture of how these pieces work together. +The proposed architecture ensures that authorization decisions are made consistently at the edge before requests reach the application. + +### Authentication Flow + ```mermaid sequenceDiagram participant User - participant Envoy - participant authzd - participant sparkled + box Pink Docker Image + participant Envoy + participant authzd + participant sparkled + end participant OIDC Provider - Note over User,OIDC Provider: Initial Authentication Flow User->>Envoy: GET /dashboard (no auth) Envoy->>Envoy: OAuth2 filter detects no auth Envoy->>User: Redirect to OIDC Provider @@ -300,8 +307,52 @@ sequenceDiagram Envoy->>OIDC Provider: Exchange code for tokens OIDC Provider->>Envoy: Return tokens (ID, access, refresh) Envoy->>User: Set cookies & redirect to /dashboard +``` + +The `envoy.filters.http.oauth2` HTTP filter can be configured to detect an +unauthenticated request and intercept all inbound requests by redirecting the +user-agent to the hard-coded OAuth Authorization Server endpoints. This filter +does not support the OIDC Discovery endpoint but an Envoy Gateway +[plugin](https://gateway.envoyproxy.io/docs/tasks/security/oidc/) does. +Envoy Gateway is a control plane that is outside the scope of this document. + +```yaml + - name: envoy.filters.http.oauth2 + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.http.oauth2.v3.OAuth2 + config: + auth_scopes: + - email + - openid + - profile + authorization_endpoint: "https://gitlab.com/oauth/authorize" + credentials: + client_id: "OAUTH_CLIENT_ID" + redirect_path_matcher: + path: + exact: /callback + redirect_uri: "%REQ(x-forwarded-proto)%://%REQ(:authority)%/callback" + signout_path: + path: + exact: /signout + token_endpoint: + cluster: oidc + uri: "https://gitlab.com/oauth/token" + use_refresh_token: true +``` + +### Authorization Flow + +```mermaid +sequenceDiagram + participant User + box Pink Docker Image + participant Envoy + participant authzd + participant sparkled + end + participant OIDC Provider - Note over User,sparkled: Authenticated Request Flow User->>Envoy: GET /dashboard (with cookies) Envoy->>Envoy: Extract ID token from cookie Envoy->>Envoy: JWT filter validates & extracts claims @@ -319,24 +370,40 @@ sequenceDiagram else Authorization Denied Envoy->>User: Return 401 Unauthorized end - - Note over User,sparkled: Public Endpoint Flow - User->>Envoy: GET /health - Envoy->>authzd: Check authorization (gRPC) - authzd->>authzd: Recognize public endpoint - authzd->>Envoy: Return OK (no auth required) - Envoy->>sparkled: Forward request - sparkled->>User: Return health status ``` -Request Flow Summary - -1. Initial Authentication: Unauthenticated users are redirected to the OIDC provider -1. Token Validation: Envoy validates ID tokens and extracts claims as headers -1. Authorization Check: Every request goes through `authzd` for authorization decisions -1. Application Layer: `sparkled` receives pre-authorized requests with user information in headers - -This architecture ensures that authorization decisions are made consistently at the edge before requests reach the application. +The ID token can be validated using the `envoy.filters.http.jwt_authn` HTTP +filter. The following configuration will look for an `id_token` cookie and then +parse the value, validate it against the list of keys specified at the +`remote_jwks` uri and then it will inject a header called `x-jwt-payload` with +the valid JWT as well as the `x-jwt-claim-sub` with the body section of the JWT. + +```yaml + - name: envoy.filters.http.jwt_authn + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.http.jwt_authn.v3.JwtAuthentication + providers: + gitlab_provider: + audiences: + - OAUTH_CLIENT_ID + claim_to_headers: + - header_name: x-jwt-claim-sub + claim_name: sub + forward: true + forward_payload_header: x-jwt-payload + from_cookies: + - id_token + issuer: https://gitlab.com + remote_jwks: + http_uri: + uri: https://gitlab.com/oauth/discovery/keys + cluster: oidc + rules: + - match: + path: / + requires: + provider_name: gitlab_provider +``` ## Envoy Configuration -- cgit v1.2.3 From a0cff5d8fe103b34fcf26e943616d9546444aa96 Mon Sep 17 00:00:00 2001 From: mo khan Date: Mon, 26 May 2025 13:52:08 -0600 Subject: docs: replace pink background with grey --- share/man/ENVOY.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/share/man/ENVOY.md b/share/man/ENVOY.md index 0eca644..4e57c52 100644 --- a/share/man/ENVOY.md +++ b/share/man/ENVOY.md @@ -291,7 +291,7 @@ The proposed architecture ensures that authorization decisions are made consiste ```mermaid sequenceDiagram participant User - box Pink Docker Image + box grey Docker Image participant Envoy participant authzd participant sparkled @@ -346,7 +346,7 @@ Envoy Gateway is a control plane that is outside the scope of this document. ```mermaid sequenceDiagram participant User - box Pink Docker Image + box grey Docker Image participant Envoy participant authzd participant sparkled -- cgit v1.2.3 From 43928ae40537c3b0b57ff509c26a2cab87d20fae Mon Sep 17 00:00:00 2001 From: mo khan Date: Mon, 26 May 2025 13:58:23 -0600 Subject: docs: add static_resources section to yaml example --- share/man/ENVOY.md | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/share/man/ENVOY.md b/share/man/ENVOY.md index 4e57c52..cd84781 100644 --- a/share/man/ENVOY.md +++ b/share/man/ENVOY.md @@ -317,6 +317,14 @@ does not support the OIDC Discovery endpoint but an Envoy Gateway Envoy Gateway is a control plane that is outside the scope of this document. ```yaml +static_resources: + listeners: + - 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 + http_filters: - name: envoy.filters.http.oauth2 typed_config: "@type": type.googleapis.com/envoy.extensions.filters.http.oauth2.v3.OAuth2 @@ -328,6 +336,8 @@ Envoy Gateway is a control plane that is outside the scope of this document. authorization_endpoint: "https://gitlab.com/oauth/authorize" credentials: client_id: "OAUTH_CLIENT_ID" + cookie_names: + id_token: id_token redirect_path_matcher: path: exact: /callback @@ -336,7 +346,6 @@ Envoy Gateway is a control plane that is outside the scope of this document. path: exact: /signout token_endpoint: - cluster: oidc uri: "https://gitlab.com/oauth/token" use_refresh_token: true ``` @@ -379,6 +388,16 @@ parse the value, validate it against the list of keys specified at the the valid JWT as well as the `x-jwt-claim-sub` with the body section of the JWT. ```yaml +static_resources: + listeners: + - 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 + http_filters: + - name: envoy.filters.http.oauth2 + # ... - name: envoy.filters.http.jwt_authn typed_config: "@type": type.googleapis.com/envoy.extensions.filters.http.jwt_authn.v3.JwtAuthentication @@ -397,7 +416,6 @@ the valid JWT as well as the `x-jwt-claim-sub` with the body section of the JWT. remote_jwks: http_uri: uri: https://gitlab.com/oauth/discovery/keys - cluster: oidc rules: - match: path: / -- cgit v1.2.3 From d0e24e258b97a78d66678962553ce1d63f5e7440 Mon Sep 17 00:00:00 2001 From: mo khan Date: Mon, 26 May 2025 14:13:18 -0600 Subject: docs: add example golang code --- share/man/ENVOY.md | 84 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 84 insertions(+) diff --git a/share/man/ENVOY.md b/share/man/ENVOY.md index cd84781..716aaaa 100644 --- a/share/man/ENVOY.md +++ b/share/man/ENVOY.md @@ -386,6 +386,8 @@ filter. The following configuration will look for an `id_token` cookie and then parse the value, validate it against the list of keys specified at the `remote_jwks` uri and then it will inject a header called `x-jwt-payload` with the valid JWT as well as the `x-jwt-claim-sub` with the body section of the JWT. +This filter ensures ensures the integrity and authenticity of the detected JWT +and will immediately reject tokens that are invalid. ```yaml static_resources: @@ -423,6 +425,88 @@ static_resources: provider_name: gitlab_provider ``` +The `envoy.filters.http.ext_authz` filter can be used to forward the incoming HTTP request to an external +policy decision point that can be used to make the authorization decision. For +Sparkle the PDP is hosted as a sidecar process called `authzd` that makes the +authorization decision specifically on the contents of the HTTP request. + +```yaml + # ... + - name: envoy.filters.http.oauth2 + # ... + - name: envoy.filters.http.jwt_authn + # ... + - name: envoy.filters.http.ext_authz + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.http.ext_authz.v3.ExtAuthz + grpc_service: + envoy_grpc: + cluster_name: authzd + failure_mode_allow: false +``` + +The external authorization service must implement the [`CheckRequest` protobuf](https://github.com/envoyproxy/envoy/blob/04378898516847d1107c5b15c22ac602ff06372c/api/envoy/service/auth/v3/external_auth.proto#L35) service definition. +An example of this can be found in the Sparkle repo. Below is an example +snippet: + +```golang +package authz + +import ( + "context" + + core "github.com/envoyproxy/go-control-plane/envoy/config/core/v3" + auth "github.com/envoyproxy/go-control-plane/envoy/service/auth/v3" + types "github.com/envoyproxy/go-control-plane/envoy/type/v3" + status "google.golang.org/genproto/googleapis/rpc/status" + "google.golang.org/grpc/codes" +) + +type CheckService struct { + auth.UnimplementedAuthorizationServer +} + +func (svc *CheckService) Check(ctx context.Context, request *auth.CheckRequest) (*auth.CheckResponse, error) { + if svc.isAllowed(ctx, request) { + return svc.OK(ctx), nil + } + return svc.Denied(ctx), nil +} + +// ... + +func (svc *CheckService) OK(ctx context.Context) *auth.CheckResponse { + return &auth.CheckResponse{ + Status: &status.Status{ + Code: int32(codes.OK), + }, + HttpResponse: &auth.CheckResponse_OkResponse{ + OkResponse: &auth.OkHttpResponse{ + Headers: []*core.HeaderValueOption{}, + HeadersToRemove: []string{}, + ResponseHeadersToAdd: []*core.HeaderValueOption{}, + }, + }, + } +} + +func (svc *CheckService) Denied(ctx context.Context) *auth.CheckResponse { + return &auth.CheckResponse{ + Status: &status.Status{ + Code: int32(codes.PermissionDenied), + }, + HttpResponse: &auth.CheckResponse_DeniedResponse{ + DeniedResponse: &auth.DeniedHttpResponse{ + Status: &types.HttpStatus{ + Code: types.StatusCode_Unauthorized, + }, + Headers: []*core.HeaderValueOption{}, + }, + }, + } +} +``` + ## Envoy Configuration Let's dive into the envoy configuration. -- cgit v1.2.3 From fdb4ed884668075c146a5b470d25eac2dfe7d2e9 Mon Sep 17 00:00:00 2001 From: mo khan Date: Mon, 26 May 2025 14:16:00 -0600 Subject: docs: fix formatting in doc --- share/man/ENVOY.md | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/share/man/ENVOY.md b/share/man/ENVOY.md index 716aaaa..882fb7b 100644 --- a/share/man/ENVOY.md +++ b/share/man/ENVOY.md @@ -38,24 +38,24 @@ In order to adequately understand what Envoy is providing I will start with going over the following primitives: 1. Authentication - * Public Key Cryptography - * Public Key Infrastructure - * Digital Signing + * Public Key Cryptography + * Public Key Infrastructure + * Digital Signing 1. Authorization - * Access Control Models - * DAC - * RBAC - * ABAC + * Access Control Models + * DAC + * RBAC + * ABAC After this brief overview I will dive into how to configure Envoy to provide the bare necessities for booting up a new service with authentication and authorization delegated to Envoy. 1. Authentication - * OpenID Connect Provider using `envoy.filters.http.oauth2` - * JSON Web Token Validation using `envoy.filters.http.jwt_authn` + * OpenID Connect Provider using `envoy.filters.http.oauth2` + * JSON Web Token Validation using `envoy.filters.http.jwt_authn` 1. Authorization - * External policy decision point (PDP) using `envoy.filters.http.ext_authz` + * External policy decision point (PDP) using `envoy.filters.http.ext_authz` ## Pre-requisite Concepts -- cgit v1.2.3 From 2a102d4abbf5310d9c13f4b97509b82d3152922b Mon Sep 17 00:00:00 2001 From: mo khan Date: Mon, 26 May 2025 14:31:00 -0600 Subject: docs: describe the reverse proxy filter --- share/man/ENVOY.md | 76 ++++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 57 insertions(+), 19 deletions(-) diff --git a/share/man/ENVOY.md b/share/man/ENVOY.md index 882fb7b..64a0a12 100644 --- a/share/man/ENVOY.md +++ b/share/man/ENVOY.md @@ -286,6 +286,55 @@ architecture of how these pieces work together. The proposed architecture ensures that authorization decisions are made consistently at the edge before requests reach the application. +Envoy can be configured to host multiple listeners and each listener can be +configured to have its own pipeline of middleware to execute in the order that +the middleware is declared. Sparkle uses a single listener on all interfaces +listening for TCP traffic on port 10000 to accept all incoming HTTP traffic. +The last HTTP filter to execute is the `envoy.filter.http.router` filter that +will reverse proxy the incoming request to Sparkle. + +Below is a snippet of configuration required to setup the reverse proxy. + +```yaml +static_resources: + - name: listener_0 + address: + socket_address: + protocol: TCP + address: 0.0.0.0 + port_value: 10000 + 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 + http_filters: + - name: envoy.filters.http.router + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router + route_config: + virtual_hosts: + - name: local + domains: ["*"] + routes: + - match: + prefix: "/" + route: + cluster: sparkle + clusters: + - name: sparkle + load_assignment: + cluster_name: sparkle + endpoints: + - lb_endpoints: + - endpoint: + address: + socket_address: + address: 127.0.0.1 + port_value: 8080 +``` + + ### Authentication Flow ```mermaid @@ -317,13 +366,7 @@ does not support the OIDC Discovery endpoint but an Envoy Gateway Envoy Gateway is a control plane that is outside the scope of this document. ```yaml -static_resources: - listeners: - - 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 + # ... http_filters: - name: envoy.filters.http.oauth2 typed_config: @@ -348,6 +391,8 @@ static_resources: token_endpoint: uri: "https://gitlab.com/oauth/token" use_refresh_token: true + - name: envoy.filters.http.router + # ... ``` ### Authorization Flow @@ -390,14 +435,7 @@ This filter ensures ensures the integrity and authenticity of the detected JWT and will immediately reject tokens that are invalid. ```yaml -static_resources: - listeners: - - 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 - http_filters: + # ... - name: envoy.filters.http.oauth2 # ... - name: envoy.filters.http.jwt_authn @@ -423,6 +461,8 @@ static_resources: path: / requires: provider_name: gitlab_provider + - name: envoy.filters.http.router + # ... ``` The `envoy.filters.http.ext_authz` filter can be used to forward the incoming HTTP request to an external @@ -443,6 +483,8 @@ authorization decision specifically on the contents of the HTTP request. envoy_grpc: cluster_name: authzd failure_mode_allow: false + - name: envoy.filters.http.router + # ... ``` The external authorization service must implement the [`CheckRequest` protobuf](https://github.com/envoyproxy/envoy/blob/04378898516847d1107c5b15c22ac602ff06372c/api/envoy/service/auth/v3/external_auth.proto#L35) service definition. @@ -506,7 +548,3 @@ func (svc *CheckService) Denied(ctx context.Context) *auth.CheckResponse { } } ``` - -## Envoy Configuration - -Let's dive into the envoy configuration. -- cgit v1.2.3 From 22d964d552ccf888a46977bbf6e0158a267fc335 Mon Sep 17 00:00:00 2001 From: mo khan Date: Mon, 26 May 2025 14:35:28 -0600 Subject: docs: move golang code comments to the bottom of the example --- share/man/ENVOY.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/share/man/ENVOY.md b/share/man/ENVOY.md index 64a0a12..1bdeee2 100644 --- a/share/man/ENVOY.md +++ b/share/man/ENVOY.md @@ -515,8 +515,6 @@ func (svc *CheckService) Check(ctx context.Context, request *auth.CheckRequest) return svc.Denied(ctx), nil } -// ... - func (svc *CheckService) OK(ctx context.Context) *auth.CheckResponse { return &auth.CheckResponse{ Status: &status.Status{ @@ -546,5 +544,7 @@ func (svc *CheckService) Denied(ctx context.Context) *auth.CheckResponse { }, }, } + + // ... } ``` -- cgit v1.2.3 From 60563fbfb48a97d1fbfe495c03176aae02bd81d5 Mon Sep 17 00:00:00 2001 From: mo khan Date: Mon, 26 May 2025 14:38:59 -0600 Subject: docs: convert ascii art sequence diagram into mermaid diagrams --- share/man/ENVOY.md | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/share/man/ENVOY.md b/share/man/ENVOY.md index 1bdeee2..1f4dd51 100644 --- a/share/man/ENVOY.md +++ b/share/man/ENVOY.md @@ -102,6 +102,25 @@ boarding pass, bus ticket from trusted and reputable authorities. |<--- deny access --| ``` +```mermaid +sequenceDiagram + participant P as Passenger + participant BD as Bus Driver + participant B as Bus + + P->>BD: request access + BD->>P: request ticket + P->>BD: present ticket + Note over BD: authorize (bus #, expiration, fake/legit?) + + alt Valid ticket + BD->>P: grant access + P->>B: board bus + else Invalid ticket + BD->>P: deny access + end +``` + The Bus # indicates the canonical identifier for the resource and this is similar to accessing a resource exposed via a REST/GraphQL API. The expiration check ensures that the same token cannot be re-used @@ -139,6 +158,29 @@ warrant the need for authentication and only requires authorization. |--- board plane ------------------------------------------------->| ``` +```mermaid +sequenceDiagram + participant P as Passenger + participant SA as Security Agent + participant BA as Boarding Agent + participant Plane as Plane + + P->>SA: request access to gate + SA->>P: request boarding pass + P->>SA: present boarding pass + SA->>SA: validate pass + SA->>P: allow access to gate + + P->>BA: request access to board plane + BA->>P: request passport + P->>BA: present passport + BA->>P: request boarding pass + P->>BA: present boarding pass + BA->>P: allow access to board plane + + P->>Plane: board plane +``` + To board a plane you must pass through more security checks before you can access the airplane. That is because flying in an airplane is a high security context that requires additional checks to ensure the safety of everyone and the -- cgit v1.2.3 From e56153aec78aae824df0cafc0f9c7004ae19a746 Mon Sep 17 00:00:00 2001 From: mo khan Date: Mon, 26 May 2025 16:05:33 -0600 Subject: docs: add references section --- share/man/ENVOY.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/share/man/ENVOY.md b/share/man/ENVOY.md index 1f4dd51..dad2b1c 100644 --- a/share/man/ENVOY.md +++ b/share/man/ENVOY.md @@ -590,3 +590,19 @@ func (svc *CheckService) Denied(ctx context.Context) *auth.CheckResponse { // ... } ``` + +## References + +* [Envoy Proxy](https://www.envoyproxy.io/) +* [`envoy.filters.http.oauth2`](https://www.envoyproxy.io/docs/envoy/latest/configuration/http/http_filters/oauth2_filter.html) +* [`envoy.filters.http.jwt_authn`](https://www.envoyproxy.io/docs/envoy/latest/api-v3/extensions/filters/http/jwt_authn/v3/config.proto) +* [`envoy.filters.http.ext_authz`](https://www.envoyproxy.io/docs/envoy/latest/api-v3/extensions/filters/http/ext_authz/v3/ext_authz.proto) +* [`envoy.filters.http.router`](https://www.envoyproxy.io/docs/envoy/latest/api-v3/extensions/filters/http/router/v3/router.proto) +* [Sparkle](https://gitlab.com/gitlab-org/software-supply-chain-security/authorization/sparkled) + * https://gitlab.com/gitlab-org/software-supply-chain-security/authorization/sparkled/-/merge_requests/3 + * https://gitlab.com/gitlab-org/software-supply-chain-security/authorization/sparkled/-/merge_requests/4 + * https://gitlab.com/gitlab-org/software-supply-chain-security/authorization/sparkled/-/merge_requests/6 + * https://gitlab.com/gitlab-org/software-supply-chain-security/authorization/sparkled/-/merge_requests/7 + * https://gitlab.com/gitlab-org/software-supply-chain-security/authorization/sparkled/-/merge_requests/8 + * https://gitlab.com/gitlab-org/software-supply-chain-security/authorization/sparkled/-/merge_requests/9 + -- cgit v1.2.3 From 7cf61eed2afb8a49cfa821b0554bd171e2435eab Mon Sep 17 00:00:00 2001 From: mo khan Date: Mon, 26 May 2025 16:17:18 -0600 Subject: docs: remove ascii art text diagrams --- share/man/ENVOY.md | 55 ++++++------------------------------------------------ 1 file changed, 6 insertions(+), 49 deletions(-) diff --git a/share/man/ENVOY.md b/share/man/ENVOY.md index dad2b1c..01607ae 100644 --- a/share/man/ENVOY.md +++ b/share/man/ENVOY.md @@ -1,10 +1,10 @@ # Envoy -Envoy Proxy is described as an edge and service proxy. What this means is that -Envoy can take care of managing inbound and outbound networks requests to and -from your application. This allows your application to not to have to worry -about managing key material like OAuth Client secrets, JSON Web Tokens (JWTs), -and other sensitive information. +Envoy Proxy is described as an edge and service proxy. This means that +Envoy can take care of managing inbound and outbound networks requests +to and from your application. This allows your application to not to +have to worry about managing key material like OAuth Client secrets, +JSON Web Tokens (JWTs), and other sensitive information. Envoy provides a plugin system that allows application developers to use built in plugins to handle things like: @@ -83,25 +83,6 @@ understand whether a rigorous authentication and authorization check is warranted or not. The passenger is responsible for obtaining a passport, boarding pass, bus ticket from trusted and reputable authorities. -```sequence - +-----------+ +------------+ +-----+ - | Passenger | | Bus Driver | | Bus | - +-----------+ +------------+ +-----+ - | | | - |-- request access --> | - | | | - |<- request ticket --| | - | | | - |-- present ticket --> authorize (bus #, expiration, fake/legit?) - | | | - |<--- grant access --| | - | | | - |--- board bus ---------------------------->| - --------------------------------------------------------- - |<--- deny access --| -``` - ```mermaid sequenceDiagram participant P as Passenger @@ -136,28 +117,6 @@ passenger could give the token to someone else (for example a child) so that they can access the resource. The security context of this resource does not warrant the need for authentication and only requires authorization. -```uml -+-----------+ +----------------+ +----------------+ +-------+ -| Passenger | | Security Agent | | Boarding Agent | | Plane | -+-----------+ +----------------+ +----------------+ +-------+ - | | | | - |-- request access to gate -->| | | - |<--- request boarding pass ---| | | - | | | | - |-- present boarding pass ---->| | | - | |-> validate pass | | - |<-- allow access to gate -----| | | - | | | | - |-- request access to board plane ----------------->| | - |<--- request passport -----------------------------| | - |-- present passport ------------------------------>| | - |<--- request boarding pass ------------------------| | - |-- present boarding pass ------------------------->| | - |<----- allow access to board plane | | - | | | | - |--- board plane ------------------------------------------------->| -``` - ```mermaid sequenceDiagram participant P as Passenger @@ -168,7 +127,7 @@ sequenceDiagram P->>SA: request access to gate SA->>P: request boarding pass P->>SA: present boarding pass - SA->>SA: validate pass + SA->>SA: validate boarding pass SA->>P: allow access to gate P->>BA: request access to board plane @@ -376,7 +335,6 @@ static_resources: port_value: 8080 ``` - ### Authentication Flow ```mermaid @@ -605,4 +563,3 @@ func (svc *CheckService) Denied(ctx context.Context) *auth.CheckResponse { * https://gitlab.com/gitlab-org/software-supply-chain-security/authorization/sparkled/-/merge_requests/7 * https://gitlab.com/gitlab-org/software-supply-chain-security/authorization/sparkled/-/merge_requests/8 * https://gitlab.com/gitlab-org/software-supply-chain-security/authorization/sparkled/-/merge_requests/9 - -- cgit v1.2.3 From 87e06e544c361721cfc5747e270dac0be04e89f8 Mon Sep 17 00:00:00 2001 From: mo khan Date: Mon, 26 May 2025 16:57:03 -0600 Subject: fix: do not clear id_token cookie on error --- app/middleware/id_token.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/app/middleware/id_token.go b/app/middleware/id_token.go index 8084af0..0c1503e 100644 --- a/app/middleware/id_token.go +++ b/app/middleware/id_token.go @@ -8,7 +8,6 @@ import ( "github.com/xlgmokha/x/pkg/x" xcfg "gitlab.com/gitlab-org/software-supply-chain-security/authorization/sparkled/app/cfg" "gitlab.com/gitlab-org/software-supply-chain-security/authorization/sparkled/pkg/pls" - "gitlab.com/gitlab-org/software-supply-chain-security/authorization/sparkled/pkg/web" ) func IDToken(provider *oidc.Provider, config *oidc.Config, parsers ...TokenParser) func(http.Handler) http.Handler { @@ -22,7 +21,6 @@ func IDToken(provider *oidc.Provider, config *oidc.Config, parsers ...TokenParse if err != nil { pls.LogError(r.Context(), err) - web.ExpireCookie(w, xcfg.IDTokenCookie) } else { log.WithFields(r.Context(), log.Fields{"id_token": idToken}) next.ServeHTTP( -- cgit v1.2.3 From 162fd07f7957082b172a828e69fa8ef9f125fa18 Mon Sep 17 00:00:00 2001 From: mo khan Date: Mon, 26 May 2025 16:57:27 -0600 Subject: fix: do not provide secrets to sparkle --- bin/entrypoint.sh | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/bin/entrypoint.sh b/bin/entrypoint.sh index a770286..ab38bfa 100755 --- a/bin/entrypoint.sh +++ b/bin/entrypoint.sh @@ -8,4 +8,10 @@ cd "$(dirname "$0")/.." ./bin/envoy.sh & # launch envoy in background ./bin/authzd & # launch authzd in background -./bin/sparkled # launch sparkled in foreground + +/usr/bin/env -i - \ + APP_ENV="$APP_ENV" \ + BIND_ADDR="$BIND_ADDR" \ + OAUTH_CLIENT_ID="$OAUTH_CLIENT_ID" \ + OIDC_ISSUER="$OIDC_ISSUER" \ + ./bin/sparkled # launch sparkled in foreground -- cgit v1.2.3 From 3367610c7878707e641d068b1f889209f236e54c Mon Sep 17 00:00:00 2001 From: mo khan Date: Mon, 26 May 2025 17:00:35 -0600 Subject: docs: add notes on distribution --- share/man/ENVOY.md | 64 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/share/man/ENVOY.md b/share/man/ENVOY.md index 01607ae..3f11f9e 100644 --- a/share/man/ENVOY.md +++ b/share/man/ENVOY.md @@ -549,6 +549,70 @@ func (svc *CheckService) Denied(ctx context.Context) *auth.CheckResponse { } ``` +## Distribution + +To deploy Sparkle I used bundled envoy, sparkled and authzd inside a single +docker image. This docker image uses dumb-init to run these three services +simultaneously so that these three processes can coordinate with one another to +form a logical service. Sparkle is currently distributed via Runway and all +secrets and configuration management is handled through environment variables +that are exported into the docker container when it is booted up by Runway and +OpenBao. + +Below is the Dockerfile that is used to build and distribute the Sparkle docker +image. It uses a temporary stage to build the sparkle and authz services and +then copies the compiled artifacts into the envoy base image. The final image +bundles dumb-init, sparkled, authzd and envoy. + +```Dockerfile +# syntax=docker/dockerfile:1 +FROM golang:1.24.3 AS build +ENV CGO_ENABLED=0 +WORKDIR /app +COPY . ./ +RUN go build -o /bin/sparkled ./cmd/sparkled/main.go +RUN go build -o /bin/authzd ./cmd/authzd/main.go + +FROM envoyproxy/envoy:v1.34-latest +EXPOSE 8080 9901 10000 10003 +RUN apt-get update && apt-get install -y dumb-init && rm -rf /var/lib/apt/lists/* +WORKDIR /opt/sparkle/ +RUN mkdir -p bin etc public +COPY --from=build /bin/authzd bin/authzd +COPY --from=build /bin/sparkled bin/sparkled +COPY --from=build /app/public public +COPY etc/ etc +COPY bin/*.sh bin/ +RUN chmod +x bin/*.sh +ENTRYPOINT ["/usr/bin/dumb-init", "--"] +CMD ["/opt/sparkle/bin/entrypoint.sh"] +``` + +The entrypoint script uses dumb-init as PID 1 to forward signals to child +processes. Sparkle is started up with on a limited set of environment variables. +Environment variables such as `HMAC_SECRET` and `OAUTH_CLIENT_SECRET` are not +available to sparkle. + +```sh +#!/usr/bin/dumb-init /bin/sh +# shellcheck shell=sh +set -e + +[ -n "$DEBUG" ] && set -x + +cd "$(dirname "$0")/.." + +./bin/envoy.sh & # launch envoy in background +./bin/authzd & # launch authzd in background + +/usr/bin/env -i - \ + APP_ENV="$APP_ENV" \ + BIND_ADDR="$BIND_ADDR" \ + OAUTH_CLIENT_ID="$OAUTH_CLIENT_ID" \ + OIDC_ISSUER="$OIDC_ISSUER" \ + ./bin/sparkled # launch sparkled in foreground +``` + ## References * [Envoy Proxy](https://www.envoyproxy.io/) -- cgit v1.2.3 From d5933faef4b4b773e053a2e11bf3131dac407500 Mon Sep 17 00:00:00 2001 From: mo khan Date: Mon, 26 May 2025 17:07:14 -0600 Subject: docs: add a summary --- share/man/ENVOY.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/share/man/ENVOY.md b/share/man/ENVOY.md index 3f11f9e..1dd8953 100644 --- a/share/man/ENVOY.md +++ b/share/man/ENVOY.md @@ -613,6 +613,22 @@ cd "$(dirname "$0")/.." ./bin/sparkled # launch sparkled in foreground ``` +## Summary + +Envoy provides a lot of features out of the box making it possible for +application developers to focus on their core domain. This makes it easier to +offload complex and error prone duties such as interacting with an OIDC Provider +and managing key material like an OAuth Client Secret a non-event. By moving +these responsibilities into Envoy we reduce the opportunity for tokens to get +leaked and we ensure that we adhere to open standards while also creating safe +extension points for extending authorization decisions. Envoy's ability to +modify incoming and outgoing requests before delivery makes it possible to +remove sensitive headers and/or convert them to a canonical representation in a +single consistent way. Envoy can handle mapping Authorization headers, session +cookies, query string parameters into a single consistent interface making it +possible to reduce the need for each application to handle each +authentication/authorization strategy that GitLab as a whole supports. + ## References * [Envoy Proxy](https://www.envoyproxy.io/) -- cgit v1.2.3 From 2453cfe4d9ae1b16a0233d412aa5ef3b57585b16 Mon Sep 17 00:00:00 2001 From: mo khan Date: Mon, 26 May 2025 17:18:37 -0600 Subject: docs: fix title and add overview subheading --- share/man/ENVOY.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/share/man/ENVOY.md b/share/man/ENVOY.md index 1dd8953..bfa2f26 100644 --- a/share/man/ENVOY.md +++ b/share/man/ENVOY.md @@ -1,4 +1,6 @@ -# Envoy +# Envoy Proxy + +## Overview Envoy Proxy is described as an edge and service proxy. This means that Envoy can take care of managing inbound and outbound networks requests @@ -19,7 +21,7 @@ in plugins to handle things like: Envoy can be run in multiple ways and seems to work best when working as a sidecar process to your application. The idea behind this is that you would -expose envoy to the externally and use it to reverse proxy requests to your +expose envoy to externally and use it to reverse proxy requests to your application that is only accessible via envoy. This is typically configured using a loopback address for tcp connections. Envoy can speak gRPC and HTTP quite fluently and the Envoy documentation is fairly extensive. @@ -643,3 +645,4 @@ authentication/authorization strategy that GitLab as a whole supports. * https://gitlab.com/gitlab-org/software-supply-chain-security/authorization/sparkled/-/merge_requests/7 * https://gitlab.com/gitlab-org/software-supply-chain-security/authorization/sparkled/-/merge_requests/8 * https://gitlab.com/gitlab-org/software-supply-chain-security/authorization/sparkled/-/merge_requests/9 + -- cgit v1.2.3 From 37439a7a1ae6d857a83b9ce1892e28cd586a4391 Mon Sep 17 00:00:00 2001 From: mo khan Date: Mon, 26 May 2025 17:28:59 -0600 Subject: docs: add placeholder to model these scenarios --- share/man/ENVOY.md | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/share/man/ENVOY.md b/share/man/ENVOY.md index bfa2f26..907d53e 100644 --- a/share/man/ENVOY.md +++ b/share/man/ENVOY.md @@ -399,6 +399,38 @@ Envoy Gateway is a control plane that is outside the scope of this document. ### Authorization Flow +TODO:: model these examples from https://gitlab.com/gitlab-org/architecture/auth-architecture/design-doc/-/merge_requests/12#note_2516950269 + +Example 1: Session cookie + +1. Request with a Cookie arrives to Envoy. +1. Envoy sends the request context to a separate service. +1. Separate auth service responds with HTTP OK and a token from STS representing the authenticated principal. +1. Envoy forwards the request to GitLab with the identity token injected into a header. + +Example 2: Authorization header + +1. Request with an Authorization: Bearer token arrives to Envoy. +1. Envoy sends the token to a separate service. +1. Separate service responds with an identity token from STS. +1. Envoy forwards the request to Rails. + +Example 3: Unauthenticated + +1. Unauthenticated request arrives. +1. Envoy forwards the request to Rails without an identity token. + +Example 4: Workload Identity Federation + +1. OAuth authorization request arrives for 3rd-party integration. +1. Envoy forwards the request to the authorization server. + +Example 5: ? + +1. OAuth authorization request arrives for internal service integration. +1. Envoy forwards the request to the authorization service. +1. Envoy captures authorization grant and exchanges it for the token (current solution). + ```mermaid sequenceDiagram participant User -- cgit v1.2.3