From abc6c31ae150300aca065388b3effb2bfdecb668 Mon Sep 17 00:00:00 2001 From: Philip Guyton Date: Thu, 30 Nov 2023 20:24:04 +0000 Subject: [PATCH 1/3] Adopt dedicated secrets management library #2728 Adopt password-store by Jason A. Donenfeld of wireguard fame as our base OS password store; under the root user. Employ as backed to poetry-keyring via adapter shim keyring-pass. Enabling lightweight/secure (via GPG encryption) secrets management from both an OS level and from within our Python Poetry venv. # Includes: - Keyring-pass additional dependency, with secondary dependency on python-keyring. - ExecStartPre additions to initialise GNUPG, pass, and rotate/generate Django's SECRET_KEY. - PASSWORD_STORE_DIR environmental variable in all main rockstor systemd services. - Add Django's new-in-4.1 SECRET_KEY_FALLBACKS setting. - Incidental update to cryptography: 41.0.5 to 41.0.7. - Incidental update to idna: 3.4 to 3.6. - TODO: re CLIENT_SECRET in settings.py as work-in-progress. --- conf/rockstor-bootstrap.service | 1 + conf/rockstor-pre.service | 10 ++ conf/rockstor.service | 1 + poetry.lock | 198 +++++++++++++++++++++++++++----- pyproject.toml | 1 + src/rockstor/settings.py | 22 +++- 6 files changed, 203 insertions(+), 30 deletions(-) diff --git a/conf/rockstor-bootstrap.service b/conf/rockstor-bootstrap.service index a54211a47..8d9e8b699 100644 --- a/conf/rockstor-bootstrap.service +++ b/conf/rockstor-bootstrap.service @@ -5,6 +5,7 @@ Requires=rockstor.service [Service] Environment="DJANGO_SETTINGS_MODULE=settings" +Environment="PASSWORD_STORE_DIR=/root/.password-store" WorkingDirectory=/opt/rockstor ExecStart=/opt/rockstor/.venv/bin/bootstrap Type=oneshot diff --git a/conf/rockstor-pre.service b/conf/rockstor-pre.service index e6b56f8ae..d35cda877 100644 --- a/conf/rockstor-pre.service +++ b/conf/rockstor-pre.service @@ -5,7 +5,17 @@ Requires=postgresql.service [Service] Environment="DJANGO_SETTINGS_MODULE=settings" +Environment="PASSWORD_STORE_DIR=/root/.password-store" WorkingDirectory=/opt/rockstor +# Avoid `pass` stdout leaking generated passwords (N.B. 2>&1 >/dev/null failed). +StandardOutput=null +# Idempotent: failure tolerated for pgp as key likely already exists (rc 2). +ExecStartPre=-/usr/bin/gpg --quick-generate-key --batch --passphrase '' rockstor@localhost +# Idempotent. +ExecStartPre=/usr/bin/pass init rockstor@localhost +# Rotate Django SECRET_KEY: failure tolerated in rename in case of no prior SECRET_KEY. +ExecStartPre=-/usr/bin/pass rename --force python-keyring/rockstor/SECRET_KEY python-keyring/rockstor/SECRET_KEY_FALLBACK +ExecStartPre=/usr/bin/pass generate --no-symbols --force python-keyring/rockstor/SECRET_KEY 100 ExecStart=/usr/local/bin/poetry run initrock Type=oneshot RemainAfterExit=yes diff --git a/conf/rockstor.service b/conf/rockstor.service index e75855690..8f809b782 100644 --- a/conf/rockstor.service +++ b/conf/rockstor.service @@ -5,6 +5,7 @@ Requires=rockstor-pre.service [Service] Environment="DJANGO_SETTINGS_MODULE=settings" +Environment="PASSWORD_STORE_DIR=/root/.password-store" WorkingDirectory=/opt/rockstor ExecStart=/usr/local/bin/poetry run supervisord -c /opt/rockstor/etc/supervisord.conf ExecStop=/usr/local/bin/poetry run supervisorctl shutdown diff --git a/poetry.lock b/poetry.lock index 7b04bf0a9..f1de5f7d2 100644 --- a/poetry.lock +++ b/poetry.lock @@ -206,34 +206,34 @@ files = [ [[package]] name = "cryptography" -version = "41.0.5" +version = "41.0.7" description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." optional = false python-versions = ">=3.7" files = [ - {file = "cryptography-41.0.5-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:da6a0ff8f1016ccc7477e6339e1d50ce5f59b88905585f77193ebd5068f1e797"}, - {file = "cryptography-41.0.5-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:b948e09fe5fb18517d99994184854ebd50b57248736fd4c720ad540560174ec5"}, - {file = "cryptography-41.0.5-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d38e6031e113b7421db1de0c1b1f7739564a88f1684c6b89234fbf6c11b75147"}, - {file = "cryptography-41.0.5-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e270c04f4d9b5671ebcc792b3ba5d4488bf7c42c3c241a3748e2599776f29696"}, - {file = "cryptography-41.0.5-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:ec3b055ff8f1dce8e6ef28f626e0972981475173d7973d63f271b29c8a2897da"}, - {file = "cryptography-41.0.5-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:7d208c21e47940369accfc9e85f0de7693d9a5d843c2509b3846b2db170dfd20"}, - {file = "cryptography-41.0.5-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:8254962e6ba1f4d2090c44daf50a547cd5f0bf446dc658a8e5f8156cae0d8548"}, - {file = "cryptography-41.0.5-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:a48e74dad1fb349f3dc1d449ed88e0017d792997a7ad2ec9587ed17405667e6d"}, - {file = "cryptography-41.0.5-cp37-abi3-win32.whl", hash = "sha256:d3977f0e276f6f5bf245c403156673db103283266601405376f075c849a0b936"}, - {file = "cryptography-41.0.5-cp37-abi3-win_amd64.whl", hash = "sha256:73801ac9736741f220e20435f84ecec75ed70eda90f781a148f1bad546963d81"}, - {file = "cryptography-41.0.5-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:3be3ca726e1572517d2bef99a818378bbcf7d7799d5372a46c79c29eb8d166c1"}, - {file = "cryptography-41.0.5-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:e886098619d3815e0ad5790c973afeee2c0e6e04b4da90b88e6bd06e2a0b1b72"}, - {file = "cryptography-41.0.5-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:573eb7128cbca75f9157dcde974781209463ce56b5804983e11a1c462f0f4e88"}, - {file = "cryptography-41.0.5-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:0c327cac00f082013c7c9fb6c46b7cc9fa3c288ca702c74773968173bda421bf"}, - {file = "cryptography-41.0.5-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:227ec057cd32a41c6651701abc0328135e472ed450f47c2766f23267b792a88e"}, - {file = "cryptography-41.0.5-pp38-pypy38_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:22892cc830d8b2c89ea60148227631bb96a7da0c1b722f2aac8824b1b7c0b6b8"}, - {file = "cryptography-41.0.5-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:5a70187954ba7292c7876734183e810b728b4f3965fbe571421cb2434d279179"}, - {file = "cryptography-41.0.5-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:88417bff20162f635f24f849ab182b092697922088b477a7abd6664ddd82291d"}, - {file = "cryptography-41.0.5-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:c707f7afd813478e2019ae32a7c49cd932dd60ab2d2a93e796f68236b7e1fbf1"}, - {file = "cryptography-41.0.5-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:580afc7b7216deeb87a098ef0674d6ee34ab55993140838b14c9b83312b37b86"}, - {file = "cryptography-41.0.5-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:fba1e91467c65fe64a82c689dc6cf58151158993b13eb7a7f3f4b7f395636723"}, - {file = "cryptography-41.0.5-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:0d2a6a598847c46e3e321a7aef8af1436f11c27f1254933746304ff014664d84"}, - {file = "cryptography-41.0.5.tar.gz", hash = "sha256:392cb88b597247177172e02da6b7a63deeff1937fa6fec3bbf902ebd75d97ec7"}, + {file = "cryptography-41.0.7-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:3c78451b78313fa81607fa1b3f1ae0a5ddd8014c38a02d9db0616133987b9cdf"}, + {file = "cryptography-41.0.7-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:928258ba5d6f8ae644e764d0f996d61a8777559f72dfeb2eea7e2fe0ad6e782d"}, + {file = "cryptography-41.0.7-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5a1b41bc97f1ad230a41657d9155113c7521953869ae57ac39ac7f1bb471469a"}, + {file = "cryptography-41.0.7-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:841df4caa01008bad253bce2a6f7b47f86dc9f08df4b433c404def869f590a15"}, + {file = "cryptography-41.0.7-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:5429ec739a29df2e29e15d082f1d9ad683701f0ec7709ca479b3ff2708dae65a"}, + {file = "cryptography-41.0.7-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:43f2552a2378b44869fe8827aa19e69512e3245a219104438692385b0ee119d1"}, + {file = "cryptography-41.0.7-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:af03b32695b24d85a75d40e1ba39ffe7db7ffcb099fe507b39fd41a565f1b157"}, + {file = "cryptography-41.0.7-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:49f0805fc0b2ac8d4882dd52f4a3b935b210935d500b6b805f321addc8177406"}, + {file = "cryptography-41.0.7-cp37-abi3-win32.whl", hash = "sha256:f983596065a18a2183e7f79ab3fd4c475205b839e02cbc0efbbf9666c4b3083d"}, + {file = "cryptography-41.0.7-cp37-abi3-win_amd64.whl", hash = "sha256:90452ba79b8788fa380dfb587cca692976ef4e757b194b093d845e8d99f612f2"}, + {file = "cryptography-41.0.7-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:079b85658ea2f59c4f43b70f8119a52414cdb7be34da5d019a77bf96d473b960"}, + {file = "cryptography-41.0.7-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:b640981bf64a3e978a56167594a0e97db71c89a479da8e175d8bb5be5178c003"}, + {file = "cryptography-41.0.7-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:e3114da6d7f95d2dee7d3f4eec16dacff819740bbab931aff8648cb13c5ff5e7"}, + {file = "cryptography-41.0.7-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:d5ec85080cce7b0513cfd233914eb8b7bbd0633f1d1703aa28d1dd5a72f678ec"}, + {file = "cryptography-41.0.7-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:7a698cb1dac82c35fcf8fe3417a3aaba97de16a01ac914b89a0889d364d2f6be"}, + {file = "cryptography-41.0.7-pp38-pypy38_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:37a138589b12069efb424220bf78eac59ca68b95696fc622b6ccc1c0a197204a"}, + {file = "cryptography-41.0.7-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:68a2dec79deebc5d26d617bfdf6e8aab065a4f34934b22d3b5010df3ba36612c"}, + {file = "cryptography-41.0.7-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:09616eeaef406f99046553b8a40fbf8b1e70795a91885ba4c96a70793de5504a"}, + {file = "cryptography-41.0.7-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:48a0476626da912a44cc078f9893f292f0b3e4c739caf289268168d8f4702a39"}, + {file = "cryptography-41.0.7-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:c7f3201ec47d5207841402594f1d7950879ef890c0c495052fa62f58283fde1a"}, + {file = "cryptography-41.0.7-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:c5ca78485a255e03c32b513f8c2bc39fedb7f5c5f8535545bdc223a03b24f248"}, + {file = "cryptography-41.0.7-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:d6c391c021ab1f7a82da5d8d0b3cee2f4b2c455ec86c8aebbc84837a631ff309"}, + {file = "cryptography-41.0.7.tar.gz", hash = "sha256:13f93ce9bea8016c253b34afc6bd6a75993e5c40672ed5405a9c832f0d4a00bc"}, ] [package.dependencies] @@ -533,15 +533,67 @@ redis = ["redis (>=3.0.0)"] [[package]] name = "idna" -version = "3.4" +version = "3.6" description = "Internationalized Domain Names in Applications (IDNA)" optional = false python-versions = ">=3.5" files = [ - {file = "idna-3.4-py3-none-any.whl", hash = "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2"}, - {file = "idna-3.4.tar.gz", hash = "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4"}, + {file = "idna-3.6-py3-none-any.whl", hash = "sha256:c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f"}, + {file = "idna-3.6.tar.gz", hash = "sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca"}, ] +[[package]] +name = "importlib-metadata" +version = "6.8.0" +description = "Read metadata from Python packages" +optional = false +python-versions = ">=3.8" +files = [ + {file = "importlib_metadata-6.8.0-py3-none-any.whl", hash = "sha256:3ebb78df84a805d7698245025b975d9d67053cd94c79245ba4b3eb694abe68bb"}, + {file = "importlib_metadata-6.8.0.tar.gz", hash = "sha256:dbace7892d8c0c4ac1ad096662232f831d4e64f4c4545bd53016a3e9d4654743"}, +] + +[package.dependencies] +zipp = ">=0.5" + +[package.extras] +docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +perf = ["ipython"] +testing = ["flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-perf (>=0.9.2)", "pytest-ruff"] + +[[package]] +name = "jaraco-classes" +version = "3.3.0" +description = "Utility functions for Python class constructs" +optional = false +python-versions = ">=3.8" +files = [ + {file = "jaraco.classes-3.3.0-py3-none-any.whl", hash = "sha256:10afa92b6743f25c0cf5f37c6bb6e18e2c5bb84a16527ccfc0040ea377e7aaeb"}, + {file = "jaraco.classes-3.3.0.tar.gz", hash = "sha256:c063dd08e89217cee02c8d5e5ec560f2c8ce6cdc2fcdc2e68f7b2e5547ed3621"}, +] + +[package.dependencies] +more-itertools = "*" + +[package.extras] +docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +testing = ["pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-ruff"] + +[[package]] +name = "jeepney" +version = "0.8.0" +description = "Low-level, pure Python DBus protocol wrapper." +optional = false +python-versions = ">=3.7" +files = [ + {file = "jeepney-0.8.0-py3-none-any.whl", hash = "sha256:c0a454ad016ca575060802ee4d590dd912e35c122fa04e70306de3d076cce755"}, + {file = "jeepney-0.8.0.tar.gz", hash = "sha256:5efe48d255973902f6badc3ce55e2aa6c5c3b3bc642059ef3a91247bcfcc5806"}, +] + +[package.extras] +test = ["async-timeout", "pytest", "pytest-asyncio (>=0.17)", "pytest-trio", "testpath", "trio"] +trio = ["async_generator", "trio"] + [[package]] name = "jwcrypto" version = "1.5.0" @@ -556,6 +608,55 @@ files = [ cryptography = ">=3.4" deprecated = "*" +[[package]] +name = "keyring" +version = "23.13.1" +description = "Store and access your passwords safely." +optional = false +python-versions = ">=3.7" +files = [ + {file = "keyring-23.13.1-py3-none-any.whl", hash = "sha256:771ed2a91909389ed6148631de678f82ddc73737d85a927f382a8a1b157898cd"}, + {file = "keyring-23.13.1.tar.gz", hash = "sha256:ba2e15a9b35e21908d0aaf4e0a47acc52d6ae33444df0da2b49d41a46ef6d678"}, +] + +[package.dependencies] +importlib-metadata = {version = ">=4.11.4", markers = "python_version < \"3.12\""} +"jaraco.classes" = "*" +jeepney = {version = ">=0.4.2", markers = "sys_platform == \"linux\""} +pywin32-ctypes = {version = ">=0.2.0", markers = "sys_platform == \"win32\""} +SecretStorage = {version = ">=3.2", markers = "sys_platform == \"linux\""} + +[package.extras] +completion = ["shtab"] +docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)"] +testing = ["flake8 (<5)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)"] + +[[package]] +name = "keyring-pass" +version = "0.8.1" +description = "https://www.passwordstore.org/ backend for https://pypi.org/project/keyring/" +optional = false +python-versions = ">=3.7" +files = [ + {file = "keyring_pass-0.8.1-py3-none-any.whl", hash = "sha256:7ae7d45887111f58ba8bc5cfdbd9cc9c44d4ca8edae36d13f2e9383ff2320122"}, + {file = "keyring_pass-0.8.1.tar.gz", hash = "sha256:4ab6418877a1308294db447ea621763849d2ff87a97f80fb1b3d3d21debe64ef"}, +] + +[package.dependencies] +jaraco-classes = ">=3.2.3,<4.0.0" +keyring = ">=23.9.3,<24.0.0" + +[[package]] +name = "more-itertools" +version = "10.1.0" +description = "More routines for operating on iterables, beyond itertools" +optional = false +python-versions = ">=3.8" +files = [ + {file = "more-itertools-10.1.0.tar.gz", hash = "sha256:626c369fa0eb37bac0291bce8259b332fd59ac792fa5497b59837309cd5b114a"}, + {file = "more_itertools-10.1.0-py3-none-any.whl", hash = "sha256:64e0735fcfdc6f3464ea133afe8ea4483b1c5fe3a3d69852e6503b43a0b222e6"}, +] + [[package]] name = "oauthlib" version = "3.2.2" @@ -703,6 +804,17 @@ files = [ {file = "pytz-2023.3.post1.tar.gz", hash = "sha256:7b4fddbeb94a1eba4b557da24f19fdf9db575192544270a9101d8509f9f43d7b"}, ] +[[package]] +name = "pywin32-ctypes" +version = "0.2.2" +description = "A (partial) reimplementation of pywin32 using ctypes/cffi" +optional = false +python-versions = ">=3.6" +files = [ + {file = "pywin32-ctypes-0.2.2.tar.gz", hash = "sha256:3426e063bdd5fd4df74a14fa3cf80a0b42845a87e1d1e81f6549f9daec593a60"}, + {file = "pywin32_ctypes-0.2.2-py3-none-any.whl", hash = "sha256:bf490a1a709baf35d688fe0ecf980ed4de11d2b3e37b51e5442587a75d9957e7"}, +] + [[package]] name = "pyzmq" version = "25.1.1" @@ -829,6 +941,21 @@ urllib3 = ">=1.21.1,<3" socks = ["PySocks (>=1.5.6,!=1.5.7)"] use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] +[[package]] +name = "secretstorage" +version = "3.3.3" +description = "Python bindings to FreeDesktop.org Secret Service API" +optional = false +python-versions = ">=3.6" +files = [ + {file = "SecretStorage-3.3.3-py3-none-any.whl", hash = "sha256:f356e6628222568e3af06f2eba8df495efa13b3b63081dafd4f7d9a7b7bc9f99"}, + {file = "SecretStorage-3.3.3.tar.gz", hash = "sha256:2403533ef369eca6d2ba81718576c5e0f564d5cca1b58f73a8b23e7d4eeebd77"}, +] + +[package.dependencies] +cryptography = ">=2.0" +jeepney = ">=0.6" + [[package]] name = "setuptools" version = "69.0.2" @@ -1047,6 +1174,21 @@ files = [ [package.dependencies] h11 = ">=0.9.0,<1" +[[package]] +name = "zipp" +version = "3.17.0" +description = "Backport of pathlib-compatible object wrapper for zip files" +optional = false +python-versions = ">=3.8" +files = [ + {file = "zipp-3.17.0-py3-none-any.whl", hash = "sha256:0e923e726174922dce09c53c59ad483ff7bbb8e572e00c7f7c46b88556409f31"}, + {file = "zipp-3.17.0.tar.gz", hash = "sha256:84e64a1c28cf7e91ed2078bb8cc8c259cb19b76942096c8d7b84947690cabaf0"}, +] + +[package.extras] +docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-lint"] +testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-ignore-flaky", "pytest-mypy (>=0.9.1)", "pytest-ruff"] + [[package]] name = "zope-event" version = "5.0" @@ -1121,4 +1263,4 @@ testing = ["coverage (>=5.0.3)", "zope.event", "zope.testing"] [metadata] lock-version = "2.0" python-versions = "~3.11" -content-hash = "72293915e3e0a4817906af94a08103016ba8b7bd3776d24d3215637bbb6444c1" +content-hash = "aa0bdc94739fb5b91ef1adc805400a384da00a84bc24232c06d4248021e9a5b3" diff --git a/pyproject.toml b/pyproject.toml index f022efa84..7174abdc7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -81,6 +81,7 @@ psutil = "==5.9.4" pyzmq = "*" distro = "*" URLObject = "==2.1.1" +keyring-pass = "*" # https://pypi.org/project/supervisor/ 4.1.0 onwards embeds unmaintained meld3 supervisor = "==4.2.4" diff --git a/src/rockstor/settings.py b/src/rockstor/settings.py index a69378049..6cf2cbc32 100644 --- a/src/rockstor/settings.py +++ b/src/rockstor/settings.py @@ -21,7 +21,10 @@ import os import distro import secrets +import keyring +import keyring_pass from huey import SqliteHuey +from keyring.errors import KeyringError # By default, DEBUG = False, honour this by True only if env var == "True" DEBUG = os.environ.get("DJANGO_DEBUG", "") == "True" @@ -112,10 +115,25 @@ "pipeline.finders.PipelineFinder", ) -# Make this unique, and don't share it with anybody. -SECRET_KEY = "odk7(t)1y$ls)euj3$2xs7e^i=a9b&xtf&z=-2bz$687&^q0+3" +# Resource keyring for Django's cryptographic signing key. +# https://docs.djangoproject.com/en/4.2/ref/settings/#secret-key +# Used for Sessions, Messages, PasswordResetView tokens. +# "... not used for passwords of users and key rotation will not affect them." +SECRET_KEY = keyring.get_password("rockstor", "SECRET_KEY") + +try: + secret_key_fallback = keyring.get_password("rockstor", "SECRET_KEY_FALLBACK") + if secret_key_fallback is not None: + # New in Django 4.1: https://docs.djangoproject.com/en/4.2/ref/settings/#secret-key-fallbacks + SECRET_KEY_FALLBACKS = [secret_key_fallback] + else: + print("No SECRET_KEY_FALLBACK.") +except keyring.errors.KeyringError: + print("KeyringError") + # API client secret +# TODO: move to install persistence and resourced from keyring. CLIENT_SECRET = secrets.token_urlsafe() # New in Django 1.8 to cover all prior TEMPLATE_* settings. From 7ae63b918341b606fec37ee7d21006e78779aa16 Mon Sep 17 00:00:00 2001 From: Philip Guyton Date: Fri, 1 Dec 2023 18:11:15 +0000 Subject: [PATCH 2/3] Adopt dedicated secrets management library - continued #2728 - Set CLIENT_SECRET in keyring via initrock.py, and maintain as install instance persistent. Set only during db initialisation, or if it does not exist: i.e. updating from pre keyring install. - Get CLIENT_SECRET (settings.py) from keyring. - Update build.sh with developer instructions re GnuPG & pass. - Incidental addition of Poetry 1.2 style dev dependencies group, with the addition of `black` as an optional install. - Incidental black re-format of initrock.py - Simplify logging within initrock.py. --- build.sh | 6 +- poetry.lock | 104 ++++++++++++++++++++++++++++++- pyproject.toml | 7 ++- src/rockstor/scripts/initrock.py | 90 ++++++++++++++++---------- src/rockstor/settings.py | 7 +-- 5 files changed, 172 insertions(+), 42 deletions(-) diff --git a/build.sh b/build.sh index a2a034886..238e8eeaf 100644 --- a/build.sh +++ b/build.sh @@ -67,8 +67,10 @@ echo echo "ROCKSTOR BUILD SCRIPT COMPLETED" echo echo "If installing from source, from scratch, for development; i.e. NOT via RPM:" +echo "Note GnuPG & password-store ExecStartPre steps in /opt/rockstor/conf/rockstor-pre.service" echo "1. Run 'cd /opt/rockstor'." echo "2. Run 'systemctl start postgresql'." echo "3. Run 'export DJANGO_SETTINGS_MODULE=settings'." -echo "4. Run 'poetry run initrock' as root (equivalent to rockstor-pre.service)." -echo "5. Run 'systemctl enable --now rockstor-bootstrap'." \ No newline at end of file +echo "4. Run 'export PASSWORD_STORE_DIR=/root/.password-store'." +echo "5. Run 'poetry run initrock' as root (equivalent to rockstor-pre.service ExecStart)." +echo "6. Run 'systemctl enable --now rockstor-bootstrap'." \ No newline at end of file diff --git a/poetry.lock b/poetry.lock index f1de5f7d2..605712e53 100644 --- a/poetry.lock +++ b/poetry.lock @@ -30,6 +30,46 @@ docs = ["furo", "sphinx", "sphinx-copybutton"] lint = ["pre-commit"] test = ["hypothesis", "pytest", "pytest-benchmark[histogram]", "pytest-cov", "pytest-xdist", "sortedcollections", "sortedcontainers", "sphinx"] +[[package]] +name = "black" +version = "23.11.0" +description = "The uncompromising code formatter." +optional = false +python-versions = ">=3.8" +files = [ + {file = "black-23.11.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:dbea0bb8575c6b6303cc65017b46351dc5953eea5c0a59d7b7e3a2d2f433a911"}, + {file = "black-23.11.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:412f56bab20ac85927f3a959230331de5614aecda1ede14b373083f62ec24e6f"}, + {file = "black-23.11.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d136ef5b418c81660ad847efe0e55c58c8208b77a57a28a503a5f345ccf01394"}, + {file = "black-23.11.0-cp310-cp310-win_amd64.whl", hash = "sha256:6c1cac07e64433f646a9a838cdc00c9768b3c362805afc3fce341af0e6a9ae9f"}, + {file = "black-23.11.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cf57719e581cfd48c4efe28543fea3d139c6b6f1238b3f0102a9c73992cbb479"}, + {file = "black-23.11.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:698c1e0d5c43354ec5d6f4d914d0d553a9ada56c85415700b81dc90125aac244"}, + {file = "black-23.11.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:760415ccc20f9e8747084169110ef75d545f3b0932ee21368f63ac0fee86b221"}, + {file = "black-23.11.0-cp311-cp311-win_amd64.whl", hash = "sha256:58e5f4d08a205b11800332920e285bd25e1a75c54953e05502052738fe16b3b5"}, + {file = "black-23.11.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:45aa1d4675964946e53ab81aeec7a37613c1cb71647b5394779e6efb79d6d187"}, + {file = "black-23.11.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4c44b7211a3a0570cc097e81135faa5f261264f4dfaa22bd5ee2875a4e773bd6"}, + {file = "black-23.11.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2a9acad1451632021ee0d146c8765782a0c3846e0e0ea46659d7c4f89d9b212b"}, + {file = "black-23.11.0-cp38-cp38-win_amd64.whl", hash = "sha256:fc7f6a44d52747e65a02558e1d807c82df1d66ffa80a601862040a43ec2e3142"}, + {file = "black-23.11.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7f622b6822f02bfaf2a5cd31fdb7cd86fcf33dab6ced5185c35f5db98260b055"}, + {file = "black-23.11.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:250d7e60f323fcfc8ea6c800d5eba12f7967400eb6c2d21ae85ad31c204fb1f4"}, + {file = "black-23.11.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5133f5507007ba08d8b7b263c7aa0f931af5ba88a29beacc4b2dc23fcefe9c06"}, + {file = "black-23.11.0-cp39-cp39-win_amd64.whl", hash = "sha256:421f3e44aa67138ab1b9bfbc22ee3780b22fa5b291e4db8ab7eee95200726b07"}, + {file = "black-23.11.0-py3-none-any.whl", hash = "sha256:54caaa703227c6e0c87b76326d0862184729a69b73d3b7305b6288e1d830067e"}, + {file = "black-23.11.0.tar.gz", hash = "sha256:4c68855825ff432d197229846f971bc4d6666ce90492e5b02013bcaca4d9ab05"}, +] + +[package.dependencies] +click = ">=8.0.0" +mypy-extensions = ">=0.4.3" +packaging = ">=22.0" +pathspec = ">=0.9.0" +platformdirs = ">=2" + +[package.extras] +colorama = ["colorama (>=0.4.3)"] +d = ["aiohttp (>=3.7.4)"] +jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] +uvloop = ["uvloop (>=0.15.2)"] + [[package]] name = "certifi" version = "2023.11.17" @@ -204,6 +244,31 @@ files = [ {file = "charset_normalizer-3.3.2-py3-none-any.whl", hash = "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc"}, ] +[[package]] +name = "click" +version = "8.1.7" +description = "Composable command line interface toolkit" +optional = false +python-versions = ">=3.7" +files = [ + {file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"}, + {file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "platform_system == \"Windows\""} + +[[package]] +name = "colorama" +version = "0.4.6" +description = "Cross-platform colored terminal text." +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +files = [ + {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, + {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, +] + [[package]] name = "cryptography" version = "41.0.7" @@ -657,6 +722,17 @@ files = [ {file = "more_itertools-10.1.0-py3-none-any.whl", hash = "sha256:64e0735fcfdc6f3464ea133afe8ea4483b1c5fe3a3d69852e6503b43a0b222e6"}, ] +[[package]] +name = "mypy-extensions" +version = "1.0.0" +description = "Type system extensions for programs checked with the mypy type checker." +optional = false +python-versions = ">=3.5" +files = [ + {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"}, + {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, +] + [[package]] name = "oauthlib" version = "3.2.2" @@ -684,6 +760,32 @@ files = [ {file = "packaging-23.2.tar.gz", hash = "sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5"}, ] +[[package]] +name = "pathspec" +version = "0.11.2" +description = "Utility library for gitignore style pattern matching of file paths." +optional = false +python-versions = ">=3.7" +files = [ + {file = "pathspec-0.11.2-py3-none-any.whl", hash = "sha256:1d6ed233af05e679efb96b1851550ea95bbb64b7c490b0f5aa52996c11e92a20"}, + {file = "pathspec-0.11.2.tar.gz", hash = "sha256:e0d8d0ac2f12da61956eb2306b69f9469b42f4deb0f3cb6ed47b9cce9996ced3"}, +] + +[[package]] +name = "platformdirs" +version = "4.0.0" +description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." +optional = false +python-versions = ">=3.7" +files = [ + {file = "platformdirs-4.0.0-py3-none-any.whl", hash = "sha256:118c954d7e949b35437270383a3f2531e99dd93cf7ce4dc8340d3356d30f173b"}, + {file = "platformdirs-4.0.0.tar.gz", hash = "sha256:cb633b2bcf10c51af60beb0ab06d2f1d69064b43abf4c185ca6b28865f3f9731"}, +] + +[package.extras] +docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.1)", "sphinx-autodoc-typehints (>=1.24)"] +test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4)", "pytest-cov (>=4.1)", "pytest-mock (>=3.11.1)"] + [[package]] name = "psutil" version = "5.9.4" @@ -1263,4 +1365,4 @@ testing = ["coverage (>=5.0.3)", "zope.event", "zope.testing"] [metadata] lock-version = "2.0" python-versions = "~3.11" -content-hash = "aa0bdc94739fb5b91ef1adc805400a384da00a84bc24232c06d4248021e9a5b3" +content-hash = "dd7875fd49df931a41aca285bcc18cbce54862b7125a2d92496b41ad3322526a" diff --git a/pyproject.toml b/pyproject.toml index 7174abdc7..421f41ff8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -85,7 +85,12 @@ keyring-pass = "*" # https://pypi.org/project/supervisor/ 4.1.0 onwards embeds unmaintained meld3 supervisor = "==4.2.4" -[tool.poetry.dev-dependencies] +# `poetry install --with dev` +[tool.poetry.group.dev] +optional = true + +[tool.poetry.group.dev.dependencies] +black = "*" [tool.poetry.scripts] # https://python-poetry.org/docs/pyproject#scripts diff --git a/src/rockstor/scripts/initrock.py b/src/rockstor/scripts/initrock.py index 768cc558f..dbbe4dd7d 100644 --- a/src/rockstor/scripts/initrock.py +++ b/src/rockstor/scripts/initrock.py @@ -24,7 +24,9 @@ import stat import sys from tempfile import mkstemp - +import secrets +import keyring +from keyring.errors import KeyringError from django.conf import settings from system import services @@ -41,7 +43,10 @@ CONF_DIR = f"{BASE_DIR}conf" DJANGO = f"{BASE_BIN}/django-admin" DJANGO_MIGRATE_CMD = [DJANGO, "migrate", "--noinput"] -DJANGO_MIGRATE_SMART_MANAGER_CMD = DJANGO_MIGRATE_CMD + ["--database=smart_manager", "smart_manager"] +DJANGO_MIGRATE_SMART_MANAGER_CMD = DJANGO_MIGRATE_CMD + [ + "--database=smart_manager", + "smart_manager", +] STAMP = f"{BASE_DIR}/.initrock" FLASH_OPTIMIZE = f"{BASE_BIN}/flash-optimize" DJANGO_PREP_DB = f"{BASE_BIN}/prep_db" @@ -210,10 +215,10 @@ def update_nginx(log): log.exception("Exception while updating nginx: {e}".format(e=e)) -def update_tz(log): +def update_tz(): # update timezone variable in settings.py zonestr = os.path.realpath("/etc/localtime").split("zoneinfo/")[1] - log.info("system timezone = {}".format(zonestr)) + logger.info("system timezone = {}".format(zonestr)) sfile = "{}/src/rockstor/settings.py".format(BASE_DIR) fo, npath = mkstemp() updated = False @@ -226,7 +231,7 @@ def update_tz(log): else: tfo.write("TIME_ZONE = '{}'\n".format(zonestr)) updated = True - log.info("Changed timezone from {} to {}".format(curzone, zonestr)) + logger.info("Changed timezone from {} to {}".format(curzone, zonestr)) else: tfo.write(line) if updated: @@ -457,6 +462,19 @@ def establish_poetry_paths(): logger.info("### DONE establishing poetry path to binaries in local files.") +def set_api_client_secret(): + """ + Set/reset the API client secret which is used internally by OAUTH_INTERNAL_APP = "cliapp", + and the Replication service. Ultimately retrieved in setting.py and intended to be installed + instance stable. Resources OS package pass as python-keyring backend via interface project keyring-pass. + """ + try: + keyring.set_password("rockstor", "CLIENT_SECRET", secrets.token_urlsafe(100)) + logger.info("API CLIENT_SECRET set/reset successfully.") + except keyring.errors.PasswordSetError: + raise keyring.errors.PasswordSetError("Failed to set/reset API CLIENT_SECRET.") + + def main(): loglevel = logging.INFO if len(sys.argv) > 1 and sys.argv[1] == "-x": @@ -476,7 +494,7 @@ def main(): "/C=US/ST=Rockstor user's state/L=Rockstor user's " "city/O=Rockstor user/OU=Rockstor dept/CN=rockstor.user" ) - logging.info("Creating openssl cert...") + logger.info("Creating openssl cert...") run_command( [ OPENSSL, @@ -492,8 +510,8 @@ def main(): dn, ] ) - logging.debug("openssl cert created") - logging.info("Creating rockstor key...") + logger.debug("openssl cert created") + logger.info("Creating rockstor key...") run_command( [ OPENSSL, @@ -504,8 +522,8 @@ def main(): "{}/rockstor.key".format(cert_loc), ] ) - logging.debug("rockstor key created") - logging.info("Singing cert with rockstor key...") + logger.debug("rockstor key created") + logger.info("Singing cert with rockstor key...") run_command( [ OPENSSL, @@ -521,44 +539,46 @@ def main(): "3650", ] ) - logging.debug("cert signed.") - logging.info("restarting nginx...") + logger.debug("cert signed.") + logger.info("restarting nginx...") run_command([SYSTEMCTL, "restart", "nginx"]) - logging.info("Checking for flash and Running flash optimizations if appropriate.") + logger.info("Checking for flash and Running flash optimizations if appropriate.") run_command([FLASH_OPTIMIZE, "-x"], throw=False) try: - logging.info("Updating the timezone from the system") - update_tz(logging) + logger.info("Updating the timezone from the system") + update_tz() except Exception as e: - logging.error("Exception while updating timezone: {}".format(e.__str__())) - logging.exception(e) + logger.error("Exception while updating timezone: {}".format(e.__str__())) + logger.exception(e) try: - logging.info("Initialising SSHD config") + logger.info("Initialising SSHD config") bootstrap_sshd_config(logging) except Exception as e: - logging.error("Exception while updating sshd config: {}".format(e.__str__())) + logger.error("Exception while updating sshd config: {}".format(e.__str__())) db_already_setup = os.path.isfile(STAMP) + if not db_already_setup or keyring.get_password("rockstor", "CLIENT_SECRET") is None: + set_api_client_secret() for db_stage_name, db_stage_items in zip( ["Tune Postgres", "Setup Databases"], [DB_SYS_TUNE, DB_SETUP] ): if db_stage_name == "Setup Databases" and db_already_setup: continue - logging.info(f"--DB-- {db_stage_name} --DB--") + logger.info(f"--DB-- {db_stage_name} --DB--") for action, command in db_stage_items.items(): - logging.info(f"--DB-- Running - {action}") + logger.info(f"--DB-- Running - {action}") if action.startswith("migrate"): run_command(command) else: run_command(["su", "-", "postgres", "-c", command]) - logging.info(f"--DB-- Done with {action}.") - logging.info(f"--DB-- {db_stage_name} Done --DB--.") + logger.info(f"--DB-- Done with {action}.") + logger.info(f"--DB-- {db_stage_name} Done --DB--.") if db_stage_name == "Setup Databases": run_command(["touch", STAMP]) # file flag indicating db setup - logging.info("Running app database migrations...") + logger.info("Running app database migrations...") fake_migration_cmd = DJANGO_MIGRATE_CMD + ["--fake"] fake_initial_migration_cmd = DJANGO_MIGRATE_CMD + ["--fake-initial"] @@ -591,30 +611,34 @@ def main(): run_command(DJANGO_MIGRATE_CMD + ["storageadmin"], log=True) run_command(DJANGO_MIGRATE_SMART_MANAGER_CMD, log=True) - o, e, rc = run_command([DJANGO, "showmigrations", "--list", "oauth2_provider"], log=True) + o, e, rc = run_command( + [DJANGO, "showmigrations", "--list", "oauth2_provider"], log=True + ) logger.info(f"Prior migrations for oauth2_provider are: {o}") # Run all migrations for oauth2_provider run_command(DJANGO_MIGRATE_CMD + ["oauth2_provider"], log=True) - o, e, rc = run_command([DJANGO, "showmigrations", "--list", "oauth2_provider"], log=True) + o, e, rc = run_command( + [DJANGO, "showmigrations", "--list", "oauth2_provider"], log=True + ) logger.info(f"Post migrations for oauth2_provider are: {o}") - logging.info("DB Migrations Done") + logger.info("DB Migrations Done") - logging.info("Running Django prep_db.") + logger.info("Running Django prep_db.") run_command([DJANGO_PREP_DB]) - logging.info("Done") + logger.info("Done") - logging.info("Stopping firewalld...") + logger.info("Stopping firewalld...") run_command([SYSTEMCTL, "stop", "firewalld"]) run_command([SYSTEMCTL, "disable", "firewalld"]) - logging.info("Firewalld stopped and disabled") + logger.info("Firewalld stopped and disabled") - logging.info("Enabling and Starting atd...") + logger.info("Enabling and Starting atd...") run_command([SYSTEMCTL, "enable", "atd"]) run_command([SYSTEMCTL, "start", "atd"]) - logging.info("Atd enabled and started") + logger.info("Atd enabled and started") update_nginx(logging) diff --git a/src/rockstor/settings.py b/src/rockstor/settings.py index 6cf2cbc32..3694d6bc3 100644 --- a/src/rockstor/settings.py +++ b/src/rockstor/settings.py @@ -20,9 +20,7 @@ # Django settings for Rockstor project. import os import distro -import secrets import keyring -import keyring_pass from huey import SqliteHuey from keyring.errors import KeyringError @@ -127,14 +125,13 @@ # New in Django 4.1: https://docs.djangoproject.com/en/4.2/ref/settings/#secret-key-fallbacks SECRET_KEY_FALLBACKS = [secret_key_fallback] else: - print("No SECRET_KEY_FALLBACK.") + print("No SECRET_KEY_FALLBACK - rotated on reboot / rockstor services restart.") except keyring.errors.KeyringError: print("KeyringError") # API client secret -# TODO: move to install persistence and resourced from keyring. -CLIENT_SECRET = secrets.token_urlsafe() +CLIENT_SECRET = keyring.get_password("rockstor", "CLIENT_SECRET") # New in Django 1.8 to cover all prior TEMPLATE_* settings. # https://docs.djangoproject.com/en/1.11/ref/templates/upgrading/ From d85b8beb78bc10f2c9b3587e748b0fc16e95d3f8 Mon Sep 17 00:00:00 2001 From: Philip Guyton Date: Sat, 2 Dec 2023 14:06:14 +0000 Subject: [PATCH 3/3] Adopt dedicated secrets management library - build.sh mod #2728 - Additional minimal setup of GNUPG & pass in build.sh as we need a valid Django config for collectstatic, and we now store SECRET_KEY in OS provided 'pass'. --- build.sh | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/build.sh b/build.sh index 238e8eeaf..4c69810d8 100644 --- a/build.sh +++ b/build.sh @@ -56,6 +56,13 @@ if [ ! -d "jslibs" ]; then echo fi +# Ensure GNUPG is setup for 'pass' (Idempotent) +/usr/bin/gpg --quick-generate-key --batch --passphrase '' rockstor@localhost || true +# Init 'pass' in ~ using above GPG key, and generate Django SECRET_KEY +export Environment="PASSWORD_STORE_DIR=/root/.password-store" +/usr/bin/pass init rockstor@localhost +/usr/bin/pass generate --no-symbols --force python-keyring/rockstor/SECRET_KEY 100 + # Collect all static files in the STATIC_ROOT subdirectory. See settings.py. # /opt/rockstor/static # Additional collectstatic options --clear --dry-run