Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .changeset/shaggy-wolves-unite.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
"setup-gap": minor
---

- fix: (action) add --project parameter for docker compose commands
- fix: (envoy config) websocket service routing with explicit port
- fix: (authz + envoy config) host header port rewrites
- fix: (authz) logging level environment variable name
- feat: (authz) better header debug logging
9 changes: 5 additions & 4 deletions actions/setup-gap/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -321,14 +321,15 @@ runs:
PROXY_PORT: ${{ inputs.proxy-port }}
WEBSOCKETS_PROXY_PORT: ${{ inputs.websockets-proxy-port }}
WEBSOCKETS_SERVICES: ${{ inputs.websockets-services }}
ENVOY_SERVICE_NAME: ${{ inputs.gap-name }}-envoy
AUTH_SERVICE_NAME: ${{ inputs.gap-name }}-authz
AUTH_SERVICE_PORT: ${{ inputs.auth-service-port }}
PATH_CERTS_DIR: ${{ env.PATH_CERTS_DIR }}
DEBUG_MODE: ${{ steps.enable-debug.outputs.debug-mode }}
REQUIRED_ENV_VARS: >-
WEBSOCKETS_PROXY_PORT DYNAMIC_PROXY_PORT PROXY_PORT
GAP_NAME WEBSOCKETS_PROXY_PORT DYNAMIC_PROXY_PORT PROXY_PORT
K8S_API_ENDPOINT_PORT MAIN_DNS_ZONE ENVOY_PROXY_IMAGE PROXY_LOG_LEVEL
AUTH_LOG_LEVEL AUTH_SERVICE_NAME AUTH_SERVICE_PORT
AUTH_LOG_LEVEL ENVOY_SERVICE_NAME AUTH_SERVICE_NAME AUTH_SERVICE_PORT
ACTIONS_ID_TOKEN_REQUEST_TOKEN ACTIONS_ID_TOKEN_REQUEST_URL
GITHUB_REPOSITORY GITHUB_OIDC_TOKEN_HEADER_NAME GITHUB_OIDC_HOSTNAME
run: |
Expand Down Expand Up @@ -374,11 +375,11 @@ runs:

if [[ "$DEBUG_MODE" == "true" ]]; then
echo "Docker compose configuration:"
docker compose -f "${GITHUB_ACTION_PATH}/docker-compose.yml" config
docker compose -p ${GAP_NAME} -f "${GITHUB_ACTION_PATH}/docker-compose.yml" config
fi

echo "Starting the auth service and Envoy proxy..."
docker compose -f "${GITHUB_ACTION_PATH}/docker-compose.yml" up -d
docker compose -p ${GAP_NAME} -f "${GITHUB_ACTION_PATH}/docker-compose.yml" up -d

- name: Verify Envoy Proxy
shell: bash
Expand Down
63 changes: 39 additions & 24 deletions actions/setup-gap/authz/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ type OIDCResponse struct {
Value string `json:"value"`
}

