github.com/argoproj/argo-cd/v3@v3.2.1/util/dex/dex.go (about)

     1  package dex
     2  
     3  import (
     4  	"bytes"
     5  	"crypto/tls"
     6  	"crypto/x509"
     7  	stderrors "errors"
     8  	"io"
     9  	"net/http"
    10  	"net/http/httputil"
    11  	"net/url"
    12  	"path"
    13  	"strconv"
    14  	"strings"
    15  
    16  	log "github.com/sirupsen/logrus"
    17  
    18  	"github.com/argoproj/argo-cd/v3/common"
    19  	"github.com/argoproj/argo-cd/v3/util/errors"
    20  )
    21  
    22  func decorateDirector(director func(req *http.Request), target *url.URL) func(req *http.Request) {
    23  	return func(req *http.Request) {
    24  		director(req)
    25  		req.Host = target.Host
    26  	}
    27  }
    28  
    29  type DexTLSConfig struct {
    30  	DisableTLS       bool
    31  	StrictValidation bool
    32  	RootCAs          *x509.CertPool
    33  	Certificate      []byte
    34  }
    35  
    36  func TLSConfig(tlsConfig *DexTLSConfig) *tls.Config {
    37  	if tlsConfig == nil || tlsConfig.DisableTLS {
    38  		return nil
    39  	}
    40  	if !tlsConfig.StrictValidation {
    41  		return &tls.Config{
    42  			InsecureSkipVerify: true,
    43  		}
    44  	}
    45  	return &tls.Config{
    46  		InsecureSkipVerify: false,
    47  		RootCAs:            tlsConfig.RootCAs,
    48  		VerifyPeerCertificate: func(rawCerts [][]byte, _ [][]*x509.Certificate) error {
    49  			if !bytes.Equal(rawCerts[0], tlsConfig.Certificate) {
    50  				return stderrors.New("dex server certificate does not match")
    51  			}
    52  			return nil
    53  		},
    54  	}
    55  }
    56  
    57  // NewDexHTTPReverseProxy returns a reverse proxy to the Dex server. Dex is assumed to be configured
    58  // with the external issuer URL muxed to the same path configured in server.go. In other words, if
    59  // Argo CD API server wants to proxy requests at /api/dex, then the dex config yaml issuer URL should
    60  // also be /api/dex (e.g. issuer: https://argocd.example.com/api/dex)
    61  func NewDexHTTPReverseProxy(serverAddr string, baseHRef string, tlsConfig *DexTLSConfig) func(writer http.ResponseWriter, request *http.Request) {
    62  	fullAddr := DexServerAddressWithProtocol(serverAddr, tlsConfig)
    63  
    64  	target, err := url.Parse(fullAddr)
    65  	errors.CheckError(err)
    66  	target.Path = baseHRef
    67  
    68  	proxy := httputil.NewSingleHostReverseProxy(target)
    69  
    70  	if tlsConfig != nil && !tlsConfig.DisableTLS {
    71  		proxy.Transport = &http.Transport{
    72  			TLSClientConfig: TLSConfig(tlsConfig),
    73  		}
    74  	}
    75  
    76  	proxy.ModifyResponse = func(resp *http.Response) error {
    77  		if resp.StatusCode == http.StatusInternalServerError {
    78  			b, err := io.ReadAll(resp.Body)
    79  			if err != nil {
    80  				return err
    81  			}
    82  			err = resp.Body.Close()
    83  			if err != nil {
    84  				return err
    85  			}
    86  			log.WithFields(log.Fields{
    87  				common.SecurityField: common.SecurityMedium,
    88  			}).Errorf("received error from dex: %s", string(b))
    89  			resp.ContentLength = 0
    90  			resp.Header.Set("Content-Length", strconv.Itoa(0))
    91  			resp.Header.Set("Location", path.Join(baseHRef, "login")+"?has_sso_error=true")
    92  			resp.StatusCode = http.StatusSeeOther
    93  			resp.Body = io.NopCloser(bytes.NewReader(make([]byte, 0)))
    94  			return nil
    95  		}
    96  		return nil
    97  	}
    98  	proxy.Director = decorateDirector(proxy.Director, target)
    99  	return func(w http.ResponseWriter, r *http.Request) {
   100  		proxy.ServeHTTP(w, r)
   101  	}
   102  }
   103  
   104  // NewDexRewriteURLRoundTripper creates a new DexRewriteURLRoundTripper
   105  func NewDexRewriteURLRoundTripper(dexServerAddr string, t http.RoundTripper) DexRewriteURLRoundTripper {
   106  	dexURL, _ := url.Parse(dexServerAddr)
   107  	return DexRewriteURLRoundTripper{
   108  		DexURL: dexURL,
   109  		T:      t,
   110  	}
   111  }
   112  
   113  // DexRewriteURLRoundTripper is an HTTP RoundTripper to rewrite HTTP requests to the specified
   114  // dex server address. This is used when reverse proxying Dex to avoid the API server from
   115  // unnecessarily communicating to Argo CD through its externally facing load balancer, which is not
   116  // always permitted in firewalled/air-gapped networks.
   117  type DexRewriteURLRoundTripper struct {
   118  	DexURL *url.URL
   119  	T      http.RoundTripper
   120  }
   121  
   122  func (s DexRewriteURLRoundTripper) RoundTrip(r *http.Request) (*http.Response, error) {
   123  	r.URL.Host = s.DexURL.Host
   124  	r.URL.Scheme = s.DexURL.Scheme
   125  	r.Host = s.DexURL.Host
   126  	return s.T.RoundTrip(r)
   127  }
   128  
   129  func DexServerAddressWithProtocol(orig string, tlsConfig *DexTLSConfig) string {
   130  	if strings.Contains(orig, "://") {
   131  		return orig
   132  	}
   133  	if tlsConfig == nil || tlsConfig.DisableTLS {
   134  		return "http://" + orig
   135  	}
   136  	return "https://" + orig
   137  }