Skip to content

Commit ec49972

Browse files
Productionize HDFS rego rules (#11)
* pull in new rego rules version * feat: Implement regex support * default matches_identity to false * Usernames must match entire regex (not only parts) * Change bob regex * Introduce userRegex and shortUserRegex * fix bob userRegex * use backticks * Add groupRegex * Update rego/hdfs.rego Co-authored-by: Siegfried Weber <mail@siegfriedweber.net> * Add missing shortUser test * fix test name * fix(tests): Add test-case for shortUserRegex * Use the new rules also in test stack * fix: Let tilt stack work again --------- Co-authored-by: Siegfried Weber <mail@siegfriedweber.net>
1 parent bdb3eb1 commit ec49972

File tree

5 files changed

+129
-59
lines changed

5 files changed

+129
-59
lines changed

rego/hdfs.rego

Lines changed: 48 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@ package hdfs
22

33
import rego.v1
44

5-
default allow = false
5+
default allow := false
6+
default matches_identity(identity) := false
67

78
# HDFS authorizer
89
allow if {
@@ -12,21 +13,22 @@ allow if {
1213
action_sufficient_for_operation(acl.action, input.operationName)
1314
}
1415

15-
# HDFS group mapper (this returns a list of strings)
16-
groups := {group |
17-
raw = groups_for_user[input.username][_]
18-
# Keycloak groups have trailing slashes
19-
group := trim_prefix(raw, "/")
16+
# Identity mentions the (long) userName or shortUsername explicitly
17+
matches_identity(identity) if {
18+
identity in {
19+
concat("", ["user:", input.callerUgi.userName]),
20+
concat("", ["shortUser:", input.callerUgi.shortUserName])
21+
}
2022
}
2123

22-
# Identity mentions the (long) userName explicitly
24+
# Identity regex matches the (long) userName
2325
matches_identity(identity) if {
24-
identity == concat("", ["user:", input.callerUgi.userName])
26+
match_entire(identity, concat("", ["userRegex:", input.callerUgi.userName]))
2527
}
2628

27-
# Identity mentions the shortUserName explicitly
29+
# Identity regex matches the shortUsername
2830
matches_identity(identity) if {
29-
identity == concat("", ["shortUser:", input.callerUgi.shortUserName])
31+
match_entire(identity, concat("", ["shortUserRegex:", input.callerUgi.shortUserName]))
3032
}
3133

3234
# Identity mentions group the user is part of (by looking up using the (long) userName)
@@ -35,6 +37,24 @@ matches_identity(identity) if {
3537
identity == concat("", ["group:", group])
3638
}
3739

40+
# Identity regex matches group the user is part of (by looking up using the (long) userName)
41+
matches_identity(identity) if {
42+
some group in groups_for_user[input.callerUgi.userName]
43+
match_entire(identity, concat("", ["groupRegex:", group]))
44+
}
45+
46+
# Identity mentions group the user is part of (by looking up using the shortUserName)
47+
matches_identity(identity) if {
48+
some group in groups_for_short_user_name[input.callerUgi.shortUserName]
49+
identity == concat("", ["group:", group])
50+
}
51+
52+
# Identity regex matches group the user is part of (by looking up using the shortUserName)
53+
matches_identity(identity) if {
54+
some group in groups_for_short_user_name[input.callerUgi.shortUserName]
55+
match_entire(identity, concat("", ["groupRegex:", group]))
56+
}
57+
3858
# Resource mentions the file explicitly
3959
matches_resource(file, resource) if {
4060
resource == concat("", ["hdfs:file:", file])
@@ -63,6 +83,13 @@ action_hierarchy := {
6383
"ro": ["ro"],
6484
}
6585

86+
match_entire(pattern, value) if {
87+
# Add the anchors ^ and $
88+
pattern_with_anchors := concat("", ["^", pattern, "$"])
89+
90+
regex.match(pattern_with_anchors, value)
91+
}
92+
6693
# To get a (hopefully complete) list of actions run "ack 'String operationName = '" in the hadoop source code
6794
action_for_operation := {
6895
# The "rename" operation will be actually called on both - the source and the target location.
@@ -182,14 +209,16 @@ groups_for_user := {
182209
"bob/test-hdfs-permissions.default.svc.cluster.local@CLUSTER.LOCAL": []
183210
}
184211

212+
groups_for_short_user_name := {}
213+
185214
acls := [
186215
{
187216
"identity": "group:admins",
188217
"action": "full",
189218
"resource": "hdfs:dir:/",
190219
},
191220
{
192-
"identity": "group:developers",
221+
"identity": "groupRegex:(developers)",
193222
"action": "rw",
194223
"resource": "hdfs:dir:/developers/",
195224
},
@@ -204,7 +233,7 @@ acls := [
204233
"resource": "hdfs:dir:/alice/",
205234
},
206235
{
207-
"identity": "user:bob/test-hdfs-permissions.default.svc.cluster.local@CLUSTER.LOCAL",
236+
"identity": `userRegex:bob/.+\.default\.svc\.cluster\.local@CLUSTER\.LOCAL`,
208237
"action": "rw",
209238
"resource": "hdfs:dir:/bob/",
210239
},
@@ -216,11 +245,16 @@ acls := [
216245
{
217246
"identity": "user:bob/test-hdfs-permissions.default.svc.cluster.local@CLUSTER.LOCAL",
218247
"action": "rw",
219-
"resource": "hdfs:file:/developers/file-from-bob",
248+
"resource": "hdfs:file:/developers/file-from-bob-via-user",
220249
},
221250
{
222251
"identity": "shortUser:bob",
223252
"action": "rw",
224-
"resource": "hdfs:file:/developers/file-from-bob",
253+
"resource": "hdfs:file:/developers/file-from-bob-via-short-user",
254+
},
255+
{
256+
"identity": "shortUserRegex:(bob|bobby)",
257+
"action": "rw",
258+
"resource": "hdfs:file:/developers/file-from-bob-via-short-user-regex",
225259
},
226260
]

rego/hdfs_test.rego

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -141,13 +141,35 @@ test_bob_no_rw_access_to_developers if {
141141
}
142142
}
143143

144-
test_bob_rw_access_to_developers_special_file if {
144+
test_bob_rw_access_to_developers_special_file_via_user if {
145145
allow with input as {
146146
"callerUgi": {
147147
"shortUserName": "bob",
148148
"userName": "bob/test-hdfs-permissions.default.svc.cluster.local@CLUSTER.LOCAL",
149149
},
150-
"path": "/developers/file-from-bob",
150+
"path": "/developers/file-from-bob-via-user",
151+
"operationName": "create",
152+
}
153+
}
154+
155+
test_bob_rw_access_to_developers_special_file_via_short_user if {
156+
allow with input as {
157+
"callerUgi": {
158+
"shortUserName": "bob",
159+
"userName": "bob/test-hdfs-permissions.default.svc.cluster.local@CLUSTER.LOCAL",
160+
},
161+
"path": "/developers/file-from-bob-via-short-user",
162+
"operationName": "create",
163+
}
164+
}
165+
166+
test_bob_rw_access_to_developers_special_file_via_short_user_regex if {
167+
allow with input as {
168+
"callerUgi": {
169+
"shortUserName": "bob",
170+
"userName": "bob/test-hdfs-permissions.default.svc.cluster.local@CLUSTER.LOCAL",
171+
},
172+
"path": "/developers/file-from-bob-via-short-user-regex",
151173
"operationName": "create",
152174
}
153175
}

test/stack/11-rego-rules.yaml

Lines changed: 51 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -11,34 +11,59 @@ data:
1111
1212
import rego.v1
1313
14-
default allow = false
14+
default allow := false
15+
default matches_identity(identity) := false
1516
1617
# HDFS authorizer
1718
allow if {
1819
some acl in acls
19-
matches_identity(input.callerUgi.shortUserName, acl.identity)
20+
matches_identity(acl.identity)
2021
matches_resource(input.path, acl.resource)
2122
action_sufficient_for_operation(acl.action, input.operationName)
2223
}
2324
24-
# HDFS group mapper (this returns a list of strings)
25-
groups := {group |
26-
raw = groups_for_user[input.username][_]
27-
# Keycloak groups have trailing slashes
28-
group := trim_prefix(raw, "/")
25+
# Identity mentions the (long) userName or shortUsername explicitly
26+
matches_identity(identity) if {
27+
identity in {
28+
concat("", ["user:", input.callerUgi.userName]),
29+
concat("", ["shortUser:", input.callerUgi.shortUserName])
30+
}
2931
}
3032
31-
# Identity mentions the user explicitly
32-
matches_identity(user, identity) if {
33-
identity == concat("", ["user:", user])
33+
# Identity regex matches the (long) userName
34+
matches_identity(identity) if {
35+
match_entire(identity, concat("", ["userRegex:", input.callerUgi.userName]))
3436
}
3537
36-
# Identity mentions group the user is part of
37-
matches_identity(user, identity) if {
38-
some group in groups_for_user[user]
38+
# Identity regex matches the shortUsername
39+
matches_identity(identity) if {
40+
match_entire(identity, concat("", ["shortUserRegex:", input.callerUgi.shortUserName]))
41+
}
42+
43+
# Identity mentions group the user is part of (by looking up using the (long) userName)
44+
matches_identity(identity) if {
45+
some group in groups_for_user[input.callerUgi.userName]
46+
identity == concat("", ["group:", group])
47+
}
48+
49+
# Identity regex matches group the user is part of (by looking up using the (long) userName)
50+
matches_identity(identity) if {
51+
some group in groups_for_user[input.callerUgi.userName]
52+
match_entire(identity, concat("", ["groupRegex:", group]))
53+
}
54+
55+
# Identity mentions group the user is part of (by looking up using the shortUserName)
56+
matches_identity(identity) if {
57+
some group in groups_for_short_user_name[input.callerUgi.shortUserName]
3958
identity == concat("", ["group:", group])
4059
}
4160
61+
# Identity regex matches group the user is part of (by looking up using the shortUserName)
62+
matches_identity(identity) if {
63+
some group in groups_for_short_user_name[input.callerUgi.shortUserName]
64+
match_entire(identity, concat("", ["groupRegex:", group]))
65+
}
66+
4267
# Resource mentions the file explicitly
4368
matches_resource(file, resource) if {
4469
resource == concat("", ["hdfs:file:", file])
@@ -67,6 +92,13 @@ data:
6792
"ro": ["ro"],
6893
}
6994
95+
match_entire(pattern, value) if {
96+
# Add the anchors ^ and $
97+
pattern_with_anchors := concat("", ["^", pattern, "$"])
98+
99+
regex.match(pattern_with_anchors, value)
100+
}
101+
70102
# To get a (hopefully complete) list of actions run "ack 'String operationName = '" in the hadoop source code
71103
action_for_operation := {
72104
# The "rename" operation will be actually called on both - the source and the target location.
@@ -180,7 +212,8 @@ data:
180212
"transitionToStandby": "full",
181213
}
182214
183-
groups_for_user := {"admin": ["admins"], "alice": ["developers"], "bob": []}
215+
groups_for_user := {}
216+
groups_for_short_user_name := {"admin": ["admins"], "alice": ["developers"], "bob": []}
184217
185218
acls := [
186219
{
@@ -199,22 +232,22 @@ data:
199232
"resource": "hdfs:dir:/developers-ro/",
200233
},
201234
{
202-
"identity": "user:alice",
235+
"identity": "shortUser:alice",
203236
"action": "rw",
204237
"resource": "hdfs:dir:/alice/",
205238
},
206239
{
207-
"identity": "user:bob",
240+
"identity": "shortUser:bob",
208241
"action": "rw",
209242
"resource": "hdfs:dir:/bob/",
210243
},
211244
{
212-
"identity": "user:bob",
245+
"identity": "shortUser:bob",
213246
"action": "ro",
214247
"resource": "hdfs:dir:/developers/",
215248
},
216249
{
217-
"identity": "user:bob",
250+
"identity": "shortUser:bob",
218251
"action": "rw",
219252
"resource": "hdfs:file:/developers/file-from-bob",
220253
},

test/stack/20-hdfs.yaml

Lines changed: 4 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -35,21 +35,11 @@ spec:
3535
tlsSecretClass: tls # pragma: allowlist secret
3636
kerberos:
3737
secretClass: kerberos-default # pragma: allowlist secret
38+
authorization:
39+
opa:
40+
configMapName: opa
41+
package: hdfs
3842
nameNodes:
39-
envOverrides: &envOverrides
40-
HADOOP_CLASSPATH: "/stackable/hadoop/share/hadoop/tools/lib/*.jar"
41-
configOverrides: &configOverrides
42-
hdfs-site.xml:
43-
dfs.namenode.inode.attributes.provider.class: tech.stackable.hadoop.StackableAuthorizer
44-
core-site.xml:
45-
# The mapper is only handled on the namenode so no need to apply this config to all roles
46-
hadoop.security.group.mapping: tech.stackable.hadoop.StackableGroupMapper
47-
hadoop.security.group.mapping.opa.policy.url: http://opa.default.svc.cluster.local:8081/v1/data/hdfs/groups
48-
hadoop.security.authorization.opa.policy.url: http://opa.default.svc.cluster.local:8081/v1/data/hdfs/allow
49-
# The operator adds a default static mapping when kerberos is activated, see:
50-
# https://github.com/stackabletech/hdfs-operator/blob/main/rust/operator-binary/src/kerberos.rs#L97-L101
51-
# This should be removed so that the mapping implementation can provide this information instead:
52-
hadoop.user.group.static.mapping.overrides: ""
5343
config:
5444
logging:
5545
containers:
@@ -65,14 +55,10 @@ spec:
6555
default:
6656
replicas: 2
6757
dataNodes:
68-
configOverrides: *configOverrides
69-
envOverrides: *envOverrides
7058
roleGroups:
7159
default:
7260
replicas: 1
7361
journalNodes:
74-
configOverrides: *configOverrides
75-
envOverrides: *envOverrides
7662
roleGroups:
7763
default:
7864
replicas: 1

test/stack/30-test-hdfs-permissions.yaml

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -52,13 +52,8 @@ spec:
5252
bin/hdfs dfs -ls /developers
5353
bin/hdfs dfs -ls /developers-ro && exit 1
5454
55-
sleep infinity
56-
57-
bin/hdfs dfs -ls /
58-
bin/hdfs dfs -rm -f /hosts
59-
bin/hdfs dfs -put -f /etc/hosts /hosts
60-
bin/hdfs dfs -ls /
61-
bin/hdfs dfs -cat /hosts
55+
echo "Test passed"
56+
exit 0
6257
volumeMounts:
6358
- name: hdfs-config
6459
mountPath: /stackable/conf/hdfs

0 commit comments

Comments
 (0)