Skip to content

Commit

Permalink
Merge pull request #1421 from grycap/admin_user
Browse files Browse the repository at this point in the history
Admin user #1414
  • Loading branch information
micafer authored Sep 28, 2022
2 parents ddd9ab7 + 0051b50 commit 95dde41
Show file tree
Hide file tree
Showing 15 changed files with 194 additions and 23 deletions.
29 changes: 20 additions & 9 deletions IM/InfrastructureInfo.py
Original file line number Diff line number Diff line change
Expand Up @@ -686,15 +686,6 @@ def _is_authorized(self, self_im_auth, auth):
res = False
for other_im_auth in auth.getAuthInfo("InfrastructureManager"):
res = True
for elem in ['username', 'password']:
if elem not in other_im_auth:
res = False
break
if elem not in self_im_auth:
InfrastructureInfo.logger.error("Inf ID %s has not elem %s in the auth data" % (self.id, elem))
if self_im_auth[elem] != other_im_auth[elem]:
res = False
break

if 'token' in self_im_auth:
if 'token' not in other_im_auth:
Expand All @@ -710,8 +701,23 @@ def _is_authorized(self, self_im_auth, auth):
res = False
break

# Username must start with the defined prefix
if not other_im_auth['username'].startswith(InfrastructureInfo.OPENID_USER_PREFIX):
res = False
break

# In case of OIDC token update it in each call to get a fresh version
self_im_auth['token'] = other_im_auth['token']
else:
for elem in ['username', 'password']:
if elem not in other_im_auth:
res = False
break
if elem not in self_im_auth:
InfrastructureInfo.logger.error("Inf ID %s has not elem %s in the auth data" % (self.id, elem))
if self_im_auth[elem] != other_im_auth[elem]:
res = False
break

if (self_im_auth['username'].startswith(InfrastructureInfo.OPENID_USER_PREFIX) and
'token' not in other_im_auth):
Expand All @@ -731,6 +737,11 @@ def is_authorized(self, auth):
for self_im_auth in self.auth.getAuthInfo("InfrastructureManager"):
if self._is_authorized(self_im_auth, auth):
return True
if Config.ADMIN_USER:
admin_auth = dict(Config.ADMIN_USER)
admin_auth["type"] = "InfrastructureManager"
if self._is_authorized(admin_auth, auth):
return True

return False
else:
Expand Down
24 changes: 24 additions & 0 deletions IM/InfrastructureManager.py
Original file line number Diff line number Diff line change
Expand Up @@ -1780,3 +1780,27 @@ def ChangeInfrastructureAuth(inf_id, new_auth, overwrite, auth):
sel_inf.change_auth(new_auth, overwrite)
IM.InfrastructureList.InfrastructureList.save_data(inf_id)
return ""

@staticmethod
def GetInfrastructureOwners(inf_id, auth):
"""
Get the list of owners of an infrastructure.
Args:
- inf_id(str): infrastructure id.
- auth(Authentication): parsed authentication tokens.
Return: list of str
"""
auth = InfrastructureManager.check_auth_data(auth)

InfrastructureManager.logger.info("Getting RADL of the Inf ID: " + str(inf_id))

sel_inf = InfrastructureManager.get_infrastructure(inf_id, auth)

res = []
for im_auth in sel_inf.auth.getAuthInfo("InfrastructureManager"):
res.append(im_auth['username'])

return res
2 changes: 2 additions & 0 deletions IM/REST.py
Original file line number Diff line number Diff line change
Expand Up @@ -471,6 +471,8 @@ def RESTGetInfrastructureProperty(infid=None, prop=None):

data = InfrastructureManager.ExportInfrastructure(infid, delete, auth)
return format_output(data, default_type="application/json", field_name="data")
elif prop == "owners":
res = InfrastructureManager.GetInfrastructureOwners(infid, auth)
else:
return return_error(404, "Incorrect infrastructure property")

