Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,26 @@ chaitin-cli apisec risk event list --query count=20 --query offset=0 --output js

`apisec raw` 保留为高级入口,用于调用生成出的底层 API 操作;日常查询建议先运行 `chaitin-cli apisec --help` 或对应语义命令的 `--help`。

### SafeLine 企业版 AI 站点操作

SafeLine 企业版命令支持面向 AI/AISOC 调度的环境检查、证书查询/上传、站点创建预览、站点创建和回退删除。

首版站点创建支持的部署模式:

- `Software Reverse Proxy`
- `Software Cluster Reverse Proxy`

推荐调度流程:

```bash
chaitin-cli safeline inspect --indent
chaitin-cli safeline site create capabilities --indent
chaitin-cli safeline cert list --indent
chaitin-cli safeline site create --check --name app-a --domain app.example.com --port 443 --ssl --cert-id 12 --upstream http://10.0.0.1:8080 --policy-group 3 --indent
chaitin-cli safeline site create --yes --name app-a --domain app.example.com --port 443 --ssl --cert-id 12 --upstream http://10.0.0.1:8080 --policy-group 3 --indent
chaitin-cli safeline site delete 123 --yes --indent
```

`.env` 示例:

```bash
Expand Down
6 changes: 6 additions & 0 deletions config.yaml.example
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,12 @@ safeline-ce:
safeline:
url: ""
api_key: ""
# Optional fallback when the CLI cannot detect these from the target.
# Supported first-version site-create modes:
# - Software Reverse Proxy
# - Software Cluster Reverse Proxy
version: ""
operation_mode: ""

ddr:
url: ""
Expand Down
56 changes: 56 additions & 0 deletions products/safeline/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,62 @@ safeline site enable <id>
safeline site disable <id>
```

#### AI 站点创建

该能力面向 AISOC 或其他智能体调度,按一次命令操作一个 SafeLine 目标。CLI 不导入站点定义文件,也不在内部批量操作多个 SafeLine 实例。

推荐流程:

1. 工单中包含一个或多个站点定义。
2. 智能体读取 CLI help 和目标 capabilities。
3. 智能体按目标/站点逐条执行命令并记录返回结果。
4. 回退时使用返回的站点 ID 执行 `safeline site delete <id> --yes`。

检测目标版本、部署模式和能力:

```bash
chaitin-cli safeline inspect --indent
chaitin-cli safeline site create capabilities --indent
```

当 CLI 无法从目标检测版本和部署模式时,可以使用命令行参数或 `config.yaml` 中的 `version`、`operation_mode` 作为回退:

```bash
chaitin-cli safeline --safeline-version 25.03.007_r7 --operation-mode software-reverse-proxy inspect --indent
```

首版站点创建支持:

- `Software Reverse Proxy`
- `Software Cluster Reverse Proxy`

HTTPS 站点创建前先查询或上传证书:

```bash
chaitin-cli safeline cert list --indent
chaitin-cli safeline cert get 12 --indent
chaitin-cli safeline cert upload --name app-cert --crt ./app.crt --key ./app.key --indent
```

站点创建默认值和限制:

- 创建后默认禁用;传 `--enable` 才会创建为启用状态。
- `--domain` 必填且可重复,`*` 会原样传给 SafeLine 后端校验。
- `--port` 必填且可重复。
- `--ssl` 必须同时传 `--cert-id`。
- `--http2` 和 `--sni` 必须同时启用 `--ssl`。
- 默认 URL 路径为 `/`,匹配操作为 `pre`。
- 软件反向代理默认后端类型为 `proxy`。
- `response` 后端只通过 `--request` 原始 JSON 支持。

示例:

```bash
chaitin-cli safeline site create --check --name app-a --domain app.example.com --port 443 --ssl --cert-id 12 --upstream http://10.0.0.1:8080 --policy-group 3 --indent
chaitin-cli safeline site create --yes --name app-a --domain app.example.com --port 443 --ssl --cert-id 12 --upstream http://10.0.0.1:8080 --policy-group 3 --indent
chaitin-cli safeline site delete 123 --yes --indent
```

---

### ip-group - IP 组管理
Expand Down
97 changes: 97 additions & 0 deletions products/safeline/cmd/cert/cert.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
package cert

import (
"bytes"
"fmt"
"io"
"mime/multipart"
"os"
"path/filepath"

safelinecmd "github.com/chaitin/chaitin-cli/products/safeline/cmd"
"github.com/spf13/cobra"
)

func NewCommand() *cobra.Command {
c := &cobra.Command{Use: "cert", Short: "SSL certificate management commands"}
c.AddCommand(newListCmd(), newGetCmd(), newUploadCmd())
return c
}

func newListCmd() *cobra.Command {
return &cobra.Command{Use: "list", Short: "List SSL certificates", RunE: func(c *cobra.Command, args []string) error {
env, err := safelinecmd.NewClient().Do("GET", "/api/CertAPI", nil, nil)
if err != nil {
return err
}
return safelinecmd.PrintEnvelope(c, env)
}}
}

func newGetCmd() *cobra.Command {
return &cobra.Command{Use: "get <id>", Short: "Get SSL certificate details", Args: cobra.ExactArgs(1), RunE: func(c *cobra.Command, args []string) error {
env, err := safelinecmd.NewClient().Do("GET", "/api/CertAPI", nil, map[string]string{"id": args[0]})
if err != nil {
return err
}
return safelinecmd.PrintEnvelope(c, env)
}}
}

