Merge branch 'federation-actor-public-key' into 'develop'

Federation actor public key

See merge request funkwhale/funkwhale!115
This commit is contained in:
Eliot Berriot 2018-03-29 21:47:06 +00:00
commit 7cf097626b
35 changed files with 664 additions and 71 deletions

1
.gitignore vendored
View File

@ -86,3 +86,4 @@ front/selenium-debug.log
docs/_build
data/
.env

View File

@ -3,6 +3,53 @@ Changelog
.. towncrier
Release notes:
Preparing for federation
^^^^^^^^^^^^^^^^^^^^^^^^
In order to prepare for federation (see #136 and #137), new API endpoints
have been added under /federation and /.well-known/webfinger.
For these endpoints to work, you will need to update your nginx configuration,
and add the following snippets::
location /federation/ {
include /etc/nginx/funkwhale_proxy.conf;
proxy_pass http://funkwhale-api/federation/;
}
location /.well-known/webfinger {
include /etc/nginx/funkwhale_proxy.conf;
proxy_pass http://funkwhale-api/.well-known/webfinger;
}
This will ensure federation endpoints will be reachable in the future.
You can of course skip this part if you know you will not federate your instance.
A new ``FEDERATION_ENABLED`` env var have also been added to control wether
federation is enabled or not on the application side. This settings defaults
to True, which should have no consequencies at the moment, since actual
federation is not implemented and the only available endpoints are for
testing purposes.
Add ``FEDERATION_ENABLED=false`` to your .env file to disable federation
on the application side.
The last step involves generating RSA private and public keys for signing
your instance requests on the federation. This can be done via::
# on docker setups
docker-compose --rm api python manage.py generate_keys --no-input
# on non-docker setups
source /srv/funkwhale/virtualenv/bin/activate
source /srv/funkwhale/load_env
python manage.py generate_keys --no-input
That's it :)
0.7 (2018-03-21)
----------------

View File

@ -73,6 +73,19 @@ via the following command::
docker-compose -f dev.yml build
Creating your env file
^^^^^^^^^^^^^^^^^^^^^^
We provide a working .env.dev configuration file that is suitable for
development. However, to enable customization on your machine, you should
also create a .env file that will hold your personal environment
variables (those will not be commited to the project).
Create it like this::
touch .env
Database management
^^^^^^^^^^^^^^^^^^^

View File

@ -10,6 +10,7 @@ https://docs.djangoproject.com/en/dev/ref/settings/
"""
from __future__ import absolute_import, unicode_literals
from urllib.parse import urlsplit
import os
import environ
from funkwhale_api import __version__
@ -24,8 +25,13 @@ try:
except FileNotFoundError:
pass
ALLOWED_HOSTS = env.list('DJANGO_ALLOWED_HOSTS')
FUNKWHALE_URL = env('FUNKWHALE_URL')
FUNKWHALE_HOSTNAME = urlsplit(FUNKWHALE_URL).netloc
FEDERATION_ENABLED = env.bool('FEDERATION_ENABLED', default=True)
FEDERATION_HOSTNAME = env('FEDERATION_HOSTNAME', default=FUNKWHALE_HOSTNAME)
ALLOWED_HOSTS = env.list('DJANGO_ALLOWED_HOSTS')
# APP CONFIGURATION
# ------------------------------------------------------------------------------
@ -395,4 +401,5 @@ ACCOUNT_USERNAME_BLACKLIST = [
'owner',
'superuser',
'staff',
'service',
] + env.list('ACCOUNT_USERNAME_BLACKLIST', default=[])

View File

@ -13,6 +13,9 @@ urlpatterns = [
url(settings.ADMIN_URL, admin.site.urls),
url(r'^api/', include(("config.api_urls", 'api'), namespace="api")),
url(r'^', include(
('funkwhale_api.federation.urls', 'federation'),
namespace="federation")),
url(r'^api/v1/auth/', include('rest_auth.urls')),
url(r'^api/v1/auth/registration/', include('funkwhale_api.users.rest_auth_urls')),
url(r'^accounts/', include('allauth.urls')),

View File

@ -0,0 +1,34 @@
from django.forms import widgets
from dynamic_preferences import types
from dynamic_preferences.registries import global_preferences_registry
federation = types.Section('federation')
@global_preferences_registry.register
class FederationPrivateKey(types.StringPreference):
show_in_api = False
section = federation
name = 'private_key'
default = ''
help_text = (
'Instance private key, used for signing federation HTTP requests'
)
verbose_name = (
'Instance private key (keep it secret, do not change it)'
)
@global_preferences_registry.register
class FederationPublicKey(types.StringPreference):
show_in_api = False
section = federation
name = 'public_key'
default = ''
help_text = (
'Instance public key, used for signing federation HTTP requests'
)
verbose_name = (
'Instance public key (do not change it)'
)

View File

@ -0,0 +1,4 @@
class MalformedPayload(ValueError):
pass

View File

@ -4,16 +4,16 @@ import requests_http_signature
from funkwhale_api.factories import registry
from . import signing
from . import keys
registry.register(signing.get_key_pair, name='federation.KeyPair')
registry.register(keys.get_key_pair, name='federation.KeyPair')
@registry.register(name='federation.SignatureAuth')
class SignatureAuthFactory(factory.Factory):
algorithm = 'rsa-sha256'
key = factory.LazyFunction(lambda: signing.get_key_pair()[0])
key = factory.LazyFunction(lambda: keys.get_key_pair()[0])
key_id = factory.Faker('url')
class Meta:

View File

@ -0,0 +1,43 @@
from cryptography.hazmat.primitives import serialization as crypto_serialization
from cryptography.hazmat.primitives.asymmetric import rsa
from cryptography.hazmat.backends import default_backend as crypto_default_backend
import requests
from . import exceptions
def get_key_pair(size=2048):
key = rsa.generate_private_key(
backend=crypto_default_backend(),
public_exponent=65537,
key_size=size
)
private_key = key.private_bytes(
crypto_serialization.Encoding.PEM,
crypto_serialization.PrivateFormat.PKCS8,
crypto_serialization.NoEncryption())
public_key = key.public_key().public_bytes(
crypto_serialization.Encoding.PEM,
crypto_serialization.PublicFormat.PKCS1
)
return private_key, public_key
def get_public_key(actor_url):
"""
Given an actor_url, request it and extract publicKey data from
the response payload.
"""
response = requests.get(actor_url)
response.raise_for_status()
payload = response.json()
try:
return {
'public_key_pem': payload['publicKey']['publicKeyPem'],
'id': payload['publicKey']['id'],
'owner': payload['publicKey']['owner'],
}
except KeyError:
raise exceptions.MalformedPayload(str(payload))

View File

@ -0,0 +1,53 @@
from django.core.management.base import BaseCommand, CommandError
from django.db import transaction
from dynamic_preferences.registries import global_preferences_registry
from funkwhale_api.federation import keys
class Command(BaseCommand):
help = (
'Generate a public/private key pair for your instance,'
' for federation purposes. If a key pair already exists, does nothing.'
)
def add_arguments(self, parser):
parser.add_argument(
'--replace',
action='store_true',
dest='replace',
default=False,
help='Replace existing key pair, if any',
)
parser.add_argument(
'--noinput', '--no-input', action='store_false', dest='interactive',
help="Do NOT prompt the user for input of any kind.",
)
@transaction.atomic
def handle(self, *args, **options):
preferences = global_preferences_registry.manager()
existing_public = preferences['federation__public_key']
existing_private = preferences['federation__public_key']
if existing_public or existing_private and not options['replace']:
raise CommandError(
'Keys are already present! '
'Replace them with --replace if you know what you are doing.')
if options['interactive']:
message = (
'Are you sure you want to do this?\n\n'
"Type 'yes' to continue, or 'no' to cancel: "
)
if input(''.join(message)) != 'yes':
raise CommandError("Operation cancelled.")
private, public = keys.get_key_pair()
preferences['federation__public_key'] = public.decode('utf-8')
preferences['federation__private_key'] = private.decode('utf-8')
self.stdout.write(
'Your new key pair was generated.'
'Your public key is now:\n\n{}'.format(public.decode('utf-8'))
)

View File

@ -0,0 +1,9 @@
from rest_framework.renderers import JSONRenderer
class ActivityPubRenderer(JSONRenderer):
media_type = 'application/activity+json'
class WebfingerRenderer(JSONRenderer):
media_type = 'application/jrd+json'

View File

@ -0,0 +1,21 @@
from django.urls import reverse
from django.conf import settings
from . import utils
def repr_instance_actor():
"""
We do not use a serializer here, since it's pretty static
"""
return {
'@context': [
'https://www.w3.org/ns/activitystreams',
'https://w3id.org/security/v1',
{},
],
'id': utils.full_url(reverse('federation:instance-actor')),
'type': 'Service',
'inbox': utils.full_url(reverse('federation:instance-inbox')),
'outbox': utils.full_url(reverse('federation:instance-outbox')),
}

View File

@ -1,28 +1,6 @@
import requests
import requests_http_signature
from cryptography.hazmat.primitives import serialization as crypto_serialization
from cryptography.hazmat.primitives.asymmetric import rsa
from cryptography.hazmat.backends import default_backend as crypto_default_backend
def get_key_pair(size=2048):
key = rsa.generate_private_key(
backend=crypto_default_backend(),
public_exponent=65537,
key_size=size
)
private_key = key.private_bytes(
crypto_serialization.Encoding.PEM,
crypto_serialization.PrivateFormat.PKCS8,
crypto_serialization.NoEncryption())
public_key = key.public_key().public_bytes(
crypto_serialization.Encoding.PEM,
crypto_serialization.PublicFormat.PKCS1
)
return private_key, public_key
def verify(request, public_key):
return requests_http_signature.HTTPSignatureAuth.verify(

View File

@ -0,0 +1,15 @@
from rest_framework import routers
from . import views
router = routers.SimpleRouter(trailing_slash=False)
router.register(
r'instance',
views.InstanceViewSet,
'instance')
router.register(
r'.well-known',
views.WellKnownViewSet,
'well-known')
urlpatterns = router.urls

View File

@ -0,0 +1,14 @@
from django.conf import settings
def full_url(path):
"""
Given a relative path, return a full url usable for federation purpose
"""
root = settings.FUNKWHALE_URL
if path.startswith('/') and root.endswith('/'):
return root + path[1:]
elif not path.startswith('/') and not root.endswith('/'):
return root + '/' + path
else:
return root + path

View File

@ -0,0 +1,74 @@
from django import forms
from django.conf import settings
from django.http import HttpResponse
from rest_framework import viewsets
from rest_framework import views
from rest_framework import response
from rest_framework.decorators import list_route
from . import renderers
from . import serializers
from . import webfinger
class FederationMixin(object):
def dispatch(self, request, *args, **kwargs):
if not settings.FEDERATION_ENABLED:
return HttpResponse(status=405)
return super().dispatch(request, *args, **kwargs)
class InstanceViewSet(FederationMixin, viewsets.GenericViewSet):
authentication_classes = []
permission_classes = []
renderer_classes = [renderers.ActivityPubRenderer]
@list_route(methods=['get'])
def actor(self, request, *args, **kwargs):
return response.Response(serializers.repr_instance_actor())
@list_route(methods=['get'])
def inbox(self, request, *args, **kwargs):
raise NotImplementedError()
@list_route(methods=['get'])
def outbox(self, request, *args, **kwargs):
raise NotImplementedError()
class WellKnownViewSet(FederationMixin, viewsets.GenericViewSet):
authentication_classes = []
permission_classes = []
renderer_classes = [renderers.WebfingerRenderer]
@list_route(methods=['get'])
def webfinger(self, request, *args, **kwargs):
try:
resource_type, resource = webfinger.clean_resource(
request.GET['resource'])
cleaner = getattr(webfinger, 'clean_{}'.format(resource_type))
result = cleaner(resource)
except forms.ValidationError as e:
return response.Response({
'errors': {
'resource': e.message
}
}, status=400)
except KeyError:
return response.Response({
'errors': {
'resource': 'This field is required',
}
}, status=400)
handler = getattr(self, 'handler_{}'.format(resource_type))
data = handler(result)
return response.Response(data)
def handler_acct(self, clean_result):
username, hostname = clean_result
if username == 'service':
return webfinger.serialize_system_acct()
return {}

View File

@ -0,0 +1,52 @@
from django import forms
from django.conf import settings
from django.urls import reverse
from . import utils
VALID_RESOURCE_TYPES = ['acct']
def clean_resource(resource_string):
if not resource_string:
raise forms.ValidationError('Invalid resource string')
try:
resource_type, resource = resource_string.split(':', 1)
except ValueError:
raise forms.ValidationError('Missing webfinger resource type')
if resource_type not in VALID_RESOURCE_TYPES:
raise forms.ValidationError('Invalid webfinger resource type')
return resource_type, resource
def clean_acct(acct_string):
try:
username, hostname = acct_string.split('@')
except ValueError:
raise forms.ValidationError('Invalid format')
if hostname != settings.FEDERATION_HOSTNAME:
raise forms.ValidationError('Invalid hostname')
if username != 'service':
raise forms.ValidationError('Invalid username')
return username, hostname
def serialize_system_acct():
return {
'subject': 'acct:service@{}'.format(settings.FEDERATION_HOSTNAME),
'aliases': [
utils.full_url(reverse('federation:instance-actor'))
],
'links': [
{
'rel': 'self',
'type': 'application/activity+json',
'href': utils.full_url(reverse('federation:instance-actor')),
}
]
}

View File

@ -10,3 +10,4 @@ pytest-mock
pytest-sugar
pytest-xdist
pytest-cov
requests-mock

View File

@ -2,6 +2,7 @@ import factory
import tempfile
import shutil
import pytest
import requests_mock
from django.contrib.auth.models import AnonymousUser
from django.core.cache import cache as django_cache
@ -148,3 +149,9 @@ def media_root(settings):
settings.MEDIA_ROOT = tmp_dir
yield settings.MEDIA_ROOT
shutil.rmtree(tmp_dir)
@pytest.fixture
def r_mock():
with requests_mock.mock() as m:
yield m

View File

View File

@ -0,0 +1,14 @@
from django.core.management import call_command
def test_generate_instance_key_pair(preferences, mocker):
mocker.patch(
'funkwhale_api.federation.keys.get_key_pair',
return_value=(b'private', b'public'))
assert preferences['federation__public_key'] == ''
assert preferences['federation__private_key'] == ''
call_command('generate_keys', interactive=False)
assert preferences['federation__private_key'] == 'private'
assert preferences['federation__public_key'] == 'public'

View File

@ -0,0 +1,16 @@
from funkwhale_api.federation import keys
def test_public_key_fetching(r_mock):
payload = {
'id': 'https://actor.mock/users/actor#main-key',
'owner': 'test',
'publicKeyPem': 'test_pem',
}
actor = 'https://actor.mock/'
r_mock.get(actor, json={'publicKey': payload})
r = keys.get_public_key(actor)
assert r['id'] == payload['id']
assert r['owner'] == payload['owner']
assert r['public_key_pem'] == payload['publicKeyPem']

View File

@ -4,6 +4,7 @@ import pytest
import requests_http_signature
from funkwhale_api.federation import signing
from funkwhale_api.federation import keys
def test_can_sign_and_verify_request(factories):
@ -45,7 +46,7 @@ def test_verify_fails_with_wrong_key(factories):
def test_can_verify_django_request(factories, api_request):
private_key, public_key = signing.get_key_pair()
private_key, public_key = keys.get_key_pair()
signed_request = factories['federation.SignedRequest'](
auth__key=private_key
)
@ -61,7 +62,7 @@ def test_can_verify_django_request(factories, api_request):
def test_can_verify_django_request_digest(factories, api_request):
private_key, public_key = signing.get_key_pair()
private_key, public_key = keys.get_key_pair()
signed_request = factories['federation.SignedRequest'](
auth__key=private_key,
method='post',
@ -81,7 +82,7 @@ def test_can_verify_django_request_digest(factories, api_request):
def test_can_verify_django_request_digest_failure(factories, api_request):
private_key, public_key = signing.get_key_pair()
private_key, public_key = keys.get_key_pair()
signed_request = factories['federation.SignedRequest'](
auth__key=private_key,
method='post',
@ -102,7 +103,7 @@ def test_can_verify_django_request_digest_failure(factories, api_request):
def test_can_verify_django_request_failure(factories, api_request):
private_key, public_key = signing.get_key_pair()
private_key, public_key = keys.get_key_pair()
signed_request = factories['federation.SignedRequest'](
auth__key=private_key
)

View File

@ -0,0 +1,14 @@
import pytest
from funkwhale_api.federation import utils
@pytest.mark.parametrize('url,path,expected', [
('http://test.com', '/hello', 'http://test.com/hello'),
('http://test.com/', 'hello', 'http://test.com/hello'),
('http://test.com/', '/hello', 'http://test.com/hello'),
('http://test.com', 'hello', 'http://test.com/hello'),
])
def test_full_url(settings, url, path, expected):
settings.FUNKWHALE_URL = url
assert utils.full_url(path) == expected

View File

@ -0,0 +1,69 @@
from django.urls import reverse
import pytest
from funkwhale_api.federation import webfinger
def test_instance_actor(db, settings, api_client):
settings.FUNKWHALE_URL = 'http://test.com'
url = reverse('federation:instance-actor')
response = api_client.get(url)
assert response.data['id'] == (
settings.FUNKWHALE_URL + url
)
assert response.data['type'] == 'Service'
assert response.data['inbox'] == (
settings.FUNKWHALE_URL + reverse('federation:instance-inbox')
)
assert response.data['outbox'] == (
settings.FUNKWHALE_URL + reverse('federation:instance-outbox')
)
assert response.data['@context'] == [
'https://www.w3.org/ns/activitystreams',
'https://w3id.org/security/v1',
{},
]
@pytest.mark.parametrize('route', [
'instance-outbox',
'instance-inbox',
'instance-actor',
'well-known-webfinger',
])
def test_instance_inbox_405_if_federation_disabled(
db, settings, api_client, route):
settings.FEDERATION_ENABLED = False
url = reverse('federation:{}'.format(route))
response = api_client.get(url)
assert response.status_code == 405
def test_wellknown_webfinger_validates_resource(
db, api_client, settings, mocker):
clean = mocker.spy(webfinger, 'clean_resource')
settings.FEDERATION_ENABLED = True
url = reverse('federation:well-known-webfinger')
response = api_client.get(url, data={'resource': 'something'})
clean.assert_called_once_with('something')
assert url == '/.well-known/webfinger'
assert response.status_code == 400
assert response.data['errors']['resource'] == (
'Missing webfinger resource type'
)
def test_wellknown_webfinger_system(
db, api_client, settings, mocker):
settings.FEDERATION_ENABLED = True
settings.FEDERATION_HOSTNAME = 'test.federation'
url = reverse('federation:well-known-webfinger')
response = api_client.get(
url, data={'resource': 'acct:service@test.federation'})
assert response.status_code == 200
assert response['Content-Type'] == 'application/jrd+json'
assert response.data == webfinger.serialize_system_acct()

View File

@ -0,0 +1,66 @@
import pytest
from django import forms
from django.urls import reverse
from funkwhale_api.federation import webfinger
def test_webfinger_clean_resource():
t, r = webfinger.clean_resource('acct:service@test.federation')
assert t == 'acct'
assert r == 'service@test.federation'
@pytest.mark.parametrize('resource,message', [
('', 'Invalid resource string'),
('service@test.com', 'Missing webfinger resource type'),
('noop:service@test.com', 'Invalid webfinger resource type'),
])
def test_webfinger_clean_resource_errors(resource, message):
with pytest.raises(forms.ValidationError) as excinfo:
webfinger.clean_resource(resource)
assert message == str(excinfo)
def test_webfinger_clean_acct(settings):
settings.FEDERATION_HOSTNAME = 'test.federation'
username, hostname = webfinger.clean_acct('service@test.federation')
assert username == 'service'
assert hostname == 'test.federation'
@pytest.mark.parametrize('resource,message', [
('service', 'Invalid format'),
('service@test.com', 'Invalid hostname'),
('noop@test.federation', 'Invalid account'),
])
def test_webfinger_clean_acct_errors(resource, message, settings):
settings.FEDERATION_HOSTNAME = 'test.federation'
with pytest.raises(forms.ValidationError) as excinfo:
webfinger.clean_resource(resource)
assert message == str(excinfo)
def test_service_serializer(settings):
settings.FEDERATION_HOSTNAME = 'test.federation'
settings.FUNKWHALE_URL = 'https://test.federation'
expected = {
'subject': 'acct:service@test.federation',
'links': [
{
'rel': 'self',
'href': 'https://test.federation/instance/actor',
'type': 'application/activity+json',
}
],
'aliases': [
'https://test.federation/instance/actor',
]
}
assert expected == webfinger.serialize_system_acct()

View File

@ -3,8 +3,8 @@ proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Host $host:$server_port;
proxy_set_header X-Forwarded-Port $server_port;
proxy_set_header X-Forwarded-Host $host:$server_port;
proxy_set_header X-Forwarded-Port $server_port;
proxy_redirect off;
# websocket support

View File

@ -62,6 +62,16 @@ server {
proxy_pass http://funkwhale-api/api/;
}
location /federation/ {
include /etc/nginx/funkwhale_proxy.conf;
proxy_pass http://funkwhale-api/federation/;
}
location /.well-known/webfinger {
include /etc/nginx/funkwhale_proxy.conf;
proxy_pass http://funkwhale-api/.well-known/webfinger;
}
location /media/ {
alias /srv/funkwhale/data/media/;
}

39
dev.yml
View File

@ -1,27 +1,35 @@
version: '2'
version: '3'
services:
front:
build: front
env_file: .env.dev
env_file:
- .env.dev
- .env
environment:
- "HOST=0.0.0.0"
- "WEBPACK_DEVSERVER_PORT=${WEBPACK_DEVSERVER_PORT-8080}"
ports:
- "8080:8080"
- "${WEBPACK_DEVSERVER_PORT-8080}:${WEBPACK_DEVSERVER_PORT-8080}"
volumes:
- './front:/app'
postgres:
env_file: .env.dev
env_file:
- .env.dev
- .env
image: postgres
redis:
env_file: .env.dev
env_file:
- .env.dev
- .env
image: redis:3.0
celeryworker:
env_file: .env.dev
env_file:
- .env.dev
- .env
build:
context: ./api
dockerfile: docker/Dockerfile.test
@ -36,12 +44,14 @@ services:
- C_FORCE_ROOT=true
- "DATABASE_URL=postgresql://postgres@postgres/postgres"
- "CACHE_URL=redis://redis:6379/0"
- "FUNKWHALE_URL=http://funkwhale.test"
- "FUNKWHALE_URL=http://localhost"
volumes:
- ./api:/app
- ./data/music:/music
api:
env_file: .env.dev
env_file:
- .env.dev
- .env
build:
context: ./api
dockerfile: docker/Dockerfile.test
@ -56,19 +66,26 @@ services:
- "DJANGO_SECRET_KEY=dev"
- "DATABASE_URL=postgresql://postgres@postgres/postgres"
- "CACHE_URL=redis://redis:6379/0"
- "FUNKWHALE_URL=http://funkwhale.test"
- "FUNKWHALE_URL=http://localhost"
links:
- postgres
- redis
nginx:
env_file: .env.dev
command: /entrypoint.sh
env_file:
- .env.dev
- .env
image: nginx
environment:
- "WEBPACK_DEVSERVER_PORT=${WEBPACK_DEVSERVER_PORT-8080}"
links:
- api
- front
volumes:
- ./docker/nginx/conf.dev:/etc/nginx/nginx.conf
- ./docker/nginx/entrypoint.sh:/entrypoint.sh:ro
- ./deploy/funkwhale_proxy.conf:/etc/nginx/funkwhale_proxy.conf.template:ro
- ./api/funkwhale_api/media:/protected/media
ports:
- "0.0.0.0:6001:6001"

View File

@ -37,19 +37,7 @@ http {
listen 6001;
charset utf-8;
client_max_body_size 20M;
# global proxy pass config
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Host localhost:8080;
proxy_set_header X-Forwarded-Port 8080;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_redirect off;
include /etc/nginx/funkwhale_proxy.conf;
location /_protected/media {
internal;
alias /protected/media;
@ -63,8 +51,7 @@ http {
if ($request_uri ~* "[^\?]+\?(.*)$") {
set $query $1;
}
proxy_set_header X-Forwarded-Host localhost:8080;
proxy_set_header X-Forwarded-Port 8080;
include /etc/nginx/funkwhale_proxy.conf;
proxy_pass http://api:12081/api/v1/trackfiles/viewable/?$query;
proxy_pass_request_body off;
proxy_set_header Content-Length "";
@ -78,6 +65,7 @@ http {
if ($args ~ (.*)jwt=[^&]*(.*)) {
set $cleaned_args $1$2;
}
include /etc/nginx/funkwhale_proxy.conf;
proxy_cache_key "$scheme$request_method$host$uri$is_args$cleaned_args";
proxy_cache transcode;
proxy_cache_valid 200 7d;
@ -87,6 +75,7 @@ http {
proxy_pass http://api:12081;
}
location / {
include /etc/nginx/funkwhale_proxy.conf;
proxy_pass http://api:12081/;
}
}

9
docker/nginx/entrypoint.sh Executable file
View File

@ -0,0 +1,9 @@
#!/bin/bash -eux
echo "Copying template file..."
cp /etc/nginx/funkwhale_proxy.conf{.template,}
sed -i "s/X-Forwarded-Host \$host:\$server_port/X-Forwarded-Host localhost:${WEBPACK_DEVSERVER_PORT}/" /etc/nginx/funkwhale_proxy.conf
sed -i "s/proxy_set_header X-Forwarded-Port \$server_port/proxy_set_header X-Forwarded-Port ${WEBPACK_DEVSERVER_PORT}/" /etc/nginx/funkwhale_proxy.conf
cat /etc/nginx/funkwhale_proxy.conf
nginx -g "daemon off;"

View File

@ -23,25 +23,37 @@ module.exports = {
},
dev: {
env: require('./dev.env'),
port: 8080,
port: parseInt(process.env.WEBPACK_DEVSERVER_PORT),
host: '127.0.0.1',
autoOpenBrowser: true,
assetsSubDirectory: 'static',
assetsPublicPath: '/',
proxyTable: {
'/api': {
'**': {
target: 'http://nginx:6001',
changeOrigin: true,
ws: true
ws: true,
filter: function (pathname, req) {
let proxified = ['.well-known', 'staticfiles', 'media', 'instance', 'api']
let matches = proxified.filter(e => {
return pathname.match(`^/${e}`)
})
return matches.length > 0
}
},
'/media': {
target: 'http://nginx:6001',
changeOrigin: true,
},
'/staticfiles': {
target: 'http://nginx:6001',
changeOrigin: true,
}
// '/.well-known': {
// target: 'http://nginx:6001',
// changeOrigin: true
// },
// '/media': {
// target: 'http://nginx:6001',
// changeOrigin: true,
// },
// '/staticfiles': {
// target: 'http://nginx:6001',
// changeOrigin: true,
// },
},
// CSS Sourcemaps off by default because relative paths are "buggy"
// with this option, according to the CSS-Loader README

View File

@ -41,7 +41,7 @@
"autoprefixer": "^6.7.2",
"babel-core": "^6.22.1",
"babel-eslint": "^7.1.1",
"babel-loader": "^6.2.10",
"babel-loader": "7",
"babel-plugin-istanbul": "^4.1.1",
"babel-plugin-transform-runtime": "^6.22.0",
"babel-preset-env": "^1.3.2",
@ -101,7 +101,7 @@
"vue-loader": "^12.1.0",
"vue-style-loader": "^3.0.1",
"vue-template-compiler": "^2.3.3",
"webpack": "^2.6.1",
"webpack": "3",
"webpack-bundle-analyzer": "^2.2.1",
"webpack-dev-middleware": "^1.10.0",
"webpack-hot-middleware": "^2.18.0",