diff --git a/changes/41242-jit-technician-role b/changes/41242-jit-technician-role new file mode 100644 index 0000000000..1a83261592 --- /dev/null +++ b/changes/41242-jit-technician-role @@ -0,0 +1 @@ +* Added support for JIT provisioning of the Technician role via SSO SAML attributes. diff --git a/server/fleet/sessions.go b/server/fleet/sessions.go index c3f606c0fa..6a1d5cce18 100644 --- a/server/fleet/sessions.go +++ b/server/fleet/sessions.go @@ -122,7 +122,7 @@ const ( // for setting role for a team with ID . // // For both attributes currently supported values are `admin`, `maintainer`, `observer`, -// `observer_plus` and `null`. A `null` value is used to ignore the attribute. +// `observer_plus`, `technician` and `null`. A `null` value is used to ignore the attribute. func RolesFromSSOAttributes(attributes []SAMLAttribute) (SSORolesInfo, error) { ssoRolesInfo := SSORolesInfo{} for _, attribute := range attributes { @@ -175,6 +175,7 @@ func parseRole(values []SAMLAttributeValue) (string, error) { value != RoleMaintainer && value != RoleObserver && value != RoleObserverPlus && + value != RoleTechnician && value != ssoAttrNullRoleValue { return "", fmt.Errorf("invalid role: %s", value) } diff --git a/server/fleet/sessions_test.go b/server/fleet/sessions_test.go index 2511c9016e..4549977fc6 100644 --- a/server/fleet/sessions_test.go +++ b/server/fleet/sessions_test.go @@ -314,6 +314,65 @@ func TestRolesFromSSOAttributes(t *testing.T) { }, shouldFail: true, }, + { + name: "global-technician", + attributes: []SAMLAttribute{ + { + Name: globalUserRoleSSOAttrName, + Values: []SAMLAttributeValue{ + {Value: "technician"}, + }, + }, + }, + shouldFail: false, + expectedSSORolesInfo: SSORolesInfo{ + Global: ptr.String("technician"), + }, + }, + { + name: "team-technician", + attributes: []SAMLAttribute{ + { + Name: teamUserRoleSSOAttrNamePrefix + "3", + Values: []SAMLAttributeValue{ + {Value: "technician"}, + }, + }, + }, + shouldFail: false, + expectedSSORolesInfo: SSORolesInfo{ + Teams: []TeamRole{ + { + ID: 3, + Role: "technician", + }, + }, + }, + }, + { + name: "global-gitops-not-supported-for-jit", + attributes: []SAMLAttribute{ + { + Name: globalUserRoleSSOAttrName, + Values: []SAMLAttributeValue{ + {Value: "gitops"}, + }, + }, + }, + shouldFail: true, + }, + { + name: "team-gitops-not-supported-for-jit", + attributes: []SAMLAttribute{ + { + Name: teamUserRoleSSOAttrNamePrefix + "1", + Values: []SAMLAttributeValue{ + {Value: "gitops"}, + }, + }, + }, + shouldFail: true, + }, } { t.Run(tc.name, func(t *testing.T) { ssoRolesInfo, err := RolesFromSSOAttributes(tc.attributes) diff --git a/server/service/integration_enterprise_test.go b/server/service/integration_enterprise_test.go index ba1234412a..fb31f1ba76 100644 --- a/server/service/integration_enterprise_test.go +++ b/server/service/integration_enterprise_test.go @@ -4890,6 +4890,17 @@ func (s *integrationEnterpriseTestSuite) TestSSOJITProvisioning() { assert.Equal(t, "SSO User 6", user6.Name) require.NotNil(t, user6.GlobalRole) require.Equal(t, fleet.RoleObserver, *user6.GlobalRole) + + // A user with a global technician role can be created via JIT provisioning, + // see `tools/saml/users.php` for details. + body = s.LoginSSOUser("sso_user_8_global_technician", "user123#") + require.Contains(t, body, "Redirecting to Fleet at ...") + user8, err := s.ds.UserByEmail(context.Background(), "sso_user_8_global_technician@example.com") + require.NoError(t, err) + assert.Equal(t, "sso_user_8_global_technician@example.com", user8.Email) + assert.Equal(t, "SSO User 8", user8.Name) + require.NotNil(t, user8.GlobalRole) + require.Equal(t, fleet.RoleTechnician, *user8.GlobalRole) } func (s *integrationEnterpriseTestSuite) TestDistributedReadWithFeatures() { diff --git a/server/service/sessions_test.go b/server/service/sessions_test.go index 901b96e97b..777eaf17fe 100644 --- a/server/service/sessions_test.go +++ b/server/service/sessions_test.go @@ -449,6 +449,71 @@ func TestGetSSOUser(t *testing.T) { _, err = svc.GetSSOUser(ctx, auth) require.Error(t, err) + + // (5) Test JIT provisioning with global technician role. + + ds.AppConfigFunc = func(ctx context.Context) (*fleet.AppConfig, error) { + return &fleet.AppConfig{ + SSOSettings: &fleet.SSOSettings{ + EnableSSO: true, + EnableSSOIdPLogin: true, + EnableJITProvisioning: true, + }, + }, nil + } + + newUser = nil + ds.UserByEmailFunc = func(ctx context.Context, email string) (*fleet.User, error) { + return nil, newNotFoundError() + } + ds.NewUserFuncInvoked = false + + auth.assertionAttributes = []fleet.SAMLAttribute{ + { + Name: "FLEET_JIT_USER_ROLE_GLOBAL", + Values: []fleet.SAMLAttributeValue{ + {Value: "technician"}, + }, + }, + } + + _, err = svc.GetSSOUser(ctx, auth) + require.NoError(t, err) + + require.NotNil(t, newUser) + require.NotNil(t, newUser.GlobalRole) + require.Equal(t, fleet.RoleTechnician, *newUser.GlobalRole) + require.Empty(t, newUser.Teams) + + // (6) Test JIT provisioning with team technician role. + + newUser = nil + ds.UserByEmailFunc = func(ctx context.Context, email string) (*fleet.User, error) { + return nil, newNotFoundError() + } + ds.NewUserFuncInvoked = false + + ds.TeamWithExtrasFunc = func(ctx context.Context, tid uint) (*fleet.Team, error) { + return &fleet.Team{ID: tid}, nil + } + + auth.assertionAttributes = []fleet.SAMLAttribute{ + { + Name: "FLEET_JIT_USER_ROLE_TEAM_1", + Values: []fleet.SAMLAttributeValue{ + {Value: "technician"}, + }, + }, + } + + _, err = svc.GetSSOUser(ctx, auth) + require.NoError(t, err) + + require.NotNil(t, newUser) + require.Nil(t, newUser.GlobalRole) + require.Len(t, newUser.Teams, 1) + require.Equal(t, uint(1), newUser.Teams[0].ID) + require.Equal(t, fleet.RoleTechnician, newUser.Teams[0].Role) } func TestInitiateSSOWithSSOServerURL(t *testing.T) { diff --git a/tools/saml/users.php b/tools/saml/users.php index a77a513ba9..986bc078d1 100644 --- a/tools/saml/users.php +++ b/tools/saml/users.php @@ -68,6 +68,14 @@ $config = array( 'eduPersonAffiliation' => array('group1'), 'email' => 'sso_user_no_displayname@example.com', ), + // sso_user_8_global_technician has FLEET_JIT_USER_ROLE_GLOBAL attribute to be added as global technician. + 'sso_user_8_global_technician:user123#' => array( + 'uid' => array('8'), + 'eduPersonAffiliation' => array('group1'), + 'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name' => array('SSO User 8'), + 'email' => 'sso_user_8_global_technician@example.com', + 'FLEET_JIT_USER_ROLE_GLOBAL' => 'technician', + ), ), );