github.com/qiniu/go-sdk/v7@v7.27.0/examples/sandbox_request_injections/main.go (about)

     1  package main
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"log"
     7  	"os"
     8  	"strings"
     9  
    10  	"github.com/qiniu/go-sdk/v7/sandbox"
    11  )
    12  
    13  func main() {
    14  	// 确保设置了环境变量 QINIU_API_KEY
    15  	apiKey := os.Getenv("QINIU_API_KEY")
    16  	if apiKey == "" {
    17  		log.Fatal("请设置 QINIU_API_KEY 环境变量")
    18  	}
    19  
    20  	apiURL := os.Getenv("QINIU_SANDBOX_API_URL")
    21  
    22  	ctx := context.Background()
    23  
    24  	// 初始化客户端
    25  	config := &sandbox.Config{
    26  		APIKey:   apiKey,
    27  		Endpoint: apiURL,
    28  	}
    29  	client, err := sandbox.NewClient(config)
    30  	if err != nil {
    31  		log.Fatalf("Failed to create client: %v", err)
    32  	}
    33  	if err := run(ctx, client); err != nil {
    34  		log.Fatal(err)
    35  	}
    36  }
    37  
    38  func run(ctx context.Context, client *sandbox.Client) error {
    39  	log.Println("Creating sandbox with request injections...")
    40  
    41  	// 设置针对特定域名的请求注入规则
    42  	// 这个规则告诉底层网络架构,当请求向 "httpbingo.org" 发起时,
    43  	// 如果它是 HTTPS,拦截掉该请求并将 header 中的 "Authorization"
    44  	// 重写为 "Bearer real_xxx"
    45  	headers := map[string]string{
    46  		"Authorization": "Bearer real_xxx",
    47  	}
    48  	ifHeaders := map[string]string{
    49  		"X-Injection-Scope": "demo",
    50  	}
    51  	ifQueries := map[string]string{
    52  		"inject": "true",
    53  	}
    54  
    55  	// 准备创建参数(HTTP 自定义注入)。如果设置了 QINIU_GITHUB_TOKEN,
    56  	// 同时演示 GitHub 凭证注入和运行时 token 更新。
    57  	injections := []sandbox.SandboxInjectionSpec{
    58  		{
    59  			HTTP: &sandbox.HTTPInjection{
    60  				BaseURL:   "https://httpbingo.org",
    61  				Headers:   &headers,
    62  				IfHeaders: &ifHeaders,
    63  				IfQueries: &ifQueries,
    64  			},
    65  		},
    66  	}
    67  	githubToken := os.Getenv("QINIU_GITHUB_TOKEN")
    68  	if githubToken != "" {
    69  		injections = append(injections, sandbox.SandboxInjectionSpec{
    70  			Github: &sandbox.GithubInjection{Token: &githubToken},
    71  		})
    72  	}
    73  
    74  	params := sandbox.CreateParams{
    75  		TemplateID: "base",
    76  		Injections: &injections,
    77  	}
    78  
    79  	// 也可以使用已知 API 协议的快捷注入(OpenAI、Anthropic、Gemini)
    80  	// openaiKey := "sk-real-openai-key"
    81  	// params := sandbox.CreateParams{
    82  	// 	TemplateID: "base",
    83  	// 	Injections: &[]sandbox.SandboxInjectionSpec{
    84  	// 		{
    85  	// 			OpenAI: &sandbox.OpenAIInjection{
    86  	// 				APIKey: &openaiKey,
    87  	// 				// BaseURL 可选,默认 api.openai.com
    88  	// 			},
    89  	// 		},
    90  	// 	},
    91  	// }
    92  
    93  	// 也可以通过注入规则 ID 引用已保存的注入规则
    94  	// injectionRuleID := "<injection-rule-id>"
    95  	// params := sandbox.CreateParams{
    96  	// 	TemplateID: "base",
    97  	// 	Injections: &[]sandbox.SandboxInjectionSpec{
    98  	// 		{ByID: &injectionRuleID},
    99  	// 	},
   100  	// }
   101  
   102  	// 创建并等待沙箱就绪
   103  	sb, info, err := client.CreateAndWait(ctx, params)
   104  	if err != nil {
   105  		return fmt.Errorf("failed to create sandbox: %w", err)
   106  	}
   107  	defer func() {
   108  		log.Println("Killing sandbox...")
   109  		if err := sb.Kill(ctx); err != nil {
   110  			log.Printf("Failed to kill sandbox: %v", err)
   111  		}
   112  	}()
   113  
   114  	log.Printf("Sandbox created successfully! ID: %s, State: %s\n", sb.ID(), info.State)
   115  
   116  	// 查询当前运行时注入。敏感字段由服务端脱敏,返回值不能直接用于更新。
   117  	current, err := sb.GetInjections(ctx)
   118  	if err != nil {
   119  		return fmt.Errorf("failed to get runtime injections: %w", err)
   120  	}
   121  	logMaskedInjections("Current runtime injections", current)
   122  
   123  	// 在沙箱内执行使用假 APIKEY 的请求
   124  	// 沙箱内的程序完全感觉不到外部注入;只有请求同时满足 IfHeaders / IfQueries
   125  	// 匹配条件时,外层才会拦截并替换为真实的 Key。
   126  	fakeTokenCmd := `curl -sSL -X GET "https://httpbingo.org/bearer?inject=true" -H "accept: application/json" -H "Authorization: Bearer fake_xxx" -H "X-Injection-Scope: demo"`
   127  	if err := runBearerCheck(ctx, sb, fakeTokenCmd, "real_xxx"); err != nil {
   128  		return err
   129  	}
   130  
   131  	// 替换运行中沙箱的全部注入规则。更新接口需要重新提供真实敏感值。
   132  	updatedHeaders := map[string]string{
   133  		"Authorization": "Bearer updated_xxx",
   134  	}
   135  	updatedInjections := []sandbox.SandboxInjectionSpec{
   136  		{
   137  			HTTP: &sandbox.HTTPInjection{
   138  				BaseURL:   "https://httpbingo.org",
   139  				Headers:   &updatedHeaders,
   140  				IfHeaders: &ifHeaders,
   141  				IfQueries: &ifQueries,
   142  			},
   143  		},
   144  	}
   145  	if githubToken != "" {
   146  		updatedInjections = append(updatedInjections, sandbox.SandboxInjectionSpec{
   147  			Github: &sandbox.GithubInjection{Token: &githubToken},
   148  		})
   149  	}
   150  	if err := sb.UpdateInjections(ctx, updatedInjections); err != nil {
   151  		return fmt.Errorf("failed to update runtime injections: %w", err)
   152  	}
   153  	current, err = sb.GetInjections(ctx)
   154  	if err != nil {
   155  		return fmt.Errorf("failed to get updated runtime injections: %w", err)
   156  	}
   157  	logMaskedInjections("Updated runtime injections", current)
   158  	if err := runBearerCheck(ctx, sb, fakeTokenCmd, "updated_xxx"); err != nil {
   159  		return err
   160  	}
   161  
   162  	// 已配置 GitHub 注入时,可独立轮换 GitHub token,无需替换其他注入规则。
   163  	if updatedGitHubToken := os.Getenv("QINIU_GITHUB_TOKEN_UPDATED"); updatedGitHubToken != "" {
   164  		if githubToken == "" {
   165  			return fmt.Errorf("设置 QINIU_GITHUB_TOKEN_UPDATED 时也必须设置 QINIU_GITHUB_TOKEN")
   166  		}
   167  		if err := sb.UpdateGitHubToken(ctx, updatedGitHubToken); err != nil {
   168  			return fmt.Errorf("failed to update GitHub token: %w", err)
   169  		}
   170  		log.Println("GitHub token updated successfully")
   171  	}
   172  	return nil
   173  }
   174  
   175  func runBearerCheck(ctx context.Context, sb *sandbox.Sandbox, command, expectedToken string) error {
   176  	log.Printf("Executing command in sandbox:\n$ %s\n", command)
   177  	result, err := sb.Commands().Run(ctx, command)
   178  	if err != nil {
   179  		return fmt.Errorf("failed to run command: %w", err)
   180  	}
   181  	if result.ExitCode != 0 {
   182  		return fmt.Errorf("command failed with exit code %d: %s", result.ExitCode, result.Stderr)
   183  	}
   184  	if !strings.Contains(result.Stdout, `"token": "`+expectedToken+`"`) {
   185  		return fmt.Errorf("request injection did not replace the bearer token, stdout: %s", result.Stdout)
   186  	}
   187  	log.Printf("Request injection verified with token %q", expectedToken)
   188  	return nil
   189  }
   190  
   191  func logMaskedInjections(label string, injections []sandbox.MaskedSandboxInjection) {
   192  	log.Printf("%s (%d):", label, len(injections))
   193  	for i, injection := range injections {
   194  		switch {
   195  		case injection.ByID != nil:
   196  			log.Printf("  %d. rule ID: %s", i+1, *injection.ByID)
   197  		case injection.OpenAI != nil:
   198  			log.Printf("  %d. OpenAI injection", i+1)
   199  		case injection.Anthropic != nil:
   200  			log.Printf("  %d. Anthropic injection", i+1)
   201  		case injection.Gemini != nil:
   202  			log.Printf("  %d. Gemini injection", i+1)
   203  		case injection.Qiniu != nil:
   204  			log.Printf("  %d. Qiniu injection", i+1)
   205  		case injection.Github != nil:
   206  			log.Printf("  %d. GitHub injection", i+1)
   207  		case injection.HTTP != nil:
   208  			log.Printf("  %d. HTTP injection for %s", i+1, injection.HTTP.BaseURL)
   209  		default:
   210  			log.Printf("  %d. unknown injection", i+1)
   211  		}
   212  	}
   213  }