0
Fork 0
mirror of https://github.com/caddyserver/caddy.git synced 2024-12-23 22:27:38 -05:00
caddy/caddytls/filestorage.go
Matthew Holt abdf13ea30 Improve TLS storage provider errors
We renamed caddytls.ErrStorageNotFound to caddytls.ErrNotExist to more
closely mirror the os package. We changed it to an interface wrapper
so that the custom error message can be preserved. Returning only "data
not found" was useless in debugging because we couldn't know the
concrete value of the error (like what it was trying to load).

Users can do a type assertion to determine if the error value is a "not
found" error instead of doing an equality check.
2016-09-08 18:50:04 -06:00

269 lines
7.7 KiB
Go

package caddytls
import (
"fmt"
"io/ioutil"
"net/url"
"os"
"path/filepath"
"strings"
"github.com/mholt/caddy"
)
func init() {
RegisterStorageProvider("file", FileStorageCreator)
}
// storageBasePath is the root path in which all TLS/ACME assets are
// stored. Do not change this value during the lifetime of the program.
var storageBasePath = filepath.Join(caddy.AssetsPath(), "acme")
// FileStorageCreator creates a new Storage instance backed by the local
// disk. The resulting Storage instance is guaranteed to be non-nil if
// there is no error. This can be used by "middleware" implementations that
// may want to proxy the disk storage.
func FileStorageCreator(caURL *url.URL) (Storage, error) {
return FileStorage(filepath.Join(storageBasePath, caURL.Host)), nil
}
// FileStorage is a root directory and facilitates forming file paths derived
// from it. It is used to get file paths in a consistent, cross- platform way
// for persisting ACME assets on the file system.
type FileStorage string
// sites gets the directory that stores site certificate and keys.
func (s FileStorage) sites() string {
return filepath.Join(string(s), "sites")
}
// site returns the path to the folder containing assets for domain.
func (s FileStorage) site(domain string) string {
domain = strings.ToLower(domain)
return filepath.Join(s.sites(), domain)
}
// siteCertFile returns the path to the certificate file for domain.
func (s FileStorage) siteCertFile(domain string) string {
domain = strings.ToLower(domain)
return filepath.Join(s.site(domain), domain+".crt")
}
// siteKeyFile returns the path to domain's private key file.
func (s FileStorage) siteKeyFile(domain string) string {
domain = strings.ToLower(domain)
return filepath.Join(s.site(domain), domain+".key")
}
// siteMetaFile returns the path to the domain's asset metadata file.
func (s FileStorage) siteMetaFile(domain string) string {
domain = strings.ToLower(domain)
return filepath.Join(s.site(domain), domain+".json")
}
// users gets the directory that stores account folders.
func (s FileStorage) users() string {
return filepath.Join(string(s), "users")
}
// user gets the account folder for the user with email
func (s FileStorage) user(email string) string {
if email == "" {
email = emptyEmail
}
email = strings.ToLower(email)
return filepath.Join(s.users(), email)
}
// emailUsername returns the username portion of an email address (part before
// '@') or the original input if it can't find the "@" symbol.
func emailUsername(email string) string {
at := strings.Index(email, "@")
if at == -1 {
return email
} else if at == 0 {
return email[1:]
}
return email[:at]
}
// userRegFile gets the path to the registration file for the user with the
// given email address.
func (s FileStorage) userRegFile(email string) string {
if email == "" {
email = emptyEmail
}
email = strings.ToLower(email)
fileName := emailUsername(email)
if fileName == "" {
fileName = "registration"
}
return filepath.Join(s.user(email), fileName+".json")
}
// userKeyFile gets the path to the private key file for the user with the
// given email address.
func (s FileStorage) userKeyFile(email string) string {
if email == "" {
email = emptyEmail
}
email = strings.ToLower(email)
fileName := emailUsername(email)
if fileName == "" {
fileName = "private"
}
return filepath.Join(s.user(email), fileName+".key")
}
// readFile abstracts a simple ioutil.ReadFile, making sure to return an
// ErrNotExist instance when the file is not found.
func (s FileStorage) readFile(file string) ([]byte, error) {
b, err := ioutil.ReadFile(file)
if os.IsNotExist(err) {
return nil, ErrNotExist(err)
}
return b, err
}
// SiteExists implements Storage.SiteExists by checking for the presence of
// cert and key files.
func (s FileStorage) SiteExists(domain string) (bool, error) {
_, err := os.Stat(s.siteCertFile(domain))
if os.IsNotExist(err) {
return false, nil
} else if err != nil {
return false, err
}
_, err = os.Stat(s.siteKeyFile(domain))
if err != nil {
return false, err
}
return true, nil
}
// LoadSite implements Storage.LoadSite by loading it from disk. If it is not
// present, an instance of ErrNotExist is returned.
func (s FileStorage) LoadSite(domain string) (*SiteData, error) {
var err error
siteData := new(SiteData)
siteData.Cert, err = s.readFile(s.siteCertFile(domain))
if err != nil {
return nil, err
}
siteData.Key, err = s.readFile(s.siteKeyFile(domain))
if err != nil {
return nil, err
}
siteData.Meta, err = s.readFile(s.siteMetaFile(domain))
if err != nil {
return nil, err
}
return siteData, nil
}
// StoreSite implements Storage.StoreSite by writing it to disk. The base
// directories needed for the file are automatically created as needed.
func (s FileStorage) StoreSite(domain string, data *SiteData) error {
err := os.MkdirAll(s.site(domain), 0700)
if err != nil {
return fmt.Errorf("making site directory: %v", err)
}
err = ioutil.WriteFile(s.siteCertFile(domain), data.Cert, 0600)
if err != nil {
return fmt.Errorf("writing certificate file: %v", err)
}
err = ioutil.WriteFile(s.siteKeyFile(domain), data.Key, 0600)
if err != nil {
return fmt.Errorf("writing key file: %v", err)
}
err = ioutil.WriteFile(s.siteMetaFile(domain), data.Meta, 0600)
if err != nil {
return fmt.Errorf("writing cert meta file: %v", err)
}
return nil
}
// DeleteSite implements Storage.DeleteSite by deleting just the cert from
// disk. If it is not present, an instance of ErrNotExist is returned.
func (s FileStorage) DeleteSite(domain string) error {
err := os.Remove(s.siteCertFile(domain))
if err != nil {
if os.IsNotExist(err) {
return ErrNotExist(err)
}
return err
}
return nil
}
// LockRegister implements Storage.LockRegister by just returning true because
// it is not a multi-server storage implementation.
func (s FileStorage) LockRegister(domain string) (bool, error) {
return true, nil
}
// UnlockRegister implements Storage.UnlockRegister as a no-op because it is
// not a multi-server storage implementation.
func (s FileStorage) UnlockRegister(domain string) error {
return nil
}
// LoadUser implements Storage.LoadUser by loading it from disk. If it is not
// present, an instance of ErrNotExist is returned.
func (s FileStorage) LoadUser(email string) (*UserData, error) {
var err error
userData := new(UserData)
userData.Reg, err = s.readFile(s.userRegFile(email))
if err != nil {
return nil, err
}
userData.Key, err = s.readFile(s.userKeyFile(email))
if err != nil {
return nil, err
}
return userData, nil
}
// StoreUser implements Storage.StoreUser by writing it to disk. The base
// directories needed for the file are automatically created as needed.
func (s FileStorage) StoreUser(email string, data *UserData) error {
err := os.MkdirAll(s.user(email), 0700)
if err != nil {
return fmt.Errorf("making user directory: %v", err)
}
err = ioutil.WriteFile(s.userRegFile(email), data.Reg, 0600)
if err != nil {
return fmt.Errorf("writing user registration file: %v", err)
}
err = ioutil.WriteFile(s.userKeyFile(email), data.Key, 0600)
if err != nil {
return fmt.Errorf("writing user key file: %v", err)
}
return nil
}
// MostRecentUserEmail implements Storage.MostRecentUserEmail by finding the
// most recently written sub directory in the users' directory. It is named
// after the email address. This corresponds to the most recent call to
// StoreUser.
func (s FileStorage) MostRecentUserEmail() string {
userDirs, err := ioutil.ReadDir(s.users())
if err != nil {
return ""
}
var mostRecent os.FileInfo
for _, dir := range userDirs {
if !dir.IsDir() {
continue
}
if mostRecent == nil || dir.ModTime().After(mostRecent.ModTime()) {
mostRecent = dir
}
}
if mostRecent != nil {
return mostRecent.Name()
}
return ""
}