1package alicloud 2 3import ( 4 "context" 5 "encoding/json" 6 "errors" 7 "fmt" 8 "strings" 9 "time" 10 11 "github.com/hashicorp/go-uuid" 12 "github.com/hashicorp/vault/sdk/framework" 13 "github.com/hashicorp/vault/sdk/logical" 14) 15 16func (b *backend) pathListRoles() *framework.Path { 17 return &framework.Path{ 18 Pattern: "role/?$", 19 Callbacks: map[logical.Operation]framework.OperationFunc{ 20 logical.ListOperation: b.operationRolesList, 21 }, 22 HelpSynopsis: pathListRolesHelpSyn, 23 HelpDescription: pathListRolesHelpDesc, 24 } 25} 26 27func (b *backend) pathRole() *framework.Path { 28 return &framework.Path{ 29 Pattern: "role/" + framework.GenericNameRegex("name"), 30 Fields: map[string]*framework.FieldSchema{ 31 "name": { 32 Type: framework.TypeLowerCaseString, 33 Description: "The name of the role.", 34 }, 35 "role_arn": { 36 Type: framework.TypeString, 37 Description: `ARN of the role to be assumed. If provided, inline_policies and 38remote_policies should be blank. At creation time, this role must have configured trusted actors, 39and the access key and secret that will be used to assume the role (in /config) must qualify 40as a trusted actor.`, 41 }, 42 "inline_policies": { 43 Type: framework.TypeString, 44 Description: "JSON of policies to be dynamically applied to users of this role.", 45 }, 46 "remote_policies": { 47 Type: framework.TypeStringSlice, 48 Description: `The name and type of each remote policy to be applied. 49Example: "name:AliyunRDSReadOnlyAccess,type:System".`, 50 }, 51 "ttl": { 52 Type: framework.TypeDurationSecond, 53 Description: `Duration in seconds after which the issued token should expire. Defaults 54to 0, in which case the value will fallback to the system/mount defaults.`, 55 }, 56 "max_ttl": { 57 Type: framework.TypeDurationSecond, 58 Description: "The maximum allowed lifetime of tokens issued using this role.", 59 }, 60 }, 61 ExistenceCheck: b.operationRoleExistenceCheck, 62 Callbacks: map[logical.Operation]framework.OperationFunc{ 63 logical.CreateOperation: b.operationRoleCreateUpdate, 64 logical.UpdateOperation: b.operationRoleCreateUpdate, 65 logical.ReadOperation: b.operationRoleRead, 66 logical.DeleteOperation: b.operationRoleDelete, 67 }, 68 HelpSynopsis: pathRolesHelpSyn, 69 HelpDescription: pathRolesHelpDesc, 70 } 71} 72 73func (b *backend) operationRoleExistenceCheck(ctx context.Context, req *logical.Request, data *framework.FieldData) (bool, error) { 74 entry, err := readRole(ctx, req.Storage, data.Get("name").(string)) 75 if err != nil { 76 return false, err 77 } 78 return entry != nil, nil 79} 80 81func (b *backend) operationRoleCreateUpdate(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) { 82 roleName := data.Get("name").(string) 83 if roleName == "" { 84 return nil, errors.New("name is required") 85 } 86 87 role, err := readRole(ctx, req.Storage, roleName) 88 if err != nil { 89 return nil, err 90 } 91 if role == nil && req.Operation == logical.UpdateOperation { 92 return nil, fmt.Errorf("no role found to update for %s", roleName) 93 } else if role == nil { 94 role = &roleEntry{} 95 } 96 97 if raw, ok := data.GetOk("role_arn"); ok { 98 role.RoleARN = raw.(string) 99 } 100 if raw, ok := data.GetOk("inline_policies"); ok { 101 policyDocsStr := raw.(string) 102 103 var policyDocs []map[string]interface{} 104 if err := json.Unmarshal([]byte(policyDocsStr), &policyDocs); err != nil { 105 return nil, err 106 } 107 108 // If any inline policies were set before, we need to clear them and consider 109 // these the new ones. 110 role.InlinePolicies = make([]*inlinePolicy, len(policyDocs)) 111 112 for i, policyDoc := range policyDocs { 113 uid, err := uuid.GenerateUUID() 114 if err != nil { 115 return nil, err 116 } 117 uid = strings.Replace(uid, "-", "", -1) 118 role.InlinePolicies[i] = &inlinePolicy{ 119 UUID: uid, 120 PolicyDocument: policyDoc, 121 } 122 } 123 } 124 if raw, ok := data.GetOk("remote_policies"); ok { 125 strPolicies := raw.([]string) 126 127 // If any remote policies were set before, we need to clear them and consider 128 // these the new ones. 129 role.RemotePolicies = make([]*remotePolicy, len(strPolicies)) 130 131 for i, strPolicy := range strPolicies { 132 policy := &remotePolicy{} 133 kvPairs := strings.Split(strPolicy, ",") 134 for _, kvPair := range kvPairs { 135 kvFields := strings.Split(kvPair, ":") 136 if len(kvFields) != 2 { 137 return nil, fmt.Errorf("unable to recognize pair in %s", kvPair) 138 } 139 switch kvFields[0] { 140 case "name": 141 policy.Name = kvFields[1] 142 case "type": 143 policy.Type = kvFields[1] 144 default: 145 return nil, fmt.Errorf("invalid key: %s", kvFields[0]) 146 } 147 } 148 if policy.Name == "" { 149 return nil, fmt.Errorf("policy name is required in %s", strPolicy) 150 } 151 if policy.Type == "" { 152 return nil, fmt.Errorf("policy type is required in %s", strPolicy) 153 } 154 role.RemotePolicies[i] = policy 155 } 156 } 157 if raw, ok := data.GetOk("ttl"); ok { 158 role.TTL = time.Duration(raw.(int)) * time.Second 159 } 160 if raw, ok := data.GetOk("max_ttl"); ok { 161 role.MaxTTL = time.Duration(raw.(int)) * time.Second 162 } 163 164 // Now that the role is built, validate it. 165 if role.MaxTTL > 0 && role.TTL > role.MaxTTL { 166 return nil, errors.New("ttl exceeds max_ttl") 167 } 168 if role.Type() == roleTypeSTS { 169 if len(role.RemotePolicies) > 0 { 170 return nil, errors.New("remote_policies must be blank when an arn is present") 171 } 172 if len(role.InlinePolicies) > 0 { 173 return nil, errors.New("inline_policies must be blank when an arn is present") 174 } 175 } else if len(role.InlinePolicies)+len(role.RemotePolicies) == 0 { 176 return nil, errors.New("must include an arn, or at least one of inline_policies or remote_policies") 177 } 178 179 entry, err := logical.StorageEntryJSON("role/"+roleName, role) 180 if err != nil { 181 return nil, err 182 } 183 if err := req.Storage.Put(ctx, entry); err != nil { 184 return nil, err 185 } 186 187 // Let's create a response that we're only going to return if there are warnings. 188 resp := &logical.Response{} 189 if role.Type() == roleTypeSTS && (role.TTL > 0 || role.MaxTTL > 0) { 190 resp.AddWarning("role_arn is set so ttl and max_ttl will be ignored because they're not editable on STS tokens") 191 } 192 if role.TTL > b.System().MaxLeaseTTL() { 193 resp.AddWarning(fmt.Sprintf("ttl of %d exceeds the system max ttl of %d, the latter will be used during login", role.TTL, b.System().MaxLeaseTTL())) 194 } 195 if len(resp.Warnings) > 0 { 196 return resp, nil 197 } 198 // No warnings, let's return a 204. 199 return nil, nil 200} 201 202func (b *backend) operationRoleRead(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) { 203 roleName := data.Get("name").(string) 204 if roleName == "" { 205 return nil, errors.New("name is required") 206 } 207 208 role, err := readRole(ctx, req.Storage, roleName) 209 if err != nil { 210 return nil, err 211 } 212 if role == nil { 213 return nil, nil 214 } 215 return &logical.Response{ 216 Data: map[string]interface{}{ 217 "role_arn": role.RoleARN, 218 "remote_policies": role.RemotePolicies, 219 "inline_policies": role.InlinePolicies, 220 "ttl": role.TTL / time.Second, 221 "max_ttl": role.MaxTTL / time.Second, 222 }, 223 }, nil 224} 225 226func (b *backend) operationRoleDelete(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) { 227 if err := req.Storage.Delete(ctx, "role/"+data.Get("name").(string)); err != nil { 228 return nil, err 229 } 230 return nil, nil 231} 232 233func (b *backend) operationRolesList(ctx context.Context, req *logical.Request, _ *framework.FieldData) (*logical.Response, error) { 234 entries, err := req.Storage.List(ctx, "role/") 235 if err != nil { 236 return nil, err 237 } 238 return logical.ListResponse(entries), nil 239} 240 241func readRole(ctx context.Context, s logical.Storage, roleName string) (*roleEntry, error) { 242 role, err := s.Get(ctx, "role/"+roleName) 243 if err != nil { 244 return nil, err 245 } 246 if role == nil { 247 return nil, nil 248 } 249 result := &roleEntry{} 250 if err := role.DecodeJSON(result); err != nil { 251 return nil, err 252 } 253 return result, nil 254} 255 256type roleType int 257 258const ( 259 roleTypeUnknown roleType = iota 260 roleTypeRAM 261 roleTypeSTS 262) 263 264func parseRoleType(nameOfRoleType string) (roleType, error) { 265 switch nameOfRoleType { 266 case "ram": 267 return roleTypeRAM, nil 268 case "sts": 269 return roleTypeSTS, nil 270 default: 271 return roleTypeUnknown, fmt.Errorf("received unknown role type: %s", nameOfRoleType) 272 } 273} 274 275func (t roleType) String() string { 276 switch t { 277 case roleTypeRAM: 278 return "ram" 279 case roleTypeSTS: 280 return "sts" 281 } 282 return "unknown" 283} 284 285type roleEntry struct { 286 RoleARN string `json:"role_arn"` 287 RemotePolicies []*remotePolicy `json:"remote_policies"` 288 InlinePolicies []*inlinePolicy `json:"inline_policies"` 289 TTL time.Duration `json:"ttl"` 290 MaxTTL time.Duration `json:"max_ttl"` 291} 292 293func (r *roleEntry) Type() roleType { 294 if r.RoleARN != "" { 295 return roleTypeSTS 296 } 297 return roleTypeRAM 298} 299 300// Policies don't have ARNs and instead, their unique combination of their name and type comprise 301// their unique identifier. 302type remotePolicy struct { 303 Name string `json:"name"` 304 Type string `json:"type"` 305} 306 307type inlinePolicy struct { 308 // UUID is used in naming the policy. The policy document has no fields 309 // that would reliably be there and make a beautiful, human-readable name. 310 // So instead, we generate a UUID for it and use that in the policy name, 311 // which is likewise returned when roles are read so generated policy names 312 // can be tied back to which policy document they're for. 313 UUID string `json:"hash"` 314 PolicyDocument map[string]interface{} `json:"policy_document"` 315} 316 317const pathListRolesHelpSyn = "List the existing roles in this backend." 318 319const pathListRolesHelpDesc = "Roles will be listed by the role name." 320 321const pathRolesHelpSyn = ` 322Read, write and reference policies and roles that API keys or STS credentials can be made for. 323` 324 325const pathRolesHelpDesc = ` 326This path allows you to read and write roles that are used to 327create API keys or STS credentials. 328 329If you supply a role ARN, that role must have been created to allow trusted actors, 330and the access key and secret that will be used to call AssumeRole (configured at 331the /config path) must qualify as a trusted actor. 332 333If you instead supply inline and/or remote policies to be applied, a user and API 334key will be dynamically created. The remote policies will be applied to that user, 335and the inline policies will also be dynamically created and applied. 336 337To obtain an API key or STS credential after the role is created, if the 338backend is mounted at "alicloud" and you create a role at "alicloud/roles/deploy", 339then a user could request access credentials at "alicloud/creds/deploy". 340 341To validate the keys, attempt to read an access key after writing the policy. 342` 343