diff --git a/content/blog/coller-la-petite.md b/content/blog/coller-la-petite.md new file mode 100644 index 0000000..ec7886a --- /dev/null +++ b/content/blog/coller-la-petite.md @@ -0,0 +1,207 @@ ++++ +title = 'Coller (la petite)' +date = 2025-08-21T20:00:00+02:00 ++++ + +At the beginning of my career, we used e-mails to communicate with co-workers. +We could explain an issue in details with a very long e-mail, but it was hard +to follow live discussions. For synchronous communication, we used phones. We +had to constantly switch between both systems. Have you ever received a call +saying "have you seen my last e-mail?". + +Then instant messaging systems came to fill the gap. There was +[MSN](https://en.m.wikipedia.org/wiki/MSN_Messenger) at home to chat with your +friends. Or XMPP at work. I have used this protocol for 10 years. OK there was +no gif, limited support of emojis and you can't call your colleagues but it +could ran everywhere, even on a [solarpunk phone](http://compost.party/). + +Nowadays, we have Slack, Webex, Microsoft Teams, Discord. Maybe you have the +chance to use an open source solution like Matrix or Mattermost. The decision +to use one of another has probably been made before you joined the company and +you don't have enough power to introduce a major change because communication +systems are central to companies. So we use Webex at work. We have to deal with +all the limitations. One of them is the ability to paste long text without +polluting the feed. + +# Pastebin service + +In french, "paste" could be translated to "coller". One day, I decided to start +my own pastebin service based on [sticky +notes](https://github.com/sayakb/sticky-notes). I have ordered a VPS, an +_internal_ top-level domain, restricted network access. The _coller la petite_ +service was born (in reference to [this +song](https://genius.com/Franko-coller-la-petite-lyrics)). The name was fun. +The service was useful. I did no marketing campaign around it because I knew it +could be used to store sensible data. I have contacted our security team to +tell them the service existed and told every user that it's an **external** +service with no service level agreement (_best effort_ mode). + +This website had little to no maintenance. Upgrade Debian packages, dump and +restore the database on a new major version every year, easy peasy. For fun, I +have created a Perl client to learn this language (because I had to). Then a +more portable Go client was created by one of my team mates. Why do we need +clients when there's already a website? Because sometimes we prefer to use a +CLI and not a browser to share the content of our clipboard or the content of a +file. + +The service became so popular that the need was proven. The team responsible +for managing collaboration tools came to me to see how could we move this +external service and make it internal: hosted on private services with the +enterprise security layer on top of it. I have explained the history, how it +works and how I see the future to multiple people of that team. There is a high +turnover rate apparently. Now I'm sick of explaining it over and over again. + +# What's the problem? + +There are multiple issues with this service today. Sticky notes is not +maintained anymore. It's written in PHP which makes it harder for us to fork, +contribute and deploy. Last but not least, there is no encryption. + +# PrivateBin + +The most popular and secure pastebin service out there is +[PrivateBin](https://privatebin.info/). They take security very seriously. The +content is encrypted from the browser so the hosting provider never knows +what's inside. While this solution is awesome for the security, it makes it +harder to use on a day to day basis. We can't use [curl](https://curl.se/) to +download the raw content of a note. I get the argument of users always making +the poorest choice but I don't want to set up a second solution to fill the gap +or telling people to deal with it. I want a single solution for doing both. I +want to paste public data. I also want to paste and download snippets securely. + +I also don't like long URLs because, unlike Mastodon where size doesn't matter, +they tend to bloat the message on those platforms. The fact that you have to +use an URL shortener in the +[FAQ](https://github.com/PrivateBin/PrivateBin/wiki/FAQ#the-url-is-so-long-cant-i-just-use-an-url-shortener) +says it all. + +# Coller + +So I created a pastebin server and clients to create and read notes. Yep. Yet +another pastebin solution. Not because we definitely needed one more, but +because I use it daily, I like simplicity, usability, security and I love doing +side projects in Go. + +## Features + +- Website +- Raw content by default +- Compatible with curl +- Encryption with password +- Compression on the backend +- Automatic retention +- PostgreSQL or sqlite database +- CLI to create notes ("coller") from the clipboard or a file +- CLI to read encrypted notes ("copier") +- Easy to deploy + +--- + +![Screenshot of the landing page](/coller/coller-index.png) + +--- + +![Screenshot of the page saying "Note created sucessfully" with the note +URL](/coller/coller-created.png) + +--- + +![Screenshot of the "coller" CLI creating a note from a file and reading the +node with "copier"](/coller/coller-cli.png) + +--- + +# Encryption levels + +## No encryption + +[It starts with](https://www.youtube.com/watch?v=eVTXPUF4Oz4) no encryption. +Sounds crazy in a world where everything has to be encrypted, right? Not so +crazy. Sometimes, we just want to paste a bunch of characters that we could +have been said publicly on social networks and that's fine. The URL will be +very short and easy to share. + +## Server side encryption + +The next level is to use basic encryption by letting the website generate a +password for you. You click on a checkbox and the content of the note is +encrypted before being stored. The encryption key is the password. You can +choose to keep the key for yourself and only you and the server know it. Or +share it to other people you trust. Anyway, the server knows it. + +## Server side encryption with user key + +Same solution as the server side encryption but this time, the user defines the +password. Security could be lowered if the key is weak (too short, words from +the dictionary) when the server will always generate strong enough keys. In the +end, the server still knows your choosen key. + +## Client side encryption + +If you don't trust the server but you want to use the service anyway, you could +tell the client to encrypt the content before sending it to the server. The +client will generate a random (strong) key. This time, the key is private. Only +you knows it. You can do whatever you want with the key, but you should keep it +to yourself or share it carefully. + +Accessing the encrypted note using the password in the URL will make the server +aware of your password. You should use the "copier" client to download the +encrypted note and decrypt outside of the server. + +## Client side encryption with user key + +Same as client side encryption but you can choose the password you want. + +## Encryption summary + +A picture is worth a thousand words: + +![Diagram with the server on the left and two URL: one with the note ID, one +with the note ID and the password. On the right, there are the copier and the +web clients. Clients and server are separated by a straight line. Three lines +are linking clients and the server. The first one is between the copier client +and the server where the key icon is on the client for client-side encryption. +The second one is between the web client and the URL without password to show +no encryption. The last one is between the web client and the server with a key +icon on the URL below the server to show the server-side +encryption.](/coller/coller-encryption.svg) + +# What to choose? + + +| Level | Usability | Confidentiality | +| ---------------------- | --------- | --------------- | +| No encryption | ⭐⭐ | ⭐ | +| Server-side encryption | ⭐⭐⭐ | ⭐⭐ | +| Client-side encryption | ⭐ | ⭐⭐⭐ | + +If you can use the copier client (CLI) and keep the password for yourself, go +for it. It's the most secure solution. If you can't, the website is designed to +use the server-side encryption with a strong generated password. + +# Cipher + +The most famous cipher is +[AES](https://en.wikipedia.org/wiki/Advanced_Encryption_Standard) 256 with +[GCM](https://en.wikipedia.org/wiki/Galois/Counter_Mode). It would have been +simpler to implement +[CBC](https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation) like +[pgBackRest](https://pgbackrest.org/) but the mode has been removed from TLS +1.3 for several flaws. I decided to use +[XChaha20-Poly1305](https://pkg.go.dev/golang.org/x/crypto/chacha20) because it +reminds me [this banger](https://www.youtube.com/watch?v=DGsL8hA-1rE) from this +year's Eurovision and I wanted to use something different. Don't worry, this +cipher is [one of the most secure in the +world](https://wiki.mozilla.org/Security/Server_Side_TLS). + +# Source code + +The source code is available [on my forgejo](https://git.riou.xyz/jriou/coller) +instance under the permissive +[MIT](https://git.riou.xyz/jriou/coller/src/branch/main/LICENSE) license. + +# Conclusion + +This little side project turned into a fully functional pastebin solution. It +was also a great excuse to implement encryption in Go. If you find a security +issue or want to implement a feature or fix bugs, contributions are welcomed. diff --git a/static/coller/coller-cli.png b/static/coller/coller-cli.png new file mode 100644 index 0000000..2a490bd Binary files /dev/null and b/static/coller/coller-cli.png differ diff --git a/static/coller/coller-created.png b/static/coller/coller-created.png new file mode 100644 index 0000000..e781040 Binary files /dev/null and b/static/coller/coller-created.png differ diff --git a/static/coller/coller-encryption.excalidraw b/static/coller/coller-encryption.excalidraw new file mode 100644 index 0000000..9022b04 --- /dev/null +++ b/static/coller/coller-encryption.excalidraw @@ -0,0 +1,542 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "https://excalidraw.com", + "elements": [ + { + "id": "yKFkkk4Yj5Zauko2WKG-d", + "type": "rectangle", + "x": 555.25, + "y": 331, + "width": 179.99999999999997, + "height": 212, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "as", + "roundness": { + "type": 3 + }, + "seed": 760287448, + "version": 140, + "versionNonce": 1684089560, + "isDeleted": false, + "boundElements": [ + { + "type": "text", + "id": "sNYRyKWZkChLuCLsm0VYi" + } + ], + "updated": 1755789246679, + "link": null, + "locked": false + }, + { + "id": "sNYRyKWZkChLuCLsm0VYi", + "type": "text", + "x": 592.5250015258789, + "y": 413, + "width": 105.44999694824219, + "height": 48, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "at", + "roundness": null, + "seed": 1834769832, + "version": 114, + "versionNonce": 27660248, + "isDeleted": false, + "boundElements": null, + "updated": 1755789246679, + "link": null, + "locked": false, + "text": "Server\n(collerd)", + "fontSize": 20, + "fontFamily": 3, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "yKFkkk4Yj5Zauko2WKG-d", + "originalText": "Server\n(collerd)", + "autoResize": true, + "lineHeight": 1.2 + }, + { + "id": "Kc7_mrdgSLrf6jNGvILS3", + "type": "rectangle", + "x": 1175.75, + "y": 554, + "width": 141.00000000000003, + "height": 156, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "au", + "roundness": { + "type": 3 + }, + "seed": 545326552, + "version": 444, + "versionNonce": 652351192, + "isDeleted": false, + "boundElements": [ + { + "type": "text", + "id": "CepAMVAuKJpOQI-AzdS4a" + }, + { + "id": "ocyk_yJCiGwmnkR55FoTJ", + "type": "arrow" + }, + { + "id": "BkLH2VDz86-eIlEB8PA3M", + "type": "arrow" + } + ], + "updated": 1755789238168, + "link": null, + "locked": false + }, + { + "id": "CepAMVAuKJpOQI-AzdS4a", + "type": "text", + "x": 1211.099998474121, + "y": 608, + "width": 70.30000305175781, + "height": 48, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "av", + "roundness": null, + "seed": 51546840, + "version": 421, + "versionNonce": 1445250264, + "isDeleted": false, + "boundElements": [], + "updated": 1755789187612, + "link": null, + "locked": false, + "text": "Client\n(web)", + "fontSize": 20, + "fontFamily": 3, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "Kc7_mrdgSLrf6jNGvILS3", + "originalText": "Client\n(web)", + "autoResize": true, + "lineHeight": 1.2 + }, + { + "id": "iEFEFdLxEQSOj9PksxsbT", + "type": "text", + "x": 499.25, + "y": 584, + "width": 257.76666259765625, + "height": 24, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "aw", + "roundness": null, + "seed": 1132344792, + "version": 117, + "versionNonce": 151768792, + "isDeleted": false, + "boundElements": null, + "updated": 1755788934562, + "link": null, + "locked": false, + "text": "https://coller/note-id", + "fontSize": 20, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "https://coller/note-id", + "autoResize": true, + "lineHeight": 1.2 + }, + { + "id": "8nsGIc_KHl1TFnpg6ejUo", + "type": "text", + "x": 499.3666687011719, + "y": 661, + "width": 399.9333190917969, + "height": 24, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "ax", + "roundness": null, + "seed": 1693301208, + "version": 179, + "versionNonce": 1850772184, + "isDeleted": false, + "boundElements": [], + "updated": 1755789170471, + "link": null, + "locked": false, + "text": "https://coller/note-id/password 🔑", + "fontSize": 20, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "https://coller/note-id/password 🔑", + "autoResize": true, + "lineHeight": 1.2 + }, + { + "id": "_z-MUabVHx5E8_l4BWJ1z", + "type": "line", + "x": 1045.25, + "y": 274, + "width": 0, + "height": 461, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "dotted", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "ay", + "roundness": { + "type": 2 + }, + "seed": 228580056, + "version": 141, + "versionNonce": 528663256, + "isDeleted": false, + "boundElements": null, + "updated": 1755788967611, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 0, + 461 + ] + ], + "lastCommittedPoint": null, + "startBinding": null, + "endBinding": null, + "startArrowhead": null, + "endArrowhead": null, + "polygon": false + }, + { + "id": "JouKOO4uPqBFGr6XZX_py", + "type": "rectangle", + "x": 1174.75, + "y": 277, + "width": 141.00000000000003, + "height": 156, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "az", + "roundness": { + "type": 3 + }, + "seed": 496191400, + "version": 519, + "versionNonce": 1511097560, + "isDeleted": false, + "boundElements": [ + { + "type": "text", + "id": "VEdH7Z2EKX0em85vXetqX" + } + ], + "updated": 1755789290129, + "link": null, + "locked": false + }, + { + "id": "VEdH7Z2EKX0em85vXetqX", + "type": "text", + "x": 1198.3833351135254, + "y": 331, + "width": 93.73332977294922, + "height": 48, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "b00", + "roundness": null, + "seed": 1578898088, + "version": 520, + "versionNonce": 1421427624, + "isDeleted": false, + "boundElements": [], + "updated": 1755789189329, + "link": null, + "locked": false, + "text": "Client\n(copier)", + "fontSize": 20, + "fontFamily": 3, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "JouKOO4uPqBFGr6XZX_py", + "originalText": "Client\n(copier)", + "autoResize": true, + "lineHeight": 1.2 + }, + { + "id": "ktbpPk_s1UpLcnSnu93ef", + "type": "text", + "x": 1183.2833404541016, + "y": 457, + "width": 411.6499938964844, + "height": 24, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "b03", + "roundness": null, + "seed": 1415160792, + "version": 493, + "versionNonce": 21443032, + "isDeleted": false, + "boundElements": [], + "updated": 1755789281126, + "link": null, + "locked": false, + "text": "copier -password PASSWORD 🔑 ", + "fontSize": 20, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "copier -password PASSWORD 🔑 ", + "autoResize": true, + "lineHeight": 1.2 + }, + { + "id": "6CQhcN4aPSfHv0u_lyIDx", + "type": "arrow", + "x": 1161.25, + "y": 477, + "width": 361, + "height": 104, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "b04", + "roundness": { + "type": 2 + }, + "seed": 2133643432, + "version": 109, + "versionNonce": 574848728, + "isDeleted": false, + "boundElements": null, + "updated": 1755789311725, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + -361, + 104 + ] + ], + "lastCommittedPoint": null, + "startBinding": null, + "endBinding": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "elbowed": false + }, + { + "id": "ocyk_yJCiGwmnkR55FoTJ", + "type": "arrow", + "x": 1158.25, + "y": 664, + "width": 237, + "height": 0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "b05", + "roundness": { + "type": 2 + }, + "seed": 1443036840, + "version": 52, + "versionNonce": 1027734696, + "isDeleted": false, + "boundElements": null, + "updated": 1755789226112, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + -237, + 0 + ] + ], + "lastCommittedPoint": null, + "startBinding": { + "elementId": "Kc7_mrdgSLrf6jNGvILS3", + "focus": -0.41025641025641013, + "gap": 17.5 + }, + "endBinding": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "elbowed": false + }, + { + "id": "BkLH2VDz86-eIlEB8PA3M", + "type": "arrow", + "x": 1153.25, + "y": 599, + "width": 351, + "height": 0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "b06", + "roundness": { + "type": 2 + }, + "seed": 2066376872, + "version": 194, + "versionNonce": 2036082856, + "isDeleted": false, + "boundElements": null, + "updated": 1755789319209, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + -351, + 0 + ] + ], + "lastCommittedPoint": null, + "startBinding": { + "elementId": "Kc7_mrdgSLrf6jNGvILS3", + "focus": 0.42307692307692313, + "gap": 22.5 + }, + "endBinding": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "elbowed": false + } + ], + "appState": { + "gridSize": 20, + "gridStep": 5, + "gridModeEnabled": false, + "viewBackgroundColor": "#ffffff", + "lockedMultiSelections": {} + }, + "files": {} +} \ No newline at end of file diff --git a/static/coller/coller-encryption.svg b/static/coller/coller-encryption.svg new file mode 100644 index 0000000..f15fff8 --- /dev/null +++ b/static/coller/coller-encryption.svg @@ -0,0 +1,4 @@ + + +Server(collerd)Client(web)https://coller/note-idhttps://coller/note-id/password 🔑Client(copier)copier -password PASSWORD 🔑 <URL> \ No newline at end of file diff --git a/static/coller/coller-index.png b/static/coller/coller-index.png new file mode 100644 index 0000000..3ae2ef9 Binary files /dev/null and b/static/coller/coller-index.png differ