# IAM身份提供商安全配置最佳实践
本最佳实践仅针对IAM旧版控制台中[基于SAML协议的虚拟用户SSO](https://support.huaweicloud.com/usermanual-iam/iam_08_0002.html)和[基于OIDC协议的虚拟用户SSO](https://support.huaweicloud.com/usermanual-iam/iam_08_0010.html)这两种配置进行指导说明，不包含[基于SAML协议的IAM用户SSO](https://support.huaweicloud.com/usermanual-iam/iam_08_0254.html)，也不包含IAM新版控制台中的[基于SAML协议的信任委托SSO](https://support.huaweicloud.com/usermanual-iam5/iam_01_1308.html)和[基于OIDC协议的信任委托SSO](https://support.huaweicloud.com/usermanual-iam5/iam_01_1321.html)这两种配置。
#### 联邦登录的两个环节
IAM身份提供商的联邦登录可以分为认证和授权两个环节，共同完成登录流程的控制，详细信息请参见下表：
表1联邦登录的两个环节 
| 环节   | 发生位置   | 作用                                                                                                                                                                                                                            |
|:---|:---|:---|
| 认证环节 | 企业IdP | 校验用户账号密码，确认这是一个合法的IdP用户，并签发携带用户属性的SAML断言或OIDC令牌。                                                                                                                                                                              |
| 授权环节    | IAM SP  | 对SAML断言或OIDC令牌签名做验证后，按已配置的[身份转换规则](https://support.huaweicloud.com/usermanual-iam/iam_08_0006.html)（虚拟用户SSO）或者[外部身份ID](https://support.huaweicloud.com/usermanual-iam/iam_08_0257.html)（IAM用户SSO），对IdP用户进行**身份准入** 和**权限授予**。 |
   
IAM作为服务提供方，负责授权环节的配置管理，如果使用者在授权环节配置不当，则可能导致权限范围失控。
本实践面向计划将企业身份系统（IdP）对接到IAM身份提供商（SP）的架构与安全设计人员，围绕授权环节的两个核心问题给出配置建议：
- **身份准入**：哪些IdP用户可以登录华为云。
- **权限授予**：登录华为云的IdP用户拥有哪些操作权限。
 
基于SAML协议与基于OIDC协议的虚拟用户SSO，在IAM身份转换规则的配置逻辑上完全一致，为避免重复，下文中的配置案例统一以基于SAML协议的虚拟用户SSO为例进行讲解。对于基于OIDC协议的虚拟用户SSO，仅需按下表将案例中的协议要素做对应替换，文中的问题分析与最佳实践同样适用：
表2协议要素说明 
| **SAML协议要素**                                           | **OIDC协议要素**                          |
|:---|:---|
| 断言（Assertion）                                           | OIDC令牌（ID Token）                    |
| 断言的验签                                                  | OIDC令牌的验签                            |
| 断言Subject元素中的NameID（__NAMEID__）                          | OIDC令牌中的sub声明                       |
| 断言AttributeStatement元素中的属性（如Groups、FirstName、LastName） | OIDC令牌中的声明（如Groups、FirstName、LastName） |
| 属性中携带多个AttributeValue（多值属性）                            | 数组形式的多值声明                            |
   
即下文出现的"SAML断言"均可替换为"OIDC令牌"理解，"断言中的属性"均可替换为"OIDC令牌中的声明"理解，不再单独给出OIDC协议的配置案例。
#### IAM身份提供商安全配置的常见问题与最佳实践
- **常见问题一：身份准入配置过宽，导致任意IdP用户均可登录华为云**
  假设您已经按照[身份提供商概述](https://support.huaweicloud.com/usermanual-iam/iam_08_0001.html)配置好了基于SAML协议或OIDC协议的虚拟用户身份提供商，并在IAM中创建了ecs_readonly用户组，给该用户组授予了ECS ReadOnlyAccess的系统策略，其中IAM身份提供商的身份转换规则如下：
  ```
  [
      {
          "remote": [
              {
                  "type": "__NAMEID__"
              }
          ],
          "local": [
              {
                  "user": {
                      "name": "FederationUser"
                  }
              }
          ]
      },
      {
          "remote": [
              {
                  "any_one_of": [
                      "idp_ecs_readonly"
                  ],
                  "type": "Groups"
              }
          ],
          "local": [
              {
                  "group": {
                      "name": "ecs_readonly"
                  }
              }
          ]
      }
  ]
  ```
  上述身份转换规则一共有两条规则，第一条为身份准入规则，第二条为权限授予规则，表达的含义为：
  1. 在SAML断言验签通过后，只要SAML断言的Subject元素中存在NameID子元素，那么就可以将IdP用户映射为一个华为云虚拟用户，这个虚拟用户名称为FederationUser。
  
  2. 如果SAML断言的AttributeStatement元素中存在Groups属性，Groups属性中只要有任意一个属性值为idp_ecs_readonly，那么这个名称为FederationUser的虚拟用户就拥有IAM中ecs_readonly用户组的权限。
   
  这个身份转换规则有以下两个问题：
  1. 在remote侧的映射中使用了"__NAMEID__"这个通用type。
  
  2. 将第一条身份准入规则单独抽取出来但是没有使用条件运算符。
   
  一般情况下，SAML断言的Subject元素中都会携带NameID子元素，而该规则未配置条件运算符进行校验，导致IdP中的所有用户均可通过第一条规则登录华为云。即便用户不属于IdP侧的idp_ecs_readonly用户组，也仅会影响其登录华为云后是否被授予相应权限，并不能阻止其完成登录。
  
- **最佳实践一：合理配置身份准入规则，只允许目标IdP用户登录华为云**
  正确的做法可以将身份准入规则和权限授予规则合并在一起，然后利用条件运算符限制身份准入：
  ```
  [
      {
          "remote": [
              {
                  "type": "FirstName"
              },
              {
                  "type": "LastName"
              },
              {
                  "any_one_of": [
                      "idp_ecs_readonly"
                  ],
                  "type": "Groups"
              }
          ],
          "local": [
              {
                  "user": {
                      "name": "{0} {1}"
                  }
              },
              {
                  "group": {
                      "name": "ecs_readonly"
                  }
              }
          ]
      }
  ]
  ```
  在上述身份转换规则中，并没有对remote中的FirstName、LastName这两个type添加条件运算符，因为需要将它们的值传递到local的user.name的占位符中，但是因为将身份准入规则和权限授予规则合并在一起了，整个身份转换规则都会受到any_one_of这个条件运算符控制，表达式的含义为：
  在SAML断言验签通过后，当断言中存在Groups属性且Groups属性中有一个属性值为idp_ecs_readonly时，如果断言中的FirstName与LastName属性也存在，那么就可以将IdP用户映射为一个华为云虚拟用户，这个虚拟用户名称为${FirstName} ${LastName}，且该虚拟用户拥有IAM中ecs_readonly用户组的权限。
  这样便可以做到只允许IdP侧idp_ecs_readonly用户组的用户登录华为云，而其他不相关的IdP合法用户无法登录华为云，即使他们也有FirstName和LastName属性。
  ![](https://support.huaweicloud.com/bestpractice-iam/public_sys-resources/note_3.0-zh-cn.png)
  针对最佳实践一中的身份转换规则，如果any_one_of配置空数组，例如："any_one_of": \[\]，则表示拒绝所有登录，不论IdP用户在IdP侧是否加入了用户组。
  
- **常见问题二：未进行最小化权限授予，导致任意登录用户获取较大权限**
  ```
  [
      {
          "remote": [
              {
                  "type": "FirstName"
              },
              {
                  "type": "LastName"
              }
          ],
          "local": [
              {
                  "user": {
                      "name": "{0} {1}"
                  }
              },
              {
                  "group": {
                      "name": "admin"
                  }
              }
          ]
      }
  ]
  ```
  上述身份转换规则，在问题一的基础上又引入了权限授予过大的问题，表达的含义为：
  在SAML断言验签通过后，如果断言中的FirstName与LastName属性存在，那么就可以将IdP用户映射为一个华为云虚拟用户，这个虚拟用户名称为${FirstName} ${LastName}，且该虚拟用户拥有IAM中admin用户组的权限。
  一般来说，IdP中的所有用户都拥有FirstName与LastName这种表示姓名的基础属性，因此上述规则等价于IdP中的所有用户都拥有操作华为云资源的所有权限。
  
- **最佳实践二：按照工作职能分组，进行最小化权限授予**
  正确的做法是对权限授予规则进行拆分，将IdP中的不同用户按照工作职能分配IdP的不同用户组中，然后将IdP用户组映射到IAM对应的用户组中，最后根据不同的IAM用户组进行最小化权限授权。假设您已经在IAM中创建了所需的ecs_readonly、ecs_admin用户组，并分别授予了ECS ReadOnlyAccess、ECS FullAccess的系统策略，那么一个最小化权限授予的身份转换规则如下：
  ```
  [
      {
          "remote": [
              {
                  "type": "FirstName"
              },
              {
                  "type": "LastName"
              },
              {
                  "any_one_of": [
                      "idp_ecs_readonly"
                  ],
                  "type": "Groups"
              }
          ],
          "local": [
              {
                  "user": {
                      "name": "{0} {1}"
                  }
              },
              {
                  "group": {
                      "name": "ecs_readonly"
                  }
              }
          ]
      },
      {
          "remote": [
              {
                  "type": "FirstName"
              },
              {
                  "type": "LastName"
              },
              {
                  "any_one_of": [
                      "idp_ecs_admin"
                  ],
                  "type": "Groups"
              }
          ],
          "local": [
              {
                  "user": {
                      "name": "{0} {1}"
                  }
              },
              {
                  "group": {
                      "name": "ecs_admin"
                  }
              }
          ]
      }
  ]
  ```
  在上述身份转换规则中，根据工作职能拆分为两条规则，将允许ECS只读用户分配到idp_ecs_readonly用户组中，将ECS管理员用户分配到idp_ecs_admin用户组中，表达的含义为：
  1. 在SAML断言验签通过后，当断言中存在Groups属性且Groups属性中有一个属性值为idp_ecs_readonly时，如果断言中的FirstName与LastName属性也存在，那么就可以将IdP用户映射为一个华为云虚拟用户，这个虚拟用户名称为${FirstName} ${LastName}，且该虚拟用户拥有IAM中ecs_readonly用户组的权限。
  
  2. 在SAML断言验签通过后，当断言中存在Groups属性且Groups属性中有一个属性值为idp_ecs_admin时，如果断言中的FirstName与LastName属性也存在，那么就可以将IdP用户映射为一个华为云虚拟用户，这个虚拟用户名称为${FirstName} ${LastName}，且该虚拟用户拥有IAM中ecs_admin用户组的权限。
   
  这样便做到了最小化权限授予，IdP中不同用户组的用户联邦登录后分别拥有不同的权限，需要有新用户登录华为云时，只需要将其加入IdP中的相应用户组即可。
  ![](https://support.huaweicloud.com/bestpractice-iam/public_sys-resources/note_3.0-zh-cn.png)
  在使用[多值属性]时，如果在IdP侧同时加入了idp_ecs_readonly和idp_ecs_admin用户组，那么该虚拟用户会同时映射到华为云的ecs_readonly和ecs_admin用户组。
  
- **常见问题三：将IdP用户组直接映射到IAM用户组时，未配置条件运算符导致身份准入失控**
  在使用身份转换规则将IdP的用户组直接映射到IAM的用户组时，如果remote侧对Groups这个type仅做存在性检查而未使用条件运算符，则身份准入效果取决于IdP对Groups属性的处理行为：
  ```
  [
      {
          "remote": [
              {
                  "type": "FirstName"
              },
              {
                  "type": "LastName"
              },
              {
                  "type": "Groups"
              }
          ],
          "local": [
              {
                  "user": {
                      "name": "{0} {1}"
                  }
              },
              {
                  "groups": "{2}"
              }
          ]
      }
  ]
  ```
  上述身份转换规则表达的含义为：
  在SAML断言验签通过后，如果断言中的FirstName、LastName、Groups属性都存在，那么就可以将IdP用户映射为一个华为云虚拟用户，这个虚拟用户名称为${FirstName} ${LastName}，并且将IdP侧的Groups属性值直接作为IAM侧的用户组名进行匹配，实际该虚拟用户是否拥有权限取决于IdP侧的用户组是否在IAM中真实存在。
  注意，这个时候如果IdP用户未在IdP侧加入任何用户组，那么其身份**准入效果** 取决于IdP对Groups属性的处理行为：
  表3IdP用户未加入任何用户组时IdP的处理行为 
  | **IdP用户未加入任何用户组时IdP的处理行为** | **IAM识别到的Groups属性状态** | **身份准入结果** |
  |:---|:---|:---|
  | IdP发送的断言不存在Groups属性       | 属性不存在                | 登录失败       |
  | IdP发送的断言存在Groups属性但值为空      | 属性存在，值为空字符串          | 登录成功     |
     
  也就是说，如果IdP在用户未加入任何用户组时仍发送空值的Groups属性，则该规则等价于所有拥有FirstName和LastName的IdP用户均可登录华为云，虽然登录后没有任何用户组权限，但仍然存在身份准入配置过宽的问题。
  
- **最佳实践三：将IdP用户组直接映射到IAM用户组时，应配合条件运算符确保身份准入可控**
  如果需要将IdP的用户组名直接映射到IAM的同名用户组，正确的做法是使用条件运算符进行身份准入控制：
  ```
  [
      {
          "remote": [
              {
                  "type": "FirstName"
              },
              {
                  "type": "LastName"
              },
              {
                  "type": "Groups"
              },
              {
                  "any_one_of": [
                      "idp_ecs_readonly",
                      "idp_ecs_admin"
                  ],
                  "type": "Groups"
              }
          ],
          "local": [
              {
                  "user": {
                      "name": "{0} {1}"
                  }
              },
              {
                  "groups": "{2}"
              }
          ]
      }
  ]
  ```
  在上述身份转换规则中，在remote中有两个Groups的type，这是因为拥有any_one_of的Groups的type只用于做身份准入，确保了只有在断言的Groups属性中包含idp_ecs_readonly或idp_ecs_admin的用户才能登录华为云，它不能提取{2}的值。所以，想要在local的groups中仍然使用{2}直接从断言中提取IdP的用户组名，就必须在remote中额外携带一个不带条件运算符的Groups的type。整体表达的含义为：
  在SAML断言验签通过后，当断言的Groups属性中存在idp_ecs_readonly或idp_ecs_admin时，如果断言中的FirstName与LastName属性也存在，那么就可以将IdP用户映射为一个华为云虚拟用户，这个虚拟用户名称为${FirstName} ${LastName}，并且将IdP侧的Groups属性值直接作为IAM侧的用户组名进行匹配，实际该虚拟用户是否拥有权限仍然取决于IdP侧的用户组是否在IAM中真实存在；
  这样即使IdP在用户未加入任何用户组时发送了空值的Groups属性，any_one_of条件运算符也会校验失败从而阻止该用户登录华为云。
  
 
#### IAM身份提供商使用的注意事项
- **注意事项一：针对断言中AttributeStatement元素的同名独立属性，IAM身份转换规则只会取最后一个属性的值**
  例如，对于**最佳实践三** 中的身份转换规则，如果IdP断言中的AttributeStatement元素如下：
  ```
  <saml:AttributeStatement>
      <saml:Attribute Name="Groups"
                      NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic">
          <saml:AttributeValue xmlns:xs="http://www.w3.org/2001/XMLSchema"
                               xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                               xsi:type="xs:string">idp_ecs_readonly</saml:AttributeValue>
      </saml:Attribute>
      <saml:Attribute Name="Groups"
                      NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic">
          <saml:AttributeValue xmlns:xs="http://www.w3.org/2001/XMLSchema"
                               xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                               xsi:type="xs:string">idp_other</saml:AttributeValue>
      </saml:Attribute>
      <saml:Attribute FriendlyName="First Name"
                      Name="FirstName"
                      NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic">
          <saml:AttributeValue xmlns:xs="http://www.w3.org/2001/XMLSchema"
                               xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                               xsi:type="xs:string">FirstName</saml:AttributeValue>
      </saml:Attribute>
      <saml:Attribute FriendlyName="Last Name"
                      Name="LastName"
                      NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic">
          <saml:AttributeValue xmlns:xs="http://www.w3.org/2001/XMLSchema"
                               xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                               xsi:type="xs:string">LastName</saml:AttributeValue>
      </saml:Attribute>
  </saml:AttributeStatement>
  ```
  那么，针对上述断言，IAM在local中取到的groups为\["idp_other"\]，因此不满足any_one_of运算符中的值，所以会登录失败。想要传递IdP中多个用户组标准做法是使用**多值属性** ，IdP断言设定Groups为多值属性的示例如下：
  ```
  <saml:AttributeStatement>
      <saml:Attribute Name="Groups"
                      NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic">
          <saml:AttributeValue xmlns:xs="http://www.w3.org/2001/XMLSchema"
                               xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                               xsi:type="xs:string">idp_ecs_readonly</saml:AttributeValue>
          <saml:AttributeValue xmlns:xs="http://www.w3.org/2001/XMLSchema"
                               xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                               xsi:type="xs:string">idp_other</saml:AttributeValue>
      </saml:Attribute>
      <saml:Attribute FriendlyName="First Name"
                      Name="FirstName"
                      NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic">
          <saml:AttributeValue xmlns:xs="http://www.w3.org/2001/XMLSchema"
                               xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                               xsi:type="xs:string">FirstName</saml:AttributeValue>
      </saml:Attribute>
      <saml:Attribute FriendlyName="Last Name"
                      Name="LastName"
                      NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic">
          <saml:AttributeValue xmlns:xs="http://www.w3.org/2001/XMLSchema"
                               xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                               xsi:type="xs:string">LastName</saml:AttributeValue>
      </saml:Attribute>
  </saml:AttributeStatement>
  ```
  针对上述断言，IAM在local中取到的groups为\["idp_ecs_readonly", "idp_other"\]，因此满足any_one_of运算符中的值，所以登录成功。
  ![](https://support.huaweicloud.com/bestpractice-iam/public_sys-resources/note_3.0-zh-cn.png)
  上述注意事项源于SAML的XML结构允许在AttributeStatement元素中重复出现同名Attribute元素。在基于OIDC协议的虚拟用户SSO中，OIDC令牌为JSON格式，声明的名称唯一，不存在同名独立声明只取最后一个值的问题；如需传递多个用户组，在OIDC协议下直接使用数组形式的多值声明即可，例如"Groups": \["idp_ecs_readonly", "idp_other"\]。
  
 
