GithubHelp home page GithubHelp logo

oknoah / sabayon Goto Github PK

View Code? Open in Web Editor NEW

This project forked from dmathieu/sabayon

0.0 2.0 0.0 447 KB

Automated generation and renewal of ACME/Letsencrypt SSL certificates for Heroku apps.

Home Page: http://dmathieu.com/articles/development/heroku-ssl-letsencrypt/

License: MIT License

Makefile 2.64% Go 97.36%

sabayon's Introduction

Sabayon

Automated generation and renewal of ACME/Letsencrypt SSL certificates for Heroku apps.

architecture

Setup

There are two parts to the setup:

  1. Activating Heroku's HTTP SNI
  2. Your application setup
  3. Creating a new sabayon app

Heroku's HTTP SNI

This project relies on Heroku's Free SSL offering. You need to enable the http-sni lab feature in your project.

heroku labs:enable http-sni

Configuring your application

In step 5 (above) ACME calls a specific, unique URL on your application that allows ownership to be validated. This URL is based upon config vars set by the sabayon app (both during initial create and on an ongoing basis).

There are a couple options to read the config vars and automagically create the appropriate URL endpoint.

Static apps

For a static app change the web process type in your Procfile:

web: bin/start

Add a bin/start file to your app:

#!/usr/bin/env ruby
data = []
if ENV['ACME_KEY'] && ENV['ACME_TOKEN']
  data << {key: ENV['ACME_KEY'], token: ENV['ACME_TOKEN']}
else
  ENV.each do |k, v|
    if d = k.match(/^ACME_KEY_([0-9]+)/)
      index = d[1]

      data << {key: v, token: ENV["ACME_TOKEN_#{index}"]}
    end
  end
end

`mkdir -p dist/.well-known/acme-challenge`
data.each do |e|
  `echo #{e[:key]} > dist/.well-known/acme-challenge/#{e[:token]}`
end

`bin/boot`

Make that file executable:

chmod +x bin/start

Commit this code then deploy your main app with those changes.

Rails Applications

Add a route to handle the request. Based on schneems's codetriage commit.

There is also a rack example next if you would rather handle this in rack or if you have a non-rails app.

YourAppName::Application.routes.draw do

  if ENV['ACME_KEY'] && ENV['ACME_TOKEN']
    get ".well-known/acme-challenge/#{ ENV["ACME_TOKEN"] }" => proc { [200, {}, [ ENV["ACME_KEY"] ] ] }
  else
    ENV.each do |var, _|
      next unless var.start_with?("ACME_TOKEN_")
      number = var.sub(/ACME_TOKEN_/, '')
      get ".well-known/acme-challenge/#{ ENV["ACME_TOKEN_#{number}"] }" => proc { [200, {}, [ ENV["ACME_KEY_#{number}"] ] ] }
    end
  end
end

Ruby apps

Add the following rack middleware to your app:

class SabayonMiddleware
  def initialize(app)
    @app = app
  end

  def call(env)
    data = []
    if ENV['ACME_KEY'] && ENV['ACME_TOKEN']
      data << { key: ENV['ACME_KEY'], token: ENV['ACME_TOKEN'] }
    else
      ENV.each do |k, v|
        if d = k.match(/^ACME_KEY_([0-9]+)/)
          index = d[1]
          data << { key: v, token: ENV["ACME_TOKEN_#{index}"] }
        end
      end
    end

    data.each do |e|
      if env["PATH_INFO"] == "/.well-known/acme-challenge/#{e[:token]}"
        return [200, { "Content-Type" => "text/plain" }, [e[:key]]]
      end
    end

    @app.call(env)
  end
end

Go apps

Add the following handler to your app:

http.HandleFunc("/.well-known/acme-challenge/", func(w http.ResponseWriter, r *http.Request) {
  pt := strings.TrimPrefix(r.URL.Path, "/.well-known/acme-challenge/")
  rk := ""

  k := os.Getenv("ACME_KEY")
  t := os.Getenv("ACME_TOKEN")
  if k != "" && t != "" {
  	if pt == t {
  		rk = k
  	}
  } else {
  	for i := 1; ; i++ {
  		is := strconv.Itoa(i)
  		k = os.Getenv("ACME_KEY_" + is)
  		t = os.Getenv("ACME_TOKEN_" + is)
  		if k != "" && t != "" {
  			if pt == t {
  				rk = k
  				break
  			}
  		} else {
  			break
  		}
  	}
  }

  if rk != "" {
  	fmt.Fprint(w, rk)
  } else {
  	http.NotFound(w, r)
  }
})

Express apps

Define the following route in your app.

app.get('/.well-known/acme-challenge/:acmeToken', function(req, res, next) {
  var acmeToken = req.params.acmeToken;
  var acmeKey;

  if (process.env.ACME_KEY && process.env.ACME_TOKEN) {
    if (acmeToken === process.env.ACME_TOKEN) {
      acmeKey = process.env.ACME_KEY;
    }
  }

  for (var key in process.env) {
    if (key.startsWith('ACME_TOKEN_')) {
      var num = key.split('ACME_TOKEN_')[1];
      if (acmeToken === process.env['ACME_TOKEN_' + num]) {
        acmeKey = process.env['ACME_KEY_' + num];
      }
    }
  }

  if (acmeKey) res.send(acmeKey);
  else res.status(404).send();
});

Other HTTP implementations

In any other language, you need to be able to respond to requests on the path /.well-known/acme-challenge/$ACME_TOKEN with $ACME_KEY as the content.

Please add any other language/framework by opening a Pull Request.

Creating and deploy the sabayon app

In addition to configuring your application, you will also need to create a new Heroku application which will run sabayon to create and update the certificates for your main application.

To easily create a new Heroku application with the sabayon code, click on this deploy button and fill in all the required config vars.

Deploy

You can then generate your first certificate with the following command (this will add configuration to your main application and restart it as well).

heroku run sabayon

Open the scheduler add-on provisioned, and add the following daily command to regenerate your certificate automatically one month before it expires:

sabayon

Update DNS

After configuring and successfully running Sabayon, you'll likely need to change your DNS settings. Non-SSL apps usually use a CNAME or ALIAS pointing to your-app-name.herokuapp.com, while apps with http-si are accessible at your-app-name.com.herokudns.com. You should check your exact DNS target in your Heroku Dashboard under the Settings tab, within the Domains section. Look for "DNS Targets" under "Custom domains".

Force-reload a certificate

You can force-reload your app's certificate:

heroku run sabayon --force

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.