Skip to content

Commit fb99ec4

Browse files
committed
Updating PR
1 parent 4a6b7e3 commit fb99ec4

File tree

2 files changed

+45
-76
lines changed

2 files changed

+45
-76
lines changed

provider-kubeconfig.py

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -137,10 +137,10 @@ def _apply_consumer_rbac(self, sa, namespace, kubeconfig):
137137
}
138138
create_role_rolebinding(role_binding, sa + "-rolebinding-impersonate.yaml", kubeconfig)
139139

140-
all_resources = ["*", "deployments", "daemonsets", "pods/portforward", "users", "groups", "serviceaccounts"]
140+
all_resources = [res for r in rule_list for res in r.get("resources", [])]
141141
cfg_map_filename = sa + "-perms.txt"
142142
with open(cfg_map_filename, "w", encoding="utf-8") as fp:
143-
fp.write(str(sorted(list(set(all_resources)))))
143+
fp.write(str(sorted(set(all_resources))))
144144
run_command(
145145
"kubectl create configmap " + sa + "-perms -n " + namespace
146146
+ " --from-file=" + cfg_map_filename + kubeconfig
@@ -203,9 +203,12 @@ def _apply_provider_rbac(self, sa, namespace, kubeconfig):
203203
{"apiGroups": [""], "resources": ["resourcequotas"], "verbs": ["create", "delete", "deletecollection", "patch", "update"]},
204204
{"apiGroups": [""], "resources": ["persistentvolumes", "persistentvolumeclaims"], "verbs": ["get", "watch", "list", "create", "delete", "update", "patch"]},
205205
]
206-
all_resources = []
207-
for r in rule_list:
208-
all_resources.extend(r.get("resources", []))
206+
# Match original: skip "*" resources (ruleGroup1, ruleGroup5, ruleGroup14)
207+
all_resources = [
208+
res for r in rule_list
209+
for res in r.get("resources", [])
210+
if res != "*"
211+
]
209212

