[#292] authmate: Support custom attributes #293
8 changed files with 109 additions and 35 deletions
|
@ -23,6 +23,7 @@ This document outlines major changes between releases.
|
|||
- Support policy-engine (#257)
|
||||
- Support `policy` contract (#259)
|
||||
- Support `proxy` contract (#287)
|
||||
- Authmate: support custom attributes (#292)
|
||||
|
||||
### Changed
|
||||
- Generalise config param `use_default_xmlns_for_complete_multipart` to `use_default_xmlns` so that use default xmlns for all requests (#221)
|
||||
|
|
|
@ -11,9 +11,7 @@ import (
|
|||
apistatus "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/client/status"
|
||||
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
|
||||
oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/user"
|
||||
"github.com/aws/aws-sdk-go/aws/credentials"
|
||||
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
|
@ -42,11 +40,11 @@ func (m credentialsMock) GetBox(_ context.Context, addr oid.Address) (*accessbox
|
|||
return box, nil
|
||||
}
|
||||
|
||||
func (m credentialsMock) Put(context.Context, cid.ID, user.ID, *accessbox.AccessBox, uint64, ...*keys.PublicKey) (oid.Address, error) {
|
||||
func (m credentialsMock) Put(context.Context, cid.ID, tokens.CredentialsParam) (oid.Address, error) {
|
||||
return oid.Address{}, nil
|
||||
}
|
||||
|
||||
func (m credentialsMock) Update(context.Context, oid.Address, user.ID, *accessbox.AccessBox, uint64, ...*keys.PublicKey) (oid.Address, error) {
|
||||
func (m credentialsMock) Update(context.Context, oid.Address, tokens.CredentialsParam) (oid.Address, error) {
|
||||
return oid.Address{}, nil
|
||||
}
|
||||
|
||||
|
|
|
@ -22,6 +22,7 @@ import (
|
|||
frostfsecdsa "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/crypto/ecdsa"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/eacl"
|
||||
"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"
|
||||
|
@ -113,6 +114,7 @@ type (
|
|||
Lifetime time.Duration
|
||||
AwsCliCredentialsFile string
|
||||
ContainerPolicies ContainerPolicies
|
||||
CustomAttributes []object.Attribute
|
||||
}
|
||||
|
||||
// UpdateSecretOptions contains options for passing to Agent.UpdateSecret method.
|
||||
|
@ -121,6 +123,7 @@ type (
|
|||
GatesPublicKeys []*keys.PublicKey
|
||||
Address oid.Address
|
||||
GatePrivateKey *keys.PrivateKey
|
||||
CustomAttributes []object.Attribute
|
||||
}
|
||||
|
||||
tokenUpdateOptions struct {
|
||||
|
@ -278,7 +281,15 @@ func (a *Agent) IssueSecret(ctx context.Context, w io.Writer, options *IssueSecr
|
|||
|
||||
creds := tokens.New(a.frostFS, secrets.EphemeralKey, cache.DefaultAccessBoxConfig(a.log))
|
||||
|
||||
addr, err := creds.Put(ctx, id, idOwner, box, lifetime.Exp, options.GatesPublicKeys...)
|
||||
prm := tokens.CredentialsParam{
|
||||
OwnerID: idOwner,
|
||||
AccessBox: box,
|
||||
Expiration: lifetime.Exp,
|
||||
Keys: options.GatesPublicKeys,
|
||||
CustomAttributes: options.CustomAttributes,
|
||||
}
|
||||
|
||||
addr, err := creds.Put(ctx, id, prm)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to put creds: %w", err)
|
||||
}
|
||||
|
@ -354,8 +365,16 @@ func (a *Agent) UpdateSecret(ctx context.Context, w io.Writer, options *UpdateSe
|
|||
a.log.Info(logs.UpdateAccessCredObjectIntoFrostFS,
|
||||
zap.Stringer("owner_tkn", idOwner))
|
||||
|
||||
prm := tokens.CredentialsParam{
|
||||
OwnerID: idOwner,
|
||||
AccessBox: updatedBox,
|
||||
Expiration: lifetime.Exp,
|
||||
Keys: options.GatesPublicKeys,
|
||||
CustomAttributes: options.CustomAttributes,
|
||||
}
|
||||
|
||||
oldAddr := options.Address
|
||||
addr, err := creds.Update(ctx, oldAddr, idOwner, updatedBox, lifetime.Exp, options.GatesPublicKeys...)
|
||||
addr, err := creds.Update(ctx, oldAddr, prm)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to update creds: %w", err)
|
||||
}
|
||||
|
|
|
@ -20,7 +20,8 @@ var issueSecretCmd = &cobra.Command{
|
|||
Use: "issue-secret",
|
||||
Short: "Issue a secret in FrostFS network",
|
||||
Long: "Creates new s3 credentials to use with frostfs-s3-gw",
|
||||
Example: `frostfs-s3-authmate issue-secret --wallet wallet.json --peer s01.frostfs.devenv:8080 --gate-public-key 031a6c6fbbdf02ca351745fa86b9ba5a9452d785ac4f7fc2b7548ca2a46c4fcf4a`,
|
||||
Example: `frostfs-s3-authmate issue-secret --wallet wallet.json --peer s01.frostfs.devenv:8080 --gate-public-key 031a6c6fbbdf02ca351745fa86b9ba5a9452d785ac4f7fc2b7548ca2a46c4fcf4a
|
||||
frostfs-s3-authmate issue-secret --wallet wallet.json --peer s01.frostfs.devenv:8080 --gate-public-key 031a6c6fbbdf02ca351745fa86b9ba5a9452d785ac4f7fc2b7548ca2a46c4fcf4a --attributes LOGIN=NUUb82KR2JrVByHs2YSKgtK29gKnF5q6Vt`,
|
||||
RunE: runIssueSecretCmd,
|
||||
}
|
||||
|
||||
|
@ -42,6 +43,7 @@ const (
|
|||
frostfsIDProxyFlag = "frostfsid-proxy"
|
||||
frostfsIDNamespaceFlag = "frostfsid-namespace"
|
||||
rpcEndpointFlag = "rpc-endpoint"
|
||||
attributesFlag = "attributes"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -86,6 +88,7 @@ func initIssueSecretCmd() {
|
|||
issueSecretCmd.Flags().String(frostfsIDProxyFlag, "", "Proxy contract hash (LE) or name in NNS to use when interact with frostfsid contract")
|
||||
issueSecretCmd.Flags().String(frostfsIDNamespaceFlag, "", "Namespace to register public key in frostfsid contract")
|
||||
issueSecretCmd.Flags().String(rpcEndpointFlag, "", "NEO node RPC address")
|
||||
issueSecretCmd.Flags().String(attributesFlag, "", "User attributes in form of Key1=Value1,Key2=Value2 (note: you cannot override system attributes)")
|
||||
|
||||
_ = issueSecretCmd.MarkFlagRequired(walletFlag)
|
||||
_ = issueSecretCmd.MarkFlagRequired(peerFlag)
|
||||
|
@ -184,6 +187,11 @@ func runIssueSecretCmd(cmd *cobra.Command, _ []string) error {
|
|||
}
|
||||
}
|
||||
|
||||
customAttrs, err := parseObjectAttrs(viper.GetString(attributesFlag))
|
||||
if err != nil {
|
||||
return wrapPreparationError(fmt.Errorf("failed to parse attributes: %s", err))
|
||||
}
|
||||
|
||||
issueSecretOptions := &authmate.IssueSecretOptions{
|
||||
Container: authmate.ContainerOptions{
|
||||
ID: cnrID,
|
||||
|
@ -199,6 +207,7 @@ func runIssueSecretCmd(cmd *cobra.Command, _ []string) error {
|
|||
ContainerPolicies: policies,
|
||||
Lifetime: lifetime,
|
||||
AwsCliCredentialsFile: viper.GetString(awsCLICredentialFlag),
|
||||
CustomAttributes: customAttrs,
|
||||
}
|
||||
|
||||
if err = authmate.New(log, frostFS).IssueSecret(ctx, os.Stdout, issueSecretOptions); err != nil {
|
||||
|
|
|
@ -44,6 +44,7 @@ func initUpdateSecretCmd() {
|
|||
updateSecretCmd.Flags().String(frostfsIDProxyFlag, "", "Proxy contract hash (LE) or name in NNS to use when interact with frostfsid contract")
|
||||
updateSecretCmd.Flags().String(frostfsIDNamespaceFlag, "", "Namespace to register public key in frostfsid contract")
|
||||
updateSecretCmd.Flags().String(rpcEndpointFlag, "", "NEO node RPC address")
|
||||
updateSecretCmd.Flags().String(attributesFlag, "", "User attributes in form of Key1=Value1,Key2=Value2 (note: you cannot override system attributes)")
|
||||
|
||||
_ = updateSecretCmd.MarkFlagRequired(walletFlag)
|
||||
_ = updateSecretCmd.MarkFlagRequired(peerFlag)
|
||||
|
@ -122,11 +123,17 @@ func runUpdateSecretCmd(cmd *cobra.Command, _ []string) error {
|
|||
}
|
||||
}
|
||||
|
||||
customAttrs, err := parseObjectAttrs(viper.GetString(attributesFlag))
|
||||
if err != nil {
|
||||
return wrapPreparationError(fmt.Errorf("failed to parse attributes: %s", err))
|
||||
}
|
||||
|
||||
updateSecretOptions := &authmate.UpdateSecretOptions{
|
||||
Address: accessBoxAddress,
|
||||
FrostFSKey: key,
|
||||
GatesPublicKeys: gatesPublicKeys,
|
||||
GatePrivateKey: gateKey,
|
||||
CustomAttributes: customAttrs,
|
||||
}
|
||||
|
||||
if err = authmate.New(log, frostFS).UpdateSecret(ctx, os.Stdout, updateSecretOptions); err != nil {
|
||||
|
|
|
@ -5,6 +5,7 @@ import (
|
|||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api"
|
||||
|
@ -12,6 +13,7 @@ import (
|
|||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/internal/frostfs"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/internal/frostfs/frostfsid"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/internal/logs"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/pool"
|
||||
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||
"github.com/spf13/viper"
|
||||
|
@ -153,3 +155,23 @@ func createFrostFSID(ctx context.Context, log *zap.Logger, cfg frostfsid.Config)
|
|||
|
||||
return cli, nil
|
||||
}
|
||||
|
||||
func parseObjectAttrs(attributes string) ([]object.Attribute, error) {
|
||||
if len(attributes) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
rawAttrs := strings.Split(attributes, ",")
|
||||
|
||||
attrs := make([]object.Attribute, len(rawAttrs))
|
||||
for i := range rawAttrs {
|
||||
k, v, found := strings.Cut(rawAttrs[i], "=")
|
||||
if !found {
|
||||
return nil, fmt.Errorf("invalid attribute format: %s", rawAttrs[i])
|
||||
}
|
||||
attrs[i].SetKey(k)
|
||||
attrs[i].SetValue(v)
|
||||
}
|
||||
|
||||
return attrs, nil
|
||||
}
|
||||
|
|
|
@ -10,6 +10,7 @@ import (
|
|||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/cache"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/creds/accessbox"
|
||||
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
|
||||
"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/user"
|
||||
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||
|
@ -19,8 +20,16 @@ type (
|
|||
// Credentials is a bearer token get/put interface.
|
||||
Credentials interface {
|
||||
GetBox(context.Context, oid.Address) (*accessbox.Box, error)
|
||||
Put(context.Context, cid.ID, user.ID, *accessbox.AccessBox, uint64, ...*keys.PublicKey) (oid.Address, error)
|
||||
Update(context.Context, oid.Address, user.ID, *accessbox.AccessBox, uint64, ...*keys.PublicKey) (oid.Address, error)
|
||||
Put(context.Context, cid.ID, CredentialsParam) (oid.Address, error)
|
||||
Update(context.Context, oid.Address, CredentialsParam) (oid.Address, error)
|
||||
}
|
||||
|
||||
CredentialsParam struct {
|
||||
OwnerID user.ID
|
||||
AccessBox *accessbox.AccessBox
|
||||
Expiration uint64
|
||||
Keys keys.PublicKeys
|
||||
CustomAttributes []object.Attribute
|
||||
}
|
||||
|
||||
cred struct {
|
||||
|
@ -50,6 +59,9 @@ type PrmObjectCreate struct {
|
|||
|
||||
// Object payload.
|
||||
Payload []byte
|
||||
|
||||
// CustomAttributes are additional user provided attributes for box object.
|
||||
CustomAttributes []object.Attribute
|
||||
}
|
||||
|
||||
// FrostFS represents virtual connection to FrostFS network.
|
||||
|
@ -123,33 +135,34 @@ func (c *cred) getAccessBox(ctx context.Context, addr oid.Address) (*accessbox.A
|
|||
return &box, nil
|
||||
}
|
||||
|
||||
func (c *cred) Put(ctx context.Context, idCnr cid.ID, issuer user.ID, box *accessbox.AccessBox, expiration uint64, keys ...*keys.PublicKey) (oid.Address, error) {
|
||||
return c.createObject(ctx, idCnr, nil, issuer, box, expiration, keys...)
|
||||
func (c *cred) Put(ctx context.Context, idCnr cid.ID, prm CredentialsParam) (oid.Address, error) {
|
||||
return c.createObject(ctx, idCnr, nil, prm)
|
||||
}
|
||||
|
||||
func (c *cred) Update(ctx context.Context, addr oid.Address, issuer user.ID, box *accessbox.AccessBox, expiration uint64, keys ...*keys.PublicKey) (oid.Address, error) {
|
||||
func (c *cred) Update(ctx context.Context, addr oid.Address, prm CredentialsParam) (oid.Address, error) {
|
||||
objID := addr.Object()
|
||||
return c.createObject(ctx, addr.Container(), &objID, issuer, box, expiration, keys...)
|
||||
return c.createObject(ctx, addr.Container(), &objID, prm)
|
||||
}
|
||||
|
||||
func (c *cred) createObject(ctx context.Context, cnrID cid.ID, newVersionFor *oid.ID, issuer user.ID, box *accessbox.AccessBox, expiration uint64, keys ...*keys.PublicKey) (oid.Address, error) {
|
||||
if len(keys) == 0 {
|
||||
func (c *cred) createObject(ctx context.Context, cnrID cid.ID, newVersionFor *oid.ID, prm CredentialsParam) (oid.Address, error) {
|
||||
if len(prm.Keys) == 0 {
|
||||
return oid.Address{}, ErrEmptyPublicKeys
|
||||
} else if box == nil {
|
||||
} else if prm.AccessBox == nil {
|
||||
return oid.Address{}, ErrEmptyBearerToken
|
||||
}
|
||||
data, err := box.Marshal()
|
||||
data, err := prm.AccessBox.Marshal()
|
||||
if err != nil {
|
||||
return oid.Address{}, fmt.Errorf("marshall box: %w", err)
|
||||
}
|
||||
|
||||
idObj, err := c.frostFS.CreateObject(ctx, PrmObjectCreate{
|
||||
Creator: issuer,
|
||||
Creator: prm.OwnerID,
|
||||
Container: cnrID,
|
||||
Filepath: strconv.FormatInt(time.Now().Unix(), 10) + "_access.box",
|
||||
ExpirationEpoch: expiration,
|
||||
ExpirationEpoch: prm.Expiration,
|
||||
NewVersionFor: newVersionFor,
|
||||
Payload: data,
|
||||
CustomAttributes: prm.CustomAttributes,
|
||||
})
|
||||
if err != nil {
|
||||
return oid.Address{}, fmt.Errorf("create object: %w", err)
|
||||
|
|
|
@ -120,6 +120,11 @@ func (x *AuthmateFrostFS) CreateObject(ctx context.Context, prm tokens.PrmObject
|
|||
attributes = append(attributes, [2]string{accessBoxCRDTNameAttr, versions.Name()})
|
||||
}
|
||||
|
||||
for _, attr := range prm.CustomAttributes {
|
||||
// we don't check attribute duplication since storage node does this
|
||||
attributes = append(attributes, [2]string{attr.Key(), attr.Value()})
|
||||
}
|
||||
|
||||
return x.frostFS.CreateObject(ctx, layer.PrmObjectCreate{
|
||||
Container: prm.Container,
|
||||
Filepath: prm.Filepath,
|
||||
|
|
Loading…
Reference in a new issue