mirror of
https://github.com/hanzoai/deploy.git
synced 2026-10-07 08:44:50 +00:00
House rule: /v1/ is the API prefix, everywhere, and /api/ is never added on top of it. The api.* subdomain names the API; a path prefix restating it is noise. This applies it to the whole route surface: /api/v1/* -> /v1/* 106 proto annotations, 109 gateway patterns /api/version -> /v1/version /api/badge -> /v1/badge /api/webhook -> /v1/webhook (server + applicationset controller) /api/dex/* -> /v1/dex/* (constants; dex is built, not deployed) The grpc-gateway .pb.gw.go files are generated, and the plugin toolchain is not on this machine, so they were rewritten by a script that applies exactly the transform codegen would: for /api/v1 paths, drop the leading "api" literal-push opcode pair and decrement every LitPush(2,N)/Capture(5,N) pool index; for /api/version, rename the segment. ConcatN(4,N) operands are counts, not pool indexes, and are untouched. The .proto annotations moved in the same commit, so the next real codegen run is a no-op against these files. MustPattern panics at package init on a malformed pattern, so every rewritten pattern is validated by any import of pkg/apiclient — those tests pass. Also moved: assets/swagger.json (82 paths), the UI's two roots (apiRoot() '/v1', version-service '/v1/version') and its two relative composers (app logs download, badge panel), the five audit-pattern regexes in server.go, and the tests that asserted the old paths. terminal_test's request URL claimed /api/v1/terminal, a route that never existed — the handler is mounted at /terminal; the fixture now says so. NOT touched, deliberately: /api/v3 in GitHub Enterprise base-url examples (GitHub's URL shape, not ours), util/vendored/kubeclientmetrics (parses the KUBERNETES API server's /api/v1 paths), and util/http's cookie-path fixture (arbitrary string exercising slash normalization). go build ./... clean. Pre-existing failures in badge/session/webhook/account tests reproduce identically on the parent commit (rebrand fallout — badge colors, cookie name length, dex issuer) and are not made worse here. External consumers checked: universe has zero references to cd.hanzo.ai/api; the CLI speaks gRPC, not the REST gateway; UI and server ship in one image so they move together. Co-authored-by: Hanzo Dev <[email protected]>
134 lines
3.7 KiB
Go
134 lines
3.7 KiB
Go
package dex
|
|
|
|
import (
|
|
"bytes"
|
|
"crypto/tls"
|
|
"crypto/x509"
|
|
stderrors "errors"
|
|
"io"
|
|
"net/http"
|
|
"net/http/httputil"
|
|
"net/url"
|
|
"path"
|
|
"strconv"
|
|
"strings"
|
|
|
|
log "github.com/sirupsen/logrus"
|
|
|
|
"github.com/hanzoai/cd/common"
|
|
"github.com/hanzoai/cd/util/errors"
|
|
)
|
|
|
|
type DexTLSConfig struct {
|
|
DisableTLS bool
|
|
StrictValidation bool
|
|
RootCAs *x509.CertPool
|
|
Certificate []byte
|
|
}
|
|
|
|
func TLSConfig(tlsConfig *DexTLSConfig) *tls.Config {
|
|
if tlsConfig == nil || tlsConfig.DisableTLS {
|
|
return nil
|
|
}
|
|
if !tlsConfig.StrictValidation {
|
|
return &tls.Config{
|
|
InsecureSkipVerify: true,
|
|
}
|
|
}
|
|
return &tls.Config{
|
|
InsecureSkipVerify: false,
|
|
RootCAs: tlsConfig.RootCAs,
|
|
VerifyPeerCertificate: func(rawCerts [][]byte, _ [][]*x509.Certificate) error {
|
|
if !bytes.Equal(rawCerts[0], tlsConfig.Certificate) {
|
|
return stderrors.New("dex server certificate does not match")
|
|
}
|
|
return nil
|
|
},
|
|
}
|
|
}
|
|
|
|
// NewDexHTTPReverseProxy returns a reverse proxy to the Dex server. Dex is assumed to be configured
|
|
// with the external issuer URL muxed to the same path configured in server.go. In other words, if
|
|
// Hanzo CD API server wants to proxy requests at /v1/dex, then the dex config yaml issuer URL should
|
|
// also be /v1/dex (e.g. issuer: https://cd.example.com/v1/dex)
|
|
func NewDexHTTPReverseProxy(serverAddr string, baseHRef string, tlsConfig *DexTLSConfig) func(writer http.ResponseWriter, request *http.Request) {
|
|
fullAddr := DexServerAddressWithProtocol(serverAddr, tlsConfig)
|
|
|
|
target, err := url.Parse(fullAddr)
|
|
errors.CheckError(err)
|
|
target.Path = baseHRef
|
|
|
|
proxy := &httputil.ReverseProxy{
|
|
Rewrite: func(r *httputil.ProxyRequest) {
|
|
r.SetURL(target)
|
|
r.Out.Host = target.Host
|
|
},
|
|
ModifyResponse: func(resp *http.Response) error {
|
|
if resp.StatusCode == http.StatusInternalServerError {
|
|
b, err := io.ReadAll(resp.Body)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
err = resp.Body.Close()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
log.WithFields(log.Fields{
|
|
common.SecurityField: common.SecurityMedium,
|
|
}).Errorf("received error from dex: %s", string(b))
|
|
resp.ContentLength = 0
|
|
resp.Header.Set("Content-Length", strconv.Itoa(0))
|
|
resp.Header.Set("Location", path.Join(baseHRef, "login")+"?has_sso_error=true")
|
|
resp.StatusCode = http.StatusSeeOther
|
|
resp.Body = io.NopCloser(bytes.NewReader(make([]byte, 0)))
|
|
return nil
|
|
}
|
|
return nil
|
|
},
|
|
}
|
|
|
|
if tlsConfig != nil && !tlsConfig.DisableTLS {
|
|
proxy.Transport = &http.Transport{
|
|
TLSClientConfig: TLSConfig(tlsConfig),
|
|
}
|
|
}
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
proxy.ServeHTTP(w, r)
|
|
}
|
|
}
|
|
|
|
// NewDexRewriteURLRoundTripper creates a new DexRewriteURLRoundTripper
|
|
func NewDexRewriteURLRoundTripper(dexServerAddr string, t http.RoundTripper) DexRewriteURLRoundTripper {
|
|
dexURL, _ := url.Parse(dexServerAddr)
|
|
return DexRewriteURLRoundTripper{
|
|
DexURL: dexURL,
|
|
T: t,
|
|
}
|
|
}
|
|
|
|
// DexRewriteURLRoundTripper is an HTTP RoundTripper to rewrite HTTP requests to the specified
|
|
// dex server address. This is used when reverse proxying Dex to avoid the API server from
|
|
// unnecessarily communicating to Hanzo CD through its externally facing load balancer, which is not
|
|
// always permitted in firewalled/air-gapped networks.
|
|
type DexRewriteURLRoundTripper struct {
|
|
DexURL *url.URL
|
|
T http.RoundTripper
|
|
}
|
|
|
|
func (s DexRewriteURLRoundTripper) RoundTrip(r *http.Request) (*http.Response, error) {
|
|
r.URL.Host = s.DexURL.Host
|
|
r.URL.Scheme = s.DexURL.Scheme
|
|
r.Host = s.DexURL.Host
|
|
return s.T.RoundTrip(r)
|
|
}
|
|
|
|
func DexServerAddressWithProtocol(orig string, tlsConfig *DexTLSConfig) string {
|
|
if strings.Contains(orig, "://") {
|
|
return orig
|
|
}
|
|
if tlsConfig == nil || tlsConfig.DisableTLS {
|
|
return "http://" + orig
|
|
}
|
|
return "https://" + orig
|
|
}
|