Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CLI for local users MFA #2979

Open
wants to merge 18 commits into
base: main
Choose a base branch
from
Open

CLI for local users MFA #2979

wants to merge 18 commits into from

Conversation

tmalikconfluent
Copy link
Contributor

@tmalikconfluent tmalikconfluent commented Dec 20, 2024

Release Notes

Breaking Changes

  • PLACEHOLDER

New Features

  • Add Multi-Factor Authentication (MFA) for users logging in using the CLI.

Bug Fixes

  • PLACEHOLDER

Checklist

  • I have successfully built and used a custom CLI binary, without linter issues from this PR.
  • I have clearly specified in the What section below whether this PR applies to Confluent Cloud, Confluent Platform, or both.
  • I have verified this PR in Confluent Cloud pre-prod or production environment, if applicable.
  • I have verified this PR in Confluent Platform on-premises environment, if applicable.
  • I have attached manual CLI verification results or screenshots in the Test & Review section below.
  • I have added appropriate CLI integration or unit tests for any new or updated commands and functionality.
  • I confirm that this PR introduces no breaking changes or backward compatibility issues.
  • I have indicated the potential customer impact if something goes wrong in the Blast Radius section below.
  • I have put checkmarks below confirming that the feature associated with this PR is enabled in:
    • Confluent Cloud prod
    • Confluent Cloud stag
    • Confluent Platform
    • Check this box if the feature is enabled for certain organizations only

What

Add Multi-Factor Authentication (MFA) for users logging in using the CLI.

Blast Radius

This will impact all users trying to log into the confluent CLI whose organization has enabled Multi-Factor Authentication (MFA).

References

https://confluentinc.atlassian.net/browse/CLI-3318

Test & Review

Testing Doc: https://docs.google.com/document/d/1woyiPp9a4cPlECj0tfgxl5QRzdkxH-4KDj89MMDQbFM/edit?tab=t.0

@confluent-cla-assistant
Copy link

🎉 All Contributor License Agreements have been signed. Ready to merge.
Please push an empty commit if you would like to re-run the checks to verify CLA status for all contributors.

Copy link
Member

@sgagniere sgagniere left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! I left some comments/questions

pkg/auth/auth_token_handler.go Outdated Show resolved Hide resolved
pkg/auth/auth_token_handler.go Outdated Show resolved Hide resolved
pkg/auth/auth_token_handler.go Outdated Show resolved Hide resolved
pkg/auth/mfa/auth_server.go Show resolved Hide resolved
@tmalikconfluent tmalikconfluent marked this pull request as ready for review January 13, 2025 19:49
@tmalikconfluent tmalikconfluent requested a review from a team as a code owner January 13, 2025 19:49
pkg/auth/auth_token_handler.go Show resolved Hide resolved
pkg/auth/login_credentials_manager.go Outdated Show resolved Hide resolved
sgagniere
sgagniere previously approved these changes Jan 30, 2025
@@ -112,7 +112,7 @@ func PersistCCloudCredentialsToConfig(config *config.Config, client *ccloudv1.Cl
return ctx.CurrentEnvironment, user.GetOrganization(), nil
}

func addOrUpdateContext(cfg *config.Config, isCloud bool, credentials *Credentials, ctxName, url string, state *config.ContextState, caCertPath, organizationId string, save bool) error {
func addOrUpdateContext(cfg *config.Config, isCloud bool, credentials *Credentials, ctxName, url string, state *config.ContextState, caCertPath, organizationId string, save, IsMfa bool) error {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For go, please follow the acronyms convention to use isMFA instead.

For local variable including the ones in the arguments, the first letter should be non capitalized.

@@ -494,7 +494,7 @@ func (c *Config) FindContext(name string) (*Context, error) {
return context, nil
}

func (c *Config) AddContext(name, platformName, credentialName string, kafkaClusters map[string]*KafkaClusterConfig, kafka string, state *ContextState, organizationId, environmentId string) error {
func (c *Config) AddContext(name, platformName, credentialName string, kafkaClusters map[string]*KafkaClusterConfig, kafka string, state *ContextState, organizationId, environmentId string, IsMfa bool) error {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here, please update the argument names wherever applies.

@@ -40,11 +42,16 @@ func (a *AuthTokenHandlerImpl) GetCCloudTokens(clientFactory CCloudClientFactory
if token, refreshToken, err := a.refreshCCloudSSOToken(client, credentials.AuthRefreshToken, organizationId); err == nil {
return token, refreshToken, nil
}
} else if credentials.IsMfa {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

refreshCCloudMFAToken

}

client = clientFactory.JwtHTTPClientFactory(context.Background(), token, url)
err = a.checkMFAEmailMatchesLogin(client, credentials.Username)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the function returns an error: “Validate” or “verify” implies the function will produce an error if the emails do not match.

validateMFAEmailMatchesLogin()

@@ -146,6 +209,24 @@ func (a *AuthTokenHandlerImpl) refreshCCloudSSOToken(client *ccloudv1.Client, re

return res.GetToken(), refreshToken, err
}
func (a *AuthTokenHandlerImpl) refreshCCloudMfaToken(client *ccloudv1.Client, refreshToken, organizationId, email string) (string, string, error) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we check for things as we did in getCCloudMfaToken()?

@@ -108,6 +109,9 @@ func (h *LoginCredentialsManagerImpl) getCredentialsFromEnvVarFunc(envVars envir
if h.isSSOUser(email, organizationId) {
log.CliLogger.Debugf("%s=%s belongs to an SSO user.", ConfluentCloudEmail, email)
return &Credentials{Username: email, IsSSO: true}, nil
} else if h.isMFARequired(email, organizationId) {
log.CliLogger.Debugf("%s=%s belongs to an MFA user.", ConfluentCloudEmail, email)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

a MFA user.

res, err := h.client.User.LoginRealm(req)
// Fine to ignore non-nil err for this request: e.g. what if this fails due to invalid/malicious
// email, we want to silently continue and give the illusion of password prompt.
return err == nil && res.GetMfaRequired()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems the comments indicate we can ignore the err, but line 303 doesn't can you please confirm?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we are ignoring the non-nil error. We still want to catch the nil error. This is very similar to the SSO case where we do the similar thing. This should be good as is

MFAProviderIDToken string
MFAProviderRefreshToken string
MFAProviderState string
email string
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we want to export it we should use Email

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we are not exporting email, it is only for MFA

return &authServer{
wait: make(chan bool),
State: state,
}
}

// Start begins the server including attempting to bind the desired TCP port
func (s *authServer) startServer() error {
func (s *authServer) StartServer() error {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are we making this change, while the StartServer() inside mfa folder is non-capitalized?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes i will revert this, there were some changes made for testing purpose, i will fix all the capitalization/non-capitaloizatiom

@@ -13,7 +13,7 @@ import (
)

func Login(authURL string, noBrowser bool, connectionName string) (string, string, error) {
state, err := newState(authURL, noBrowser)
state, err := NewState(authURL, noBrowser)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Uppercase first letter → Exported (visible outside the package).
Lowercase first letter → Unexported (internal to the package).
Acronyms (e.g., URL, HTTP, ID) remain uppercase as per Go style guidelines.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants