Skip to content

Commit 1a4b9b8

Browse files
authored
Merge pull request #81 from ConductorOne/ggreer/group-grant-expansion
Add support for groups being members of groups.
2 parents c539549 + f3d56bb commit 1a4b9b8

File tree

4 files changed

+150
-11
lines changed

4 files changed

+150
-11
lines changed

.github/workflows/ci.yaml

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -88,11 +88,27 @@ jobs:
8888
run: node ./scripts/ldif.js && ls -la
8989
- name: Import ldif into openldap
9090
run: ./scripts/import.sh
91+
- name: Install baton
92+
run: ./scripts/get-baton.sh && mv baton /usr/local/bin
9193
- name: Build baton-ldap
9294
run: go build ./cmd/baton-ldap
9395
- name: Run baton-ldap
9496
run: ./baton-ldap
95-
- name: Revoke grants
96-
run: ./baton-ldap --revoke-grant 'group:cn=testgroup00000,dc=example,dc=org:member:user:cn=testuser00099@example.com,dc=example,dc=org' && ./baton-ldap --revoke-grant 'group:cn=othertestgroup00000,dc=example,dc=org:member:user:cn=testuser00099@example.com,dc=example,dc=org'
97-
- name: Grant entitlements
98-
run: ./baton-ldap --grant-entitlement 'group:cn=testgroup00000,dc=example,dc=org:member' --grant-principal 'cn=testuser00099@example.com,dc=example,dc=org' --grant-principal-type 'user' && ./baton-ldap --grant-entitlement 'group:cn=othertestgroup00000,dc=example,dc=org:member' --grant-principal 'cn=testuser00099@example.com,dc=example,dc=org' --grant-principal-type 'user'
97+
- name: List grants
98+
run: baton grants
99+
- name: Test grant/revoking posixGroup entitlements
100+
env:
101+
BATON: baton
102+
BATON_LDAP: ./baton-ldap
103+
BATON_ENTITLEMENT: "group:cn=testgroup00000,dc=example,dc=org:member"
104+
BATON_PRINCIPAL: "cn=testuser00099@example.com,dc=example,dc=org"
105+
BATON_PRINCIPAL_TYPE: "user"
106+
run: ./scripts/grant-revoke.sh
107+
- name: Test grant/revoking groupOfUniqueNames entitlements
108+
env:
109+
BATON: baton
110+
BATON_LDAP: ./baton-ldap
111+
BATON_ENTITLEMENT: "group:cn=othertestgroup00000,dc=example,dc=org:member"
112+
BATON_PRINCIPAL: "cn=testuser00099@example.com,dc=example,dc=org"
113+
BATON_PRINCIPAL_TYPE: "user"
114+
run: ./scripts/grant-revoke.sh

pkg/connector/group.go

Lines changed: 63 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,17 @@ import (
1919
"golang.org/x/exp/slices"
2020
)
2121

