letsencrypt

package module
v0.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 10, 2025 License: MIT Imports: 24 Imported by: 0

README

Let's Encrypt Module

The Let's Encrypt module provides automatic SSL/TLS certificate generation and management using Let's Encrypt's ACME protocol. It integrates seamlessly with the Modular framework to provide HTTPS capabilities for your applications.

Go Reference

Features

  • Automatic Certificate Generation: Obtain SSL/TLS certificates from Let's Encrypt automatically
  • Multiple Challenge Types: Support for HTTP-01 and DNS-01 challenges
  • Auto-Renewal: Automatic certificate renewal before expiration
  • Multiple DNS Providers: Support for various DNS providers (Cloudflare, Route53, Azure DNS, etc.)
  • Staging Environment: Use Let's Encrypt's staging environment for testing
  • Certificate Storage: Persistent storage of certificates and account information
  • Production Ready: Built with best practices for production deployments

Installation

go get github.com/CrisisTextLine/modular/modules/letsencrypt

Quick Start

Basic Usage with HTTP Challenge
package main

import (
    "context"
    "log/slog"
    "os"

    "github.com/CrisisTextLine/modular"
    "github.com/CrisisTextLine/modular/modules/letsencrypt"
    "github.com/CrisisTextLine/modular/modules/httpserver"
)

type AppConfig struct {
    LetsEncrypt letsencrypt.LetsEncryptConfig `yaml:"letsencrypt"`
    HTTPServer  httpserver.HTTPServerConfig   `yaml:"httpserver"`
}

func main() {
    logger := slog.New(slog.NewTextHandler(os.Stdout, nil))
    
    config := &AppConfig{
        LetsEncrypt: letsencrypt.LetsEncryptConfig{
            Email:       "your-email@example.com",
            Domains:     []string{"example.com", "www.example.com"},
            UseStaging:  false, // Set to true for testing
            StoragePath: "./certs",
            AutoRenew:   true,
            RenewBefore: 30, // Renew 30 days before expiration
        },
        HTTPServer: httpserver.HTTPServerConfig{
            Host: "0.0.0.0",
            Port: 443,
            TLS:  &httpserver.TLSConfig{Enabled: true},
        },
    }

    configProvider := modular.NewStdConfigProvider(config)
    app := modular.NewStdApplication(configProvider, logger)

    // Register modules
    app.RegisterModule(letsencrypt.NewLetsEncryptModule())
    app.RegisterModule(httpserver.NewHTTPServerModule())

    if err := app.Run(); err != nil {
        logger.Error("Application error", "error", err)
        os.Exit(1)
    }
}
DNS Challenge with Cloudflare
config := &AppConfig{
    LetsEncrypt: letsencrypt.LetsEncryptConfig{
        Email:       "your-email@example.com",
        Domains:     []string{"*.example.com", "example.com"},
        UseStaging:  false,
        StoragePath: "./certs",
        AutoRenew:   true,
        UseDNS:      true,
        DNSProvider: &letsencrypt.DNSProviderConfig{
            Name: "cloudflare",
        },
        DNSConfig: map[string]string{
            "CLOUDFLARE_EMAIL":   "your-email@example.com",
            "CLOUDFLARE_API_KEY": "your-api-key",
        },
    },
}

Configuration

LetsEncryptConfig
Field Type Description Default
email string Email address for Let's Encrypt registration Required
domains []string List of domain names to obtain certificates for Required
use_staging bool Use Let's Encrypt staging environment false
storage_path string Directory for certificate storage "./letsencrypt"
renew_before int Days before expiry to renew certificates 30
auto_renew bool Enable automatic renewal true
use_dns bool Use DNS-01 challenges instead of HTTP-01 false
DNS Provider Configuration

For DNS challenges, configure the DNS provider:

letsencrypt:
  email: "your-email@example.com"
  domains:
    - "example.com"
    - "*.example.com"
  use_dns: true
  dns_provider:
    name: "cloudflare"
  dns_config:
    CLOUDFLARE_EMAIL: "your-email@example.com"
    CLOUDFLARE_API_KEY: "your-api-key"
Supported DNS Providers
  • Cloudflare: cloudflare
  • Route53 (AWS): route53
  • Azure DNS: azuredns
  • Google Cloud DNS: gcloud
  • DigitalOcean: digitalocean
  • Namecheap: namecheap

Each provider requires specific environment variables or configuration parameters.

