awscontainer.go 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166
  1. //go:build awscontainer
  2. // +build awscontainer
  3. package service
  4. import (
  5. "context"
  6. "errors"
  7. "fmt"
  8. "time"
  9. "github.com/aws/aws-sdk-go-v2/aws"
  10. awsconfig "github.com/aws/aws-sdk-go-v2/config"
  11. "github.com/aws/aws-sdk-go-v2/feature/ec2/imds"
  12. "github.com/aws/aws-sdk-go-v2/service/marketplacemetering"
  13. "github.com/aws/aws-sdk-go-v2/service/secretsmanager"
  14. "github.com/google/uuid"
  15. "github.com/drakkan/sftpgo/v2/config"
  16. "github.com/drakkan/sftpgo/v2/dataprovider"
  17. "github.com/drakkan/sftpgo/v2/httpd"
  18. "github.com/drakkan/sftpgo/v2/logger"
  19. "github.com/drakkan/sftpgo/v2/util"
  20. )
  21. const (
  22. installCodeName = "SFTPGo_Installation_Code"
  23. )
  24. var (
  25. awsProductCode = ""
  26. )
  27. func registerAWSContainer() error {
  28. if awsProductCode == "" {
  29. return errors.New("product code not set")
  30. }
  31. ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
  32. defer cancel()
  33. cfg, err := getAWSConfig(ctx)
  34. if err != nil {
  35. return fmt.Errorf("unable to get config to register AWS container: %w", err)
  36. }
  37. if err := setInstallationCode(cfg); err != nil {
  38. return err
  39. }
  40. requestNonce, err := uuid.NewRandom()
  41. if err != nil {
  42. return fmt.Errorf("unable to generate nonce for metering API: %w", err)
  43. }
  44. svc := marketplacemetering.NewFromConfig(cfg)
  45. result, err := svc.RegisterUsage(ctx, &marketplacemetering.RegisterUsageInput{
  46. ProductCode: aws.String(awsProductCode),
  47. PublicKeyVersion: aws.Int32(1),
  48. Nonce: aws.String(requestNonce.String()),
  49. })
  50. if err != nil {
  51. return fmt.Errorf("unable to register API operation for AWSMarketplace Metering: %w", err)
  52. }
  53. logger.Debug(logSender, "", "API operation for AWSMarketplace Metering registered, token %#v",
  54. util.GetStringFromPointer(result.Signature))
  55. return nil
  56. }
  57. func getAWSConfig(ctx context.Context) (aws.Config, error) {
  58. cfg, err := awsconfig.LoadDefaultConfig(ctx)
  59. if err != nil {
  60. return cfg, fmt.Errorf("unable to get config to register AWS container: %w", err)
  61. }
  62. if cfg.Region == "" {
  63. svc := imds.NewFromConfig(cfg)
  64. region, err := svc.GetRegion(ctx, &imds.GetRegionInput{})
  65. if err == nil {
  66. logger.Debug(logSender, "", "AWS region from imds %#v", region.Region)
  67. cfg.Region = region.Region
  68. } else {
  69. logger.Warn(logSender, "", "unable to get region from imds, continuing anyway, error: %v", err)
  70. }
  71. }
  72. return cfg, nil
  73. }
  74. func setInstallationCode(cfg aws.Config) error {
  75. if dataprovider.HasAdmin() {
  76. return nil
  77. }
  78. installationCode := util.GenerateUniqueID()
  79. requestToken, err := uuid.NewRandom()
  80. if err != nil {
  81. return fmt.Errorf("unable to generate client request token: %w", err)
  82. }
  83. ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
  84. defer cancel()
  85. svc := secretsmanager.NewFromConfig(cfg)
  86. _, err = svc.GetSecretValue(ctx, &secretsmanager.GetSecretValueInput{
  87. SecretId: aws.String(installCodeName),
  88. })
  89. if err == nil {
  90. // update existing secret
  91. result, err := svc.UpdateSecret(ctx, &secretsmanager.UpdateSecretInput{
  92. SecretId: aws.String(installCodeName),
  93. ClientRequestToken: aws.String(requestToken.String()),
  94. SecretString: aws.String(installationCode),
  95. })
  96. if err != nil {
  97. return fmt.Errorf("unable to update installation code: %w", err)
  98. }
  99. logger.Debug(logSender, "", "installation code updated, secret name %#v, arn %#v, version id %#v",
  100. util.GetStringFromPointer(result.Name), util.GetStringFromPointer(result.ARN),
  101. util.GetStringFromPointer(result.VersionId))
  102. } else {
  103. // create new secret
  104. logger.Debug(logSender, "", "unable to get the current installation secret, trying to create a new one, error: %v", err)
  105. result, err := svc.CreateSecret(ctx, &secretsmanager.CreateSecretInput{
  106. Name: aws.String(installCodeName),
  107. ClientRequestToken: aws.String(requestToken.String()),
  108. SecretString: aws.String(installationCode),
  109. })
  110. if err != nil {
  111. return fmt.Errorf("unable to create installation code: %w", err)
  112. }
  113. logger.Debug(logSender, "", "installation code set, secret name %#v, arn %#v, version id %#v",
  114. util.GetStringFromPointer(result.Name), util.GetStringFromPointer(result.ARN),
  115. util.GetStringFromPointer(result.VersionId))
  116. }
  117. httpdConfig := config.GetHTTPDConfig()
  118. httpdConfig.Setup.InstallationCode = installationCode
  119. httpdConfig.Setup.InstallationCodeHint = "Installation code stored in Secrets Manager"
  120. config.SetHTTPDConfig(httpdConfig)
  121. httpd.SetInstallationCodeResolver(resolveInstallationCode)
  122. return nil
  123. }
  124. // function called to validate the user provided secret
  125. func resolveInstallationCode(defaultInstallationCode string) string {
  126. logger.Debug(logSender, "", "resolving installation code")
  127. ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
  128. defer cancel()
  129. cfg, err := getAWSConfig(ctx)
  130. if err != nil {
  131. logger.Error(logSender, "", "unable to get config to resolve installation code: %v", err)
  132. return defaultInstallationCode
  133. }
  134. svc := secretsmanager.NewFromConfig(cfg)
  135. result, err := svc.GetSecretValue(ctx, &secretsmanager.GetSecretValueInput{
  136. SecretId: aws.String(installCodeName),
  137. })
  138. if err != nil {
  139. logger.Error(logSender, "", "unable to resolve installation code: %v", err)
  140. return defaultInstallationCode
  141. }
  142. resolvedCode := util.GetStringFromPointer(result.SecretString)
  143. if resolvedCode == "" {
  144. logger.Error(logSender, "", "resolved installation code is empty")
  145. return defaultInstallationCode
  146. }
  147. logger.Debug(logSender, "", "installation code resolved")
  148. return resolvedCode
  149. }