github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/provider/ec2/provider.go (about) 1 // Copyright 2011-2014 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package ec2 5 6 import ( 7 "fmt" 8 "strings" 9 10 "github.com/juju/errors" 11 "github.com/juju/jsonschema" 12 "github.com/juju/loggo" 13 "gopkg.in/amz.v3/aws" 14 "gopkg.in/amz.v3/ec2" 15 16 "github.com/juju/juju/cloud" 17 "github.com/juju/juju/environs" 18 "github.com/juju/juju/environs/config" 19 "github.com/juju/juju/environs/context" 20 "github.com/juju/juju/environs/simplestreams" 21 "github.com/juju/juju/provider/common" 22 ) 23 24 var logger = loggo.GetLogger("juju.provider.ec2") 25 26 type environProvider struct { 27 environProviderCredentials 28 } 29 30 var providerInstance environProvider 31 32 // Version is part of the EnvironProvider interface. 33 func (environProvider) Version() int { 34 return 0 35 } 36 37 // Open is specified in the EnvironProvider interface. 38 func (p environProvider) Open(args environs.OpenParams) (environs.Environ, error) { 39 logger.Infof("opening model %q", args.Config.Name()) 40 41 e := new(environ) 42 e.cloud = args.Cloud 43 e.name = args.Config.Name() 44 45 // The endpoints in public-clouds.yaml from 2.0-rc2 46 // and before were wrong, so we use whatever is defined 47 // in goamz/aws if available. 48 if isBrokenCloud(e.cloud) { 49 if region, ok := aws.Regions[e.cloud.Region]; ok { 50 e.cloud.Endpoint = region.EC2Endpoint 51 } 52 } 53 54 var err error 55 e.ec2, err = awsClient(e.cloud) 56 if err != nil { 57 return nil, errors.Trace(err) 58 } 59 60 if err := e.SetConfig(args.Config); err != nil { 61 return nil, errors.Trace(err) 62 } 63 return e, nil 64 } 65 66 // isBrokenCloud reports whether the given CloudSpec is from an old, 67 // broken version of public-clouds.yaml. 68 func isBrokenCloud(cloud environs.CloudSpec) bool { 69 // The public-clouds.yaml from 2.0-rc2 and before was 70 // complete nonsense for general regions and for 71 // govcloud. The cn-north-1 region has a trailing slash, 72 // which we don't want as it means we won't match the 73 // simplestreams data. 74 switch cloud.Region { 75 case "us-east-1", "us-west-1", "us-west-2", "eu-west-1", 76 "eu-central-1", "ap-southeast-1", "ap-southeast-2", 77 "ap-northeast-1", "ap-northeast-2", "sa-east-1": 78 return cloud.Endpoint == fmt.Sprintf("https://%s.aws.amazon.com/v1.2/", cloud.Region) 79 case "cn-north-1": 80 return strings.HasSuffix(cloud.Endpoint, "/") 81 case "us-gov-west-1": 82 return cloud.Endpoint == "https://ec2.us-gov-west-1.amazonaws-govcloud.com" 83 } 84 return false 85 } 86 87 func awsClient(cloud environs.CloudSpec) (*ec2.EC2, error) { 88 if err := validateCloudSpec(cloud); err != nil { 89 return nil, errors.Annotate(err, "validating cloud spec") 90 } 91 92 credentialAttrs := cloud.Credential.Attributes() 93 accessKey := credentialAttrs["access-key"] 94 secretKey := credentialAttrs["secret-key"] 95 auth := aws.Auth{ 96 AccessKey: accessKey, 97 SecretKey: secretKey, 98 } 99 100 region := aws.Region{ 101 Name: cloud.Region, 102 EC2Endpoint: cloud.Endpoint, 103 } 104 signer := aws.SignV4Factory(cloud.Region, "ec2") 105 return ec2.New(auth, region, signer), nil 106 } 107 108 // CloudSchema returns the schema used to validate input for add-cloud. Since 109 // this provider does not support custom clouds, this always returns nil. 110 func (p environProvider) CloudSchema() *jsonschema.Schema { 111 return nil 112 } 113 114 // Ping tests the connection to the cloud, to verify the endpoint is valid. 115 func (p environProvider) Ping(ctx context.ProviderCallContext, endpoint string) error { 116 return errors.NotImplementedf("Ping") 117 } 118 119 // PrepareConfig is specified in the EnvironProvider interface. 120 func (p environProvider) PrepareConfig(args environs.PrepareConfigParams) (*config.Config, error) { 121 if err := validateCloudSpec(args.Cloud); err != nil { 122 return nil, errors.Annotate(err, "validating cloud spec") 123 } 124 // Set the default block-storage source. 125 attrs := make(map[string]interface{}) 126 if _, ok := args.Config.StorageDefaultBlockSource(); !ok { 127 attrs[config.StorageDefaultBlockSourceKey] = EBS_ProviderType 128 } 129 if len(attrs) == 0 { 130 return args.Config, nil 131 } 132 return args.Config.Apply(attrs) 133 } 134 135 func validateCloudSpec(c environs.CloudSpec) error { 136 if err := c.Validate(); err != nil { 137 return errors.Trace(err) 138 } 139 if c.Credential == nil { 140 return errors.NotValidf("missing credential") 141 } 142 if authType := c.Credential.AuthType(); authType != cloud.AccessKeyAuthType { 143 return errors.NotSupportedf("%q auth-type", authType) 144 } 145 return nil 146 } 147 148 // Validate is specified in the EnvironProvider interface. 149 func (environProvider) Validate(cfg, old *config.Config) (valid *config.Config, err error) { 150 newEcfg, err := validateConfig(cfg, old) 151 if err != nil { 152 return nil, fmt.Errorf("invalid EC2 provider config: %v", err) 153 } 154 return newEcfg.Apply(newEcfg.attrs) 155 } 156 157 // MetadataLookupParams returns parameters which are used to query image metadata to 158 // find matching image information. 159 func (p environProvider) MetadataLookupParams(region string) (*simplestreams.MetadataLookupParams, error) { 160 if region == "" { 161 return nil, fmt.Errorf("region must be specified") 162 } 163 ec2Region, ok := aws.Regions[region] 164 if !ok { 165 return nil, fmt.Errorf("unknown region %q", region) 166 } 167 return &simplestreams.MetadataLookupParams{ 168 Region: region, 169 Endpoint: ec2Region.EC2Endpoint, 170 }, nil 171 } 172 173 const badKeys = ` 174 The provided credentials could not be validated and 175 may not be authorized to carry out the request. 176 Ensure that your account is authorized to use the Amazon EC2 service and 177 that you are using the correct access keys. 178 These keys are obtained via the "Security Credentials" 179 page in the AWS console. 180 ` 181 182 const unauthorized = ` 183 Please subscribe to the requested Amazon service. 184 You are currently not authorized to use it. 185 New Amazon accounts might take some time to be activated while 186 your details are being verified.` 187 188 // verifyCredentials issues a cheap, non-modifying/idempotent request to EC2 to 189 // verify the configured credentials. If verification fails, a user-friendly 190 // error will be returned, and the original error will be logged at debug 191 // level. 192 var verifyCredentials = func(e *environ, ctx context.ProviderCallContext) error { 193 _, err := e.ec2.AccountAttributes() 194 return maybeConvertCredentialError(err, ctx) 195 } 196 197 // maybeConvertCredentialError examines the error received from the provider. 198 // Authentication related errors are wrapped in common.CredentialNotValid. 199 // Authorisation related errors are annotated with an additional 200 // user-friendly explanation. 201 // All other errors are returned un-wrapped and not annotated. 202 var maybeConvertCredentialError = func(err error, ctx context.ProviderCallContext) error { 203 if err == nil { 204 return nil 205 } 206 207 convert := func(converted error) error { 208 callbackErr := ctx.InvalidateCredential(converted.Error()) 209 if callbackErr != nil { 210 // We want to proceed with the actual proessing but still keep a log of a problem. 211 logger.Infof("callback to invalidate model credential failed with %v", converted) 212 } 213 return converted 214 } 215 216 if err, ok := err.(*ec2.Error); ok { 217 // EC2 error codes are from https://docs.aws.amazon.com/AWSEC2/latest/APIReference/errors-overview.html. 218 switch err.Code { 219 case "AuthFailure": 220 return convert(common.CredentialNotValidf(err, badKeys)) 221 case "InvalidClientTokenId": 222 return convert(common.CredentialNotValidf(err, badKeys)) 223 case "MissingAuthenticationToken": 224 return convert(common.CredentialNotValidf(err, badKeys)) 225 case "Blocked": 226 return convert(common.CredentialNotValidf(err, "\nYour Amazon account is currently blocked.")) 227 case "CustomerKeyHasBeenRevoked": 228 return convert(common.CredentialNotValidf(err, "\nYour Amazon keys have been revoked.")) 229 case "PendingVerification": 230 return convert(common.CredentialNotValidf(err, "\nYour account is pending verification by Amazon.")) 231 case "SignatureDoesNotMatch": 232 return convert(common.CredentialNotValidf(err, badKeys)) 233 case "OptInRequired": 234 return errors.Annotate(err, unauthorized) 235 case "UnauthorizedOperation": 236 return errors.Annotate(err, unauthorized) 237 default: 238 // This error is unrelated to access keys, account or credentials... 239 return err 240 } 241 } 242 return err 243 }