Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adopt dedicated secrets management library #2728 #2756

Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions conf/rockstor-bootstrap.service
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ Requires=rockstor.service

[Service]
Environment="DJANGO_SETTINGS_MODULE=settings"
Environment="PASSWORD_STORE_DIR=/root/.password-store"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

does it have to be in the root directory? Or would it be better to also have it somewhere below the /opt/rockstor tree to be part of the Rockstor package "boundaries"?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Hooverdan96 Thanks for the interest here, I'll answer piece-meal:

does it have to be in the root directory?

No.

Or would it be better to also have it somewhere below the /opt/rockstor tree to be part of the Rockstor package "boundaries"?

No, and my reasoning here is that the most secure location on the "/" system is /root at least with the default fs arrangement, we also use this (our) users gpg (GnuPG) key (generated via the new rockstor-pre.service additions) to encrypt these passwords (or 'pass' does it for us anyway). Plus we currently run under this user. So just a security thing really. If in /opt/rockstor or any other user home (with default group setting for users) other users can at least get to the parent directly. We will likely revisit these locations and concerns when we approach: #2700
as at that time we ourselves will not be able to access /root. But there is likely always going to be a need for us to assume root at some point (but likely through specific capabilities) but again, this can be approached as we consider the dedicated user issue. But this is not on the Milestone for our coming stable release.

For now I'm trying to use defaults of the user we run under and for pass this is it for the 'root' user, which we are currently. But as we are in the constrained env of systemd it needed to be re-established.

Copy link
Member Author

@phillxnet phillxnet Dec 1, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another element here, given it's all default for the 'root' user is that one can interact easily via the pass command when simply logged in as root. I.e.:

lbuildvm:~ # pass
Password Store
└── python-keyring
    └── rockstor
        ├── CLIENT_SECRET
        └── SECRET_KEY

Is what the code here creates on first services start / first boot/install.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And on subsequent (second in this case) reboots we get our current stable/full set of:

lbuildvm:~ # pass
Password Store
└── python-keyring
    └── rockstor
        ├── CLIENT_SECRET
        ├── SECRET_KEY_FALLBACK
        └── SECRET_KEY

Making use of Django's 4.1 onwards fallback mechanism to smooth over CLIENT_SECRET signing key rotation. Which we now do in this branch on every 'cycle' of rockstor-pre.service by renaming SECRET_KEY to SECRET_KEY_FALLBACK each service start and creating a new SECRET_KEY: thus honouring the current and last in-play SECRET_KEY. Hopefully allowing for a smoother transition and less failed sessions. But also maintaining a robust key rotation.

WorkingDirectory=/opt/rockstor
ExecStart=/opt/rockstor/.venv/bin/bootstrap
Type=oneshot
Expand Down
10 changes: 10 additions & 0 deletions conf/rockstor-pre.service
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions conf/rockstor.service
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
198 changes: 170 additions & 28 deletions poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand Down
22 changes: 20 additions & 2 deletions src/rockstor/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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.
Expand Down