Expand Down
14 changes: 14 additions & 0 deletions IM/ServiceRequests.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ class IMBaseRequest(AsyncRequest):
GET_CLOUD_IMAGE_LIST = "GetCloudImageList"
GET_CLOUD_QUOTAS = "GetCloudQuotas"
CHANGE_INFRASTRUCTURE_AUTH = "ChangeInfrastructureAuth"
GET_INFRASTRUCTURE_OWNERS = "GetInfrastructureOwners"

@staticmethod
def create_request(function, arguments=()):
Expand Down Expand Up @@ -113,6 +114,8 @@ def create_request(function, arguments=()):
return Request_GetCloudQuotas(arguments)
elif function == IMBaseRequest.CHANGE_INFRASTRUCTURE_AUTH:
return Request_ChangeInfrastructureAuth(arguments)
elif function == IMBaseRequest.GET_INFRASTRUCTURE_OWNERS:
return Request_GetInfrastructureOwners(arguments)
else:
raise NotImplementedError("Function not Implemented")

Expand Down Expand Up @@ -445,3 +448,14 @@ def _call_function(self):
Authentication(new_auth),
overwrite,
Authentication(auth_data))


class Request_GetInfrastructureOwners(IMBaseRequest):
"""
Request class for the GetInfrastructureOwners function
"""

def _call_function(self):
self._error_mesage = "Error getting the Inf. owners"
(inf_id, auth_data) = self.arguments
return IM.InfrastructureManager.InfrastructureManager.GetInfrastructureOwners(inf_id, Authentication(auth_data))
1 change: 1 addition & 0 deletions IM/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ class Config:
VM_TAG_INF_ID = None
VM_TAG_IM_URL = None
VM_TAG_IM = None
ADMIN_USER = None


config = ConfigParser()
Expand Down
8 changes: 5 additions & 3 deletions doc/source/REST.rst
Original file line number Diff line number Diff line change
Expand Up @@ -46,11 +46,12 @@ Next tables summaries the resources and the HTTP methods available.
| **GET** | | **Get** the specified property ``property_name`` | | **Get** the specified property ``property_name`` |
| | | associated to the machine ``vmId`` in ``infId``. | | associated to the infrastructure ``infId``. |
| | | It has one special property: ``contmsg``. | | It has six properties: ``contmsg``, ``radl``, |
| | | | | ``state``, ``outputs``, ``tosca`` and ``data``. |
| | | | | ``state``, ``outputs``, ``tosca``, ``data`` and |
| | | | | ``owners``. |
+-------------+------------------------------------------------------+------------------------------------------------------+
| **POST** | | | **Modify** the specified property ``property_name``|
| | | | associated to the infrastructure ``infId``. |
| | | | only ``authorization`` property is valid. |
| | | | only ``authorization`` property is valid. |
+-------------+------------------------------------------------------+------------------------------------------------------+


Expand Down Expand Up @@ -173,6 +174,7 @@ GET ``http://imserver.com/infrastructures/<infId>/<property_name>``
:``data``: a string with the JSOMN serialized data of the infrastructure. In case of ``delete`` flag is set to 'yes',
'true' or '1' the data not only will be exported but also the infrastructure will be set deleted
(the virtual infrastructure will not be modified).
:``owners``: a list of strings with the current owners of the infrastructure.
:``state``: a JSON object with two elements:
:``state``: a string with the aggregated state of the infrastructure (see list of valid states in :ref:`IM-States`).
Expand All @@ -181,7 +183,7 @@ GET ``http://imserver.com/infrastructures/<infId>/<property_name>``
The result is JSON format has the following format::
{
["radl"|"tosca"|"state"|"contmsg"|"outputs"|"data"]: <property_value>
["radl"|"tosca"|"state"|"contmsg"|"outputs"|"data"|"owners"]: <property_value>
}

