diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..54d0d69 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,8 @@ +log/ +.idea +.github +*.db +*.yml +*.md +*.sh +Makefile diff --git a/.gitignore b/.gitignore index a149ec6..874f86a 100644 --- a/.gitignore +++ b/.gitignore @@ -129,4 +129,7 @@ dmypy.json .pyre/ # Helm -charts/*/*.tgz \ No newline at end of file +charts/*/*.tgz +/.idea/ +docker-compose-*.yml +*.sh diff --git a/docker/docker-py3-kms-minimal/Dockerfile b/docker/docker-py3-kms-minimal/Dockerfile index 25a28af..e4cd8ed 100644 --- a/docker/docker-py3-kms-minimal/Dockerfile +++ b/docker/docker-py3-kms-minimal/Dockerfile @@ -1,6 +1,5 @@ # This is a minimized version from docker/docker-py3-kms/Dockerfile without SQLite support to further reduce image size - -FROM alpine:3.12 +FROM alpine:3.14 ENV IP 0.0.0.0 ENV PORT 1688 @@ -11,7 +10,7 @@ ENV ACTIVATION_INTERVAL 120 ENV RENEWAL_INTERVAL 10080 ENV HWID RANDOM ENV LOGLEVEL INFO -ENV LOGFILE /dev/stdout +ENV LOGFILE STDOUT ENV LOGSIZE "" COPY ./py-kms /home/py-kms @@ -25,13 +24,17 @@ RUN apk add --no-cache --update \ python3-tkinter \ sqlite-libs \ py3-pip \ - tzdata \ + tzdata \ + netcat-openbsd \ build-base python3-dev && \ - pip3 install peewee tzlocal pytz && \ - apk del git build-base python3-dev - -# Fix undefined timezone, in case the user did not mount the /etc/localtime -RUN cp /usr/share/zoneinfo/UTC /etc/localtime + pip3 install --no-cache peewee tzlocal pytz wheel && \ + apk del git build-base python3-dev && \ + && addgroup power_users \ + && adduser -S py-kms -G users -s /bin/bash \ + && usermod -a -G power_users py-kms \ + && chown py-kms:users /home/py-kms \ + # Fix undefined timezone, in case the user did not mount the /etc/localtime + && ln -sf /usr/share/zoneinfo/UTC /etc/localtime WORKDIR /home/py-kms @@ -39,4 +42,7 @@ EXPOSE ${PORT}/tcp COPY docker/entrypoint.py /usr/bin/entrypoint.py RUN chmod a+x /usr/bin/entrypoint.py -ENTRYPOINT ["/usr/bin/python3", "/usr/bin/entrypoint.py"] +HEALTHCHECK --interval=5m --timeout=3s --start-period=10s --retries=4 CMD echo | nc -t localhost ${PORT} || exit 1 + +ENTRYPOINT ["/usr/bin/python3", "-u","/usr/bin/entrypoint.py"] +CMD["/usr/bin/start.py"] diff --git a/docker/docker-py3-kms/Dockerfile b/docker/docker-py3-kms/Dockerfile index c2ee203..c4221ac 100644 --- a/docker/docker-py3-kms/Dockerfile +++ b/docker/docker-py3-kms/Dockerfile @@ -1,4 +1,5 @@ -FROM alpine:3.12 +# Switch to the target image +FROM alpine:3.14 ENV IP 0.0.0.0 ENV PORT 1688 @@ -11,39 +12,52 @@ ENV SQLITE true ENV SQLITE_PORT 8080 ENV HWID RANDOM ENV LOGLEVEL INFO -ENV LOGFILE /dev/stdout +ENV LOGFILE STDOUT ENV LOGSIZE "" +ENV TZ America/Chicago -COPY ./py-kms /home/py-kms - +COPY py-kms /home/py-kms/ +#hadolint ignore=DL3013,DL3018 RUN apk add --no-cache --update \ - bash \ - git \ - py3-argparse \ - py3-flask \ - py3-pygments \ - python3-tkinter \ - sqlite-libs \ - py3-pip \ - tzdata \ - build-base python3-dev && \ - git clone https://github.com/coleifer/sqlite-web.git /tmp/sqlite_web && \ - cd /tmp/sqlite_web && \ - git checkout 2e7c85da3d37f80074ed3ae39b5851069b4f301c && \ - cd / && \ - mv /tmp/sqlite_web/sqlite_web /home/ && \ - rm -rf /tmp/sqlite_web && \ - pip3 install peewee tzlocal pytz pysqlite3 && \ - apk del git build-base python3-dev - -# Fix undefined timezone, in case the user did not mount the /etc/localtime -RUN cp /usr/share/zoneinfo/UTC /etc/localtime - -WORKDIR /home/py-kms - -EXPOSE ${SQLITE_PORT}/tcp -EXPOSE ${PORT}/tcp + bash \ + git \ + python3 \ + py3-argparse \ + py3-flask \ + py3-pygments \ + python3-tkinter \ + sqlite-libs \ + py3-pip \ + build-base python3-dev \ + ca-certificates \ + duplicity \ + tzdata \ + shadow \ + netcat-openbsd \ + && git clone --branch master --depth 1 https://github.com/coleifer/sqlite-web.git /tmp/sqlite_web \ + && mv /tmp/sqlite_web/sqlite_web /home/ \ + && rm -rf /tmp/sqlite_web \ + && pip3 install --no-cache-dir peewee tzlocal pytz pysqlite3 wheel \ + && apk del git build-base python3-dev \ + && mkdir /db/ \ + && addgroup power_users \ + && adduser -S py-kms -G users -s /bin/bash \ + && usermod -a -G power_users py-kms \ + && chown py-kms:users /home/py-kms \ + # Fix undefined timezone, in case the user did not mount the /etc/localtime + && ln -sf /usr/share/zoneinfo/UTC /etc/localtime COPY docker/entrypoint.py /usr/bin/entrypoint.py -RUN chmod a+x /usr/bin/entrypoint.py -ENTRYPOINT ["/usr/bin/python3", "/usr/bin/entrypoint.py"] +COPY docker/start.py /usr/bin/start.py + +RUN chmod 755 /usr/bin/entrypoint.py + +WORKDIR /home/py-kms +#USER py-kms +EXPOSE ${PORT}/tcp +EXPOSE 8080 + +HEALTHCHECK --interval=5m --timeout=3s --start-period=10s --retries=4 CMD echo | nc -t localhost ${PORT} || exit 1 + +ENTRYPOINT [ "/usr/bin/python3","-u","/usr/bin/entrypoint.py" ] +CMD ["/usr/bin/start.py"] diff --git a/docker/entrypoint.py b/docker/entrypoint.py index bd0805e..17cff0e 100755 --- a/docker/entrypoint.py +++ b/docker/entrypoint.py @@ -1,57 +1,62 @@ -#!/usr/bin/python3 +#!/usr/bin/python3 -u -# This replaces the old start.sh and ensures all arguments are bound correctly from the environment variables... +# Need root privileges to change timezone, and user uid/gid, file/folder ownernship +import grp +import logging import os -import time +import pwd import subprocess +import sys -argumentVariableMapping = { - '-l': 'LCID', - '-c': 'CLIENT_COUNT', - '-a': 'ACTIVATION_INTERVAL', - '-r': 'RENEWAL_INTERVAL', - '-w': 'HWID', - '-V': 'LOGLEVEL', - '-F': 'LOGFILE', - '-S': 'LOGSIZE', - '-e': 'EPID' -} -sqliteWebPath = '/home/sqlite_web/sqlite_web.py' +PYTHON3 = '/usr/bin/python3' +dbPath = os.path.join(os.sep, 'home', 'py-kms', 'db', 'pykms_database.db') +log_level = os.getenv('LOGLEVEL', 'INFO') -# Build the command to execute -listenIP = os.environ.get('IP', '0.0.0.0') -listenPort = os.environ.get('PORT', '1688') -command = ['/usr/bin/python3', 'pykms_Server.py', listenIP, listenPort] -for (arg, env) in argumentVariableMapping.items(): - if env in os.environ and os.environ.get(env) != '': - command.append(arg) - command.append(os.environ.get(env)) - -enableSQLITE = os.path.isfile(sqliteWebPath) and os.environ.get('SQLITE', 'false').lower() == 'true' -if enableSQLITE: - dbPath = os.path.join('db', 'pykms_database.db') - print('Storing database file to ' + dbPath) - os.makedirs('db', exist_ok=True) - command.append('-s') - command.append(dbPath) +loggersrv = logging.getLogger('logsrv') +loggersrv.setLevel(log_level) +streamhandler = logging.StreamHandler(sys.stdout) +streamhandler.setLevel(log_level) +formatter = logging.Formatter(fmt = '\x1b[94m%(asctime)s %(levelname)-8s %(message)s', + datefmt = '%a, %d %b %Y %H:%M:%S',) +streamhandler.setFormatter(formatter) +loggersrv.addHandler(streamhandler) -pykmsProcess = subprocess.Popen(command) -# In case SQLITE is defined: Start the web interface -if enableSQLITE: - time.sleep(5) # The server may take a while to start - if not os.path.isfile(dbPath): - # Start a dummy activation to ensure the database file is created - subprocess.run(['/usr/bin/python3', 'pykms_Client.py', listenIP, listenPort, '-m', 'Windows10', '-n', 'DummyClient', '-c', 'ae3a27d1-b73a-4734-9878-70c949815218']) - sqliteProcess = subprocess.Popen(['/usr/bin/python3', sqliteWebPath, '-H', listenIP, '--read-only', '-x', dbPath, '-p', os.environ.get('SQLITE_PORT', 8080)]) +def change_uid_grp(): + user_db_entries = pwd.getpwnam("py-kms") + user_grp_db_entries = grp.getgrnam("power_users") + uid = int(user_db_entries.pw_uid) + gid = int(user_grp_db_entries.gr_gid) + new_gid = int(os.getenv('GID', str(gid))) + new_uid = int(os.getenv('UID', str(uid))) + os.chown("/home/py-kms", new_uid, new_gid) + os.chown("/usr/bin/start.py", new_uid, new_gid) + if os.path.isfile(dbPath): os.chown(dbPath, new_uid, new_gid) + loggersrv.debug("%s" %str(subprocess.check_output("ls -al " + dbPath, shell=True))) + if gid != new_gid: + loggersrv.info("Setting gid to '%s'." % str(new_gid)) + os.setgid(gid) -try: - pykmsProcess.wait() -except: - # In case of any error - just shut down - pass + if uid != new_uid: + loggersrv.info("Setting uid to '%s'." % str(new_uid)) + os.setuid(uid) -if enableSQLITE: - sqliteProcess.terminate() - pykmsProcess.terminate() + +def change_tz(): + tz = os.getenv('TZ', 'etc/UTC') + # TZ is not symlinked and defined TZ exists + if tz not in os.readlink('/etc/localtime') and os.path.isfile('/usr/share/zoneinfo/' + tz): + loggersrv.info("Setting timzeone to %s" % tz ) + os.remove('/etc/localtime') + os.symlink(os.path.join('/usr/share/zoneinfo/', tz), '/etc/localtime') + f = open("/etc/timezone", "w") + f.write(tz) + f.close() + + +# Main +if (__name__ == "__main__"): + loggersrv.info("Log level: %s" % log_level) + change_tz() + subprocess.call(PYTHON3 + " -u /usr/bin/start.py", preexec_fn=change_uid_grp(), shell=True) diff --git a/docker/start.py b/docker/start.py new file mode 100644 index 0000000..625cf2f --- /dev/null +++ b/docker/start.py @@ -0,0 +1,97 @@ +#!/usr/bin/python3 -u + +# This replaces the old start.sh and ensures all arguments are bound correctly from the environment variables... +import logging +import os +import subprocess +import sys +import time + +PYTHON3 = '/usr/bin/python3' +argumentVariableMapping = { + '-l': 'LCID', + '-c': 'CLIENT_COUNT', + '-a': 'ACTIVATION_INTERVAL', + '-r': 'RENEWAL_INTERVAL', + '-w': 'HWID', + '-V': 'LOGLEVEL', + '-F': 'LOGFILE', + '-S': 'LOGSIZE', + '-e': 'EPID' +} + +sqliteWebPath = '/home/sqlite_web/sqlite_web.py' +enableSQLITE = os.path.isfile(sqliteWebPath) and os.environ.get('SQLITE', 'false').lower() == 'true' +dbPath = os.path.join(os.sep, 'home', 'py-kms', 'db', 'pykms_database.db') +log_level = os.getenv('LOGLEVEL', 'INFO') +log_file = os.environ.get('LOGFILE', 'STDOUT') +listen_ip = os.environ.get('IP', '0.0.0.0') +listen_port = os.environ.get('PORT', '1688') +sqlite_port = os.environ.get('SQLITE_PORT', '8080') + + +def start_kms_client(): + if not os.path.isfile(dbPath): + # Start a dummy activation to ensure the database file is created + client_cmd = [PYTHON3, '-u', 'pykms_Client.py', listen_ip, listen_port, + '-m', 'Windows10', '-n', 'DummyClient', '-c', 'ae3a27d1-b73a-4734-9878-70c949815218', + '-V', log_level, '-F', log_file] + if os.environ.get('LOGSIZE', '') != "": + client_cmd.append('-S') + client_cmd.append(os.environ.get('LOGSIZE')) + loggersrv.info("Starting a dummy activation to ensure the database file is created") + loggersrv.debug("client_cmd: %s" % (" ".join(str(x) for x in client_cmd).strip())) + + subprocess.run(client_cmd) + + +def start_kms(): + sqlite_process = None + # Build the command to execute + command = [PYTHON3, '-u', 'pykms_Server.py', listen_ip, listen_port] + for (arg, env) in argumentVariableMapping.items(): + if env in os.environ and os.environ.get(env) != '': + command.append(arg) + command.append(os.environ.get(env)) + + if enableSQLITE: + loggersrv.info("Storing database file to %s" % dbPath) + command.append('-s') + command.append(dbPath) + os.makedirs(os.path.dirname(dbPath), exist_ok=True) + + loggersrv.debug("server_cmd: %s" % (" ".join(str(x) for x in command).strip())) + pykms_process = subprocess.Popen(command) + + # In case SQLITE is defined: Start the web interface + if enableSQLITE: + time.sleep(5) # The server may take a while to start + start_kms_client() + sqlite_cmd = [PYTHON3, '-u', '/home/sqlite_web/sqlite_web.py', '-H', listen_ip, '--read-only', '-x', + dbPath, '-p', sqlite_port] + + loggersrv.debug("sqlite_cmd: %s" % (" ".join(str(x) for x in sqlite_cmd).strip())) + sqlite_process = subprocess.Popen(sqlite_cmd) + + try: + pykms_process.wait() + except Exception: + # In case of any error - just shut down + pass + + if enableSQLITE: + if None != sqlite_process: sqlite_process.terminate() + pykms_process.terminate() + + +# Main +if (__name__ == "__main__"): + loggersrv = logging.getLogger('logsrv') + loggersrv.setLevel(log_level) + streamhandler = logging.StreamHandler(sys.stdout) + streamhandler.setLevel(log_level) + formatter = logging.Formatter(fmt='\x1b[94m%(asctime)s %(levelname)-8s %(message)s', + datefmt='%a, %d %b %Y %H:%M:%S') + streamhandler.setFormatter(formatter) + loggersrv.addHandler(streamhandler) + start_kms()