frostfs: Expire saved tokens automatically
Signed-off-by: Vitaliy Potyarkin <v.potyarkin@yadro.com>
This commit is contained in:
parent
9ff9d5be25
commit
61ce76f648
3 changed files with 97 additions and 1 deletions
|
@ -7,6 +7,7 @@ import (
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"math"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/client"
|
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/client"
|
||||||
|
@ -26,6 +27,7 @@ type Storage struct {
|
||||||
container containerid.ID
|
container containerid.ID
|
||||||
key *ecdsa.PrivateKey
|
key *ecdsa.PrivateKey
|
||||||
user user.ID
|
user user.ID
|
||||||
|
epoch epochCalc
|
||||||
}
|
}
|
||||||
|
|
||||||
const storageRequestTimeout = 10 * time.Second
|
const storageRequestTimeout = 10 * time.Second
|
||||||
|
@ -215,3 +217,59 @@ func getKey(walletPath, walletAccount, walletPassword string) (*ecdsa.PrivateKey
|
||||||
key := account.PrivateKey().PrivateKey
|
key := account.PrivateKey().PrivateKey
|
||||||
return &key, nil
|
return &key, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Epoch converts human time value into FrostFS epoch that is expected to be
|
||||||
|
// current at that time.
|
||||||
|
//
|
||||||
|
// Due to nonlinear nature of FrostFS time these calculations are approximate
|
||||||
|
// for the future and are likely wrong for the past.
|
||||||
|
func (s *Storage) Epoch(ctx context.Context, t time.Time) (epoch uint64, err error) {
|
||||||
|
if !s.epoch.Ready() {
|
||||||
|
var cancel context.CancelFunc
|
||||||
|
ctx, cancel = context.WithTimeout(ctx, storageRequestTimeout)
|
||||||
|
defer cancel()
|
||||||
|
c, err := s.dial(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return 0, fmt.Errorf("connecting to storage node: %w", err)
|
||||||
|
}
|
||||||
|
res, err := c.NetworkInfo(ctx, client.PrmNetworkInfo{})
|
||||||
|
if err != nil {
|
||||||
|
return 0, fmt.Errorf("network info request: %w", err)
|
||||||
|
}
|
||||||
|
stat := res.Status()
|
||||||
|
if !status.IsSuccessful(stat) {
|
||||||
|
return 0, fmt.Errorf("network info: %w", stat.(error))
|
||||||
|
}
|
||||||
|
info := res.Info()
|
||||||
|
s.epoch = epochCalc{
|
||||||
|
timestamp: time.Now(),
|
||||||
|
epoch: info.CurrentEpoch(),
|
||||||
|
blockPerEpoch: info.EpochDuration(),
|
||||||
|
msPerBlock: info.MsPerBlock(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !s.epoch.Ready() {
|
||||||
|
return 0, errors.New("failed to initialize epoch calculator")
|
||||||
|
}
|
||||||
|
return s.epoch.At(t), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type epochCalc struct {
|
||||||
|
timestamp time.Time
|
||||||
|
epoch uint64
|
||||||
|
msPerBlock int64
|
||||||
|
blockPerEpoch uint64
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e epochCalc) At(t time.Time) uint64 {
|
||||||
|
return uint64(
|
||||||
|
int64(e.epoch) +
|
||||||
|
int64(math.Ceil(
|
||||||
|
float64(t.Sub(e.timestamp).Milliseconds())/
|
||||||
|
float64(e.msPerBlock)/
|
||||||
|
float64(e.blockPerEpoch))))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e epochCalc) Ready() bool {
|
||||||
|
return !e.timestamp.IsZero() && e.epoch != 0 && e.msPerBlock != 0 && e.blockPerEpoch != 0
|
||||||
|
}
|
||||||
|
|
|
@ -10,6 +10,7 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"sync"
|
"sync"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/user"
|
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/user"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||||
|
@ -188,3 +189,23 @@ func TestLoadWalletAndSign(t *testing.T) {
|
||||||
t.Errorf("signature %x (%d bytes): check failed: wallet key", sig, len(sig))
|
t.Errorf("signature %x (%d bytes): check failed: wallet key", sig, len(sig))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Show epoch values for nearby times.
|
||||||
|
//
|
||||||
|
// This test is barely useful when run unattended: it will only validate "no
|
||||||
|
// errors, no panic" result.
|
||||||
|
// Human operator is required to check if epoch calculations actually make
|
||||||
|
// sense for network configuration being used.
|
||||||
|
func TestEpoch(t *testing.T) {
|
||||||
|
storage := openStorage(t)
|
||||||
|
now := time.Now()
|
||||||
|
var i time.Duration
|
||||||
|
for i = -10; i < 10; i++ {
|
||||||
|
timestamp := now.Add(i * time.Minute) //nolint:durationcheck
|
||||||
|
epoch, err := storage.Epoch(context.Background(), timestamp)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
t.Logf("%s: %d", timestamp, epoch)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -7,10 +7,18 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/go-acme/lego/v4/challenge"
|
"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.
|
// HTTPProvider is a custom solver for HTTP-01 challenge that saves token to FrostFS.
|
||||||
type HTTPProvider struct {
|
type HTTPProvider struct {
|
||||||
frostfs *Storage
|
frostfs *Storage
|
||||||
|
@ -42,11 +50,20 @@ func (w *HTTPProvider) Present(domain, token, keyAuth string) error {
|
||||||
if w.oid != "" {
|
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)
|
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()
|
||||||
|
|
||||||
|
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(
|
w.oid, err = w.frostfs.Save(
|
||||||
context.TODO(),
|
ctx,
|
||||||
[]byte(keyAuth),
|
[]byte(keyAuth),
|
||||||
"FileName", token,
|
"FileName", token,
|
||||||
"ACME", token,
|
"ACME", token,
|
||||||
|
"__SYSTEM__EXPIRATION_EPOCH", strconv.FormatUint(expires, 10),
|
||||||
)
|
)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue