注意:
- 此教程需要访问 Oracle Cloud。要注册免费账户,请参阅开始使用 Oracle Cloud Infrastructure Free Tier 。
- 它使用 Oracle Cloud Infrastructure 身份证明、租户和区间示例值。完成实验室时,请将这些值替换为特定于您的云环境的值。
启用 Oracle Cloud Infrastructure Identity and Access Management 身份证明的自动轮换
简介
本教程有助于自动轮换 API 密钥、验证令牌、客户密钥和 OCI 控制台登录密码的 Oracle Cloud Infrastructure Identity and Access Management (OCI IAM) 身份证明。它基于 Oracle Cloud Guard 问题修复和 OCI 通知服务。它还弥补了默认响应器规则之间轮换 OCI IAM 身份证明的差距。
Oracle Cloud Guard 根据策略中定义的规则检测问题。规则一旦满足,就会产生问题。
这是 Oracle Cloud Infrastructure 基础(CIS OCI 基础)互联网安全中心 (Center for Internet Security Oracle Cloud Infrastructure Foundations) 基准测试建议的安全优秀实践,每 90 天轮换一次身份证明。
注:此解决方案已针对默认身份域进行了测试。
目标
-
通过自动化修复 Oracle Cloud Guard 问题,轮换 Oracle Cloud Infrastructure (OCI) 验证令牌、API 密钥、客户密钥和 OCI 控制台密码。以下 Oracle Cloud Guard 问题用于自动轮换 OCI IAM 身份证明。
- API 密钥太旧
- IAM 验证令牌太旧
- IAM 客户密钥太旧
- 密码太旧
先决条件
OCI IAM 管理员应创建:
-
OCI Functions 的动态组和策略,用于调用、补救问题并发送电子邮件通知。
-
Dynamic Group(动态组):为函数区间创建动态组。有关详细信息,请参阅创建动态组。
# Replace OCID for function compartment All {resource.type = 'fnfunc', resource.compartment.id = '<function-compartment>'} Example: All {resource.type = 'fnfunc', resource.compartment.id = 'ocid1.compartment.oc1..aaaaaaaanovmfmmnonjjyxeq4jyghszj2eczlrkgj5svnxrt...'}
-
策略:创建策略以分配函数的动态组。有关详细信息,请参阅策略入门。
# Replace dynamic-group-name, function-compartment and OCID of vault secret for OCI function Allow dynamic-group <dynamic-group-name> to inspect compartments in tenancy Allow dynamic-group <dynamic-group-name> to inspect users in tenancy Allow dynamic-group <dynamic-group-name> to manage users in tenancy where any {request.permission = 'USER_UPDATE', request.permission = 'USER_AUTHTOKEN_SET', request.permission = 'USER_AUTHTOKEN_REMOVE', request.permission = 'USER_SECRETKEY_ADD', request.permission = 'USER_SECRETKEY_REMOVE', request.permission = 'USER_UIPASS_SET', request.permission = 'USER_APIKEY_ADD', request.permission = 'USER_APIKEY_REMOVE'} Allow dynamic-group <dynamic-group-name> to read repos in compartment <function-compartment> Allow dynamic-group <dynamic-group-name> to use secret-family in compartment <vault-compartment> where target.secret.id='<ocid of OCI vault secret to store rotated OCI secrets>' Allow dynamic-group <dynamic-group-name> to use ons-topics in tenancy
-
-
区间、组和策略,用于开发人员创建密钥和部署函数。
# Replace group-name as per your tenancy Allow group <group-name> to manage repos in compartment <function-compartment> Allow group <group-name> to manage vaults in compartment <function-compartment> Allow group <group-name> to manage keys in compartment <function-compartment> Allow group <group-name> to manage secret-family in compartment <function-compartment> Allow group <group-name> to manage functions-family in compartment <function-compartment> Allow group <group-name> to use cloud-shell in tenancy Allow group <group-name> to use virtual-network-family in compartment <network-compartment> Allow group <group-name> to read objectstorage-namespaces in tenancy
-
OCI 通知主题,用于 SecOps 团队接收电子邮件通知。
-
Oracle Cloud Infrastructure Secrets in Vault ,用于存储由自动化创建的轮换 OCI IAM 凭证。
Note : Make sure you have copied existing OCI IAM credentials for Oracle Cloud Infrastructure Secrets in Vault before running this automation for the first time.
任务 1:创建和部署 OCI 函数
-
登录到 OCI 控制台,然后从顶部导航中单击云 Shell 。
注:您还可以使用自己的 IDE 工具创建和部署函数。
-
使用云 shell 中的 Fn 项目 CLI 创建函数。有关更多信息,请参阅创建、部署和调用 Helloworld 函数。
fn init --runtime python <function-name> Example: fn init --runtime python rotatecred-func
-
将目录更改为新创建的目录。
-
使用以下命令创建应用程序以部署函数。
# Specify the OCID of subnet fn create app <app-name> --annotation oracle.com/oci/subnetIds='["<subnet OCID>"]' Example: fn create app rotatecredapp --annotation oracle.com/oci/subnetIds='["ocid1.subnet.oc1.ap-mumbai-1.aaaaaaaabitp32dkyox37qa3tk3evl2nxivwb....."]'
-
通过覆盖现有内容,将以下脚本复制并粘贴到
func.py
中。# python script for auto rotation of OCI IAM credentials import io import json import logging import oci import base64 from fdk import response from Cryptodome.PublicKey import RSA # Get Resource Principal Credentials signer = oci.auth.signers.get_resource_principals_signer() # Initialize client identity_client = oci.identity.IdentityClient(config={}, signer=signer) onsclient = oci.ons.NotificationDataPlaneClient(config={}, signer=signer) vault_client = oci.vault.VaultsClient(config={}, signer=signer) # Get tenancy id and name tenancy_data = identity_client.get_tenancy(tenancy_id=signer.tenancy_id).data t_name = str(tenancy_data.name) t_id = signer.tenancy_id # Get secret OCID from comments def get_secret_ocids(comments_items,find_name): secret_ocid = "" for comment in comments_items: if comment.split(":")[0] == find_name: secret_ocid = comment.split(":")[1] return secret_ocid # Function to store secret in OCI vault def update_secret(vault_client,secret_id,new_value): # Base64 encode new_token_ascii = new_value.encode("ascii") base64_bytes = base64.b64encode(new_token_ascii) base64_string = base64_bytes.decode("ascii") # Create new version of secret vault_client.update_secret(secret_id=secret_id,update_secret_details=oci.vault.models.UpdateSecretDetails(secret_content=oci.vault.models.Base64SecretContentDetails(content_type="BASE64", content=base64_string))) def handler(ctx, data: io.BytesIO=None): try: cfg = ctx.Config() ons_topic = cfg["ons_topic"] body = json.loads(data.getvalue()) # Get common parameters values e_time = str(body["eventTime"]).lstrip() problem_name = str(body["data"]["additionalDetails"]["problemName"]).lstrip() status = "NOT RESOLVED" resource_name = str(body["data"]["resourceName"]).lstrip() user_ocid = str(body["data"]["additionalDetails"]["problemAdditionalDetails"]["User OCID"]).lstrip() target_resource_name = str(body["data"]["additionalDetails"]["resourceName"]).lstrip() target_resource_id = str(body["data"]["additionalDetails"]["resourceId"]).lstrip() risk_level = str(body["data"]["additionalDetails"]["riskLevel"]).lstrip() comments = str(body["data"]["additionalDetails"]["problemAdditionalDetails"]["comments"]).lstrip() comments_items = comments.split(",") additional_details = "\r\r\nAction : Closure comments was not in required format hence, no action by automation." try: # Check Problem Type if problem_name == "PASSWORD_TOO_OLD": identity_client.create_or_reset_ui_password(user_id=user_ocid) additional_details = "\r\r\nAction : Your password has been reset by the System Administrator as per password policy rotation. Please set new password by clicking on forgot password from OCI console. " status = "RESOLVED" elif problem_name == "AUTH_TOKEN_TOO_OLD": auth_secret_ocid = get_secret_ocids(comments_items,"auth_secret_ocid") if auth_secret_ocid != "": # Delete existing auth token identity_client.delete_auth_token(user_id=user_ocid, auth_token_id=target_resource_id) # Create new auth token create_auth_token_response = identity_client.create_auth_token( create_auth_token_details=oci.identity.models.CreateAuthTokenDetails(description=target_resource_name),user_id=user_ocid).data new_value = create_auth_token_response.token # Store new auth token in vault secret update_secret(vault_client,auth_secret_ocid,new_value) additional_details = '\r\nAuth Token - Secret OCID : ' + auth_secret_ocid status = "RESOLVED" elif problem_name == "SECRET_KEY_TOO_OLD": access_id_secret_ocid = get_secret_ocids(comments_items, "accesskey_secret_ocid") secret_key_secret_ocid = get_secret_ocids(comments_items, "secretkey_secret_ocid") if access_id_secret_ocid != "" and secret_key_secret_ocid != "": # Delete existing customer secrete key delete_secret_key_response = identity_client.delete_customer_secret_key(user_ocid, target_resource_id).data # Create new customer secret key create_customer_secret_key_response = identity_client.create_customer_secret_key(create_customer_secret_key_details=oci.identity.models.CreateCustomerSecretKeyDetails(display_name=target_resource_name),user_id=user_ocid).data new_secret_key = str(create_customer_secret_key_response.key) new_access_key_id = str(create_customer_secret_key_response.id) # Store new customer secret key in vault secret update_secret(vault_client,secret_key_secret_ocid,new_secret_key) update_secret(vault_client,access_id_secret_ocid,new_access_key_id) additional_details = '\r\nAccess Key - Secret OCID : ' + access_id_secret_ocid + \ '\r\nSecret Key - Secret OCID : ' + secret_key_secret_ocid status = "RESOLVED" elif problem_name == "API_KEY_TOO_OLD": key_fingerprint = target_resource_id.split("/")[2] api_secret_ocid = get_secret_ocids(comments_items,"api_secret_ocid") if api_secret_ocid != "": key = RSA.generate(2048) key_private = key.exportKey() pubkey = key.publickey() key_public = pubkey.exportKey() # Delete existing API key delete_api_key_response = identity_client.delete_api_key(user_id=user_ocid,fingerprint=key_fingerprint) # Upload new public API key in OCI for the user upload_api_key_response = identity_client.upload_api_key(user_id=user_ocid, create_api_key_details=oci.identity.models.CreateApiKeyDetails( key=key_public.decode())) # Store content of new private key in vault secret update_secret(vault_client,api_secret_ocid,key_private.decode()) additional_details = '\r\nSecret OCID for private API key : ' + api_secret_ocid status = "RESOLVED" except Exception as e: additional_details = '\r\r\n Error: '+ str(e) # Message Body Customization, it can be updated as per need line_head = 'Oracle Cloud Notification' + '\n=====================' message_body = line_head + \ '\r\r\nProblem Name : ' + problem_name + \ '\r\r\nRisk Level : ' + risk_level + \ '\r\nEvent Time : ' + e_time + \ '\r\nTenancy Name : ' + t_name + \ '\r\nTenancy ID : ' + t_id + \ '\r\r\nAdditional Details : ' + '\n-------------------------' \ '\r\nResource Name : ' + target_resource_name + \ '\r\nResource ID : ' + target_resource_id + \ '\r\nResource User OCID : ' + user_ocid + ' ' + additional_details # Message Title message_title = 'Problem : ' + resource_name + ' | ' + status +' by automation ' # Message Detail message_details = oci.ons.models.MessageDetails(body=message_body, title=message_title) # Publish message to ONS onsclient.publish_message(ons_topic, message_details) except (Exception, ValueError) as ex: logging.getLogger().info('error parsing json payload: ' + str(ex)) return response.Response(ctx, response_data=json.dumps({"message": "success"}),headers={"Content-Type": "application/json"})
-
更新
requirements.txt
,如下所示。fdk>=0.1.59 oci pycryptodomex
-
运行以下命令以部署该函数。
fn -v deploy --app <app-name> Example: fn -v deploy --app rotatecredapp
-
执行以下命令为配置键和值添加函数配置。
ons_topic
:使用通知主题的 Oracle Cloud 标识符 (OCID) 接收电子邮件通知。
fn config function <app-name> <function-name> <config-key> <config-value> Example: fn config function rotatecredapp rotatecred-func ons_topic ocid1.onstopic.oc1.ap-mumbai-1.aaaaaaaau3onlufcfz.......kae26637zovwd7q
任务 2:创建事件规则
-
转至 OCI 控制台,然后单击观测和管理。
-
在事件服务下,单击规则和创建规则。
-
在创建规则页面中,输入规则的名称和规则的说明。
-
在规则条件部分中,输入以下信息以指定用于触发函数的事件类型和操作。
- 条件:
Event Type
。 - 服务名:
Cloud Guard
。 - 事件类型:
Remediated-Problem
。 - 属性名:
ProblemRecommendation
。 - 属性值:
Rotate IAM Console password regularly, at least every 90 days.
,Rotate IAM Auth token regularly, at least every 90 days.
,Rotate IAM Customer secret keys regularly, at least every 90 days.
,Rotate API keys regularly, at least every 90 days
.
下图是 Oracle Cloud Guard 问题补救事件的示例。
- 条件:
任务 3:修复 Oracle Cloud Guard 问题
-
转到 OCI 控制台,然后单击身份和安全。
-
在 Cloud Guard 下,单击问题。
-
单击支持自动旋转的问题之一。
-
单击标记为已解决,然后按以下格式在注释部分中输入以下信息。您可以根据组织流程添加附加信息以及以下用逗号 (,) 分隔的信息。
-
IAM 客户密钥太旧。
accesskey_secret_ocid:<OCID of access key secret>,secretkey_secret_ocid:<OCID of secret key secret>,<additional comments>
-
API 密钥太旧。
api_secret_ocid:<OCID of api key secret>,<additional comments>
-
IAM 验证令牌太旧。
auth_secret_ocid:<OCID of auth token secret>,<additional comments>
-
密码太旧。
注:
- 没有必要的信息可以包含在此问题的备注中。
- 单个用户应在下次登录时单击忘记密码来重置密码。用户应具有有效的电子邮件地址才能接收电子邮件通知。
-
任务 4:验证新的 OCI IAM 身份证明和电子邮件通知
您将收到问题解决电子邮件通知,其中包含轮换的密钥 OCID 详细信息和其他信息。
相关链接
确认
-
作者 - Dipesh Kumar Rathod(基础设施首席云架构师)
-
贡献者 - Bhanu Prakash Lohumi(基础设施高级云工程师)
更多学习资源
浏览 docs.oracle.com/learn 上的其他实验室,或者通过 Oracle Learning YouTube 频道访问更多免费学习内容。此外,请访问 education.oracle.com/learning-explorer 以成为 Oracle Learning Explorer。
有关产品文档,请访问 Oracle 帮助中心。
Enable Auto Rotation of Oracle Cloud Infrastructure Identity and Access Management Credentials
F93269-01
February 2024
Copyright © 2024, Oracle and/or its affiliates.