1package alicloud 2 3import ( 4 "context" 5 "errors" 6 "fmt" 7 "github.com/hashicorp/vault/sdk/helper/jsonutil" 8 "math/rand" 9 "time" 10 11 "github.com/hashicorp/vault-plugin-secrets-alicloud/clients" 12 "github.com/hashicorp/vault/sdk/framework" 13 "github.com/hashicorp/vault/sdk/logical" 14) 15 16func (b *backend) pathCreds() *framework.Path { 17 return &framework.Path{ 18 Pattern: "creds/" + framework.GenericNameRegex("name"), 19 Fields: map[string]*framework.FieldSchema{ 20 "name": { 21 Type: framework.TypeLowerCaseString, 22 Description: "The name of the role.", 23 }, 24 }, 25 Callbacks: map[logical.Operation]framework.OperationFunc{ 26 logical.ReadOperation: b.operationCredsRead, 27 }, 28 HelpSynopsis: pathCredsHelpSyn, 29 HelpDescription: pathCredsHelpDesc, 30 } 31} 32 33func (b *backend) operationCredsRead(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) { 34 roleName := data.Get("name").(string) 35 if roleName == "" { 36 return nil, errors.New("name is required") 37 } 38 39 role, err := readRole(ctx, req.Storage, roleName) 40 if err != nil { 41 return nil, err 42 } 43 if role == nil { 44 // Attempting to read a role that doesn't exist. 45 return nil, nil 46 } 47 48 creds, err := readCredentials(ctx, req.Storage) 49 if err != nil { 50 return nil, err 51 } 52 if creds == nil { 53 return nil, errors.New("unable to create secret because no credentials are configured") 54 } 55 56 switch role.Type() { 57 58 case roleTypeSTS: 59 client, err := clients.NewSTSClient(b.sdkConfig, creds.AccessKey, creds.SecretKey) 60 if err != nil { 61 return nil, err 62 } 63 assumeRoleResp, err := client.AssumeRole(generateRoleSessionName(req.DisplayName, roleName), role.RoleARN) 64 if err != nil { 65 return nil, err 66 } 67 // Parse the expiration into a time, so that when we return it from our API it's formatted 68 // the same way as how _we_ format times, which could differ from this over time. 69 expiration, err := time.Parse("2006-01-02T15:04:05Z", assumeRoleResp.Credentials.Expiration) 70 if err != nil { 71 return nil, err 72 } 73 resp := b.Secret(secretType).Response(map[string]interface{}{ 74 "access_key": assumeRoleResp.Credentials.AccessKeyId, 75 "secret_key": assumeRoleResp.Credentials.AccessKeySecret, 76 "security_token": assumeRoleResp.Credentials.SecurityToken, 77 "expiration": expiration, 78 }, map[string]interface{}{ 79 "role_type": roleTypeSTS.String(), 80 }) 81 82 // Set the secret TTL to appropriately match the expiration of the token. 83 ttl := expiration.Sub(time.Now()) 84 resp.Secret.TTL = ttl 85 resp.Secret.MaxTTL = ttl 86 87 // STS credentials are purposefully short-lived and aren't renewable. 88 resp.Secret.Renewable = false 89 return resp, nil 90 91 case roleTypeRAM: 92 client, err := clients.NewRAMClient(b.sdkConfig, creds.AccessKey, creds.SecretKey) 93 if err != nil { 94 return nil, err 95 } 96 97 /* 98 Now we're embarking upon a multi-step process that could fail at any time. 99 If it does, let's do our best to clean up after ourselves. Success will be 100 our flag at the end indicating whether we should leave things be, or clean 101 things up, based on how we exit this method. Since defer statements are 102 last-in-first-out, it will perfectly reverse the order of everything just 103 like we need. 104 */ 105 success := false 106 107 createUserResp, err := client.CreateUser(generateUsername(req.DisplayName, roleName)) 108 if err != nil { 109 return nil, err 110 } 111 defer func() { 112 if success { 113 return 114 } 115 if err := client.DeleteUser(createUserResp.User.UserName); err != nil { 116 if b.Logger().IsError() { 117 b.Logger().Error(fmt.Sprintf("unable to delete user %s", createUserResp.User.UserName), err) 118 } 119 } 120 }() 121 122 // We need to gather up all the names and types of the remote policies we're 123 // about to create so we can detach and delete them later. 124 inlinePolicies := make([]*remotePolicy, len(role.InlinePolicies)) 125 126 for i, inlinePolicy := range role.InlinePolicies { 127 128 // By combining the userName with the particular policy's UUID, 129 // it'll be possible to figure out who this policy is for and which one 130 // it is using the policy name alone. The max length of a policy name is 131 // 128, but given the max lengths of our username and inline policy UUID, 132 // we will always remain well under that here. 133 policyName := createUserResp.User.UserName + "-" + inlinePolicy.UUID 134 135 policyDoc, err := jsonutil.EncodeJSON(inlinePolicy.PolicyDocument) 136 if err != nil { 137 return nil, err 138 } 139 140 createPolicyResp, err := client.CreatePolicy(policyName, string(policyDoc)) 141 if err != nil { 142 return nil, err 143 } 144 145 inlinePolicies[i] = &remotePolicy{ 146 Name: createPolicyResp.Policy.PolicyName, 147 Type: createPolicyResp.Policy.PolicyType, 148 } 149 150 // This defer is in this loop on purpose. 151 defer func() { 152 if success { 153 return 154 } 155 if err := client.DeletePolicy(createPolicyResp.Policy.PolicyName); err != nil { 156 if b.Logger().IsError() { 157 b.Logger().Error(fmt.Sprintf("unable to delete policy %s", createPolicyResp.Policy.PolicyName), err) 158 } 159 } 160 }() 161 162 if err := client.AttachPolicy(createUserResp.User.UserName, createPolicyResp.Policy.PolicyName, createPolicyResp.Policy.PolicyType); err != nil { 163 return nil, err 164 } 165 // This defer is also in this loop on purpose. 166 defer func() { 167 if success { 168 return 169 } 170 if err := client.DetachPolicy(createUserResp.User.UserName, createPolicyResp.Policy.PolicyName, createPolicyResp.Policy.PolicyType); err != nil { 171 if b.Logger().IsError() { 172 b.Logger().Error(fmt.Sprintf( 173 "unable to detach policy name:%s, type:%s from user:%s", createPolicyResp.Policy.PolicyName, createPolicyResp.Policy.PolicyType, createUserResp.User.UserName)) 174 } 175 } 176 }() 177 } 178 179 for _, remotePol := range role.RemotePolicies { 180 if err := client.AttachPolicy(createUserResp.User.UserName, remotePol.Name, remotePol.Type); err != nil { 181 return nil, err 182 } 183 // This defer is also in this loop on purpose. 184 // Separate these strings from the remotePol pointer so the defer statement will retain the correct values 185 // due to pointer reuse for the remotePol var on each iteration of the loop. 186 remotePolName := remotePol.Name 187 remotePolType := remotePol.Type 188 defer func() { 189 if success { 190 return 191 } 192 if err := client.DetachPolicy(createUserResp.User.UserName, remotePolName, remotePolType); err != nil { 193 if b.Logger().IsError() { 194 b.Logger().Error(fmt.Sprintf("unable to detach policy name:%s, type:%s from user:%s", remotePolName, remotePolType, createUserResp.User.UserName)) 195 } 196 } 197 }() 198 } 199 200 accessKeyResp, err := client.CreateAccessKey(createUserResp.User.UserName) 201 if err != nil { 202 return nil, err 203 } 204 // It's unlikely we wouldn't have success at this point because there are no further errors returned below, but 205 // there could be a panic if somehow one of the objects below were missing a pointer, so let's play it safe and 206 // add a defer rolling back the access key if that happens. 207 defer func() { 208 if success { 209 return 210 } 211 if err := client.DeleteAccessKey(createUserResp.User.UserName, accessKeyResp.AccessKey.AccessKeyId); err != nil { 212 if b.Logger().IsError() { 213 b.Logger().Error(fmt.Sprintf("unable to delete access key for username:%s", createUserResp.User.UserName)) 214 } 215 } 216 }() 217 218 resp := b.Secret(secretType).Response(map[string]interface{}{ 219 "access_key": accessKeyResp.AccessKey.AccessKeyId, 220 "secret_key": accessKeyResp.AccessKey.AccessKeySecret, 221 }, map[string]interface{}{ 222 "role_type": roleTypeRAM.String(), 223 "role_name": roleName, 224 "username": createUserResp.User.UserName, 225 "access_key_id": accessKeyResp.AccessKey.AccessKeyId, 226 "inline_policies": inlinePolicies, 227 "remote_policies": role.RemotePolicies, 228 }) 229 if role.TTL != 0 { 230 resp.Secret.TTL = role.TTL 231 } 232 if role.MaxTTL != 0 { 233 resp.Secret.MaxTTL = role.MaxTTL 234 } 235 236 success = true 237 return resp, nil 238 239 default: 240 return nil, fmt.Errorf("unsupported role type: %s", role.Type()) 241 } 242} 243 244// The max length of a username per AliCloud is 64. 245func generateUsername(displayName, roleName string) string { 246 return generateName(displayName, roleName, 64) 247} 248 249// The max length of a role session name per AliCloud is 32. 250func generateRoleSessionName(displayName, roleName string) string { 251 return generateName(displayName, roleName, 32) 252} 253 254func generateName(displayName, roleName string, maxLength int) string { 255 name := fmt.Sprintf("%s-%s-", displayName, roleName) 256 257 // The time and random number take up to 15 more in length, so if the name 258 // is too long we need to trim it. 259 if len(name) > maxLength-15 { 260 name = name[:maxLength-15] 261 } 262 r := rand.New(rand.NewSource(time.Now().UnixNano())) 263 return fmt.Sprintf("%s%d-%d", name, time.Now().Unix(), r.Intn(10000)) 264} 265 266const pathCredsHelpSyn = ` 267Generate an API key or STS credential using the given role's configuration.' 268` 269 270const pathCredsHelpDesc = ` 271This path will generate a new API key or STS credential for 272accessing AliCloud. The RAM policies used to back this key pair will be 273configured on the role. For example, if this backend is mounted at "alicloud", 274then "alicloud/creds/deploy" would generate access keys for the "deploy" role. 275 276The API key or STS credential will have a ttl associated with it. API keys can 277be renewed or revoked as described here: 278https://www.vaultproject.io/docs/concepts/lease.html, 279but STS credentials do not support renewal or revocation. 280` 281