POST ``http://imserver.com/infrastructures/<infId>/authorization``
Expand Down
10 changes: 10 additions & 0 deletions doc/source/xmlrpc.rst
Original file line number Diff line number Diff line change
Expand Up @@ -406,3 +406,13 @@ This is the list of method names:
Change the authentication data of the infrastructure with ID ``infId``. using
the ``newAuth`` provider. If ``overwrite`` is true, the authentication data will
be overwrited otherwise it will be appended.

.. _GetInfrastructureOwners-xmlrpc:

``GetInfrastructureOwners``
:parameter 0: ``infId``: integer
:parameter 1: ``auth``: array of structs
:ok response: [true, list of strings]
:fail response: [false, ``error``: string]

Return the list of current owners of the infrastructure with ID ``infId``.
44 changes: 44 additions & 0 deletions doc/swagger_api.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -738,6 +738,50 @@ paths:
'404':
description: Not Found

/infrastructures/{InfId}/owners:
get:
tags:
- infrastructures
summary: Get the list of infrastructure owners.
security:
- IMAuth: []
description: >-
Return a list of strings with the set of users of the infrastructure
with ID InfId.
operationId: GetInfrastructureOwners
parameters:
- name: InfId
in: path
description: The ID of the specific infrastructure.
required: true
schema:
type: string
responses:
'200':
description: successful operation
content:
text/plain:
examples:
response:
value: |
user1
user2
application/json:
examples:
response:
value:
owners:
- user1
- user2
'400':
description: Invalid status value
'401':
description: Unauthorized
'403':
description: Forbidden
'404':
description: Not Found

/infrastructures/{InfId}/authorization:
post:
tags:
Expand Down
6 changes: 2 additions & 4 deletions docker-devel/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# Dockerfile to create a container with the IM service
FROM ubuntu:20.04
FROM ubuntu:22.04
ARG BRANCH=devel
LABEL maintainer="Miguel Caballer <[email protected]>"
LABEL version="1.12.0"
Expand All @@ -13,9 +13,7 @@ RUN apt-get update && apt-get install --no-install-recommends -y python3 python3
# Install IM
RUN apt-get update && apt-get install --no-install-recommends -y python3-setuptools python3-pip git && \
pip3 install msrest msrestazure azure-common azure-mgmt-storage azure-mgmt-compute azure-mgmt-network azure-mgmt-resource azure-mgmt-dns azure-identity && \
pip3 install pyOpenSSL cheroot xmltodict pymongo ansible==2.10.7&& \
pip3 install git+https://github.com/openstack/tosca-parser && \
pip3 install git+https://github.com/micafer/libcloud@micro && \
pip3 install pyOpenSSL cheroot xmltodict pymongo ansible==6.4.0&& \
pip3 install git+https://github.com/grycap/im@$BRANCH && \
apt-get purge -y git python3-pip && \
apt-get autoremove -y && apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* && rm -rf ~/.cache/
Expand Down
4 changes: 2 additions & 2 deletions docker-py3/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# Dockerfile to create a container with the IM service
FROM ubuntu:20.04
FROM ubuntu:22.04
LABEL maintainer="Miguel Caballer <[email protected]>"
LABEL version="1.12.0"
LABEL description="Container image to run the IM service. (http://www.grycap.upv.es/im)"
Expand All @@ -12,7 +12,7 @@ RUN apt-get update && apt-get install --no-install-recommends -y python3 python3
# Install IM
RUN apt-get update && apt-get install --no-install-recommends -y python3-setuptools python3-pip git && \
pip3 install msrest msrestazure azure-common azure-mgmt-storage azure-mgmt-compute azure-mgmt-network azure-mgmt-resource azure-mgmt-dns azure-identity==1.8.0 && \
pip3 install pyOpenSSL cheroot xmltodict pymongo ansible==2.10.7&& \
pip3 install pyOpenSSL cheroot xmltodict pymongo ansible==6.4.0&& \
pip3 install IM==1.12.0 && \
apt-get purge -y python3-pip git && \
apt-get autoremove -y && apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* && rm -rf ~/.cache/
Expand Down
7 changes: 7 additions & 0 deletions etc/im.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,13 @@ DATA_DB = sqlite:///etc/im/inf.dat
# IM user DB. To restrict the users that can access the IM service.
# Comment it or set a blank value to disable user check.
USER_DB =

