1
0

awscontainer.go 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182
  1. // Copyright (C) 2019-2023 Nicola Murino
  2. //
  3. // This program is free software: you can redistribute it and/or modify
  4. // it under the terms of the GNU Affero General Public License as published
  5. // by the Free Software Foundation, version 3.
  6. //
  7. // This program is distributed in the hope that it will be useful,
  8. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  9. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  10. // GNU Affero General Public License for more details.
  11. //
  12. // You should have received a copy of the GNU Affero General Public License
  13. // along with this program. If not, see <https://www.gnu.org/licenses/>.
  14. //go:build awscontainer
  15. // +build awscontainer
  16. package service
  17. import (
  18. "context"
  19. "errors"
  20. "fmt"
  21. "time"
  22. "github.com/aws/aws-sdk-go-v2/aws"
  23. awsconfig "github.com/aws/aws-sdk-go-v2/config"
  24. "github.com/aws/aws-sdk-go-v2/feature/ec2/imds"
  25. "github.com/aws/aws-sdk-go-v2/service/marketplacemetering"
  26. "github.com/aws/aws-sdk-go-v2/service/secretsmanager"
  27. "github.com/google/uuid"
  28. "github.com/drakkan/sftpgo/v2/internal/config"
  29. "github.com/drakkan/sftpgo/v2/internal/dataprovider"
  30. "github.com/drakkan/sftpgo/v2/internal/httpd"
  31. "github.com/drakkan/sftpgo/v2/internal/logger"
  32. "github.com/drakkan/sftpgo/v2/internal/util"
  33. )
  34. const (
  35. installCodeName = "SFTPGo_Installation_Code"
  36. )
  37. var (
  38. awsProductCode = ""
  39. )
  40. func registerAWSContainer(disableAWSInstallationCode bool) error {
  41. if awsProductCode == "" {
  42. return errors.New("product code not set")
  43. }
  44. ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
  45. defer cancel()
  46. cfg, err := getAWSConfig(ctx)
  47. if err != nil {
  48. return fmt.Errorf("unable to get config to register AWS container: %w", err)
  49. }
  50. if !disableAWSInstallationCode {
  51. if err := setInstallationCode(cfg); err != nil {
  52. return err
  53. }
  54. }
  55. requestNonce, err := uuid.NewRandom()
  56. if err != nil {
  57. return fmt.Errorf("unable to generate nonce for metering API: %w", err)
  58. }
  59. svc := marketplacemetering.NewFromConfig(cfg)
  60. result, err := svc.RegisterUsage(ctx, &marketplacemetering.RegisterUsageInput{
  61. ProductCode: aws.String(awsProductCode),
  62. PublicKeyVersion: aws.Int32(1),
  63. Nonce: aws.String(requestNonce.String()),
  64. })
  65. if err != nil {
  66. return fmt.Errorf("unable to register API operation for AWSMarketplace Metering: %w", err)
  67. }
  68. logger.Debug(logSender, "", "API operation for AWSMarketplace Metering registered, token %q",
  69. util.GetStringFromPointer(result.Signature))
  70. return nil
  71. }
  72. func getAWSConfig(ctx context.Context) (aws.Config, error) {
  73. cfg, err := awsconfig.LoadDefaultConfig(ctx)
  74. if err != nil {
  75. return cfg, fmt.Errorf("unable to get config to register AWS container: %w", err)
  76. }
  77. if cfg.Region == "" {
  78. svc := imds.NewFromConfig(cfg)
  79. region, err := svc.GetRegion(ctx, &imds.GetRegionInput{})
  80. if err == nil {
  81. logger.Debug(logSender, "", "AWS region from imds %q", region.Region)
  82. cfg.Region = region.Region
  83. } else {
  84. logger.Warn(logSender, "", "unable to get region from imds, continuing anyway, error: %v", err)
  85. }
  86. }
  87. return cfg, nil
  88. }
  89. func setInstallationCode(cfg aws.Config) error {
  90. if dataprovider.HasAdmin() {
  91. return nil
  92. }
  93. installationCode := util.GenerateUniqueID()
  94. requestToken, err := uuid.NewRandom()
  95. if err != nil {
  96. return fmt.Errorf("unable to generate client request token: %w", err)
  97. }
  98. ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
  99. defer cancel()
  100. svc := secretsmanager.NewFromConfig(cfg)
  101. _, err = svc.GetSecretValue(ctx, &secretsmanager.GetSecretValueInput{
  102. SecretId: aws.String(installCodeName),
  103. })
  104. if err == nil {
  105. // update existing secret
  106. result, err := svc.UpdateSecret(ctx, &secretsmanager.UpdateSecretInput{
  107. SecretId: aws.String(installCodeName),
  108. ClientRequestToken: aws.String(requestToken.String()),
  109. SecretString: aws.String(installationCode),
  110. })
  111. if err != nil {
  112. return fmt.Errorf("unable to update installation code: %w", err)
  113. }
  114. logger.Debug(logSender, "", "installation code updated, secret name %q, arn %q, version id %q",
  115. util.GetStringFromPointer(result.Name), util.GetStringFromPointer(result.ARN),
  116. util.GetStringFromPointer(result.VersionId))
  117. } else {
  118. // create new secret
  119. logger.Debug(logSender, "", "unable to get the current installation secret, trying to create a new one, error: %v", err)
  120. result, err := svc.CreateSecret(ctx, &secretsmanager.CreateSecretInput{
  121. Name: aws.String(installCodeName),
  122. ClientRequestToken: aws.String(requestToken.String()),
  123. SecretString: aws.String(installationCode),
  124. })
  125. if err != nil {
  126. return fmt.Errorf("unable to create installation code: %w", err)
  127. }
  128. logger.Debug(logSender, "", "installation code set, secret name %q, arn %q, version id %q",
  129. util.GetStringFromPointer(result.Name), util.GetStringFromPointer(result.ARN),
  130. util.GetStringFromPointer(result.VersionId))
  131. }
  132. httpdConfig := config.GetHTTPDConfig()
  133. httpdConfig.Setup.InstallationCode = installationCode
  134. httpdConfig.Setup.InstallationCodeHint = "Installation code stored in Secrets Manager"
  135. config.SetHTTPDConfig(httpdConfig)
  136. httpd.SetInstallationCodeResolver(resolveInstallationCode)
  137. return nil
  138. }
  139. // function called to validate the user provided secret
  140. func resolveInstallationCode(defaultInstallationCode string) string {
  141. logger.Debug(logSender, "", "resolving installation code")
  142. ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
  143. defer cancel()
  144. cfg, err := getAWSConfig(ctx)
  145. if err != nil {
  146. logger.Error(logSender, "", "unable to get config to resolve installation code: %v", err)
  147. return defaultInstallationCode
  148. }
  149. svc := secretsmanager.NewFromConfig(cfg)
  150. result, err := svc.GetSecretValue(ctx, &secretsmanager.GetSecretValueInput{
  151. SecretId: aws.String(installCodeName),
  152. })
  153. if err != nil {
  154. logger.Error(logSender, "", "unable to resolve installation code: %v", err)
  155. return defaultInstallationCode
  156. }
  157. resolvedCode := util.GetStringFromPointer(result.SecretString)
  158. if resolvedCode == "" {
  159. logger.Error(logSender, "", "resolved installation code is empty")
  160. return defaultInstallationCode
  161. }
  162. logger.Debug(logSender, "", "installation code resolved")
  163. return resolvedCode
  164. }