<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Maxence Henneron</title>
    <description>The latest articles on Forem by Maxence Henneron (@maxencehenneron).</description>
    <link>https://forem.com/maxencehenneron</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F147472%2Ffcea572e-4cff-47a7-ba60-e2f72d970560.jpeg</url>
      <title>Forem: Maxence Henneron</title>
      <link>https://forem.com/maxencehenneron</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/maxencehenneron"/>
    <language>en</language>
    <item>
      <title>Automatic DNS and SSL management with Traefik</title>
      <dc:creator>Maxence Henneron</dc:creator>
      <pubDate>Tue, 23 Apr 2019 18:27:28 +0000</pubDate>
      <link>https://forem.com/maxencehenneron/automatic-dns-and-ssl-management-with-traefik-8cc</link>
      <guid>https://forem.com/maxencehenneron/automatic-dns-and-ssl-management-with-traefik-8cc</guid>
      <description>&lt;p&gt;Last month, we launched a new SaaS service called &lt;a href="https://upload.express"&gt;upload.express&lt;/a&gt;. It is a fully customizable uploader for anyone who often shares files. Upload.express is fully open-source and our business model is to offer hosting services. &lt;/p&gt;

&lt;p&gt;We quickly faced our first problem: how will we manage all the DNS records and SSL certificates?&lt;/p&gt;

&lt;p&gt;The first solution that came to our mind was to develop a small micro-service that would call the API of our domain name hosting provider to create the records, and finally use Let’s Encrypt to generate the SSL certificate.&lt;/p&gt;

&lt;p&gt;This is something I implemented 3 years ago in one of my last projects and we came across a huge issue: Let’s Encrypt has per-domain rate limits. You can only request  50 subdomain SSL certificates per week. Our project has a free trial plan so we expect to create more than 50 subdomains per week. &lt;/p&gt;

&lt;h2&gt;
  
  
  Wildcard certificates
&lt;/h2&gt;

&lt;p&gt;In the past, wildcard SSL certificates used to cost $300 but in 2018 everything changed when let’s encrypt announced they would support them. A wildcard certificate is a certificate you can use on all your subdomains. &lt;br&gt;
In example, if you own the domain “example.com”, you can use the same wildcard certificate “*.example.com” on all your subdomains. &lt;/p&gt;
&lt;h2&gt;
  
  
  Wildcard DNS records
&lt;/h2&gt;

&lt;p&gt;Along with wildcard certificates, most DNS providers support wildcard DNS records. By creating an “A” entry named “*.example.com”, that points to your server’s IP address, any request that come from any of your subdomain would redirect to your server’s IP. By doing this, you remove the necessity to implement your own domain-management microservice.&lt;/p&gt;
&lt;h2&gt;
  
  
  Using Traefik as a load balancer
&lt;/h2&gt;

&lt;p&gt;As you grow, you will need more than a single server to handle all the traffic. Also, configuring the SSL certificates at the load balancer level is easier as you do not have to configure all the different types of applications, you will only have to secure the transmission between the user and the load balancer. &lt;/p&gt;

&lt;p&gt;On upload.express, I use docker swarm to manage the deployment. You can also use other cluster management systems with traefik, such as kubernetes. In this short tutorial, I’m going to assume you’re using docker swarm, but it should not be hard to adapt it to kubernetes. &lt;/p&gt;

&lt;p&gt;First, I created a network to let traefik communicate with the services deployed on my cluster&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;&lt;code&gt;docker network create --opt encrypted -d overlay webgateway&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;Then, I used the following stack to deploy traefik.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;3.5"&lt;/span&gt;
&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;traefik&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;traefik:1.7&lt;/span&gt;
    &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--api"&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--loglevel=debug"&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--docker"&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--docker.swarmMode"&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--docker.domain=upload.express"&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--docker.watch"&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--configFile=/etc/traefik/traefik.toml"&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;/var/run/docker.sock:/var/run/docker.sock&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;/opt/traefik/traefik.toml:/etc/traefik/traefik.toml&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;/opt/traefik/acme.json:/etc/traefik/acme/acme.json&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;AWS_ACCESS_KEY_ID=&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;AWS_SECRET_ACCESS_KEY=&lt;/span&gt;
    &lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;webgateway&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;traefik&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;target&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;80&lt;/span&gt;
        &lt;span class="na"&gt;published&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;80&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;target&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;443&lt;/span&gt;
        &lt;span class="na"&gt;published&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;443&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;target&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;8080&lt;/span&gt;
        &lt;span class="na"&gt;published&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;8080&lt;/span&gt;
        &lt;span class="na"&gt;mode&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;host&lt;/span&gt;
    &lt;span class="na"&gt;deploy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;mode&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;global&lt;/span&gt;
      &lt;span class="na"&gt;placement&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;constraints&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;node.role == manager&lt;/span&gt;
      &lt;span class="na"&gt;update_config&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;parallelism&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
        &lt;span class="na"&gt;delay&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;10s&lt;/span&gt;
      &lt;span class="na"&gt;restart_policy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;condition&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;on-failure&lt;/span&gt;
&lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;webgateway&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;driver&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;overlay&lt;/span&gt;
    &lt;span class="na"&gt;external&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;
  &lt;span class="na"&gt;traefik&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;driver&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;overlay&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;As you can see, I set two environment variables that correspond to my AWS credentials. This is because we need to use the DNS validation to generate the SSL wildcard certificates. As I’m using route53 to manage my domain, I had to provide my AWS credentials. You can see the list of environment variables you need to set depending on your DNS provider &lt;a href="https://docs.traefik.io/configuration/acme/#provider"&gt;here&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;The last step is to create the configuration file for traefik. In my swarm stack, I configured the path to “/opt/traefik/traefik.toml”. All you have to do is creating the file and pasting the following configuration.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="py"&gt;defaultEntryPoints&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;["https","http"]&lt;/span&gt;

&lt;span class="nn"&gt;[entryPoints]&lt;/span&gt;
  &lt;span class="nn"&gt;[entryPoints.http]&lt;/span&gt;
  &lt;span class="py"&gt;address&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;":80"&lt;/span&gt;
    &lt;span class="nn"&gt;[entryPoints.http.redirect]&lt;/span&gt;
    &lt;span class="py"&gt;entryPoint&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"https"&lt;/span&gt;
  &lt;span class="nn"&gt;[entryPoints.https]&lt;/span&gt;
  &lt;span class="py"&gt;address&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;":443"&lt;/span&gt;
  &lt;span class="nn"&gt;[entryPoints.https.tls]&lt;/span&gt;

&lt;span class="nn"&gt;[retry]&lt;/span&gt;

&lt;span class="nn"&gt;[accessLog]&lt;/span&gt;

&lt;span class="nn"&gt;[docker]&lt;/span&gt;
&lt;span class="py"&gt;endpoint&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"unix:///var/run/docker.sock"&lt;/span&gt;
&lt;span class="py"&gt;domain&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"example.com"&lt;/span&gt;
&lt;span class="py"&gt;watch&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="py"&gt;exposedbydefault&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;

&lt;span class="nn"&gt;[acme]&lt;/span&gt;
&lt;span class="py"&gt;email&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"mail@example.com "&lt;/span&gt;
&lt;span class="py"&gt;storage&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"/etc/traefik/acme/acme.json"&lt;/span&gt;
&lt;span class="py"&gt;entryPoint&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"https"&lt;/span&gt;
&lt;span class="py"&gt;acmeLogging&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="py"&gt;onHostRule&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;

&lt;span class="nn"&gt;[acme.dnsChallenge]&lt;/span&gt;
  &lt;span class="py"&gt;provider&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"route53"&lt;/span&gt;
  &lt;span class="py"&gt;delayBeforeCheck&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;

&lt;span class="nn"&gt;[[acme.domains]]&lt;/span&gt;
   &lt;span class="py"&gt;main&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"*.example.com"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Do not forget to replace the example domain by yours. Now, start your traefik stack by running the following command:&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;&lt;code&gt;docker stack deploy -c traefik-stack.yml traefik&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;To finish, for each of the stacks you will deploy in your swarm cluster, set the following labels:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;- traefik.port=4000
- traefik.enable=true
- traefik.docker.network=webgateway
- traefik.backend=service_name
- traefik.frontend.rule=Host:service_name.example.com
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;and link the webgateway network we created earlier like so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;networks:
  webgateway:
    external: true
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;In example, here's the docker stack I'm using to deploy a upload.express instance:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;3.7'&lt;/span&gt;
&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;uploadexpress&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;uploadexpress/app&lt;/span&gt;
    &lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;webgateway&lt;/span&gt;
    &lt;span class="na"&gt;deploy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;placement&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;constraints&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;node.role==worker&lt;/span&gt;
      &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;traefik.port=4000&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;traefik.enable=true&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;traefik.docker.network=webgateway&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;traefik.backend=maxencehenneron&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;traefik.frontend.rule=Host:maxencehenneron.upload.express&lt;/span&gt;
      &lt;span class="na"&gt;restart_policy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;condition&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;on-failure&lt;/span&gt;
      &lt;span class="na"&gt;mode&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;replicated&lt;/span&gt;
      &lt;span class="na"&gt;replicas&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
&lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;webgateway&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;external&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;That's it! Traefik will automatically pick the correct wildcard certificate and you will be able to reach your service at "service_name.example.com"&lt;/p&gt;

</description>
      <category>devops</category>
      <category>docker</category>
      <category>traefik</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>One-command file sharing with upload.express</title>
      <dc:creator>Maxence Henneron</dc:creator>
      <pubDate>Wed, 17 Apr 2019 11:40:59 +0000</pubDate>
      <link>https://forem.com/maxencehenneron/one-command-file-sharing-with-upload-express-1io6</link>
      <guid>https://forem.com/maxencehenneron/one-command-file-sharing-with-upload-express-1io6</guid>
      <description>&lt;p&gt;In this short tutorial, I'm going to present the latest feature we implemented on &lt;a href="https://upload.express"&gt;upload.express&lt;/a&gt;: a one-command terminal file sharing.&lt;/p&gt;

&lt;p&gt;If you want to upload a file in your current working directory, all you have to do is typing the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;--upload-file&lt;/span&gt; ./file_name.ext https://upload.express/upload/file_name.ext
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;It will return a link to the uploaded file. &lt;/p&gt;

&lt;h2&gt;
  
  
  Creating an alias
&lt;/h2&gt;

&lt;p&gt;Remembering the curl command arguments can take a while. You can simplify the command by creating an alias in your ~/.bashrc or ~/.zshrc file.&lt;/p&gt;

&lt;p&gt;Just append the following code at the end of the file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;upload&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nv"&gt;$# &lt;/span&gt;&lt;span class="nt"&gt;-eq&lt;/span&gt; 0 &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s2"&gt;"No arguments specified. Usage:&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;echo upload /path/to/file.md"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;return &lt;/span&gt;1&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;fi
    &lt;/span&gt;curl &lt;span class="nt"&gt;--progress-bar&lt;/span&gt; &lt;span class="nt"&gt;--upload-file&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; https://upload.express/upload/&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;basename&lt;/span&gt; &lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt; | &lt;span class="nb"&gt;tee&lt;/span&gt; /dev/null&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="nb"&gt;alias &lt;/span&gt;&lt;span class="nv"&gt;upload&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;upload
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;You can now use the command &lt;code&gt;upload file_path&lt;/code&gt; to share any file.&lt;/p&gt;

</description>
      <category>showdev</category>
      <category>productivity</category>
      <category>bash</category>
      <category>linux</category>
    </item>
    <item>
      <title>Handling stripe webhooks with Ruby on Rails</title>
      <dc:creator>Maxence Henneron</dc:creator>
      <pubDate>Tue, 26 Mar 2019 18:24:05 +0000</pubDate>
      <link>https://forem.com/maxencehenneron/handling-stripe-webhooks-with-ruby-on-rails-4bb7</link>
      <guid>https://forem.com/maxencehenneron/handling-stripe-webhooks-with-ruby-on-rails-4bb7</guid>
      <description>&lt;p&gt;When accepting payments on a Ruby on Rails app, if you want to be aware of every actions that happen on stripe, you will have to implement the stripe &lt;a href="https://stripe.com/docs/webhooks" rel="noopener noreferrer"&gt;webhooks&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I found a nice way to handle them, which I'm going to share in this article.&lt;/p&gt;

&lt;p&gt;I &lt;em&gt;strongly&lt;/em&gt; discourage you to implement them on your own by adding a simple POST route to /webhooks, because:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You need to check stripe's signature before accepting them (someone may be trying to impersonate Stripe!)&lt;/li&gt;
&lt;li&gt;There are gems available to simplify this task&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In this tutorial, we're going to use a gem called &lt;a href="https://github.com/integrallis/stripe_event" rel="noopener noreferrer"&gt;stripe_event&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Let's get started
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Installing the dependencies
&lt;/h3&gt;

&lt;p&gt;The first step is to add the gem in your Gemfile. If you're new to ruby, the file is located at the root of your app.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;gem&lt;/span&gt; &lt;span class="s1"&gt;'stripe_event'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, in your routes.rb file, located in /config, add the following line:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;mount&lt;/span&gt; &lt;span class="no"&gt;StripeEvent&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Engine&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;at: &lt;/span&gt;&lt;span class="s1"&gt;'/stripe-webhooks'&lt;/span&gt; &lt;span class="c1"&gt;#you can change this url&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will create a POST route to handle all the webhooks. We're going to implement that in a few moments, after we setup everything in the stripe dashboard &lt;/p&gt;

&lt;h3&gt;
  
  
  Setting up the webhooks
&lt;/h3&gt;