func newUploadCmd() *cobra.Command {
var name, crtPath, keyPath, password string
cmd := &cobra.Command{Use: "upload", Short: "Upload an ordinary SSL certificate and private key", RunE: func(c *cobra.Command, args []string) error {
if name == "" {
return fmt.Errorf("--name is required")
}
if crtPath == "" {
return fmt.Errorf("--crt is required")
}
if keyPath == "" {
return fmt.Errorf("--key is required")
}
body := &bytes.Buffer{}
mw := multipart.NewWriter(body)
_ = mw.WriteField("name", name)
_ = mw.WriteField("password", password)
if err := addFile(mw, "crt_file", crtPath); err != nil {
return err
}
if err := addFile(mw, "key_file", keyPath); err != nil {
return err
}
if err := mw.Close(); err != nil {
return err
}

if safelinecmd.IsDryRun() {
fmt.Fprintf(c.ErrOrStderr(), "[DRY-RUN] POST /api/UploadSSLCertAPI\n")
fmt.Fprintf(c.ErrOrStderr(), "Fields: name=%s crt_file=%s key_file=%s\n", name, crtPath, keyPath)
return nil
}
env, err := safelinecmd.NewClient().DoRaw("POST", "/api/UploadSSLCertAPI", body, mw.FormDataContentType(), nil)
if err != nil {
return err
}
return safelinecmd.PrintEnvelope(c, env)
}}
cmd.Flags().StringVar(&name, "name", "", "Certificate name")
cmd.Flags().StringVar(&crtPath, "crt", "", "Certificate file path")
cmd.Flags().StringVar(&keyPath, "key", "", "Private key file path")
cmd.Flags().StringVar(&password, "password", "", "Private key password, if required")
return cmd
}

func addFile(mw *multipart.Writer, field, path string) error {
f, err := os.Open(path)
if err != nil {
return fmt.Errorf("open %s: %w", path, err)
}
defer f.Close()
part, err := mw.CreateFormFile(field, filepath.Base(path))
if err != nil {
return err
}
_, err = io.Copy(part, f)
return err
}
50 changes: 50 additions & 0 deletions products/safeline/cmd/inspect/inspect.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package inspect

import (
safelinecmd "github.com/chaitin/chaitin-cli/products/safeline/cmd"
safelineruntime "github.com/chaitin/chaitin-cli/products/safeline/runtime"
"github.com/spf13/cobra"
)

type result struct {
OK bool `json:"ok"`
Operation string `json:"operation"`
Warnings []string `json:"warnings"`
Errors []string `json:"errors"`
Data data `json:"data"`
}

type data struct {
URL string `json:"url"`
Context safelineruntime.Context `json:"context"`
SiteCreate safelineruntime.SiteCreateCapability `json:"site_create"`
}

func NewCommand() *cobra.Command {
return &cobra.Command{
Use: "inspect",
Short: "Inspect SafeLine target version, mode, and CLI capabilities",
RunE: func(c *cobra.Command, args []string) error {
ctx, err := safelineruntime.ResolveContext(safelinecmd.NewClient(), safelineruntime.Options{
VersionOverride: safelinecmd.VersionOverride,
OperationModeOverride: safelinecmd.OperationModeOverride,
ConfigVersion: safelinecmd.ConfigVersion,
ConfigOperationMode: safelinecmd.ConfigOperationMode,
})
if err != nil {
return err
}
return safelinecmd.PrintResult(c, result{
OK: true,
Operation: "inspect",
Warnings: ctx.Warnings,
Errors: []string{},
Data: data{
URL: safelinecmd.URL,
Context: ctx,
SiteCreate: safelineruntime.SiteCreateCapabilities(ctx),
},
})
},
}
}
2 changes: 1 addition & 1 deletion products/safeline/cmd/log/log_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ func testDetectGetOmitsEmptyTimestamp(t *testing.T, args []string) {
}))
defer srv.Close()

cmdpkg.SetFlags(srv.URL, "token", "json", false, false)
cmdpkg.SetFlags(srv.URL, "token", "json", false, false, "", "", "", "")

c := newDetectGetCmd()
out := &strings.Builder{}
Expand Down
22 changes: 15 additions & 7 deletions products/safeline/cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,22 +17,30 @@ import (

// Global flags - these are set by the parent safeline command via SetFlags
var (
URL string
APIKey string
Output string
Insecure bool
DryRun bool
ServerVersion string // detected server version, empty if unknown
URL string
APIKey string
Output string
Insecure bool
DryRun bool
ServerVersion string // detected server version, empty if unknown
VersionOverride string
OperationModeOverride string
ConfigVersion string
ConfigOperationMode string
)

// SetFlags sets the global flags from the safeline package.
// This must be called by the safeline package before commands run.
func SetFlags(url, apiKey, output string, insecure bool, dryRun bool) {
func SetFlags(url, apiKey, output string, insecure bool, dryRun bool, versionOverride, operationModeOverride, configVersion, configOperationMode string) {
URL = url
APIKey = apiKey
Output = output
Insecure = insecure
DryRun = dryRun
VersionOverride = versionOverride
OperationModeOverride = operationModeOverride
ConfigVersion = configVersion
ConfigOperationMode = configOperationMode
}

// SetServerVersion sets the detected server version for subcommand use.
Expand Down
Loading
Loading