[#225] bearer: Introduce APEOverride field for the token

Signed-off-by: Airat Arifullin <a.arifullin@yadro.com>
This commit is contained in:
Airat Arifullin 2024-05-30 11:27:21 +03:00 committed by Evgenii Stratonikov
parent dd23c6fd2b
commit 717a7d00ef
3 changed files with 177 additions and 2 deletions

View file

@ -6,7 +6,9 @@ import (
"fmt" "fmt"
"git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/acl" "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" "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" cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
frostfscrypto "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/crypto" frostfscrypto "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/crypto"
frostfsecdsa "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/crypto/ecdsa" frostfsecdsa "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/crypto/ecdsa"
@ -33,9 +35,85 @@ type Token struct {
sigSet bool sigSet bool
sig refs.Signature sig refs.Signature
apeOverrideSet bool
apeOverride APEOverride
impersonate bool 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 (c *APEOverride) Marshal() ([]byte, error) {
return c.ToV2().StableMarshal(nil), nil
}
// Unmarshal unmarshals protobuf binary representation of APEOverride.
func (c *APEOverride) Unmarshal(data []byte) error {
overrideV2 := new(acl.APEOverride)
if err := overrideV2.Unmarshal(data); err != nil {
return err
}
return c.FromV2(overrideV2)
}
// MarshalJSON encodes APEOverride to protobuf JSON format.
func (c *APEOverride) MarshalJSON() ([]byte, error) {
return c.ToV2().MarshalJSON()
}
// UnmarshalJSON decodes APEOverride from protobuf JSON format.
func (c *APEOverride) UnmarshalJSON(data []byte) error {
overrideV2 := new(acl.APEOverride)
if err := overrideV2.UnmarshalJSON(data); err != nil {
return err
}
return c.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] = c.Chains[i].ToV2()
}
apeOverride.SetChains(chains)
return apeOverride
}
// reads Token from the acl.BearerToken message. If checkFieldPresence is set, // reads Token from the acl.BearerToken message. If checkFieldPresence is set,
// returns an error on absence of any protocol-required field. // returns an error on absence of any protocol-required field.
func (b *Token) readFromV2(m acl.BearerToken, checkFieldPresence bool) error { 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() b.impersonate = body.GetImpersonate()
apeOverrides := body.GetAPEOverride()
eaclTable := body.GetEACL() eaclTable := body.GetEACL()
if b.eaclTableSet = eaclTable != nil; b.eaclTableSet { if b.eaclTableSet = eaclTable != nil; b.eaclTableSet {
b.eaclTable = *eacl.NewTableFromV2(eaclTable) b.eaclTable = *eacl.NewTableFromV2(eaclTable)
} else if checkFieldPresence && !b.impersonate { } else if checkFieldPresence && !b.impersonate && apeOverrides == nil {
return errors.New("missing eACL table") return errors.New("missing eACL table")
} }
@ -72,6 +151,14 @@ func (b *Token) readFromV2(m acl.BearerToken, checkFieldPresence bool) error {
return errors.New("missing token lifetime") return errors.New("missing token lifetime")
} }
if b.apeOverrideSet = apeOverrides != nil; b.apeOverrideSet {
if err = b.apeOverride.FromV2(apeOverrides); err != nil {
return err
}
} else if checkFieldPresence && !b.impersonate && !b.eaclTableSet {
return errors.New("missing APE override")
}
sig := m.GetSignature() sig := m.GetSignature()
if b.sigSet = sig != nil; sig != nil { if b.sigSet = sig != nil; sig != nil {
b.sig = *sig b.sig = *sig
@ -90,7 +177,7 @@ func (b *Token) ReadFromV2(m acl.BearerToken) error {
} }
func (b Token) fillBody() *acl.BearerTokenBody { 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.apeOverrideSet {
return nil return nil
} }
@ -116,6 +203,10 @@ func (b Token) fillBody() *acl.BearerTokenBody {
body.SetLifetime(&lifetime) body.SetLifetime(&lifetime)
} }
if b.apeOverrideSet {
body.SetAPEOverride(b.apeOverride.ToV2())
}
body.SetImpersonate(b.impersonate) body.SetImpersonate(b.impersonate)
return &body return &body
@ -214,6 +305,25 @@ func (b Token) EACLTable() eacl.Table {
return 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
b.apeOverrideSet = true
}
// APEOverride returns APE override set by SetAPEOverride.
//
// Zero Token has zero APEOverride.
func (b *Token) APEOverride() APEOverride {
if b.apeOverrideSet {
return b.apeOverride
}
return APEOverride{}
}
// SetImpersonate mark token as impersonate to consider token signer as request owner. // 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. // If this field is true extended EACLTable in token body isn't processed.
func (b *Token) SetImpersonate(v bool) { func (b *Token) SetImpersonate(v bool) {

View file

@ -3,6 +3,7 @@ package bearer_test
import ( import (
"bytes" "bytes"
"math/rand" "math/rand"
"reflect"
"testing" "testing"
"git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/acl" "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/acl"
@ -81,6 +82,58 @@ func TestToken_SetEACLTable(t *testing.T) {
require.True(t, isEqualEACLTables(eaclTable, val.EACLTable())) 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.Zero(t, val2.APEOverride())
val2 = filled
jd, err := val.MarshalJSON()
require.NoError(t, err)
require.NoError(t, val2.UnmarshalJSON(jd))
require.Zero(t, val2.APEOverride())
// set value
tApe := bearertest.APEOverride()
val.SetAPEOverride(tApe)
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()))
apeOverride := val2.APEOverride()
require.True(t, tokenAPEOverridesEqual(tApe.ToV2(), apeOverride.ToV2()))
val2 = filled
jd, err = val.MarshalJSON()
require.NoError(t, err)
require.NoError(t, val2.UnmarshalJSON(jd))
apeOverride = val.APEOverride()
require.True(t, tokenAPEOverridesEqual(tApe.ToV2(), apeOverride.ToV2()))
}
func tokenAPEOverridesEqual(lhs, rhs *acl.APEOverride) bool {
return reflect.DeepEqual(lhs, rhs)
}
func TestToken_ForUser(t *testing.T) { func TestToken_ForUser(t *testing.T) {
var val bearer.Token var val bearer.Token
var m acl.BearerToken var m acl.BearerToken

View file

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