diff --git a/.github/workflows/push_trigger.yml b/.github/workflows/push_trigger.yml index aa10dbd..afe4624 100644 --- a/.github/workflows/push_trigger.yml +++ b/.github/workflows/push_trigger.yml @@ -28,6 +28,8 @@ jobs: include: - SERVICE_LOCATION: 'databreachdetector' SERVICE_NAME: 'databreachdetector' + - SERVICE_LOCATION: 'certmanager' + SERVICE_NAME: 'certmanager' fail-fast: false name: ${{ matrix.SERVICE_NAME }} uses: mosip/kattu/.github/workflows/docker-build.yml@master diff --git a/certmanager/Dockerfile b/certmanager/Dockerfile new file mode 100644 index 0000000..6a6fb3c --- /dev/null +++ b/certmanager/Dockerfile @@ -0,0 +1,39 @@ +FROM python:3.9 + +ARG SOURCE +ARG COMMIT_HASH +ARG COMMIT_ID +ARG BUILD_TIME +LABEL source=${SOURCE} +LABEL commit_hash=${COMMIT_HASH} +LABEL commit_id=${COMMIT_ID} +LABEL build_time=${BUILD_TIME} + +ARG container_user=mosip +ARG container_user_group=mosip +ARG container_user_uid=1001 +ARG container_user_gid=1001 + +# Create user group +RUN groupadd -r ${container_user_group} && useradd -u ${container_user_uid} -r -g ${container_user_group} -s /bin/bash -m -d /home/${container_user} ${container_user} +RUN chown -R ${container_user}:${container_user} /home/${container_user} +WORKDIR /home/${container_user} +USER ${container_user} + +ENV MYDIR=`pwd` +ENV DATE="$(date --utc +%FT%T.%3NZ)" +ENV ENABLE_INSECURE=false +ENV MODULE= + + +ENV db-server= +ENV db-port= +ENV db-su-user= +ENV postgres-password= + +COPY partner.properties . +COPY requirements.txt . +COPY checkupdate.py . +COPY bootstrap.properties . +RUN pip install --no-cache-dir -r requirements.txt +CMD ["python", "checkupdate.py"] \ No newline at end of file diff --git a/certmanager/bootstrap.properties b/certmanager/bootstrap.properties new file mode 100644 index 0000000..6e21043 --- /dev/null +++ b/certmanager/bootstrap.properties @@ -0,0 +1,9 @@ +[Database] +db-host = postgres.soil-security.mosip.net +db-port = 5432 +db-su-user = postgres +postgres-password = cUZ5DLgLqz +[API] +mosip-api-internal-host=api-internal.soil-security.mosip.net +mosip_pms_client_secret=kxDGFoBJRsa9BprI +pre-expiry-days=400 \ No newline at end of file diff --git a/certmanager/checkupdate.py b/certmanager/checkupdate.py new file mode 100644 index 0000000..fadc0f5 --- /dev/null +++ b/certmanager/checkupdate.py @@ -0,0 +1,225 @@ +import os +import json +import psycopg2 +import requests +from urllib.request import Request, urlopen +from urllib.error import HTTPError +from datetime import datetime, timedelta +from configparser import ConfigParser + +# Function to read value from bootstrap.properties +def read_bootstrap_properties(key): + with open('bootstrap.properties', 'r') as file: + for line in file: + if line.startswith(key): + return line.split('=')[1].strip() + return None + +# Function to check if certificate is expired +def is_certificate_expired(expiration_date): + # Parse expiration date string + expiration_date = datetime.strptime(expiration_date, "%b %d %H:%M:%S %Y %Z") + # Get current date + current_date = datetime.utcnow() + # Compare expiration date with current date + return current_date > expiration_date + +# Function to write expired certificates to a text file +def write_to_expired_txt(cert_name): + with open('expired.txt', 'a') as file: + file.write(cert_name + '\n') + +# Function to format certificate data +def format_certificate(cert_data): + # Replace line breaks with "\\n" + formatted_cert_data = cert_data.replace("\n", "\\n") + return formatted_cert_data + +# Function to retrieve certificate data from the database +def retrieve_certificate_data(partner_id, db_host, db_port, db_user, db_password): + try: + # Connect to the PMS database + pms_conn = psycopg2.connect( + host=db_host, + port=db_port, + database="mosip_pms", + user=db_user, + password=db_password + ) + pms_cursor = pms_conn.cursor() + + # Query to retrieve the certificate alias + sql_query_cert_alias = f"SELECT certificate_alias FROM pms.partner WHERE id = '{partner_id}';" + pms_cursor.execute(sql_query_cert_alias) + certificate_alias = pms_cursor.fetchone()[0] + + # Query to retrieve cert_data using the certificate alias + sql_query_cert_data = f"SELECT cert_data FROM keymgr.partner_cert_store WHERE cert_id = '{certificate_alias}';" + + # Connect to the Keymgr database + keymgr_conn = psycopg2.connect( + host=db_host, + port=db_port, + database="mosip_keymgr", + user=db_user, + password=db_password + ) + keymgr_cursor = keymgr_conn.cursor() + keymgr_cursor.execute(sql_query_cert_data) + cert_data = keymgr_cursor.fetchone()[0] + + # Format the certificate data + formatted_cert_data = format_certificate(cert_data) + + # Close connections + pms_cursor.close() + pms_conn.close() + keymgr_cursor.close() + keymgr_conn.close() + + return formatted_cert_data + + except Exception as e: + print(f"Error retrieving certificate data for Partner ID '{partner_id}', Check partner name in expired.txt: {str(e)}") + return None + +# Function to authenticate and retrieve the token +def authenticate_and_get_token(base_url, client_secret): + auth_url = f"https://{base_url}/v1/authmanager/authenticate/clientidsecretkey" + headers = {"Content-Type": "application/json"} + + auth_data = { + "id": "string", + "metadata": {}, + "request": { + "appId": "ida", + "clientId": "mosip-pms-client", + "secretKey": client_secret + }, + "requesttime": "", # Generate timestamp in desired format + "version": "string" + } + + response = requests.post(auth_url, headers=headers, json=auth_data) + if response.status_code == 200: + token = response.headers.get("authorization") + return token + else: + print("Authentication failed.") + print("Auth API Response:", response.text) + return None + +# Function to upload certificate with authentication token +def upload_certificate_with_token(token, cert_data, partner_id, base_url): + upload_url = f"https://{base_url}/v1/partnermanager/partners/certificate/upload" + headers = { + "Content-Type": "application/json", + "Cookie": f"Authorization={token}" + } + + # Format certificate data + formatted_cert_data = cert_data.replace("\\n", "\n") + + upload_data = { + "id": "string", + "metadata": {}, + "request": { + "certificateData": formatted_cert_data, + "partnerDomain": "AUTH", + "partnerId": partner_id + }, + "requesttime": "", # Generate timestamp in desired format + "version": "string" + } + + response = requests.post(upload_url, headers=headers, json=upload_data) + + if "certificateId" not in response.text: + print("Certificate renewal failed.") + print("Upload API Response:", response.text) + else: + print("Certificate renewed successfully.") + +# Fetching environment variables or values from bootstrap.properties +postgres_host = os.environ.get('db-host') +postgres_port = os.environ.get('db-port') +postgres_user = os.environ.get('db-su-user') +postgres_password = os.environ.get('postgres-password') +base_url = os.environ.get('mosip-api-internal-host') +client_secret = os.environ.get('mosip_pms_client_secret') + +# If environment variables are not set, read from bootstrap.properties file +if not all([postgres_host, postgres_port, postgres_user, postgres_password, base_url, client_secret]): + config = ConfigParser() + config.read('bootstrap.properties') + postgres_host = config.get('Database', 'db-host', fallback='') + postgres_port = config.get('Database', 'db-port', fallback='') + postgres_user = config.get('Database', 'db-su-user', fallback='') + postgres_password = config.get('Database', 'postgres-password', fallback='') + base_url = config.get('API', 'mosip-api-internal-host', fallback='') + client_secret = config.get('API', 'mosip_pms_client_secret', fallback='') + +# Authenticate and get the token +TOKEN = authenticate_and_get_token(base_url, client_secret) + +# Check if token is obtained successfully +if TOKEN: + # Read pre-expiry days from bootstrap.properties + PRE_EXPIRY_DAYS = read_bootstrap_properties("pre-expiry-days") + + # PARTNER_IDS read from partner.properties + with open('partner.properties', 'r') as file: + for line in file: + if line.startswith('PARTNER_ID'): + partner_ids = line.strip().split('=')[1].split(',') + for PARTNER_ID in partner_ids: + print(f"\nProcessing partner ID: {PARTNER_ID.strip()}") + # Request certificate information + try: + req = Request(f"https://{base_url}/v1/partnermanager/partners/{PARTNER_ID.strip()}/certificate", + headers={ + "Content-Type": "application/json", + "Cookie": f"Authorization={TOKEN}" + }, + method="GET") + response = urlopen(req) + response_data = json.loads(response.read().decode('utf-8')) + CERTIFICATE_DATA = response_data.get('response', {}).get('certificateData') + print(CERTIFICATE_DATA) + # Run openssl command to print certificate details + openssl_command = f"echo '{CERTIFICATE_DATA}' | openssl x509 -noout -enddate" + expiration_date = os.popen(openssl_command).read().split('=')[1].strip() + print("Certificate expiration date:", expiration_date) + # Check if certificate is expired or pre-expiry + if is_certificate_expired(expiration_date) or \ + (datetime.strptime(expiration_date, "%b %d %H:%M:%S %Y %Z") - datetime.utcnow()) <= timedelta(days=int(PRE_EXPIRY_DAYS)): + write_to_expired_txt(PARTNER_ID.strip()) + except HTTPError as e: + print(f"Error occurred while fetching certificate information for {PARTNER_ID}: {e}") + continue + + if not CERTIFICATE_DATA: + print(f"No data available for {PARTNER_ID} in keymanager.") + continue + + # Check if expired.txt exists before trying to read from it + if os.path.exists("expired.txt"): + with open("expired.txt", "r") as file: + expired_partner_ids = [line.strip() for line in file if line.strip()] + else: + expired_partner_ids = [] + + # Check if any certificates were found to be expired + if not expired_partner_ids: + print("None of the certs have expired.") + exit(0) + + for partner_id in expired_partner_ids: + print(f"Certificate renewal started for Partner ID: {partner_id}") + cert_data = retrieve_certificate_data(partner_id, postgres_host, postgres_port, postgres_user, postgres_password) + if cert_data is not None: + upload_certificate_with_token(TOKEN, cert_data, partner_id, base_url) + + print("Certificate check and renewal process completed.") +else: + print("Failed while trying to get auth-token") diff --git a/certmanager/partner.properties b/certmanager/partner.properties new file mode 100644 index 0000000..cc2571b --- /dev/null +++ b/certmanager/partner.properties @@ -0,0 +1 @@ +PARTNER_ID=mpartner-default-auth \ No newline at end of file diff --git a/certmanager/requirements.txt b/certmanager/requirements.txt new file mode 100644 index 0000000..33678ea --- /dev/null +++ b/certmanager/requirements.txt @@ -0,0 +1,2 @@ +psycopg2-binary==2.9.1 +requests==2.26.0 \ No newline at end of file