diff --git a/contrib/encrypter/Dockerfile b/contrib/encrypter/Dockerfile new file mode 100644 index 0000000000..d7e592c55c --- /dev/null +++ b/contrib/encrypter/Dockerfile @@ -0,0 +1,60 @@ +# Copyright (c) 2022, 2023 Humanitarian OpenStreetMap Team +# This file is part of FMTM. +# +# FMTM is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# FMTM is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with FMTM. If not, see . +# +ARG PYTHON_IMG_TAG=3.10 + + +# Includes all labels and timezone info to extend from +FROM docker.io/python:${PYTHON_IMG_TAG}-slim-bookworm as base +RUN set -ex \ + && apt-get update \ + && DEBIAN_FRONTEND=noninteractive apt-get install \ + -y --no-install-recommends "locales" "ca-certificates" \ + && DEBIAN_FRONTEND=noninteractive apt-get upgrade -y \ + && rm -rf /var/lib/apt/lists/* \ + && update-ca-certificates +# Set locale +RUN sed -i '/en_US.UTF-8/s/^# //g' /etc/locale.gen && locale-gen +ENV LANG en_US.UTF-8 +ENV LANGUAGE en_US:en +ENV LC_ALL en_US.UTF-8 + + +# Build stage will all dependencies required to build Python wheels +FROM base as build +RUN set -ex \ + && apt-get update \ + && DEBIAN_FRONTEND=noninteractive apt-get install \ + -y --no-install-recommends \ + "build-essential" \ + "gcc" \ + && rm -rf /var/lib/apt/lists/* +RUN pip install --user --no-warn-script-location \ + --no-cache-dir cryptography==42.0.5 + + +# Run stage will minimal dependencies required to run Python libraries +FROM base as runtime +ENV PYTHONDONTWRITEBYTECODE=1 \ + PYTHONUNBUFFERED=1 \ + PYTHONFAULTHANDLER=1 +# Copy Python deps from build to runtime +COPY --from=build \ + /root/.local \ + /root/.local +WORKDIR /code +COPY encrypter.py . +ENTRYPOINT ["python", "encrypter.py"] diff --git a/contrib/encrypter/README.md b/contrib/encrypter/README.md new file mode 100644 index 0000000000..6f645b11d1 --- /dev/null +++ b/contrib/encrypter/README.md @@ -0,0 +1,42 @@ +# Encrypter Util + +FMTM encrypts the ODK access token & ODK passwords in the database. + +Sometimes these must be manually encrypted / decrypted for the database. + +This util makes that process easier. + +```bash +usage: encrypter.py [-h] [--encrypt] [--decrypt] key value + +Encrypt or decrypt string values. + +positional arguments: + key Encryption key. + value Value to encrypt or decrypt. + +options: + -h, --help show this help message and exit + --encrypt Encrypt the value. + --decrypt Decrypt the value. +``` + +> Note the values may need to be quoted, as they often contain special chars. + +## Encrypt a value + +```bash +docker run -i --rm ghcr.io/hotosm/fmtm/encrypter:latest \ + --encrypt \ + your_encryption_token \ + some_value_to_encrypt +``` + +## Decrypt a value + +```bash +docker run -i --rm ghcr.io/hotosm/fmtm/encrypter:latest \ + --decrypt \ + your_encryption_token \ + some_value_to_encrypt +``` diff --git a/contrib/encrypter/build.sh b/contrib/encrypter/build.sh new file mode 100644 index 0000000000..4f6776cdb8 --- /dev/null +++ b/contrib/encrypter/build.sh @@ -0,0 +1,5 @@ +#!/bin/bash + +docker build . -t ghcr.io/hotosm/fmtm/encrypter:latest + +docker push ghcr.io/hotosm/fmtm/encrypter:latest diff --git a/contrib/encrypter/encrypter.py b/contrib/encrypter/encrypter.py new file mode 100644 index 0000000000..e15d9214d9 --- /dev/null +++ b/contrib/encrypter/encrypter.py @@ -0,0 +1,76 @@ +"""Convert between JSON and QRCode formats.""" + +import sys +import argparse +import base64 +from cryptography.fernet import Fernet + + + +def get_cipher_suite(key): + """Cache cypher suite.""" + return Fernet(key) + + +def encrypt_value(key: str, value: str) -> str: + """Encrypt value before going to the DB.""" + cipher_suite = get_cipher_suite(key) + encrypted_password = cipher_suite.encrypt(value.encode("utf-8")) + return base64.b64encode(encrypted_password).decode("utf-8") + + +def decrypt_value(key: str, value: str) -> str: + """Decrypt the database value.""" + cipher_suite = get_cipher_suite(key) + encrypted_password = base64.b64decode(value.encode("utf-8")) + decrypted_password = cipher_suite.decrypt(encrypted_password) + return decrypted_password.decode("utf-8") + + +def display_value(value: str) -> None: + """Pretty print the final value.""" + print("") + print("Value:") + print("") + print(value) + print("") + + +if __name__ == "__main__": + parser = argparse.ArgumentParser( + description="Encrypt or decrypt string values." + ) + parser.add_argument( + "key", + help="Encryption key.", + ) + parser.add_argument( + "value", + help="Value to encrypt or decrypt.", + ) + parser.add_argument( + "--encrypt", + action="store_true", + help="Encrypt the value.", + ) + parser.add_argument( + "--decrypt", + action="store_true", + help="Decrypt the value.", + ) + + args = parser.parse_args() + + if args.encrypt and args.decrypt: + print("Cannot both encrypt and decrypt at the same time.") + print("Pass one of either: --encrypt or --decrypt") + sys.exit(1) + + if args.encrypt: + result = encrypt_value(args.key, args.value) + display_value(result) + elif args.decrypt: + result = decrypt_value(args.key, args.value) + display_value(result) + else: + print("Please provide either --encrypt or --decrypt flag.")