lego/providers/http/frostfs/frostfs.go
Vitaliy Potyarkin 597d147c7d frostfs: Reject tokens with slash character
Current reverse proxy configs assume that token is a valid filename
with no nesting levels. It's better to reject unsupported tokens early

Signed-off-by: Vitaliy Potyarkin <v.potyarkin@yadro.com>
2024-10-16 17:16:35 +03:00

85 lines
2.1 KiB
Go

// Package frostfs provides HTTP-01 solver that saves challenge token to
// FrostFS to make it available to multiple hosts at once.
// Useful for deploying FrostFS gateways (HTTP or S3)
package frostfs
import (
"context"
"errors"
"fmt"
"strconv"
"strings"
"time"
"github.com/go-acme/lego/v4/challenge"
)
const (
// Challenge token will be garbage collected sometime after this interval
// even if Cleanup() call fails for whatever reason.
tokenLifetime = 1 * time.Hour
)
// HTTPProvider is a custom solver for HTTP-01 challenge that saves token to FrostFS.
type HTTPProvider struct {
frostfs *Storage
oid string
}
var _ challenge.Provider = new(HTTPProvider)
func NewHTTPProvider(endpoint, cid, walletPath, walletAccount, walletPassword string) (*HTTPProvider, error) {
if endpoint == "" {
return nil, errors.New("empty endpoint")
}
if cid == "" {
return nil, errors.New("empty container id")
}
key, err := getKey(walletPath, walletAccount, walletPassword)
if err != nil {
return nil, err
}
storage, err := Open(endpoint, cid, key)
if err != nil {
return nil, err
}
return &HTTPProvider{frostfs: storage}, nil
}
func (w *HTTPProvider) Present(domain, token, keyAuth string) error {
if strings.Contains(token, "/") {
return fmt.Errorf("token with slash character is not supported: %s", token)
}
if w.oid != "" {
return fmt.Errorf("%T is not safe to re-enter: object was saved and not yet cleaned up: %s", w, w.oid)
}
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
var err error
expires, err := w.frostfs.Epoch(ctx, time.Now().Add(tokenLifetime))
if err != nil {
return fmt.Errorf("failed to calculate token expiration: %w", err)
}
w.oid, err = w.frostfs.Save(
ctx,
[]byte(keyAuth),
"FileName", token,
"ACME", token,
"__SYSTEM__EXPIRATION_EPOCH", strconv.FormatUint(expires, 10),
)
return err
}
func (w *HTTPProvider) CleanUp(domain, token, keyAuth string) error {
if w.oid == "" {
panic("Cleanup() called before Present()")
}
err := w.frostfs.Delete(context.TODO(), w.oid)
if err != nil {
return err
}
w.oid = ""
return nil
}