sigs.k8s.io/cluster-api-provider-aws@v1.5.5/pkg/cloud/services/s3/s3.go (about) 1 /* 2 Copyright 2021 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package s3 18 19 import ( 20 "bytes" 21 "encoding/json" 22 "fmt" 23 "net/url" 24 "path" 25 26 "github.com/aws/aws-sdk-go/aws" 27 "github.com/aws/aws-sdk-go/aws/awserr" 28 "github.com/aws/aws-sdk-go/service/s3" 29 "github.com/aws/aws-sdk-go/service/s3/s3iface" 30 "github.com/aws/aws-sdk-go/service/sts" 31 "github.com/aws/aws-sdk-go/service/sts/stsiface" 32 "github.com/pkg/errors" 33 34 iam "sigs.k8s.io/cluster-api-provider-aws/iam/api/v1beta1" 35 "sigs.k8s.io/cluster-api-provider-aws/pkg/cloud/scope" 36 ) 37 38 // Service holds a collection of interfaces. 39 // The interfaces are broken down like this to group functions together. 40 // One alternative is to have a large list of functions from the ec2 client. 41 type Service struct { 42 scope scope.S3Scope 43 S3Client s3iface.S3API 44 STSClient stsiface.STSAPI 45 } 46 47 // NewService returns a new service given the api clients. 48 func NewService(s3Scope scope.S3Scope) *Service { 49 s3Client := scope.NewS3Client(s3Scope, s3Scope, s3Scope, s3Scope.InfraCluster()) 50 STSClient := scope.NewSTSClient(s3Scope, s3Scope, s3Scope, s3Scope.InfraCluster()) 51 52 return &Service{ 53 scope: s3Scope, 54 S3Client: s3Client, 55 STSClient: STSClient, 56 } 57 } 58 59 func (s *Service) ReconcileBucket() error { 60 if !s.bucketManagementEnabled() { 61 return nil 62 } 63 64 bucketName := s.bucketName() 65 66 if err := s.createBucketIfNotExist(bucketName); err != nil { 67 return errors.Wrap(err, "ensuring bucket exists") 68 } 69 70 if err := s.ensureBucketPolicy(bucketName); err != nil { 71 return errors.Wrap(err, "ensuring bucket policy") 72 } 73 74 return nil 75 } 76 77 func (s *Service) DeleteBucket() error { 78 if !s.bucketManagementEnabled() { 79 return nil 80 } 81 82 bucketName := s.bucketName() 83 84 log := s.scope.WithValues("name", bucketName) 85 86 log.Info("Deleting S3 Bucket") 87 88 _, err := s.S3Client.DeleteBucket(&s3.DeleteBucketInput{ 89 Bucket: aws.String(bucketName), 90 }) 91 if err == nil { 92 return nil 93 } 94 95 aerr, ok := err.(awserr.Error) 96 if !ok { 97 return errors.Wrap(err, "deleting S3 bucket") 98 } 99 100 switch aerr.Code() { 101 case s3.ErrCodeNoSuchBucket: 102 log.Info("Bucket already removed") 103 case "BucketNotEmpty": 104 log.Info("Bucket not empty, skipping removal") 105 default: 106 return errors.Wrap(aerr, "deleting S3 bucket") 107 } 108 109 return nil 110 } 111 112 func (s *Service) Create(m *scope.MachineScope, data []byte) (string, error) { 113 if !s.bucketManagementEnabled() { 114 return "", errors.New("requested object creation but bucket management is not enabled") 115 } 116 117 if m == nil { 118 return "", errors.New("machine scope can't be nil") 119 } 120 121 if len(data) == 0 { 122 return "", errors.New("got empty data") 123 } 124 125 bucket := s.bucketName() 126 key := s.bootstrapDataKey(m) 127 128 s.scope.Info("Creating object", "bucket_name", bucket, "key", key) 129 130 if _, err := s.S3Client.PutObject(&s3.PutObjectInput{ 131 Body: aws.ReadSeekCloser(bytes.NewReader(data)), 132 Bucket: aws.String(bucket), 133 Key: aws.String(key), 134 ServerSideEncryption: aws.String("aws:kms"), 135 }); err != nil { 136 return "", errors.Wrap(err, "putting object") 137 } 138 139 objectURL := &url.URL{ 140 Scheme: "s3", 141 Host: bucket, 142 Path: key, 143 } 144 145 return objectURL.String(), nil 146 } 147 148 func (s *Service) Delete(m *scope.MachineScope) error { 149 if !s.bucketManagementEnabled() { 150 return errors.New("requested object creation but bucket management is not enabled") 151 } 152 153 if m == nil { 154 return errors.New("machine scope can't be nil") 155 } 156 157 bucket := s.bucketName() 158 key := s.bootstrapDataKey(m) 159 160 s.scope.Info("Deleting object", "bucket_name", bucket, "key", key) 161 162 _, err := s.S3Client.DeleteObject(&s3.DeleteObjectInput{ 163 Bucket: aws.String(bucket), 164 Key: aws.String(key), 165 }) 166 if err == nil { 167 return nil 168 } 169 170 aerr, ok := err.(awserr.Error) 171 if !ok { 172 return errors.Wrap(err, "deleting S3 object") 173 } 174 175 switch aerr.Code() { 176 case s3.ErrCodeNoSuchBucket: 177 default: 178 return errors.Wrap(aerr, "deleting S3 object") 179 } 180 181 return nil 182 } 183 184 func (s *Service) createBucketIfNotExist(bucketName string) error { 185 input := &s3.CreateBucketInput{ 186 Bucket: aws.String(bucketName), 187 } 188 189 _, err := s.S3Client.CreateBucket(input) 190 if err == nil { 191 s.scope.Info("Created bucket", "bucket_name", bucketName) 192 193 return nil 194 } 195 196 aerr, ok := err.(awserr.Error) 197 if !ok { 198 return errors.Wrap(err, "creating S3 bucket") 199 } 200 201 switch aerr.Code() { 202 // If bucket already exists, all good. 203 // 204 // TODO: This will fail if bucket is shared with other cluster. 205 case s3.ErrCodeBucketAlreadyOwnedByYou: 206 return nil 207 default: 208 return errors.Wrap(aerr, "creating S3 bucket") 209 } 210 } 211 212 func (s *Service) ensureBucketPolicy(bucketName string) error { 213 bucketPolicy, err := s.bucketPolicy(bucketName) 214 if err != nil { 215 return errors.Wrap(err, "generating Bucket policy") 216 } 217 218 input := &s3.PutBucketPolicyInput{ 219 Bucket: aws.String(bucketName), 220 Policy: aws.String(bucketPolicy), 221 } 222 223 if _, err := s.S3Client.PutBucketPolicy(input); err != nil { 224 return errors.Wrap(err, "creating S3 bucket policy") 225 } 226 227 s.scope.V(4).Info("Updated bucket policy", "bucket_name", bucketName) 228 229 return nil 230 } 231 232 func (s *Service) bucketPolicy(bucketName string) (string, error) { 233 accountID, err := s.STSClient.GetCallerIdentity(&sts.GetCallerIdentityInput{}) 234 if err != nil { 235 return "", errors.Wrap(err, "getting account ID") 236 } 237 238 bucket := s.scope.Bucket() 239 240 statements := []iam.StatementEntry{ 241 { 242 Sid: "control-plane", 243 Effect: iam.EffectAllow, 244 Principal: map[iam.PrincipalType]iam.PrincipalID{ 245 iam.PrincipalAWS: []string{fmt.Sprintf("arn:aws:iam::%s:role/%s", *accountID.Account, bucket.ControlPlaneIAMInstanceProfile)}, 246 }, 247 Action: []string{"s3:GetObject"}, 248 Resource: []string{fmt.Sprintf("arn:aws:s3:::%s/control-plane/*", bucketName)}, 249 }, 250 } 251 252 for _, iamInstanceProfile := range bucket.NodesIAMInstanceProfiles { 253 statements = append(statements, iam.StatementEntry{ 254 Sid: iamInstanceProfile, 255 Effect: iam.EffectAllow, 256 Principal: map[iam.PrincipalType]iam.PrincipalID{ 257 iam.PrincipalAWS: []string{fmt.Sprintf("arn:aws:iam::%s:role/%s", *accountID.Account, iamInstanceProfile)}, 258 }, 259 Action: []string{"s3:GetObject"}, 260 Resource: []string{fmt.Sprintf("arn:aws:s3:::%s/node/*", bucketName)}, 261 }) 262 } 263 264 policy := iam.PolicyDocument{ 265 Version: "2012-10-17", 266 Statement: statements, 267 } 268 269 policyRaw, err := json.Marshal(policy) 270 if err != nil { 271 return "", errors.Wrap(err, "building bucket policy") 272 } 273 274 return string(policyRaw), nil 275 } 276 277 func (s *Service) bucketManagementEnabled() bool { 278 return s.scope.Bucket() != nil 279 } 280 281 func (s *Service) bucketName() string { 282 return s.scope.Bucket().Name 283 } 284 285 func (s *Service) bootstrapDataKey(m *scope.MachineScope) string { 286 // Use machine name as object key. 287 return path.Join(m.Role(), m.Name()) 288 }