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 }