210213
role = {
211214
"apiVersion": "rbac.authorization.k8s.io/v1",
@@ -226,7 +229,7 @@ def _apply_provider_rbac(self, sa, namespace, kubeconfig):
226229

227230
cfg_map_filename = sa + "-perms.txt"
228231
with open(cfg_map_filename, "w", encoding="utf-8") as fp:
229-
fp.write(str(sorted(list(set(all_resources)))))
232+
fp.write(str(sorted(set(all_resources))))
230233
run_command(
231234
"kubectl create configmap " + sa + "-perms -n " + namespace
232235
+ " --from-file=" + cfg_map_filename + kubeconfig
@@ -294,7 +297,7 @@ def _update_rbac(self, permissionfile, sa, namespace, kubeconfig):
294297
new_resources.extend(kubeplus_perms)
295298

296299
run_command("kubectl delete configmap " + cfg_map_name + " -n " + namespace + kubeconfig)
297-
new_resources = sorted(list(set(new_resources)))
300+
new_resources = sorted(set(new_resources))
298301
with open(cfg_map_filename, "w", encoding="utf-8") as fp:
299302
fp.write(str(new_resources))
300303
run_command(
@@ -439,7 +442,7 @@ def _generate_kubeconfig(self, sa, namespace, filename, api_server_ip="", kubeco
439442
pargs = parser.parse_args()
440443
action = pargs.action
441444
namespace = pargs.namespace
442-
kubeconfig_path = pargs.kubeconfig or os.path.join(os.getenv("HOME", ""), ".kube", "config")
445+
kubeconfig_path = pargs.kubeconfig or os.path.join(os.path.expanduser("~"), ".kube", "config")
443446
kubeconfigString = " --kubeconfig=" + kubeconfig_path
444447
api_s_ip = pargs.apiserverurl or ""
445448
permission_file = pargs.permissionfile or ""

tests/test_provider_kubeconfig.py

Lines changed: 34 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,6 @@
88
import sys
99
import unittest
1010

11-
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
12-
1311

1412
def _run_command(cmd):
1513
"""Run shell command, return (stdout, stderr)."""
@@ -26,6 +24,7 @@ def _cluster_available(kubeconfig=""):
2624

2725

2826
SCRIPT = "provider-kubeconfig.py"
27+
ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
2928

3029
# All CLI elements that must appear in --help
3130
HELP_ELEMENTS = [
@@ -43,44 +42,25 @@ def _cluster_available(kubeconfig=""):
4342
class TestCli(unittest.TestCase):
4443
"""provider-kubeconfig.py exposes expected CLI (actions and flags)."""
4544

46-
def test_help_shows_all_actions(self):
47-
root = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
45+
def test_help_shows_all_actions_flags_and_namespace(self):
46+
"""--help must show actions, flags, and namespace argument."""
4847
proc = subprocess.run(
49-
[sys.executable, os.path.join(root, SCRIPT), "--help"],
50-
capture_output=True, text=True, cwd=root,
48+
[sys.executable, os.path.join(ROOT, SCRIPT), "--help"],
49+
capture_output=True, text=True, cwd=ROOT,
5150
)
5251
self.assertEqual(proc.returncode, 0, proc.stderr)
5352
out = proc.stdout or ""
5453
for elem in ["create", "delete", "update", "extract"]:
5554
self.assertIn(elem, out, f"Action {elem} should appear in help")
56-
57-
def test_help_shows_all_flags(self):
58-
root = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
59-
proc = subprocess.run(
60-
[sys.executable, os.path.join(root, SCRIPT), "--help"],
61-
capture_output=True, text=True, cwd=root,
62-
)
63-
self.assertEqual(proc.returncode, 0)
64-
out = proc.stdout or ""
6555
for elem in HELP_ELEMENTS:
6656
self.assertIn(elem, out, f"Help should mention {elem}")
67-
68-
def test_help_shows_namespace_argument(self):
69-
root = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
70-
proc = subprocess.run(
71-
[sys.executable, os.path.join(root, SCRIPT), "--help"],
72-
capture_output=True, text=True, cwd=root,
73-
)
74-
self.assertEqual(proc.returncode, 0)
75-
out = proc.stdout or ""
7657
self.assertIn("namespace", out.lower())
7758

7859
def test_update_without_permissionfile_exits_with_error(self):
7960
"""update action without -p exits with code 1."""
80-
root = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
8161
proc = subprocess.run(
82-
[sys.executable, os.path.join(root, SCRIPT), "update", "default"],
83-
capture_output=True, text=True, cwd=root,
62+
[sys.executable, os.path.join(ROOT, SCRIPT), "update", "default"],
63+
capture_output=True, text=True, cwd=ROOT,
8464
)
8565
self.assertNotEqual(proc.returncode, 0)
8666
self.assertIn("permission", (proc.stdout or proc.stderr or "").lower())
@@ -101,6 +81,10 @@ def setUpClass(cls):
10181
cls.has_cluster = _cluster_available(cls.kubeconfig)
10282
cls.kubeconfig_flag = " --kubeconfig=" + cls.kubeconfig if cls.kubeconfig else ""
10383

84+
def setUp(self):
85+
if not self.has_cluster:
86+
self.skipTest("No cluster reachable (set KUBECONFIG)")
87+
10488
def _create_and_get_kubeconfig(self, root, ns, sa="kubeplus-saas-provider", extra_args=None, output_filename=None):
10589
"""Run create, return (kubeconfig_dict, proc). Caller must delete to cleanup."""
10690
create_args = ["create", ns]
@@ -124,10 +108,13 @@ def _create_and_get_kubeconfig(self, root, ns, sa="kubeplus-saas-provider", extr
124108
cfg = json.load(fp)
125109
return cfg, proc
126110

127-
def _delete_for_cleanup(self, root, ns, sa="kubeplus-saas-provider"):
111+
def _delete_for_cleanup(self, root, ns, sa="kubeplus-saas-provider", filename=None):
112+
"""Delete k8s resources and local files. Pass filename when -f was used (e.g. custom-provider-kubeconfig)."""
128113
delete_args = ["delete", ns]
129114
if sa != "kubeplus-saas-provider":
130115
delete_args += ["-c", sa]
116+
if filename:
117+
delete_args += ["-f", filename]
131118
subprocess.run(
132119
[sys.executable, os.path.join(root, SCRIPT)] + delete_args + self.kubeconfig_arg,
133120
cwd=root, capture_output=True, timeout=60,
@@ -191,12 +178,9 @@ def _sa_exists(self, sa, ns):
191178

192179
def test_provider_kubeconfig_all_fields_nonempty(self):
193180
"""Provider kubeconfig: every field that should exist is non-empty."""
194-
if not self.has_cluster:
195-
self.skipTest("No cluster reachable (set KUBECONFIG)")
196-
root = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
197181
ns = "kubeplus-test-prov-" + str(os.getpid())
198182
try:
199-
cfg, proc = self._create_and_get_kubeconfig(root, ns)
183+
cfg, proc = self._create_and_get_kubeconfig(ROOT, ns)
200184
self.assertEqual(proc.returncode, 0, proc.stderr)
201185
self._assert_kubeconfig_valid(
202186
cfg,
@@ -205,17 +189,14 @@ def test_provider_kubeconfig_all_fields_nonempty(self):
205189
)
206190
self.assertTrue(self._sa_exists("kubeplus-saas-provider", ns))
207191
finally:
208-
self._delete_for_cleanup(root, ns)
192+
self._delete_for_cleanup(ROOT, ns)
209193

210194
def test_consumer_kubeconfig_all_fields_nonempty(self):
211195
"""Consumer kubeconfig: every field non-empty, user name matches SA, namespace in context."""
212-
if not self.has_cluster:
213-
self.skipTest("No cluster reachable (set KUBECONFIG)")
214-
root = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
215196
ns = "kubeplus-test-cons-" + str(os.getpid())
216197
consumer_sa = "test-consumer-sa"
217198
try:
218-
cfg, proc = self._create_and_get_kubeconfig(root, ns, sa=consumer_sa)
199+
cfg, proc = self._create_and_get_kubeconfig(ROOT, ns, sa=consumer_sa)
219200
self.assertEqual(proc.returncode, 0, proc.stderr)
220201
self._assert_kubeconfig_valid(
221202
cfg,
@@ -224,35 +205,29 @@ def test_consumer_kubeconfig_all_fields_nonempty(self):
224205
)
225206
self.assertTrue(self._sa_exists(consumer_sa, ns))
226207
finally:
227-
self._delete_for_cleanup(root, ns, sa=consumer_sa)
208+
self._delete_for_cleanup(ROOT, ns, sa=consumer_sa)
228209

229210
def test_flag_s_apiserverurl_reflected_in_kubeconfig(self):
230211
"""-s/--apiserverurl sets cluster server in kubeconfig."""
231-
if not self.has_cluster:
232-
self.skipTest("No cluster reachable (set KUBECONFIG)")
233-
root = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
234212
ns = "kubeplus-test-s-" + str(os.getpid())
235213
test_server = "https://api.example.com:6443"
236214
try:
237215
cfg, proc = self._create_and_get_kubeconfig(
238-
root, ns,
216+
ROOT, ns,
239217
extra_args=["-s", test_server],
240218
)
241219
self.assertEqual(proc.returncode, 0, proc.stderr)
242220
self._assert_kubeconfig_valid(cfg, expected_server=test_server, expected_namespace=ns)
243221
finally:
244-
self._delete_for_cleanup(root, ns)
222+
self._delete_for_cleanup(ROOT, ns)
245223

246224
def test_flag_x_clustername_reflected_in_kubeconfig(self):
247225
"""-x/--clustername sets context name and cluster name in kubeconfig."""
248-
if not self.has_cluster:
249-
self.skipTest("No cluster reachable (set KUBECONFIG)")
250-
root = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
251226
ns = "kubeplus-test-x-" + str(os.getpid())
252227
test_cluster = "my-test-cluster"
253228
try:
254229
cfg, proc = self._create_and_get_kubeconfig(
255-
root, ns,
230+
ROOT, ns,
256231
extra_args=["-x", test_cluster],
257232
)
258233
self.assertEqual(proc.returncode, 0, proc.stderr)
@@ -262,38 +237,32 @@ def test_flag_x_clustername_reflected_in_kubeconfig(self):
262237
expected_namespace=ns,
263238
)
264239
finally:
265-
self._delete_for_cleanup(root, ns)
240+
self._delete_for_cleanup(ROOT, ns)
266241

267242
def test_flag_f_filename_uses_custom_output_file(self):
268243
"""-f/--filename writes kubeconfig to specified file."""
269-
if not self.has_cluster:
270-
self.skipTest("No cluster reachable (set KUBECONFIG)")
271-
root = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
272244
ns = "kubeplus-test-f-" + str(os.getpid())
273245
custom_name = "custom-provider-kubeconfig"
274246
try:
275247
cfg, proc = self._create_and_get_kubeconfig(
276-
root, ns,
248+
ROOT, ns,
277249
extra_args=["-f", custom_name],
278250
output_filename=custom_name,
279251
)
280252
self.assertEqual(proc.returncode, 0, proc.stderr)
281253
self._assert_kubeconfig_valid(cfg, expected_namespace=ns)
282-
self.assertTrue(os.path.exists(os.path.join(root, custom_name + ".json")))
254+
self.assertTrue(os.path.exists(os.path.join(ROOT, custom_name + ".json")))
283255
finally:
284-
self._delete_for_cleanup(root, ns)
256+
self._delete_for_cleanup(ROOT, ns, filename=custom_name)
285257

286258
def test_flags_s_and_x_combined(self):
287259
"""-s and -x together: both server and cluster name in kubeconfig."""
288-
if not self.has_cluster:
289-
self.skipTest("No cluster reachable (set KUBECONFIG)")
290-
root = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
291260
ns = "kubeplus-test-sx-" + str(os.getpid())
292261
test_server = "https://api.example.com:6443"
293262
test_cluster = "my-test-cluster"
294263
try:
295264
cfg, proc = self._create_and_get_kubeconfig(
296-
root, ns,
265+
ROOT, ns,
297266
extra_args=["-s", test_server, "-x", test_cluster],
298267
)
299268
self.assertEqual(proc.returncode, 0, proc.stderr)
@@ -304,22 +273,19 @@ def test_flags_s_and_x_combined(self):
304273
expected_namespace=ns,
305274
)
306275
finally:
307-
self._delete_for_cleanup(root, ns)
276+
self._delete_for_cleanup(ROOT, ns)
308277

309-
def test_consumer_cannot_create_pod_in_other_namespacte(self):
278+
def test_consumer_cannot_create_pod_in_other_namespace(self):
310279
"""
311280
Consumer kubeconfig: verify create/delete in other namespaces is forbidden.
312281
Consumer RBAC should restrict operations; creating a pod in another ns should fail.
313282
"""
314-
if not self.has_cluster:
315-
self.skipTest("No cluster reachable (set KUBECONFIG)")
316-
root = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
317283
ns = "kubeplus-test-restrict-" + str(os.getpid())
318284
other_ns = "kubeplus-test-other-" + str(os.getpid())
319285
consumer_sa = "test-consumer-restrict"
320-
kubeconfig_path = os.path.join(root, consumer_sa + ".json")
286+
kubeconfig_path = os.path.join(ROOT, consumer_sa + ".json")
321287
try:
322-
cfg, proc = self._create_and_get_kubeconfig(root, ns, sa=consumer_sa)
288+
cfg, proc = self._create_and_get_kubeconfig(ROOT, ns, sa=consumer_sa)
323289
self.assertEqual(proc.returncode, 0, proc.stderr)
324290
self._assert_kubeconfig_valid(cfg, expected_namespace=ns, expected_user_name=consumer_sa)
325291

@@ -331,15 +297,15 @@ def test_consumer_cannot_create_pod_in_other_namespacte(self):
331297
"kubectl run nginx --image=nginx -n " + other_ns
332298
+ " --kubeconfig=" + kubeconfig_path
333299
)
334-
# Expect Forbidden or similar
300+
# Expect Forbidden (authorization denial), not generic errors (DNS, image pull, etc.)
335301
self.assertTrue(
336-
"Forbidden" in err or "forbidden" in err or "Error" in err,
302+
"forbidden" in err.lower(),
337303
"Consumer should not be able to create pod in other namespace; got out=%r err=%r"
338304
% (out, err),
339305
)
340306
finally:
341307
_run_command("kubectl delete namespace " + other_ns + self.kubeconfig_flag + " 2>/dev/null")
342-
self._delete_for_cleanup(root, ns, sa=consumer_sa)
308+
self._delete_for_cleanup(ROOT, ns, sa=consumer_sa)
343309

344310

345311
if __name__ == "__main__":

0 commit comments

Comments
 (0)