mirror of
https://github.com/freedombox/FreedomBox.git
synced 2026-05-27 10:44:33 +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}
|
, ${misc:Depends}
|
||||||
, ${plinth:Depends}
|
, ${plinth:Depends}
|
||||||
, adduser
|
, adduser
|
||||||
|
, debconf
|
||||||
, augeas-tools
|
, augeas-tools
|
||||||
, e2fsprogs
|
, e2fsprogs
|
||||||
, fonts-lato
|
, fonts-lato
|
||||||
|
|||||||
22
debian/postinst
vendored
22
debian/postinst
vendored
@ -2,6 +2,9 @@
|
|||||||
|
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
|
# Source debconf library.
|
||||||
|
. /usr/share/debconf/confmodule
|
||||||
|
|
||||||
daemonuser=plinth
|
daemonuser=plinth
|
||||||
daemongroup=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
|
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
|
case "$1" in
|
||||||
configure)
|
configure)
|
||||||
addgroup --system --quiet plinth
|
addgroup --system --quiet plinth
|
||||||
adduser --system --quiet --ingroup plinth --no-create-home --home /var/lib/plinth plinth
|
adduser --system --quiet --ingroup plinth --no-create-home --home /var/lib/plinth plinth
|
||||||
chown -R plinth: /var/lib/plinth /var/log/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
|
esac
|
||||||
|
|
||||||
#DEBHELPER#
|
#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 operator
|
||||||
|
import os
|
||||||
|
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
|
|
||||||
from plinth import module_loader
|
from plinth import cfg, module_loader
|
||||||
from plinth.signals import post_setup
|
from plinth.signals import post_setup
|
||||||
|
|
||||||
version = 1
|
version = 1
|
||||||
@ -140,3 +141,14 @@ def set_completed():
|
|||||||
global _is_completed
|
global _is_completed
|
||||||
_is_completed = True
|
_is_completed = True
|
||||||
kvstore.set('firstboot_completed', 1)
|
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 {
|
.navbar-brand {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
img.firstboot {
|
||||||
|
display: block;
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<p class="text-center">
|
<div class="logo">
|
||||||
<img src="{% static 'theme/img/FreedomBox-logo-standard.png' %}"
|
<img class="firstboot" src="{% static 'theme/img/FreedomBox-logo-standard.png' %}"
|
||||||
alt="{{ box_name }}" width="640"/>
|
alt="{{ box_name }}" />
|
||||||
</p>
|
</div>
|
||||||
|
|
||||||
<form class="form text-center" method="post">
|
<form class="form text-center" method="post">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
|
|
||||||
|
{% if show_wizard_password_prompt %}
|
||||||
|
{{ form|bootstrap }}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
<input type="submit" class="btn btn-lg btn-primary"
|
<input type="submit" class="btn btn-lg btn-primary"
|
||||||
value="{% trans "Start Setup" %}"/>
|
value="{% trans "Start Setup" %}"/>
|
||||||
</p>
|
</p>
|
||||||
</form>
|
</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 %}
|
{% endblock %}
|
||||||
|
|||||||
@ -14,7 +14,6 @@
|
|||||||
# You should have received a copy of the GNU Affero General Public License
|
# 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/>.
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
#
|
#
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Template tags for first boot module.
|
Template tags for first boot module.
|
||||||
"""
|
"""
|
||||||
|
|||||||
@ -14,7 +14,6 @@
|
|||||||
# You should have received a copy of the GNU Affero General Public License
|
# 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/>.
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
#
|
#
|
||||||
|
|
||||||
"""
|
"""
|
||||||
URLs for the First Boot module
|
URLs for the First Boot module
|
||||||
"""
|
"""
|
||||||
@ -22,8 +21,7 @@ URLs for the First Boot module
|
|||||||
from django.conf.urls import url
|
from django.conf.urls import url
|
||||||
from stronghold.decorators import public
|
from stronghold.decorators import public
|
||||||
|
|
||||||
from .views import WelcomeView, CompleteView
|
from .views import CompleteView, WelcomeView
|
||||||
|
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
# Take care of the firstboot middleware when changing URLs
|
# Take care of the firstboot middleware when changing URLs
|
||||||
|
|||||||
@ -19,21 +19,31 @@ from django import http
|
|||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from django.utils.translation import ugettext as _
|
from django.utils.translation import ugettext as _
|
||||||
from django.views.generic import TemplateView
|
from django.views.generic import TemplateView
|
||||||
|
from django.views.generic.edit import FormView
|
||||||
|
|
||||||
from plinth import network
|
from plinth import network
|
||||||
from plinth.modules import first_boot
|
from plinth.modules import first_boot
|
||||||
|
|
||||||
|
from .forms import FirstbootWizardSecretForm
|
||||||
|
|
||||||
class WelcomeView(TemplateView):
|
|
||||||
|
class WelcomeView(FormView):
|
||||||
"""Show the welcome screen."""
|
"""Show the welcome screen."""
|
||||||
|
form_class = FirstbootWizardSecretForm
|
||||||
template_name = 'firstboot_welcome.html'
|
template_name = 'firstboot_welcome.html'
|
||||||
|
|
||||||
def post(self, request, *args, **kwargs):
|
def form_valid(self, form):
|
||||||
"""On POST, mark this step as done and move to next step."""
|
"""If form is valid, mark this step as done and move to next step."""
|
||||||
first_boot.mark_step_done('firstboot_welcome')
|
first_boot.mark_step_done('firstboot_welcome')
|
||||||
return http.HttpResponseRedirect(reverse(first_boot.next_step()))
|
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):
|
class CompleteView(TemplateView):
|
||||||
"""Show summary after all firstboot setup is done.
|
"""Show summary after all firstboot setup is done.
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user