Skip to content

johanbrandhorst/certify

Repository files navigation

Certify

CircleCI GoDoc Go Report Card Code Coverage Releases License Join the chat at https://gitter.im/go-certify/community

Certify

Certify allows easy automatic certificate distribution and maintenance. Certificates are requested as TLS connections are made, courtesy of the GetCertificate and GetClientCertificate tls.Config hooks. Certificates are optionally cached. Simultaneous requests are deduplicated to minimize pressure on issuers.

Vault walkthrough

My presentation from GolangPiter 2019 contains a walkthrough of how to configure your Vault instance to securely issue certificates for your Go clients and servers.

Certify presentation

Users

Are you using Certify and want to be visible here? Open an issue!

Issuers

Certify exposes an Issuer interface which is used to allow switching between issuer backends.

Currently implemented issuers:

Usage

Create an issuer:

issuer := &vault.Issuer{
    URL: &url.URL{
        Scheme: "https",
        Host: "my-local-vault-instance.com",
    },
    Token:     "myVaultToken",
    Role:      "myVaultRole",
}

Create a Certify:

c := &certify.Certify{
    // Used when request client-side certificates and
    // added to SANs or IPSANs depending on format.
    CommonName: "MyServer.com",
    Issuer: issuer,
    // It is recommended to use a cache.
    Cache: certify.NewMemCache(),
    // It is recommended to set RenewBefore.
    // Refresh cached certificates when < 24H left before expiry.
    RenewBefore: 24*time.Hour,
}

Use in your TLS Config:

tlsConfig := &tls.Config{
    GetCertificate: c.GetCertificate,
}

That's it! Both server-side and client-side certificates can be generated:

tlsConfig := &tls.Config{
    GetClientCertificate: c.GetClientCertificate,
}

For an end-to-end example using gRPC with mutual TLS authentication, see the Vault tests.

Vault PKI Key Types

When setting up a Vault PKI backend and creating a role for Certify to use when it requests certificates, you'll be asked to specify the key type for the role to use. By default, Certify uses ecdsa keys with a 256-bit key length when it generates CSRs for Vault to sign.

If your Vault PKI role is created with a key type other than ec or any, API calls to Vault will fail with errors like

Error making API request.

URL: PUT https://localhost:8200/v1/pki/sign/example.com
Code: 400. Errors:

* role requires keys of type rsa

To use Certify with rsa or ed25519 keys, you'll need to pass a custom KeyGenerator to Certify which satisfies the certify.KeyGenerator interface. For example, for an rsa key:

type rsaKeyGenerator struct {
    key crypto.PrivateKey
    err error
    o   sync.Once
}

// This satisfies the `certify.KeyGenerator` interface.
func (s *rsaKeyGenerator) Generate() (crypto.PrivateKey, error) {
    s.o.Do(func() {
        // Use a different random data provider and key length if required.
        s.key, s.err = rsa.GenerateKey(rand.Reader, 2048)
    })
    return s.key, s.err
}

// Configure Certify's CSR generator to use our custom KeyGenerator
cfg := &certify.CertConfig{
    KeyGenerator: &rsaKeyGenerator{},
}

certify := &certify.Certify{
    CommonName:  "service1.example.com",
    Cache:       certify.DirCache("certificates"),
    Issuer:      issuer,
    RenewBefore: 10 * time.Minute,
    // Pass our custom configuration to Certify
    CertConfig:  cfg,
}

Docker image (sidecar model)

If you really want to use Certify but you are not able to use Go, there is now a Docker image available!

Simply configure this image as the access point for your Kubernetes pod and let it proxy traffic to your server.

How does it work?

How it works

Certify hooks into the GetCertificate and GetClientCertificate methods of the Go TLS stack Config struct. These get called when the server/client respectively is required to present its certificate. If possible, this is fetched from the cache, based on the requested server name. If not, a new certificate is issued with the requested server name present. For client requests, the configured CommonName is used.