github.com/verrazzano/verrazzano@v1.7.0/authproxy/src/proxy/proxy.go (about)

     1  // Copyright (c) 2023, Oracle and/or its affiliates.
     2  // Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl.
     3  
     4  package proxy
     5  
     6  import (
     7  	"crypto/x509"
     8  	"fmt"
     9  	"net/http"
    10  	"os"
    11  	"sync"
    12  	"sync/atomic"
    13  	"time"
    14  
    15  	"github.com/hashicorp/go-retryablehttp"
    16  	"github.com/verrazzano/verrazzano/authproxy/internal/httputil"
    17  	"github.com/verrazzano/verrazzano/authproxy/src/apiserver"
    18  	"github.com/verrazzano/verrazzano/authproxy/src/auth"
    19  	"github.com/verrazzano/verrazzano/authproxy/src/config"
    20  	"github.com/verrazzano/verrazzano/pkg/k8sutil"
    21  	"go.uber.org/zap"
    22  	"k8s.io/client-go/rest"
    23  	"k8s.io/client-go/util/cert"
    24  	"sigs.k8s.io/controller-runtime/pkg/client"
    25  )
    26  
    27  const (
    28  	callbackPath = "/_authentication_callback"
    29  	logoutPath   = "/_logout"
    30  )
    31  
    32  var (
    33  	getConfigFunc     = k8sutil.GetConfigFromController
    34  	getOIDCConfigFunc = getOIDCConfiguration
    35  )
    36  
    37  var mutex sync.RWMutex
    38  
    39  // AuthProxy wraps the server instance
    40  type AuthProxy struct {
    41  	http.Server
    42  }
    43  
    44  type handlerFuncType func(w http.ResponseWriter, r *http.Request)
    45  
    46  // Handler performs HTTP handling for the AuthProxy Server
    47  type Handler struct {
    48  	URL           string
    49  	Client        *retryablehttp.Client
    50  	Log           *zap.SugaredLogger
    51  	OIDCConfig    map[string]string
    52  	Authenticator auth.Authenticator
    53  	K8sClient     client.Client
    54  	AuthInited    atomic.Bool
    55  	BearerToken   string
    56  }
    57  
    58  var _ http.Handler = &Handler{}
    59  
    60  // InitializeProxy returns a configured AuthProxy instance
    61  func InitializeProxy(port int) *AuthProxy {
    62  	return &AuthProxy{
    63  		Server: http.Server{
    64  			Addr:         fmt.Sprintf(":%d", port),
    65  			ReadTimeout:  10 * time.Second,
    66  			WriteTimeout: 30 * time.Second,
    67  		},
    68  	}
    69  }
    70  
    71  // ConfigureKubernetesAPIProxy configures the server handler and the proxy client for the AuthProxy instance
    72  func ConfigureKubernetesAPIProxy(authproxy *AuthProxy, k8sClient client.Client, log *zap.SugaredLogger) error {
    73  	restConfig, err := getConfigFunc()
    74  	if err != nil {
    75  		log.Errorf("Failed to get Kubeconfig for the proxy: %v", err)
    76  		return err
    77  	}
    78  
    79  	rootCA, err := loadCAData(restConfig, log)
    80  	if err != nil {
    81  		return err
    82  	}
    83  
    84  	bearerToken, err := loadBearerToken(restConfig, log)
    85  	if err != nil {
    86  		return err
    87  	}
    88  
    89  	httpClient := httputil.GetHTTPClientWithCABundle(rootCA)
    90  	authproxy.Handler = &Handler{
    91  		URL:         restConfig.Host,
    92  		Client:      httpClient,
    93  		Log:         log,
    94  		K8sClient:   k8sClient,
    95  		BearerToken: bearerToken,
    96  	}
    97  	return nil
    98  }
    99  
   100  // findPathHandler returns the path handler function given the request path
   101  func (h *Handler) findPathHandler(req *http.Request) handlerFuncType {
   102  	switch req.URL.Path {
   103  	case callbackPath:
   104  		return h.handleAuthCallback
   105  	case logoutPath:
   106  		return h.handleLogout
   107  	default:
   108  		return h.handleAPIRequest
   109  	}
   110  }
   111  
   112  // ServeHTTP accepts an incoming server request and forwards it to the Kubernetes API server
   113  func (h *Handler) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
   114  	h.Log.Debug("Incoming request: %+v", httputil.ObfuscateRequestData(req))
   115  
   116  	err := h.initializeAuthenticator()
   117  	if err != nil {
   118  		h.Log.Errorf("Failed to initialize Authenticator: %v", err)
   119  		http.Error(rw, "Failed to initialize Authenticator", http.StatusInternalServerError)
   120  		return
   121  	}
   122  
   123  	handlerFunc := h.findPathHandler(req)
   124  	handlerFunc(rw, req)
   125  }
   126  
   127  // handleAuthCallback is the http handler for authentication callback
   128  func (h *Handler) handleAuthCallback(rw http.ResponseWriter, req *http.Request) {
   129  
   130  }
   131  
   132  // handleLogout is the http handler for logout
   133  func (h *Handler) handleLogout(rw http.ResponseWriter, req *http.Request) {
   134  
   135  }
   136  
   137  // handleAPIRequest is the http handler for API requests
   138  func (h *Handler) handleAPIRequest(rw http.ResponseWriter, req *http.Request) {
   139  	apiRequest := apiserver.APIRequest{
   140  		RW:            rw,
   141  		Request:       req,
   142  		Authenticator: h.Authenticator,
   143  		Client:        h.Client,
   144  		APIServerURL:  h.URL,
   145  		CallbackPath:  callbackPath,
   146  		BearerToken:   h.BearerToken,
   147  		Log:           h.Log,
   148  	}
   149  	apiRequest.ForwardAPIRequest()
   150  }
   151  
   152  // initializeAuthenticator initializes the handler authenticator
   153  func (h *Handler) initializeAuthenticator() error {
   154  	if h.AuthInited.Load() {
   155  		return nil
   156  	}
   157  
   158  	oidcConfig := getOIDCConfigFunc()
   159  
   160  	mutex.Lock()
   161  	defer mutex.Unlock()
   162  
   163  	// double-check the condition in case it changed by the time we acquired the lock
   164  	if h.AuthInited.Load() {
   165  		return nil
   166  	}
   167  
   168  	authenticator, err := auth.NewAuthenticator(&oidcConfig, h.Log, h.K8sClient)
   169  	if err != nil {
   170  		return err
   171  	}
   172  	h.Authenticator = authenticator
   173  	h.AuthInited.Store(true)
   174  	return nil
   175  }
   176  
   177  // loadCAData returns the config CA data from the byte array or from the file name
   178  func loadCAData(config *rest.Config, log *zap.SugaredLogger) (*x509.CertPool, error) {
   179  	if len(config.CAData) < 1 {
   180  		rootCA, err := cert.NewPool(config.CAFile)
   181  		if err != nil {
   182  			log.Errorf("Failed to get in cluster Root Certificate for the Kubernetes API server")
   183  		}
   184  		return rootCA, err
   185  	}
   186  
   187  	rootCA, err := cert.NewPoolFromBytes(config.CAData)
   188  	if err != nil {
   189  		log.Errorf("Failed to load CA data from the Kubeconfig")
   190  	}
   191  	return rootCA, err
   192  }
   193  
   194  // loadBearerToken loads the bearer token from the config or from the specified file
   195  func loadBearerToken(config *rest.Config, log *zap.SugaredLogger) (string, error) {
   196  	if config.BearerToken != "" {
   197  		return config.BearerToken, nil
   198  	}
   199  
   200  	if config.BearerTokenFile != "" {
   201  		data, err := os.ReadFile(config.BearerTokenFile)
   202  		if err != nil {
   203  			log.Errorf("Failed to read bearer token file: %v", err)
   204  			return "", err
   205  		}
   206  		return string(data), nil
   207  	}
   208  
   209  	return "", nil
   210  }
   211  
   212  // getOIDCConfiguration returns an OIDC configuration populated from the config package
   213  func getOIDCConfiguration() auth.OIDCConfiguration {
   214  	return auth.OIDCConfiguration{
   215  		ExternalURL: config.GetExternalURL(),
   216  		ServiceURL:  config.GetServiceURL(),
   217  		ClientID:    config.GetClientID(),
   218  	}
   219  }