22+
var objectClassesToResourceTypes = map[string]*v2.ResourceType{
23+
"group": resourceTypeGroup,
24+
"groupOfNames": resourceTypeGroup,
25+
"groupOfUniqueNames": resourceTypeGroup,
26+
"inetOrgPerson": resourceTypeUser,
27+
"posixGroup": resourceTypeGroup,
28+
"organizationalPerson": resourceTypeUser,
29+
"person": resourceTypeUser,
30+
"user": resourceTypeUser,
31+
}
32+
2233
const (
2334
groupObjectClasses = "(objectClass=groupOfUniqueNames)(objectClass=groupOfNames)(objectClass=posixGroup)(objectClass=group)"
2435
groupFilter = "(|" + groupObjectClasses + ")"
@@ -146,22 +157,49 @@ func (g *groupResourceType) Entitlements(ctx context.Context, resource *v2.Resou
146157
}
147158

148159
// newGrantFromDN - create a `Grant` from a given group and user distinguished name.
149-
func newGrantFromDN(resource *v2.Resource, userDN string) *v2.Grant {
160+
func newGrantFromDN(groupResource *v2.Resource, dn string, resourceType *v2.ResourceType) *v2.Grant {
161+
grantOpts := []grant.GrantOption{}
162+
if resourceType == resourceTypeGroup {
163+
grantOpts = append(grantOpts, grant.WithAnnotation(&v2.GrantExpandable{
164+
EntitlementIds: []string{
165+
fmt.Sprintf("group:%s:member", dn),
166+
},
167+
}))
168+
}
150169
g := grant.NewGrant(
151170
// remove group profile from grant so we're not saving all group memberships in every grant
152171
&v2.Resource{
153-
Id: resource.Id,
172+
Id: groupResource.Id,
154173
},
155174
groupMemberEntitlement,
156175
// remove user profile from grant so we're not saving repetitive user info in every grant
157176
&v2.ResourceId{
158-
ResourceType: resourceTypeUser.Id,
159-
Resource: userDN,
177+
ResourceType: resourceType.Id,
178+
Resource: dn,
160179
},
180+
grantOpts...,
161181
)
162182
return g
163183
}
164184

185+
func newGrantFromEntry(groupResource *v2.Resource, entry *ldap3.Entry) *v2.Grant {
186+
var dn string
187+
parsedDN, err := ldap.CanonicalizeDN(entry.DN)
188+
if err == nil {
189+
dn = parsedDN.String()
190+
} else {
191+
dn = entry.DN
192+
}
193+
194+
for _, objectClass := range entry.GetAttributeValues("objectClass") {
195+
if resourceType, ok := objectClassesToResourceTypes[objectClass]; ok {
196+
return newGrantFromDN(groupResource, dn, resourceType)
197+
}
198+
}
199+
200+
return newGrantFromDN(groupResource, dn, resourceTypeUser)
201+
}
202+
165203
func (g *groupResourceType) Grants(ctx context.Context, resource *v2.Resource, token *pagination.Token) ([]*v2.Grant, string, annotations.Annotations, error) {
166204
l := ctxzap.Extract(ctx)
167205
groupDN, err := ldap.CanonicalizeDN(resource.Id.Resource)
@@ -196,7 +234,25 @@ func (g *groupResourceType) Grants(ctx context.Context, resource *v2.Resource, t
196234
for memberId := range memberIDs.Iter() {
197235
parsedDN, err := ldap.CanonicalizeDN(memberId)
198236
if err == nil {
199-
g := newGrantFromDN(resource, parsedDN.String())
237+
member, _, err := g.client.LdapSearch(
238+
ctx,
239+
ldap3.ScopeWholeSubtree,
240+
parsedDN,
241+
"",
242+
nil,
243+
"",
244+
1,
245+
)
246+
if err != nil {
247+
l.Error("ldap-connector: failed to get group member", zap.String("group", groupDN.String()), zap.String("member_id", memberId), zap.Error(err))
248+
}
249+
var g *v2.Grant
250+
if len(member) == 1 {
251+
g = newGrantFromEntry(resource, member[0])
252+
} else {
253+
// Fall back to creating a grant and assuming it's for a user.
254+
g = newGrantFromDN(resource, parsedDN.String(), resourceTypeUser)
255+
}
200256
rv = append(rv, g)
201257
continue
202258
}
@@ -208,7 +264,7 @@ func (g *groupResourceType) Grants(ctx context.Context, resource *v2.Resource, t
208264
if memberDN == "" {
209265
continue
210266
}
211-
g := newGrantFromDN(resource, memberDN)
267+
g := newGrantFromDN(resource, memberDN, resourceTypeUser)
212268
rv = append(rv, g)
213269
}
214270

@@ -238,7 +294,7 @@ func (g *groupResourceType) Grants(ctx context.Context, resource *v2.Resource, t
238294
l.Error("ldap-connector: invalid user DN", zap.String("user_dn", userEntry.DN), zap.Error(err))
239295
continue
240296
}
241-
g := newGrantFromDN(resource, userDN.String())
297+
g := newGrantFromDN(resource, userDN.String(), resourceTypeUser)
242298
rv = append(rv, g)
243299
}
244300
if nextPage == "" {

scripts/get-baton.sh

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
#!/usr/bin/env bash
2+
3+
set -euxo pipefail
4+
5+
OS=$(uname -s | tr '[:upper:]' '[:lower:]')
6+
ARCH=$(uname -m)
7+
if [ "${ARCH}" = "x86_64" ]; then
8+
ARCH="amd64"
9+
fi
10+
11+
RELEASES_URL="https://api.github.com/repos/conductorone/baton/releases/latest"
12+
BASE_URL="https://github.com/conductorone/baton/releases/download"
13+
14+
DOWNLOAD_URL=$(curl "${RELEASES_URL}" | jq --raw-output ".assets[].browser_download_url | match(\"${BASE_URL}/v[.0-9]+/baton-v[.0-9]+-${OS}-${ARCH}.*\"; \"i\").string")
15+
16+
FILENAME=$(basename ${DOWNLOAD_URL})
17+
18+
curl -LO ${DOWNLOAD_URL}
19+
tar xzf ${FILENAME}

scripts/grant-revoke.sh

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
#!/bin/bash
2+
3+
set -exo pipefail
4+
5+
if [ -z "$BATON_LDAP" ]; then
6+
echo "BATON_LDAP not set. using baton-ldap"
7+
BATON_LDAP=baton-ldap
8+
fi
9+
if [ -z "$BATON" ]; then
10+
echo "BATON not set. using baton"
11+
BATON=baton
12+
fi
13+
14+
# Error on unbound variables now that we've set BATON & BATON_LDAP
15+
set -u
16+
17+
# Sync
18+
$BATON_LDAP
19+
20+
# Grant entitlement
21+
$BATON_LDAP --grant-entitlement="$BATON_ENTITLEMENT" --grant-principal="$BATON_PRINCIPAL" --grant-principal-type="$BATON_PRINCIPAL_TYPE"
22+
23+
# Check for grant before revoking
24+
$BATON_LDAP
25+
$BATON grants --entitlement="$BATON_ENTITLEMENT" --output-format=json | jq --exit-status ".grants[] | select( .principal.id.resource == \"$BATON_PRINCIPAL\" )"
26+
27+
# Grant already-granted entitlement
28+
$BATON_LDAP --grant-entitlement="$BATON_ENTITLEMENT" --grant-principal="$BATON_PRINCIPAL" --grant-principal-type="$BATON_PRINCIPAL_TYPE"
29+
30+
# Get grant ID
31+
BATON_GRANT=$($BATON grants --entitlement="$BATON_ENTITLEMENT" --output-format=json | jq --raw-output --exit-status ".grants[] | select( .principal.id.resource == \"$BATON_PRINCIPAL\" ).grant.id")
32+
33+
# Revoke grant
34+
$BATON_LDAP --revoke-grant="$BATON_GRANT"
35+
36+
# Revoke already-revoked grant
37+
$BATON_LDAP --revoke-grant="$BATON_GRANT"
38+
39+
# Check grant was revoked
40+
$BATON_LDAP
41+
$BATON grants --entitlement="$BATON_ENTITLEMENT" --output-format=json | jq --exit-status "if .grants then [ .grants[] | select( .principal.id.resource == \"$BATON_PRINCIPAL\" ) ] | length == 0 else . end"
42+
43+
# Re-grant entitlement
44+
$BATON_LDAP --grant-entitlement="$BATON_ENTITLEMENT" --grant-principal="$BATON_PRINCIPAL" --grant-principal-type="$BATON_PRINCIPAL_TYPE"
45+
46+
# Check grant was re-granted
47+
$BATON_LDAP
48+
$BATON grants --entitlement="$BATON_ENTITLEMENT" --output-format=json | jq --exit-status ".grants[] | select( .principal.id.resource == \"$BATON_PRINCIPAL\" )"

0 commit comments

Comments
 (0)