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:
Joseph Nuthalapati 2018-06-29 17:33:06 +05:30 committed by James Valleroy
parent d846987e8c
commit ba9af6ddff
No known key found for this signature in database
GPG Key ID: 77C0C75E7B650808
9 changed files with 113 additions and 25 deletions

1
debian/control vendored
View File

@ -51,6 +51,7 @@ Depends: ${python3:Depends}
, ${misc:Depends}
, ${plinth:Depends}
, adduser
, debconf
, augeas-tools
, e2fsprogs
, fonts-lato

22
debian/postinst vendored
View File

@ -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
View 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}

View File

@ -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

View 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

View File

@ -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 %}

View File

@ -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.
"""

View File

@ -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

View File

@ -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.