Integration with HTTP Server

The Let's Encrypt module works seamlessly with the HTTP Server module by implementing the CertificateService interface:

// The HTTP server module will automatically use certificates from Let's Encrypt
app.RegisterModule(letsencrypt.NewLetsEncryptModule())
app.RegisterModule(httpserver.NewHTTPServerModule())

Advanced Usage

Custom Certificate Handling
// Get certificate service for custom handling
var certService httpserver.CertificateService
app.GetService("certificateService", &certService)

// Get certificate for a specific domain
cert := certService.GetCertificate("example.com")
Manual Certificate Operations
letsEncryptModule := letsencrypt.NewLetsEncryptModule()

// Force certificate renewal
if err := letsEncryptModule.RenewCertificate("example.com"); err != nil {
    log.Printf("Failed to renew certificate: %v", err)
}

Environment Variables

You can configure the module using environment variables:

LETSENCRYPT_EMAIL=your-email@example.com
LETSENCRYPT_DOMAINS=example.com,www.example.com
LETSENCRYPT_USE_STAGING=false
LETSENCRYPT_STORAGE_PATH=./certs
LETSENCRYPT_AUTO_RENEW=true

Best Practices

  1. Use Staging for Testing: Always test with use_staging: true to avoid rate limits
  2. Secure Storage: Ensure certificate storage directory has proper permissions
  3. Monitor Renewals: Set up monitoring for certificate renewal failures
  4. Backup Certificates: Regularly backup your certificate storage directory
  5. DNS Challenge for Wildcards: Use DNS challenges for wildcard certificates

Troubleshooting

Common Issues
  1. Rate Limits: Use staging environment for testing
  2. DNS Propagation: DNS challenges may take time to propagate
  3. Firewall: Ensure port 80 is accessible for HTTP challenges
  4. Domain Validation: Verify domain ownership and DNS configuration
Debug Mode

Enable debug logging to troubleshoot issues:

logger := slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{
    Level: slog.LevelDebug,
}))

Examples

See the examples directory for complete working examples:

  • Basic HTTPS server with Let's Encrypt
  • Multi-domain certificate management
  • DNS challenge configuration

Dependencies

  • lego - ACME client library
  • Works with the httpserver module for HTTPS support

License

This module is part of the Modular framework and is licensed under the MIT License.

Documentation

Overview

Package letsencrypt provides a module for automatic SSL certificate generation via Let's Encrypt for the modular framework.

Package letsencrypt provides a module for automatic SSL certificate generation via Let's Encrypt for the modular framework.

This module integrates Let's Encrypt ACME protocol support into the modular framework, enabling automatic SSL/TLS certificate provisioning, renewal, and management. It supports multiple challenge types and DNS providers for flexible certificate acquisition.

Features

The letsencrypt module provides the following capabilities:

  • Automatic SSL certificate acquisition from Let's Encrypt
  • Support for HTTP-01 and DNS-01 challenge types
  • Multiple DNS provider integrations (Cloudflare, Route53, DigitalOcean, etc.)
  • Automatic certificate renewal before expiration
  • Certificate storage and management
  • Staging and production environment support
  • Service interface for integration with HTTP servers

Challenge Types

The module supports two ACME challenge types:

  • HTTP-01: Domain validation via HTTP endpoints
  • DNS-01: Domain validation via DNS TXT records

Supported DNS Providers

When using DNS-01 challenges, the following providers are supported:

  • Cloudflare
  • AWS Route53
  • DigitalOcean
  • Google Cloud DNS
  • Azure DNS
  • Namecheap

Configuration

The module can be configured through the LetsEncryptConfig structure:

config := &LetsEncryptConfig{
    Email:           "admin@example.com",
    Domains:         []string{"example.com", "www.example.com"},
    ChallengeType:   "http-01",        // or "dns-01"
    DNSProvider:     "cloudflare",     // for DNS challenges
    CADirectory:     CAProduction,     // or CAStaging for testing
    CertificatePath: "/etc/ssl/certs", // certificate storage path
    KeyPath:         "/etc/ssl/private", // private key storage path
    DNSProviderConfig: &CloudflareConfig{
        APIToken: "your-cloudflare-token",
    },
}

Service Registration

The module registers itself as a certificate service:

// Get the certificate service
certService := app.GetService("letsencrypt.certificates").(letsencrypt.CertificateService)

