@@ -54,6 +54,8 @@ type HeadlampConfig struct {
54
54
oidcClientID string
55
55
oidcClientSecret string
56
56
oidcIdpIssuerURL string
57
+ oidcImpersonate bool
58
+ oidcImpersonateClaim string
57
59
baseURL string
58
60
oidcScopes []string
59
61
proxyURLs []string
@@ -359,7 +361,7 @@ func createHeadlampHandler(config *HeadlampConfig) http.Handler {
359
361
if config .useInCluster {
360
362
context , err := kubeconfig .GetInClusterContext (config .oidcIdpIssuerURL ,
361
363
config .oidcClientID , config .oidcClientSecret ,
362
- strings .Join (config .oidcScopes , "," ))
364
+ strings .Join (config .oidcScopes , "," ), config . oidcImpersonate )
363
365
if err != nil {
364
366
logger .Log (logger .LevelError , nil , err , "Failed to get in-cluster context" )
365
367
}
@@ -883,10 +885,76 @@ func (c *HeadlampConfig) OIDCTokenRefreshMiddleware(next http.Handler) http.Hand
883
885
})
884
886
}
885
887
888
+ func (c * HeadlampConfig ) OIDCImpersonateMiddleware (next http.Handler ) http.Handler {
889
+ return http .HandlerFunc (func (w http.ResponseWriter , r * http.Request ) {
890
+ // skip if not cluster request
891
+ if ! strings .HasPrefix (r .URL .String (), "/clusters/" ) {
892
+ next .ServeHTTP (w , r )
893
+ return
894
+ }
895
+
896
+ // parse cluster and token
897
+ cluster , token := parseClusterAndToken (r )
898
+ if cluster == "" || token == "" {
899
+ next .ServeHTTP (w , r )
900
+ return
901
+ }
902
+
903
+ idToken , err := c .getValidToken (r .Context (), token )
904
+ if err != nil {
905
+ next .ServeHTTP (w , r )
906
+ return
907
+ }
908
+
909
+ var claims map [string ]interface {}
910
+ if err := idToken .Claims (& claims ); err != nil {
911
+ next .ServeHTTP (w , r )
912
+ return
913
+ }
914
+ user , ok := claims [c .oidcImpersonateClaim ].(string )
915
+ if ! ok || user == "" {
916
+ next .ServeHTTP (w , r )
917
+ return
918
+ }
919
+
920
+ context , err := c .kubeConfigStore .GetContext (cluster )
921
+ if err != nil {
922
+ next .ServeHTTP (w , r )
923
+ return
924
+ }
925
+
926
+ // User was successfully authenticated, execute request as this user
927
+ r .Header .Set ("Authorization" , "Bearer " + context .BearerToken )
928
+ r .Header .Set ("Impersonate-User" , user )
929
+ next .ServeHTTP (w , r )
930
+ })
931
+ }
932
+
933
+ func (c * HeadlampConfig ) getValidToken (ctx context.Context , token string ) (* oidc.IDToken , error ) {
934
+ provider , err := oidc .NewProvider (ctx , c .oidcIdpIssuerURL )
935
+ if err != nil {
936
+ logger .Log (logger .LevelError , map [string ]string {"idpIssuerURL" : c .oidcIdpIssuerURL },
937
+ err , "failed to get oidc provider" )
938
+ return nil , fmt .Errorf ("failed to get oidc provider: %w" , err )
939
+ }
940
+ oidcConfig := & oidc.Config {
941
+ ClientID : c .oidcClientID ,
942
+ }
943
+ oauth2Token , err := provider .Verifier (oidcConfig ).Verify (ctx , token )
944
+ if err != nil {
945
+ return nil , fmt .Errorf ("token is not valid: %w" , err )
946
+ }
947
+ return oauth2Token , nil
948
+ }
949
+
886
950
func StartHeadlampServer (config * HeadlampConfig ) {
887
951
handler := createHeadlampHandler (config )
888
952
889
953
handler = config .OIDCTokenRefreshMiddleware (handler )
954
+ if config .oidcImpersonate {
955
+ handler = config .OIDCImpersonateMiddleware (handler )
956
+ logger .Log (logger .LevelInfo , nil , nil , "OIDC impersonate active" )
957
+ }
890
958
891
959
// Start server
892
960
err := http .ListenAndServe (fmt .Sprintf (":%d" , config .port ), handler ) //nolint:gosec
0 commit comments