bool,'id'=>string,'secret'=>string,'token'=>string,'expires'=>int,'error'=>string] */ function aliyunEcsRamCredentials($roleName = '', $force = false) { static $memo = []; $roleName = trim((string)$roleName); $memoKey = 'aliyun:' . $roleName; if (!$force && isset($memo[$memoKey])) return $memo[$memoKey]; // 跨请求缓存(临时凭据有效期通常 >= 1 小时) if (!$force) { $cached = getSettingJson('cache_meta_aliyun', []); if (!empty($cached['expires']) && $cached['expires'] > time() + 300 && ($cached['role'] ?? '') === $roleName) { return $memo[$memoKey] = $cached + ['ok' => true, 'error' => '']; } } $base = 'http://100.100.100.200/latest/meta-data/ram/security-credentials/'; // IMDSv2:先取一次性 token(实例关闭 v1 时必须) $token = ''; $t = storageHttp('PUT', 'http://100.100.100.200/latest/api/token', ['X-aliyun-ecs-metadata-token-ttl-seconds: 21600'], null, STORAGE_META_TIMEOUT); if ($t['ok'] && trim($t['body']) !== '') { $token = trim($t['body']); } $authHeader = $token !== '' ? ["X-aliyun-ecs-metadata-token: {$token}"] : []; // 角色名 if ($roleName === '') { $r = storageHttp('GET', $base, $authHeader, null, STORAGE_META_TIMEOUT); if (!$r['ok']) { return $memo[$memoKey] = aliyunMetaFail($r, '无法访问 ECS 元数据服务'); } $first = trim(strtok($r['body'], "\r\n")); if ($first === '') { return $memo[$memoKey] = aliyunMetaFail(null, '该实例未绑定 RAM 角色'); } $roleName = $first; } $r = storageHttp('GET', $base . rawurlencode($roleName), $authHeader, null, STORAGE_META_TIMEOUT); if (!$r['ok']) { return $memo[$memoKey] = aliyunMetaFail($r, "获取 RAM 角色 {$roleName} 的临时凭据失败"); } $json = json_decode($r['body'], true); if (!is_array($json) || empty($json['AccessKeyId']) || empty($json['AccessKeySecret'])) { $returned = is_array($json) ? ($json['Code'] ?? '') : ''; return $memo[$memoKey] = aliyunMetaFail(null, '元数据返回内容异常' . ($returned ? "({$returned})" : '')); } $expires = !empty($json['Expiration']) ? (int)strtotime($json['Expiration']) : time() + 1800; $cred = [ 'ok' => true, 'id' => (string)$json['AccessKeyId'], 'secret' => (string)$json['AccessKeySecret'], 'token' => (string)($json['SecurityToken'] ?? ''), 'expires' => $expires, 'error' => '', 'role' => $roleName, ]; setSettingJson('cache_meta_aliyun', $cred); return $memo[$memoKey] = $cred; } function aliyunMetaFail($resp, $prefix) { $detail = $resp === null ? '' : ($resp['error'] ?? ''); return [ 'ok' => false, 'id' => '', 'secret' => '', 'token' => '', 'expires' => 0, 'error' => $prefix . ($detail ? ":{$detail}" : '') . '(请确认服务器位于阿里云 ECS,且实例已绑定 RAM 角色;否则请改用 AccessKey 方式)', ]; } /** * 当前 ECS 所在地域 ID(如 cn-qingdao、cn-beijing)。不在阿里云 ECS 上时返回空串。 * * 用途:判断「内网 endpoint 能不能用」。`oss--internal.aliyuncs.com` * **只在与该桶同地域的 ECS 上可路由**;跨地域时 DNS 照常解析(秒回 A 记录) * 但 TCP 永远连不通,于是每次读取都要先白等一次连接超时——这是「服务端代理 * 慢到不可用」的典型成因。先比地域,可以完全免掉这类探测。 * * 地域不会变,结果缓存 1 小时。 * * @param bool $force 忽略缓存强制重取 * @return string 地域 ID,未知时为空串 */ function aliyunEcsRegion($force = false) { static $memo = null; if (!$force && $memo !== null) return $memo; if (!$force) { $cached = getSettingJson('cache_ecs_region', []); if (!empty($cached['expires']) && $cached['expires'] > time()) { return $memo = (string)($cached['region'] ?? ''); } } $r = storageHttp('GET', 'http://100.100.100.200/latest/meta-data/region-id', [], null, STORAGE_META_TIMEOUT, false, 2); $region = $r['ok'] ? trim((string)$r['body']) : ''; if ($region !== '' && !preg_match('#^[A-Za-z0-9-]+$#', $region)) $region = ''; setSettingJson('cache_ecs_region', ['region' => $region, 'expires' => time() + 3600]); return $memo = $region; } /** * 腾讯云 CVM:获取 CAM 角色的临时密钥。 * * @param string $roleName 指定的 CAM 角色名;留空则自动发现 * @param bool $force 忽略缓存强制刷新 * @return array ['ok'=>bool,'id'=>string,'secret'=>string,'token'=>string,'expires'=>int,'error'=>string] */ function tencentCvmCamCredentials($roleName = '', $force = false) { static $memo = []; $roleName = trim((string)$roleName); $memoKey = 'tencent:' . $roleName; if (!$force && isset($memo[$memoKey])) return $memo[$memoKey]; if (!$force) { $cached = getSettingJson('cache_meta_tencent', []); if (!empty($cached['expires']) && $cached['expires'] > time() + 300 && ($cached['role'] ?? '') === $roleName) { return $memo[$memoKey] = $cached + ['ok' => true, 'error' => '']; } } // 腾讯云元数据服务需要先获取会话 token $token = ''; $t = storageHttp('PUT', 'http://metadata.tencentyun.com/latest/api/token', ['X-ttc-metadata-token-ttl-seconds: 21600'], null, STORAGE_META_TIMEOUT); if ($t['ok'] && trim($t['body']) !== '') { $token = trim($t['body']); } $authHeader = $token !== '' ? ["X-ttc-metadata-token: {$token}"] : []; $base = 'http://metadata.tencentyun.com/latest/meta-data/cam/security-credentials/'; if ($roleName === '') { $r = storageHttp('GET', $base, $authHeader, null, STORAGE_META_TIMEOUT); if (!$r['ok']) { return $memo[$memoKey] = tencentMetaFail($r, '无法访问 CVM 元数据服务'); } $first = trim(strtok($r['body'], "\r\n")); if ($first === '') { return $memo[$memoKey] = tencentMetaFail(null, '该实例未绑定 CAM 角色'); } $roleName = $first; } $r = storageHttp('GET', $base . rawurlencode($roleName), $authHeader, null, STORAGE_META_TIMEOUT); if (!$r['ok']) { return $memo[$memoKey] = tencentMetaFail($r, "获取 CAM 角色 {$roleName} 的临时密钥失败"); } $json = json_decode($r['body'], true); if (!is_array($json) || empty($json['TmpSecretId']) || empty($json['TmpSecretKey'])) { $returned = is_array($json) ? ($json['Code'] ?? '') : ''; return $memo[$memoKey] = tencentMetaFail(null, '元数据返回内容异常' . ($returned ? "({$returned})" : '')); } $cred = [ 'ok' => true, 'id' => (string)$json['TmpSecretId'], 'secret' => (string)$json['TmpSecretKey'], 'token' => (string)($json['Token'] ?? ''), 'expires' => !empty($json['ExpiredTime']) ? (int)$json['ExpiredTime'] : time() + 1800, 'error' => '', 'role' => $roleName, ]; setSettingJson('cache_meta_tencent', $cred); return $memo[$memoKey] = $cred; } function tencentMetaFail($resp, $prefix) { $detail = $resp === null ? '' : ($resp['error'] ?? ''); return [ 'ok' => false, 'id' => '', 'secret' => '', 'token' => '', 'expires' => 0, 'error' => $prefix . ($detail ? ":{$detail}" : '') . '(请确认服务器位于腾讯云 CVM,且实例已绑定 CAM 角色;否则请改用 SecretId 方式)', ]; } // 清除元数据凭据缓存(设置页"重新探测"时使用) function clearMetaCredentialCache() { deleteSetting('cache_meta_aliyun'); deleteSetting('cache_meta_tencent'); deleteSetting('cache_ecs_region'); deleteSetting('cache_oss_internal'); }