mirror of
https://github.com/kubeshark/kubeshark.git
synced 2025-06-22 14:32:35 +00:00
* Make `logger` a separate module such that don't depend on `shared` module as a whole for logging * Update `Dockerfile`
148 lines
4.5 KiB
Go
148 lines
4.5 KiB
Go
package auth
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"net"
|
|
"net/http"
|
|
"time"
|
|
|
|
"github.com/google/uuid"
|
|
"github.com/up9inc/mizu/cli/config"
|
|
"github.com/up9inc/mizu/cli/config/configStructs"
|
|
"github.com/up9inc/mizu/cli/uiUtils"
|
|
"github.com/up9inc/mizu/logger"
|
|
"golang.org/x/oauth2"
|
|
)
|
|
|
|
const loginTimeoutInMin = 2
|
|
|
|
// Ports are configured in keycloak "cli" client as valid redirect URIs. A change here must be reflected there as well.
|
|
var listenPorts = []int{3141, 4001, 5002, 6003, 7004, 8005, 9006, 10007}
|
|
|
|
func Login() error {
|
|
token, loginErr := loginInteractively()
|
|
if loginErr != nil {
|
|
return fmt.Errorf("failed login interactively, err: %v", loginErr)
|
|
}
|
|
|
|
authConfig := configStructs.AuthConfig{
|
|
EnvName: config.Config.Auth.EnvName,
|
|
Token: token.AccessToken,
|
|
}
|
|
|
|
if err := config.UpdateConfig(func(configStruct *config.ConfigStruct) { configStruct.Auth = authConfig }); err != nil {
|
|
return fmt.Errorf("failed updating config with auth, err: %v", err)
|
|
}
|
|
|
|
config.Config.Auth = authConfig
|
|
|
|
logger.Log.Infof("Login successfully, token stored in config path: %s", fmt.Sprintf(uiUtils.Purple, config.Config.ConfigFilePath))
|
|
return nil
|
|
}
|
|
|
|
func loginInteractively() (*oauth2.Token, error) {
|
|
tokenChannel := make(chan *oauth2.Token)
|
|
errorChannel := make(chan error)
|
|
|
|
server := http.Server{}
|
|
go startLoginServer(tokenChannel, errorChannel, &server)
|
|
|
|
defer func() {
|
|
if err := server.Shutdown(context.Background()); err != nil {
|
|
logger.Log.Debugf("Error shutting down server, err: %v", err)
|
|
}
|
|
}()
|
|
|
|
select {
|
|
case <-time.After(loginTimeoutInMin * time.Minute):
|
|
return nil, errors.New("auth timed out")
|
|
case err := <-errorChannel:
|
|
return nil, err
|
|
case token := <-tokenChannel:
|
|
return token, nil
|
|
}
|
|
}
|
|
|
|
func startLoginServer(tokenChannel chan *oauth2.Token, errorChannel chan error, server *http.Server) {
|
|
for _, port := range listenPorts {
|
|
var authConfig = &oauth2.Config{
|
|
ClientID: "cli",
|
|
RedirectURL: fmt.Sprintf("http://localhost:%v/callback", port),
|
|
Endpoint: oauth2.Endpoint{
|
|
AuthURL: fmt.Sprintf("https://auth.%s/auth/realms/testr/protocol/openid-connect/auth", config.Config.Auth.EnvName),
|
|
TokenURL: fmt.Sprintf("https://auth.%s/auth/realms/testr/protocol/openid-connect/token", config.Config.Auth.EnvName),
|
|
},
|
|
}
|
|
|
|
state := uuid.New()
|
|
|
|
mux := http.NewServeMux()
|
|
server.Handler = mux
|
|
mux.Handle("/callback", loginCallbackHandler(tokenChannel, errorChannel, authConfig, state))
|
|
|
|
listener, listenErr := net.Listen("tcp", fmt.Sprintf("%s:%d", "127.0.0.1", port))
|
|
if listenErr != nil {
|
|
logger.Log.Debugf("failed to start listening on port %v, err: %v", port, listenErr)
|
|
continue
|
|
}
|
|
|
|
authorizationUrl := authConfig.AuthCodeURL(state.String())
|
|
uiUtils.OpenBrowser(authorizationUrl)
|
|
|
|
serveErr := server.Serve(listener)
|
|
if serveErr == http.ErrServerClosed {
|
|
logger.Log.Debugf("received server shutdown, server on port %v is closed", port)
|
|
return
|
|
} else if serveErr != nil {
|
|
logger.Log.Debugf("failed to start serving on port %v, err: %v", port, serveErr)
|
|
continue
|
|
}
|
|
|
|
logger.Log.Debugf("didn't receive server closed on port %v", port)
|
|
return
|
|
}
|
|
|
|
errorChannel <- fmt.Errorf("failed to start serving on all listen ports, ports: %v", listenPorts)
|
|
}
|
|
|
|
func loginCallbackHandler(tokenChannel chan *oauth2.Token, errorChannel chan error, authConfig *oauth2.Config, state uuid.UUID) http.Handler {
|
|
return http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
|
|
if err := request.ParseForm(); err != nil {
|
|
errorMsg := fmt.Sprintf("failed to parse form, err: %v", err)
|
|
http.Error(writer, errorMsg, http.StatusBadRequest)
|
|
errorChannel <- fmt.Errorf(errorMsg)
|
|
return
|
|
}
|
|
|
|
requestState := request.Form.Get("state")
|
|
if requestState != state.String() {
|
|
errorMsg := fmt.Sprintf("state invalid, requestState: %v, authState:%v", requestState, state.String())
|
|
http.Error(writer, errorMsg, http.StatusBadRequest)
|
|
errorChannel <- fmt.Errorf(errorMsg)
|
|
return
|
|
}
|
|
|
|
code := request.Form.Get("code")
|
|
if code == "" {
|
|
errorMsg := "code not found"
|
|
http.Error(writer, errorMsg, http.StatusBadRequest)
|
|
errorChannel <- fmt.Errorf(errorMsg)
|
|
return
|
|
}
|
|
|
|
token, err := authConfig.Exchange(context.Background(), code)
|
|
if err != nil {
|
|
errorMsg := fmt.Sprintf("failed to create token, err: %v", err)
|
|
http.Error(writer, errorMsg, http.StatusInternalServerError)
|
|
errorChannel <- fmt.Errorf(errorMsg)
|
|
return
|
|
}
|
|
|
|
tokenChannel <- token
|
|
|
|
http.Redirect(writer, request, fmt.Sprintf("https://%s/CliLogin", config.Config.Auth.EnvName), http.StatusFound)
|
|
})
|
|
}
|