github.com/khulnasoft-lab/defsec@v1.0.5-0.20230827010352-5e9f46893d95/internal/adapters/cloud/aws/s3/s3.go (about)

     1  package s3
     2  
     3  import (
     4  	"strings"
     5  
     6  	"github.com/aws/aws-sdk-go/aws/awserr"
     7  	"github.com/khulnasoft-lab/defsec/pkg/concurrency"
     8  	defsecTypes "github.com/khulnasoft-lab/defsec/pkg/types"
     9  
    10  	s3api "github.com/aws/aws-sdk-go-v2/service/s3"
    11  	s3types "github.com/aws/aws-sdk-go-v2/service/s3/types"
    12  	"github.com/khulnasoft-lab/defsec/internal/adapters/cloud/aws"
    13  	"github.com/khulnasoft-lab/defsec/pkg/providers/aws/iam"
    14  	"github.com/khulnasoft-lab/defsec/pkg/providers/aws/s3"
    15  	"github.com/khulnasoft-lab/defsec/pkg/state"
    16  	"github.com/liamg/iamgo"
    17  )
    18  
    19  type adapter struct {
    20  	*aws.RootAdapter
    21  	api *s3api.Client
    22  }
    23  
    24  func init() {
    25  	aws.RegisterServiceAdapter(&adapter{})
    26  }
    27  
    28  func (a *adapter) Provider() string {
    29  	return "aws"
    30  }
    31  
    32  func (a *adapter) Name() string {
    33  	return "s3"
    34  }
    35  
    36  func (a *adapter) Adapt(root *aws.RootAdapter, state *state.State) error {
    37  
    38  	a.RootAdapter = root
    39  	a.api = s3api.NewFromConfig(root.SessionConfig())
    40  
    41  	var err error
    42  	state.AWS.S3.Buckets, err = a.getBuckets()
    43  	if err != nil {
    44  		return err
    45  	}
    46  
    47  	return nil
    48  }
    49  
    50  func (a *adapter) getBuckets() (buckets []s3.Bucket, err error) {
    51  	a.Tracker().SetServiceLabel("Discovering buckets...")
    52  	apiBuckets, err := a.api.ListBuckets(a.Context(), &s3api.ListBucketsInput{})
    53  	if err != nil {
    54  		return buckets, err
    55  	}
    56  
    57  	a.Tracker().SetTotalResources(len(apiBuckets.Buckets))
    58  	a.Tracker().SetServiceLabel("Discovering buckets...")
    59  	return concurrency.Adapt(apiBuckets.Buckets, a.RootAdapter, a.adaptBucket), nil
    60  }
    61  
    62  func (a *adapter) adaptBucket(bucket s3types.Bucket) (*s3.Bucket, error) {
    63  
    64  	if bucket.Name == nil {
    65  		return nil, nil
    66  	}
    67  
    68  	location, err := a.api.GetBucketLocation(a.Context(), &s3api.GetBucketLocationInput{
    69  		Bucket: bucket.Name,
    70  	})
    71  	if err != nil {
    72  		a.Debug("Error getting bucket location: %s", err)
    73  		return nil, nil
    74  	}
    75  	region := string(location.LocationConstraint)
    76  	if region == "" { // Region us-east-1 have a LocationConstraint of null (???)
    77  		region = "us-east-1"
    78  	}
    79  	if region != a.Region() {
    80  		return nil, nil
    81  	}
    82  
    83  	bucketMetadata := a.CreateMetadata(*bucket.Name)
    84  
    85  	name := defsecTypes.StringDefault("", bucketMetadata)
    86  	if bucket.Name != nil {
    87  		name = defsecTypes.String(*bucket.Name, bucketMetadata)
    88  	}
    89  
    90  	b := s3.Bucket{
    91  		Metadata:                      bucketMetadata,
    92  		Name:                          name,
    93  		PublicAccessBlock:             a.getPublicAccessBlock(bucket.Name, bucketMetadata),
    94  		BucketPolicies:                a.getBucketPolicies(bucket.Name, bucketMetadata),
    95  		Encryption:                    a.getBucketEncryption(bucket.Name, bucketMetadata),
    96  		Versioning:                    a.getBucketVersioning(bucket.Name, bucketMetadata),
    97  		Logging:                       a.getBucketLogging(bucket.Name, bucketMetadata),
    98  		ACL:                           a.getBucketACL(bucket.Name, bucketMetadata),
    99  		Objects:                       a.getObjects(bucket.Name, bucketMetadata),
   100  		AccelerateConfigurationStatus: a.getBucketAccelarate(bucket.Name, bucketMetadata),
   101  		LifecycleConfiguration:        a.getBucketLifecycle(bucket.Name, bucketMetadata),
   102  		BucketLocation:                a.getBucketLocation(bucket.Name, bucketMetadata),
   103  		Website:                       a.getWebsite(bucket.Name, bucketMetadata),
   104  	}
   105  
   106  	return &b, nil
   107  
   108  }
   109  
   110  func (a *adapter) getPublicAccessBlock(bucketName *string, metadata defsecTypes.Metadata) *s3.PublicAccessBlock {
   111  
   112  	publicAccessBlocks, err := a.api.GetPublicAccessBlock(a.Context(), &s3api.GetPublicAccessBlockInput{
   113  		Bucket: bucketName,
   114  	})
   115  	if err != nil {
   116  		// nolint
   117  		if awsError, ok := err.(awserr.Error); ok {
   118  			if awsError.Code() == "NoSuchPublicAccessBlockConfiguration" {
   119  				return nil
   120  			}
   121  		}
   122  		a.Debug("Error getting public access block: %s", err)
   123  		return nil
   124  	}
   125  
   126  	if publicAccessBlocks == nil {
   127  		return nil
   128  	}
   129  
   130  	config := publicAccessBlocks.PublicAccessBlockConfiguration
   131  	pab := s3.NewPublicAccessBlock(metadata)
   132  
   133  	pab.BlockPublicACLs = defsecTypes.Bool(config.BlockPublicAcls, metadata)
   134  	pab.BlockPublicPolicy = defsecTypes.Bool(config.BlockPublicPolicy, metadata)
   135  	pab.IgnorePublicACLs = defsecTypes.Bool(config.IgnorePublicAcls, metadata)
   136  	pab.RestrictPublicBuckets = defsecTypes.Bool(config.RestrictPublicBuckets, metadata)
   137  
   138  	return &pab
   139  }
   140  
   141  func (a *adapter) getBucketPolicies(bucketName *string, metadata defsecTypes.Metadata) []iam.Policy {
   142  	var bucketPolicies []iam.Policy
   143  
   144  	bucketPolicy, err := a.api.GetBucketPolicy(a.Context(), &s3api.GetBucketPolicyInput{Bucket: bucketName})
   145  	if err != nil {
   146  		// nolint
   147  		if awsError, ok := err.(awserr.Error); ok {
   148  			if awsError.Code() == "NoSuchBucketPolicy" {
   149  				return nil
   150  			}
   151  		}
   152  		a.Debug("Error getting public access block: %s", err)
   153  		return nil
   154  
   155  	}
   156  
   157  	if bucketPolicy.Policy != nil {
   158  		policyDocument, err := iamgo.ParseString(*bucketPolicy.Policy)
   159  		if err != nil {
   160  			a.Debug("Error parsing bucket policy: %s", err)
   161  			return bucketPolicies
   162  		}
   163  
   164  		bucketPolicies = append(bucketPolicies, iam.Policy{
   165  			Metadata: metadata,
   166  			Name:     defsecTypes.StringDefault("", metadata),
   167  			Document: iam.Document{
   168  				Metadata: metadata,
   169  				Parsed:   *policyDocument,
   170  			},
   171  			Builtin: defsecTypes.Bool(false, metadata),
   172  		})
   173  	}
   174  
   175  	return bucketPolicies
   176  
   177  }
   178  
   179  func (a *adapter) getBucketEncryption(bucketName *string, metadata defsecTypes.Metadata) s3.Encryption {
   180  	bucketEncryption := s3.Encryption{
   181  		Metadata:  metadata,
   182  		Enabled:   defsecTypes.BoolDefault(false, metadata),
   183  		Algorithm: defsecTypes.StringDefault("", metadata),
   184  		KMSKeyId:  defsecTypes.StringDefault("", metadata),
   185  	}
   186  
   187  	encryption, err := a.api.GetBucketEncryption(a.Context(), &s3api.GetBucketEncryptionInput{Bucket: bucketName})
   188  	if err != nil {
   189  		// nolint
   190  		if awsError, ok := err.(awserr.Error); ok {
   191  			if awsError.Code() == "ServerSideEncryptionConfigurationNotFoundError" {
   192  				return bucketEncryption
   193  			}
   194  		}
   195  		a.Debug("Error getting encryption block: %s", err)
   196  		return bucketEncryption
   197  	}
   198  
   199  	if encryption.ServerSideEncryptionConfiguration != nil && len(encryption.ServerSideEncryptionConfiguration.Rules) > 0 {
   200  		defaultEncryption := encryption.ServerSideEncryptionConfiguration.Rules[0]
   201  		algorithm := defaultEncryption.ApplyServerSideEncryptionByDefault.SSEAlgorithm
   202  		bucketEncryption.Algorithm = defsecTypes.StringDefault(string(algorithm), metadata)
   203  		bucketEncryption.Enabled = defsecTypes.Bool(defaultEncryption.BucketKeyEnabled, metadata)
   204  		if algorithm != "" {
   205  			bucketEncryption.Enabled = defsecTypes.Bool(true, metadata)
   206  		}
   207  		kmsKeyId := defaultEncryption.ApplyServerSideEncryptionByDefault.KMSMasterKeyID
   208  		if kmsKeyId != nil {
   209  			bucketEncryption.KMSKeyId = defsecTypes.StringDefault(*kmsKeyId, metadata)
   210  		}
   211  	}
   212  
   213  	return bucketEncryption
   214  }
   215  
   216  func (a *adapter) getBucketVersioning(bucketName *string, metadata defsecTypes.Metadata) s3.Versioning {
   217  	bucketVersioning := s3.Versioning{
   218  		Metadata:  metadata,
   219  		Enabled:   defsecTypes.BoolDefault(false, metadata),
   220  		MFADelete: defsecTypes.BoolDefault(false, metadata),
   221  	}
   222  
   223  	versioning, err := a.api.GetBucketVersioning(a.Context(), &s3api.GetBucketVersioningInput{Bucket: bucketName})
   224  	if err != nil {
   225  		// nolint
   226  		if awsError, ok := err.(awserr.Error); ok {
   227  			if awsError.Code() == "NotImplemented" {
   228  				return bucketVersioning
   229  			}
   230  		}
   231  		a.Debug("Error getting bucket versioning: %s", err)
   232  		return bucketVersioning
   233  	}
   234  
   235  	if versioning.Status == s3types.BucketVersioningStatusEnabled {
   236  		bucketVersioning.Enabled = defsecTypes.Bool(true, metadata)
   237  	}
   238  
   239  	bucketVersioning.MFADelete = defsecTypes.Bool(versioning.MFADelete == s3types.MFADeleteStatusEnabled, metadata)
   240  
   241  	return bucketVersioning
   242  }
   243  
   244  func (a *adapter) getBucketLogging(bucketName *string, metadata defsecTypes.Metadata) s3.Logging {
   245  
   246  	bucketLogging := s3.Logging{
   247  		Metadata:     metadata,
   248  		Enabled:      defsecTypes.BoolDefault(false, metadata),
   249  		TargetBucket: defsecTypes.StringDefault("", metadata),
   250  	}
   251  
   252  	logging, err := a.api.GetBucketLogging(a.Context(), &s3api.GetBucketLoggingInput{Bucket: bucketName})
   253  	if err != nil {
   254  		a.Debug("Error getting bucket logging: %s", err)
   255  		return bucketLogging
   256  	}
   257  
   258  	if logging.LoggingEnabled != nil {
   259  		bucketLogging.Enabled = defsecTypes.Bool(true, metadata)
   260  		bucketLogging.TargetBucket = defsecTypes.StringDefault(*logging.LoggingEnabled.TargetBucket, metadata)
   261  	}
   262  
   263  	return bucketLogging
   264  }
   265  
   266  func (a *adapter) getBucketACL(bucketName *string, metadata defsecTypes.Metadata) defsecTypes.StringValue {
   267  	acl, err := a.api.GetBucketAcl(a.Context(), &s3api.GetBucketAclInput{Bucket: bucketName})
   268  	if err != nil {
   269  		a.Debug("Error getting bucket ACL: %s", err)
   270  		return defsecTypes.StringDefault("private", metadata)
   271  	}
   272  
   273  	aclValue := "private"
   274  
   275  	for _, grant := range acl.Grants {
   276  		if grant.Grantee != nil && grant.Grantee.Type == "Group" {
   277  			switch grant.Permission {
   278  			case s3types.PermissionWrite, s3types.PermissionWriteAcp:
   279  				aclValue = "public-read-write"
   280  			case s3types.PermissionRead, s3types.PermissionReadAcp:
   281  				if strings.HasSuffix(*grant.Grantee.URI, "AuthenticatedUsers") {
   282  					aclValue = "authenticated-read"
   283  				} else {
   284  					aclValue = "public-read"
   285  				}
   286  			}
   287  		}
   288  	}
   289  
   290  	return defsecTypes.String(aclValue, metadata)
   291  }
   292  
   293  func (a *adapter) getBucketLifecycle(bucketName *string, metadata defsecTypes.Metadata) []s3.Rules {
   294  	output, err := a.api.GetBucketLifecycleConfiguration(a.Context(), &s3api.GetBucketLifecycleConfigurationInput{
   295  		Bucket: bucketName,
   296  	})
   297  	if err != nil {
   298  		return nil
   299  	}
   300  	var rules []s3.Rules
   301  	for _, r := range output.Rules {
   302  		rules = append(rules, s3.Rules{
   303  			Metadata: metadata,
   304  			Status:   defsecTypes.String(string(r.Status), metadata),
   305  		})
   306  	}
   307  	return rules
   308  }
   309  
   310  func (a *adapter) getBucketAccelarate(bucketName *string, metadata defsecTypes.Metadata) defsecTypes.StringValue {
   311  	output, err := a.api.GetBucketAccelerateConfiguration(a.Context(), &s3api.GetBucketAccelerateConfigurationInput{
   312  		Bucket: bucketName,
   313  	})
   314  	if err != nil {
   315  		return defsecTypes.StringDefault("", metadata)
   316  	}
   317  	return defsecTypes.String(string(output.Status), metadata)
   318  }
   319  
   320  func (a *adapter) getBucketLocation(bucketName *string, metadata defsecTypes.Metadata) defsecTypes.StringValue {
   321  	output, err := a.api.GetBucketLocation(a.Context(), &s3api.GetBucketLocationInput{
   322  		Bucket: bucketName,
   323  	})
   324  	if err != nil {
   325  		return defsecTypes.StringDefault("", metadata)
   326  	}
   327  	return defsecTypes.String(string(output.LocationConstraint), metadata)
   328  }
   329  
   330  func (a *adapter) getObjects(bucketName *string, metadata defsecTypes.Metadata) []s3.Contents {
   331  	output, err := a.api.ListObjects(a.Context(), &s3api.ListObjectsInput{
   332  		Bucket: bucketName,
   333  	})
   334  	if err != nil {
   335  		return nil
   336  	}
   337  	var obj []s3.Contents
   338  	for range output.Contents {
   339  		obj = append(obj, s3.Contents{
   340  			Metadata: metadata,
   341  		})
   342  	}
   343  	return obj
   344  }
   345  
   346  func (a *adapter) getWebsite(bucketName *string, metadata defsecTypes.Metadata) *s3.Website {
   347  
   348  	website, err := a.api.GetBucketWebsite(a.Context(), &s3api.GetBucketWebsiteInput{
   349  		Bucket: bucketName,
   350  	})
   351  	if err != nil {
   352  		a.Debug("Error getting website: %s", err)
   353  		return nil
   354  	}
   355  
   356  	if website == nil {
   357  		return nil
   358  	} else {
   359  		return &s3.Website{
   360  			Metadata: metadata,
   361  		}
   362  	}
   363  }