[#131] authmate: Add agent.UpdateSecret

Signed-off-by: Denis Kirillov <d.kirillov@yadro.com>
This commit is contained in:
Denis Kirillov 2023-06-23 16:06:59 +03:00
parent dea7b39805
commit f74ab12f91

View file

@ -11,6 +11,8 @@ import (
"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/creds/accessbox"
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/creds/tokens"
@ -108,6 +110,21 @@ type (
UpdateCreds *UpdateOptions
}
// UpdateSecretOptions contains options for passing to Agent.UpdateSecret method.
UpdateSecretOptions struct {
FrostFSKey *keys.PrivateKey
GatesPublicKeys []*keys.PublicKey
Address oid.Address
GatePrivateKey *keys.PrivateKey
}
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
@ -136,8 +153,8 @@ type lifetimeOptions struct {
type (
issuingResult struct {
AccessKeyID string `json:"access_key_id"`
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"`
@ -237,12 +254,7 @@ func (a *Agent) IssueSecret(ctx context.Context, w io.Writer, options *IssueSecr
return fmt.Errorf("create tokens: %w", err)
}
var secret []byte
if options.UpdateCreds != nil {
secret = options.UpdateCreds.SecretAccessKey
}
box, secrets, err := accessbox.PackTokens(gatesData, secret)
box, secrets, err := accessbox.PackTokens(gatesData, nil)
if err != nil {
return fmt.Errorf("pack tokens: %w", err)
}
@ -261,24 +273,15 @@ func (a *Agent) IssueSecret(ctx context.Context, w io.Writer, options *IssueSecr
creds := tokens.New(a.frostFS, secrets.EphemeralKey, cache.DefaultAccessBoxConfig(a.log))
var addr oid.Address
var oldAddr oid.Address
if options.UpdateCreds != nil {
oldAddr = options.UpdateCreds.Address
addr, err = creds.Update(ctx, oldAddr, idOwner, box, lifetime.Exp, options.GatesPublicKeys...)
} else {
addr, err = creds.Put(ctx, id, idOwner, box, lifetime.Exp, options.GatesPublicKeys...)
oldAddr = addr
}
addr, err := creds.Put(ctx, id, idOwner, box, lifetime.Exp, options.GatesPublicKeys...)
if err != nil {
return fmt.Errorf("failed to put creds: %w", err)
}
accessKeyID := addr.Container().EncodeToString() + "0" + addr.Object().EncodeToString()
accessKeyID := accessKeyIDFromAddr(addr)
ir := &issuingResult{
InitialAccessKeyID: accessKeyID,
AccessKeyID: accessKeyID,
InitialAccessKeyID: oldAddr.Container().EncodeToString() + "0" + oldAddr.Object().EncodeToString(),
SecretAccessKey: secrets.AccessKey,
OwnerPrivateKey: hex.EncodeToString(secrets.EphemeralKey.Bytes()),
WalletPublicKey: hex.EncodeToString(options.FrostFSKey.PublicKey().Bytes()),
@ -309,6 +312,73 @@ func (a *Agent) IssueSecret(ctx context.Context, w io.Writer, options *IssueSecr
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 {
creds := tokens.New(a.frostFS, options.GatePrivateKey, cache.DefaultAccessBoxConfig(a.log))
box, err := creds.GetBox(ctx, options.Address)
if err != nil {
return fmt.Errorf("get accessbox: %w", err)
}
secret, err := hex.DecodeString(box.Gate.AccessKey)
if 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)
if err != nil {
return fmt.Errorf("pack tokens: %w", err)
}
var idOwner user.ID
user.IDFromKey(&idOwner, options.FrostFSKey.PrivateKey.PublicKey)
a.log.Info("update access cred object into FrostFS",
zap.Stringer("owner_tkn", idOwner))
oldAddr := options.Address
addr, err := creds.Update(ctx, oldAddr, idOwner, updatedBox, lifetime.Exp, options.GatesPublicKeys...)
if err != nil {
return fmt.Errorf("failed to update creds: %w", err)
}
ir := &issuingResult{
AccessKeyID: accessKeyIDFromAddr(addr),
InitialAccessKeyID: accessKeyIDFromAddr(oldAddr),
SecretAccessKey: secrets.AccessKey,
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 {
@ -467,3 +537,55 @@ func createTokens(options *IssueSecretOptions, lifetime lifetimeOptions) ([]*acc
return gates, nil
}
func formTokensToUpdate(options tokenUpdateOptions) ([]*accessbox.GateData, error) {
btoken := options.box.Gate.BearerToken
table := btoken.EACLTable()
bearerTokens, err := buildBearerTokens(options.frostFSKey, btoken.Impersonate(), &table, 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()
}