// Get a certificate for a domain
cert, err := certService.GetCertificate("example.com")

// Configure TLS with automatic certificates
tlsConfig := &tls.Config{
    GetCertificate: certService.GetCertificate,
}

Usage Examples

Basic HTTP server with automatic HTTPS:

// Configure Let's Encrypt module
config := &LetsEncryptConfig{
    Email:   "admin@example.com",
    Domains: []string{"example.com"},
    ChallengeType: "http-01",
    CADirectory: CAProduction,
}

// Get certificate service
certService := app.GetService("letsencrypt.certificates").(CertificateService)

// Create TLS config with automatic certificates
tlsConfig := &tls.Config{
    GetCertificate: certService.GetCertificate,
}

// Start HTTPS server
server := &http.Server{
    Addr:      ":443",
    TLSConfig: tlsConfig,
    Handler:   httpHandler,
}
server.ListenAndServeTLS("", "")

DNS challenge with Cloudflare:

config := &LetsEncryptConfig{
    Email:       "admin@example.com",
    Domains:     []string{"example.com", "*.example.com"},
    ChallengeType: "dns-01",
    DNSProvider: "cloudflare",
    DNSProviderConfig: &CloudflareConfig{
        APIToken: os.Getenv("CLOUDFLARE_API_TOKEN"),
    },
}

Certificate Management

The module automatically handles:

  • Certificate acquisition on first request
  • Certificate renewal (default: 30 days before expiration)
  • Certificate storage and loading
  • OCSP stapling support
  • Certificate chain validation

Security Considerations

- Use staging environment for testing to avoid rate limits - Store API credentials securely (environment variables, secrets) - Ensure proper file permissions for certificate storage - Monitor certificate expiration and renewal logs - Use strong private keys (RSA 2048+ or ECDSA P-256+)

Package letsencrypt provides a module for automatic SSL certificate generation via Let's Encrypt for the modular framework.

Index

Constants

View Source
const (
	// CAStaging is the URL for Let's Encrypt's staging environment.
	// Use this for testing to avoid hitting production rate limits.
	// Certificates from staging are not trusted by browsers.
	CAStaging = "https://acme-staging-v02.api.letsencrypt.org/directory"

	// CAProduction is the URL for Let's Encrypt's production environment.
	// Use this for production deployments. Has strict rate limits.
	// Certificates from production are trusted by all major browsers.
	CAProduction = "https://acme-v02.api.letsencrypt.org/directory"
)

Constants for Let's Encrypt URLs

View Source
const ModuleName = "letsencrypt"

ModuleName is the unique identifier for the letsencrypt module.

Variables

View Source
var (
	// Configuration errors
	ErrEmailRequired              = errors.New("email address is required for Let's Encrypt registration")
	ErrDomainsRequired            = errors.New("at least one domain is required")
	ErrConflictingProviders       = errors.New("cannot specify both HTTP and DNS challenge providers")
	ErrServerNameEmpty            = errors.New("server name is empty")
	ErrNoCertificateFound         = errors.New("no certificate found for domain")
	ErrCloudflareConfigMissing    = errors.New("cloudflare provider configuration is missing")
	ErrRoute53ConfigMissing       = errors.New("route53 provider configuration is missing")
	ErrDigitalOceanConfigMissing  = errors.New("digitalocean provider configuration is missing")
	ErrDigitalOceanTokenRequired  = errors.New("digitalocean auth token is required")
	ErrDNSConfigMissing           = errors.New("DNS provider configuration is missing or invalid")
	ErrUnsupportedDNSProvider     = errors.New("unsupported DNS provider")
	ErrGoogleCloudProjectRequired = errors.New("google Cloud DNS project ID is required")
	ErrAzureDNSConfigIncomplete   = errors.New("azure DNS provider requires client_id, client_secret, subscription_id, tenant_id, and resource_group")
	ErrNamecheapConfigIncomplete  = errors.New("namecheap DNS provider requires api_user, api_key, and username")
	ErrHTTPChallengeNotConfigured = errors.New("HTTP challenge handler not configured")

	// Certificate errors
	ErrCertificateFileNotFound = errors.New("certificate file not found")
	ErrKeyFileNotFound         = errors.New("key file not found")
	ErrPEMDecodeFailure        = errors.New("failed to decode PEM block containing certificate")
)

Static error definitions for the letsencrypt module

Functions

