From f74ab12f91cceb063b645fe488b305dce5e7c96b Mon Sep 17 00:00:00 2001 From: Denis Kirillov Date: Fri, 23 Jun 2023 16:06:59 +0300 Subject: [PATCH] [#131] authmate: Add agent.UpdateSecret Signed-off-by: Denis Kirillov --- authmate/authmate.go | 160 ++++++++++++++++++++++++++++++++++++++----- 1 file changed, 141 insertions(+), 19 deletions(-) diff --git a/authmate/authmate.go b/authmate/authmate.go index 6f9c91e0..5e80e364 100644 --- a/authmate/authmate.go +++ b/authmate/authmate.go @@ -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() +}