&lt;p&gt;First, if you want to try the webhooks on your development environment, you will need to use a tool like &lt;a href="https://ngrok.io" rel="noopener noreferrer"&gt;ngrok&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Then, in your Stripe dashboard, navigate to Developers -&amp;gt; Webhooks or click on this &lt;a href="https://dashboard.stripe.com/account/webhooks" rel="noopener noreferrer"&gt;link&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Finally, switch to "test data" and add a new endpoint. In "URL to be called", add your ngrok URL followed by the route name you chose earlier.&lt;/p&gt;

&lt;p&gt;Once you created your webhook, please note the signing secret for that webhook somewhwere.&lt;br&gt;
&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fc4ymaupnogq7xih4emrz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fc4ymaupnogq7xih4emrz.png" alt="Signing secret" width="800" height="82"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Adding the credentials
&lt;/h3&gt;

&lt;p&gt;We will now add the stripe credentials to the (relatively) new &lt;a href="https://www.engineyard.com/blog/rails-encrypted-credentials-on-rails-5.2" rel="noopener noreferrer"&gt;rails encrypted credentials&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To edit the credentials, type the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;EDITOR=nano rails credentials:edit
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and write the following lines:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;stripe&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;development&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;publishable_key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;pk_test_'&lt;/span&gt;
    &lt;span class="na"&gt;secret_key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;sk_test_'&lt;/span&gt;
    &lt;span class="na"&gt;signing_secret&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;whsec_'&lt;/span&gt;
  &lt;span class="na"&gt;production&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;publishable_key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;pk_live_'&lt;/span&gt;
    &lt;span class="na"&gt;secret_key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;sk_live_'&lt;/span&gt;
    &lt;span class="na"&gt;signing_secret&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;whsec_'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;publishable_key and secret_key are your stripe keys, you can find them in your account settings, signing_secret is the secret we generated earlier.&lt;/p&gt;

&lt;h3&gt;
  
  
  Configuring stripe_events
&lt;/h3&gt;

&lt;p&gt;Create a file called "stripe_events.rb" in config/initializers and paste the following code&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;Stripe&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;api_key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Rails&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;application&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;credentials&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stripe&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="no"&gt;Rails&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_sym&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="ss"&gt;:publishable_key&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="no"&gt;StripeEvent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;signing_secret&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Rails&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;application&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;credentials&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stripe&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="no"&gt;Rails&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_sym&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="ss"&gt;:signing_secret&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="no"&gt;StripeEvent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;configure&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;events&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="n"&gt;events&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;subscribe&lt;/span&gt; &lt;span class="s1"&gt;'invoice.'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;Stripe&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;InvoiceEventHandler&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first two lines are setting the correct tokens, depending on your current environment (development or production)&lt;/p&gt;

&lt;p&gt;In the last three lines, we are telling stripe_event to subscribe to all the events starting with 'invoice.' and redirect them to a class called "InvoiceEventHandler"&lt;/p&gt;

&lt;p&gt;The next step is to implement this class.&lt;/p&gt;

&lt;p&gt;In your app folder, create a new folder called "services" and add a folder called "stripe" in it.&lt;/p&gt;

&lt;p&gt;Then, add a file called invoice_event_handler.rb in the stripe folder we just created.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;Stripe&lt;/span&gt;
  &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;InvoiceEventHandler&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="k"&gt;begin&lt;/span&gt;
        &lt;span class="nb"&gt;method&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"handle_"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;type&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;tr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'.'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'_'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt; &lt;span class="nb"&gt;method&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;
      &lt;span class="k"&gt;rescue&lt;/span&gt; &lt;span class="no"&gt;JSON&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;ParserError&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;
        &lt;span class="c1"&gt;# handle the json parsing error here&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="c1"&gt;# re-raise the exception to return a 500 error to stripe&lt;/span&gt;
      &lt;span class="k"&gt;rescue&lt;/span&gt; &lt;span class="no"&gt;NoMethodError&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;
        &lt;span class="c1"&gt;#code to run when handling an unknown event&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;handle_invoice_payment_failed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;handle_invoice_payment_succeeded&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here, I implemented two events: invoice.payment.failed and invoice.payment.succeeded. Using 'send', I'm forwarding the event to the correct method. (Credit: &lt;a href="https://dev.to/arandilopez/efficent-webhook-handlig-with-ruby-on-rails-4pj"&gt;@aradilopez&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;That's it! &lt;/p&gt;

&lt;p&gt;In my various projects, I'm redirecting all the useful notifications to a slack channel, so I'm always aware of what's happening.&lt;/p&gt;

</description>
      <category>rails</category>
      <category>ruby</category>
      <category>stripe</category>
      <category>webhook</category>
    </item>
  </channel>
</rss>