This section is empty.

Types

type CertificateService

type CertificateService interface {
	// GetCertificate returns a certificate for the given ClientHello
	GetCertificate(*tls.ClientHelloInfo) (*tls.Certificate, error)

	// GetCertificateForDomain returns a certificate for the specified domain
	GetCertificateForDomain(domain string) (*tls.Certificate, error)

	// Domains returns a list of domains this service can provide certificates for
	Domains() []string
}

CertificateService defines the interface for a service that can provide TLS certificates

type ChallengeHandler

type ChallengeHandler interface {
	// PresentChallenge is called when a challenge token needs to be made available
	PresentChallenge(domain, token, keyAuth string) error

	// CleanupChallenge is called when a challenge token needs to be removed
	CleanupChallenge(domain, token, keyAuth string) error
}

ChallengeHandler defines the interface for handlers that can handle ACME challenges

type CloudflareConfig

type CloudflareConfig struct {
	Email    string `yaml:"email" json:"email" env:"EMAIL"`
	APIKey   string `yaml:"api_key" json:"api_key" env:"API_KEY"`
	APIToken string `yaml:"api_token" json:"api_token" env:"API_TOKEN"`
}

CloudflareConfig holds the configuration for Cloudflare DNS API

type DNSProviderConfig

type DNSProviderConfig struct {
	// Provider is the name of the DNS provider (e.g., "cloudflare", "route53", etc.)
	Provider string `yaml:"provider" json:"provider" env:"PROVIDER"`

	// Parameters is a map of provider-specific configuration parameters
	Parameters map[string]string `yaml:"parameters" json:"parameters" env:"PARAMETERS"`

	// Provider-specific configurations
	Cloudflare   *CloudflareConfig   `yaml:"cloudflare,omitempty" json:"cloudflare,omitempty"`
	Route53      *Route53Config      `yaml:"route53,omitempty" json:"route53,omitempty"`
	DigitalOcean *DigitalOceanConfig `yaml:"digitalocean,omitempty" json:"digitalocean,omitempty"`
}

DNSProviderConfig defines the configuration for DNS challenge providers

type DigitalOceanConfig

type DigitalOceanConfig struct {
	AuthToken string `yaml:"auth_token" json:"auth_token" env:"AUTH_TOKEN"`
}

DigitalOceanConfig holds the configuration for DigitalOcean DNS API

type HTTPProviderConfig

type HTTPProviderConfig struct {
	// Use the built-in HTTP server for challenges
	UseBuiltIn bool `yaml:"use_built_in" json:"use_built_in" env:"USE_BUILT_IN"`

	// Port to use for the HTTP challenge server (default: 80)
	Port int `yaml:"port" json:"port" env:"PORT"`
}

HTTPProviderConfig defines the configuration for HTTP challenge providers

type LetsEncryptConfig

type LetsEncryptConfig struct {
	// Email is the email address to use for registration with Let's Encrypt
	Email string `yaml:"email" json:"email" env:"EMAIL"`

	// Domains is a list of domain names to obtain certificates for
	Domains []string `yaml:"domains" json:"domains" env:"DOMAINS"`

	// UseStaging determines whether to use Let's Encrypt's staging environment
	// Set to true for testing to avoid rate limits
	UseStaging bool `yaml:"use_staging" json:"use_staging" env:"USE_STAGING"`

	// UseProduction is the opposite of UseStaging, for clarity in configuration
	UseProduction bool `yaml:"use_production" json:"use_production" env:"USE_PRODUCTION"`

	// StoragePath is the directory where certificates and account information will be stored
	StoragePath string `yaml:"storage_path" json:"storage_path" env:"STORAGE_PATH"`

	// RenewBefore sets how long before expiry certificates should be renewed (in days)
	RenewBefore int `yaml:"renew_before" json:"renew_before" env:"RENEW_BEFORE"`

	// RenewBeforeDays is an alias for RenewBefore for backward compatibility
	RenewBeforeDays int `yaml:"renew_before_days" json:"renew_before_days" env:"RENEW_BEFORE_DAYS"`

	// AutoRenew enables automatic certificate renewal
	AutoRenew bool `yaml:"auto_renew" json:"auto_renew" env:"AUTO_RENEW"`

	// UseDNS indicates whether to use DNS challenges instead of HTTP
	UseDNS bool `yaml:"use_dns" json:"use_dns" env:"USE_DNS"`

	// DNSProvider configuration for DNS challenges
	DNSProvider *DNSProviderConfig `yaml:"dns_provider,omitempty" json:"dns_provider,omitempty"`

	// DNSConfig is a map of DNS provider specific configuration parameters
	DNSConfig map[string]string `yaml:"dns_config,omitempty" json:"dns_config,omitempty" env:"DNS_CONFIG"`

	// HTTPProvider configuration for HTTP challenges
	HTTPProvider *HTTPProviderConfig `yaml:"http_provider,omitempty" json:"http_provider,omitempty"`

	// HTTPChallengeHandler is an HTTP handler for HTTP-01 challenges
	HTTPChallengeHandler http.Handler `yaml:"-" json:"-"`

	// CustomCACertificate is a custom CA certificate to be trusted
	CustomCACertificate []byte `yaml:"-" json:"-"`
}

