[#428] Update SDK

Includes:
- container removal fix
- new session token structure: authmate does not
  parse session context anymore, instead it is
  application defined flexible structure with
  container ID encoded in human-readable format

Signed-off-by: Alex Vanin <alexey@nspcc.ru>
This commit is contained in:
Alex Vanin 2022-05-04 15:29:11 +03:00 committed by Kira
parent 9d3e6f75be
commit 1c33f06bfe
17 changed files with 184 additions and 151 deletions

View file

@ -210,7 +210,7 @@ func (h *handler) PutBucketACLHandler(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
}
func (h *handler) updateBucketACL(r *http.Request, astChild *ast, bktInfo *data.BucketInfo, sessionToken *session.Token) error {
func (h *handler) updateBucketACL(r *http.Request, astChild *ast, bktInfo *data.BucketInfo, sessionToken *session.Container) error {
bucketACL, err := h.obj.GetBucketACL(r.Context(), bktInfo)
if err != nil {
return fmt.Errorf("could not get bucket eacl: %w", err)

View file

@ -346,7 +346,7 @@ func (h *handler) CompleteMultipartUploadHandler(w http.ResponseWriter, r *http.
}
var (
sessionTokenSetEACL *session.Token
sessionTokenSetEACL *session.Container
uploadID = r.URL.Query().Get(uploadIDHeaderName)
uploadInfo = &layer.UploadInfoParams{

View file

@ -176,7 +176,7 @@ func (h *handler) PutObjectHandler(w http.ResponseWriter, r *http.Request) {
var (
err error
newEaclTable *eacl.Table
sessionTokenEACL *session.Token
sessionTokenEACL *session.Container
containsACL = containsACLHeaders(r)
reqInfo = api.GetReqInfo(r.Context())
)
@ -286,7 +286,7 @@ func (h *handler) PostObject(w http.ResponseWriter, r *http.Request) {
var (
newEaclTable *eacl.Table
tagSet map[string]string
sessionTokenEACL *session.Token
sessionTokenEACL *session.Container
reqInfo = api.GetReqInfo(r.Context())
metadata = make(map[string]string)
containsACL = containsACLHeaders(r)

View file

@ -82,7 +82,7 @@ func parseRange(s string) (*layer.RangeParams, error) {
}, nil
}
func getSessionTokenSetEACL(ctx context.Context) (*session.Token, error) {
func getSessionTokenSetEACL(ctx context.Context) (*session.Container, error) {
boxData, err := layer.GetBoxData(ctx)
if err != nil {
return nil, err

View file

@ -187,7 +187,7 @@ func (n *layer) GetContainerEACL(ctx context.Context, idCnr *cid.ID) (*eacl.Tabl
}
func (n *layer) deleteContainer(ctx context.Context, idCnr *cid.ID) error {
var sessionToken *session.Token
var sessionToken *session.Container
boxData, err := GetBoxData(ctx)
if err == nil {
sessionToken = boxData.Gate.SessionTokenForDelete()

View file

@ -138,7 +138,7 @@ type (
ACL uint32
Policy *netmap.PlacementPolicy
EACL *eacl.Table
SessionToken *session.Token
SessionToken *session.Container
LocationConstraint string
ObjectLockEnabled bool
}
@ -627,7 +627,7 @@ func (n *layer) CreateBucket(ctx context.Context, p *CreateBucketParams) (*data.
return nil, err
}
if p.SessionToken != nil && bktInfo.Owner.Equals(*p.SessionToken.OwnerID()) {
if p.SessionToken != nil && p.SessionToken.IssuedBy(*bktInfo.Owner) {
return nil, errors.GetAPIError(errors.ErrBucketAlreadyOwnedByYou)
}

View file

@ -31,7 +31,7 @@ type PrmContainerCreate struct {
Name string
// Token of the container's creation session. Nil means session absence.
SessionToken *session.Token
SessionToken *session.Container
// Basic ACL of the container.
BasicACL acl.BasicACL
@ -176,7 +176,7 @@ type NeoFS interface {
// Successful return does not guarantee actual removal.
//
// It returns any error encountered which prevented the removal request from being sent.
DeleteContainer(context.Context, cid.ID, *session.Token) error
DeleteContainer(context.Context, cid.ID, *session.Container) error
// SelectObjects performs object selection from the NeoFS container according
// to the specified parameters. It selects user's objects only.

View file

@ -18,6 +18,7 @@ import (
"github.com/nspcc-dev/neofs-s3-gw/creds/tokens"
"github.com/nspcc-dev/neofs-sdk-go/bearer"
cid "github.com/nspcc-dev/neofs-sdk-go/container/id"
neofsecdsa "github.com/nspcc-dev/neofs-sdk-go/crypto/ecdsa"
"github.com/nspcc-dev/neofs-sdk-go/eacl"
"github.com/nspcc-dev/neofs-sdk-go/netmap"
"github.com/nspcc-dev/neofs-sdk-go/object/address"
@ -352,48 +353,6 @@ func restrictedRecords() (records []*eacl.Record) {
return
}
func buildContext(rules []byte) ([]*session.ContainerContext, error) {
var sessionCtxs []*session.ContainerContext
if len(rules) != 0 {
// cast ToV2 temporary, because there is no method for unmarshalling in ContainerContext in api-go
err := json.Unmarshal(rules, &sessionCtxs)
if err != nil {
return nil, fmt.Errorf("failed to unmarshal rules for session token: %w", err)
}
var (
containsPut = false
containsSetEACL = false
)
for _, s := range sessionCtxs {
if s.IsForPut() {
containsPut = true
} else if s.IsForSetEACL() {
containsSetEACL = true
}
}
if containsPut && !containsSetEACL {
ectx := session.NewContainerContext()
ectx.ForSetEACL()
sessionCtxs = append(sessionCtxs, ectx)
}
return sessionCtxs, nil
}
sessionCtxPut := session.NewContainerContext()
sessionCtxPut.ForPut()
sessionCtxDelete := session.NewContainerContext()
sessionCtxDelete.ForDelete()
sessionCtxEACL := session.NewContainerContext()
sessionCtxEACL.ForSetEACL()
return []*session.ContainerContext{sessionCtxPut, sessionCtxDelete, sessionCtxEACL}, nil
}
func buildBearerToken(key *keys.PrivateKey, table *eacl.Table, lifetime lifetimeOptions, gateKey *keys.PublicKey) (*bearer.Token, error) {
var ownerID user.ID
user.IDFromKey(&ownerID, (ecdsa.PublicKey)(*gateKey))
@ -420,30 +379,29 @@ func buildBearerTokens(key *keys.PrivateKey, table *eacl.Table, lifetime lifetim
return bearerTokens, nil
}
func buildSessionToken(key *keys.PrivateKey, oid *user.ID, lifetime lifetimeOptions, ctx *session.ContainerContext, gateKey *keys.PublicKey) (*session.Token, error) {
tok := session.NewToken()
tok.SetContext(ctx)
uid, err := uuid.New().MarshalBinary()
if err != nil {
return nil, err
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 != nil {
tok.AppliedTo(*ctx.containerID)
}
tok.SetID(uid)
tok.SetOwnerID(oid)
tok.SetSessionKey(gateKey.Bytes())
tok.SetID(uuid.New())
tok.SetAuthKey((*neofsecdsa.PublicKey)(gateKey))
tok.SetIat(lifetime.Iat)
tok.SetNbf(lifetime.Iat)
tok.SetExp(lifetime.Exp)
return tok, tok.Sign(&key.PrivateKey)
return tok, tok.Sign(key.PrivateKey)
}
func buildSessionTokens(key *keys.PrivateKey, oid *user.ID, lifetime lifetimeOptions, ctxs []*session.ContainerContext, gatesKeys []*keys.PublicKey) ([][]*session.Token, error) {
sessionTokens := make([][]*session.Token, 0, len(gatesKeys))
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.Token, len(ctxs))
tkns := make([]*session.Container, len(ctxs))
for i, ctx := range ctxs {
tkn, err := buildSessionToken(key, oid, lifetime, ctx, gateKey)
tkn, err := buildSessionToken(key, lifetime, ctx, gateKey)
if err != nil {
return nil, err
}
@ -475,10 +433,7 @@ func createTokens(options *IssueSecretOptions, lifetime lifetimeOptions) ([]*acc
return nil, fmt.Errorf("failed to build context for session token: %w", err)
}
var ownerID user.ID
user.IDFromKey(&ownerID, options.NeoFSKey.PrivateKey.PublicKey)
sessionTokens, err := buildSessionTokens(options.NeoFSKey, &ownerID, lifetime, sessionRules, options.GatesPublicKeys)
sessionTokens, err := buildSessionTokens(options.NeoFSKey, lifetime, sessionRules, options.GatesPublicKeys)
if err != nil {
return nil, fmt.Errorf("failed to biuild session token: %w", err)
}

View file

@ -1,39 +0,0 @@
package authmate
import (
"testing"
"github.com/stretchr/testify/require"
)
func TestContainerSessionRules(t *testing.T) {
jsonRules := []byte(`
[
{
"verb": "PUT",
"wildcard": true,
"containerID": null
},
{
"verb": "DELETE",
"wildcard": true,
"containerID": null
},
{
"verb": "SETEACL",
"wildcard": true,
"containerID": null
}
]`)
sessionContext, err := buildContext(jsonRules)
require.NoError(t, err)
require.Len(t, sessionContext, 3)
require.True(t, sessionContext[0].IsForPut())
require.Nil(t, sessionContext[0].Container())
require.True(t, sessionContext[1].IsForDelete())
require.Nil(t, sessionContext[1].Container())
require.True(t, sessionContext[2].IsForSetEACL())
require.Nil(t, sessionContext[2].Container())
}

View file

@ -0,0 +1,84 @@
package authmate
import (
"encoding/json"
"fmt"
apisession "github.com/nspcc-dev/neofs-api-go/v2/session"
cid "github.com/nspcc-dev/neofs-sdk-go/container/id"
"github.com/nspcc-dev/neofs-sdk-go/session"
)
type (
sessionTokenModel struct {
Verb string `json:"verb"`
ContainerID string `json:"ContainerID"`
}
sessionTokenContext struct {
verb session.ContainerVerb
containerID *cid.ID
}
)
func (c *sessionTokenContext) UnmarshalJSON(data []byte) (err error) {
var m sessionTokenModel
if err = json.Unmarshal(data, &m); err != nil {
return err
}
switch m.Verb {
case apisession.ContainerVerbPut.String():
c.verb = session.VerbContainerPut
case apisession.ContainerVerbSetEACL.String():
c.verb = session.VerbContainerSetEACL
case apisession.ContainerVerbDelete.String():
c.verb = session.VerbContainerDelete
default:
return fmt.Errorf("unknown container token verb %s", m.Verb)
}
if len(m.ContainerID) > 0 {
c.containerID = new(cid.ID)
return c.containerID.DecodeString(m.ContainerID)
}
return nil
}
func buildContext(rules []byte) ([]sessionTokenContext, error) {
var sessionCtxs []sessionTokenContext
if len(rules) != 0 {
err := json.Unmarshal(rules, &sessionCtxs)
if err != nil {
return nil, fmt.Errorf("failed to unmarshal rules for session token: %w", err)
}
var (
containsPut = false
containsSetEACL = false
)
for _, d := range sessionCtxs {
if d.verb == session.VerbContainerPut {
containsPut = true
} else if d.verb == session.VerbContainerSetEACL {
containsSetEACL = true
}
}
if containsPut && !containsSetEACL {
sessionCtxs = append(sessionCtxs, sessionTokenContext{
verb: session.VerbContainerSetEACL,
})
}
return sessionCtxs, nil
}
return []sessionTokenContext{
{verb: session.VerbContainerPut},
{verb: session.VerbContainerDelete},
{verb: session.VerbContainerSetEACL},
}, nil
}

View file

@ -0,0 +1,36 @@
package authmate
import (
"testing"
"github.com/nspcc-dev/neofs-sdk-go/session"
"github.com/stretchr/testify/require"
)
func TestContainerSessionRules(t *testing.T) {
jsonRules := []byte(`
[
{
"verb": "PUT",
"containerID": null
},
{
"verb": "DELETE",
"containerID": "6CcWg8LkcbfMUC8pt7wiy5zM1fyS3psNoxgfppcCgig1"
},
{
"verb": "SETEACL"
}
]`)
sessionContext, err := buildContext(jsonRules)
require.NoError(t, err)
require.Len(t, sessionContext, 3)
require.Equal(t, sessionContext[0].verb, session.VerbContainerPut)
require.Nil(t, sessionContext[0].containerID)
require.Equal(t, sessionContext[1].verb, session.VerbContainerDelete)
require.NotNil(t, sessionContext[1].containerID)
require.Equal(t, sessionContext[2].verb, session.VerbContainerSetEACL)
require.Nil(t, sessionContext[2].containerID)
}

View file

@ -11,7 +11,6 @@ import (
"io"
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
apisession "github.com/nspcc-dev/neofs-api-go/v2/session"
"github.com/nspcc-dev/neofs-sdk-go/bearer"
"github.com/nspcc-dev/neofs-sdk-go/netmap"
"github.com/nspcc-dev/neofs-sdk-go/session"
@ -36,7 +35,7 @@ type ContainerPolicy struct {
type GateData struct {
AccessKey string
BearerToken *bearer.Token
SessionTokens []*session.Token
SessionTokens []*session.Container
GateKey *keys.PublicKey
}
@ -46,36 +45,36 @@ func NewGateData(gateKey *keys.PublicKey, bearerTkn *bearer.Token) *GateData {
}
// SessionTokenForPut returns the first suitable container session context for PUT operation.
func (g *GateData) SessionTokenForPut() *session.Token {
return g.containerSessionToken(apisession.ContainerVerbPut)
func (g *GateData) SessionTokenForPut() *session.Container {
return g.containerSessionToken(session.VerbContainerPut)
}
// SessionTokenForDelete returns the first suitable container session context for DELETE operation.
func (g *GateData) SessionTokenForDelete() *session.Token {
return g.containerSessionToken(apisession.ContainerVerbDelete)
func (g *GateData) SessionTokenForDelete() *session.Container {
return g.containerSessionToken(session.VerbContainerDelete)
}
// SessionTokenForSetEACL returns the first suitable container session context for SetEACL operation.
func (g *GateData) SessionTokenForSetEACL() *session.Token {
return g.containerSessionToken(apisession.ContainerVerbSetEACL)
func (g *GateData) SessionTokenForSetEACL() *session.Container {
return g.containerSessionToken(session.VerbContainerSetEACL)
}
func (g *GateData) containerSessionToken(verb apisession.ContainerSessionVerb) *session.Token {
func (g *GateData) containerSessionToken(verb session.ContainerVerb) *session.Container {
for _, sessionToken := range g.SessionTokens {
switch ctx := sessionToken.Context().(type) {
case *session.ContainerContext:
if isAppropriateContainerContext(ctx, verb) {
if isAppropriateContainerContext(sessionToken, verb) {
return sessionToken
}
}
}
return nil
}
func isAppropriateContainerContext(ctx *session.ContainerContext, verb apisession.ContainerSessionVerb) bool {
return verb == apisession.ContainerVerbPut && ctx.IsForPut() ||
verb == apisession.ContainerVerbDelete && ctx.IsForDelete() ||
verb == apisession.ContainerVerbSetEACL && ctx.IsForSetEACL()
func isAppropriateContainerContext(tok *session.Container, verb session.ContainerVerb) bool {
switch verb {
case session.VerbContainerSetEACL, session.VerbContainerDelete, session.VerbContainerPut:
return tok.AssertVerb(verb)
default:
return false
}
}
// Secrets represents AccessKey and the key to encrypt gate tokens.
@ -179,11 +178,7 @@ func (x *AccessBox) addTokens(gatesData []*GateData, ephemeralKey *keys.PrivateK
encBearer := gate.BearerToken.Marshal()
encSessions := make([][]byte, len(gate.SessionTokens))
for i, sessionToken := range gate.SessionTokens {
encSession, err := sessionToken.Marshal()
if err != nil {
return fmt.Errorf("%w, sender = %d", err, i)
}
encSessions[i] = encSession
encSessions[i] = sessionToken.Marshal()
}
tokens := new(Tokens)
@ -232,9 +227,9 @@ func decodeGate(gate *AccessBox_Gate, owner *keys.PrivateKey, sender *keys.Publi
return nil, err
}
sessionTkns := make([]*session.Token, len(tokens.SessionTokens))
sessionTkns := make([]*session.Container, len(tokens.SessionTokens))
for i, encSessionToken := range tokens.SessionTokens {
sessionTkn := session.NewToken()
sessionTkn := new(session.Container)
if err := sessionTkn.Unmarshal(encSessionToken); err != nil {
return nil, err
}

View file

@ -6,6 +6,7 @@ import (
"github.com/google/uuid"
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
"github.com/nspcc-dev/neofs-sdk-go/bearer"
neofsecdsa "github.com/nspcc-dev/neofs-sdk-go/crypto/ecdsa"
"github.com/nspcc-dev/neofs-sdk-go/eacl"
"github.com/nspcc-dev/neofs-sdk-go/session"
"github.com/stretchr/testify/require"
@ -73,7 +74,7 @@ func Test_session_token_in_access_box(t *testing.T) {
var (
box *AccessBox
box2 AccessBox
tkn = session.NewToken()
tkn = new(session.Container)
)
sec, err := keys.NewPrivateKey()
@ -82,17 +83,13 @@ func Test_session_token_in_access_box(t *testing.T) {
cred, err := keys.NewPrivateKey()
require.NoError(t, err)
tok := session.NewToken()
tok.SetContext(session.NewContainerContext())
uid, err := uuid.New().MarshalBinary()
require.NoError(t, err)
tok.SetID(uid)
tok.SetSessionKey(sec.PublicKey().Bytes())
require.NoError(t, tkn.Sign(&sec.PrivateKey))
tkn.SetID(uuid.New())
tkn.SetAuthKey((*neofsecdsa.PublicKey)(sec.PublicKey()))
require.NoError(t, tkn.Sign(sec.PrivateKey))
var newTkn bearer.Token
gate := NewGateData(cred.PublicKey(), &newTkn)
gate.SessionTokens = []*session.Token{tkn}
gate.SessionTokens = []*session.Container{tkn}
box, _, err = PackTokens([]*GateData{gate})
require.NoError(t, err)
@ -105,7 +102,7 @@ func Test_session_token_in_access_box(t *testing.T) {
tkns, err := box2.GetTokens(cred)
require.NoError(t, err)
require.Equal(t, []*session.Token{tkn}, tkns.SessionTokens)
require.Equal(t, []*session.Container{tkn}, tkns.SessionTokens)
}
func Test_accessbox_multiple_keys(t *testing.T) {

View file

@ -203,22 +203,25 @@ where content of `session.json`:
[
{
"verb": "PUT",
"wildcard": true,
"containerID": null
},
{
"verb": "DELETE",
"wildcard": true,
"containerID": null
},
{
"verb": "SETEACL",
"wildcard": true,
"containerID": null
}
]
```
Available `verb` values: `PUT`, `DELETE`, `SETEACL`.
If `containerID` is `null` or omitted, then session token rule will be applied
to all containers. Otherwise, specify `containerID` value in human-redabale
format (base58 encoded string).
> **_NB!_** To create buckets in NeoFS it's necessary to have session tokens with `PUT` and `SETEACL` permissions, that's why
the authmate creates a `SETEACL` session token automatically in case when a user specified the token rule with `PUT` and
forgot about the rule with `SETEACL`.

2
go.mod
View file

@ -13,7 +13,7 @@ require (
github.com/nats-io/nats.go v1.13.1-0.20220121202836-972a071d373d
github.com/nspcc-dev/neo-go v0.98.2
github.com/nspcc-dev/neofs-api-go/v2 v2.12.1
github.com/nspcc-dev/neofs-sdk-go v1.0.0-rc.3.0.20220424111116-497053c785f5
github.com/nspcc-dev/neofs-sdk-go v1.0.0-rc.3.0.20220504192402-12ea1e8d740f
github.com/prometheus/client_golang v1.11.0
github.com/spf13/pflag v1.0.5
github.com/spf13/viper v1.7.1

6
go.sum
View file

@ -306,8 +306,10 @@ github.com/nspcc-dev/neofs-crypto v0.3.0 h1:zlr3pgoxuzrmGCxc5W8dGVfA9Rro8diFvVnB
github.com/nspcc-dev/neofs-crypto v0.3.0/go.mod h1:8w16GEJbH6791ktVqHN9YRNH3s9BEEKYxGhlFnp0cDw=
github.com/nspcc-dev/neofs-sdk-go v0.0.0-20211201182451-a5b61c4f6477/go.mod h1:dfMtQWmBHYpl9Dez23TGtIUKiFvCIxUZq/CkSIhEpz4=
github.com/nspcc-dev/neofs-sdk-go v0.0.0-20220113123743-7f3162110659/go.mod h1:/jay1lr3w7NQd/VDBkEhkJmDmyPNsu4W+QV2obsUV40=
github.com/nspcc-dev/neofs-sdk-go v1.0.0-rc.3.0.20220424111116-497053c785f5 h1:upiT6iVOy81tiY2x593E8+mxpb9BuW3fsvKFdqdXenk=
github.com/nspcc-dev/neofs-sdk-go v1.0.0-rc.3.0.20220424111116-497053c785f5/go.mod h1:u567oWTnAyGXbPWMrbcN0NB5zCPF+PqkaKg+vcijcho=
github.com/nspcc-dev/neofs-sdk-go v1.0.0-rc.3.0.20220504122248-539ac9915ed0 h1:tEwL29lFFX983YLUWc5rmnw5MK/z4id0KlBWuUboe+E=
github.com/nspcc-dev/neofs-sdk-go v1.0.0-rc.3.0.20220504122248-539ac9915ed0/go.mod h1:u567oWTnAyGXbPWMrbcN0NB5zCPF+PqkaKg+vcijcho=
github.com/nspcc-dev/neofs-sdk-go v1.0.0-rc.3.0.20220504192402-12ea1e8d740f h1:cMxSTUkugwaBFL7dEYirmjOEQ2p12phcpKO1Z3UhP64=
github.com/nspcc-dev/neofs-sdk-go v1.0.0-rc.3.0.20220504192402-12ea1e8d740f/go.mod h1:u567oWTnAyGXbPWMrbcN0NB5zCPF+PqkaKg+vcijcho=
github.com/nspcc-dev/rfc6979 v0.1.0/go.mod h1:exhIh1PdpDC5vQmyEsGvc4YDM/lyQp/452QxGq/UEso=
github.com/nspcc-dev/rfc6979 v0.2.0 h1:3e1WNxrN60/6N0DW7+UYisLeZJyfqZTNOjeV/toYvOE=
github.com/nspcc-dev/rfc6979 v0.2.0/go.mod h1:exhIh1PdpDC5vQmyEsGvc4YDM/lyQp/452QxGq/UEso=

View file

@ -197,7 +197,7 @@ func (x *NeoFS) ContainerEACL(ctx context.Context, id cid.ID) (*eacl.Table, erro
}
// DeleteContainer implements neofs.NeoFS interface method.
func (x *NeoFS) DeleteContainer(ctx context.Context, id cid.ID, token *session.Token) error {
func (x *NeoFS) DeleteContainer(ctx context.Context, id cid.ID, token *session.Container) error {
var prm pool.PrmContainerDelete
prm.SetContainerID(id)
prm.SetSessionToken(*token)