cyberark / KubiScan

A tool to scan Kubernetes cluster for risky permissions
GNU General Public License v3.0
1.31k stars 130 forks source link

Fix 'NoneType' object is not iterable and always connection to localhost #24

Closed k-popov closed 3 years ago

k-popov commented 3 years ago

What does this PR do?

First issue: Appears when running python3 KubiScan.py -rar on local machine against Azure AKS regardless of specifying -ho and -t or using context from local ~/.kube/config

Traceback (most recent call last):
  File "/home/hub/Work/KubiScan/KubiScan.py", line 635, in <module>
    main()
  File "/home/hub/Work/KubiScan/KubiScan.py", line 558, in main
    print_all_risky_roles(show_rules=args.rules, days=args.less_than, priority=args.priority)
  File "/home/hub/Work/KubiScan/KubiScan.py", line 44, in print_all_risky_roles
    risky_any_roles = engine.utils.get_risky_roles_and_clusterroles()
  File "/home/hub/Work/KubiScan/engine/utils.py", line 132, in get_risky_roles_and_clusterroles
    risky_roles = get_risky_roles()
  File "/home/hub/Work/KubiScan/engine/utils.py", line 140, in get_risky_roles
    return get_risky_role_by_kind('Role')
  File "/home/hub/Work/KubiScan/engine/utils.py", line 126, in get_risky_role_by_kind
    risky_roles = find_risky_roles(all_roles.items, kind)
  File "/home/hub/Work/KubiScan/engine/utils.py", line 105, in find_risky_roles
    is_risky, priority = is_risky_role(role)
  File "/home/hub/Work/KubiScan/engine/utils.py", line 94, in is_risky_role
    if are_rules_contain_other_rules(role.metadata.name, role.rules, risky_role.rules):
  File "/home/hub/Work/KubiScan/engine/utils.py", line 81, in are_rules_contain_other_rules
    for source_rule in source_rules:
TypeError: 'NoneType' object is not iterable

Second issue: kubernetes API client is always trying to connect to localhost port 80 when running on local machine against remote K8S cluster in both cases: setting -ho + -t and using context from local ~/.kube/config (both default context and specified explicitly)

Traceback (most recent call last):
  File "/home/hub/Work/KubiScan/KubiScan.py", line 635, in <module>
    main()
  File "/home/hub/Work/KubiScan/KubiScan.py", line 558, in main
    print_all_risky_roles(show_rules=args.rules, days=args.less_than, priority=args.priority)
  File "/home/hub/Work/KubiScan/KubiScan.py", line 44, in print_all_risky_roles
    risky_any_roles = engine.utils.get_risky_roles_and_clusterroles()
  File "/home/hub/Work/KubiScan/engine/utils.py", line 134, in get_risky_roles_and_clusterroles
    risky_clusterroles = get_risky_clusterroles()
  File "/home/hub/Work/KubiScan/engine/utils.py", line 144, in get_risky_clusterroles
    return get_risky_role_by_kind('ClusterRole')
  File "/home/hub/Work/KubiScan/engine/utils.py", line 124, in get_risky_role_by_kind
    all_roles = get_roles_by_kind(kind)
  File "/home/hub/Work/KubiScan/engine/utils.py", line 118, in get_roles_by_kind
    all_roles = api_client.api_temp.list_cluster_role()
  File "/home/hub/Work/KubiScan/api/api_client_temp.py", line 664, in list_cluster_role
    json_data = self.__call_api('/apis/rbac.authorization.k8s.io/v1/clusterroles', 'GET',
  File "/home/hub/Work/KubiScan/api/api_client_temp.py", line 153, in __call_api
    response_data = self.request(method, url,
  File "/home/hub/Work/KubiScan/api/api_client_temp.py", line 343, in request
    return self.rest_client.GET(url,
  File "/home/hub/Work/KubiScan/kubiscan/lib/python3.9/site-packages/kubernetes/client/rest.py", line 239, in GET
    return self.request("GET", url,
  File "/home/hub/Work/KubiScan/kubiscan/lib/python3.9/site-packages/kubernetes/client/rest.py", line 212, in request
    r = self.pool_manager.request(method, url,
  File "/home/hub/Work/KubiScan/kubiscan/lib/python3.9/site-packages/urllib3/request.py", line 74, in request
    return self.request_encode_url(
  File "/home/hub/Work/KubiScan/kubiscan/lib/python3.9/site-packages/urllib3/request.py", line 96, in request_encode_url
    return self.urlopen(method, url, **extra_kw)
  File "/home/hub/Work/KubiScan/kubiscan/lib/python3.9/site-packages/urllib3/poolmanager.py", line 375, in urlopen
    response = conn.urlopen(method, u.request_uri, **kw)
  File "/home/hub/Work/KubiScan/kubiscan/lib/python3.9/site-packages/urllib3/connectionpool.py", line 783, in urlopen
    return self.urlopen(
  File "/home/hub/Work/KubiScan/kubiscan/lib/python3.9/site-packages/urllib3/connectionpool.py", line 783, in urlopen
    return self.urlopen(
  File "/home/hub/Work/KubiScan/kubiscan/lib/python3.9/site-packages/urllib3/connectionpool.py", line 783, in urlopen
    return self.urlopen(
  File "/home/hub/Work/KubiScan/kubiscan/lib/python3.9/site-packages/urllib3/connectionpool.py", line 755, in urlopen
    retries = retries.increment(
  File "/home/hub/Work/KubiScan/kubiscan/lib/python3.9/site-packages/urllib3/util/retry.py", line 573, in increment
    raise MaxRetryError(_pool, url, error or ResponseError(cause))
urllib3.exceptions.MaxRetryError: HTTPConnectionPool(host='localhost', port=80): Max retries exceeded with url: /apis/rbac.authorization.k8s.io/v1/clusterroles (Caused by NewConnectionError('<urllib3.connection.HTTPConnection object at 0x7f97b46c29d0>: Failed to establish a new connection: [Errno 111] Connection refused'))

This pull request is similar to #23 but is IMO shorter and a bit more effective (target_rules are not uselessly iterated).

Checklists

Change log

Test coverage

Documentation

g3rzi commented 3 years ago

Thanks for your contribution! I appreciate it! :)

One thing to notice, In #23 there was a fix to the api_client.py:

        configuration = Configuration()
        api_client = ApiClient()
        ...
        api_client = ApiClient(configuration=configuration)
        CoreV1Api = client.CoreV1Api(api_client=api_client)
        RbacAuthorizationV1Api = client.RbacAuthorizationV1Api(api_client=api_client)
        api_temp = ApiClientTemp(configuration=configuration)

I saw that Configuration() already set the default localhost so I there is no need to use:

api_temp = ApiClientTemp(configuration=client.configuration.Configuration.get_default_copy())

If you think I am wrong, let me know.