LetsEncryptConfig defines the configuration for the Let's Encrypt module.

func (*LetsEncryptConfig) Validate

func (c *LetsEncryptConfig) Validate() error

Validate checks if the configuration is valid and sets default values where appropriate.

type LetsEncryptModule

type LetsEncryptModule struct {
	// contains filtered or unexported fields
}

LetsEncryptModule provides automatic SSL certificate management using Let's Encrypt. It handles certificate acquisition, renewal, and storage with support for multiple challenge types and DNS providers.

The module implements the following interfaces:

  • modular.Module: Basic module lifecycle
  • modular.Configurable: Configuration management
  • modular.ServiceAware: Service dependency management
  • modular.Startable: Startup logic
  • modular.Stoppable: Shutdown logic
  • CertificateService: Certificate management interface

Certificate operations are thread-safe and support concurrent requests.

func New

func New(config *LetsEncryptConfig) (*LetsEncryptModule, error)

New creates a new Let's Encrypt module

func (*LetsEncryptModule) Config

func (m *LetsEncryptModule) Config() interface{}

Config returns the module's configuration

func (*LetsEncryptModule) Domains

func (m *LetsEncryptModule) Domains() []string

Domains returns the list of domains this service can provide certificates for

func (*LetsEncryptModule) GetCertificate

func (m *LetsEncryptModule) GetCertificate(clientHello *tls.ClientHelloInfo) (*tls.Certificate, error)

GetCertificate implements the CertificateService.GetCertificate method to be used with tls.Config.GetCertificate

func (*LetsEncryptModule) GetCertificateForDomain

func (m *LetsEncryptModule) GetCertificateForDomain(domain string) (*tls.Certificate, error)

GetCertificateForDomain returns a certificate for the specified domain

func (*LetsEncryptModule) Name

func (m *LetsEncryptModule) Name() string

Name returns the name of the module

func (*LetsEncryptModule) RevokeCertificate

func (m *LetsEncryptModule) RevokeCertificate(domain string) error

RevokeCertificate revokes a certificate for the specified domain

func (*LetsEncryptModule) Start

func (m *LetsEncryptModule) Start(ctx context.Context) error

Start initializes the module and starts any background processes

func (*LetsEncryptModule) Stop

func (m *LetsEncryptModule) Stop(ctx context.Context) error

Stop stops any background processes

type Route53Config

type Route53Config struct {
	AccessKeyID     string `yaml:"access_key_id" json:"access_key_id" env:"ACCESS_KEY_ID"`
	SecretAccessKey string `yaml:"secret_access_key" json:"secret_access_key" env:"SECRET_ACCESS_KEY"`
	Region          string `yaml:"region" json:"region" env:"REGION"`
	HostedZoneID    string `yaml:"hosted_zone_id" json:"hosted_zone_id" env:"HOSTED_ZONE_ID"`
}

Route53Config holds the configuration for AWS Route53 DNS API

type User

type User struct {
	Email        string
	Registration *registration.Resource
	Key          crypto.PrivateKey // Changed from certcrypto.PrivateKey to crypto.PrivateKey
}

User implements the ACME User interface for Let's Encrypt

func (*User) GetEmail

func (u *User) GetEmail() string

GetEmail returns the email address for the user

func (*User) GetPrivateKey

func (u *User) GetPrivateKey() crypto.PrivateKey

GetPrivateKey returns the private key for the user

func (*User) GetRegistration

func (u *User) GetRegistration() *registration.Resource

GetRegistration returns the registration resource

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL