diff options
| author | mo khan <mo@mokhan.ca> | 2025-05-26 17:33:24 -0600 |
|---|---|---|
| committer | mo khan <mo@mokhan.ca> | 2025-05-26 17:33:24 -0600 |
| commit | f774e50118697503619dd17e1dde64629bbf7f07 (patch) | |
| tree | a38af52238af2e5e17884bff5967bc6a3a46785b | |
| parent | 5de37b29ec6a60deff83b480cd9c3047df79d041 (diff) | |
| parent | 37439a7a1ae6d857a83b9ce1892e28cd586a4391 (diff) | |
Merge branch 'envoy-docs' into 'main'
Add documentation on Envoy
See merge request gitlab-org/software-supply-chain-security/authorization/sparkled!12
| -rw-r--r-- | app/middleware/id_token.go | 2 | ||||
| -rwxr-xr-x | bin/entrypoint.sh | 8 | ||||
| -rw-r--r-- | share/man/ENVOY.md | 680 | ||||
| -rw-r--r-- | share/man/README.md | 4 |
4 files changed, 691 insertions, 3 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( 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 diff --git a/share/man/ENVOY.md b/share/man/ENVOY.md new file mode 100644 index 0000000..907d53e --- /dev/null +++ b/share/man/ENVOY.md @@ -0,0 +1,680 @@ +# 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 +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 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. + +```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 +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. + +```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 boarding 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 +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. + +### 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. + +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 +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! + +## 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. + +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 +sequenceDiagram + participant User + box grey Docker Image + participant Envoy + participant authzd + participant sparkled + end + participant OIDC Provider + + 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 +``` + +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 + # ... + http_filters: + - 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" + cookie_names: + id_token: id_token + redirect_path_matcher: + path: + exact: /callback + redirect_uri: "%REQ(x-forwarded-proto)%://%REQ(:authority)%/callback" + signout_path: + path: + exact: /signout + token_endpoint: + uri: "https://gitlab.com/oauth/token" + use_refresh_token: true + - name: envoy.filters.http.router + # ... +``` + +### 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 + box grey Docker Image + participant Envoy + participant authzd + participant sparkled + end + participant OIDC Provider + + 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:<br/>x-jwt-payload<br/>x-jwt-claim-sub + + Envoy->>authzd: Check authorization (gRPC) + Note right of authzd: Request includes:<br/>- Method & Path<br/>- Headers (inc. cookies)<br/>- 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 +``` + +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. +This filter ensures ensures the integrity and authenticity of the detected JWT +and will immediately reject tokens that are invalid. + +```yaml + # ... + - 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 + 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 + rules: + - match: + 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 +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 + - 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. +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{}, + }, + }, + } + + // ... +} +``` + +## 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 +``` + +## 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/) +* [`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 + 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) |
