diff --git a/topaz/cmd/access/wellknown.go b/topaz/cmd/access/wellknown.go index 6e96c8e8..0b15c893 100644 --- a/topaz/cmd/access/wellknown.go +++ b/topaz/cmd/access/wellknown.go @@ -2,10 +2,12 @@ package access import ( "context" + "crypto/tls" "encoding/json" "fmt" "io" "net/http" + "net/url" "os" "github.com/aserto-dev/topaz/topaz/cc" @@ -30,7 +32,11 @@ func (cmd *WellKnownCmd) Run(ctx context.Context) error { return fmt.Errorf("creating request: %w", err) } - resp, err := http.DefaultClient.Do(req) + if req.URL, err = wellKnownURL(req.URL, info.Directory.Plaintext); err != nil { + return fmt.Errorf("building request URL: %w", err) + } + + resp, err := wellKnownClient(info.Directory.Insecure).Do(req) if err != nil { return fmt.Errorf("request failed: %w", err) } @@ -60,3 +66,49 @@ func (cmd *WellKnownCmd) Run(ctx context.Context) error { return enc.Encode(wellknown) } + +func wellKnownURL(reqURL *url.URL, plaintext bool) (*url.URL, error) { + u, err := url.Parse(reqURL.String()) + if err != nil { + return nil, err + } + + if plaintext { + u.Scheme = "http" + } + + return u, nil +} + +func wellKnownClient(insecure bool) *http.Client { + if !insecure { + return http.DefaultClient + } + + client := *http.DefaultClient + + baseTransport := client.Transport + if baseTransport == nil { + baseTransport = http.DefaultTransport + } + + transport, ok := baseTransport.(*http.Transport) + if !ok { + return &client + } + + clone := transport.Clone() + + tlsConfig := &tls.Config{} + if clone.TLSClientConfig != nil { + tlsConfig = clone.TLSClientConfig.Clone() + } + + // #nosec G402 -- TOPAZ_INSECURE explicitly requests skipping TLS verification. + tlsConfig.InsecureSkipVerify = true + clone.TLSClientConfig = tlsConfig + + client.Transport = clone + + return &client +} diff --git a/topaz/cmd/access/wellknown_test.go b/topaz/cmd/access/wellknown_test.go new file mode 100644 index 00000000..365ca8df --- /dev/null +++ b/topaz/cmd/access/wellknown_test.go @@ -0,0 +1,88 @@ +//nolint:testpackage +package access + +import ( + "context" + "io" + "net/http" + "net/http/httptest" + "net/url" + "strings" + "testing" +) + +func TestWellKnownURLUsesHTTPWhenPlaintext(t *testing.T) { + t.Parallel() + + reqURL, err := url.Parse("https://example.com/.well-known/authzen-configuration") + if err != nil { + t.Fatalf("failed to parse URL: %v", err) + } + + got, err := wellKnownURL(reqURL, true) + if err != nil { + t.Fatalf("failed to build URL: %v", err) + } + + if got.String() != "http://example.com/.well-known/authzen-configuration" { + t.Fatalf("expected plaintext URL, got %q", got.String()) + } +} + +func TestWellKnownClientSupportsInsecureTLS(t *testing.T) { + t.Parallel() + + srv := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + _, _ = io.WriteString(w, `{"issuer":"topaz"}`) + })) + defer srv.Close() + + req, err := http.NewRequestWithContext(context.Background(), http.MethodGet, srv.URL, nil) + if err != nil { + t.Fatalf("failed to build request: %v", err) + } + + resp, err := wellKnownClient(true).Do(req) + if err != nil { + t.Fatalf("expected insecure client to connect: %v", err) + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + t.Fatalf("expected status %d, got %d", http.StatusOK, resp.StatusCode) + } +} + +func TestWellKnownURLMatchesPlaintextServer(t *testing.T) { + t.Parallel() + + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + _, _ = io.WriteString(w, `{"issuer":"topaz"}`) + })) + defer srv.Close() + + reqURL, err := url.Parse(strings.Replace(srv.URL, "http://", "https://", 1)) + if err != nil { + t.Fatalf("failed to parse URL: %v", err) + } + + plaintextURL, err := wellKnownURL(reqURL, true) + if err != nil { + t.Fatalf("failed to build URL: %v", err) + } + + req, err := http.NewRequestWithContext(context.Background(), http.MethodGet, plaintextURL.String(), nil) + if err != nil { + t.Fatalf("failed to build request: %v", err) + } + + resp, err := wellKnownClient(false).Do(req) + if err != nil { + t.Fatalf("expected plaintext client to connect: %v", err) + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + t.Fatalf("expected status %d, got %d", http.StatusOK, resp.StatusCode) + } +}