# IM admin user. It will be able to manage all the infrastructures in the service.
# But it should also provide correct credentials to access cloud providers.
# ADMIN_USER = {"username": "user", "password": "pass"}
# In case of OIDC users, use this format:
# ADMIN_USER = {"username": "", "password": "https://some_issuer.com/user_sub", "token": ""}

# Maximum number of simultaneous VM launch/delete operations
# In some old versions of python (prior to 2.7.5 or 3.3.2) it can produce an error
# See https://bugs.python.org/issue10015. In this case set this value to 1
Expand Down
7 changes: 7 additions & 0 deletions im_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,12 @@ def ChangeInfrastructureAuth(inf_id, new_auth_data, overwrite, auth_data):
return WaitRequest(request)


def GetInfrastructureOwners(inf_id, auth_data):
request = IMBaseRequest.create_request(
IMBaseRequest.GET_INFRASTRUCTURE_OWNERS, (inf_id, auth_data))
return WaitRequest(request)


def launch_daemon():
"""
Launch the IM daemon
Expand Down Expand Up @@ -266,6 +272,7 @@ def launch_daemon():
server.register_function(GetCloudImageList)
server.register_function(GetCloudQuotas)
server.register_function(ChangeInfrastructureAuth)
server.register_function(GetInfrastructureOwners)

InfrastructureManager.logger.info(
'************ Start Infrastructure Manager daemon (v.%s) ************' % version)
Expand Down
18 changes: 18 additions & 0 deletions test/unit/REST.py
Original file line number Diff line number Diff line change
Expand Up @@ -1074,6 +1074,24 @@ def test_ChangeInfrastructureAuth(self, ChangeInfrastructureAuth, bottle_request
"password": "new_pass"}])
self.assertEqual(ChangeInfrastructureAuth.call_args_list[0][0][2], True)

@patch("IM.InfrastructureManager.InfrastructureManager.GetInfrastructureOwners")
@patch("bottle.request")
def test_GetInfrastructureOwners(self, bottle_request, GetInfrastructureOwners):
"""Test REST StopInfrastructure."""
bottle_request.return_value = MagicMock()
bottle_request.headers = {"AUTHORIZATION": ("type = InfrastructureManager; username = user; password = pass\\n"
"id = one; type = OpenNebula; host = onedock.i3m.upv.es:2633; "
"username = user; password = pass")}

GetInfrastructureOwners.return_value = ["user1", "user2"]

res = RESTGetInfrastructureProperty("1", "owners")
self.assertEqual(res, 'user1\nuser2')

bottle_request.headers["Accept"] = "application/json"
res = RESTGetInfrastructureProperty("1", "owners")
self.assertEqual(res, '{"owners": ["user1", "user2"]}')


if __name__ == "__main__":
unittest.main()
7 changes: 7 additions & 0 deletions test/unit/ServiceRequests.py
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,13 @@ def test_change_auth(self, inflist):
IM.ServiceRequests.IMBaseRequest.CHANGE_INFRASTRUCTURE_AUTH, ("", "", False, ""))
req._call_function()

@patch('IM.InfrastructureManager.InfrastructureManager')
def test_get_owners(self, inflist):
import IM.ServiceRequests
req = IM.ServiceRequests.IMBaseRequest.create_request(
IM.ServiceRequests.IMBaseRequest.GET_INFRASTRUCTURE_OWNERS, ("", ""))
req._call_function()


if __name__ == '__main__':
unittest.main()
Loading

0 comments on commit 95dde41

Please sign in to comment.