mirror of
https://github.com/freedombox/FreedomBox.git
synced 2026-01-28 08:03:36 +00:00
firstboot: Prompt for secret during firstboot welcome
- A freshly installed FreedomBox can be hijacked by a third party and an admin account can be created which can be used to inject malware or simply take over the instance. Password protecting the firstboot step is a good way to avoid this. A secret will be displayed to the user as soon as the Plinth package is installed, which they have to enter during firstboot welcome step. Also, writing this to a file in plinth's home in case the user loses it. - This protection is not applicable for images built by freedom-maker and for Amazon Machine Images. Signed-off-by: Joseph Nuthalapati <njoseph@thoughtworks.com> Reviewed-by: James Valleroy <jvalleroy@mailbox.org>
This commit is contained in:
parent
d846987e8c
commit
ba9af6ddff
1
debian/control
vendored
1
debian/control
vendored
@ -51,6 +51,7 @@ Depends: ${python3:Depends}
|
||||
, ${misc:Depends}
|
||||
, ${plinth:Depends}
|
||||
, adduser
|
||||
, debconf
|
||||
, augeas-tools
|
||||
, e2fsprogs
|
||||
, fonts-lato
|
||||
|
||||
22
debian/postinst
vendored
22
debian/postinst
vendored
@ -2,6 +2,9 @@
|
||||
|
||||
set -e
|
||||
|
||||
# Source debconf library.
|
||||
. /usr/share/debconf/confmodule
|
||||
|
||||
daemonuser=plinth
|
||||
daemongroup=plinth
|
||||
|
||||
@ -12,11 +15,20 @@ daemongroup=plinth
|
||||
sed -ie 's+-:ALL EXCEPT root fbx (admin) (sudo):ALL+-:ALL EXCEPT root fbx plinth (admin) (sudo):ALL+' /etc/security/access.conf
|
||||
|
||||
case "$1" in
|
||||
configure)
|
||||
addgroup --system --quiet plinth
|
||||
adduser --system --quiet --ingroup plinth --no-create-home --home /var/lib/plinth plinth
|
||||
chown -R plinth: /var/lib/plinth /var/log/plinth
|
||||
;;
|
||||
configure)
|
||||
addgroup --system --quiet plinth
|
||||
adduser --system --quiet --ingroup plinth --no-create-home --home /var/lib/plinth plinth
|
||||
chown -R plinth: /var/lib/plinth /var/log/plinth
|
||||
|
||||
if [ ! -e '/var/lib/freedombox/is-freedombox-disk-image' ]; then
|
||||
umask 377
|
||||
cat /dev/urandom | base64 | head -c16 > /var/lib/plinth/firstboot-wizard-secret
|
||||
chown plinth:plinth /var/lib/plinth/firstboot-wizard-secret
|
||||
db_subst plinth/firstboot_wizard_secret secret $(cat /var/lib/plinth/firstboot-wizard-secret)
|
||||
db_input high plinth/firstboot_wizard_secret || true
|
||||
db_go
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
|
||||
#DEBHELPER#
|
||||
|
||||
8
debian/templates
vendored
Normal file
8
debian/templates
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
Template: plinth/firstboot_wizard_secret
|
||||
Type: note
|
||||
Description: FreedomBox first wizard secret - ${secret}
|
||||
Please save this string. You will be asked to enter this in the first screen
|
||||
after you launch the FreedomBox interface. In case you lose it, you can find
|
||||
it in the file /var/lib/plinth/firstboot-wizard-secret.
|
||||
.
|
||||
${secret}
|
||||
@ -19,10 +19,11 @@ FreedomBox app for first boot wizard.
|
||||
"""
|
||||
|
||||
import operator
|
||||
import os
|
||||
|
||||
from django.urls import reverse
|
||||
|
||||
from plinth import module_loader
|
||||
from plinth import cfg, module_loader
|
||||
from plinth.signals import post_setup
|
||||
|
||||
version = 1
|
||||
@ -140,3 +141,14 @@ def set_completed():
|
||||
global _is_completed
|
||||
_is_completed = True
|
||||
kvstore.set('firstboot_completed', 1)
|
||||
|
||||
|
||||
def get_secret_file_path():
|
||||
"""Returns the path to the first boot wizard secret file."""
|
||||
return os.path.join(cfg.data_dir, 'firstboot-wizard-secret')
|
||||
|
||||
|
||||
def firstboot_wizard_secret_exists():
|
||||
"""Return whether a firstboot wizard secret exists."""
|
||||
secret_file = get_secret_file_path()
|
||||
return os.path.exists(secret_file) and os.path.getsize(secret_file) > 0
|
||||
|
||||
47
plinth/modules/first_boot/forms.py
Normal file
47
plinth/modules/first_boot/forms.py
Normal file
@ -0,0 +1,47 @@
|
||||
#
|
||||
# This file is part of FreedomBox.
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
# published by the Free Software Foundation, either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This program 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 Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
from django import forms
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from plinth.modules import first_boot
|
||||
|
||||
|
||||
class FirstbootWizardSecretForm(forms.Form):
|
||||
"""Form to collect and validate the first boot wizard secret."""
|
||||
secret = forms.CharField(
|
||||
label='', help_text=_(
|
||||
'Enter the secret generated during FreedomBox installation. '
|
||||
'This secret can also be obtained from the file '
|
||||
'/var/lib/plinth/firstboot-wizard-secret'), required=False,
|
||||
widget=forms.PasswordInput(attrs={'placeholder': _('Secret')}))
|
||||
|
||||
def validate_secret(self, secret):
|
||||
"""Match the secret provided by the user with the one
|
||||
generated during installation.
|
||||
"""
|
||||
secret_file_path = first_boot.get_secret_file_path()
|
||||
with open(secret_file_path) as secret_file:
|
||||
if secret != secret_file.read().strip():
|
||||
self.add_error('secret', 'Invalid secret')
|
||||
|
||||
def clean(self):
|
||||
"""Override clean to add form validation logic."""
|
||||
cleaned_data = super().clean()
|
||||
if first_boot.firstboot_wizard_secret_exists():
|
||||
self.validate_secret(cleaned_data.get("secret"))
|
||||
return cleaned_data
|
||||
@ -27,28 +27,29 @@
|
||||
.navbar-brand {
|
||||
display: none;
|
||||
}
|
||||
img.firstboot {
|
||||
display: block;
|
||||
max-width: 100%;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<p class="text-center">
|
||||
<img src="{% static 'theme/img/FreedomBox-logo-standard.png' %}"
|
||||
alt="{{ box_name }}" width="640"/>
|
||||
</p>
|
||||
<div class="logo">
|
||||
<img class="firstboot" src="{% static 'theme/img/FreedomBox-logo-standard.png' %}"
|
||||
alt="{{ box_name }}" />
|
||||
</div>
|
||||
|
||||
<form class="form text-center" method="post">
|
||||
{% csrf_token %}
|
||||
|
||||
{% if show_wizard_password_prompt %}
|
||||
{{ form|bootstrap }}
|
||||
{% endif %}
|
||||
|
||||
<p>
|
||||
<input type="submit" class="btn btn-lg btn-primary"
|
||||
value="{% trans "Start Setup" %}"/>
|
||||
</p>
|
||||
</form>
|
||||
|
||||
<p class="text-center" style="font-size: larger">
|
||||
{% blocktrans trimmed %}
|
||||
To complete the setup of your {{ box_name }}, please provide
|
||||
some basic information.
|
||||
{% endblocktrans %}
|
||||
</p>
|
||||
{% endblock %}
|
||||
|
||||
@ -14,7 +14,6 @@
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
"""
|
||||
Template tags for first boot module.
|
||||
"""
|
||||
|
||||
@ -14,7 +14,6 @@
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
"""
|
||||
URLs for the First Boot module
|
||||
"""
|
||||
@ -22,8 +21,7 @@ URLs for the First Boot module
|
||||
from django.conf.urls import url
|
||||
from stronghold.decorators import public
|
||||
|
||||
from .views import WelcomeView, CompleteView
|
||||
|
||||
from .views import CompleteView, WelcomeView
|
||||
|
||||
urlpatterns = [
|
||||
# Take care of the firstboot middleware when changing URLs
|
||||
|
||||
@ -19,21 +19,31 @@ from django import http
|
||||
from django.urls import reverse
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.views.generic import TemplateView
|
||||
from django.views.generic.edit import FormView
|
||||
|
||||
from plinth import network
|
||||
from plinth.modules import first_boot
|
||||
|
||||
from .forms import FirstbootWizardSecretForm
|
||||
|
||||
class WelcomeView(TemplateView):
|
||||
|
||||
class WelcomeView(FormView):
|
||||
"""Show the welcome screen."""
|
||||
|
||||
form_class = FirstbootWizardSecretForm
|
||||
template_name = 'firstboot_welcome.html'
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
"""On POST, mark this step as done and move to next step."""
|
||||
def form_valid(self, form):
|
||||
"""If form is valid, mark this step as done and move to next step."""
|
||||
first_boot.mark_step_done('firstboot_welcome')
|
||||
return http.HttpResponseRedirect(reverse(first_boot.next_step()))
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
"""Add network connections to context list."""
|
||||
context = super().get_context_data(**kwargs)
|
||||
show_prompt = first_boot.firstboot_wizard_secret_exists()
|
||||
context['show_wizard_password_prompt'] = show_prompt
|
||||
return context
|
||||
|
||||
|
||||
class CompleteView(TemplateView):
|
||||
"""Show summary after all firstboot setup is done.
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user