diff --git a/apps/accounts/api/account/application.py b/apps/accounts/api/account/application.py index 057456fa5..3905fa53d 100644 --- a/apps/accounts/api/account/application.py +++ b/apps/accounts/api/account/application.py @@ -1,8 +1,6 @@ import os - -from django.utils.translation import gettext_lazy as _ +from django.utils.translation import gettext_lazy as _, get_language from django.conf import settings -from django.utils import translation from rest_framework.decorators import action from rest_framework.response import Response @@ -34,16 +32,25 @@ class IntegrationApplicationViewSet(OrgBulkModelViewSet): permission_classes=[IsValidUser] ) def get_sdks_info(self, request, *args, **kwargs): - readme = '' - sdk_language = self.request.query_params.get('language', 'python') - filename = f'readme.{translation.get_language()}.md' - readme_path = os.path.join( - settings.APPS_DIR, 'accounts', 'demos', sdk_language, filename - ) - if os.path.exists(readme_path): - with open(readme_path, 'r') as f: - readme = f.read() - return Response(data={'readme': readme}) + code_suffix_mapper = { + 'python': 'py', + 'java': 'java', + 'go': 'go', + 'javascript': 'js', + 'php': 'php', + } + sdk_language = request.query_params.get('language','python') + sdk_path = os.path.join(settings.APPS_DIR, 'accounts', 'demos', sdk_language) + readme_path = os.path.join(sdk_path, f'readme.{get_language()}.md') + demo_path = os.path.join(sdk_path, f'demo.{code_suffix_mapper[sdk_language]}') + + def read_file(path): + if os.path.exists(path): + with open(path, 'r', encoding='utf-8') as f: + return f.read() + return '' + + return Response(data={'readme': read_file(readme_path), 'code': read_file(demo_path)}) @action( ['GET'], detail=True, url_path='secret', diff --git a/apps/accounts/api/account/pam_dashboard.py b/apps/accounts/api/account/pam_dashboard.py index 79a922065..35378de37 100644 --- a/apps/accounts/api/account/pam_dashboard.py +++ b/apps/accounts/api/account/pam_dashboard.py @@ -37,7 +37,7 @@ class PamDashboardApi(APIView): @staticmethod def get_account_risk_data(_all, query_params): agg_map = { - 'total_privileged_accounts': ('long_time_no_login_count', Q(risk='long_time_no_login')), + 'total_long_time_no_login_accounts': ('long_time_no_login_count', Q(risk='long_time_no_login')), 'total_new_found_accounts': ('new_found_count', Q(risk='new_found')), 'total_group_changed_accounts': ('group_changed_count', Q(risk='group_changed')), 'total_sudo_changed_accounts': ('sudo_changed_count', Q(risk='sudo_changed')), @@ -74,7 +74,7 @@ class PamDashboardApi(APIView): 'total_privileged_accounts': ('privileged_count', Count('id', filter=Q(privileged=True))), 'total_connectivity_ok_accounts': ('connectivity_ok_count', Count('id', filter=Q(connectivity='ok'))), 'total_secret_reset_accounts': ('secret_reset_count', Count('id', filter=Q(secret_reset=True))), - 'total_unavailable_accounts': ('unavailable_count', Count('id', filter=Q(is_active=False))), + 'total_valid_accounts': ('valid_count', Count('id', filter=Q(is_active=True))), 'total_week_add_accounts': ('week_add_count', Count('id', filter=Q(date_created__gte=local_monday()))), } @@ -105,7 +105,6 @@ class PamDashboardApi(APIView): 'total_count_gathered_account_automation': GatherAccountsAutomation, 'total_count_push_account_automation': PushAccountAutomation, 'total_count_backup_account_automation': BackupAccountAutomation, - 'total_count_risk_account': AccountRisk, 'total_count_integration_application': IntegrationApplication, } diff --git a/apps/accounts/demos/go/README.zh-hans.md b/apps/accounts/demos/go/README.zh-hans.md index 3b4e49a43..2a9747b7b 100644 --- a/apps/accounts/demos/go/README.zh-hans.md +++ b/apps/accounts/demos/go/README.zh-hans.md @@ -1,121 +1,45 @@ -```go -package main +# 使用说明 -import ( - "crypto/hmac" - "crypto/sha256" - "encoding/base64" - "encoding/json" - "fmt" - "log" - "net/http" - "net/url" - "os" - "strings" - "time" -) +## 1. 简介 -type APIClient struct { - Client *http.Client - APIURL string - KeyID string - KeySecret string - OrgID string +本 API 提供了 PAM 查看资产账号服务,支持 RESTful 风格的调用,返回数据采用 JSON 格式。 + +## 2. 环境要求 + +- `Go 1.16+` +- `crypto/hmac` +- `crypto/sha256` +- `encoding/base64` +- `net/http` + +## 3. 使用方法 + +**请求方式**: `GET api/v1/accounts/integration-applications/account-secret/` + +**请求参数** + +| 参数名 | 类型 | 必填 | 说明 | +|----------|------|-----|---------------| +| asset | str | 是 | 资产 ID / 资产名称 | +| account | str | 是 | 账号 ID / 账号名称 | + +**响应示例**: +```json +{ + "id": "72b0b0aa-ad82-4182-a631-ae4865e8ae0e", + "secret": "123456" } +``` -func NewAPIClient() *APIClient { - return &APIClient{ - Client: &http.Client{}, - APIURL: getEnv("API_URL", "http://127.0.0.1:8080"), - KeyID: getEnv("API_KEY_ID", "72b0b0aa-ad82-4182-a631-ae4865e8ae0e"), - KeySecret: getEnv("API_KEY_SECRET", "6fuSO7P1m4cj8SSlgaYdblOjNAmnxDVD7tr8"), - OrgID: getEnv("ORG_ID", "00000000-0000-0000-0000-000000000002"), - } -} +## 常见问题(FAQ) -func getEnv(key, defaultValue string) string { - value := os.Getenv(key) - if value == "" { - return defaultValue - } - return value -} +Q: API Key 如何获取? -func (c *APIClient) GetAccountSecret(asset, account string) (map[string]interface{}, error) { - u, err := url.Parse(c.APIURL) - if err != nil { - return nil, fmt.Errorf("failed to parse API URL: %v", err) - } - u.Path = "/api/v1/accounts/integration-applications/account-secret/" +A: 你可以在 PAM - 应用管理 创建应用生成 KEY_ID 和 KEY_SECRET。 - q := u.Query() - q.Add("asset", asset) - q.Add("account", account) - u.RawQuery = q.Encode() +## 版本历史(Changelog) - req, err := http.NewRequest("GET", u.String(), nil) - if err != nil { - return nil, fmt.Errorf("failed to create request: %v", err) - } - date := time.Now().UTC().Format("Mon, 02 Jan 2006 15:04:05 GMT") - req.Header.Set("Accept", "application/json") - req.Header.Set("X-JMS-ORG", c.OrgID) - req.Header.Set("Date", date) - req.Header.Set("X-Source", "jms-pam") - - headersList := []string{"(request-target)", "accept", "date", "x-jms-org"} - var signatureParts []string - - for _, h := range headersList { - var value string - if h == "(request-target)" { - value = strings.ToLower(req.Method) + " " + req.URL.RequestURI() - } else { - canonicalKey := http.CanonicalHeaderKey(h) - value = req.Header.Get(canonicalKey) - } - signatureParts = append(signatureParts, fmt.Sprintf("%s: %s", h, value)) - } - - signatureString := strings.Join(signatureParts, "\n") - mac := hmac.New(sha256.New, []byte(c.KeySecret)) - mac.Write([]byte(signatureString)) - signatureB64 := base64.StdEncoding.EncodeToString(mac.Sum(nil)) - - headersJoined := strings.Join(headersList, " ") - authHeader := fmt.Sprintf( - `Signature keyId="%s",algorithm="hmac-sha256",headers="%s",signature="%s"`, - c.KeyID, - headersJoined, - signatureB64, - ) - req.Header.Set("Authorization", authHeader) - - resp, err := c.Client.Do(req) - if err != nil { - return nil, fmt.Errorf("request failed: %v", err) - } - defer resp.Body.Close() - - if resp.StatusCode != http.StatusOK { - return nil, fmt.Errorf("API returned non-200 status: %d", resp.StatusCode) - } - - var result map[string]interface{} - if err := json.NewDecoder(resp.Body).Decode(&result); err != nil { - return nil, fmt.Errorf("failed to decode response: %v", err) - } - - return result, nil -} - -func main() { - client := NewAPIClient() - result, err := client.GetAccountSecret("ubuntu_docker", "root") - if err != nil { - log.Fatalf("Error: %v", err) - } - fmt.Printf("Result: %+v\n", result) -} -``` \ No newline at end of file +| 版本号 | 变更内容 | 日期 | +| ----- | ----------------- |------------| +| 1.0.0 | 初始版本 | 2025-02-11 | \ No newline at end of file diff --git a/apps/accounts/demos/go/demo.go b/apps/accounts/demos/go/demo.go new file mode 100644 index 000000000..e27f26aad --- /dev/null +++ b/apps/accounts/demos/go/demo.go @@ -0,0 +1,119 @@ +package main + +import ( + "crypto/hmac" + "crypto/sha256" + "encoding/base64" + "encoding/json" + "fmt" + "log" + "net/http" + "net/url" + "os" + "strings" + "time" +) + +type APIClient struct { + Client *http.Client + APIURL string + KeyID string + KeySecret string + OrgID string +} + +func NewAPIClient() *APIClient { + return &APIClient{ + Client: &http.Client{}, + APIURL: getEnv("API_URL", "http://127.0.0.1:8080"), + KeyID: getEnv("API_KEY_ID", "72b0b0aa-ad82-4182-a631-ae4865e8ae0e"), + KeySecret: getEnv("API_KEY_SECRET", "6fuSO7P1m4cj8SSlgaYdblOjNAmnxDVD7tr8"), + OrgID: getEnv("ORG_ID", "00000000-0000-0000-0000-000000000002"), + } +} + +func getEnv(key, defaultValue string) string { + value := os.Getenv(key) + if value == "" { + return defaultValue + } + return value +} + +func (c *APIClient) GetAccountSecret(asset, account string) (map[string]interface{}, error) { + u, err := url.Parse(c.APIURL) + if err != nil { + return nil, fmt.Errorf("failed to parse API URL: %v", err) + } + u.Path = "/api/v1/accounts/integration-applications/account-secret/" + + q := u.Query() + q.Add("asset", asset) + q.Add("account", account) + u.RawQuery = q.Encode() + + req, err := http.NewRequest("GET", u.String(), nil) + if err != nil { + return nil, fmt.Errorf("failed to create request: %v", err) + } + + date := time.Now().UTC().Format("Mon, 02 Jan 2006 15:04:05 GMT") + req.Header.Set("Accept", "application/json") + req.Header.Set("X-JMS-ORG", c.OrgID) + req.Header.Set("Date", date) + req.Header.Set("X-Source", "jms-pam") + + headersList := []string{"(request-target)", "accept", "date", "x-jms-org"} + var signatureParts []string + + for _, h := range headersList { + var value string + if h == "(request-target)" { + value = strings.ToLower(req.Method) + " " + req.URL.RequestURI() + } else { + canonicalKey := http.CanonicalHeaderKey(h) + value = req.Header.Get(canonicalKey) + } + signatureParts = append(signatureParts, fmt.Sprintf("%s: %s", h, value)) + } + + signatureString := strings.Join(signatureParts, "\n") + mac := hmac.New(sha256.New, []byte(c.KeySecret)) + mac.Write([]byte(signatureString)) + signatureB64 := base64.StdEncoding.EncodeToString(mac.Sum(nil)) + + headersJoined := strings.Join(headersList, " ") + authHeader := fmt.Sprintf( + `Signature keyId="%s",algorithm="hmac-sha256",headers="%s",signature="%s"`, + c.KeyID, + headersJoined, + signatureB64, + ) + req.Header.Set("Authorization", authHeader) + + resp, err := c.Client.Do(req) + if err != nil { + return nil, fmt.Errorf("request failed: %v", err) + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("API returned non-200 status: %d", resp.StatusCode) + } + + var result map[string]interface{} + if err := json.NewDecoder(resp.Body).Decode(&result); err != nil { + return nil, fmt.Errorf("failed to decode response: %v", err) + } + + return result, nil +} + +func main() { + client := NewAPIClient() + result, err := client.GetAccountSecret("ubuntu_docker", "root") + if err != nil { + log.Fatalf("Error: %v", err) + } + fmt.Printf("Result: %+v\n", result) +} diff --git a/apps/accounts/demos/python/README.zh-hans.md b/apps/accounts/demos/python/README.zh-hans.md index f2ec3182d..4d88ecf84 100644 --- a/apps/accounts/demos/python/README.zh-hans.md +++ b/apps/accounts/demos/python/README.zh-hans.md @@ -1,45 +1,42 @@ -```python -import requests -import os -from datetime import datetime -from httpsig.requests_auth import HTTPSignatureAuth +# 使用说明 -API_URL = os.getenv("API_URL", "http://127.0.0.1:8080") -KEY_ID = os.getenv("API_KEY_ID", "72b0b0aa-ad82-4182-a631-ae4865e8ae0e") -KEY_SECRET = os.getenv("API_KEY_SECRET", "6fuSO7P1m4cj8SSlgaYdblOjNAmnxDVD7tr8") -ORG_ID = os.getenv("ORG_ID", "00000000-0000-0000-0000-000000000002") +## 1. 简介 + +本 API 提供了 PAM 查看资产账号服务,支持 RESTful 风格的调用,返回数据采用 JSON 格式。 + +## 2. 环境要求 + +- `Python 3.11+` +- `requests==2.31.0` +- `httpsig==1.3.0` + +## 3. 使用方法 +**请求方式**: `GET api/v1/accounts/integration-applications/account-secret/` + +**请求参数** + +| 参数名 | 类型 | 必填 | 说明 | +|------------|------|----|--------------| +| asset | str | 是 | 资产 ID / 资产名称 | +| account | str | 是 | 账号 ID / 账号名称 | + +**响应示例**: +```json +{ + "id": "72b0b0aa-ad82-4182-a631-ae4865e8ae0e", + "secret": "123456" +} +``` + +## 常见问题(FAQ) + +Q: API Key 如何获取? + +A: 你可以在 PAM - 应用管理 创建应用生成 KEY_ID 和 KEY_SECRET。 + +## 版本历史(Changelog) -class APIClient: - def __init__(self): - self.session = requests.Session() - self.auth = HTTPSignatureAuth( - key_id=KEY_ID, secret=KEY_SECRET, - algorithm='hmac-sha256', headers=['(request-target)', 'accept', 'date', 'x-jms-org'] - ) - - def get_account_secret(self, asset, account): - url = f"{API_URL}/api/v1/accounts/integration-applications/account-secret/" - headers = { - 'Accept': 'application/json', - 'X-JMS-ORG': ORG_ID, - 'Date': datetime.utcnow().strftime('%a, %d %b %Y %H:%M:%S GMT'), - 'X-Source': 'jms-pam' - } - params = {"asset": asset, "account": account} - - try: - response = self.session.get(url, auth=self.auth, headers=headers, params=params, timeout=10) - response.raise_for_status() - return response.json() - except requests.RequestException as e: - print(f"API 请求失败: {e}") - return None - - -# 示例调用 -if __name__ == "__main__": - client = APIClient() - result = client.get_account_secret(asset="ubuntu_docker", account="root") - print(result) -``` \ No newline at end of file +| 版本号 | 变更内容 | 日期 | +| ----- | ----------------- |------------| +| 1.0.0 | 初始版本 | 2025-02-11 | diff --git a/apps/accounts/demos/python/demo.py b/apps/accounts/demos/python/demo.py new file mode 100644 index 000000000..5d7f9d4b6 --- /dev/null +++ b/apps/accounts/demos/python/demo.py @@ -0,0 +1,44 @@ +# 示例调用 + +import requests +import os +from datetime import datetime +from httpsig.requests_auth import HTTPSignatureAuth + +API_URL = os.getenv("API_URL", "http://127.0.0.1:8080") +KEY_ID = os.getenv("API_KEY_ID", "72b0b0aa-ad82-4182-a631-ae4865e8ae0e") +KEY_SECRET = os.getenv("API_KEY_SECRET", "6fuSO7P1m4cj8SSlgaYdblOjNAmnxDVD7tr8") +ORG_ID = os.getenv("ORG_ID", "00000000-0000-0000-0000-000000000002") + + +class APIClient: + def __init__(self): + self.session = requests.Session() + self.auth = HTTPSignatureAuth( + key_id=KEY_ID, secret=KEY_SECRET, + algorithm='hmac-sha256', headers=['(request-target)', 'accept', 'date', 'x-jms-org'] + ) + + def get_account_secret(self, asset, account): + url = f"{API_URL}/api/v1/accounts/integration-applications/account-secret/" + headers = { + 'Accept': 'application/json', + 'X-JMS-ORG': ORG_ID, + 'Date': datetime.utcnow().strftime('%a, %d %b %Y %H:%M:%S GMT'), + 'X-Source': 'jms-pam' + } + params = {"asset": asset, "account": account} + + try: + response = self.session.get(url, auth=self.auth, headers=headers, params=params, timeout=10) + response.raise_for_status() + return response.json() + except requests.RequestException as e: + print(f"API 请求失败: {e}") + return None + + +if __name__ == "__main__": + client = APIClient() + result = client.get_account_secret(asset="ubuntu_docker", account="root") + print(result) \ No newline at end of file diff --git a/apps/i18n/lina/en.json b/apps/i18n/lina/en.json index f2664f1c0..d157b6fca 100644 --- a/apps/i18n/lina/en.json +++ b/apps/i18n/lina/en.json @@ -351,6 +351,7 @@ "ConvenientOperate": "Convenient action", "Copy": "Copy", "CopySuccess": "Copy successful", + "CopyFailed": "Copy failed", "Corporation": "Company", "Create": "Create", "CreateAccessKey": "Create access key", @@ -857,7 +858,6 @@ "Password": "Password", "PasswordAndSSHKey": "Password & SSH key", "PasswordChangeLog": "Password change", - "PasswordExpired": "Password expired", "PasswordPlaceholder": "Please enter password", "PasswordRecord": "Password record", "PasswordRule": "Password rules", @@ -1425,7 +1425,6 @@ "LongTimeNoLogin": "Long time no login", "UnmanagedAccount": "Unmanaged account", "UnavailableAccount": "Unavailable account", - "WeakPassword": "Weak password", "EmptyPassword": "Empty password", "LongTimeNoChangeSecret": "The password has not been changed for a long time", "LongTimeNoVerify": "Long time no verify", @@ -1436,5 +1435,29 @@ "CopyToAsset": "Copy to asset", "MoveToAsset": "Move to asset", "Cloud": "Cloud", - "Device": "Device" + "Device": "Device", + "NoLoginLongTime": "No login for long time", + "NewAccountsFound": "New accounts found", + "GroupChanged": "Group changed", + "SudoChanged": "Sudo changed", + "AuthorizedKeysChanged": "Authorized keys changed", + "AccountDeleted": "Account deleted", + "PasswordExpired": "Password expired", + "LongTimePassword": "Long time password", + "WeakPassword": "Weak password", + "LeakedPassword": "Leaked password", + "RepeatedPassword": "Repeated password", + "PasswordError": "Password error", + "NoAdminAccount": "No admin account", + "ProportionOfAccountTypes": "Account type proportion", + "CurrentStatus": "Current Status", + "ChangeSecretTaskStatus": "Secret Change Task Status", + "TaskCount": "Task Count", + "ScheduledTaskCount": "Scheduled Task Count", + "TaskExecutionCount": "Task Execution Count", + "SuccessCount": "Success Count", + "FailCount": "Failure Count", + "ChangeSecretFailAccounts": "Secret Change Failed Accounts", + "OngoingPwdChange": "Ongoing Secret Change", + "AccountResult": "Account Success/Failure" } \ No newline at end of file diff --git a/apps/i18n/lina/zh.json b/apps/i18n/lina/zh.json index 80e1b40eb..2d47f9eff 100644 --- a/apps/i18n/lina/zh.json +++ b/apps/i18n/lina/zh.json @@ -8,7 +8,7 @@ "AccessIP": "IP 白名单", "AccessKey": "访问密钥", "Account": "账号", - "AccountAmount": "账号数量", + "AccountAmount": "账号数", "AccountBackup": "账号备份", "AccountBackupCreate": "创建账号备份", "AccountBackupDetail": "账号备份详情", @@ -347,6 +347,7 @@ "ConvenientOperate": "便捷操作", "Copy": "复制", "CopySuccess": "复制成功", + "CopyFailed": "复制失败", "Corporation": "公司", "Create": "创建", "CreateAccessKey": "创建访问密钥", @@ -849,7 +850,6 @@ "Password": "密码", "PasswordAndSSHKey": "认证设置", "PasswordChangeLog": "改密日志", - "PasswordExpired": "密码过期了", "PasswordPlaceholder": "请输入密码", "PasswordRecord": "密码记录", "PasswordRule": "密码规则", @@ -1422,7 +1422,6 @@ "LongTimeNoLogin": "长期未登录账号", "UnmanagedAccount": "未托管账号", "UnavailableAccount": "不可用账号", - "WeakPassword": "弱密码", "EmptyPassword": "空密码", "LongTimeNoChangeSecret": "长时间未改密", "LongTimeNoVerify": "长时间未验证", @@ -1435,5 +1434,29 @@ "Host": "主机", "Cloud": "云", "Device": "网络设备", - "Other": "其他" + "Other": "其他", + "NoLoginLongTime": "长时间未登录", + "NewAccountsFound": "发现新账户", + "GroupChanged": "组已更改", + "SudoChanged": "Sudo已更改", + "AuthorizedKeysChanged": "授权密钥已更改", + "AccountDeleted": "账户已删除", + "PasswordExpired": "密码过期", + "LongTimePassword": "长期使用的密码", + "WeakPassword": "弱密码", + "LeakedPassword": "泄露密码", + "RepeatedPassword": "重复密码", + "PasswordError": "密码错误", + "NoAdminAccount": "无管理员账户", + "ProportionOfAccountTypes": "账号类型占比", + "CurrentStatus": "当前状态", + "ChangeSecretTaskStatus": "改密任务执行状态", + "TaskCount": "任务数", + "ScheduledTaskCount": "定时任务数", + "TaskExecutionCount": "任务执行数", + "SuccessCount": "成功数", + "FailCount": "失败数", + "ChangeSecretFailAccounts": "改密失败账号", + "OngoingPwdChange": "当前正在改密情况", + "AccountResult": "账号成功/失败情况" } \ No newline at end of file