call_end

    • chevron_right

      Mutation Testing in Prosody

      pubsub.prosody.im / blog • 13 October, 2022 • 8 minutes

    This is a post about a new automated testing technique we have recently adopted to help us during our daily development work on Prosody. It’s probably most interesting to developers, but anyone technically-inclined should be able to follow along! If you’re unfamiliar with our project, it’s an open-source real-time messaging server, built around the XMPP protocol. It’s used by many organizations and self-hosting hobbyists, and also powers applications such as Snikket, JMP.

    This is a post about a new automated testing technique we have recently adopted to help us during our daily development work on Prosody. It’s probably most interesting to developers, but anyone technically-inclined should be able to follow along!

    If you’re unfamiliar with our project, it’s an open-source real-time messaging server, built around the XMPP protocol. It’s used by many organizations and self-hosting hobbyists, and also powers applications such as Snikket, JMP.chat and Jitsi Meet.

    Like most software projects, we routinely use automated testing tools to ensure Prosody is behaving correctly, even as we continue to work daily on fixes and improvements throughout the project.

    We use unit tests, which test the individual modules that Prosody is built from, via the busted testing tool for Lua. We also developed scansion, an automated XMPP client, for our integration tests that ensure Prosody as a whole is functioning as expected at the XMPP level.

    Recently we’ve been experimenting with a new testing technique.

    Introducing ‘mutation testing’

    Mutation testing is a way to test the tests. It is an automated process that introduces intentional errors (known as “mutations”) into the source code, and then runs the tests after each possible mutation, to make sure they identify the error and fail.

    Example mutations are things like changing true to false, or + to -. If the program was originally correct, then these changes should make it incorrect and the tests should fail. However, if the tests were not extensive enough, they might not notice the change and continue to report that the code is working correctly. That’s when there is work to do!

    Mutation testing is similar and related to other testing methods such as fault injection, which intentionally introduce errors into an application at runtime to ensure it handles them correctly. Mutation testing is specifically about errors introduced by modifying the application source code in certain ways. For this reason it is applicable to any code written in a given language, and does not need to be aware of any application-specific APIs or the runtime environment.

    One end result of a full mutation testing analysis is a “mutation score”, which is simply the percentage of mutated versions of the program (“mutants”) that the test suite failed to identify. Along with coverage (which counts the percentage of lines successfully executed during a test run), the mutation score provides a way to measure the quality of a test suite.

    Code coverage is not enough

    Measuring coverage alone does not suffice to assess the quality of a test suite. Take this example function:

    function max(a, b, c)
    	if a > b or a > c then
    		return a
    	elseif b > a or b > c then
    		return b
    	elseif c > a or c > b then
    		return c
    	end
    end
    

    This (not necessarily correct) function returns the largest of three input values. The lazy (fictional!) developer who wrote it was asked to ensure 100% test coverage for this function, here is the set of tests they produced:

    assert(max(10, 0, 0) == 10) -- test case 1, a is greater
    assert(max(0, 10, 0) == 10) -- test case 2, b is greater
    assert(max(0, 0, 10) == 10) -- test case 3, c is greater
    

    Like most tests, it executes the function with various input values and ensures it returns the expected result. In this case, the developer moves the maximum value ‘10’ between the three input parameters and successfully exercises every line of the function, achieving 100% code coverage. Mission accomplished!

    But wait… is this really a comprehensive test suite? How can we judge how extensively the behaviour of this function is actually being tested?

    Mutation testing

    Running this function through a mutation testing tool will highlight behaviour that the developer forgot to test. So that’s exactly what I did.

    The tool generated 5 mutants, and the tests failed to catch 4 of them. This means the test suite only has a mutation score of 20%. This is a very low score, and despite the 100% line and branch coverage of the tests, we now have a strong indication that they are inadequate.

    To fix this, we next have to analyze the mutants that our tests considered acceptable. Here is mutant number one:

    function max(a, b, c)
    	if false and a > b or a > c then
    		return a
    	elseif b > a or b > c then
    		return b
    	elseif c > a or c > b then
    		return c
    	end
    end
    

    See what it did? It changed the first if a > b to if false and a > b, effectively ensuring the condition a > b will never be checked. A condition was entirely disabled, yet the tests continued to pass?! There are two possible reasons for this: either this condition is not really needed for the program to work correctly, or we just don’t have any tests verifying that this condition is doing its job.

    Which test case should have tested this path? Obviously ‘test case 1’:

    assert(max(10, 0, 0) == 10)
    

    a is the greatest input here, and indeed the test confirms that the function returns it correctly. But according to our mutation testing, this is happening even without the a > b check, and that seems wrong - we would only want to return a if it is also greater than b. So let’s add a test for the case where a is greater than c but not greater than b:

    assert(max(10, 15, 0) == 15)
    

    What a surprise, our new test fails:

    Failure â spec/max_spec.lua @ 4
    max produces the expected results
    spec/max_spec.lua:1: Expected objects to be equal.
    Passed in:
    (number) 10
    Expected:
    (number) 15
    

    With this new test case added, the mutant we looked at will no longer be passed, and we’ve successfully improved our mutation score.

    Mutation testing helped us discover that our tests were not complete, despite having 100% coverage, and helped us identify which test cases we had forgotten to write. We can now go and fix our code to make the new test case pass, resulting in better tests and more confidence in the correctness of our code.

    Mutation testing limitations

    As a new tool in our toolbox, mutation testing has already helped us improve lots of our unit tests in ways we didn’t previously know they were lacking, and we’re focusing especially on improving our tests that currently have a low mutation score. But before you get too excited, you should be aware that although it is an amazing tool to have, it is not entirely perfect.

    Probably the biggest problem with mutation testing, as anyone who tries it will soon discover, is what are called ‘equivalent mutants’. These are mutated versions of the source code that still behave correctly. Unfortunately, identifying whether mutants are equivalent to the original code often requires manual inspection by a developer.

    Equivalent mutants are common where there are performance optimizations in the code but the code still works correctly without them. There are other cases too, such as when code only deals with whether a number is positive or negative (the mutation tool might change -1 to -2 and expect the tests to fail). There are also APIs where modifying parameters will not change the result. A common example of this in Prosody’s code is Lua’s string.sub(), where indices outside the boundaries of the input string do not affect the result (string.sub("test", 1, 4) and string.sub("test", 1, 5) are equivalent because the string is only 4 characters long).

    The implementation

    Although mutation testing is something I first read about many years ago and it immediately interested me, there were no mutation testing tools available for Lua source code at the time. As this is the language I spend most of my time in while working on Prosody, I’ve never been able to properly use the technique.

    However, for our new authorization API in Prosody, I’m currently adding more new code and tests than usual and the new code is security-related. I want to be sure that everything I add is covered well by the accompanying tests, and that sparked again my interest in mutation testing to support this effort.

    Still no tool was available for Lua, so I set aside a couple of hours to determine whether producing such a thing would be feasible. Luckily I didn’t need to start from scratch - there is already a mature project for parsing and modifying Lua source code called ltokenp written by Luiz Henrique de Figueiredo. On top of this I needed to write a small filter script to actually define the mutations, and a helper script for the testing tool we use (busted) to actually inject the mutated source code during test runs.

    Combining this all together, I wrote a simple shell script to wrap the process of generating the mutants, running the tests, and keeping score. The result is a single-file script that I’ve committed to the Prosody repository, and we will probably link it up to our CI in the future.

    It’s still very young, and there are many improvements that could be made, but it is already proving very useful to us. If there is sufficient interest, maybe it will graduate into its own project some day!

    If you’re interested in learning more about mutation testing, check out these resources:

    • wifi_tethering open_in_new

      This post is public

      blog.prosody.im /mutation-testing-in-prosody/

    • chevron_right

      Starring roles: Introducing dynamic permissions in Prosody

      pubsub.prosody.im / blog • 22 August, 2022 • 7 minutes

    We just pushed the first stage of our modern auth project to Prosody’s development branch! In previous versions of Prosody (0.12 and earlier), Prosody’s internal API only really supported one type of permission check: “is this user an admin?”. Our new work replaces this with a fully flexible roles/permissions system. Upgrading to the new system Despite all our excitement about this new feature, the new changes are designed to be largely invisible to server admins by default.

    We just pushed the first stage of our modern auth project to Prosody’s development branch!

    In previous versions of Prosody (0.12 and earlier), Prosody’s internal API only really supported one type of permission check: “is this user an admin?”. Our new work replaces this with a fully flexible roles/permissions system.

    Upgrading to the new system

    Despite all our excitement about this new feature, the new changes are designed to be largely invisible to server admins by default. We always aim to make Prosody upgrades as smooth as possible, and we have ensured that no configuration changes are necessary.

    If you previously used Prosody’s earlier experimental support for roles in 0.12.x (very unlikely) and assigned roles to users, there is a data migration command to run: prosodyctl mod_authz_internal migrate. This feature in 0.12 was undocumented and therefore unused by most deployments.

    There are, however, some changes for module developers to be aware of. This may affect some community modules, though we’ve already updated the major ones ourselves. If you have developed your own custom modules, you may also need to update those. Details about the API changes are discussed later in this post. If you encounter any issues, please do report them to the relevant places.

    Keeping it simple

    One of our project’s primary goals has always been to keep things as light and simple as possible. Access control is an amazingly complex topic once you scratch the surface, and it’s easy to drown in a sea of roles, permissions and policies.

    To keep complexity down, we made some decisions early on about what not to support, so we could instead focus on a minimalist core API and interface that can still support a range of use-cases.

    For example, while some systems allow a user to have multiple roles assigned, we decided that any given session should only have one active role at a time. As well as simplifying code, this decision also makes things easier for a human to reason about (e.g. you don’t have to wonder what happens if role A forbids an action and role B permits it, and both are assigned to the same user!).

    To keep some flexibility, we do allow multiple “secondary” roles to be assigned to a user. This list simply provides a list of alternative roles the user is permitted to use when requested.

    Viewing and managing roles

    Currently the best way to view and manage roles is via the admin console. We have added roles to the default output of c2s:show(), and new commands to show and modify the primary role of users.

    prosody@prosody: ~/ $ prosodyctl shell c2s show 
    Session ID           | JID                              | Role           |
    c2s5618e2f92150      | admin@localhost/gajim.M1S9AUK2   | prosody:admin  |
    c2s5618e38167e0      | test1@localhost/gajim.FQJLBQIN   | prosody:user   |
    OK: 2 c2s sessions shown
    

    Custom roles and permissions

    A big reason for the new permissions framework is so that server admins can have more control over permissions. To achieve this, we’ve made it possible to define custom roles and permission policies directly in the config file.

    For example, previously mod_announce would let you send an announcement to all users on the server only if you were a server admin. But what if you want to grant this permission to a bot or a script, without giving that bot full admin access to everything else on the server?

    Simple! Create a new role for your announcement bot, let’s call it for example “announcer”. Then we just need to give it the “mod_announce:send-announcement” permission. The config looks like this:

    VirtualHost "example.com"
    
      custom_roles = {
          {
              name = "announcer";
              inherits = { "prosody:user" };
              allow = {
                  "mod_announce:send-announcement"
              }
          }
      }

    This creates a new role that has all the same permissions as a normal user, but with the extra permission to send announcements. After assigning this role to your bot’s user account, it will have permission to send announcements but won’t be able to access any of the other features usually reserved for admins.

    Changes for module developers

    Deprecation of is_admin API

    The old permissions API (usermanager.is_admin()) has been deprecated. Usually we take a more gradual approach to deprecating APIs that are used by modules, however we have special reasons for removing this one.

    The deprecated API accepts only a JID (which can be a local or remote user), and returns true or false depending on whether they have admin privileges on the specified host.

    The problem is that our new system allows per-session roles. It’s possible for an admin to connect with a client that they don’t want to have full admin access to the server. In this case the session would have a more restrictive role assigned.

    However, any modules that continue to use the is_admin() API can only perform permission checks on the JID, and they cannot make any decisions about a specific session. This could lead to a bypass of access control.

    To ease the transition, we have initially kept is_admin() working. It will continue to return true if the JID’s default role is prosody:admin or prosody:operator, though it will emit a warning and traceback in the logs for easy identification.

    In the near future, it will be disabled - that is, it will log an error and always return false (even if the JID has an admin/operator role).

    For admins who want to enforce the new behaviour early, or keep the current (warning only) behaviour for a bit longer, you can set the global option strict_deprecate_is_admin to true or false (it currently defaults to false, and will default to true in a future nightly build at least a week from the date of this post). This is a temporary solution though: eventually this option and the compatibility mechanism will be removed.

    Switching to the new API

    To ensure your module continues to work with Prosody’s development branch and future Prosody versions, you need to replace all usage of the is_admin() API. If you don’t use this API, great, there is nothing you need to do!

    If you do use is_admin(), you should switch to the new role API. This is not provided by Prosody 0.12 and earlier, but to keep compatibility with those versions you can use our new module, mod_compat_roles. Simply add to the top of your module this line:

    module:depends("compat_roles");

    This will make available the new module API methods (module:may(), module:default_permission()). Note that it won’t provide many other features such as the new role management API in usermanager, or the ability for admins to define custom roles and permissions.

    If you have code that looks like:

    if usermanager.is_admin(sender_jid, module.host) then
      -- Perform some action
    end

    Decide on a name for the action that is being performed, and then change it to something like:

    if module:may(":my-action", event) then
      -- Perform some action
    end

    By default nobody will have permission to perform your new action, so you will also want to add near the top of your module a default policy for this action:

    -- Allow admins to perform this action by default
    module:default_permission("prosody:admin", ":my-action");

    You can find the full API documentation here.

    Coming next

    While this new feature is just a part of the current modern auth project, we’re very excited about the new possibilities it already brings to Prosody - improvements that can be used right now.

    However, we’re also looking forward to the next stages. With the authorization layer now in place, acting as the foundations, we’ll be moving on to the second stage: authentication. This will allow clients to authenticate with the server using XEP-0388 (Extensible SASL Profile) with support for advanced features such as multi-factor authentication.

    Stay tuned for further updates about this project in the near future!

    • wifi_tethering open_in_new

      This post is public

      blog.prosody.im /role-auth/

    • chevron_right

      Modernizing XMPP authentication and authorization

      pubsub.prosody.im / blog • 20 June, 2022 • 7 minutes

    We’re excited to announce that we have received funding, from the EU’s NGI Assure via the NLnet Foundation, to work on some important enhancements to Prosody and XMPP. Our work will be focusing on XMPP authentication and authorization, and bringing it up to date with current and emerging best practices. What kind of changes are we talking about? Well, there are a few aspects we are planning to work on.

    We’re excited to announce that we have received funding, from the EU’s NGI Assure via the NLnet Foundation, to work on some important enhancements to Prosody and XMPP. Our work will be focusing on XMPP authentication and authorization, and bringing it up to date with current and emerging best practices.

    What kind of changes are we talking about? Well, there are a few aspects we are planning to work on. Let’s start with “authentication” - that is, how you prove to the server that you are who you claim to be. We’ll skim the surface of some of the technologies used, but this post won’t descend too deep for most people to follow along.

    Authentication

    Traditionally, authentication is accomplished by providing a password to your client app. XMPP uses a standard authentication protocol known as the “Simple Authentication and Security Layer” (SASL), which in turn is a framework of different authentication methods it calls “mechanisms”. Most XMPP services use the SCRAM family of mechanisms. In fact, it’s mandatory for modern XMPP software to support SCRAM.

    These SCRAM mechanisms are quite clever: they allow both the server and the client to store a hash instead of the password, allow the server to verify the client knows the password, and allow the client to verify that the server knows the password (and isn’t just faking success - such as an attacker might try to do if they managed to compromise the server’s TLS certificate and wanted to intercept your traffic).

    Yet, as far as we have come with password authentication, there are some real-world problems with passwords that we need to recognize. Passwords have proven, time and time again, to be a weak point in account security. From users choosing weak passwords, reusing them across multiple services, or accidentally exposing them to fake services (phishing attacks), there are multiple ways that unauthorized parties can gain access to password-based services.

    Multi-factor authentication

    To try and plug the holes in this leaky boat, many online services have adopted multi-factor authentication (“MFA”). This extra layer of security generally requires you to provide proof that you also possess an additional secret securely generated by the service. This is achieved using hardware tokens, mobile apps and often simply sending a numeric code via SMS. Using this extra step ensures accounts can still be protected even if passwords are guessed or obtained by attackers.

    Most XMPP services and software do not currently support multi-factor authentication. If you’re a security-aware individual, that’s not a major problem in itself: you can achieve practically equivalent security by using a strong unique password and only using it to access your XMPP account. But as a service provider, you know that’s not going to be the case across all your users. As XMPP continues to gain adoption with non-technical users through new projects such as Snikket, we need to provide the safest environment we can for everyone.

    Although we have had some hacky solutions available for multi-factor authentication with Prosody for a long time, there has been no standard approach implemented in clients and servers. The most recent and promising standard is XEP-0388: Extensible SASL Profile, which defines a way for the server to ask the client to perform more steps (such as prompting the user to provide a second factor) after authentication.

    There are no known open-source implementations of XEP-0388 currently, but we plan to add support for it in Prosody as part of this project. Once this is in place, clients will be able to start introducing support for it too.

    One of the challenges for multi-factor authentication in XMPP is that you don’t necessarily want to enter an authentication code every time your app connects to your account. With most people using XMPP on mobile networks these days, it’s common for your XMPP app to re-authenticate to the server multiple times per day due to network changes. You don’t really want to miss messages because the app was waiting for you to enter an auth code!

    On websites, you generally provide a password once, when you initially log in. If successfully verified, the website then stores a cookie in your browser. This cookie is very similar to a temporary, unique and session-specific password, which is used to identify you from then on (which is why your account password isn’t required on every page request).

    XMPP doesn’t have anything like cookies, so when a verified device reconnects, it will just use the password again. The server (if multi-factor is enforced) will inconveniently require the user to provide a second factor again too. There are some proposed to solutions, such as the CLIENT-KEY SASL mechanism by Dave Cridland. TLS client certificates are also supported in XMPP, and would provide a solution to this issue too. Usage of CLIENT-KEY in XMPP is described in XEP-0399, and time-based MFA authentication codes (TOTP) in XEP-0400, however neither are available in current XMPP clients and servers.

    During this project, we plan to expand and implement these XEPs in Prosody, to make multi-factor authentication practical and user-friendly.

    Authorization

    Once Prosody has securely proven that you are the account owner, that’s often the end of the story - today. However, with the mechanisms that we just discussed that allow us to securely identify individual clients, we can start to do more interesting things. For example, Prosody will be able to show you exactly what clients are currently authorized on your account. If a device gets lost or stolen, it becomes possible to selectively revoke that device’s authorization.

    As well as revoking access, we’ll be able to assign different permission levels for each of your sessions even if the device isn’t compromised. For example, maybe you want to connect from a client on an untrusted machine - but you don’t want it to have access to read past messages from your archive. That’s something we will be able to arrange.

    Combining the ability to revoke sessions and the ability to specify per-session permissions leads us to another new possibility: granting others limited access to your account.

    For example, Movim is a popular social web XMPP client. Anyone with an XMPP account can log in to a Movim instance, and use it to chat, follow news, and discover communities. One problem is that Movim needs to log in to your account, so it needs your credentials. That’s no so bad if you are self-hosting Movim, or you are using an instance managed by your XMPP provider. However, many people don’t have that option, and rely on third-party hosted Movim instances to sign in.

    You might also want to connect other special-purpose clients to your account, for account backup and migration, bots, or apps that integrate with XMPP for synchronization and collaboration.

    Using our new authorization capabilities, one of our big goals is to allow you to log in to such third-party apps and utilities without ever sharing your password with them. And when you are finished, you can easily revoke their access to your account without needing to reset and change your password across all your other clients.

    Flexible permissions framework

    Internally, we’ll support these new authorization possibilities through an overhaul of Prosody’s permission handling. In 0.12 and earlier, the only permission check supported in most of Prosody is: “is this user an admin?”. We are adding support for arbitrary roles, and allowing you to fully customize the permissions associated with each role. Users and even individual sessions can be assigned roles.

    That means someone who generally has admin access to Prosody may choose not to grant that level of access to all their clients. Or they might choose to enable their admin powers only when they need them, spending most of their time as a normal user.

    These changes alone will unlock many new possibilities for operators and developers. Expect the first pieces of this work to land in Prosody trunk nightly builds very soon, as it forms the basis of all the rest of the features discussed in this post!

    Further updates about this project will be posted on this blog. The project homepage is over at modernxmpp.org.

    • wifi_tethering open_in_new

      This post is public

      blog.prosody.im /modern-xmpp-auth/

    • chevron_right

      Prosody 0.12.1 released

      pubsub.prosody.im / blog • 9 June, 2022 • 3 minutes

    We are pleased to announce a new minor release from our stable branch. While the 0.12.0 release has been a huge success, inevitably people found some aspects that didn’t work quite as intended, or weren’t as polished as they ought to be. With the appreciation for the help from everyone reporting issues to us, we’re happy to now release our best version yet - 0.12.1 is here! Notably, we made a couple of changes that improve compatibility with Jitsi Meet, we fixed some bugs in our newly-extended XEP-0227 support, invites, and DNS handling.

    We are pleased to announce a new minor release from our stable branch.

    While the 0.12.0 release has been a huge success, inevitably people found some aspects that didn’t work quite as intended, or weren’t as polished as they ought to be. With the appreciation for the help from everyone reporting issues to us, we’re happy to now release our best version yet - 0.12.1 is here!

    Notably, we made a couple of changes that improve compatibility with Jitsi Meet, we fixed some bugs in our newly-extended XEP-0227 support, invites, and DNS handling. We also improved compatibility with some less common platforms.

    A summary of changes in this release:

    Fixes and improvements

    • mod_http (and dependent modules): Make CORS opt-in by default (#1731)
    • mod_http: Reintroduce support for disabling or limiting CORS (#1730)
    • net.unbound: Disable use of hosts file by default (fixes #1737)
    • MUC: Allow kicking users with the same affiliation as the kicker (fixes #1724 and improves Jitsi Meet compatibility)
    • mod_tombstones: Add caching to improve performance on busy servers (fixes #1728: mod_tombstone: inefficient I/O with internal storage)

    Minor changes

    • prosodyctl check config: Report paths of loaded configuration files (#1729)
    • prosodyctl about: Report version of lua-readline
    • prosodyctl: check config: Skip bare JID components in orphan check
    • prosodyctl: check turn: Fail with error if our own address is supplied for the ping test
    • prosodyctl: check turn: warn about external port mismatches behind NAT
    • mod_turn_external: Update status and friendlier handling of missing secret option (#1727)
    • prosodyctl: Pass server when listing (outdated) plugins (fix #1738: prosodyctl list --outdated does not handle multiple versions of a module)
    • util.prosodyctl: check turn: ensure a result is always returned from a check (thanks eTaurus)
    • util.prosodyctl: check turn: Report lack of TURN services as a problem #1749
    • util.random: Ensure that native random number generator works before using it, falling back to /dev/urandom (#1734)
    • mod_storage_xep0227: Fix mapping of nodes without explicit configuration
    • mod_admin_shell: Fix error in ‘module:info()’ when statistics is not enabled (#1754)
    • mod_admin_socket: Compat for luasocket prior to unix datagram support
    • mod_admin_socket: Improve error reporting when socket can’t be created (#1719)
    • mod_cron: Record last time a task runs to ensure correct intervals (#1751)
    • core.moduleapi, core.modulemanager: Fix internal flag affecting logging in in some global modules, like mod_http (#1736, #1748)
    • core.certmanager: Expand debug messages about cert lookups in index
    • configmanager: Clearer errors when providing unexpected values after VirtualHost (#1735)
    • mod_storage_xep0227: Support basic listing of PEP nodes in absence of pubsub#admin data
    • mod_storage_xep0227: Handle missing {pubsub#owner}pubsub element (fixes #1740: mod_storage_xep0227 tracebacks reading non-existent PEP store)
    • mod_storage_xep0227: Fix conversion of SCRAM into internal format (#1741)
    • mod_external_services: Move error message to correct place (fix #1725: mod_external_services: Misplaced textual error message)
    • mod_smacks: Fix handling of unhandled stanzas on disconnect (#1759)
    • mod_smacks: Fix counting of handled stanzas
    • mod_smacks: Fix bounce of stanzas directed to full JID on unclean disconnect
    • mod_pubsub: Don’t attempt to use server actor as publisher (#1723)
    • mod_s2s: Improve robustness of outgoing s2s certificate verification
    • mod_invites_adhoc: Fall back to generic allow_user_invites for role-less users
    • mod_invites_register: Push invitee contact entry to inviter
    • util.startup: Show error for unrecognized command-line arguments passed to ‘prosody’ (#1722)
    • util.jsonpointer: Add tests, compat improvements and minor fixes
    • util.jsonschema: Lua version compat improvements

    Download

    As usual, download instructions for many platforms can be found on our download page

    If you have any questions, comments or other issues with this release, let us know!

    • wifi_tethering open_in_new

      This post is public

      blog.prosody.im /prosody-0.12.1-released/

    • chevron_right

      How Prosody developers spent 2020

      pubsub.prosody.im / blog • 8 January, 2021 • 11 minutes

    Nobody here knew quite what a year 2020 was going to be! However despite pandemics and lockdowns, we have continued to work on Prosody. This post is a summary of how the project is doing, and what we’ve been up to in the past year. One quick note before we begin… Prosody is an independent open-source project and exists only because the developers have been fortunate enough to be in a position to work on it.

    Nobody here knew quite what a year 2020 was going to be! However despite pandemics and lockdowns, we have continued to work on Prosody. This post is a summary of how the project is doing, and what we’ve been up to in the past year.

    One quick note before we begin… Prosody is an independent open-source project and exists only because the developers have been fortunate enough to be in a position to work on it. A couple of core team members are currently looking for freelance work. If you have projects in need of a Prosody expert, check the bottom of this post for more details!

    More users than ever before

    Prosody does not “phone home” in any way, which means we do not have a lot of insight into how many people are using Prosody. But there are some indicators that we can use to see at least the growth of the project.

    Many years ago, circa 2014, I was filling out a form that asked how many users the project had. I thought long and hard, but with no idea how to measure, I wrote down an estimate of “500” based on nothing but a gut feeling. Only a few weeks later I learned that the XMPP Observatory had already seen over 2200 domains submitted that were running Prosody. As most deployments were unlikely to have been submitted to xmpp.net, my estimate was clearly far out. These days I jump at any chance for even a vague estimate of our userbase. It helps us to know that people are out there!

    One useful tool is Shodan. This project scans the entire internet, just to see what it can find, and it records the results. Often used by academics and security researchers, a free account can also be used by anyone to run simple queries over the data they collect.

    A Shodan search in 2017 turned up nearly 7000 Prosody deployments. The same search 8 months later returned over 16000. Today we’re at over 52000 Prosody servers! And this only counts instances using port 5269 and accessible to the internet. There are also many private/internal deployments of Prosody that are not included in these numbers. Unfortunately we didn’t run a report in 2019, but here’s a graph of the previous reports we have run:

    Graph of Prosody server counts in previous paragraph

    Shodan reports over 85000 federating XMPP servers on port 5269 in total. Based on this, Prosody makes up 44% of the public XMPP network. That’s quite an achievement!

    Another handy insight into one sector of Prosody deployments is via Debian’s “popularity contest” service. This is an automated survey that administrators of Debian servers can choose to opt into. It reports anonymously to Debian what software packages are installed and in use. Although it reflects only a small slice of even Debian installations, it is useful to see trends.

    March 2020 marked an unprecedented spike in Prosody installations!

    Graph of Debian popularity contest data for prosody

    Although we don’t know for certain, we suspect this was caused by an surge of interest in the self-hosted video conferencing software, Jitsi Meet. Jitsi Meet integrates with Prosody, which is used to power the authentication, signaling and chat of the video conferences. Jitsi’s Videobridge component handles the media routing. Together they make a very powerful and flexible communication system, and it is hardly surprising that interest has spiked this year when there has been a massive shift to remote work, and online meetings have replaced physical ones.

    The code counts

    Now let’s look at some stats about the Prosody codebase itself.

    Language Files Code Comment Comment % Blank Total
    Lua 295 48358 3536 6.8% 6483 58377
    C 13 2613 346 11.7% 643 3602
    Other (build tools, etc.) 11 1551 22 6.3% 138 1711
    ————————- —— ———- ———- ———- ———- ———
    Total 323 54760 3909 6.7% 7265 65934

    Changes in 2020

    In 2020 (looking at the development branch) we added 597 commits, changing 191 files. The changes added 9872 lines of code, and 3637 lines of code were removed.

    A significant portion of the new lines were in our unit and integration tests (2200 lines, about 22%) which we have been working hard to expand over the past couple of years.

    Community modules

    If you use Prosody, you know we have an emphasis on modularity and extensibility. We like to make developing plugins for Prosody as easy as possible, whether it’s for integration with other systems or crazy experiments.

    Here’s a random selection of the 363 modules currently in the repository:

    mod_muc_eventsource
    Receive messages from MUC rooms with 4 lines of Javascript
    mod_log_ringbuffer
    Send debug logs to RAM unless they are needed.
    mod_component_client
    Allow a Prosody server to run as a (XEP-0114) external component of another (Prosody or not) XMPP server.
    mod_firewall
    Powerful rules-based scripts for filtering and redirecting XMPP traffic.
    mod_minimix
    An experiment in making MUC joins persistent (like MIX).

    During 2020 we saw 37 new modules published in the community repository, and 499 commits from 19 contributors. Together they added over 10,000 lines of code (and removed 728 lines). This makes it our most active year apart from 2018!

    Outside of the Prosody dev team, the most active contributor was Seve, who also made their first contribution this year and added a total of four new modules. Welcome aboard!

    Features

    But the most important part… what features have we been working on? All these things are scheduled for the 0.12 release (more on that in a bit).

    Plugin installer

    We have been applying further polish to, and setting up the infrastructure for the plugin installer. This was a Google Summer of Code project by João Duarte. It utilizes the Lua package manager, LuaRocks, to download, install and manage community modules.

    Although the the installer was completed in 2019, to make it generally usable we also had to ensure every module in the community repository could be packaged and served in an automated way by our server. We now have this working.

    Bye bye telnet, hello prosodyctl shell!

    The telnet console is one of the best things about Prosody, and we’ve been working on its successor. An early version of prosodyctl shell is already available to try out in trunk nightly builds.

    Using prosodyctl allows us to more easily support advanced features such as line editing and history (previously attainable using a third-party utility, rlwrap). It also allows for some richer UIs and is more secure on shared servers (it uses a unix socket instead of TCP).

    DNS improvements

    Since Prosody needs to resolve special DNS record types (such as SRV records) and in an asynchronous manner, the built-in operating system APIs are generally inadequate.

    For a long time we’ve been using an adopted library simply known as ‘dns.lua’ combined with our own asynchronous wrapper around it. Although it hasn’t been terrible, it has a few issues, especially in some uncommon environments. It also doesn’t support many advanced features such as DNSSEC.

    Now we are migrating to libunbound, part of the unbound project. This is one of the leading DNS implementations, and will be a big improvement over our current DNS library. To try it out, you can simply install luaunbound (already available in luarocks, Debian testing, AUR and others - poke your distro maintainers if you don’t have it yet!).

    HTTP server upload performance

    We didn’t set out to write a HTTP server, but we ended up with one anyway! Originally added so that we could natively support BOSH (XMPP over HTTP) clients, it grew to support websockets, and various modules now provide HTTP APIs for integration between Prosody and other systems.

    One big problem is that the original implementation was designed for only small amounts of data. Since the widespread of adoption of XEP-0363 people now want to be able to upload files, pictures and videos using Prosody’s internal HTTP server. We have limits in place to protect against denial of service attacks, but those same limits prevent large uploads from trusted users.

    We’ve put some work into supporting “streaming uploads”, where incoming data can be saved directly to disk instead of RAM. This means it will be safe to increase file upload limits without opening up your server to increase RAM usage and denial of service attacks.

    In general though, we do recommend using a real external HTTP server in a production or high traffic deployment (using mod_http_upload_external).

    Beyond passwords

    Passwords are the fundamental means of authenticating to your server in XMPP today. XMPP is quite good at this, adopting strong standard authentication mechanisms such as SCRAM far earlier than the rest of the industry. But the rest of the industry is also moving away from passwords in many places. We’re aiming to follow this movement also. Not that we are scrapping passwords entirely, but making it easier to offer alternatives.

    Prosody actually has a number of non-password authentication modules already, such as mod_auth_oauthbearer (OAuth2 tokens), mod_auth_ccert (client certificates) and mod_auth_token (HMAC-based tokens). But most of the modules have limitations and are not well integrated (e.g. you can set up Prosody to accept passwords, or set it up to accept tokens, but you can’t offer both methods at the same time).

    An important related aspect is authorization. In most systems authentication via a token also provides limited access to the account (e.g. if a password is associated with an account, a session that logged in using a token should not be allowed to reset the password).

    We’ve been working on two things. Firstly, a built-in authorization system (more flexible than the current admins configuration option) where users and sessions can be associated with specific permissions and roles.

    Secondly we’re using this authorization layer to add built-in support for OAuth2-style authentication and authorization.

    This is exciting for a number of reasons. It will allow, for example, specialized clients to request and receive (when granted by the user) limited access to an account without giving it your password. Some example scenarios could be:

    • Allow a third-party service to send messages via your account, but not access your contacts or message history
    • Allow an account backup/migration tool read-only access to your account without giving it your password
    • A specialized client could be granted access to only specific types of incoming messages. For example a chess game that used your XMPP account to communicate with a remote player, but wouldn’t be able to read your IM messages.

    We’re still working out the details right now, but the expectation is that some of these features may require or be enhanced by extensions to the XMPP protocol. Once we have some implementation experience, we will standardize such extensions as XEPs. That way we can open the door to a world where every XMPP client doesn’t require a password, and doesn’t automatically get full access to your account.

    The boring branches

    All the above work has been happening in our trunk branch (nightly builds are available, by the way! ). But we are also maintaining our 0.11 stable branch with bug fixes.

    In 2020 we made 98 commits to the 0.11 branch, and made 4 releases. The latest version is 0.11.7, and 0.11.8 will be coming soon.

    We also have two other branches open - 0.9 (old old stable) and 0.10 (old stable). We have a soft policy to keep supporting our branches while they are in an active Debian release. Debian stable is on 0.11.x, and the previous Debian release (stretch) was on 0.9.x and stopped receiving security updates from the core Debian team in July. As such, we will be formally deprecating these branches shortly.

    In summary: if you are not on 0.11.x already, get moving :)

    Not to be missed…

    Through 2020 we put a lot of attention into helping increase adoption of XMPP and helping it reach new users. This includes the launch of our sister project, Snikket at FOSDEM, and the backporting of Snikket’s invite-based registration to generic Prosody deployments.

    Up next: Prosody 0.12

    We’re currently working towards the next major release of Prosody. It’s a lot of work to polish off all the new features and get them sufficiently tested and documented. We are likely to start with a beta or release candidate in the coming months.

    Many thanks to all the testers of the Prosody trunk nightly builds, who provide valuable feedback and deployment experience.

    Available for hire

    Prosody has been in active development for over a decade. Over this time we’ve deliberately strived to keep the core project free from any commercial influence or activities. We believe that this helps keep the project focused on what the community wants, instead of simply what people with money want :)

    That said, we also need to pay our bills so we can continue to work on Prosody. Two members of the dev team are currently available for freelancing on a full or part-time basis for any size project. We do strongly prefer projects related to Prosody/XMPP, and open-source if possible.

    Whether you want to hire us to integrate Prosody with your application, review your architecture, or develop a new feature in Prosody itself, get in touch. You can reach us at developers@prosody.im.

    Finally, we hope everyone is having a great start to the new year. That’s all for now. Stay healthy!

    • wifi_tethering open_in_new

      This post is public

      blog.prosody.im /2020-retrospective/