diff --git a/Changelog b/Changelog
index ec77e55b..cfefdd01 100644
--- a/Changelog
+++ b/Changelog
@@ -1,4 +1,8 @@
-Next:
+2.5.11: 2019-06-11
+   *  #6672: [api] Check VM belongs to user in the infrastructure directly (MR!707)
+   *  #bugfix: DE translation fix "Learn mehr" -> "Lerne mehr" (MR!708)
+2.5.10: 2019-05-16
+   *  #6672: [api] REST endpoint for ungleich-cli to verify if a VM belongs to a user (MR!705)
    *  #6670: [hosting/save_ssh_key] Upgrade cdist version to 5.0.1 to manage keys on Alpine linux
 2.5.9: 2019-05-09
    *  #6669: [hosting] Fix opennebula vm query takes long (MR!703)
diff --git a/datacenterlight/utils.py b/datacenterlight/utils.py
index bbcb16ab..208d39f3 100644
--- a/datacenterlight/utils.py
+++ b/datacenterlight/utils.py
@@ -1,5 +1,8 @@
 import logging
+import pyotp
+import requests
 from django.contrib.sites.models import Site
+from django.conf import settings
 
 from datacenterlight.tasks import create_vm_task
 from hosting.models import HostingOrder, HostingBill, OrderDetail
@@ -11,7 +14,6 @@ from .models import VMPricing, VMTemplate
 
 logger = logging.getLogger(__name__)
 
-
 def get_cms_integration(name):
     current_site = Site.objects.get_current()
     try:
@@ -100,3 +102,22 @@ def clear_all_session_vars(request):
                             'generic_payment_details', 'product_id']:
             if session_var in request.session:
                 del request.session[session_var]
+
+
+def check_otp(name, realm, token):
+    data = {
+        "auth_name": settings.AUTH_NAME,
+        "auth_token": pyotp.TOTP(settings.AUTH_SEED).now(),
+        "auth_realm": settings.AUTH_REALM,
+        "name": name,
+        "realm": realm,
+        "token": token
+    }
+    response = requests.post(
+        "https://{OTP_SERVER}{OTP_VERIFY_ENDPOINT}".format(
+            OTP_SERVER=settings.OTP_SERVER,
+            OTP_VERIFY_ENDPOINT=settings.OTP_VERIFY_ENDPOINT
+        ),
+        data=data
+    )
+    return response.status_code
diff --git a/digitalglarus/locale/de/LC_MESSAGES/django.po b/digitalglarus/locale/de/LC_MESSAGES/django.po
index 2d0129c4..ec96f5dc 100644
--- a/digitalglarus/locale/de/LC_MESSAGES/django.po
+++ b/digitalglarus/locale/de/LC_MESSAGES/django.po
@@ -342,7 +342,7 @@ msgstr ""
 "dieser Website erklärst Du Dich damit einverstanden, diese zu nutzen."
 
 msgid "Learn more"
-msgstr "Learn mehr"
+msgstr "Lerne mehr"
 
 msgid "OK"
 msgstr ""
diff --git a/dynamicweb/settings/base.py b/dynamicweb/settings/base.py
index b267c31d..1051c4ab 100644
--- a/dynamicweb/settings/base.py
+++ b/dynamicweb/settings/base.py
@@ -721,6 +721,14 @@ X_FRAME_OPTIONS = ('SAMEORIGIN' if X_FRAME_OPTIONS_ALLOW_FROM_URI is None else
 
 DEBUG = bool_env('DEBUG')
 
+READ_VM_REALM = env('READ_VM_REALM')
+AUTH_NAME = env('AUTH_NAME')
+AUTH_SEED = env('AUTH_SEED')
+AUTH_REALM = env('AUTH_REALM')
+OTP_SERVER = env('OTP_SERVER')
+OTP_VERIFY_ENDPOINT = env('OTP_VERIFY_ENDPOINT')
+
+
 if DEBUG:
     from .local import *  # flake8: noqa
 else:
diff --git a/hosting/management/commands/fetch_stripe_bills.py b/hosting/management/commands/fetch_stripe_bills.py
index df30535c..20f1cbe0 100644
--- a/hosting/management/commands/fetch_stripe_bills.py
+++ b/hosting/management/commands/fetch_stripe_bills.py
@@ -45,7 +45,12 @@ class Command(BaseCommand):
                     num_invoice_created = 0
                     for invoice in all_invoices:
                         invoice['customer'] = user.stripecustomer
-                        num_invoice_created += 1 if MonthlyHostingBill.create(invoice) is not None else logger.error("Did not import invoice for %s" % str(invoice))
+                        try:
+                            existing_mhb = MonthlyHostingBill.objects.get(invoice_id=invoice['invoice_id'])
+                            logger.debug("Invoice %s exists already. Not importing." % invoice['invoice_id'])
+                        except MonthlyHostingBill.DoesNotExist as dne:
+                            logger.debug("Invoice id %s does not exist" % invoice['invoice_id'])
+                            num_invoice_created += 1 if MonthlyHostingBill.create(invoice) is not None else logger.error("Did not import invoice for %s" % str(invoice))
                     self.stdout.write(
                         self.style.SUCCESS("Number of invoices imported = %s" % num_invoice_created)
                     )
diff --git a/hosting/urls.py b/hosting/urls.py
index 5aa29f88..4779f67c 100644
--- a/hosting/urls.py
+++ b/hosting/urls.py
@@ -10,12 +10,13 @@ from .views import (
     HostingPricingView, CreateVirtualMachinesView, HostingBillListView,
     HostingBillDetailView, SSHKeyDeleteView, SSHKeyCreateView, SSHKeyListView,
     SSHKeyChoiceView, DashboardView, SettingsView, ResendActivationEmailView,
-    InvoiceListView, InvoiceDetailView
+    InvoiceListView, InvoiceDetailView, CheckUserVM
 )
 
 urlpatterns = [
     url(r'index/?$', IndexView.as_view(), name='index'),
     url(r'django/?$', DjangoHostingView.as_view(), name='djangohosting'),
+    url(r'checkvm/?$', CheckUserVM.as_view(), name='check_vm'),
     url(r'dashboard/?$', DashboardView.as_view(), name='dashboard'),
     url(r'nodejs/?$', NodeJSHostingView.as_view(), name='nodejshosting'),
     url(r'rails/?$', RailsHostingView.as_view(), name='railshosting'),
diff --git a/hosting/views.py b/hosting/views.py
index cedf18f0..2ef5383d 100644
--- a/hosting/views.py
+++ b/hosting/views.py
@@ -28,13 +28,16 @@ from django.views.generic import (
 )
 from guardian.mixins import PermissionRequiredMixin
 from oca.pool import WrongIdError
+from rest_framework.renderers import JSONRenderer
+from rest_framework.response import Response
+from rest_framework.views import APIView
 from stored_messages.api import mark_read
 from stored_messages.models import Message
 from stored_messages.settings import stored_messages_settings
 
 from datacenterlight.cms_models import DCLCalculatorPluginModel
 from datacenterlight.models import VMTemplate, VMPricing
-from datacenterlight.utils import create_vm, get_cms_integration
+from datacenterlight.utils import create_vm, get_cms_integration, check_otp
 from hosting.models import UserCardDetail
 from membership.models import CustomUser, StripeCustomer
 from opennebula_api.models import OpenNebulaManager
@@ -67,9 +70,12 @@ from .models import (
 
 logger = logging.getLogger(__name__)
 
+
 CONNECTION_ERROR = "Your VMs cannot be displayed at the moment due to a \
                     backend connection error. please try again in a few \
                     minutes."
+
+
 decorators = [never_cache]
 
 
@@ -1757,3 +1763,39 @@ def forbidden_view(request, exception=None, reason=''):
                 'again.')
     messages.add_message(request, messages.ERROR, err_msg)
     return HttpResponseRedirect(request.get_full_path())
+
+
+class CheckUserVM(APIView):
+    renderer_classes = (JSONRenderer, )
+
+    def get(self, request):
+        try:
+            email = request.data['email']
+            ip = request.data['ip']
+            user = request.data['user']
+            realm = request.data['realm']
+            token = request.data['token']
+            if realm != settings.READ_VM_REALM:
+                return Response("User not allowed", 403)
+            response = check_otp(user, realm, token)
+            if response != 200:
+                return Response('Invalid token', 403)
+            manager = OpenNebulaManager()
+            # not the best way to lookup vms by ip
+            # TODO: make this optimal
+            vms = manager.get_vms()
+            users_vms = [vm for vm in vms if vm.uname == email]
+            if len(users_vms) == 0:
+                return Response('No VM found with the given email address',
+                                404)
+            for vm in users_vms:
+                for nic in vm.template.nics:
+                    if hasattr(nic, 'ip6_global'):
+                        if nic.ip6_global == ip:
+                            return Response('success', 200)
+                    elif hasattr(nic, 'ip'):
+                        if nic.ip == ip:
+                            return Response('success', 200)
+            return Response('No VM found matching the ip address provided', 404)
+        except KeyError:
+            return Response('Not enough data provided', 400)
diff --git a/requirements.txt b/requirements.txt
index b219ca84..c60c83e9 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -98,3 +98,4 @@ amqp==2.2.1
 vine==1.1.4
 cdist==5.0.1
 git+https://github.com/ungleich/djangocms-multisite.git#egg=djangocms_multisite
+pyotp