-
Notifications
You must be signed in to change notification settings - Fork 42
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
Add OpenID authentication support using a KeyCloak server #720
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is brilliant, @fmartingr, thank you so much! I left some comments below, mostly nits but some other that do need discussing. I also have some higher-level questions:
- Do we need to expose the settings for Keycloak's DB? I'd lean towards being more opinionated whenever possible so that we expose less settings. Given that we don't want to monitor/load-test this server, can we use a one-size-fits-all instance type? Maybe not only for the DB but also for the Keycloak server? Or do the specs differ a lot when using more users? In any case, I think we can at least hardcode the usage of mysql or postgres here.
- Why do the docs say that production mode is not implemented? Is there anything missing? Also: do we really need the distinction? I guess development mode is just cheaper to deploy because of the lack of DB, right?
- Could you add some docs on the logic around the prefix for the usernames? The overwriting of the users file path was a bit confusing for me at first.
deployment/terraform/keycloak.go
Outdated
// Check if users file exists. Prevents from creating users multiple times. | ||
if _, err := os.Stat(usersTxtPath); err == nil || os.IsExist(err) { | ||
return nil | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure if this is a dumb question, but: could it happen that the users file exists but that it doesn't contain the users we want? For example, we create a deployment with 10 users and then recreate it with 50.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, I see that hapenning if that's an usual way of using the load test. Alternatively you could remove the file and the keycloak database to start from scratch.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wouldn't discard that edge case, to be honest, so we should either:
- Check the contents of the
keycloak-users.txt
file, not only that it exists - Clarify it in the docs
Your call, I'm good with both :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
+1 to this. We should do a quick check to ensure that # of users in keycloak file is equal to # users in users.txt file. And if possible, have a way to add new users, or fail clearly in the logs. But importantly, there should never be a case of some users partially logging in via keycloak and some via email/pass.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Changed it to create the number of users setup in the configuration. If the number is higher it will create more users accordingly, using the number of lines in the users.txt
file as the source. It will skip user creation if the number of lines is the same or higher than the expected number of users.
username := fmt.Sprintf("keycloak-auto-%06d", i) // this is the password too | ||
email := username + "@test.mattermost.com" | ||
|
||
_, err := sshc.RunCommand(fmt.Sprintf("%s/kcadm.sh create users -r %s -s username=%s -s enabled=true -s email=%s", keycloakBinPath, t.config.ExternalAuthProviderSettings.KeycloakRealmName, username, email)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh wow there is no way to create more than one user at a time? 😭
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We could call the API I guess, but there's no guarantee it would be faster.
Is this ready to review now? |
I have to address Alejandro's comments and make some changes from the work in the LT environment. I will assign you again when that's done. |
Co-authored-by: Alejandro García Montoro <[email protected]>
5012e4e
to
25c3ba0
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
New commits look good to me. The only small thing is that let's change the default keycloak instance spec to c7i
series. Thanks again for all the hard work!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Very quickly skimmed through this. Looks great overall. Left some comments.
// Check if the user has a custom authentication type. Custom authentication types are | ||
// specified by prepending the username with the authentication type followed by a colon. | ||
// Example: "openid:[email protected] user1password" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we add this information to docs/external_auth_providers.md?
if usernameParts := strings.Split(username, ":"); len(usernameParts) > 1 { | ||
authenticationType = usernameParts[0] | ||
username = usernameParts[1] | ||
|
||
// Fix the email as well | ||
if emailParts := strings.Split(email, ":"); len(emailParts) > 1 { | ||
email = emailParts[1] | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this could become a util function.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm going to make a note of this but refrain from modifying too much logic until this is merged now that we have this working. I can address the rest of the comments in individual PRs later.
@@ -33,12 +33,19 @@ type UserEntity struct { | |||
wsServerSeq int64 | |||
} | |||
|
|||
const ( | |||
AuthenticationTypeMattermost = "mattermost" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
+1
me, _, err := ue.client.GetMe(context.Background(), "") | ||
if err != nil { | ||
return fmt.Errorf("error while getting user: %w", err) | ||
} | ||
|
||
if err := ue.store.SetUser(me); err != nil { | ||
return fmt.Errorf("error while setting user: %w", err) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we need to do this here? I'd expect this method to purely authenticate (e.g. set the token) and then deal with everything else in SignUp/Login
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's a mix of logic here, is either I get the user and set it here, or I get the user by username later and set it in the other method. I've set a TODO to change the logic in a separate PR since I don't want to compromise or delay the test we're going to run today.
deployment/terraform/dump.go
Outdated
appClients := make([]*ssh.Client, len(output.Instances)) | ||
for i, instance := range output.Instances { | ||
client, err := extAgent.NewClient(instance.PublicIP) | ||
if err != nil { | ||
return fmt.Errorf("error in getting ssh connection %w", err) | ||
} | ||
defer client.Close() | ||
appClients[i] = client | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why are we initializing N clients if we just use the first?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch.
sshc, err := extAgent.NewClient(t.output.KeycloakServer.PublicIP) | ||
if err != nil { | ||
return fmt.Errorf("error in getting ssh connection %w", err) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we close this client on returning?
deployment/terraform/keycloak.go
Outdated
// Values for the keycloak.env file | ||
keycloakEnvFileContents := []string{ | ||
// Enable health endpoints | ||
"KC_HEALTH_ENABLED=true", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe we could use a template file as we do with other plaintext assets.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, that's probably better with the current logic. At the beginning this was changuing depending on the deployer configuration that's why I followed this approach as it was simpler, but now that variables are fixed it makes more sense to have this on a static asset. Making a note for that.
deployment/terraform/keycloak.go
Outdated
|
||
// Check if users file exists and has the expected number of users. If the file has less users than expected, | ||
// we will start creating users from the next number, otherwise we will skip user creation. | ||
if _, err := os.Stat(usersTxtPath); err == nil || os.IsExist(err) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why would Stat
return an error if the file exists?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this was supposed to be os.IsNotExist
in a previous apporach or something else with a different logic and I just inherited it here, this doesn't make any sense at the moment.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Again, amazing work, @fmartingr. Nothing else to add. Thank you!
Merge it at your convenience. 😄 |
@streamer45 I've put comments on heavy logic changes that you mentioned so I can address those on separate PRs so I can merge this for now since it's getting too big and not easy to review. Thank you for reviewing! The only logic change was: 12b5487 |
Summary
KeycloakClientID
,KeycloakClientSecret
andKeycloakRealmName
(only needed when a custom import realm file is provided if the default values do not match).