mirror of
https://salsa.debian.org/freeipa-team/freeipa.git
synced 2026-07-29 15:55:47 -05:00
group: allow services as members of groups
Allow services to be members of the groups, like users and other groups can already be. This is required for use cases where such services aren't associated with a particular host (and thus, the host object cannot be used to retrieve the keytabs) but represent purely client Kerberos principals to use in a dynamically generated environment such as Kubernetes. Fixes: https://pagure.io/freeipa/issue/7513 Reviewed-By: Rob Crittenden <rcritten@redhat.com>
This commit is contained in:
committed by
Rob Crittenden
parent
9e8fb94e87
commit
e642865717
@@ -1944,13 +1944,14 @@ output: Entry('result')
|
|||||||
output: Output('summary', type=[<type 'unicode'>, <type 'NoneType'>])
|
output: Output('summary', type=[<type 'unicode'>, <type 'NoneType'>])
|
||||||
output: PrimaryKey('value')
|
output: PrimaryKey('value')
|
||||||
command: group_add_member/1
|
command: group_add_member/1
|
||||||
args: 1,7,3
|
args: 1,8,3
|
||||||
arg: Str('cn', cli_name='group_name')
|
arg: Str('cn', cli_name='group_name')
|
||||||
option: Flag('all', autofill=True, cli_name='all', default=False)
|
option: Flag('all', autofill=True, cli_name='all', default=False)
|
||||||
option: Str('group*', alwaysask=True, cli_name='groups')
|
option: Str('group*', alwaysask=True, cli_name='groups')
|
||||||
option: Str('ipaexternalmember*', cli_name='external')
|
option: Str('ipaexternalmember*', cli_name='external')
|
||||||
option: Flag('no_members', autofill=True, default=False)
|
option: Flag('no_members', autofill=True, default=False)
|
||||||
option: Flag('raw', autofill=True, cli_name='raw', default=False)
|
option: Flag('raw', autofill=True, cli_name='raw', default=False)
|
||||||
|
option: Str('service*', alwaysask=True, cli_name='services')
|
||||||
option: Str('user*', alwaysask=True, cli_name='users')
|
option: Str('user*', alwaysask=True, cli_name='users')
|
||||||
option: Str('version?')
|
option: Str('version?')
|
||||||
output: Output('completed', type=[<type 'int'>])
|
output: Output('completed', type=[<type 'int'>])
|
||||||
@@ -1972,7 +1973,7 @@ output: Output('result', type=[<type 'bool'>])
|
|||||||
output: Output('summary', type=[<type 'unicode'>, <type 'NoneType'>])
|
output: Output('summary', type=[<type 'unicode'>, <type 'NoneType'>])
|
||||||
output: PrimaryKey('value')
|
output: PrimaryKey('value')
|
||||||
command: group_find/1
|
command: group_find/1
|
||||||
args: 1,28,4
|
args: 1,30,4
|
||||||
arg: Str('criteria?')
|
arg: Str('criteria?')
|
||||||
option: Flag('all', autofill=True, cli_name='all', default=False)
|
option: Flag('all', autofill=True, cli_name='all', default=False)
|
||||||
option: Str('cn?', autofill=False, cli_name='group_name')
|
option: Str('cn?', autofill=False, cli_name='group_name')
|
||||||
@@ -1987,6 +1988,7 @@ option: Str('in_role*', cli_name='in_roles')
|
|||||||
option: Str('in_sudorule*', cli_name='in_sudorules')
|
option: Str('in_sudorule*', cli_name='in_sudorules')
|
||||||
option: Str('no_group*', cli_name='no_groups')
|
option: Str('no_group*', cli_name='no_groups')
|
||||||
option: Flag('no_members', autofill=True, default=True)
|
option: Flag('no_members', autofill=True, default=True)
|
||||||
|
option: Principal('no_service*', cli_name='no_services')
|
||||||
option: Str('no_user*', cli_name='no_users')
|
option: Str('no_user*', cli_name='no_users')
|
||||||
option: Flag('nonposix', autofill=True, cli_name='nonposix', default=False)
|
option: Flag('nonposix', autofill=True, cli_name='nonposix', default=False)
|
||||||
option: Str('not_in_group*', cli_name='not_in_groups')
|
option: Str('not_in_group*', cli_name='not_in_groups')
|
||||||
@@ -1998,6 +2000,7 @@ option: Flag('pkey_only?', autofill=True, default=False)
|
|||||||
option: Flag('posix', autofill=True, cli_name='posix', default=False)
|
option: Flag('posix', autofill=True, cli_name='posix', default=False)
|
||||||
option: Flag('private', autofill=True, cli_name='private', default=False)
|
option: Flag('private', autofill=True, cli_name='private', default=False)
|
||||||
option: Flag('raw', autofill=True, cli_name='raw', default=False)
|
option: Flag('raw', autofill=True, cli_name='raw', default=False)
|
||||||
|
option: Principal('service*', cli_name='services')
|
||||||
option: Int('sizelimit?', autofill=False)
|
option: Int('sizelimit?', autofill=False)
|
||||||
option: Int('timelimit?', autofill=False)
|
option: Int('timelimit?', autofill=False)
|
||||||
option: Str('user*', cli_name='users')
|
option: Str('user*', cli_name='users')
|
||||||
@@ -2026,13 +2029,14 @@ output: Entry('result')
|
|||||||
output: Output('summary', type=[<type 'unicode'>, <type 'NoneType'>])
|
output: Output('summary', type=[<type 'unicode'>, <type 'NoneType'>])
|
||||||
output: PrimaryKey('value')
|
output: PrimaryKey('value')
|
||||||
command: group_remove_member/1
|
command: group_remove_member/1
|
||||||
args: 1,7,3
|
args: 1,8,3
|
||||||
arg: Str('cn', cli_name='group_name')
|
arg: Str('cn', cli_name='group_name')
|
||||||
option: Flag('all', autofill=True, cli_name='all', default=False)
|
option: Flag('all', autofill=True, cli_name='all', default=False)
|
||||||
option: Str('group*', alwaysask=True, cli_name='groups')
|
option: Str('group*', alwaysask=True, cli_name='groups')
|
||||||
option: Str('ipaexternalmember*', cli_name='external')
|
option: Str('ipaexternalmember*', cli_name='external')
|
||||||
option: Flag('no_members', autofill=True, default=False)
|
option: Flag('no_members', autofill=True, default=False)
|
||||||
option: Flag('raw', autofill=True, cli_name='raw', default=False)
|
option: Flag('raw', autofill=True, cli_name='raw', default=False)
|
||||||
|
option: Str('service*', alwaysask=True, cli_name='services')
|
||||||
option: Str('user*', alwaysask=True, cli_name='users')
|
option: Str('user*', alwaysask=True, cli_name='users')
|
||||||
option: Str('version?')
|
option: Str('version?')
|
||||||
output: Output('completed', type=[<type 'int'>])
|
output: Output('completed', type=[<type 'int'>])
|
||||||
|
|||||||
@@ -128,6 +128,10 @@ return {
|
|||||||
$type: 'association',
|
$type: 'association',
|
||||||
name: 'member_group'
|
name: 'member_group'
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
$type: 'association',
|
||||||
|
name: 'member_service'
|
||||||
|
},
|
||||||
{
|
{
|
||||||
$type: 'attribute',
|
$type: 'attribute',
|
||||||
name: 'member_external',
|
name: 'member_external',
|
||||||
|
|||||||
@@ -62,10 +62,10 @@ if api.env.in_server and api.env.context in ['lite', 'server']:
|
|||||||
__doc__ = _("""
|
__doc__ = _("""
|
||||||
Groups of users
|
Groups of users
|
||||||
|
|
||||||
Manage groups of users. By default, new groups are POSIX groups. You
|
Manage groups of users, groups, or services. By default, new groups are POSIX
|
||||||
can add the --nonposix option to the group-add command to mark a new group
|
groups. You can add the --nonposix option to the group-add command to mark a
|
||||||
as non-POSIX. You can use the --posix argument with the group-mod command
|
new group as non-POSIX. You can use the --posix argument with the group-mod
|
||||||
to convert a non-POSIX group into a POSIX group. POSIX groups cannot be
|
command to convert a non-POSIX group into a POSIX group. POSIX groups cannot be
|
||||||
converted to non-POSIX groups.
|
converted to non-POSIX groups.
|
||||||
|
|
||||||
Every group must have a description.
|
Every group must have a description.
|
||||||
@@ -75,6 +75,10 @@ supported but can have an impact on your file permissions. It is not necessary
|
|||||||
to supply a GID when creating a group. IPA will generate one automatically
|
to supply a GID when creating a group. IPA will generate one automatically
|
||||||
if it is not provided.
|
if it is not provided.
|
||||||
|
|
||||||
|
Groups members can be users, other groups, and Kerberos services. In POSIX
|
||||||
|
environments only users will be visible as group members, but nested groups and
|
||||||
|
groups of services can be used for IPA management purposes.
|
||||||
|
|
||||||
EXAMPLES:
|
EXAMPLES:
|
||||||
|
|
||||||
Add a new group:
|
Add a new group:
|
||||||
@@ -101,6 +105,9 @@ EXAMPLES:
|
|||||||
Add multiple users to the "localadmins" group:
|
Add multiple users to the "localadmins" group:
|
||||||
ipa group-add-member --users=test1 --users=test2 localadmins
|
ipa group-add-member --users=test1 --users=test2 localadmins
|
||||||
|
|
||||||
|
To add Kerberos services to the "printer admins" group:
|
||||||
|
ipa group-add-member --services=CUPS/some.host printeradmins
|
||||||
|
|
||||||
Remove a user from the "localadmins" group:
|
Remove a user from the "localadmins" group:
|
||||||
ipa group-remove-member --users=test2 localadmins
|
ipa group-remove-member --users=test2 localadmins
|
||||||
|
|
||||||
@@ -171,9 +178,9 @@ class group(LDAPObject):
|
|||||||
]
|
]
|
||||||
uuid_attribute = 'ipauniqueid'
|
uuid_attribute = 'ipauniqueid'
|
||||||
attribute_members = {
|
attribute_members = {
|
||||||
'member': ['user', 'group'],
|
'member': ['user', 'group', 'service'],
|
||||||
'memberof': ['group', 'netgroup', 'role', 'hbacrule', 'sudorule'],
|
'memberof': ['group', 'netgroup', 'role', 'hbacrule', 'sudorule'],
|
||||||
'memberindirect': ['user', 'group'],
|
'memberindirect': ['user', 'group', 'service'],
|
||||||
'memberofindirect': ['group', 'netgroup', 'role', 'hbacrule',
|
'memberofindirect': ['group', 'netgroup', 'role', 'hbacrule',
|
||||||
'sudorule'],
|
'sudorule'],
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -625,7 +625,7 @@ class service_add(LDAPCreate):
|
|||||||
except errors.NotFound:
|
except errors.NotFound:
|
||||||
raise errors.NotFound(reason=_(
|
raise errors.NotFound(reason=_(
|
||||||
"The host '%s' does not exist to add a service to.") %
|
"The host '%s' does not exist to add a service to.") %
|
||||||
hostname)
|
hostname)
|
||||||
|
|
||||||
self.obj.validate_ipakrbauthzdata(entry_attrs)
|
self.obj.validate_ipakrbauthzdata(entry_attrs)
|
||||||
|
|
||||||
|
|||||||
@@ -320,6 +320,7 @@ class test_netgroup(Declarative):
|
|||||||
member=dict(
|
member=dict(
|
||||||
group=tuple(),
|
group=tuple(),
|
||||||
user=tuple(),
|
user=tuple(),
|
||||||
|
service=tuple(),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
result={
|
result={
|
||||||
|
|||||||
@@ -240,6 +240,7 @@ class test_selinuxusermap(Declarative):
|
|||||||
member=dict(
|
member=dict(
|
||||||
group=tuple(),
|
group=tuple(),
|
||||||
user=tuple(),
|
user=tuple(),
|
||||||
|
service=tuple(),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
result={
|
result={
|
||||||
|
|||||||
@@ -890,6 +890,7 @@ class test_service_allowed_to(Declarative):
|
|||||||
cleanup_commands = [
|
cleanup_commands = [
|
||||||
('user_del', [user1], {}),
|
('user_del', [user1], {}),
|
||||||
('user_del', [user2], {}),
|
('user_del', [user2], {}),
|
||||||
|
('service_del', [d_service], {}),
|
||||||
('group_del', [group1], {}),
|
('group_del', [group1], {}),
|
||||||
('group_del', [group2], {}),
|
('group_del', [group2], {}),
|
||||||
('host_del', [fqdn1], {}),
|
('host_del', [fqdn1], {}),
|
||||||
@@ -938,6 +939,40 @@ class test_service_allowed_to(Declarative):
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
# Create a service disconnected from any host
|
||||||
|
dict(
|
||||||
|
desc='Try to create service %r without any host' % d_service,
|
||||||
|
command=('service_add', [d_service],
|
||||||
|
dict(force=True, skip_host_check=True)),
|
||||||
|
expected=dict(
|
||||||
|
value=d_service,
|
||||||
|
summary=u'Added service "%s"' % d_service,
|
||||||
|
result=dict(
|
||||||
|
dn=d_servicedn,
|
||||||
|
krbprincipalname=[d_service],
|
||||||
|
krbcanonicalname=[d_service],
|
||||||
|
objectclass=objectclasses.service,
|
||||||
|
ipauniqueid=[fuzzy_uuid],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
dict(
|
||||||
|
desc='Add service %r to a group: %r' % (d_service, group1),
|
||||||
|
command=('group_add_member', [group1],
|
||||||
|
dict(service=[d_service_no_realm])),
|
||||||
|
expected=dict(
|
||||||
|
completed=1,
|
||||||
|
failed=dict(member=dict(group=[],
|
||||||
|
service=[],
|
||||||
|
user=[])),
|
||||||
|
result=dict(
|
||||||
|
cn=[group1],
|
||||||
|
gidnumber=[fuzzy_digits],
|
||||||
|
dn=group1_dn,
|
||||||
|
member_service=[d_service],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
dict(
|
dict(
|
||||||
desc='Create group: %r' % group2,
|
desc='Create group: %r' % group2,
|
||||||
command=(
|
command=(
|
||||||
|
|||||||
@@ -12,9 +12,10 @@ from ipatests.util import assert_deepequal, get_group_dn
|
|||||||
class GroupTracker(Tracker):
|
class GroupTracker(Tracker):
|
||||||
""" Class for host plugin like tests """
|
""" Class for host plugin like tests """
|
||||||
retrieve_keys = {u'dn', u'cn', u'gidnumber', u'member_user',
|
retrieve_keys = {u'dn', u'cn', u'gidnumber', u'member_user',
|
||||||
u'member_group', u'description',
|
u'member_group', u'member_service', u'description',
|
||||||
u'memberof_group', u'memberofindirect_group',
|
u'memberof_group', u'memberofindirect_group',
|
||||||
u'memberindirect_group', u'memberindirect_user'}
|
u'memberindirect_group', u'memberindirect_user',
|
||||||
|
u'memberindirect_service'}
|
||||||
|
|
||||||
retrieve_all_keys = retrieve_keys | {u'ipauniqueid', u'objectclass'}
|
retrieve_all_keys = retrieve_keys | {u'ipauniqueid', u'objectclass'}
|
||||||
|
|
||||||
@@ -112,7 +113,7 @@ class GroupTracker(Tracker):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def add_member(self, options):
|
def add_member(self, options):
|
||||||
""" Add a member (group OR user) and performs check """
|
""" Add a member (group OR user OR service) and performs check """
|
||||||
if u'user' in options:
|
if u'user' in options:
|
||||||
try:
|
try:
|
||||||
self.attrs[u'member_user'] =\
|
self.attrs[u'member_user'] =\
|
||||||
@@ -125,6 +126,12 @@ class GroupTracker(Tracker):
|
|||||||
self.attrs[u'member_group'] + [options[u'group']]
|
self.attrs[u'member_group'] + [options[u'group']]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
self.attrs[u'member_group'] = [options[u'group']]
|
self.attrs[u'member_group'] = [options[u'group']]
|
||||||
|
elif u'service' in options:
|
||||||
|
try:
|
||||||
|
self.attrs[u'member_service'] =\
|
||||||
|
self.attrs[u'member_service'] + [options[u'service']]
|
||||||
|
except KeyError:
|
||||||
|
self.attrs[u'member_service'] = [options[u'service']]
|
||||||
|
|
||||||
command = self.make_add_member_command(options)
|
command = self.make_add_member_command(options)
|
||||||
result = command()
|
result = command()
|
||||||
@@ -136,6 +143,8 @@ class GroupTracker(Tracker):
|
|||||||
self.attrs[u'member_user'].remove(options[u'user'])
|
self.attrs[u'member_user'].remove(options[u'user'])
|
||||||
elif u'group' in options:
|
elif u'group' in options:
|
||||||
self.attrs[u'member_group'].remove(options[u'group'])
|
self.attrs[u'member_group'].remove(options[u'group'])
|
||||||
|
elif u'service' in options:
|
||||||
|
self.attrs[u'member_service'].remove(options[u'service'])
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if not self.attrs[u'member_user']:
|
if not self.attrs[u'member_user']:
|
||||||
@@ -147,6 +156,11 @@ class GroupTracker(Tracker):
|
|||||||
del self.attrs[u'member_group']
|
del self.attrs[u'member_group']
|
||||||
except KeyError:
|
except KeyError:
|
||||||
pass
|
pass
|
||||||
|
try:
|
||||||
|
if not self.attrs[u'member_service']:
|
||||||
|
del self.attrs[u'member_service']
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
|
||||||
command = self.make_remove_member_command(options)
|
command = self.make_remove_member_command(options)
|
||||||
result = command()
|
result = command()
|
||||||
@@ -207,7 +221,7 @@ class GroupTracker(Tracker):
|
|||||||
""" Checks 'group_add_member' command result """
|
""" Checks 'group_add_member' command result """
|
||||||
assert_deepequal(dict(
|
assert_deepequal(dict(
|
||||||
completed=1,
|
completed=1,
|
||||||
failed={u'member': {u'group': (), u'user': ()}},
|
failed={u'member': {u'group': (), u'user': (), u'service': ()}},
|
||||||
result=self.filter_attrs(self.add_member_keys)
|
result=self.filter_attrs(self.add_member_keys)
|
||||||
), result)
|
), result)
|
||||||
|
|
||||||
@@ -216,7 +230,7 @@ class GroupTracker(Tracker):
|
|||||||
when expected result is failure of the operation"""
|
when expected result is failure of the operation"""
|
||||||
expected = dict(
|
expected = dict(
|
||||||
completed=0,
|
completed=0,
|
||||||
failed={u'member': {u'group': (), u'user': ()}},
|
failed={u'member': {u'group': (), u'user': (), u'service': ()}},
|
||||||
result=self.filter_attrs(self.add_member_keys)
|
result=self.filter_attrs(self.add_member_keys)
|
||||||
)
|
)
|
||||||
if not options:
|
if not options:
|
||||||
@@ -230,6 +244,9 @@ class GroupTracker(Tracker):
|
|||||||
elif u'group' in options:
|
elif u'group' in options:
|
||||||
expected[u'failed'][u'member'][u'group'] = [(
|
expected[u'failed'][u'member'][u'group'] = [(
|
||||||
options[u'group'], u'no such entry')]
|
options[u'group'], u'no such entry')]
|
||||||
|
elif u'service' in options:
|
||||||
|
expected[u'failed'][u'member'][u'service'] = [(
|
||||||
|
options[u'service'], u'no such entry')]
|
||||||
|
|
||||||
assert_deepequal(expected, result)
|
assert_deepequal(expected, result)
|
||||||
|
|
||||||
@@ -238,7 +255,7 @@ class GroupTracker(Tracker):
|
|||||||
when expected result is failure of the operation"""
|
when expected result is failure of the operation"""
|
||||||
expected = dict(
|
expected = dict(
|
||||||
completed=0,
|
completed=0,
|
||||||
failed={u'member': {u'group': (), u'user': ()}},
|
failed={u'member': {u'group': (), u'user': (), u'service': ()}},
|
||||||
result=self.filter_attrs(self.add_member_keys)
|
result=self.filter_attrs(self.add_member_keys)
|
||||||
)
|
)
|
||||||
if u'user' in options:
|
if u'user' in options:
|
||||||
@@ -247,6 +264,9 @@ class GroupTracker(Tracker):
|
|||||||
elif u'group' in options:
|
elif u'group' in options:
|
||||||
expected[u'failed'][u'member'][u'group'] = [(
|
expected[u'failed'][u'member'][u'group'] = [(
|
||||||
options[u'group'], u'This entry is not a member')]
|
options[u'group'], u'This entry is not a member')]
|
||||||
|
elif u'service' in options:
|
||||||
|
expected[u'failed'][u'member'][u'service'] = [(
|
||||||
|
options[u'service'], u'This entry is not a member')]
|
||||||
|
|
||||||
assert_deepequal(expected, result)
|
assert_deepequal(expected, result)
|
||||||
|
|
||||||
|
|||||||
@@ -514,7 +514,7 @@ class UserTracker(CertmapdataMixin, KerberosAliasMixin, Tracker):
|
|||||||
assert_deepequal(dict(
|
assert_deepequal(dict(
|
||||||
completed=1,
|
completed=1,
|
||||||
failed=dict(
|
failed=dict(
|
||||||
member=dict(group=tuple(), user=tuple())
|
member=dict(group=tuple(), user=tuple(), service=tuple())
|
||||||
),
|
),
|
||||||
result={
|
result={
|
||||||
'dn': get_group_dn(admin_group),
|
'dn': get_group_dn(admin_group),
|
||||||
|
|||||||
Reference in New Issue
Block a user