// 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 }