123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182 |
- // Copyright (C) 2019-2023 Nicola Murino
- //
- // This program is free software: you can redistribute it and/or modify
- // it under the terms of the GNU Affero General Public License as published
- // by the Free Software Foundation, version 3.
- //
- // This program is distributed in the hope that it will be useful,
- // but WITHOUT ANY WARRANTY; without even the implied warranty of
- // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- // GNU Affero General Public License for more details.
- //
- // You should have received a copy of the GNU Affero General Public License
- // along with this program. If not, see <https://www.gnu.org/licenses/>.
- //go:build awscontainer
- // +build awscontainer
- package service
- import (
- "context"
- "errors"
- "fmt"
- "time"
- "github.com/aws/aws-sdk-go-v2/aws"
- awsconfig "github.com/aws/aws-sdk-go-v2/config"
- "github.com/aws/aws-sdk-go-v2/feature/ec2/imds"
- "github.com/aws/aws-sdk-go-v2/service/marketplacemetering"
- "github.com/aws/aws-sdk-go-v2/service/secretsmanager"
- "github.com/google/uuid"
- "github.com/drakkan/sftpgo/v2/internal/config"
- "github.com/drakkan/sftpgo/v2/internal/dataprovider"
- "github.com/drakkan/sftpgo/v2/internal/httpd"
- "github.com/drakkan/sftpgo/v2/internal/logger"
- "github.com/drakkan/sftpgo/v2/internal/util"
- )
- const (
- installCodeName = "SFTPGo_Installation_Code"
- )
- var (
- awsProductCode = ""
- )
- func registerAWSContainer(disableAWSInstallationCode bool) error {
- if awsProductCode == "" {
- return errors.New("product code not set")
- }
- ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
- defer cancel()
- cfg, err := getAWSConfig(ctx)
- if err != nil {
- return fmt.Errorf("unable to get config to register AWS container: %w", err)
- }
- if !disableAWSInstallationCode {
- if err := setInstallationCode(cfg); err != nil {
- return err
- }
- }
- requestNonce, err := uuid.NewRandom()
- if err != nil {
- return fmt.Errorf("unable to generate nonce for metering API: %w", err)
- }
- svc := marketplacemetering.NewFromConfig(cfg)
- result, err := svc.RegisterUsage(ctx, &marketplacemetering.RegisterUsageInput{
- ProductCode: aws.String(awsProductCode),
- PublicKeyVersion: aws.Int32(1),
- Nonce: aws.String(requestNonce.String()),
- })
- if err != nil {
- return fmt.Errorf("unable to register API operation for AWSMarketplace Metering: %w", err)
- }
- logger.Debug(logSender, "", "API operation for AWSMarketplace Metering registered, token %q",
- util.GetStringFromPointer(result.Signature))
- return nil
- }
- func getAWSConfig(ctx context.Context) (aws.Config, error) {
- cfg, err := awsconfig.LoadDefaultConfig(ctx)
- if err != nil {
- return cfg, fmt.Errorf("unable to get config to register AWS container: %w", err)
- }
- if cfg.Region == "" {
- svc := imds.NewFromConfig(cfg)
- region, err := svc.GetRegion(ctx, &imds.GetRegionInput{})
- if err == nil {
- logger.Debug(logSender, "", "AWS region from imds %q", region.Region)
- cfg.Region = region.Region
- } else {
- logger.Warn(logSender, "", "unable to get region from imds, continuing anyway, error: %v", err)
- }
- }
- return cfg, nil
- }
- func setInstallationCode(cfg aws.Config) error {
- if dataprovider.HasAdmin() {
- return nil
- }
- installationCode := util.GenerateUniqueID()
- requestToken, err := uuid.NewRandom()
- if err != nil {
- return fmt.Errorf("unable to generate client request token: %w", err)
- }
- ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
- defer cancel()
- svc := secretsmanager.NewFromConfig(cfg)
- _, err = svc.GetSecretValue(ctx, &secretsmanager.GetSecretValueInput{
- SecretId: aws.String(installCodeName),
- })
- if err == nil {
- // update existing secret
- result, err := svc.UpdateSecret(ctx, &secretsmanager.UpdateSecretInput{
- SecretId: aws.String(installCodeName),
- ClientRequestToken: aws.String(requestToken.String()),
- SecretString: aws.String(installationCode),
- })
- if err != nil {
- return fmt.Errorf("unable to update installation code: %w", err)
- }
- logger.Debug(logSender, "", "installation code updated, secret name %q, arn %q, version id %q",
- util.GetStringFromPointer(result.Name), util.GetStringFromPointer(result.ARN),
- util.GetStringFromPointer(result.VersionId))
- } else {
- // create new secret
- logger.Debug(logSender, "", "unable to get the current installation secret, trying to create a new one, error: %v", err)
- result, err := svc.CreateSecret(ctx, &secretsmanager.CreateSecretInput{
- Name: aws.String(installCodeName),
- ClientRequestToken: aws.String(requestToken.String()),
- SecretString: aws.String(installationCode),
- })
- if err != nil {
- return fmt.Errorf("unable to create installation code: %w", err)
- }
- logger.Debug(logSender, "", "installation code set, secret name %q, arn %q, version id %q",
- util.GetStringFromPointer(result.Name), util.GetStringFromPointer(result.ARN),
- util.GetStringFromPointer(result.VersionId))
- }
- httpdConfig := config.GetHTTPDConfig()
- httpdConfig.Setup.InstallationCode = installationCode
- httpdConfig.Setup.InstallationCodeHint = "Installation code stored in Secrets Manager"
- config.SetHTTPDConfig(httpdConfig)
- httpd.SetInstallationCodeResolver(resolveInstallationCode)
- return nil
- }
- // function called to validate the user provided secret
- func resolveInstallationCode(defaultInstallationCode string) string {
- logger.Debug(logSender, "", "resolving installation code")
- ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
- defer cancel()
- cfg, err := getAWSConfig(ctx)
- if err != nil {
- logger.Error(logSender, "", "unable to get config to resolve installation code: %v", err)
- return defaultInstallationCode
- }
- svc := secretsmanager.NewFromConfig(cfg)
- result, err := svc.GetSecretValue(ctx, &secretsmanager.GetSecretValueInput{
- SecretId: aws.String(installCodeName),
- })
- if err != nil {
- logger.Error(logSender, "", "unable to resolve installation code: %v", err)
- return defaultInstallationCode
- }
- resolvedCode := util.GetStringFromPointer(result.SecretString)
- if resolvedCode == "" {
- logger.Error(logSender, "", "resolved installation code is empty")
- return defaultInstallationCode
- }
- logger.Debug(logSender, "", "installation code resolved")
- return resolvedCode
- }
|