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 }