Skip to content
Snippets Groups Projects
Commit 794e34fa authored by torrespel's avatar torrespel
Browse files

New register Service with httpauth and uuid

parent 0f75053d
No related branches found
No related tags found
2 merge requests!43Staging to Main for Release 1,!20Ocf29 new registration flow
Pipeline #5447 passed with warnings
Showing
with 148 additions and 156 deletions
......@@ -104,18 +104,13 @@ def onboarded_invokers_post(body): # noqa: E501
"""
identity = get_jwt_identity()
username, role = identity.split()
if role != "invoker":
prob = ProblemDetails(title="Unauthorized", status=401, detail="Role not authorized for this API route",
cause="User role must be invoker")
return Response(json.dumps(prob, cls=JSONEncoder), status=401, mimetype='application/json')
username, uuid = identity.split()
if connexion.request.is_json:
body = APIInvokerEnrolmentDetails.from_dict(connexion.request.get_json()) # noqa: E501
current_app.logger.info("Creating Invoker")
res = invoker_operations.add_apiinvokerenrolmentdetail(body, username)
res = invoker_operations.add_apiinvokerenrolmentdetail(body, username, uuid)
if res.status_code == 201:
current_app.logger.info("Invoker Created")
publisher_ops.publish_message("events", "API_INVOKER_ONBOARDED")
......
......@@ -52,13 +52,11 @@ class InvokerManagementOperations(Resource):
self.config = Config().get_config()
def add_apiinvokerenrolmentdetail(self, apiinvokerenrolmentdetail, username):
def add_apiinvokerenrolmentdetail(self, apiinvokerenrolmentdetail, username, uuid):
mycol = self.db.get_col_by_name(self.db.invoker_enrolment_details)
register = self.db.get_col_by_name_register(self.db.capif_users)
#try:
current_app.logger.debug("Creating invoker resource")
res = mycol.find_one({'onboarding_information.api_invoker_public_key': apiinvokerenrolmentdetail.onboarding_information.api_invoker_public_key})
......@@ -83,9 +81,10 @@ class InvokerManagementOperations(Resource):
# Onboarding Date Record
invoker_dict = apiinvokerenrolmentdetail.to_dict()
invoker_dict["onboarding_date"] = datetime.now()
invoker_dict["username"]=username
invoker_dict["uuid"]=uuid
mycol.insert_one(apiinvokerenrolmentdetail.to_dict())
register.update_one({'username':username}, {"$push":{'list_invokers':api_invoker_id}})
current_app.logger.debug("Invoker inserted in database")
current_app.logger.debug("Netapp onboarded sucessfuly")
......@@ -141,7 +140,6 @@ class InvokerManagementOperations(Resource):
def remove_apiinvokerenrolmentdetail(self, onboard_id):
mycol = self.db.get_col_by_name(self.db.invoker_enrolment_details)
register = self.db.get_col_by_name_register(self.db.capif_users)
try:
current_app.logger.debug("Removing invoker resource")
result = self.__check_api_invoker_id(onboard_id)
......@@ -150,7 +148,6 @@ class InvokerManagementOperations(Resource):
return result
mycol.delete_one({'api_invoker_id':onboard_id})
register.update_one({'list_invokers':onboard_id}, {"$pull":{'list_invokers':onboard_id}})
self.auth_manager.remove_auth_invoker(onboard_id)
current_app.logger.debug("Invoker resource removed from database")
......
......@@ -15,7 +15,6 @@ class MongoDatabse():
def __init__(self):
self.config = Config().get_config()
self.db = self.__connect()
self.register = self.__connect_register()
self.invoker_enrolment_details = self.config['mongo']['col']
self.capif_users = self.config['mongo']['capif_users_col']
self.service_col = self.config['mongo']["service_col"]
......@@ -25,10 +24,6 @@ class MongoDatabse():
def get_col_by_name(self, name):
return self.db[name].with_options(codec_options=CodecOptions(tz_aware=True))
def get_col_by_name_register(self, name):
return self.register[name].with_options(codec_options=CodecOptions(tz_aware=True))
def __connect(self, max_retries=3, retry_delay=1):
retries = 0
......@@ -48,29 +43,8 @@ class MongoDatabse():
print(f"Reconnecting... Retry {retries} of {max_retries}")
time.sleep(retry_delay)
return None
def __connect_register(self, max_retries=3, retry_delay=1):
retries = 0
while retries < max_retries:
try:
uri = f"mongodb://{self.config['mongo_register']['user']}:{self.config['mongo_register']['password']}@" \
f"{self.config['mongo_register']['host']}:{self.config['mongo_register']['port']}"
client = MongoClient(uri)
mydb = client[self.config['mongo_register']['db']]
mydb.command("ping")
return mydb
except AutoReconnect:
retries += 1
print(f"Reconnecting... Retry {retries} of {max_retries}")
time.sleep(retry_delay)
return None
def close_connection(self):
if self.db.client:
self.db.client.close()
if self.register.client:
self.register.client.close()
......@@ -59,20 +59,15 @@ def registrations_post(body): # noqa: E501
"""
identity = get_jwt_identity()
username, role = identity.split()
username, uuid = identity.split()
current_app.logger.info("Registering Provider Domain")
if role != "provider":
prob = ProblemDetails(title="Unauthorized", status=401, detail="Role not authorized for this API route",
cause="User role must be provider")
return Response(json.dumps(prob, cls=JSONEncoder), status=401, mimetype='application/json')
if connexion.request.is_json:
body = APIProviderEnrolmentDetails.from_dict(connexion.request.get_json()) # noqa: E501
res = provider_management_ops.register_api_provider_enrolment_details(body, username)
res = provider_management_ops.register_api_provider_enrolment_details(body, username, uuid)
return res
......
......@@ -31,10 +31,9 @@ class ProviderManagementOperations(Resource):
Resource.__init__(self)
self.auth_manager = AuthManager()
def register_api_provider_enrolment_details(self, api_provider_enrolment_details, username):
def register_api_provider_enrolment_details(self, api_provider_enrolment_details, username, uuid):
try:
mycol = self.db.get_col_by_name(self.db.provider_enrolment_details)
register = self.db.get_col_by_name_register(self.db.capif_users)
current_app.logger.debug("Creating api provider domain")
search_filter = {'reg_sec': api_provider_enrolment_details.reg_sec}
......@@ -58,10 +57,11 @@ class ProviderManagementOperations(Resource):
# Onboarding Date Record
provider_dict = api_provider_enrolment_details.to_dict()
provider_dict["onboarding_date"] = datetime.now()
provider_dict["username"] = username
provider_dict["uuid"] = uuid
mycol.insert_one(provider_dict)
register.update_one({'username':username}, {"$push":{'list_providers':api_provider_enrolment_details.api_prov_dom_id}})
current_app.logger.debug("Provider inserted in database")
res = make_response(object=api_provider_enrolment_details, status=201)
......@@ -76,7 +76,6 @@ class ProviderManagementOperations(Resource):
def delete_api_provider_enrolment_details(self, api_prov_dom_id):
try:
mycol = self.db.get_col_by_name(self.db.provider_enrolment_details)
register = self.db.get_col_by_name_register(self.db.capif_users)
current_app.logger.debug("Deleting provider domain")
result = self.__check_api_provider_domain(api_prov_dom_id)
......@@ -89,7 +88,6 @@ class ProviderManagementOperations(Resource):
amf_id = [ provider_func['api_prov_func_id'] for provider_func in result["api_prov_funcs"] if provider_func['api_prov_func_role'] == 'AMF' ]
mycol.delete_one({'api_prov_dom_id': api_prov_dom_id})
register.update_one({'list_providers':api_prov_dom_id}, {"$pull":{'list_providers':api_prov_dom_id}})
out = "The provider matching apiProvDomainId " + api_prov_dom_id + " was offboarded."
current_app.logger.debug("Removed provider domain from database")
......
......@@ -16,17 +16,12 @@ class MongoDatabse():
def __init__(self):
self.config = Config().get_config()
self.db = self.__connect()
self.register = self.__connect_register()
self.provider_enrolment_details = self.config['mongo']['col']
self.capif_users = self.config['mongo']['capif_users']
self.certs_col = self.config['mongo']['certs_col']
def get_col_by_name(self, name):
return self.db[name].with_options(codec_options=CodecOptions(tz_aware=True))
def get_col_by_name_register(self, name):
return self.register[name].with_options(codec_options=CodecOptions(tz_aware=True))
def __connect(self, max_retries=3, retry_delay=1):
retries = 0
......@@ -44,30 +39,8 @@ class MongoDatabse():
time.sleep(retry_delay)
return None
def __connect_register(self, max_retries=3, retry_delay=1):
retries = 0
while retries < max_retries:
try:
uri = f"mongodb://{self.config['mongo_register']['user']}:{self.config['mongo_register']['password']}@" \
f"{self.config['mongo_register']['host']}:{self.config['mongo_register']['port']}"
client = MongoClient(uri)
mydb = client[self.config['mongo_register']['db']]
mydb.command("ping")
return mydb
except AutoReconnect:
retries += 1
print(f"Reconnecting... Retry {retries} of {max_retries}")
time.sleep(retry_delay)
return None
def close_connection(self):
if self.db.client:
self.db.client.close()
if self.register.client:
self.register.client.close()
......@@ -12,3 +12,7 @@ ca_factory: {
"token": "dev-only-token"
}
register: {
register_uuid: '6ba7b810-9dad-11d1-80b4-00c04fd430c8',
admin_users: {admin: "password123"}
}
\ No newline at end of file
#!/bin/bash
CERTS_FOLDER="/usr/src/app/register_service"
CERTS_FOLDER="/usr/src/app/register_service/certs"
cd $CERTS_FOLDER
VAULT_ADDR="http://$VAULT_HOSTNAME:$VAULT_PORT"
VAULT_TOKEN=$VAULT_ACCESS_TOKEN
curl -k -retry 30 \
--retry-all-errors \
--connect-timeout 5 \
--max-time 10 \
--retry-delay 10 \
--retry-max-time 300 \
--header "X-Vault-Token: $VAULT_TOKEN" \
--request GET "$VAULT_ADDR/v1/secret/data/server_cert/private" 2>/dev/null | jq -r '.data.data.key' -j > $CERTS_FOLDER/server.key
openssl req -x509 \
-sha256 -days 356 \
-nodes \
-newkey rsa:2048 \
-subj "/CN=register/C=ES/L=Madrid" \
-keyout /usr/src/app/register_service/registerCA.key -out /usr/src/app/register_service/registerCA.crt
-keyout /usr/src/app/register_service/certs/registerCA.key -out /usr/src/app/register_service/certs/registerCA.crt
openssl genrsa -out /usr/src/app/register_service/register_key.key 2048
openssl genrsa -out /usr/src/app/register_service/certs/register_key.key 2048
COUNTRY="ES" # 2 letter country-code
STATE="Madrid" # state or province name
......@@ -37,7 +26,7 @@ COMPANY="" # company name
# DAYS="-days 365"
# create the certificate request
cat <<__EOF__ | openssl req -new $DAYS -key /usr/src/app/register_service/register_key.key -out /usr/src/app/register_service/register.csr
cat <<__EOF__ | openssl req -new $DAYS -key /usr/src/app/register_service/certs/register_key.key -out /usr/src/app/register_service/certs/register.csr
$COUNTRY
$STATE
$LOCALITY
......@@ -49,7 +38,7 @@ $CHALLENGE
$COMPANY
__EOF__
openssl x509 -req -in /usr/src/app/register_service/register.csr -CA /usr/src/app/register_service/registerCA.crt -CAkey /usr/src/app/register_service/registerCA.key -CAcreateserial -out /usr/src/app/register_service/register_cert.crt -days 365 -sha256
openssl x509 -req -in /usr/src/app/register_service/certs/register.csr -CA /usr/src/app/register_service/certs/registerCA.crt -CAkey /usr/src/app/register_service/certs/registerCA.key -CAcreateserial -out /usr/src/app/register_service/certs/register_cert.crt -days 365 -sha256
cd /usr/src/app/
python3 -m register_service
\ No newline at end of file
import os
import base64
from flask import Flask
from .controllers.register_controller import register_routes
from flask_jwt_extended import JWTManager
from OpenSSL.crypto import PKey, TYPE_RSA, X509Req, dump_certificate_request, FILETYPE_PEM, dump_privatekey
import requests
import json
from .config import Config
app = Flask(__name__)
jwt = JWTManager(app)
with open("/usr/src/app/register_service/server.key", "rb") as key_file:
key_data = key_file.read()
config = Config().get_config()
# Create a superadmin CSR and keys
key = PKey()
key.generate_key(TYPE_RSA, 2048)
req = X509Req()
req.get_subject().O = 'Telefonica I+D'
req.get_subject().OU = 'Innovation'
req.get_subject().L = 'Madrid'
req.get_subject().ST = 'Madrid'
req.get_subject().C = 'ES'
req.get_subject().emailAddress = 'inno@tid.es'
req.set_pubkey(key)
req.sign(key, 'sha256')
csr_request = dump_certificate_request(FILETYPE_PEM, req)
private_key = dump_privatekey(FILETYPE_PEM, key)
# Save superadmin private key
key_file = open("register_service/certs/superadmin.key", 'wb+')
key_file.write(bytes(private_key))
key_file.close()
# Request superadmin certificate
url = 'http://{}:{}/v1/pki_int/sign/my-ca'.format(config["ca_factory"]["url"], config["ca_factory"]["port"])
headers = {'X-Vault-Token': f"{config["ca_factory"]["token"]}"}
data = {
'format':'pem_bundle',
'ttl': '43000h',
'csr': csr_request,
'common_name': "superadmin"
}
response = requests.request("POST", url, headers=headers, data=data, verify = False)
superadmin_cert = json.loads(response.text)['data']['certificate']
# Svae the superadmin certificate
cert_file = open("register_service/certs/superadmin.crt", 'wb')
cert_file.write(bytes(superadmin_cert, 'utf-8'))
cert_file.close()
# Request CAPIF private key to encode the token
url = 'http://{}:{}/v1/secret/data/server_cert/private'.format(config["ca_factory"]["url"], config["ca_factory"]["port"])
headers = {'X-Vault-Token': f"{config["ca_factory"]["token"]}"}
response = requests.request("GET", url, headers=headers, verify = False)
key_data = json.loads(response.text)["data"]["data"]["key"]
app.config['JWT_ALGORITHM'] = 'RS256'
app.config['JWT_PRIVATE_KEY'] = key_data
app.register_blueprint(register_routes)
#----------------------------------------
# launch
#----------------------------------------
if __name__ == "__main__":
app.run(debug=True, host = '0.0.0.0', port=8080, ssl_context= ("/usr/src/app/register_service/register_cert.crt", "/usr/src/app/register_service/register_key.key"))
app.run(debug=True, host = '0.0.0.0', port=8080, ssl_context= ("/usr/src/app/register_service/certs/register_cert.crt", "/usr/src/app/register_service/certs/register_key.key"))
import bcrypt
def hash_password(password):
hashed_password = bcrypt.hashpw(password.encode('utf-8'), bcrypt.gensalt())
return hashed_password
def check_password(input_password, stored_password):
return bcrypt.checkpw(input_password.encode('utf-8'), stored_password)
\ No newline at end of file
#!/usr/bin/env python3
from flask import Flask, jsonify, request, Blueprint
from flask_jwt_extended import JWTManager, jwt_required, create_access_token
from pymongo import MongoClient
from flask import current_app, Flask, jsonify, request, Blueprint
from ..core.register_operations import RegisterOperations
import secrets
from ..config import Config
from flask_httpauth import HTTPBasicAuth
auth = HTTPBasicAuth()
config = Config().get_config()
@auth.verify_password
def verify_password(username, password):
users = register_operation.get_users()[0].json["users"]
if username in config["register"]["admin_users"] and password == config["register"]["admin_users"][username]:
return username, "admin"
for user in users:
if user["username"] == username and user["password"]==password:
return username, "client"
def admin_required(fn):
def wrapper(*args, **kwargs):
username, role = auth.current_user()
if role == 'admin':
return fn(*args, **kwargs)
else:
return {"Access denied. Administrator privileges required."}, 403
wrapper.__name__ = fn.__name__
return wrapper
register_routes = Blueprint("register_routes", __name__)
register_operation = RegisterOperations()
@register_routes.route("/register", methods=["POST"])
@register_routes.route("/createUser", methods=["POST"])
@auth.login_required
@admin_required
def register():
username = request.json["username"]
password = request.json["password"]
description = request.json["description"]
role = request.json["role"]
cn = request.json["cn"]
if role != "invoker" and role != "provider":
return jsonify(message="Role must be invoker or provider"), 400
return register_operation.register_user(username, password, description, cn, role)
email = request.json["email"]
return register_operation.register_user(username, password, description, email)
@register_routes.route("/getauth", methods=["POST"])
@auth.login_required
def getauth():
username = request.json["username"]
password = request.json["password"]
return register_operation.get_auth(username, password)
@register_routes.route("/remove", methods=["DELETE"])
@register_routes.route("/deleteUser", methods=["DELETE"])
@auth.login_required
@admin_required
def remove():
username = request.json["username"]
password = request.json["password"]
return register_operation.remove_user(username, password)
@register_routes.route("/getUsers", methods=["GET"])
@auth.login_required
@admin_required
def getUsers():
return register_operation.get_users()
from flask import Flask, jsonify, request, current_app
from flask import current_app, Flask, jsonify, request, Response
from flask_jwt_extended import create_access_token
from ..db.db import MongoDatabse
from datetime import datetime
from ..config import Config
from register_service import auth_utils
import secrets
import requests
import json
import sys
import uuid
class RegisterOperations:
def __init__(self):
......@@ -16,28 +16,20 @@ class RegisterOperations:
self.mimetype = 'application/json'
self.config = Config().get_config()
def register_user(self, username, password, description, cn, role):
def register_user(self, username, password, description, email):
mycol = self.db.get_col_by_name(self.db.capif_users)
exist_user = mycol.find_one({"username": username})
if exist_user:
return jsonify("user already exists"), 409
name_space = uuid.UUID(self.config["register"]["register_uuid"])
user_uuid = str(uuid.uuid5(name_space, username))
hashed_password = auth_utils.hash_password(password)
user_info = dict(_id=secrets.token_hex(7), username=username, password=hashed_password, role=role, description=description, cn=cn, list_invokers=[], list_providers=[])
user_info = dict(uuid=user_uuid, username=username, password=password, description=description, email=email, onboarding_date=datetime.now())
obj = mycol.insert_one(user_info)
if role == "invoker":
return jsonify(message="invoker registered successfully",
id=obj.inserted_id,
ccf_onboarding_url="api-invoker-management/v1/onboardedInvokers",
ccf_discover_url="service-apis/v1/allServiceAPIs?api-invoker-id="), 201
else:
return jsonify(message="provider" + " registered successfully",
id=obj.inserted_id,
ccf_api_onboarding_url="api-provider-management/v1/registrations",
ccf_publish_url="published-apis/v1/<apfId>/service-apis"), 201
return jsonify(message="invoker registered successfully", uuid=user_uuid), 201
def get_auth(self, username, password):
......@@ -45,16 +37,12 @@ class RegisterOperations:
try:
exist_user = mycol.find_one({"username": username})
exist_user = mycol.find_one({"username": username, "password": password})
if exist_user is None:
return jsonify("No user with these credentials"), 400
return jsonify("Not exister user with this credentials"), 400
stored_password = exist_user["password"]
if not auth_utils.check_password(password, stored_password):
return jsonify("No user with these credentials"), 400
access_token = create_access_token(identity=(username + " " + exist_user["role"]))
access_token = create_access_token(identity=(username + " " + exist_user["uuid"]))
url = f"http://{self.config['ca_factory']['url']}:{self.config['ca_factory']['port']}/v1/secret/data/ca"
headers = {
......@@ -62,7 +50,13 @@ class RegisterOperations:
}
response = requests.request("GET", url, headers=headers, verify = False)
response_payload = json.loads(response.text)
return jsonify(message="Token and CA root returned successfully", access_token=access_token, ca_root=response_payload['data']['data']['ca']), 200
return jsonify(message="Token and CA root returned successfully",
access_token=access_token,
ca_root=response_payload['data']['data']['ca'],
ccf_api_onboarding_url="api-provider-management/v1/registrations",
ccf_publish_url="published-apis/v1/<apfId>/service-apis",
ccf_onboarding_url="api-invoker-management/v1/onboardedInvokers",
ccf_discover_url="service-apis/v1/allServiceAPIs?api-invoker-id="), 200
except Exception as e:
return jsonify(message=f"Errors when try getting auth: {e}"), 500
......@@ -71,17 +65,22 @@ class RegisterOperations:
mycol = self.db.get_col_by_name(self.db.capif_users)
try:
exist_user = mycol.find_one({"username": username})
mycol.delete_one({"username": username, "password": password})
# Request to the helper to delete invokers and providers
if exist_user is None:
return jsonify("No user with these credentials"), 400
stored_password = exist_user["password"]
if not auth_utils.check_password(password, stored_password):
return jsonify("No user with these credentials"), 400
mycol.delete_one({"username": username})
return jsonify(message="User removed successfully"), 204
except Exception as e:
return jsonify(message=f"Errors when try remove user: {e}"), 500
def get_users(self):
mycol = self.db.get_col_by_name(self.db.capif_users)
try:
users=list(mycol.find({}, {"_id":0}))
return jsonify(message="Users successfully obtained", users=users), 200
except Exception as e:
return jsonify(message=f"Error trying to get users: {e}"), 500
......@@ -7,3 +7,4 @@ pyopenssl
pyyaml
requests
bcrypt
flask_httpauth
\ No newline at end of file
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment