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
39 changes: 29 additions & 10 deletions internal/infra/proxy.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,16 +79,7 @@ func NewProxy(ctx context.Context, cli *client.Client, params *RunParams, nets *
}
config := &container.Config{
Image: params.ProxyImage,
Env: []string{
"HTTP_PROXY=" + os.Getenv("HTTP_PROXY"),
"HTTPS_PROXY=" + os.Getenv("HTTPS_PROXY"),
"NO_PROXY=" + os.Getenv("NO_PROXY"),
"JOB_ID=" + jobID,
"PROXY_CACHE=true",
"LOG_RESPONSE_BODY_ON_AUTH_FAILURE=true",
"ACTIONS_ID_TOKEN_REQUEST_TOKEN=" + os.Getenv("ACTIONS_ID_TOKEN_REQUEST_TOKEN"),
"ACTIONS_ID_TOKEN_REQUEST_URL=" + os.Getenv("ACTIONS_ID_TOKEN_REQUEST_URL"),
},
Env: proxyEnv(params.ApiUrl),
Entrypoint: []string{
"sh", "-c", "update-ca-certificates && /dependabot-proxy",
},
Expand Down Expand Up @@ -141,6 +132,34 @@ func NewProxy(ctx context.Context, cli *client.Client, params *RunParams, nets *
return proxy, nil
}

// proxyEnv builds the environment variables passed to the proxy container.
func proxyEnv(apiURL string) []string {
env := []string{
"HTTP_PROXY=" + os.Getenv("HTTP_PROXY"),
"HTTPS_PROXY=" + os.Getenv("HTTPS_PROXY"),
"NO_PROXY=" + os.Getenv("NO_PROXY"),
"JOB_ID=" + jobID,
"PROXY_CACHE=" + firstNonEmpty(os.Getenv("PROXY_CACHE"), "true"),
"LOG_RESPONSE_BODY_ON_AUTH_FAILURE=true",
"ACTIONS_ID_TOKEN_REQUEST_TOKEN=" + os.Getenv("ACTIONS_ID_TOKEN_REQUEST_TOKEN"),
"ACTIONS_ID_TOKEN_REQUEST_URL=" + os.Getenv("ACTIONS_ID_TOKEN_REQUEST_URL"),
}
// Only forward JOB_TOKEN and DEPENDABOT_API_URL when JOB_TOKEN is set on the
// host. The proxy uses DEPENDABOT_API_URL to decide which host to inject the
// JOB_TOKEN into as an Authorization header; forwarding it without a token
// (e.g. in the CLI's default/Azure flows) would cause the proxy to clobber
// auth on requests to that host, so the two must be passed together.
if token, ok := os.LookupEnv("JOB_TOKEN"); ok && token != "" {
env = append(env, "JOB_TOKEN="+token)
env = append(env, "DEPENDABOT_API_URL="+apiURL)
}
// Forward OPENSSL_FORCE_FIPS_MODE only when the host has it set.
if fips, ok := os.LookupEnv("OPENSSL_FORCE_FIPS_MODE"); ok {
env = append(env, "OPENSSL_FORCE_FIPS_MODE="+fips)
}
return env
}

func putProxyConfig(ctx context.Context, cli *client.Client, config *Config, id string) error {
opt := container.CopyToContainerOptions{}

Expand Down
163 changes: 163 additions & 0 deletions internal/infra/proxy_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
package infra

import (
"os"
"strings"
"testing"
)

func envValue(env []string, key string) (string, bool) {
prefix := key + "="
for _, e := range env {
if strings.HasPrefix(e, prefix) {
return strings.TrimPrefix(e, prefix), true
}
}
return "", false
}

func Test_proxyEnv_ProxyCache(t *testing.T) {
t.Run("uses host PROXY_CACHE when set", func(t *testing.T) {
t.Setenv("PROXY_CACHE", "false")

env := proxyEnv("")

value, ok := envValue(env, "PROXY_CACHE")
if !ok {
t.Fatal("expected PROXY_CACHE to be present in proxy env")
}
if value != "false" {
t.Errorf("expected PROXY_CACHE to be %q, got %q", "false", value)
}
})

t.Run("falls back to true when host PROXY_CACHE is unset", func(t *testing.T) {
t.Setenv("PROXY_CACHE", "placeholder")
os.Unsetenv("PROXY_CACHE")

env := proxyEnv("")

value, ok := envValue(env, "PROXY_CACHE")
if !ok {
t.Fatal("expected PROXY_CACHE to be present in proxy env")
}
if value != "true" {
t.Errorf("expected PROXY_CACHE to fall back to %q, got %q", "true", value)
}
})

t.Run("falls back to true when host PROXY_CACHE is empty", func(t *testing.T) {
t.Setenv("PROXY_CACHE", "")

env := proxyEnv("")

value, ok := envValue(env, "PROXY_CACHE")
if !ok {
t.Fatal("expected PROXY_CACHE to be present in proxy env")
}
if value != "true" {
t.Errorf("expected PROXY_CACHE to fall back to %q, got %q", "true", value)
}
})
}

func Test_proxyEnv_OpenSSLForceFIPSMode(t *testing.T) {
t.Run("passes OPENSSL_FORCE_FIPS_MODE from environment", func(t *testing.T) {
t.Setenv("OPENSSL_FORCE_FIPS_MODE", "1")

env := proxyEnv("")

value, ok := envValue(env, "OPENSSL_FORCE_FIPS_MODE")
if !ok {
t.Fatal("expected OPENSSL_FORCE_FIPS_MODE to be present in proxy env")
}
if value != "1" {
t.Errorf("expected OPENSSL_FORCE_FIPS_MODE to be %q, got %q", "1", value)
}
})

t.Run("omits OPENSSL_FORCE_FIPS_MODE when unset", func(t *testing.T) {
t.Setenv("OPENSSL_FORCE_FIPS_MODE", "placeholder")
os.Unsetenv("OPENSSL_FORCE_FIPS_MODE")

env := proxyEnv("")

if _, ok := envValue(env, "OPENSSL_FORCE_FIPS_MODE"); ok {
t.Error("expected OPENSSL_FORCE_FIPS_MODE to be absent from proxy env when host has it unset")
}
})
}

func Test_proxyEnv_JobToken(t *testing.T) {
t.Run("passes JOB_TOKEN from environment", func(t *testing.T) {
t.Setenv("JOB_TOKEN", "super-secret-token")

env := proxyEnv("")

value, ok := envValue(env, "JOB_TOKEN")
if !ok {
t.Fatal("expected JOB_TOKEN to be present in proxy env")
}
if value != "super-secret-token" {
t.Errorf("expected JOB_TOKEN to be %q, got %q", "super-secret-token", value)
}
})

t.Run("omits JOB_TOKEN when unset", func(t *testing.T) {
t.Setenv("JOB_TOKEN", "placeholder")
os.Unsetenv("JOB_TOKEN")

env := proxyEnv("")

if _, ok := envValue(env, "JOB_TOKEN"); ok {
t.Error("expected JOB_TOKEN to be absent from proxy env when host has it unset")
}
})

t.Run("omits JOB_TOKEN when set to empty", func(t *testing.T) {
t.Setenv("JOB_TOKEN", "")

env := proxyEnv("")

if _, ok := envValue(env, "JOB_TOKEN"); ok {
t.Error("expected JOB_TOKEN to be absent from proxy env when host has it empty")
}
})
}

func Test_proxyEnv_DependabotAPIURL(t *testing.T) {
t.Run("sets DEPENDABOT_API_URL from the apiURL parameter when JOB_TOKEN is set", func(t *testing.T) {
t.Setenv("JOB_TOKEN", "super-secret-token")

env := proxyEnv("https://api.example.com")

value, ok := envValue(env, "DEPENDABOT_API_URL")
if !ok {
t.Fatal("expected DEPENDABOT_API_URL to be present when JOB_TOKEN is set")
}
if value != "https://api.example.com" {
t.Errorf("expected DEPENDABOT_API_URL to be %q, got %q", "https://api.example.com", value)
}
})

t.Run("omits DEPENDABOT_API_URL when JOB_TOKEN is unset", func(t *testing.T) {
t.Setenv("JOB_TOKEN", "placeholder")
os.Unsetenv("JOB_TOKEN")

env := proxyEnv("https://api.example.com")

if _, ok := envValue(env, "DEPENDABOT_API_URL"); ok {
t.Error("expected DEPENDABOT_API_URL to be absent when JOB_TOKEN is unset")
}
})

t.Run("omits DEPENDABOT_API_URL when JOB_TOKEN is empty", func(t *testing.T) {
t.Setenv("JOB_TOKEN", "")

env := proxyEnv("https://api.example.com")

if _, ok := envValue(env, "DEPENDABOT_API_URL"); ok {
t.Error("expected DEPENDABOT_API_URL to be absent when JOB_TOKEN is empty")
}
})
}