// https://www.envoyproxy.io/docs/envoy/latest/api-v3/service/auth/v3/external_auth.proto#service-auth-v3-okhttpresponse
type AuthResponse struct {
Status struct {
Code int `json:"code"`
Expand Down Expand Up @@ -165,6 +166,7 @@ func fetchGitHubOIDCToken() (string, int64, error) {
func ensurePort443(authority string) string {
// If MainDNSZone is not in the authority, no need to process
if !strings.Contains(authority, config.MainDNSZone) {
logDebug("Authority does not contain MainDNSZone (%s), no changes made: %s", config.MainDNSZone, sanitizeStr(authority))
return authority
}

Expand All @@ -184,20 +186,33 @@ func sanitizeStr(value string) string {
return sanitizedValue
}

func addHeader(w http.ResponseWriter, headerName, headerValue string, logValue bool) {
func addHeader(w http.ResponseWriter, authResp AuthResponse, headerName, headerValue string, logValue bool) {
if logValue {
logDebug("Adding header: %s=%s", headerName, sanitizeStr(headerValue))
logDebug(" Adding header: %s=%s", headerName, sanitizeStr(headerValue))
} else {
logDebug("Adding header: %s", headerName)
logDebug(" Adding header: %s", headerName)
}

// Set both the header in the HTTP response and the HTTP Body
w.Header().Set(headerName, headerValue)
authResp.HttpResponse.Headers[headerName] = headerValue
}

// handleCheck processes auth requests from Envoy
func handleCheck(w http.ResponseWriter, r *http.Request) {
logDefault("Check: %s %s %s", r.Method, sanitizeStr(r.URL.Path), sanitizeStr(r.UserAgent()))

for name, values := range r.Header {
for _, value := range values {
logDebug(" Header: %s=%s", name, sanitizeStr(value))
}
}
logDebug(" Header: :method=%s", r.Method)
logDebug(" Header: :path=%s", sanitizeStr(r.URL.Path))
if host := r.Host; host != "" {
logDebug(" Header: host=%s", sanitizeStr(host))
}

// Fetch or refresh the token
token, _, err := fetchGitHubOIDCToken()
if err != nil {
Expand All @@ -207,31 +222,37 @@ func handleCheck(w http.ResponseWriter, r *http.Request) {
return
}

// Prepare the response
// Get the authority header - this is typically the host header, but we check :authority first
authority := r.Header.Get(":authority")
if authority == "" {
authority = r.Host
logDefault("No :authority header found, using host header (%s)", sanitizeStr(authority))
}

// Always modify the authority to ensure port 443
if authority != "" {
authority = ensurePort443(authority)
logDefault("Setting upstream authority to: %s", sanitizeStr(authority))
} else {
logDefault("Warning: No authority found in request")
}

authResp := AuthResponse{}
authResp.Status.Code = 200
authResp.HttpResponse.Headers = make(map[string]string)

// Add the JWT token header
headerName := config.GithubOidcTokenHeaderName
addHeader(w, headerName, "Bearer "+token, false)

githubRepository := config.GithubRepository
addHeader(w, "x-repository", githubRepository, true)
addHeader(w, authResp, config.GithubOidcTokenHeaderName, "Bearer "+token, false)
addHeader(w, authResp, "x-repository", config.GithubRepository, true)

// Check and modify the authority header if needed
authority := r.Header.Get(":authority")
if authority != "" {
modifiedAuthority := ensurePort443(authority)
addHeader(w, ":authority", modifiedAuthority, true)
addHeader(w, authResp, ":authority", authority, true)
addHeader(w, authResp, "host", authority, true)
}

// Send the response
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(authResp)
}

// handleHealthz is a simple health check endpoint
func handleHealthz(w http.ResponseWriter, r *http.Request) {
logDebug("Health check request: %s", sanitizeStr(r.URL.Path))
fmt.Fprint(w, "OK")
Expand All @@ -243,8 +264,6 @@ func handleNotFound(w http.ResponseWriter, r *http.Request) {
http.Error(w, "Not Found", http.StatusNotFound)
}

// loadConfig loads all environment variables into the config struct
// Returns an error if any required environment variables are missing
func loadConfig() error {
var missingVars []string

Expand Down Expand Up @@ -283,7 +302,7 @@ func loadConfig() error {
missingVars = append(missingVars, "AUTH_SERVICE_PORT")
}

// Set log level from environment variable, default to LogLevelInfo
// Set log level, default to LogLevelDefault
logLevelStr := os.Getenv("LOG_LEVEL")
if logLevelStr != "" {
switch strings.ToLower(logLevelStr) {
Expand Down Expand Up @@ -318,17 +337,13 @@ func main() {
logDefault("Initial token fetch failed: %v", err)
}

// Set up HTTP server with custom mux
mux := http.NewServeMux()

// Register /check and /check/* endpoints
mux.HandleFunc("/check/", handleCheck)
mux.HandleFunc("/check", handleCheck)

mux.HandleFunc("/healthz", handleHealthz)

// Set up a catch-all handler for any other paths
mux.HandleFunc("/", handleNotFound)
mux.HandleFunc("/", handleNotFound) // Catch-all for all other paths

// Listen on all interfaces - required when containerized
address := fmt.Sprintf("0.0.0.0:%s", config.AuthServicePort)
Expand Down
4 changes: 2 additions & 2 deletions actions/setup-gap/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ services:
- ACTIONS_ID_TOKEN_REQUEST_URL=${ACTIONS_ID_TOKEN_REQUEST_URL}
- ACTIONS_ID_TOKEN_REQUEST_TOKEN=${ACTIONS_ID_TOKEN_REQUEST_TOKEN}
- GITHUB_REPOSITORY=${GITHUB_REPOSITORY}
- AUTH_LOG_LEVEL=${AUTH_LOG_LEVEL}
- LOG_LEVEL=${AUTH_LOG_LEVEL}
healthcheck:
test:
[
Expand All @@ -31,7 +31,7 @@ services:

envoy:
image: ${ENVOY_PROXY_IMAGE}
container_name: ${GAP_NAME}
container_name: ${ENVOY_SERVICE_NAME}
dns:
- 8.8.8.8
- 8.8.4.4
Expand Down
10 changes: 4 additions & 6 deletions actions/setup-gap/envoy.yaml.gotmpl
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ static_resources:
route_config:
virtual_hosts:
- name: dynamic_forward_host
domains: ["*.{{ getenv "MAIN_DNS_ZONE" }}"]
domains: ["*.{{ getenv "MAIN_DNS_ZONE" }}", "*.{{ getenv "MAIN_DNS_ZONE" }}:{{ getenv "DYNAMIC_PROXY_PORT" }}"]
routes:
- match:
prefix: /
Expand Down Expand Up @@ -170,9 +170,7 @@ static_resources:
- exact: "x-repository"
- exact: "{{ getenv "GITHUB_OIDC_TOKEN_HEADER_NAME" }}"
- exact: ":authority"
allowed_headers:
patterns:
- exact: ":authority"
- exact: "host"
failure_mode_allow: false
- name: envoy.filters.http.dynamic_forward_proxy
typed_config:
Expand Down Expand Up @@ -255,7 +253,7 @@ static_resources:
name: local_route
virtual_hosts:
- name: gap_ws_echo
domains: ["gap-ws-echo.{{ getenv "MAIN_DNS_ZONE" }}*"]
domains: ["gap-ws-echo.{{ getenv "MAIN_DNS_ZONE" }}", "gap-ws-echo.{{ getenv "MAIN_DNS_ZONE" }}:{{ getenv "WEBSOCKETS_PROXY_PORT" }}"]
routes:
- match:
prefix: "/"
Expand All @@ -281,7 +279,7 @@ static_resources:
{{- $services := getenv "WEBSOCKETS_SERVICES" | strings.Split "," }}
{{- range $services }}
- name: {{ replaceAll "-" "_" . }}
domains: ["{{ . }}.{{ getenv "MAIN_DNS_ZONE" }}*"]
domains: ["{{ . }}.{{ getenv "MAIN_DNS_ZONE" }}", "{{ . }}.{{ getenv "MAIN_DNS_ZONE" }}:{{ getenv "WEBSOCKETS_PROXY_PORT" }}"]
routes:
- match:
prefix: "/"
Expand Down
27 changes: 27 additions & 0 deletions actions/setup-gap/scripts/debug-docker-compose.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
#! /bin/bash
export GAP_NAME="gap-name"
export GITHUB_ACTION_PATH="."

export AUTH_SERVICE_NAME="auth-service-name"
export AUTH_SERVICE_PORT="9001"
export AUTH_LOG_LEVEL="debug"

export MAIN_DNS_ZONE="main.dns.zone"

export GITHUB_OIDC_TOKEN_HEADER_NAME="github-oidc-token-header-name"
export GITHUB_OIDC_HOSTNAME="github-oidc-hostname"
export ACTIONS_ID_TOKEN_REQUEST_URL="actions-id-token-request-url"
export ACTIONS_ID_TOKEN_REQUEST_TOKEN="actions-id-token-request-token"
export GITHUB_REPOSITORY="github-repository"

export ENVOY_PROXY_IMAGE="envoy-proxy-image:version"
export ENVOY_SERVICE_NAME="envoy-service-name"
export ENVOY_EXTRA_ARGS="--extra-args"
export PROXY_LOG_LEVEL="debug"

export DYNAMIC_PROXY_PORT="443"
export PROXY_PORT="8080"
export WEBSOCKETS_PROXY_PORT="9443"
export PATH_CERTS_DIR="/path/to/certs"

docker compose config
13 changes: 13 additions & 0 deletions actions/setup-gap/scripts/debug-render-template.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
#! /bin/bash

export MAIN_DNS_ZONE="main.dns.zone"
export GITHUB_OIDC_TOKEN_HEADER_NAME="github-oidc-token-header-name"
export PROXY_PORT="8080"
export K8S_API_ENDPOINT_PORT="6443"
export DYNAMIC_PROXY_PORT="443"
export AUTH_SERVICE_NAME="auth-service-name"
export AUTH_SERVICE_PORT="9001"
export WEBSOCKETS_SERVICES="websocket-service-1,websocket-service-2"
export WEBSOCKETS_PROXY_PORT="9443"

gomplate -V -f envoy.yaml.gotmpl -o envoy-debug.yaml
Loading