2023-06-23 13:10:43 +00:00
package modules
import (
"context"
"fmt"
"os"
2024-08-30 12:05:32 +00:00
"strings"
2023-06-23 13:10:43 +00:00
"time"
2024-11-19 08:22:39 +00:00
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/handler"
2023-06-23 13:10:43 +00:00
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/authmate"
2024-08-30 12:05:32 +00:00
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/internal/frostfs"
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/internal/frostfs/util"
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/internal/logs"
2023-06-23 13:10:43 +00:00
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/internal/wallet"
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
2024-08-30 12:05:32 +00:00
oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id"
2023-06-23 13:10:43 +00:00
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
"github.com/spf13/cobra"
"github.com/spf13/viper"
2024-08-30 12:05:32 +00:00
"go.uber.org/zap"
2023-06-23 13:10:43 +00:00
)
var issueSecretCmd = & cobra . Command {
2024-01-19 07:00:04 +00:00
Use : "issue-secret" ,
Short : "Issue a secret in FrostFS network" ,
Long : "Creates new s3 credentials to use with frostfs-s3-gw" ,
2024-08-30 12:05:32 +00:00
Example : ` To create new s3 credentials use :
frostfs - s3 - authmate issue - secret -- wallet wallet . json -- peer s01 . frostfs . devenv : 8080 -- gate - public - key 031 a6c6fbbdf02ca351745fa86b9ba5a9452d785ac4f7fc2b7548ca2a46c4fcf4a
frostfs - s3 - authmate issue - secret -- wallet wallet . json -- peer s01 . frostfs . devenv : 8080 -- gate - public - key 031 a6c6fbbdf02ca351745fa86b9ba5a9452d785ac4f7fc2b7548ca2a46c4fcf4a -- attributes LOGIN = NUUb82KR2JrVByHs2YSKgtK29gKnF5q6Vt
To create new s3 credentials using specific access key id and secret access key use :
frostfs - s3 - authmate issue - secret -- wallet wallet . json -- peer s01 . frostfs . devenv : 8080 -- gate - public - key 031 a6c6fbbdf02ca351745fa86b9ba5a9452d785ac4f7fc2b7548ca2a46c4fcf4a -- access - key - id my - access - key - id -- secret - access - key my - secret - key -- container - id BpExV76416Vo7GrkJsGwXGoLM35xsBwup8voedDZR3c6
` ,
2024-01-19 07:00:04 +00:00
RunE : runIssueSecretCmd ,
2023-06-23 13:10:43 +00:00
}
const (
walletFlag = "wallet"
addressFlag = "address"
peerFlag = "peer"
gatePublicKeyFlag = "gate-public-key"
containerIDFlag = "container-id"
containerFriendlyNameFlag = "container-friendly-name"
containerPlacementPolicyFlag = "container-placement-policy"
sessionTokensFlag = "session-tokens"
lifetimeFlag = "lifetime"
containerPolicyFlag = "container-policy"
awsCLICredentialFlag = "aws-cli-credentials"
2024-01-19 07:00:04 +00:00
attributesFlag = "attributes"
2024-11-19 08:22:39 +00:00
retryMaxAttemptsFlag = "retry-max-attempts"
retryMaxBackoffFlag = "retry-max-backoff"
retryStrategyFlag = "retry-strategy"
2023-06-23 13:10:43 +00:00
)
2024-06-27 09:23:13 +00:00
const walletPassphraseCfg = "wallet.passphrase"
2023-06-23 13:10:43 +00:00
const (
defaultAccessBoxLifetime = 30 * 24 * time . Hour
defaultPoolDialTimeout = 5 * time . Second
defaultPoolHealthcheckTimeout = 5 * time . Second
defaultPoolRebalanceInterval = 30 * time . Second
defaultPoolStreamTimeout = 10 * time . Second
2024-11-19 08:22:39 +00:00
defaultRetryMaxAttempts = 4
defaultRetryMaxBackoff = 30 * time . Second
defaultRetryStrategy = handler . RetryStrategyExponential
2023-06-23 13:10:43 +00:00
)
const (
poolDialTimeoutFlag = "pool-dial-timeout"
poolHealthcheckTimeoutFlag = "pool-healthcheck-timeout"
poolRebalanceIntervalFlag = "pool-rebalance-interval"
poolStreamTimeoutFlag = "pool-stream-timeout"
2024-08-30 12:05:32 +00:00
accessKeyIDFlag = "access-key-id"
secretAccessKeyFlag = "secret-access-key"
2023-06-23 13:10:43 +00:00
)
func initIssueSecretCmd ( ) {
issueSecretCmd . Flags ( ) . String ( walletFlag , "" , "Path to the wallet that will be owner of the credentials" )
issueSecretCmd . Flags ( ) . String ( addressFlag , "" , "Address of the wallet account" )
issueSecretCmd . Flags ( ) . String ( peerFlag , "" , "Address of a frostfs peer to connect to" )
issueSecretCmd . Flags ( ) . StringSlice ( gatePublicKeyFlag , nil , "Public 256r1 key of a gate (use flags repeatedly for multiple gates or separate them by comma)" )
issueSecretCmd . Flags ( ) . String ( containerIDFlag , "" , "Auth container id to put the secret into (if not provided new container will be created)" )
issueSecretCmd . Flags ( ) . String ( containerFriendlyNameFlag , "" , "Friendly name of auth container to put the secret into (flag value will be used only if --container-id is missed)" )
issueSecretCmd . Flags ( ) . String ( containerPlacementPolicyFlag , "REP 2 IN X CBF 3 SELECT 2 FROM * AS X" , "Placement policy of auth container to put the secret into (flag value will be used only if --container-id is missed)" )
issueSecretCmd . Flags ( ) . String ( sessionTokensFlag , "" , "create session tokens with rules, if the rules are set as 'none', no session tokens will be created" )
issueSecretCmd . Flags ( ) . Duration ( lifetimeFlag , defaultAccessBoxLifetime , "Lifetime of tokens. For example 50h30m (note: max time unit is an hour so to set a day you should use 24h).\nIt will be ceil rounded to the nearest amount of epoch." )
issueSecretCmd . Flags ( ) . String ( containerPolicyFlag , "" , "Mapping AWS storage class to FrostFS storage policy as plain json string or path to json file" )
issueSecretCmd . Flags ( ) . String ( awsCLICredentialFlag , "" , "Path to the aws cli credential file" )
issueSecretCmd . Flags ( ) . Duration ( poolDialTimeoutFlag , defaultPoolDialTimeout , "Timeout for connection to the node in pool to be established" )
issueSecretCmd . Flags ( ) . Duration ( poolHealthcheckTimeoutFlag , defaultPoolHealthcheckTimeout , "Timeout for request to node to decide if it is alive" )
issueSecretCmd . Flags ( ) . Duration ( poolRebalanceIntervalFlag , defaultPoolRebalanceInterval , "Interval for updating nodes health status" )
issueSecretCmd . Flags ( ) . Duration ( poolStreamTimeoutFlag , defaultPoolStreamTimeout , "Timeout for individual operation in streaming RPC" )
2024-01-19 07:00:04 +00:00
issueSecretCmd . Flags ( ) . String ( attributesFlag , "" , "User attributes in form of Key1=Value1,Key2=Value2 (note: you cannot override system attributes)" )
2024-08-30 12:05:32 +00:00
issueSecretCmd . Flags ( ) . String ( accessKeyIDFlag , "" , "Access key id of s3 credential that must be created" )
issueSecretCmd . Flags ( ) . String ( secretAccessKeyFlag , "" , "Secret access key of s3 credential that must be used" )
issueSecretCmd . Flags ( ) . String ( rpcEndpointFlag , "" , "NEO node RPC address (must be provided if container-id is nns name)" )
2024-11-19 08:22:39 +00:00
issueSecretCmd . Flags ( ) . Int ( retryMaxAttemptsFlag , defaultRetryMaxAttempts , "Max amount of request attempts" )
issueSecretCmd . Flags ( ) . Duration ( retryMaxBackoffFlag , defaultRetryMaxBackoff , "Max delay before next attempt" )
issueSecretCmd . Flags ( ) . String ( retryStrategyFlag , defaultRetryStrategy , "Backoff strategy. `exponential` and `constant` are allowed" )
2023-06-23 13:10:43 +00:00
_ = issueSecretCmd . MarkFlagRequired ( walletFlag )
_ = issueSecretCmd . MarkFlagRequired ( peerFlag )
_ = issueSecretCmd . MarkFlagRequired ( gatePublicKeyFlag )
}
func runIssueSecretCmd ( cmd * cobra . Command , _ [ ] string ) error {
ctx , cancel := context . WithTimeout ( cmd . Context ( ) , viper . GetDuration ( timeoutFlag ) )
defer cancel ( )
log := getLogger ( )
password := wallet . GetPassword ( viper . GetViper ( ) , walletPassphraseCfg )
key , err := wallet . GetKeyFromPath ( viper . GetString ( walletFlag ) , viper . GetString ( addressFlag ) , password )
if err != nil {
2023-09-04 18:01:56 +00:00
return wrapPreparationError ( fmt . Errorf ( "failed to load frostfs private key: %s" , err ) )
2023-06-23 13:10:43 +00:00
}
var gatesPublicKeys [ ] * keys . PublicKey
for _ , keyStr := range viper . GetStringSlice ( gatePublicKeyFlag ) {
gpk , err := keys . NewPublicKeyFromString ( keyStr )
if err != nil {
2023-09-04 18:01:56 +00:00
return wrapPreparationError ( fmt . Errorf ( "failed to load gate's public key: %s" , err ) )
2023-06-23 13:10:43 +00:00
}
gatesPublicKeys = append ( gatesPublicKeys , gpk )
}
lifetime := viper . GetDuration ( lifetimeFlag )
if lifetime <= 0 {
2023-09-04 18:01:56 +00:00
return wrapPreparationError ( fmt . Errorf ( "lifetime must be greater 0, current value: %d" , lifetime ) )
2023-06-23 13:10:43 +00:00
}
policies , err := parsePolicies ( viper . GetString ( containerPolicyFlag ) )
if err != nil {
2023-09-04 18:01:56 +00:00
return wrapPreparationError ( fmt . Errorf ( "couldn't parse container policy: %s" , err . Error ( ) ) )
2023-06-23 13:10:43 +00:00
}
sessionRules , skipSessionRules , err := getSessionRules ( viper . GetString ( sessionTokensFlag ) )
if err != nil {
2023-09-04 18:01:56 +00:00
return wrapPreparationError ( fmt . Errorf ( "couldn't parse 'session-tokens' flag: %s" , err . Error ( ) ) )
2023-06-23 13:10:43 +00:00
}
poolCfg := PoolConfig {
2023-08-03 12:08:22 +00:00
Key : key ,
2023-06-23 13:10:43 +00:00
Address : viper . GetString ( peerFlag ) ,
DialTimeout : viper . GetDuration ( poolDialTimeoutFlag ) ,
HealthcheckTimeout : viper . GetDuration ( poolHealthcheckTimeoutFlag ) ,
StreamTimeout : viper . GetDuration ( poolStreamTimeoutFlag ) ,
RebalanceInterval : viper . GetDuration ( poolRebalanceIntervalFlag ) ,
}
frostFS , err := createFrostFS ( ctx , log , poolCfg )
if err != nil {
2023-09-04 18:01:56 +00:00
return wrapFrostFSInitError ( fmt . Errorf ( "failed to create FrostFS component: %s" , err ) )
2023-06-23 13:10:43 +00:00
}
2024-08-30 12:05:32 +00:00
var accessBox cid . ID
if viper . IsSet ( containerIDFlag ) {
if accessBox , err = util . ResolveContainerID ( viper . GetString ( containerIDFlag ) , viper . GetString ( rpcEndpointFlag ) ) ; err != nil {
return wrapPreparationError ( fmt . Errorf ( "resolve accessbox container id (make sure you provided %s): %w" , rpcEndpointFlag , err ) )
}
} else if accessBox , err = createAccessBox ( ctx , frostFS , key , log ) ; err != nil {
return wrapPreparationError ( err )
}
accessKeyID , secretAccessKey , err := parseAccessKeys ( )
if err != nil {
return wrapPreparationError ( err )
}
2024-01-19 07:00:04 +00:00
customAttrs , err := parseObjectAttrs ( viper . GetString ( attributesFlag ) )
if err != nil {
return wrapPreparationError ( fmt . Errorf ( "failed to parse attributes: %s" , err ) )
}
2023-06-23 13:10:43 +00:00
issueSecretOptions := & authmate . IssueSecretOptions {
2024-08-30 12:05:32 +00:00
Container : accessBox ,
AccessKeyID : accessKeyID ,
SecretAccessKey : secretAccessKey ,
2023-06-23 13:10:43 +00:00
FrostFSKey : key ,
GatesPublicKeys : gatesPublicKeys ,
2024-05-28 09:10:28 +00:00
Impersonate : true ,
2023-06-23 13:10:43 +00:00
SessionTokenRules : sessionRules ,
SkipSessionRules : skipSessionRules ,
ContainerPolicies : policies ,
Lifetime : lifetime ,
AwsCliCredentialsFile : viper . GetString ( awsCLICredentialFlag ) ,
2024-01-19 07:00:04 +00:00
CustomAttributes : customAttrs ,
2023-06-23 13:10:43 +00:00
}
2024-11-19 08:22:39 +00:00
options := [ ] authmate . Option {
authmate . WithRetryMaxAttempts ( viper . GetInt ( retryMaxAttemptsFlag ) ) ,
authmate . WithRetryMaxBackoff ( viper . GetDuration ( retryMaxBackoffFlag ) ) ,
authmate . WithRetryStrategy ( handler . RetryStrategy ( viper . GetString ( retryStrategyFlag ) ) ) ,
}
if err = authmate . New ( log , frostFS , options ... ) . IssueSecret ( ctx , os . Stdout , issueSecretOptions ) ; err != nil {
2023-09-04 18:01:56 +00:00
return wrapBusinessLogicError ( fmt . Errorf ( "failed to issue secret: %s" , err ) )
2023-06-23 13:10:43 +00:00
}
return nil
}
2024-08-30 12:05:32 +00:00
func parseAccessKeys ( ) ( accessKeyID , secretAccessKey string , err error ) {
accessKeyID = viper . GetString ( accessKeyIDFlag )
secretAccessKey = viper . GetString ( secretAccessKeyFlag )
if accessKeyID == "" && secretAccessKey != "" || accessKeyID != "" && secretAccessKey == "" {
return "" , "" , fmt . Errorf ( "flags %s and %s must be both provided or not" , accessKeyIDFlag , secretAccessKeyFlag )
}
if accessKeyID != "" {
if ! isCustomCreds ( accessKeyID ) {
return "" , "" , fmt . Errorf ( "invalid custom AccessKeyID format: %s" , accessKeyID )
}
if ! checkAccessKeyLength ( accessKeyID ) {
return "" , "" , fmt . Errorf ( "invalid custom AccessKeyID length: %s" , accessKeyID )
}
if ! checkAccessKeyLength ( secretAccessKey ) {
return "" , "" , fmt . Errorf ( "invalid custom SecretAccessKey length: %s" , secretAccessKey )
}
}
return accessKeyID , secretAccessKey , nil
}
func isCustomCreds ( accessKeyID string ) bool {
var addr oid . Address
return addr . DecodeString ( strings . ReplaceAll ( accessKeyID , "0" , "/" ) ) != nil
}
func checkAccessKeyLength ( key string ) bool {
return 4 <= len ( key ) && len ( key ) <= 128
}
func createAccessBox ( ctx context . Context , frostFS * frostfs . AuthmateFrostFS , key * keys . PrivateKey , log * zap . Logger ) ( cid . ID , error ) {
friendlyName := viper . GetString ( containerFriendlyNameFlag )
placementPolicy := viper . GetString ( containerPlacementPolicyFlag )
log . Info ( logs . CreateContainer , zap . String ( "friendly_name" , friendlyName ) , zap . String ( "placement_policy" , placementPolicy ) )
prm := authmate . PrmContainerCreate {
FriendlyName : friendlyName ,
2024-11-18 12:31:09 +00:00
Owner : key . PublicKey ( ) ,
2024-08-30 12:05:32 +00:00
}
if err := prm . Policy . DecodeString ( placementPolicy ) ; err != nil {
return cid . ID { } , fmt . Errorf ( "failed to build placement policy: %w" , err )
}
accessBox , err := frostFS . CreateContainer ( ctx , prm )
if err != nil {
return cid . ID { } , fmt . Errorf ( "create container in FrostFS: %w" , err )
}
return accessBox , nil
}