[#XX] bearer: Add APE chains to Bearer token

Signed-off-by: Airat Arifullin <a.arifullin@yadro.com>
Airat Arifullin 2024-05-28 15:47:20 +03:00
parent c4d88876c4
commit a1656defb0
5 changed files with 191 additions and 5 deletions

View File

@ -6,7 +6,9 @@ import (
"fmt"
"git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/acl"
apeV2 "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/ape"
"git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/refs"
apeSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/ape"
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
frostfscrypto "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/crypto"
frostfsecdsa "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/crypto/ecdsa"
@ -33,9 +35,85 @@ type Token struct {
sigSet bool
sig refs.Signature
apeOverride *APEOverride
impersonate bool
}
// APEOverride is the list of APE chains defined for a target.
// These chains are meant to serve as overrides to the already defined (or even undefined)
// APE chains for the target (see contract `Policy`).
//
// The server-side processing of the bearer token with set APE overrides must verify if a client is permitted
// to override chains for the target, preventing unauthorized access through the APE mechanism.
type APEOverride struct {
// Target for which chains are applied.
Target apeSDK.ChainTarget
// The list of APE chains.
Chains []apeSDK.Chain
}
// Marshal marshals APEOverride into a protobuf binary form.
func (t *APEOverride) Marshal() ([]byte, error) {
return t.ToV2().StableMarshal(nil), nil
}
// Unmarshal unmarshals protobuf binary representation of APEOverride.
func (t *APEOverride) Unmarshal(data []byte) error {
overrideV2 := new(acl.APEOverride)
if err := overrideV2.Unmarshal(data); err != nil {
return err
}
return t.FromV2(overrideV2)
}
// MarshalJSON encodes APEOverride to protobuf JSON format.
func (t *APEOverride) MarshalJSON() ([]byte, error) {
return t.ToV2().MarshalJSON()
}
// UnmarshalJSON decodes APEOverride from protobuf JSON format.
func (t *APEOverride) UnmarshalJSON(data []byte) error {
overrideV2 := new(acl.APEOverride)
if err := overrideV2.UnmarshalJSON(data); err != nil {
return err
}
return t.FromV2(overrideV2)
}
func (c *APEOverride) FromV2(tokenAPEChains *acl.APEOverride) error {
c.Target.FromV2(tokenAPEChains.GetTarget())
if chains := tokenAPEChains.GetChains(); len(chains) > 0 {
c.Chains = make([]apeSDK.Chain, len(chains))
for i := range chains {
if err := c.Chains[i].ReadFromV2(chains[i]); err != nil {
return fmt.Errorf("invalid APE chain: %w", err)
}
}
}
return nil
}
func (c *APEOverride) ToV2() *acl.APEOverride {
if c == nil {
return nil
}
apeOverride := new(acl.APEOverride)
apeOverride.SetTarget(c.Target.ToV2())
chains := make([]*apeV2.Chain, len(c.Chains))
for i := range c.Chains {
chains[i] = new(apeV2.Chain)
chains[i] = c.Chains[i].ToV2()
}
apeOverride.SetChains(chains)
return apeOverride
}
// reads Token from the acl.BearerToken message. If checkFieldPresence is set,
// returns an error on absence of any protocol-required field.
func (b *Token) readFromV2(m acl.BearerToken, checkFieldPresence bool) error {
@ -48,10 +126,11 @@ func (b *Token) readFromV2(m acl.BearerToken, checkFieldPresence bool) error {
b.impersonate = body.GetImpersonate()
apeOverrides := body.GetAPEOverride()
eaclTable := body.GetEACL()
if b.eaclTableSet = eaclTable != nil; b.eaclTableSet {
b.eaclTable = *eacl.NewTableFromV2(eaclTable)
} else if checkFieldPresence && !b.impersonate {
} else if checkFieldPresence && !b.impersonate && apeOverrides == nil {
return errors.New("missing eACL table")
}
@ -72,6 +151,17 @@ func (b *Token) readFromV2(m acl.BearerToken, checkFieldPresence bool) error {
return errors.New("missing token lifetime")
}
if apeOverrides != nil {
if b.apeOverride == nil {
b.apeOverride = new(APEOverride)
}
if err = b.apeOverride.FromV2(apeOverrides); err != nil {
return err
}
} else {
return errors.New("missing APE override")
}
sig := m.GetSignature()
if b.sigSet = sig != nil; sig != nil {
b.sig = *sig
@ -90,7 +180,7 @@ func (b *Token) ReadFromV2(m acl.BearerToken) error {
}
func (b Token) fillBody() *acl.BearerTokenBody {
if !b.eaclTableSet && !b.targetUserSet && !b.lifetimeSet && !b.impersonate {
if !b.eaclTableSet && !b.targetUserSet && !b.lifetimeSet && !b.impersonate && b.apeOverride == nil {
return nil
}
@ -116,6 +206,10 @@ func (b Token) fillBody() *acl.BearerTokenBody {
body.SetLifetime(&lifetime)
}
if b.apeOverride != nil {
body.SetAPEOverride(b.apeOverride.ToV2())
}
body.SetImpersonate(b.impersonate)
return &body
@ -198,6 +292,8 @@ func (b Token) InvalidAt(epoch uint64) bool {
// FrostFS API V2 protocol.
//
// See also EACLTable, AssertContainer.
//
// Deprecated: eACL tables are irrelevant for Bearer token. Use APE chains instead.
func (b *Token) SetEACLTable(table eacl.Table) {
b.eaclTable = table
b.eaclTableSet = true
@ -214,6 +310,17 @@ func (b Token) EACLTable() eacl.Table {
return eacl.Table{}
}
// SetAPEOverride sets APE override to the bearer token.
//
// See also: APEOverride.
func (b *Token) SetAPEOverride(v *APEOverride) {
b.apeOverride = v
}
func (b *Token) APEOverride() *APEOverride {
return b.apeOverride
}
// SetImpersonate mark token as impersonate to consider token signer as request owner.
// If this field is true extended EACLTable in token body isn't processed.
func (b *Token) SetImpersonate(v bool) {

View File

@ -3,6 +3,7 @@ package bearer_test
import (
"bytes"
"math/rand"
"reflect"
"testing"
"git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/acl"
@ -81,6 +82,72 @@ func TestToken_SetEACLTable(t *testing.T) {
require.True(t, isEqualEACLTables(eaclTable, val.EACLTable()))
}
func TestToken_SetAPEOverrides(t *testing.T) {
var val bearer.Token
var m acl.BearerToken
filled := bearertest.Token()
val.WriteToV2(&m)
require.Zero(t, m.GetBody())
val2 := filled
require.NoError(t, val2.Unmarshal(val.Marshal()))
require.Nil(t, val2.APEOverride())
val2 = filled
jd, err := val.MarshalJSON()
require.NoError(t, err)
require.NoError(t, val2.UnmarshalJSON(jd))
require.Nil(t, val2.APEOverride())
// set value
tApe := bearertest.APEOverride()
val.SetAPEOverride(tApe)
require.NotNil(t, val.APEOverride())
require.Equal(t, tApe, val.APEOverride())
val.WriteToV2(&m)
require.NotNil(t, m.GetBody().GetAPEOverride())
require.True(t, tokenAPEOverridesEqual(tApe.ToV2(), m.GetBody().GetAPEOverride()))
val2 = filled
require.NoError(t, val2.Unmarshal(val.Marshal()))
require.True(t, tokenAPEOverridesEqual(tApe.ToV2(), val2.APEOverride().ToV2()))
val2 = filled
jd, err = val.MarshalJSON()
require.NoError(t, err)
require.NoError(t, val2.UnmarshalJSON(jd))
require.True(t, tokenAPEOverridesEqual(tApe.ToV2(), val.APEOverride().ToV2()))
}
func tokenAPEOverridesEqual(lhs, rhs *acl.APEOverride) bool {
if lhs == nil || rhs == nil {
return false
}
if len(lhs.GetChains()) != len(rhs.GetChains()) {
return false
}
for i := range lhs.GetChains() {
if !reflect.DeepEqual(lhs.GetChains()[i], rhs.GetChains()[i]) {
return false
}
}
return lhs.GetTarget().GetTargetType() == rhs.GetTarget().GetTargetType() &&
lhs.GetTarget().GetName() == rhs.GetTarget().GetName()
}
func TestToken_ForUser(t *testing.T) {
var val bearer.Token
var m acl.BearerToken

View File

@ -1,6 +1,7 @@
package bearertest
import (
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/ape"
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/bearer"
eacltest "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/eacl/test"
usertest "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/user/test"
@ -15,6 +16,17 @@ func Token() (t bearer.Token) {
t.SetIat(1)
t.ForUser(usertest.ID())
t.SetEACLTable(*eacltest.Table())
t.SetAPEOverride(APEOverride())
return t
}
func APEOverride() *bearer.APEOverride {
return &bearer.APEOverride{
Target: ape.ChainTarget{
TargetType: ape.TargetTypeContainer,
Name: "F8JsMnChywiPvbDvpxMbjTjx5KhWHHp6gCDt8BhzL9kF",
},
Chains: []ape.Chain{{Raw: []byte("{}")}},
}
}

2
go.mod
View File

@ -2,7 +2,7 @@ module git.frostfs.info/TrueCloudLab/frostfs-sdk-go
go 1.20
replace git.frostfs.info/TrueCloudLab/frostfs-api-go/v2 v2.16.1-0.20240516133103-0803bc6ded00 => git.frostfs.info/aarifullin/frostfs-api-go/v2 v2.15.1-0.20240528121440-2b28af2d13b4
replace git.frostfs.info/TrueCloudLab/frostfs-api-go/v2 v2.16.1-0.20240516133103-0803bc6ded00 => git.frostfs.info/aarifullin/frostfs-api-go/v2 v2.15.1-0.20240529131858-20283620283f
require (
git.frostfs.info/TrueCloudLab/frostfs-api-go/v2 v2.16.1-0.20240516133103-0803bc6ded00

4
go.sum
View File

@ -41,8 +41,8 @@ git.frostfs.info/TrueCloudLab/rfc6979 v0.4.0 h1:M2KR3iBj7WpY3hP10IevfIB9MURr4O9m
git.frostfs.info/TrueCloudLab/rfc6979 v0.4.0/go.mod h1:okpbKfVYf/BpejtfFTfhZqFP+sZ8rsHrP8Rr/jYPNRc=
git.frostfs.info/TrueCloudLab/tzhash v1.8.0 h1:UFMnUIk0Zh17m8rjGHJMqku2hCgaXDqjqZzS4gsb4UA=
git.frostfs.info/TrueCloudLab/tzhash v1.8.0/go.mod h1:dhY+oy274hV8wGvGL4MwwMpdL3GYvaX1a8GQZQHvlF8=
git.frostfs.info/aarifullin/frostfs-api-go/v2 v2.15.1-0.20240528121440-2b28af2d13b4 h1:Hsb7p/V/ivpgnZK/ByblZ/c5zyhInx8dPGT0EcWbxBc=
git.frostfs.info/aarifullin/frostfs-api-go/v2 v2.15.1-0.20240528121440-2b28af2d13b4/go.mod h1:OBDSr+DqV1z4VDouoX3YMleNc4DPBVBWTG3WDT2PK1o=
git.frostfs.info/aarifullin/frostfs-api-go/v2 v2.15.1-0.20240529131858-20283620283f h1:IvI5nO6Fg5G4iwIc3UqLaznkgfT44QAsqEU4MMO1PWI=
git.frostfs.info/aarifullin/frostfs-api-go/v2 v2.15.1-0.20240529131858-20283620283f/go.mod h1:OBDSr+DqV1z4VDouoX3YMleNc4DPBVBWTG3WDT2PK1o=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/CityOfZion/neo-go v0.62.1-pre.0.20191114145240-e740fbe708f8/go.mod h1:MJCkWUBhi9pn/CrYO1Q3P687y2KeahrOPS9BD9LDGb0=