forked from TrueCloudLab/frostfs-s3-gw
Denis Kirillov
dce34ddb9b
After using AddChain to provide access to container we have to wait: * tx with APE chain be accepted by blockchain * cache in storage node be updated it takes a while. So we add retry (the same as when we add bucket settings during bucket creation) Signed-off-by: Denis Kirillov <d.kirillov@yadro.com>
659 lines
20 KiB
Go
659 lines
20 KiB
Go
package authmate
|
|
|
|
import (
|
|
"context"
|
|
"crypto/ecdsa"
|
|
"encoding/hex"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"time"
|
|
|
|
"git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/acl"
|
|
sessionv2 "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/session"
|
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/cache"
|
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/handler"
|
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/layer/frostfs"
|
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/creds/accessbox"
|
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/creds/tokens"
|
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/internal/logs"
|
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/pkg/retryer"
|
|
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/bearer"
|
|
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
|
|
frostfsecdsa "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/crypto/ecdsa"
|
|
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/netmap"
|
|
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object"
|
|
oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id"
|
|
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/session"
|
|
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/user"
|
|
"github.com/aws/aws-sdk-go-v2/aws"
|
|
"github.com/aws/aws-sdk-go-v2/aws/retry"
|
|
"github.com/google/uuid"
|
|
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
|
"go.uber.org/zap"
|
|
)
|
|
|
|
// PrmContainerCreate groups parameters of containers created by authmate.
|
|
type PrmContainerCreate struct {
|
|
// FrostFS identifier of the container creator.
|
|
Owner *keys.PublicKey
|
|
|
|
// Container placement policy.
|
|
Policy netmap.PlacementPolicy
|
|
|
|
// Friendly name for the container (optional).
|
|
FriendlyName string
|
|
}
|
|
|
|
// NetworkState represents FrostFS network state which is needed for authmate processing.
|
|
type NetworkState struct {
|
|
// Current FrostFS time.
|
|
Epoch uint64
|
|
// Duration of the Morph chain block in ms.
|
|
BlockDuration int64
|
|
// Duration of the FrostFS epoch in Morph chain blocks.
|
|
EpochDuration uint64
|
|
}
|
|
|
|
// FrostFS represents virtual connection to FrostFS network.
|
|
type FrostFS interface {
|
|
// FrostFS interface required by credential tool.
|
|
tokens.FrostFS
|
|
|
|
// ContainerExists checks container presence in FrostFS by identifier.
|
|
// Returns nil if container exists.
|
|
ContainerExists(context.Context, cid.ID) error
|
|
|
|
// CreateContainer creates and saves parameterized container in FrostFS.
|
|
// It sets 'Timestamp' attribute to the current time.
|
|
// It returns the ID of the saved container.
|
|
//
|
|
// The container must be private with GET access for OTHERS group.
|
|
// Creation time should also be stamped.
|
|
//
|
|
// It returns exactly one non-nil value. It returns any error encountered which
|
|
// prevented the container from being created.
|
|
CreateContainer(context.Context, PrmContainerCreate) (cid.ID, error)
|
|
|
|
// TimeToEpoch computes the current epoch and the epoch that corresponds to the provided time.
|
|
// Note:
|
|
// * time must be in the future
|
|
// * time will be ceil rounded to match epoch
|
|
//
|
|
// It returns any error encountered which prevented computing epochs.
|
|
TimeToEpoch(context.Context, time.Time) (uint64, uint64, error)
|
|
}
|
|
|
|
// Agent contains client communicating with FrostFS and logger.
|
|
type Agent struct {
|
|
frostFS FrostFS
|
|
log *zap.Logger
|
|
cfg *config
|
|
}
|
|
|
|
type config struct {
|
|
RetryMaxAttempts int
|
|
RetryMaxBackoff time.Duration
|
|
RetryStrategy handler.RetryStrategy
|
|
}
|
|
|
|
func defaultConfig() *config {
|
|
return &config{
|
|
RetryMaxAttempts: 4,
|
|
RetryMaxBackoff: 30 * time.Second,
|
|
RetryStrategy: handler.RetryStrategyExponential,
|
|
}
|
|
}
|
|
|
|
type Option func(cfg *config)
|
|
|
|
func WithRetryMaxAttempts(attempts int) func(*config) {
|
|
return func(cfg *config) {
|
|
cfg.RetryMaxAttempts = attempts
|
|
}
|
|
}
|
|
|
|
func WithRetryMaxBackoff(backoff time.Duration) func(*config) {
|
|
return func(cfg *config) {
|
|
cfg.RetryMaxBackoff = backoff
|
|
}
|
|
}
|
|
|
|
func WithRetryStrategy(strategy handler.RetryStrategy) func(*config) {
|
|
return func(cfg *config) {
|
|
cfg.RetryStrategy = strategy
|
|
}
|
|
}
|
|
|
|
// New creates an object of type Agent that consists of Client and logger.
|
|
func New(log *zap.Logger, frostFS FrostFS, options ...Option) *Agent {
|
|
cfg := defaultConfig()
|
|
|
|
for _, opt := range options {
|
|
opt(cfg)
|
|
}
|
|
|
|
return &Agent{
|
|
log: log,
|
|
frostFS: frostFS,
|
|
cfg: cfg,
|
|
}
|
|
}
|
|
|
|
type (
|
|
// ContainerPolicies contains mapping of aws LocationConstraint to frostfs PlacementPolicy.
|
|
ContainerPolicies map[string]string
|
|
|
|
// IssueSecretOptions contains options for passing to Agent.IssueSecret method.
|
|
IssueSecretOptions struct {
|
|
Container cid.ID
|
|
AccessKeyID string
|
|
SecretAccessKey string
|
|
FrostFSKey *keys.PrivateKey
|
|
GatesPublicKeys []*keys.PublicKey
|
|
Impersonate bool
|
|
SessionTokenRules []byte
|
|
SkipSessionRules bool
|
|
Lifetime time.Duration
|
|
AwsCliCredentialsFile string
|
|
ContainerPolicies ContainerPolicies
|
|
CustomAttributes []object.Attribute
|
|
}
|
|
|
|
// UpdateSecretOptions contains options for passing to Agent.UpdateSecret method.
|
|
UpdateSecretOptions struct {
|
|
FrostFSKey *keys.PrivateKey
|
|
GatesPublicKeys []*keys.PublicKey
|
|
IsCustom bool
|
|
AccessKeyID string
|
|
ContainerID cid.ID
|
|
GatePrivateKey *keys.PrivateKey
|
|
CustomAttributes []object.Attribute
|
|
}
|
|
|
|
tokenUpdateOptions struct {
|
|
frostFSKey *keys.PrivateKey
|
|
gatesPublicKeys []*keys.PublicKey
|
|
lifetime lifetimeOptions
|
|
box *accessbox.Box
|
|
}
|
|
|
|
// ContainerOptions groups parameters of auth container to put the secret into.
|
|
ContainerOptions struct {
|
|
ID cid.ID
|
|
FriendlyName string
|
|
PlacementPolicy string
|
|
}
|
|
|
|
// UpdateOptions groups parameters to update existing the secret into.
|
|
UpdateOptions struct {
|
|
Address oid.Address
|
|
SecretAccessKey []byte
|
|
}
|
|
|
|
// ObtainSecretOptions contains options for passing to Agent.ObtainSecret method.
|
|
ObtainSecretOptions struct {
|
|
Container cid.ID
|
|
AccessKeyID string
|
|
GatePrivateKey *keys.PrivateKey
|
|
}
|
|
)
|
|
|
|
// lifetimeOptions holds FrostFS epochs, iat -- epoch which the token was issued at, exp -- epoch when the token expires.
|
|
type lifetimeOptions struct {
|
|
Iat uint64
|
|
Exp uint64
|
|
}
|
|
|
|
type (
|
|
issuingResult struct {
|
|
InitialAccessKeyID string `json:"initial_access_key_id"`
|
|
AccessKeyID string `json:"access_key_id"`
|
|
SecretAccessKey string `json:"secret_access_key"`
|
|
OwnerPrivateKey string `json:"owner_private_key"`
|
|
WalletPublicKey string `json:"wallet_public_key"`
|
|
ContainerID string `json:"container_id"`
|
|
}
|
|
|
|
obtainingResult struct {
|
|
BearerToken *bearer.Token `json:"bearer_token"`
|
|
SecretAccessKey string `json:"secret_access_key"`
|
|
}
|
|
)
|
|
|
|
func (a *Agent) checkContainer(ctx context.Context, cnrID cid.ID) error {
|
|
a.log.Info(logs.CheckContainer, zap.Stringer("cid", cnrID))
|
|
return a.frostFS.ContainerExists(ctx, cnrID)
|
|
}
|
|
|
|
func checkPolicy(policyString string) (*netmap.PlacementPolicy, error) {
|
|
var result netmap.PlacementPolicy
|
|
|
|
err := result.DecodeString(policyString)
|
|
if err == nil {
|
|
return &result, nil
|
|
}
|
|
|
|
if err = result.UnmarshalJSON([]byte(policyString)); err == nil {
|
|
return &result, nil
|
|
}
|
|
|
|
return nil, errors.New("can't parse placement policy")
|
|
}
|
|
|
|
func preparePolicy(policy ContainerPolicies) ([]*accessbox.AccessBox_ContainerPolicy, error) {
|
|
if policy == nil {
|
|
return nil, nil
|
|
}
|
|
|
|
var result []*accessbox.AccessBox_ContainerPolicy
|
|
for locationConstraint, placementPolicy := range policy {
|
|
parsedPolicy, err := checkPolicy(placementPolicy)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("check placement policy: %w", err)
|
|
}
|
|
|
|
result = append(result, &accessbox.AccessBox_ContainerPolicy{
|
|
LocationConstraint: locationConstraint,
|
|
Policy: parsedPolicy.Marshal(),
|
|
})
|
|
}
|
|
|
|
return result, nil
|
|
}
|
|
|
|
// IssueSecret creates an auth token, puts it in the FrostFS network and writes to io.Writer a new secret access key.
|
|
func (a *Agent) IssueSecret(ctx context.Context, w io.Writer, options *IssueSecretOptions) error {
|
|
var (
|
|
err error
|
|
box *accessbox.AccessBox
|
|
lifetime lifetimeOptions
|
|
)
|
|
|
|
policies, err := preparePolicy(options.ContainerPolicies)
|
|
if err != nil {
|
|
return fmt.Errorf("prepare policies: %w", err)
|
|
}
|
|
|
|
lifetime.Iat, lifetime.Exp, err = a.frostFS.TimeToEpoch(ctx, time.Now().Add(options.Lifetime))
|
|
if err != nil {
|
|
return fmt.Errorf("fetch time to epoch: %w", err)
|
|
}
|
|
|
|
gatesData, err := createTokens(options, lifetime)
|
|
if err != nil {
|
|
return fmt.Errorf("create tokens: %w", err)
|
|
}
|
|
|
|
var secret []byte
|
|
isCustom := options.AccessKeyID != ""
|
|
if isCustom {
|
|
secret = []byte(options.SecretAccessKey)
|
|
}
|
|
box, secrets, err := accessbox.PackTokens(gatesData, secret, isCustom)
|
|
if err != nil {
|
|
return fmt.Errorf("pack tokens: %w", err)
|
|
}
|
|
|
|
box.ContainerPolicy = policies
|
|
|
|
if err = a.checkContainer(ctx, options.Container); err != nil {
|
|
return fmt.Errorf("check container: %w", err)
|
|
}
|
|
|
|
var idOwner user.ID
|
|
user.IDFromKey(&idOwner, options.FrostFSKey.PrivateKey.PublicKey)
|
|
a.log.Info(logs.StoreBearerTokenIntoFrostFS,
|
|
zap.Stringer("owner_tkn", idOwner))
|
|
|
|
cfg := tokens.Config{
|
|
FrostFS: a.frostFS,
|
|
Key: secrets.EphemeralKey,
|
|
CacheConfig: cache.DefaultAccessBoxConfig(a.log),
|
|
}
|
|
|
|
creds := tokens.New(cfg)
|
|
|
|
prm := tokens.CredentialsParam{
|
|
Container: options.Container,
|
|
AccessKeyID: options.AccessKeyID,
|
|
AccessBox: box,
|
|
Expiration: lifetime.Exp,
|
|
Keys: options.GatesPublicKeys,
|
|
CustomAttributes: options.CustomAttributes,
|
|
}
|
|
|
|
var addr oid.Address
|
|
err = retryer.MakeWithRetry(ctx, func() error {
|
|
var inErr error
|
|
addr, inErr = creds.Put(ctx, prm)
|
|
return inErr
|
|
}, a.credsPutRetryer())
|
|
|
|
if err != nil {
|
|
return fmt.Errorf("failed to put creds: %w", err)
|
|
}
|
|
|
|
accessKeyID := options.AccessKeyID
|
|
if accessKeyID == "" {
|
|
accessKeyID = accessKeyIDFromAddr(addr)
|
|
}
|
|
|
|
ir := &issuingResult{
|
|
InitialAccessKeyID: accessKeyID,
|
|
AccessKeyID: accessKeyID,
|
|
SecretAccessKey: secrets.SecretKey,
|
|
OwnerPrivateKey: hex.EncodeToString(secrets.EphemeralKey.Bytes()),
|
|
WalletPublicKey: hex.EncodeToString(options.FrostFSKey.PublicKey().Bytes()),
|
|
ContainerID: options.Container.EncodeToString(),
|
|
}
|
|
|
|
enc := json.NewEncoder(w)
|
|
enc.SetIndent("", " ")
|
|
if err = enc.Encode(ir); err != nil {
|
|
return err
|
|
}
|
|
|
|
if options.AwsCliCredentialsFile != "" {
|
|
profileName := "authmate_cred_" + addr.Object().EncodeToString()
|
|
if _, err = os.Stat(options.AwsCliCredentialsFile); os.IsNotExist(err) {
|
|
profileName = "default"
|
|
}
|
|
file, err := os.OpenFile(options.AwsCliCredentialsFile, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0600)
|
|
if err != nil {
|
|
return fmt.Errorf("couldn't open aws cli credentials file: %w", err)
|
|
}
|
|
defer file.Close()
|
|
if _, err = file.WriteString(fmt.Sprintf("\n[%s]\naws_access_key_id = %s\naws_secret_access_key = %s\n",
|
|
profileName, accessKeyID, secrets.SecretKey)); err != nil {
|
|
return fmt.Errorf("fails to write to file: %w", err)
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// UpdateSecret updates an auth token (change list of gates that can use credential), puts new cred version to the FrostFS network and writes to io.Writer a result.
|
|
func (a *Agent) UpdateSecret(ctx context.Context, w io.Writer, options *UpdateSecretOptions) error {
|
|
cfg := tokens.Config{
|
|
FrostFS: a.frostFS,
|
|
Key: options.GatePrivateKey,
|
|
CacheConfig: cache.DefaultAccessBoxConfig(a.log),
|
|
}
|
|
|
|
creds := tokens.New(cfg)
|
|
|
|
box, _, err := creds.GetBox(ctx, options.ContainerID, options.AccessKeyID)
|
|
if err != nil {
|
|
return fmt.Errorf("get accessbox: %w", err)
|
|
}
|
|
|
|
var secret []byte
|
|
if options.IsCustom {
|
|
secret = []byte(box.Gate.SecretKey)
|
|
} else if secret, err = hex.DecodeString(box.Gate.SecretKey); err != nil {
|
|
return fmt.Errorf("failed to decode secret key access box: %w", err)
|
|
}
|
|
|
|
lifetime := getLifetimeFromGateData(box.Gate)
|
|
tokenOptions := tokenUpdateOptions{
|
|
frostFSKey: options.FrostFSKey,
|
|
gatesPublicKeys: options.GatesPublicKeys,
|
|
lifetime: lifetime,
|
|
box: box,
|
|
}
|
|
|
|
gatesData, err := formTokensToUpdate(tokenOptions)
|
|
if err != nil {
|
|
return fmt.Errorf("create tokens: %w", err)
|
|
}
|
|
|
|
updatedBox, secrets, err := accessbox.PackTokens(gatesData, secret, options.IsCustom)
|
|
if err != nil {
|
|
return fmt.Errorf("pack tokens: %w", err)
|
|
}
|
|
|
|
var idOwner user.ID
|
|
user.IDFromKey(&idOwner, options.FrostFSKey.PrivateKey.PublicKey)
|
|
a.log.Info(logs.UpdateAccessCredObjectIntoFrostFS,
|
|
zap.Stringer("owner_tkn", idOwner))
|
|
|
|
prm := tokens.CredentialsParam{
|
|
Container: options.ContainerID,
|
|
AccessBox: updatedBox,
|
|
Expiration: lifetime.Exp,
|
|
Keys: options.GatesPublicKeys,
|
|
CustomAttributes: options.CustomAttributes,
|
|
}
|
|
|
|
addr, err := creds.Update(ctx, prm)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to update creds: %w", err)
|
|
}
|
|
|
|
accessKeyID := options.AccessKeyID
|
|
if !options.IsCustom {
|
|
accessKeyID = accessKeyIDFromAddr(addr)
|
|
}
|
|
|
|
ir := &issuingResult{
|
|
AccessKeyID: accessKeyID,
|
|
InitialAccessKeyID: options.AccessKeyID,
|
|
SecretAccessKey: secrets.SecretKey,
|
|
OwnerPrivateKey: hex.EncodeToString(secrets.EphemeralKey.Bytes()),
|
|
WalletPublicKey: hex.EncodeToString(options.FrostFSKey.PublicKey().Bytes()),
|
|
ContainerID: addr.Container().EncodeToString(),
|
|
}
|
|
|
|
enc := json.NewEncoder(w)
|
|
enc.SetIndent("", " ")
|
|
return enc.Encode(ir)
|
|
}
|
|
|
|
func getLifetimeFromGateData(gateData *accessbox.GateData) lifetimeOptions {
|
|
var btokenv2 acl.BearerToken
|
|
gateData.BearerToken.WriteToV2(&btokenv2)
|
|
|
|
return lifetimeOptions{
|
|
Iat: btokenv2.GetBody().GetLifetime().GetIat(),
|
|
Exp: btokenv2.GetBody().GetLifetime().GetExp(),
|
|
}
|
|
}
|
|
|
|
// ObtainSecret receives an existing secret access key from FrostFS and
|
|
// writes to io.Writer the secret access key.
|
|
func (a *Agent) ObtainSecret(ctx context.Context, w io.Writer, options *ObtainSecretOptions) error {
|
|
cfg := tokens.Config{
|
|
FrostFS: a.frostFS,
|
|
Key: options.GatePrivateKey,
|
|
CacheConfig: cache.DefaultAccessBoxConfig(a.log),
|
|
}
|
|
|
|
bearerCreds := tokens.New(cfg)
|
|
|
|
box, _, err := bearerCreds.GetBox(ctx, options.Container, options.AccessKeyID)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to get tokens: %w", err)
|
|
}
|
|
|
|
or := &obtainingResult{
|
|
BearerToken: box.Gate.BearerToken,
|
|
SecretAccessKey: box.Gate.SecretKey,
|
|
}
|
|
|
|
enc := json.NewEncoder(w)
|
|
enc.SetIndent("", " ")
|
|
return enc.Encode(or)
|
|
}
|
|
|
|
func (a *Agent) credsPutRetryer() aws.RetryerV2 {
|
|
return retry.NewStandard(func(options *retry.StandardOptions) {
|
|
options.MaxAttempts = a.cfg.RetryMaxAttempts
|
|
options.MaxBackoff = a.cfg.RetryMaxBackoff
|
|
if a.cfg.RetryStrategy == handler.RetryStrategyExponential {
|
|
options.Backoff = retry.NewExponentialJitterBackoff(options.MaxBackoff)
|
|
} else {
|
|
options.Backoff = retry.BackoffDelayerFunc(func(int, error) (time.Duration, error) {
|
|
return options.MaxBackoff, nil
|
|
})
|
|
}
|
|
|
|
options.Retryables = []retry.IsErrorRetryable{retry.IsErrorRetryableFunc(func(err error) aws.Ternary {
|
|
if errors.Is(err, frostfs.ErrAccessDenied) {
|
|
return aws.TrueTernary
|
|
}
|
|
return aws.FalseTernary
|
|
})}
|
|
})
|
|
}
|
|
|
|
func buildBearerToken(key *keys.PrivateKey, impersonate bool, lifetime lifetimeOptions, gateKey *keys.PublicKey) (*bearer.Token, error) {
|
|
var ownerID user.ID
|
|
user.IDFromKey(&ownerID, (ecdsa.PublicKey)(*gateKey))
|
|
|
|
var bearerToken bearer.Token
|
|
bearerToken.ForUser(ownerID)
|
|
bearerToken.SetExp(lifetime.Exp)
|
|
bearerToken.SetIat(lifetime.Iat)
|
|
bearerToken.SetNbf(lifetime.Iat)
|
|
bearerToken.SetImpersonate(impersonate)
|
|
|
|
err := bearerToken.Sign(key.PrivateKey)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("sign bearer token: %w", err)
|
|
}
|
|
|
|
return &bearerToken, nil
|
|
}
|
|
|
|
func buildBearerTokens(key *keys.PrivateKey, impersonate bool, lifetime lifetimeOptions, gatesKeys []*keys.PublicKey) ([]*bearer.Token, error) {
|
|
bearerTokens := make([]*bearer.Token, 0, len(gatesKeys))
|
|
for _, gateKey := range gatesKeys {
|
|
tkn, err := buildBearerToken(key, impersonate, lifetime, gateKey)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("build bearer token: %w", err)
|
|
}
|
|
bearerTokens = append(bearerTokens, tkn)
|
|
}
|
|
return bearerTokens, nil
|
|
}
|
|
|
|
func buildSessionToken(key *keys.PrivateKey, lifetime lifetimeOptions, ctx sessionTokenContext, gateKey *keys.PublicKey) (*session.Container, error) {
|
|
tok := new(session.Container)
|
|
tok.ForVerb(ctx.verb)
|
|
if !ctx.containerID.Equals(cid.ID{}) {
|
|
tok.ApplyOnlyTo(ctx.containerID)
|
|
}
|
|
|
|
tok.SetID(uuid.New())
|
|
tok.SetAuthKey((*frostfsecdsa.PublicKey)(gateKey))
|
|
|
|
tok.SetIat(lifetime.Iat)
|
|
tok.SetNbf(lifetime.Iat)
|
|
tok.SetExp(lifetime.Exp)
|
|
|
|
return tok, tok.Sign(key.PrivateKey)
|
|
}
|
|
|
|
func buildSessionTokens(key *keys.PrivateKey, lifetime lifetimeOptions, ctxs []sessionTokenContext, gatesKeys []*keys.PublicKey) ([][]*session.Container, error) {
|
|
sessionTokens := make([][]*session.Container, 0, len(gatesKeys))
|
|
for _, gateKey := range gatesKeys {
|
|
tkns := make([]*session.Container, len(ctxs))
|
|
for i, ctx := range ctxs {
|
|
tkn, err := buildSessionToken(key, lifetime, ctx, gateKey)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("build session token: %w", err)
|
|
}
|
|
tkns[i] = tkn
|
|
}
|
|
sessionTokens = append(sessionTokens, tkns)
|
|
}
|
|
return sessionTokens, nil
|
|
}
|
|
|
|
func createTokens(options *IssueSecretOptions, lifetime lifetimeOptions) ([]*accessbox.GateData, error) {
|
|
gates := make([]*accessbox.GateData, len(options.GatesPublicKeys))
|
|
|
|
bearerTokens, err := buildBearerTokens(options.FrostFSKey, options.Impersonate, lifetime, options.GatesPublicKeys)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to build bearer tokens: %w", err)
|
|
}
|
|
for i, gateKey := range options.GatesPublicKeys {
|
|
gates[i] = accessbox.NewGateData(gateKey, bearerTokens[i])
|
|
}
|
|
|
|
if !options.SkipSessionRules {
|
|
sessionRules, err := buildContext(options.SessionTokenRules)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to build context for session token: %w", err)
|
|
}
|
|
|
|
sessionTokens, err := buildSessionTokens(options.FrostFSKey, lifetime, sessionRules, options.GatesPublicKeys)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to biuild session token: %w", err)
|
|
}
|
|
for i, sessionTkns := range sessionTokens {
|
|
gates[i].SessionTokens = sessionTkns
|
|
}
|
|
}
|
|
|
|
return gates, nil
|
|
}
|
|
|
|
func formTokensToUpdate(options tokenUpdateOptions) ([]*accessbox.GateData, error) {
|
|
btoken := options.box.Gate.BearerToken
|
|
|
|
btokenv2 := new(acl.BearerToken)
|
|
btoken.WriteToV2(btokenv2)
|
|
if btokenv2.GetBody().GetEACL() != nil {
|
|
return nil, errors.New("EACL table in bearer token isn't supported")
|
|
}
|
|
|
|
bearerTokens, err := buildBearerTokens(options.frostFSKey, btoken.Impersonate(), options.lifetime, options.gatesPublicKeys)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to build bearer tokens: %w", err)
|
|
}
|
|
|
|
gates := make([]*accessbox.GateData, len(options.gatesPublicKeys))
|
|
for i, gateKey := range options.gatesPublicKeys {
|
|
gates[i] = accessbox.NewGateData(gateKey, bearerTokens[i])
|
|
}
|
|
|
|
sessionRules := make([]sessionTokenContext, len(options.box.Gate.SessionTokens))
|
|
for i, token := range options.box.Gate.SessionTokens {
|
|
var stoken sessionv2.Token
|
|
token.WriteToV2(&stoken)
|
|
|
|
sessionCtx, ok := stoken.GetBody().GetContext().(*sessionv2.ContainerSessionContext)
|
|
if !ok {
|
|
return nil, fmt.Errorf("get context from session token: %w", err)
|
|
}
|
|
|
|
var cnrID cid.ID
|
|
if cnrIDv2 := sessionCtx.ContainerID(); cnrIDv2 != nil {
|
|
if err = cnrID.ReadFromV2(*cnrIDv2); err != nil {
|
|
return nil, fmt.Errorf("read from v2 container id: %w", err)
|
|
}
|
|
}
|
|
|
|
sessionRules[i] = sessionTokenContext{
|
|
verb: session.ContainerVerb(sessionCtx.Verb()),
|
|
containerID: cnrID,
|
|
}
|
|
}
|
|
|
|
sessionTokens, err := buildSessionTokens(options.frostFSKey, options.lifetime, sessionRules, options.gatesPublicKeys)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to biuild session token: %w", err)
|
|
}
|
|
for i, sessionTkns := range sessionTokens {
|
|
gates[i].SessionTokens = sessionTkns
|
|
}
|
|
|
|
return gates, nil
|
|
}
|
|
|
|
func accessKeyIDFromAddr(addr oid.Address) string {
|
|
return addr.Container().EncodeToString() + "0" + addr.Object().EncodeToString()
|
|
}
|