Skip to content

Commit

Permalink
feat(letsencrypt): first dump
Browse files Browse the repository at this point in the history
  • Loading branch information
streambinder committed Aug 24, 2022
1 parent 845417c commit af36645
Show file tree
Hide file tree
Showing 13 changed files with 2,497 additions and 45 deletions.
322 changes: 307 additions & 15 deletions go.mod

Large diffs are not rendered by default.

1,904 changes: 1,904 additions & 0 deletions go.sum

Large diffs are not rendered by default.

20 changes: 16 additions & 4 deletions pki/crt.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,18 +53,30 @@ func ParseBytes(data []byte) (*x509.Certificate, error) {
return crt, nil
}

func ParseKeyPair(crtPath, keyPath string) (*x509.Certificate, *Key, error) {
tls, err := tls.LoadX509KeyPair(crtPath, keyPath)
func ParseKeyPairBytes(crt, key []byte) (*x509.Certificate, *Key, error) {
tls, err := tls.X509KeyPair(crt, key)
if err != nil {
return nil, nil, err
}

crt, err := x509.ParseCertificate(tls.Certificate[0])
if crt, err := x509.ParseCertificate(tls.Certificate[0]); err != nil {
return nil, nil, err
} else {
return crt, &Key{Value: tls.PrivateKey}, nil
}
}

func ParseKeyPair(crtPath, keyPath string) (*x509.Certificate, *Key, error) {
tls, err := tls.LoadX509KeyPair(crtPath, keyPath)
if err != nil {
return nil, nil, err
}

return crt, &Key{Value: tls.PrivateKey}, nil
if crt, err := x509.ParseCertificate(tls.Certificate[0]); err != nil {
return nil, nil, err
} else {
return crt, &Key{Value: tls.PrivateKey}, nil
}
}

func NewRequest(options map[string]any) Request {
Expand Down
6 changes: 3 additions & 3 deletions provider/abstract.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package provider

import (
"encoding/pem"
"errors"
"strings"
)
Expand All @@ -10,13 +9,14 @@ type Provider interface {
ID() string
Tune(options map[string]interface{}) error
For(name string) bool
Get(name string, options map[string]string) (*pem.Block, *pem.Block, error)
CA() (*pem.Block, error)
Get(name string, options map[string]string) ([]byte, []byte, error)
CA() ([]byte, error)
}

func Tune(id string, options map[string]interface{}) (*Provider, error) {
for _, provider := range []Provider{
new(Local),
new(LetsEncrypt),
} {
if !strings.EqualFold(id, provider.ID()) {
continue
Expand Down
202 changes: 202 additions & 0 deletions provider/letsencrypt.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,202 @@
package provider

import (
"crypto"
"errors"
"fmt"
"os"
"strings"

"github.com/go-acme/lego/v4/certcrypto"
"github.com/go-acme/lego/v4/certificate"
"github.com/go-acme/lego/v4/lego"
"github.com/go-acme/lego/v4/log"
"github.com/go-acme/lego/v4/providers/dns"
"github.com/go-acme/lego/v4/registration"
"gitlab.rete.farm/sistemi/inca/pki"
"gitlab.rete.farm/sistemi/inca/util"
)

type LetsEncrypt struct {
Provider
user *LetsEncryptUser
client *lego.Client
ca string
targets []*LetsEncryptTarget
}

type LetsEncryptTarget struct {
domain string
provider string
environment map[string]string
}

type LetsEncryptUser struct {
email string
key crypto.PrivateKey
registration *registration.Resource
}

func init() {
log.Logger = util.NewZStdLogger()
}

func (u LetsEncryptUser) GetEmail() string {
return u.email
}
func (u LetsEncryptUser) GetRegistration() *registration.Resource {
return u.registration
}
func (u LetsEncryptUser) GetPrivateKey() crypto.PrivateKey {
return u.key
}

func (p LetsEncrypt) ID() string {
return "letsencrypt"
}

func (p *LetsEncrypt) Tune(options map[string]interface{}) (err error) {
caURL, ok := options["ca"]
if !ok {
caURL = "https://letsencrypt.org/certs/isrgrootx1.pem"
}

keyPath, ok := options["key"]
if !ok {
return fmt.Errorf("provider %s: key not defined", p.ID())
}

email, ok := options["email"]
if !ok {
return fmt.Errorf("provider %s: email not defined", p.ID())
}

configTargets, ok := options["targets"]
if !ok {
configTargets = make([]interface{}, 0)
}
targets := make([]*LetsEncryptTarget, 0, len(configTargets.([]interface{})))
for _, configTarget := range configTargets.([]interface{}) {
var (
target = LetsEncryptTarget{environment: make(map[string]string)}
configTargetMap = configTarget.(map[interface{}]interface{})
)
domain, ok := configTargetMap["domain"]
if !ok {
return fmt.Errorf("provider %s: target domain not defined", p.ID())
}
target.domain = domain.(string)

challenge, ok := configTargetMap["challenge"]
if !ok {
return fmt.Errorf("provider %s: target %s challenge not defined", p.ID(), target.domain)
}
challengeMap := challenge.(map[interface{}]interface{})

challengeProvider, ok := challengeMap["id"]
if !ok {
return fmt.Errorf("provider %s: target %s challenge type not defined", p.ID(), target.domain)
}
target.provider = challengeProvider.(string)

for key, value := range challenge.(map[interface{}]interface{}) {
if strings.EqualFold(key.(string), "id") {
continue
}
target.environment[strings.ToUpper(key.(string))] = value.(string)
}

targets = append(targets, &target)
}

key, err := pki.ParseKey(keyPath.(string))
if err != nil {
return err
}

user := &LetsEncryptUser{email.(string), key.Value, nil}
config := lego.NewConfig(user)
switch pki.DefaultCrtAlgo {
case pki.ECDSA:
config.Certificate.KeyType = certcrypto.EC256
case pki.RSA:
config.Certificate.KeyType = certcrypto.RSA2048
default:
return errors.New("unsupported key algorithm")
}

client, err := lego.NewClient(config)
if err != nil {
return err
}

registration, err := client.Registration.Register(registration.RegisterOptions{TermsOfServiceAgreed: true})
if err != nil {
return err
}

user.registration = registration
p.ca = caURL.(string)
p.user = user
p.client = client
p.targets = targets
return
}

func (p *LetsEncrypt) For(name string) bool {
_, err := p.getChallengeProvider(name)
return err == nil
}

func (p *LetsEncrypt) getChallengeProvider(domain string) (*LetsEncryptTarget, error) {
for _, target := range p.targets {
if strings.HasSuffix(domain, target.domain) {
return target, nil
}
}
return nil, errors.New("challenge provider not found")
}

func (p *LetsEncrypt) Get(name string, options map[string]string) ([]byte, []byte, error) {
targetProvider, err := p.getChallengeProvider(name)
if err != nil {
return nil, nil, err
}

for envKey, envValue := range targetProvider.environment {
if err := os.Setenv(envKey, envValue); err != nil {
return nil, nil, err
}
}

provider, err := dns.NewDNSChallengeProviderByName(targetProvider.provider)
if err != nil {
return nil, nil, err
}

if err := p.client.Challenge.SetDNS01Provider(provider); err != nil {
return nil, nil, err
}

names := []string{name}
if alt, ok := options["alt"]; ok {
names = append(names, strings.Split(alt, ",")...)
}

request := certificate.ObtainRequest{
Domains: names,
Bundle: true,
}

certificates, err := p.client.Certificate.Obtain(request)
if err != nil {
return nil, nil, err
}

return certificates.Certificate, certificates.PrivateKey, nil
}

func (p *LetsEncrypt) CA() ([]byte, error) {
return []byte(
fmt.Sprintf(`<html><head><meta http-equiv="refresh" content="0;URL='%s'"/></head></html>`, p.ca)), nil
}
18 changes: 13 additions & 5 deletions provider/local.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package provider

import (
"crypto/x509"
"encoding/pem"
"fmt"
"strings"

Expand Down Expand Up @@ -43,7 +42,7 @@ func (p *Local) For(name string) bool {
return false
}

func (p *Local) Get(name string, options map[string]string) (*pem.Block, *pem.Block, error) {
func (p *Local) Get(name string, options map[string]string) ([]byte, []byte, error) {
reqOptions := make(map[string]any)
reqOptions["cn"] = name
for key, value := range options {
Expand All @@ -67,9 +66,18 @@ func (p *Local) Get(name string, options map[string]string) (*pem.Block, *pem.Bl
return nil, nil, err
}

return pki.Wrap(crt, key, p.crt, p.key)
if crt, key, err := pki.Wrap(crt, key, p.crt, p.key); err != nil {
return nil, nil, err
} else {
return pki.ExportBytes(crt), pki.ExportBytes(key), nil
}
}

func (p *Local) CA() (*pem.Block, error) {
return pki.WrapCrt(p.crt, p.key, p.crt, p.key)
func (p *Local) CA() ([]byte, error) {
crt, err := pki.WrapCrt(p.crt, p.key, p.crt, p.key)
if err != nil {
return nil, err
}

return pki.ExportBytes(crt), nil
}
6 changes: 2 additions & 4 deletions server/ca.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import (

"github.com/gofiber/fiber/v2"
"github.com/rs/zerolog/log"
"gitlab.rete.farm/sistemi/inca/pki"
"gitlab.rete.farm/sistemi/inca/provider"
)

Expand Down Expand Up @@ -34,11 +33,10 @@ func (inca *Inca) handlerCA(c *fiber.Ctx) error {
return c.SendStatus(fiber.StatusInternalServerError)
}

caCrtBytes := pki.ExportBytes(caCrt)
if strings.EqualFold(c.Get("Accept", "text/plain"), "application/json") {
return c.JSON(struct {
Crt string `json:"crt"`
}{string(caCrtBytes)})
}{string(caCrt)})
}
return c.SendStream(bytes.NewReader(caCrtBytes), len(caCrtBytes))
return c.SendStream(bytes.NewReader(caCrt), len(caCrt))
}
5 changes: 2 additions & 3 deletions server/crt.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,13 +63,12 @@ func (inca *Inca) handlerCRT(c *fiber.Ctx) error {
return c.SendStatus(fiber.StatusInternalServerError)
}

crtData := pki.ExportBytes(crt)
if strings.EqualFold(c.Get("Accept", "text/plain"), "application/json") {
return c.JSON(struct {
Crt string `json:"crt"`
}{string(crtData)})
}{string(crt)})
}
return c.SendStream(bytes.NewReader(crtData), len(crtData))
return c.SendStream(bytes.NewReader(crt), len(crt))
}

func queryStringAlt(queryStrings map[string]string) (altNames []string) {
Expand Down
3 changes: 1 addition & 2 deletions storage/abstract.go
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
package storage

import (
"encoding/pem"
"errors"
"strings"
)

type Storage interface {
ID() string
Tune(options map[string]interface{}) error
Put(name string, crtData *pem.Block, keyData *pem.Block) error
Put(name string, crtData, keyData []byte) error
Get(name string) ([]byte, []byte, error)
Del(name string) error
Find(filters ...string) ([][]byte, error)
Expand Down
7 changes: 3 additions & 4 deletions storage/fs.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package storage

import (
"encoding/pem"
"errors"
"fmt"
"io/ioutil"
Expand Down Expand Up @@ -50,7 +49,7 @@ func (s *FS) Get(name string) ([]byte, []byte, error) {
return crtData, keyData, nil
}

func (s *FS) Put(name string, crtData *pem.Block, keyData *pem.Block) error {
func (s *FS) Put(name string, crtData, keyData []byte) error {
var (
dirPath = filepath.Join(s.path, name)
crtPath = filepath.Join(dirPath, fsCrtName)
Expand All @@ -62,11 +61,11 @@ func (s *FS) Put(name string, crtData *pem.Block, keyData *pem.Block) error {
}
}

if err := pki.Export(crtData, crtPath); err != nil {
if err := os.WriteFile(crtPath, crtData, 0644); err != nil {
return err
}

if err := pki.Export(keyData, keyPath); err != nil {
if err := os.WriteFile(keyPath, keyData, 0644); err != nil {
return err
}

Expand Down
Loading

0 comments on commit af36645

Please sign in to comment.