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

Add OpenID authentication support using a KeyCloak server #720

Merged
merged 70 commits into from
May 2, 2024
Merged
Show file tree
Hide file tree
Changes from 61 commits
Commits
Show all changes
70 commits
Select commit Hold shift + click to select a range
ff6685e
added userentity authentication type
fmartingr Mar 21, 2024
3b8b843
more descriptive error messages
fmartingr Mar 21, 2024
6f90dcc
handle login using openid from users file
fmartingr Mar 21, 2024
919149c
keycloak provisioning (wip)
fmartingr Mar 27, 2024
a861efe
use updateAppConfig
fmartingr Apr 1, 2024
7c43c23
added sample keycloak realm
fmartingr Apr 2, 2024
b3e5fda
setup openid from config
fmartingr Apr 2, 2024
8217a3d
setup default realm, disable ssl
fmartingr Apr 2, 2024
8cf9d2a
health argument directly in call
fmartingr Apr 2, 2024
5446c20
development by default
fmartingr Apr 2, 2024
56c7df7
docs wip
fmartingr Apr 2, 2024
343e055
disable ssl in master realm and configured realm
fmartingr Apr 3, 2024
a190c7a
autogenerate keycloak users
fmartingr Apr 3, 2024
066e964
override UsersFilePath instead of log
fmartingr Apr 4, 2024
9b4cee4
more docs
fmartingr Apr 4, 2024
b5ac989
jvm settings for large imports
fmartingr Apr 8, 2024
0b8b574
realm import improvements
fmartingr Apr 9, 2024
babebb7
disable ssl in default realm
fmartingr Apr 9, 2024
c8a37f0
updated assets
fmartingr Apr 9, 2024
5dbf56d
Update docs/external_auth_providers.md
fmartingr Apr 10, 2024
f5ac82f
db set to postgres
fmartingr Apr 10, 2024
6e19432
remove default for instance count in tf
fmartingr Apr 10, 2024
e041458
apply suggestion
fmartingr Apr 10, 2024
28c3beb
moved keycloakEnvFileContents closer to usage
fmartingr Apr 10, 2024
aadc041
return early if token is found, err otherwise
fmartingr Apr 10, 2024
003f979
removed now unused var
fmartingr Apr 10, 2024
4a454d2
keycloak db dump support
fmartingr Apr 10, 2024
e556ce6
make assets
fmartingr Apr 10, 2024
20dbc81
remove keycloak_db_instance_engine
fmartingr Apr 10, 2024
49c02dc
remove variable usage
fmartingr Apr 10, 2024
a611473
fixed family
fmartingr Apr 10, 2024
5bbd2a6
import keycloak db dump on keycloak config
fmartingr Apr 10, 2024
49605ea
fixed ingest commands
fmartingr Apr 10, 2024
dc4191d
ingest keycloak db on create only
fmartingr Apr 10, 2024
8182e44
updated docs
fmartingr Apr 10, 2024
eadca08
using bool instead of count to enable the keycloak deployment
fmartingr Apr 10, 2024
c7b08b3
install realm only when no dump is provided
fmartingr Apr 11, 2024
2b9526f
better logging
fmartingr Apr 15, 2024
c54a0a9
add hosts file to agent nodes as well
fmartingr Apr 15, 2024
b1de1d2
psql database setup
fmartingr Apr 15, 2024
bdd340e
disable specific changes using dump
fmartingr Apr 15, 2024
7a86020
get user when authenticating
fmartingr Apr 16, 2024
b3f7dae
simplified keycloak database setup
fmartingr Apr 17, 2024
67d29d4
added metrics
fmartingr Apr 17, 2024
4024790
Merge remote-tracking branch 'origin/master' into feat/openid-users
fmartingr Apr 18, 2024
49ece26
simplify logic and avoid panics
fmartingr Apr 22, 2024
95ccbe5
typo
fmartingr Apr 22, 2024
791d40c
fixed metrics path
fmartingr Apr 22, 2024
5011569
requested changes
fmartingr Apr 22, 2024
4c08af4
Merge remote-tracking branch 'origin/master' into feat/openid-users
fmartingr Apr 22, 2024
4b35f96
fixed database upload
fmartingr Apr 23, 2024
50842ad
ignore exit 5 when stopping keycloak while creating the database
fmartingr Apr 23, 2024
fec4c6e
main function should start keycloak
fmartingr Apr 23, 2024
9ed759f
Merge remote-tracking branch 'origin/master' into feat/openid-users
fmartingr Apr 24, 2024
25c3ba0
allow provisioning of extra SQL files for the mattermost db
fmartingr Apr 24, 2024
ad1fb66
remove path in prometheus target
fmartingr Apr 25, 2024
e9a5945
typo in default annotation
fmartingr Apr 25, 2024
c9aa2ba
add ON_ERROR_STOP to keycloak dump
fmartingr Apr 25, 2024
2384ca6
use siteurl for agent configuration if set up
fmartingr Apr 26, 2024
9de8440
handle failures in keycloak sql scripts
fmartingr Apr 26, 2024
7ca520b
lint
fmartingr Apr 26, 2024
19c839a
fixed site url logic on agents
fmartingr Apr 29, 2024
cba76a8
refactored server url logic to be testable
fmartingr Apr 29, 2024
e9115db
switch keycloak instance type to c7i
fmartingr Apr 30, 2024
1c83664
add externalauthprovidersetting to deployer sample
fmartingr Apr 30, 2024
12b5487
don't initialise unnecessary clients
fmartingr May 2, 2024
d812cea
close ssh client
fmartingr May 2, 2024
ffc628b
TODOs
fmartingr May 2, 2024
e7a406f
TODO
fmartingr May 2, 2024
9fccc75
add reference to users file in docs
fmartingr May 2, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 26 additions & 10 deletions api/agent.go
Original file line number Diff line number Diff line change
Expand Up @@ -363,7 +363,7 @@ func NewControllerWrapper(config *loadtest.Config, controllerConfig interface{},

creds, err := getUserCredentials(config.UsersConfiguration.UsersFilePath, config)
if err != nil {
return nil, err
return nil, fmt.Errorf("error getting user credentials from file: %w", err)
}

modAdmins := 0
Expand All @@ -373,7 +373,7 @@ func NewControllerWrapper(config *loadtest.Config, controllerConfig interface{},

err = createCustomEmoji(config)
if err != nil {
return nil, err
return nil, fmt.Errorf("error creating custom emoji from config: %w", err)
fmartingr marked this conversation as resolved.
Show resolved Hide resolved
}
mlog.Info("Custom emoji created")

Expand All @@ -383,6 +383,7 @@ func NewControllerWrapper(config *loadtest.Config, controllerConfig interface{},
username := fmt.Sprintf("%s-%d", namePrefix, id)
email := fmt.Sprintf("%s-%[email protected]", namePrefix, id)
password := "testPass123$"
authenticationType := userentity.AuthenticationTypeMattermost

if modAdmins > 0 && id%modAdmins == 0 {
username = ""
Expand All @@ -395,15 +396,30 @@ func NewControllerWrapper(config *loadtest.Config, controllerConfig interface{},
username = creds[id].username
email = creds[id].email
password = creds[id].password

// 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"
Comment on lines +400 to +402
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 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]
}
}
Comment on lines +404 to +412
Copy link
Contributor

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.

Copy link
Contributor Author

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.

}

ueConfig := userentity.Config{
ServerURL: config.ConnectionConfiguration.ServerURL,
WebSocketURL: config.ConnectionConfiguration.WebSocketURL,
Username: username,
Email: email,
Password: password,
ServerURL: config.ConnectionConfiguration.ServerURL,
WebSocketURL: config.ConnectionConfiguration.WebSocketURL,
AuthenticationType: authenticationType,
Username: username,
Email: email,
Password: password,
}

store, err := memstore.New(&memstore.Config{
MaxStoredPosts: 250,
MaxStoredUsers: 500,
Expand All @@ -413,11 +429,11 @@ func NewControllerWrapper(config *loadtest.Config, controllerConfig interface{},
MaxStoredReactions: 10,
})
if err != nil {
return nil, err
return nil, fmt.Errorf("error creating memory store: %w", err)
}

if err := store.SetServerVersion(serverVersion); err != nil {
return nil, err
return nil, fmt.Errorf("error setting server version: %w", err)
}

ueSetup := userentity.Setup{
Expand Down Expand Up @@ -527,7 +543,7 @@ func createCustomEmoji(config *loadtest.Config) error {
}
sysadmin := createSysAdmin(adminStore, config)
if err := sysadmin.Login(); err != nil {
return err
return fmt.Errorf("error login as sysadmin: %w", err)
}

emoji := &model.Emoji{
Expand Down
46 changes: 46 additions & 0 deletions deployment/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ type Config struct {
ExternalDBSettings ExternalDBSettings
// External bucket connection settings.
ExternalBucketSettings ExternalBucketSettings
// ExternalAuthProviderSettings contains the settings for configuring an external auth provider.
ExternalAuthProviderSettings ExternalAuthProviderSettings
// URL from where to download Mattermost release.
// This can also point to a local binary path if the user wants to run loadtest
// on a custom build. The path should be prefixed with "file://". In that case,
Expand Down Expand Up @@ -87,6 +89,11 @@ type Config struct {
// This can also point to a local file if prefixed with "file://".
// In such case, the dump file will be uploaded to the app servers.
DBDumpURI string `default:""`
// DBExtraSQL are optional URIs to SQL files containing SQL statements to be applied
// to the Mattermost database.
// The file is expected to be gzip compressed.
// This can also point to a local file if prefixed with "file://".
DBExtraSQL []string `default:"[]"`
// An optional host name that will:
// - Override the SiteUrl
// - Point to the proxy IP via a new entry in the server's /etc/hosts file
Expand Down Expand Up @@ -114,6 +121,8 @@ type StorageSizes struct {
Job int `default:"50"`
// Size, in GiB, for the storage of the elasticsearch instances
ElasticSearch int `default:"20"`
// Size, in GiB, for the storage of the keycloak instances
KeyCloak int `default:"10"`
}

// PyroscopeSettings contains flags to enable/disable the profiling
Expand Down Expand Up @@ -179,6 +188,43 @@ type ExternalBucketSettings struct {
AmazonS3SSE bool `default:"false"`
}

// ExternalAuthProviderSettings contains the necessary data
// to configure an external auth provider.
type ExternalAuthProviderSettings struct {
// Enabled is set to true if the external auth provider should be enabled.
Enabled bool `default:"false"`
// DevelopmentMode is set to true if the keycloak instance should be started in development mode.
DevelopmentMode bool `default:"true"`
Copy link
Member

Choose a reason for hiding this comment

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

Should it not be "false" by default? The default config is always aimed towards production usage.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I left it as false because the difference development vs production means. If I use the production option I have to disable some of the features it enables manually (like enforced hostnames, SSL, local cache). I could go that route but in the end we would have the same result but with more work. That is, unless we want to really run it in production mode with all security features enforced as they should be.

// KeycloakVersion is the version of keycloak to deploy.
KeycloakVersion string `default:"24.0.2"`
// KeycloakInstanceType is the type of the EC2 instance for keycloak.
InstanceType string `default:"c5.xlarge"`
agnivade marked this conversation as resolved.
Show resolved Hide resolved
// KeycloakAdminUser is the username of the keycloak admin interface (admin on the master realm)
KeycloakAdminUser string `default:"mmuser" validate:"notempty"`
// KeycloakAdminPassword is the password of the keycloak admin interface (admin on the master realm)
KeycloakAdminPassword string `default:"mmpass" validate:"notempty"`
// KeycloakRealmFilePath is the path to the realm file to be uploaded to the keycloak instance.
// If empty, a default realm file will be used.
KeycloakRealmFilePath string `default:""`
// KeycloakDBDumpURI
// An optional URI to a keycloak database dump file to be uploaded on environment
// creation.
// The file is expected to be gzip compressed.
// This can also point to a local file if prefixed with "file://".
KeycloakDBDumpURI string `default:""`
// GenerateUsersCount is the number of users to generate in the keycloak instance.
GenerateUsersCount int `default:"0" validate:"range:[0,)"`
// KeycloakRealmName is the name of the realm to be used in Mattermost. Must exist in the keycloak instance.
// It is used when creating users and to properly set the OpenID configuration in Mattermost.
KeycloakRealmName string `default:"mattermost"`
// KeycloakClientID is the client id to be used in Mattermost from the above realm.
// Must exist in the keycloak instance
KeycloakClientID string `default:"mattermost-openid"`
// KeycloakClientSecret is the client secret from the above realm to be used in Mattermost.
// Must exist in the keycloak instance
KeycloakClientSecret string `default:"qbdUj4dacwfa5sIARIiXZxbsBFoopTyf"`
agarciamontoro marked this conversation as resolved.
Show resolved Hide resolved
}

// ElasticSearchSettings contains the necessary data
// to configure an ElasticSearch instance to be deployed
// and provisioned.
Expand Down
25 changes: 24 additions & 1 deletion deployment/terraform/agent.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,9 @@ func (t *Terraform) generateLoadtestAgentConfig() (*loadtest.Config, error) {
return nil, err
}
url := t.output.Instances[0].PrivateIP + ":8065"
if t.output.HasProxy() {
if t.config.SiteURL != "" {
agnivade marked this conversation as resolved.
Show resolved Hide resolved
url = t.config.SiteURL
} else if t.output.HasProxy() {
url = t.output.Proxy.PrivateIP
}

Expand Down Expand Up @@ -129,6 +131,27 @@ func (t *Terraform) configureAndRunAgents(extAgent *ssh.ExtAgent) error {
batch = append(batch, uploadInfo{srcData: strings.Join(splitFiles[i], "\n"), dstPath: dstUsersFilePath, msg: "Uploading list of users credentials"})
}

// If SiteURL is set, update /etc/hosts to point to the correct IP
if t.config.SiteURL != "" {
output, err := t.Output()
if err != nil {
return err
}

// The new entry in /etc/hosts will make SiteURL point to:
// - The first instance's IP if there's a single node
// - The proxy's IP if there's more than one node
ip := output.Instances[0].PrivateIP
if output.HasProxy() {
ip = output.Proxy.PrivateIP
}

proxyHost := fmt.Sprintf("%s %s\n", ip, t.config.SiteURL)
appHostsFile := fmt.Sprintf(appHosts, proxyHost)

batch = append(batch, uploadInfo{srcData: appHostsFile, dstPath: "/etc/hosts", msg: "Updating /etc/hosts to point to the correct IP"})
}
Comment on lines +130 to +149
Copy link
Member

Choose a reason for hiding this comment

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

Can you remind me why we need the agents to update their hosts file with the SiteURL?

Copy link
Member

Choose a reason for hiding this comment

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

I think we've had a discussion here: https://community.mattermost.com/core/pl/1944im15z3f1pn9thb647kdi5y. I am 0/5 as long as it doesn't break permalinks.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Is there an straightforward way for me to check that permalinks are working?

Copy link
Member

Choose a reason for hiding this comment

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

Changing the agents host will not break permalinks: the problem was with the app nodes trying to retrieve the data from the permalink. As long as we don't touch that, permalinks will keep working.


if err := uploadBatch(sshc, batch); err != nil {
return fmt.Errorf("batch upload failed: %w", err)
}
Expand Down
Loading
Loading