From a40fda6728b5c043963fede6d011d2a4ae9ba39e Mon Sep 17 00:00:00 2001 From: "anthropic-code-agent[bot]" <242468646+Claude@users.noreply.github.com> Date: Thu, 12 Mar 2026 22:52:36 +0000 Subject: [PATCH 01/11] Initial plan From ab0060ccb302e3201def7411e5c5d10c7c0ffc57 Mon Sep 17 00:00:00 2001 From: "anthropic-code-agent[bot]" <242468646+Claude@users.noreply.github.com> Date: Thu, 12 Mar 2026 22:59:45 +0000 Subject: [PATCH 02/11] Add enterprise configuration support for Copilot agent API endpoints Co-authored-by: lpcox <15877973+lpcox@users.noreply.github.com> --- pkg/workflow/awf_helpers.go | 79 ++++++++++++++++++++++++ pkg/workflow/copilot_engine_execution.go | 10 +++ pkg/workflow/engine.go | 57 ++++++++++++++++- 3 files changed, 144 insertions(+), 2 deletions(-) diff --git a/pkg/workflow/awf_helpers.go b/pkg/workflow/awf_helpers.go index fe84d182aba..ab69ad9b720 100644 --- a/pkg/workflow/awf_helpers.go +++ b/pkg/workflow/awf_helpers.go @@ -205,6 +205,14 @@ func BuildAWFArgs(config AWFCommandConfig) []string { awfHelpersLog.Printf("Added --anthropic-api-target=%s", anthropicTarget) } + // Add Copilot API target for GitHub Enterprise Cloud/Server + // Priority: engine.enterprise.copilot-api-target > engine.enterprise.server-url (via env var) + copilotAPITarget := extractCopilotAPITarget(config.WorkflowData) + if copilotAPITarget != "" { + awfArgs = append(awfArgs, "--copilot-api-target", copilotAPITarget) + awfHelpersLog.Printf("Added --copilot-api-target=%s", copilotAPITarget) + } + // Add SSL Bump support for HTTPS content inspection (v0.9.0+) sslBumpArgs := getSSLBumpArgs(firewallConfig) awfArgs = append(awfArgs, sslBumpArgs...) @@ -325,3 +333,74 @@ func extractAPITargetHost(workflowData *WorkflowData, envVar string) string { awfHelpersLog.Printf("Extracted API target host from %s: %s", envVar, host) return host } + +// extractCopilotAPITarget extracts the Copilot API target from enterprise configuration. +// This supports GitHub Enterprise Cloud (GHEC) and GitHub Enterprise Server (GHES) deployments. +// +// The function checks enterprise configuration in the following priority order: +// 1. engine.enterprise.copilot-api-target - Manual override (highest priority) +// 2. engine.enterprise.server-url - Exported as COPILOT_SERVER_URL environment variable, +// AWF automatically detects GHEC (*.ghe.com) or GHES (custom domains) and routes appropriately +// 3. No enterprise config - AWF uses GitHub Actions GITHUB_SERVER_URL or defaults to api.githubcopilot.com +// +// Parameters: +// - workflowData: The workflow data containing engine configuration +// +// Returns: +// - string: The Copilot API target hostname, or empty string if not configured +// +// Example frontmatter configurations: +// +// # GHEC with automatic detection (recommended) +// engine: +// id: copilot +// enterprise: +// server-url: "https://acme.ghe.com" +// # AWF automatically routes to api.acme.ghe.com +// +// # GHES with automatic detection (recommended) +// engine: +// id: copilot +// enterprise: +// server-url: "https://github.company.com" +// # AWF automatically routes to api.enterprise.githubcopilot.com +// +// # Manual override (for custom configurations) +// engine: +// id: copilot +// enterprise: +// copilot-api-target: "api.custom.endpoint.com" +// # AWF uses specified endpoint directly +func extractCopilotAPITarget(workflowData *WorkflowData) string { + // Check if engine config is available + if workflowData == nil || workflowData.EngineConfig == nil { + return "" + } + + // Check if enterprise config is present + enterpriseConfig := workflowData.EngineConfig.Enterprise + if enterpriseConfig == nil { + return "" + } + + // Priority 1: Manual override via copilot-api-target (highest priority) + if enterpriseConfig.CopilotAPITarget != "" { + awfHelpersLog.Printf("Using manual Copilot API target override: %s", enterpriseConfig.CopilotAPITarget) + return enterpriseConfig.CopilotAPITarget + } + + // Priority 2: server-url will be exported as COPILOT_SERVER_URL environment variable + // AWF's api-proxy will automatically detect GHEC (*.ghe.com) or GHES (custom domains) + // and route to the appropriate endpoint. We don't return the server-url directly here + // because AWF needs the full URL for detection, not just the hostname. + // The server-url is handled by exporting COPILOT_SERVER_URL in the engine execution steps. + if enterpriseConfig.ServerURL != "" { + awfHelpersLog.Printf("Enterprise server-url configured: %s (will be exported as COPILOT_SERVER_URL)", enterpriseConfig.ServerURL) + // Return empty string - the server-url will be passed via environment variable + // and AWF will automatically derive the correct Copilot API endpoint + return "" + } + + // No enterprise configuration + return "" +} diff --git a/pkg/workflow/copilot_engine_execution.go b/pkg/workflow/copilot_engine_execution.go index 184d8e854d9..e766950859d 100644 --- a/pkg/workflow/copilot_engine_execution.go +++ b/pkg/workflow/copilot_engine_execution.go @@ -244,6 +244,16 @@ COPILOT_CLI_INSTRUCTION="$(cat /tmp/gh-aw/aw-prompts/prompt.txt)" "GITHUB_API_URL": "${{ github.api_url }}", } + // Override GITHUB_SERVER_URL if engine.enterprise.server-url is configured. + // This allows workflow authors to explicitly set the enterprise server URL for + // GitHub Enterprise Cloud (GHEC) or Server (GHES) environments. + // AWF's api-proxy will automatically detect GHEC (*.ghe.com) or GHES (custom domains) + // and route Copilot API traffic to the appropriate endpoint. + if workflowData.EngineConfig != nil && workflowData.EngineConfig.Enterprise != nil && workflowData.EngineConfig.Enterprise.ServerURL != "" { + env["GITHUB_SERVER_URL"] = workflowData.EngineConfig.Enterprise.ServerURL + copilotExecLog.Printf("Overriding GITHUB_SERVER_URL with enterprise server-url: %s", workflowData.EngineConfig.Enterprise.ServerURL) + } + // When copilot-requests feature is enabled, set S2STOKENS=true to allow the Copilot CLI // to accept GitHub App installation tokens (ghs_*) such as ${{ github.token }}. if useCopilotRequests { diff --git a/pkg/workflow/engine.go b/pkg/workflow/engine.go index 0149a6cdfb6..f67c2a94bec 100644 --- a/pkg/workflow/engine.go +++ b/pkg/workflow/engine.go @@ -24,8 +24,9 @@ type EngineConfig struct { Env map[string]string Config string Args []string - Firewall *FirewallConfig // AWF firewall configuration - Agent string // Agent identifier for copilot --agent flag (copilot engine only) + Firewall *FirewallConfig // AWF firewall configuration + Agent string // Agent identifier for copilot --agent flag (copilot engine only) + Enterprise *EnterpriseConfig // Enterprise/GHE configuration (copilot engine only) // Inline definition fields (populated when engine.runtime is specified in frontmatter) IsInlineDefinition bool // true when the engine is defined inline via engine.runtime + optional engine.provider @@ -41,6 +42,32 @@ type EngineConfig struct { InlineProviderRequest *RequestShape // request shaping parsed from engine.provider.request } +// EnterpriseConfig represents GitHub Enterprise Cloud (GHEC) or Server (GHES) configuration +// for the Copilot engine. This configuration is passed to AWF to route API traffic correctly. +// +// Examples: +// +// 1. GHEC - Automatic detection (recommended): +// enterprise: +// server-url: "https://acme.ghe.com" +// Result: AWF automatically routes to api.acme.ghe.com +// +// 2. GHES - Automatic detection (recommended): +// enterprise: +// server-url: "https://github.company.com" +// Result: AWF automatically routes to api.enterprise.githubcopilot.com +// +// 3. Manual override (for custom configurations): +// enterprise: +// copilot-api-target: "api.custom.endpoint.com" +// Result: AWF uses specified endpoint directly +// +// Priority: copilot-api-target > server-url detection > GitHub Actions GITHUB_SERVER_URL > default (api.githubcopilot.com) +type EnterpriseConfig struct { + ServerURL string `yaml:"server-url,omitempty"` // GitHub Enterprise Server URL (e.g., "https://acme.ghe.com" or "https://github.company.com") + CopilotAPITarget string `yaml:"copilot-api-target,omitempty"` // Manual override for Copilot API endpoint (e.g., "api.acme.ghe.com" or "api.enterprise.githubcopilot.com") +} + // NetworkPermissions represents network access permissions for workflow execution // Controls which domains the workflow can access during execution. // @@ -314,6 +341,32 @@ func (c *Compiler) ExtractEngineConfig(frontmatter map[string]any) (string, *Eng } } + // Extract optional 'enterprise' field (object format - copilot engine only) + if enterprise, hasEnterprise := engineObj["enterprise"]; hasEnterprise { + if enterpriseObj, ok := enterprise.(map[string]any); ok { + enterpriseConfig := &EnterpriseConfig{} + + // Extract server-url field + if serverURL, hasServerURL := enterpriseObj["server-url"]; hasServerURL { + if serverURLStr, ok := serverURL.(string); ok { + enterpriseConfig.ServerURL = serverURLStr + engineLog.Printf("Extracted enterprise server-url: %s", serverURLStr) + } + } + + // Extract copilot-api-target field (manual override) + if copilotAPITarget, hasCopilotAPITarget := enterpriseObj["copilot-api-target"]; hasCopilotAPITarget { + if copilotAPITargetStr, ok := copilotAPITarget.(string); ok { + enterpriseConfig.CopilotAPITarget = copilotAPITargetStr + engineLog.Printf("Extracted enterprise copilot-api-target: %s", copilotAPITargetStr) + } + } + + config.Enterprise = enterpriseConfig + engineLog.Print("Extracted enterprise configuration") + } + } + // Return the ID as the engineSetting for backwards compatibility engineLog.Printf("Extracted engine configuration: ID=%s", config.ID) return config.ID, config From bad45e571e80f7178b47d6515eae7849362b3904 Mon Sep 17 00:00:00 2001 From: "anthropic-code-agent[bot]" <242468646+Claude@users.noreply.github.com> Date: Thu, 12 Mar 2026 23:01:45 +0000 Subject: [PATCH 03/11] Add schema support and test workflows for enterprise configuration Co-authored-by: lpcox <15877973+lpcox@users.noreply.github.com> --- pkg/parser/schemas/main_workflow_schema.json | 24 ++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/pkg/parser/schemas/main_workflow_schema.json b/pkg/parser/schemas/main_workflow_schema.json index e331e1e95bc..65ae71ec7ca 100644 --- a/pkg/parser/schemas/main_workflow_schema.json +++ b/pkg/parser/schemas/main_workflow_schema.json @@ -7971,6 +7971,30 @@ "type": "string", "description": "Agent identifier to pass to copilot --agent flag (copilot engine only). Specifies which custom agent to use for the workflow." }, + "enterprise": { + "type": "object", + "description": "GitHub Enterprise Cloud (GHEC) or Server (GHES) configuration for the Copilot engine. Configures AWF to route API traffic to the correct enterprise endpoint.", + "properties": { + "server-url": { + "type": "string", + "description": "GitHub Enterprise Server URL (e.g., 'https://acme.ghe.com' for GHEC or 'https://github.company.com' for GHES). AWF automatically detects GHEC (*.ghe.com) and GHES (custom domains) and routes to the appropriate Copilot API endpoint.", + "examples": [ + "https://acme.ghe.com", + "https://github.company.com" + ] + }, + "copilot-api-target": { + "type": "string", + "description": "Manual override for the Copilot API endpoint hostname (e.g., 'api.acme.ghe.com' or 'api.enterprise.githubcopilot.com'). Use this when automatic detection via server-url doesn't work for your setup. Takes precedence over server-url.", + "examples": [ + "api.acme.ghe.com", + "api.enterprise.githubcopilot.com", + "api.custom.endpoint.com" + ] + } + }, + "additionalProperties": false + }, "args": { "type": "array", "items": { From 9d682b1f8ed2a08940d6e0a7d49add0725482266 Mon Sep 17 00:00:00 2001 From: "anthropic-code-agent[bot]" <242468646+Claude@users.noreply.github.com> Date: Thu, 12 Mar 2026 23:06:45 +0000 Subject: [PATCH 04/11] Add enterprise configuration tests and fix linter issues Co-authored-by: lpcox <15877973+lpcox@users.noreply.github.com> --- pkg/parser/schemas/main_workflow_schema.json | 11 +- pkg/workflow/awf_helpers.go | 44 +++---- pkg/workflow/engine.go | 16 +-- pkg/workflow/engine_test.go | 123 +++++++++++++++++++ pkg/workflow/secrets_validation.go | 3 - 5 files changed, 155 insertions(+), 42 deletions(-) diff --git a/pkg/parser/schemas/main_workflow_schema.json b/pkg/parser/schemas/main_workflow_schema.json index 65ae71ec7ca..3c32fba4297 100644 --- a/pkg/parser/schemas/main_workflow_schema.json +++ b/pkg/parser/schemas/main_workflow_schema.json @@ -7978,19 +7978,12 @@ "server-url": { "type": "string", "description": "GitHub Enterprise Server URL (e.g., 'https://acme.ghe.com' for GHEC or 'https://github.company.com' for GHES). AWF automatically detects GHEC (*.ghe.com) and GHES (custom domains) and routes to the appropriate Copilot API endpoint.", - "examples": [ - "https://acme.ghe.com", - "https://github.company.com" - ] + "examples": ["https://acme.ghe.com", "https://github.company.com"] }, "copilot-api-target": { "type": "string", "description": "Manual override for the Copilot API endpoint hostname (e.g., 'api.acme.ghe.com' or 'api.enterprise.githubcopilot.com'). Use this when automatic detection via server-url doesn't work for your setup. Takes precedence over server-url.", - "examples": [ - "api.acme.ghe.com", - "api.enterprise.githubcopilot.com", - "api.custom.endpoint.com" - ] + "examples": ["api.acme.ghe.com", "api.enterprise.githubcopilot.com", "api.custom.endpoint.com"] } }, "additionalProperties": false diff --git a/pkg/workflow/awf_helpers.go b/pkg/workflow/awf_helpers.go index ab69ad9b720..3089027d647 100644 --- a/pkg/workflow/awf_helpers.go +++ b/pkg/workflow/awf_helpers.go @@ -338,10 +338,10 @@ func extractAPITargetHost(workflowData *WorkflowData, envVar string) string { // This supports GitHub Enterprise Cloud (GHEC) and GitHub Enterprise Server (GHES) deployments. // // The function checks enterprise configuration in the following priority order: -// 1. engine.enterprise.copilot-api-target - Manual override (highest priority) -// 2. engine.enterprise.server-url - Exported as COPILOT_SERVER_URL environment variable, -// AWF automatically detects GHEC (*.ghe.com) or GHES (custom domains) and routes appropriately -// 3. No enterprise config - AWF uses GitHub Actions GITHUB_SERVER_URL or defaults to api.githubcopilot.com +// 1. engine.enterprise.copilot-api-target - Manual override (highest priority) +// 2. engine.enterprise.server-url - Exported as COPILOT_SERVER_URL environment variable, +// AWF automatically detects GHEC (*.ghe.com) or GHES (custom domains) and routes appropriately +// 3. No enterprise config - AWF uses GitHub Actions GITHUB_SERVER_URL or defaults to api.githubcopilot.com // // Parameters: // - workflowData: The workflow data containing engine configuration @@ -351,26 +351,26 @@ func extractAPITargetHost(workflowData *WorkflowData, envVar string) string { // // Example frontmatter configurations: // -// # GHEC with automatic detection (recommended) -// engine: -// id: copilot -// enterprise: -// server-url: "https://acme.ghe.com" -// # AWF automatically routes to api.acme.ghe.com +// # GHEC with automatic detection (recommended) +// engine: +// id: copilot +// enterprise: +// server-url: "https://acme.ghe.com" +// # AWF automatically routes to api.acme.ghe.com // -// # GHES with automatic detection (recommended) -// engine: -// id: copilot -// enterprise: -// server-url: "https://github.company.com" -// # AWF automatically routes to api.enterprise.githubcopilot.com +// # GHES with automatic detection (recommended) +// engine: +// id: copilot +// enterprise: +// server-url: "https://github.company.com" +// # AWF automatically routes to api.enterprise.githubcopilot.com // -// # Manual override (for custom configurations) -// engine: -// id: copilot -// enterprise: -// copilot-api-target: "api.custom.endpoint.com" -// # AWF uses specified endpoint directly +// # Manual override (for custom configurations) +// engine: +// id: copilot +// enterprise: +// copilot-api-target: "api.custom.endpoint.com" +// # AWF uses specified endpoint directly func extractCopilotAPITarget(workflowData *WorkflowData) string { // Check if engine config is available if workflowData == nil || workflowData.EngineConfig == nil { diff --git a/pkg/workflow/engine.go b/pkg/workflow/engine.go index f67c2a94bec..41004fb6c43 100644 --- a/pkg/workflow/engine.go +++ b/pkg/workflow/engine.go @@ -24,9 +24,9 @@ type EngineConfig struct { Env map[string]string Config string Args []string - Firewall *FirewallConfig // AWF firewall configuration - Agent string // Agent identifier for copilot --agent flag (copilot engine only) - Enterprise *EnterpriseConfig // Enterprise/GHE configuration (copilot engine only) + Firewall *FirewallConfig // AWF firewall configuration + Agent string // Agent identifier for copilot --agent flag (copilot engine only) + Enterprise *EnterpriseConfig // Enterprise/GHE configuration (copilot engine only) // Inline definition fields (populated when engine.runtime is specified in frontmatter) IsInlineDefinition bool // true when the engine is defined inline via engine.runtime + optional engine.provider @@ -49,23 +49,23 @@ type EngineConfig struct { // // 1. GHEC - Automatic detection (recommended): // enterprise: -// server-url: "https://acme.ghe.com" +// server-url: "https://acme.ghe.com" // Result: AWF automatically routes to api.acme.ghe.com // // 2. GHES - Automatic detection (recommended): // enterprise: -// server-url: "https://github.company.com" +// server-url: "https://github.company.com" // Result: AWF automatically routes to api.enterprise.githubcopilot.com // // 3. Manual override (for custom configurations): // enterprise: -// copilot-api-target: "api.custom.endpoint.com" +// copilot-api-target: "api.custom.endpoint.com" // Result: AWF uses specified endpoint directly // // Priority: copilot-api-target > server-url detection > GitHub Actions GITHUB_SERVER_URL > default (api.githubcopilot.com) type EnterpriseConfig struct { - ServerURL string `yaml:"server-url,omitempty"` // GitHub Enterprise Server URL (e.g., "https://acme.ghe.com" or "https://github.company.com") - CopilotAPITarget string `yaml:"copilot-api-target,omitempty"` // Manual override for Copilot API endpoint (e.g., "api.acme.ghe.com" or "api.enterprise.githubcopilot.com") + ServerURL string `yaml:"server-url,omitempty"` // GitHub Enterprise Server URL (e.g., "https://acme.ghe.com" or "https://github.company.com") + CopilotAPITarget string `yaml:"copilot-api-target,omitempty"` // Manual override for Copilot API endpoint (e.g., "api.acme.ghe.com" or "api.enterprise.githubcopilot.com") } // NetworkPermissions represents network access permissions for workflow execution diff --git a/pkg/workflow/engine_test.go b/pkg/workflow/engine_test.go index 34e4ba7d5a6..104fcee054c 100644 --- a/pkg/workflow/engine_test.go +++ b/pkg/workflow/engine_test.go @@ -250,3 +250,126 @@ func TestEngineCommandField(t *testing.T) { }) } } + +// TestEnterpriseConfigExtraction tests that enterprise configuration is correctly +// extracted from frontmatter for GitHub Enterprise Cloud (GHEC) and Server (GHES). +func TestEnterpriseConfigExtraction(t *testing.T) { + tests := []struct { + name string + frontmatter map[string]any + expectedServerURL string + expectedCopilotAPITarget string + shouldHaveEnterpriseConfig bool + }{ + { + name: "GHEC with server-url", + frontmatter: map[string]any{ + "engine": map[string]any{ + "id": "copilot", + "enterprise": map[string]any{ + "server-url": "https://acme.ghe.com", + }, + }, + }, + expectedServerURL: "https://acme.ghe.com", + expectedCopilotAPITarget: "", + shouldHaveEnterpriseConfig: true, + }, + { + name: "GHES with server-url", + frontmatter: map[string]any{ + "engine": map[string]any{ + "id": "copilot", + "enterprise": map[string]any{ + "server-url": "https://github.company.com", + }, + }, + }, + expectedServerURL: "https://github.company.com", + expectedCopilotAPITarget: "", + shouldHaveEnterpriseConfig: true, + }, + { + name: "manual copilot-api-target override", + frontmatter: map[string]any{ + "engine": map[string]any{ + "id": "copilot", + "enterprise": map[string]any{ + "copilot-api-target": "api.custom.endpoint.com", + }, + }, + }, + expectedServerURL: "", + expectedCopilotAPITarget: "api.custom.endpoint.com", + shouldHaveEnterpriseConfig: true, + }, + { + name: "both server-url and copilot-api-target", + frontmatter: map[string]any{ + "engine": map[string]any{ + "id": "copilot", + "enterprise": map[string]any{ + "server-url": "https://acme.ghe.com", + "copilot-api-target": "api.acme.ghe.com", + }, + }, + }, + expectedServerURL: "https://acme.ghe.com", + expectedCopilotAPITarget: "api.acme.ghe.com", + shouldHaveEnterpriseConfig: true, + }, + { + name: "no enterprise config", + frontmatter: map[string]any{ + "engine": map[string]any{ + "id": "copilot", + }, + }, + expectedServerURL: "", + expectedCopilotAPITarget: "", + shouldHaveEnterpriseConfig: false, + }, + { + name: "empty enterprise config", + frontmatter: map[string]any{ + "engine": map[string]any{ + "id": "copilot", + "enterprise": map[string]any{}, + }, + }, + expectedServerURL: "", + expectedCopilotAPITarget: "", + shouldHaveEnterpriseConfig: true, + }, + } + + compiler := NewCompiler() + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + _, config := compiler.ExtractEngineConfig(tt.frontmatter) + + if config == nil { + t.Fatal("Expected config to be non-nil") + } + + if tt.shouldHaveEnterpriseConfig { + if config.Enterprise == nil { + t.Fatal("Expected Enterprise config to be non-nil") + } + + if config.Enterprise.ServerURL != tt.expectedServerURL { + t.Errorf("Expected server-url %q, got %q", tt.expectedServerURL, config.Enterprise.ServerURL) + } + + if config.Enterprise.CopilotAPITarget != tt.expectedCopilotAPITarget { + t.Errorf("Expected copilot-api-target %q, got %q", tt.expectedCopilotAPITarget, config.Enterprise.CopilotAPITarget) + } + } else { + if config.Enterprise != nil { + t.Errorf("Expected Enterprise config to be nil, got %+v", config.Enterprise) + } + } + }) + } +} diff --git a/pkg/workflow/secrets_validation.go b/pkg/workflow/secrets_validation.go index 5e9e3dbc830..50aee2ad6ba 100644 --- a/pkg/workflow/secrets_validation.go +++ b/pkg/workflow/secrets_validation.go @@ -12,9 +12,6 @@ var secretsValidationLog = newValidationLogger("secrets") // This is the same pattern used in the github_token schema definition ($defs/github_token). var secretsExpressionPattern = regexp.MustCompile(`^\$\{\{\s*secrets\.[A-Za-z_][A-Za-z0-9_]*(\s*\|\|\s*secrets\.[A-Za-z_][A-Za-z0-9_]*)*\s*\}\}$`) -// secretNamePattern validates that a secret name follows environment variable naming conventions -var secretNamePattern = regexp.MustCompile(`^[A-Z][A-Z0-9_]*$`) - // validateSecretsExpression validates that a value is a proper GitHub Actions secrets expression. // Returns an error if the value is not in the format: ${{ secrets.NAME }} or ${{ secrets.NAME || secrets.NAME2 }} // Note: This function intentionally does not accept the secret key name as a parameter to prevent From fe7442f4ce9f0e5aac46d95875ad97a12c230fee Mon Sep 17 00:00:00 2001 From: "anthropic-code-agent[bot]" <242468646+Claude@users.noreply.github.com> Date: Thu, 12 Mar 2026 23:07:22 +0000 Subject: [PATCH 05/11] Add enterprise configuration documentation Co-authored-by: lpcox <15877973+lpcox@users.noreply.github.com> --- docs/enterprise-configuration.md | 247 +++++++++++++++++++++++++++++++ 1 file changed, 247 insertions(+) create mode 100644 docs/enterprise-configuration.md diff --git a/docs/enterprise-configuration.md b/docs/enterprise-configuration.md new file mode 100644 index 00000000000..aba2c0b69c6 --- /dev/null +++ b/docs/enterprise-configuration.md @@ -0,0 +1,247 @@ +# Enterprise Configuration for Copilot Agents + +This guide explains how to configure GitHub Agentic Workflows for GitHub Enterprise Cloud (GHEC) and GitHub Enterprise Server (GHES) customers. + +## Overview + +GitHub Agentic Workflows automatically detects your GitHub environment and configures the appropriate Copilot API endpoints through AWF (Agentic Workflow Firewall). The system intelligently routes GitHub Copilot API traffic based on your enterprise configuration. + +## Automatic Detection (Recommended) + +AWF automatically detects GitHub Enterprise environments based on the `GITHUB_SERVER_URL` environment variable, which is set by GitHub Actions in enterprise environments. + +### GitHub Enterprise Cloud (GHEC) + +For GHEC tenants (domains ending with `.ghe.com`), AWF automatically extracts the subdomain and routes to the tenant-specific API endpoint. + +**Workflow Configuration:** + +```yaml +--- +engine: + id: copilot + enterprise: + server-url: "https://acme.ghe.com" +network: + allowed: + - defaults + - acme.ghe.com + - api.acme.ghe.com +--- +``` + +**How it works:** +1. AWF reads `GITHUB_SERVER_URL` from the environment +2. Detects that the hostname ends with `.ghe.com` +3. Extracts the subdomain (e.g., `acme` from `acme.ghe.com`) +4. Routes Copilot API traffic to `api.acme.ghe.com` + +**Required domains in network allowlist:** +- `acme.ghe.com` - Your GHEC tenant domain (git operations, web UI) +- `api.acme.ghe.com` - Your tenant-specific Copilot API endpoint +- `raw.githubusercontent.com` - Raw content access (if using GitHub MCP server) + +### GitHub Enterprise Server (GHES) + +For GHES instances (custom domains), AWF automatically routes to the enterprise Copilot endpoint. + +**Workflow Configuration:** + +```yaml +--- +engine: + id: copilot + enterprise: + server-url: "https://github.company.com" +network: + allowed: + - defaults + - github.company.com + - api.enterprise.githubcopilot.com +--- +``` + +**How it works:** +1. AWF reads `GITHUB_SERVER_URL` from the environment +2. Detects that the hostname is not `github.com` or `*.ghe.com` +3. Routes Copilot API traffic to `api.enterprise.githubcopilot.com` + +**Required domains in network allowlist:** +- `github.company.com` - Your GHES instance (git operations, web UI) +- `api.enterprise.githubcopilot.com` - Enterprise Copilot API endpoint (used for all GHES instances) + +## Manual Override + +If automatic detection doesn't work for your setup, you can manually specify the Copilot API endpoint. + +**Workflow Configuration:** + +```yaml +--- +engine: + id: copilot + enterprise: + copilot-api-target: "api.custom.endpoint.com" +network: + allowed: + - defaults + - custom.endpoint.com + - api.custom.endpoint.com +--- +``` + +The `copilot-api-target` field takes precedence over automatic detection. + +## Priority Order + +AWF determines the Copilot API endpoint in this order: + +1. **`engine.enterprise.copilot-api-target`** (highest priority) - Manual override +2. **`engine.enterprise.server-url`** with `*.ghe.com` - Automatic GHEC detection → `api..ghe.com` +3. **`engine.enterprise.server-url`** with custom domain - Automatic GHES detection → `api.enterprise.githubcopilot.com` +4. **GitHub Actions `GITHUB_SERVER_URL`** - Uses the environment variable set by GitHub Actions +5. **Default** - Public GitHub → `api.githubcopilot.com` + +## Complete Examples + +### GHEC with GitHub MCP Server + +```yaml +--- +description: Workflow for GHEC environment with GitHub API access +on: + workflow_dispatch: +permissions: + contents: read + issues: write + pull-requests: write +engine: + id: copilot + enterprise: + server-url: "https://acme.ghe.com" +tools: + github: + mode: remote + toolsets: [default] +network: + allowed: + - defaults + - acme.ghe.com + - api.acme.ghe.com + - raw.githubusercontent.com +--- + +# Your workflow prompt here +``` + +### GHES with Custom Endpoint + +```yaml +--- +description: Workflow for GHES environment +on: + issue_comment: + types: [created] +permissions: + contents: read + issues: write +engine: + id: copilot + enterprise: + server-url: "https://github.company.com" +network: + allowed: + - defaults + - github.company.com + - api.enterprise.githubcopilot.com +--- + +# Your workflow prompt here +``` + +### Manual Override Example + +```yaml +--- +description: Workflow with manual API endpoint override +on: + workflow_dispatch: +permissions: + contents: read +engine: + id: copilot + enterprise: + copilot-api-target: "api.custom.endpoint.com" +network: + allowed: + - defaults + - custom.endpoint.com + - api.custom.endpoint.com +--- + +# Your workflow prompt here +``` + +## Verification + +To verify your configuration is working correctly: + +### 1. Check Compiled Workflow + +After compiling your workflow, check the generated `.lock.yml` file: + +```bash +gh aw compile your-workflow.md +``` + +Look for: +- `GITHUB_SERVER_URL` environment variable in the agent job +- `--copilot-api-target` flag in AWF command (if using manual override) + +### 2. Check Workflow Runs + +In GitHub Actions workflow runs: +1. Go to the agent job +2. Check the "Run Copilot Agent" step +3. Verify the AWF command includes the correct API target +4. Check AWF logs for "Copilot proxy listening" messages + +## Troubleshooting + +### Wrong API Endpoint + +**Problem:** Traffic is going to the wrong Copilot API endpoint + +**Solutions:** +1. Verify `engine.enterprise.server-url` is set correctly in your workflow frontmatter +2. Check that the domain is in your `network.allowed` list +3. Use `copilot-api-target` to manually override if automatic detection fails +4. Review AWF logs in the workflow run for endpoint detection messages + +### Domain Not Whitelisted + +**Problem:** Requests are blocked with network errors + +**Solution:** Add the missing domain to your `network.allowed` list: +- For GHEC: `[acme.ghe.com, api.acme.ghe.com]` +- For GHES: `[github.company.com, api.enterprise.githubcopilot.com]` + +### GitHub MCP Server Issues + +**Problem:** GitHub MCP server fails to connect to your enterprise instance + +**Solutions:** +1. Ensure your GHEC/GHES domain is in `network.allowed` +2. Verify the GitHub token has appropriate scopes for your enterprise tenant +3. Use `mode: remote` for the GitHub MCP server when on GHEC/GHES + +## Related Documentation + +- [AWF Enterprise Configuration](https://github.com/github/gh-aw-firewall/blob/main/docs/enterprise-configuration.md) - Detailed AWF documentation +- [GitHub Actions Environment Variables](https://docs.github.com/en/actions/learn-github-actions/variables#default-environment-variables) - Default GitHub Actions variables +- [Network Permissions](network.md) - Network access configuration +- [Tools Configuration](tools.md) - MCP server and tool setup + +## See Also + +For more information about the underlying AWF firewall configuration that enables enterprise support, see the [gh-aw-firewall PR #1264](https://github.com/github/gh-aw-firewall/pull/1264) which adds automatic endpoint detection. From e6747ceea3c65502e39fa083acc43a08f262da7b Mon Sep 17 00:00:00 2001 From: Landon Cox Date: Thu, 12 Mar 2026 19:55:14 -0700 Subject: [PATCH 06/11] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- pkg/workflow/copilot_engine_execution.go | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/pkg/workflow/copilot_engine_execution.go b/pkg/workflow/copilot_engine_execution.go index e766950859d..2fb4c013339 100644 --- a/pkg/workflow/copilot_engine_execution.go +++ b/pkg/workflow/copilot_engine_execution.go @@ -244,16 +244,6 @@ COPILOT_CLI_INSTRUCTION="$(cat /tmp/gh-aw/aw-prompts/prompt.txt)" "GITHUB_API_URL": "${{ github.api_url }}", } - // Override GITHUB_SERVER_URL if engine.enterprise.server-url is configured. - // This allows workflow authors to explicitly set the enterprise server URL for - // GitHub Enterprise Cloud (GHEC) or Server (GHES) environments. - // AWF's api-proxy will automatically detect GHEC (*.ghe.com) or GHES (custom domains) - // and route Copilot API traffic to the appropriate endpoint. - if workflowData.EngineConfig != nil && workflowData.EngineConfig.Enterprise != nil && workflowData.EngineConfig.Enterprise.ServerURL != "" { - env["GITHUB_SERVER_URL"] = workflowData.EngineConfig.Enterprise.ServerURL - copilotExecLog.Printf("Overriding GITHUB_SERVER_URL with enterprise server-url: %s", workflowData.EngineConfig.Enterprise.ServerURL) - } - // When copilot-requests feature is enabled, set S2STOKENS=true to allow the Copilot CLI // to accept GitHub App installation tokens (ghs_*) such as ${{ github.token }}. if useCopilotRequests { @@ -269,6 +259,16 @@ COPILOT_CLI_INSTRUCTION="$(cat /tmp/gh-aw/aw-prompts/prompt.txt)" // Always add GH_AW_PROMPT for agentic workflows env["GH_AW_PROMPT"] = "/tmp/gh-aw/aw-prompts/prompt.txt" + + // Override GITHUB_SERVER_URL if engine.enterprise.server-url is configured. + // This allows workflow authors to explicitly set the enterprise server URL for + // GitHub Enterprise Cloud (GHEC) or Server (GHES) environments. + // AWF's api-proxy will automatically detect GHEC (*.ghe.com) or GHES (custom domains) + // and route Copilot API traffic to the appropriate endpoint. + if workflowData.EngineConfig != nil && workflowData.EngineConfig.Enterprise != nil && workflowData.EngineConfig.Enterprise.ServerURL != "" { + env["GITHUB_SERVER_URL"] = workflowData.EngineConfig.Enterprise.ServerURL + copilotExecLog.Printf("Overriding GITHUB_SERVER_URL with enterprise server-url: %s", workflowData.EngineConfig.Enterprise.ServerURL) + } // Tag the step as a GitHub AW agentic execution for discoverability by agents env["GITHUB_AW"] = "true" // Indicate the phase: "agent" for the main run, "detection" for threat detection From bcff7e3286571f5521aa8475a9ba6dac2dd46415 Mon Sep 17 00:00:00 2001 From: Landon Cox Date: Thu, 12 Mar 2026 19:56:01 -0700 Subject: [PATCH 07/11] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- pkg/workflow/engine.go | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/pkg/workflow/engine.go b/pkg/workflow/engine.go index 41004fb6c43..e59196a7a35 100644 --- a/pkg/workflow/engine.go +++ b/pkg/workflow/engine.go @@ -345,25 +345,30 @@ func (c *Compiler) ExtractEngineConfig(frontmatter map[string]any) (string, *Eng if enterprise, hasEnterprise := engineObj["enterprise"]; hasEnterprise { if enterpriseObj, ok := enterprise.(map[string]any); ok { enterpriseConfig := &EnterpriseConfig{} + hasEnterpriseConfig := false // Extract server-url field if serverURL, hasServerURL := enterpriseObj["server-url"]; hasServerURL { - if serverURLStr, ok := serverURL.(string); ok { + if serverURLStr, ok := serverURL.(string); ok && serverURLStr != "" { enterpriseConfig.ServerURL = serverURLStr + hasEnterpriseConfig = true engineLog.Printf("Extracted enterprise server-url: %s", serverURLStr) } } // Extract copilot-api-target field (manual override) if copilotAPITarget, hasCopilotAPITarget := enterpriseObj["copilot-api-target"]; hasCopilotAPITarget { - if copilotAPITargetStr, ok := copilotAPITarget.(string); ok { + if copilotAPITargetStr, ok := copilotAPITarget.(string); ok && copilotAPITargetStr != "" { enterpriseConfig.CopilotAPITarget = copilotAPITargetStr + hasEnterpriseConfig = true engineLog.Printf("Extracted enterprise copilot-api-target: %s", copilotAPITargetStr) } } - config.Enterprise = enterpriseConfig - engineLog.Print("Extracted enterprise configuration") + if hasEnterpriseConfig { + config.Enterprise = enterpriseConfig + engineLog.Print("Extracted enterprise configuration") + } } } From 80a3a2e5e02227e183d5867e69f175721dc82136 Mon Sep 17 00:00:00 2001 From: Landon Cox Date: Thu, 12 Mar 2026 19:56:31 -0700 Subject: [PATCH 08/11] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- docs/enterprise-configuration.md | 30 ++++++++++++++++++++++-------- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/docs/enterprise-configuration.md b/docs/enterprise-configuration.md index aba2c0b69c6..4188cca0508 100644 --- a/docs/enterprise-configuration.md +++ b/docs/enterprise-configuration.md @@ -8,20 +8,18 @@ GitHub Agentic Workflows automatically detects your GitHub environment and confi ## Automatic Detection (Recommended) -AWF automatically detects GitHub Enterprise environments based on the `GITHUB_SERVER_URL` environment variable, which is set by GitHub Actions in enterprise environments. +AWF automatically detects GitHub Enterprise environments based on the `GITHUB_SERVER_URL` environment variable, which is set by GitHub Actions in enterprise environments, unless you explicitly override detection with `engine.enterprise.server-url` in your workflow frontmatter. ### GitHub Enterprise Cloud (GHEC) For GHEC tenants (domains ending with `.ghe.com`), AWF automatically extracts the subdomain and routes to the tenant-specific API endpoint. -**Workflow Configuration:** +**Workflow Configuration (automatic detection):** ```yaml --- engine: id: copilot - enterprise: - server-url: "https://acme.ghe.com" network: allowed: - defaults @@ -36,6 +34,15 @@ network: 3. Extracts the subdomain (e.g., `acme` from `acme.ghe.com`) 4. Routes Copilot API traffic to `api.acme.ghe.com` +If `GITHUB_SERVER_URL` is not set (for example, when running outside of GitHub Actions) or you need to force a specific tenant, you can override automatic detection by adding an explicit server URL: + +```yaml +engine: + id: copilot + enterprise: + server-url: "https://acme.ghe.com" +``` + **Required domains in network allowlist:** - `acme.ghe.com` - Your GHEC tenant domain (git operations, web UI) - `api.acme.ghe.com` - Your tenant-specific Copilot API endpoint @@ -43,16 +50,14 @@ network: ### GitHub Enterprise Server (GHES) -For GHES instances (custom domains), AWF automatically routes to the enterprise Copilot endpoint. +For GHES instances (custom domains), AWF automatically routes to the enterprise Copilot endpoint based on `GITHUB_SERVER_URL`. -**Workflow Configuration:** +**Workflow Configuration (automatic detection):** ```yaml --- engine: id: copilot - enterprise: - server-url: "https://github.company.com" network: allowed: - defaults @@ -61,6 +66,15 @@ network: --- ``` +If `GITHUB_SERVER_URL` is not available or you need to force a specific GHES URL, you can override automatic detection with: + +```yaml +engine: + id: copilot + enterprise: + server-url: "https://github.company.com" +``` + **How it works:** 1. AWF reads `GITHUB_SERVER_URL` from the environment 2. Detects that the hostname is not `github.com` or `*.ghe.com` From 88ae8c6eee6470c80732195160dc92828a46fcf0 Mon Sep 17 00:00:00 2001 From: Landon Cox Date: Thu, 12 Mar 2026 19:56:49 -0700 Subject: [PATCH 09/11] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- pkg/parser/schemas/main_workflow_schema.json | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pkg/parser/schemas/main_workflow_schema.json b/pkg/parser/schemas/main_workflow_schema.json index 3c32fba4297..5d9a397b34d 100644 --- a/pkg/parser/schemas/main_workflow_schema.json +++ b/pkg/parser/schemas/main_workflow_schema.json @@ -7977,6 +7977,8 @@ "properties": { "server-url": { "type": "string", + "minLength": 9, + "pattern": "^https://.*", "description": "GitHub Enterprise Server URL (e.g., 'https://acme.ghe.com' for GHEC or 'https://github.company.com' for GHES). AWF automatically detects GHEC (*.ghe.com) and GHES (custom domains) and routes to the appropriate Copilot API endpoint.", "examples": ["https://acme.ghe.com", "https://github.company.com"] }, From 7d7611e3976ca05f280ec8fcf410b0d4aa3e704a Mon Sep 17 00:00:00 2001 From: Landon Cox Date: Thu, 12 Mar 2026 19:57:09 -0700 Subject: [PATCH 10/11] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- pkg/workflow/awf_helpers.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pkg/workflow/awf_helpers.go b/pkg/workflow/awf_helpers.go index 3089027d647..6b7eb1f2626 100644 --- a/pkg/workflow/awf_helpers.go +++ b/pkg/workflow/awf_helpers.go @@ -389,13 +389,13 @@ func extractCopilotAPITarget(workflowData *WorkflowData) string { return enterpriseConfig.CopilotAPITarget } - // Priority 2: server-url will be exported as COPILOT_SERVER_URL environment variable + // Priority 2: server-url will be exported as GITHUB_SERVER_URL environment variable // AWF's api-proxy will automatically detect GHEC (*.ghe.com) or GHES (custom domains) // and route to the appropriate endpoint. We don't return the server-url directly here // because AWF needs the full URL for detection, not just the hostname. - // The server-url is handled by exporting COPILOT_SERVER_URL in the engine execution steps. + // The server-url is handled by exporting GITHUB_SERVER_URL in the engine execution steps. if enterpriseConfig.ServerURL != "" { - awfHelpersLog.Printf("Enterprise server-url configured: %s (will be exported as COPILOT_SERVER_URL)", enterpriseConfig.ServerURL) + awfHelpersLog.Printf("Enterprise server-url configured: %s (will be exported as GITHUB_SERVER_URL)", enterpriseConfig.ServerURL) // Return empty string - the server-url will be passed via environment variable // and AWF will automatically derive the correct Copilot API endpoint return "" From a31aa27caa953ceef4301109c2baa8c76f575ac3 Mon Sep 17 00:00:00 2001 From: "anthropic-code-agent[bot]" <242468646+Claude@users.noreply.github.com> Date: Fri, 13 Mar 2026 03:10:36 +0000 Subject: [PATCH 11/11] Simplify enterprise configuration to single api-target field Replace the complex enterprise object (with server-url and copilot-api-target) with a simple api-target field that works for any agentic engine. Changes: - [x] Remove EnterpriseConfig struct and Enterprise field from EngineConfig - [x] Add APITarget string field to EngineConfig for custom API endpoints - [x] Update engine config extraction to parse api-target from frontmatter - [x] Simplify AWF helpers to use engine.api-target directly - [x] Remove GITHUB_SERVER_URL override logic from copilot execution - [x] Update JSON schema to replace enterprise object with api-target string - [x] Update tests: TestAPITargetExtraction replaces TestEnterpriseConfigExtraction - [x] Update documentation: Simplified from automatic detection to manual configuration Benefits: - Works with any agentic engine (Copilot, Claude, Codex, custom) - Simpler frontmatter configuration (one field vs nested object) - No automatic detection complexity or confusion - Clearer user intent - explicit API endpoint specification Example usage: ```yaml engine: id: copilot api-target: api.acme.ghe.com ``` Co-authored-by: lpcox <15877973+lpcox@users.noreply.github.com> --- docs/enterprise-configuration.md | 127 ++++++++----------- pkg/parser/schemas/main_workflow_schema.json | 22 +--- pkg/workflow/awf_helpers.go | 82 +----------- pkg/workflow/copilot_engine_execution.go | 9 -- pkg/workflow/engine.go | 65 ++-------- pkg/workflow/engine_test.go | 97 ++++---------- 6 files changed, 93 insertions(+), 309 deletions(-) diff --git a/docs/enterprise-configuration.md b/docs/enterprise-configuration.md index 4188cca0508..da05d6dfd8d 100644 --- a/docs/enterprise-configuration.md +++ b/docs/enterprise-configuration.md @@ -1,25 +1,26 @@ -# Enterprise Configuration for Copilot Agents +# Custom API Endpoint Configuration -This guide explains how to configure GitHub Agentic Workflows for GitHub Enterprise Cloud (GHEC) and GitHub Enterprise Server (GHES) customers. +This guide explains how to configure GitHub Agentic Workflows to use custom API endpoints for GitHub Enterprise Cloud (GHEC), GitHub Enterprise Server (GHES), or custom AI endpoints. ## Overview -GitHub Agentic Workflows automatically detects your GitHub environment and configures the appropriate Copilot API endpoints through AWF (Agentic Workflow Firewall). The system intelligently routes GitHub Copilot API traffic based on your enterprise configuration. +GitHub Agentic Workflows supports custom API endpoints through the `engine.api-target` configuration field. This allows you to specify custom endpoints for: -## Automatic Detection (Recommended) +- **GitHub Enterprise Cloud (GHEC)** - Tenant-specific Copilot API endpoints +- **GitHub Enterprise Server (GHES)** - Enterprise Copilot API endpoints +- **Custom AI Endpoints** - Custom OpenAI-compatible or Anthropic-compatible endpoints -AWF automatically detects GitHub Enterprise environments based on the `GITHUB_SERVER_URL` environment variable, which is set by GitHub Actions in enterprise environments, unless you explicitly override detection with `engine.enterprise.server-url` in your workflow frontmatter. +## Configuration -### GitHub Enterprise Cloud (GHEC) - -For GHEC tenants (domains ending with `.ghe.com`), AWF automatically extracts the subdomain and routes to the tenant-specific API endpoint. +To configure a custom API endpoint, add the `api-target` field to your engine configuration: -**Workflow Configuration (automatic detection):** +**Basic Configuration:** ```yaml --- engine: id: copilot + api-target: api.acme.ghe.com network: allowed: - defaults @@ -28,19 +29,27 @@ network: --- ``` -**How it works:** -1. AWF reads `GITHUB_SERVER_URL` from the environment -2. Detects that the hostname ends with `.ghe.com` -3. Extracts the subdomain (e.g., `acme` from `acme.ghe.com`) -4. Routes Copilot API traffic to `api.acme.ghe.com` +The `api-target` field accepts a hostname (without protocol or path) and works with any agentic engine. + +## Examples + +### GitHub Enterprise Cloud (GHEC) + +For GHEC tenants (domains ending with `.ghe.com`), specify your tenant-specific API endpoint: -If `GITHUB_SERVER_URL` is not set (for example, when running outside of GitHub Actions) or you need to force a specific tenant, you can override automatic detection by adding an explicit server URL: +**Workflow Configuration:** ```yaml +--- engine: id: copilot - enterprise: - server-url: "https://acme.ghe.com" + api-target: api.acme.ghe.com +network: + allowed: + - defaults + - acme.ghe.com + - api.acme.ghe.com +--- ``` **Required domains in network allowlist:** @@ -50,14 +59,15 @@ engine: ### GitHub Enterprise Server (GHES) -For GHES instances (custom domains), AWF automatically routes to the enterprise Copilot endpoint based on `GITHUB_SERVER_URL`. +For GHES instances (custom domains), specify the enterprise Copilot endpoint: -**Workflow Configuration (automatic detection):** +**Workflow Configuration:** ```yaml --- engine: id: copilot + api-target: api.enterprise.githubcopilot.com network: allowed: - defaults @@ -66,56 +76,28 @@ network: --- ``` -If `GITHUB_SERVER_URL` is not available or you need to force a specific GHES URL, you can override automatic detection with: - -```yaml -engine: - id: copilot - enterprise: - server-url: "https://github.company.com" -``` - -**How it works:** -1. AWF reads `GITHUB_SERVER_URL` from the environment -2. Detects that the hostname is not `github.com` or `*.ghe.com` -3. Routes Copilot API traffic to `api.enterprise.githubcopilot.com` - **Required domains in network allowlist:** - `github.company.com` - Your GHES instance (git operations, web UI) - `api.enterprise.githubcopilot.com` - Enterprise Copilot API endpoint (used for all GHES instances) -## Manual Override +### Custom AI Endpoints -If automatic detection doesn't work for your setup, you can manually specify the Copilot API endpoint. +The `api-target` field works with any agentic engine, allowing you to use custom AI endpoints: **Workflow Configuration:** ```yaml --- engine: - id: copilot - enterprise: - copilot-api-target: "api.custom.endpoint.com" + id: codex + api-target: api.custom.ai-provider.com network: allowed: - defaults - - custom.endpoint.com - - api.custom.endpoint.com + - api.custom.ai-provider.com --- ``` -The `copilot-api-target` field takes precedence over automatic detection. - -## Priority Order - -AWF determines the Copilot API endpoint in this order: - -1. **`engine.enterprise.copilot-api-target`** (highest priority) - Manual override -2. **`engine.enterprise.server-url`** with `*.ghe.com` - Automatic GHEC detection → `api..ghe.com` -3. **`engine.enterprise.server-url`** with custom domain - Automatic GHES detection → `api.enterprise.githubcopilot.com` -4. **GitHub Actions `GITHUB_SERVER_URL`** - Uses the environment variable set by GitHub Actions -5. **Default** - Public GitHub → `api.githubcopilot.com` - ## Complete Examples ### GHEC with GitHub MCP Server @@ -131,8 +113,7 @@ permissions: pull-requests: write engine: id: copilot - enterprise: - server-url: "https://acme.ghe.com" + api-target: api.acme.ghe.com tools: github: mode: remote @@ -161,8 +142,7 @@ permissions: issues: write engine: id: copilot - enterprise: - server-url: "https://github.company.com" + api-target: api.enterprise.githubcopilot.com network: allowed: - defaults @@ -173,24 +153,22 @@ network: # Your workflow prompt here ``` -### Manual Override Example +### Custom AI Provider ```yaml --- -description: Workflow with manual API endpoint override +description: Workflow with custom AI endpoint on: workflow_dispatch: permissions: contents: read engine: - id: copilot - enterprise: - copilot-api-target: "api.custom.endpoint.com" + id: codex + api-target: api.custom.ai-provider.com network: allowed: - defaults - - custom.endpoint.com - - api.custom.endpoint.com + - api.custom.ai-provider.com --- # Your workflow prompt here @@ -209,28 +187,28 @@ gh aw compile your-workflow.md ``` Look for: -- `GITHUB_SERVER_URL` environment variable in the agent job -- `--copilot-api-target` flag in AWF command (if using manual override) +- `--copilot-api-target` flag in AWF command (if using Copilot engine) +- Correct API endpoint hostname in the flag value ### 2. Check Workflow Runs In GitHub Actions workflow runs: 1. Go to the agent job -2. Check the "Run Copilot Agent" step +2. Check the "Run Copilot Agent" (or equivalent) step 3. Verify the AWF command includes the correct API target -4. Check AWF logs for "Copilot proxy listening" messages +4. Check AWF logs for API connection messages ## Troubleshooting ### Wrong API Endpoint -**Problem:** Traffic is going to the wrong Copilot API endpoint +**Problem:** Traffic is going to the wrong API endpoint **Solutions:** -1. Verify `engine.enterprise.server-url` is set correctly in your workflow frontmatter +1. Verify `engine.api-target` is set correctly in your workflow frontmatter 2. Check that the domain is in your `network.allowed` list -3. Use `copilot-api-target` to manually override if automatic detection fails -4. Review AWF logs in the workflow run for endpoint detection messages +3. Review AWF logs in the workflow run for endpoint configuration messages +4. Ensure you're not using a full URL (use hostname only: `api.acme.ghe.com` not `https://api.acme.ghe.com`) ### Domain Not Whitelisted @@ -239,6 +217,7 @@ In GitHub Actions workflow runs: **Solution:** Add the missing domain to your `network.allowed` list: - For GHEC: `[acme.ghe.com, api.acme.ghe.com]` - For GHES: `[github.company.com, api.enterprise.githubcopilot.com]` +- For custom AI: `[api.custom.ai-provider.com]` ### GitHub MCP Server Issues @@ -251,11 +230,7 @@ In GitHub Actions workflow runs: ## Related Documentation -- [AWF Enterprise Configuration](https://github.com/github/gh-aw-firewall/blob/main/docs/enterprise-configuration.md) - Detailed AWF documentation +- [AWF Firewall Configuration](https://github.com/github/gh-aw-firewall) - Detailed AWF documentation - [GitHub Actions Environment Variables](https://docs.github.com/en/actions/learn-github-actions/variables#default-environment-variables) - Default GitHub Actions variables - [Network Permissions](network.md) - Network access configuration - [Tools Configuration](tools.md) - MCP server and tool setup - -## See Also - -For more information about the underlying AWF firewall configuration that enables enterprise support, see the [gh-aw-firewall PR #1264](https://github.com/github/gh-aw-firewall/pull/1264) which adds automatic endpoint detection. diff --git a/pkg/parser/schemas/main_workflow_schema.json b/pkg/parser/schemas/main_workflow_schema.json index 5d9a397b34d..af31aac6577 100644 --- a/pkg/parser/schemas/main_workflow_schema.json +++ b/pkg/parser/schemas/main_workflow_schema.json @@ -7971,24 +7971,10 @@ "type": "string", "description": "Agent identifier to pass to copilot --agent flag (copilot engine only). Specifies which custom agent to use for the workflow." }, - "enterprise": { - "type": "object", - "description": "GitHub Enterprise Cloud (GHEC) or Server (GHES) configuration for the Copilot engine. Configures AWF to route API traffic to the correct enterprise endpoint.", - "properties": { - "server-url": { - "type": "string", - "minLength": 9, - "pattern": "^https://.*", - "description": "GitHub Enterprise Server URL (e.g., 'https://acme.ghe.com' for GHEC or 'https://github.company.com' for GHES). AWF automatically detects GHEC (*.ghe.com) and GHES (custom domains) and routes to the appropriate Copilot API endpoint.", - "examples": ["https://acme.ghe.com", "https://github.company.com"] - }, - "copilot-api-target": { - "type": "string", - "description": "Manual override for the Copilot API endpoint hostname (e.g., 'api.acme.ghe.com' or 'api.enterprise.githubcopilot.com'). Use this when automatic detection via server-url doesn't work for your setup. Takes precedence over server-url.", - "examples": ["api.acme.ghe.com", "api.enterprise.githubcopilot.com", "api.custom.endpoint.com"] - } - }, - "additionalProperties": false + "api-target": { + "type": "string", + "description": "Custom API endpoint hostname for the agentic engine. Used for GitHub Enterprise Cloud (GHEC), GitHub Enterprise Server (GHES), or custom AI endpoints. Example: 'api.acme.ghe.com' for GHEC, 'api.enterprise.githubcopilot.com' for GHES, or custom endpoint hostnames.", + "examples": ["api.acme.ghe.com", "api.enterprise.githubcopilot.com", "api.custom.endpoint.com"] }, "args": { "type": "array", diff --git a/pkg/workflow/awf_helpers.go b/pkg/workflow/awf_helpers.go index 6b7eb1f2626..b59525badcf 100644 --- a/pkg/workflow/awf_helpers.go +++ b/pkg/workflow/awf_helpers.go @@ -205,12 +205,11 @@ func BuildAWFArgs(config AWFCommandConfig) []string { awfHelpersLog.Printf("Added --anthropic-api-target=%s", anthropicTarget) } - // Add Copilot API target for GitHub Enterprise Cloud/Server - // Priority: engine.enterprise.copilot-api-target > engine.enterprise.server-url (via env var) - copilotAPITarget := extractCopilotAPITarget(config.WorkflowData) - if copilotAPITarget != "" { - awfArgs = append(awfArgs, "--copilot-api-target", copilotAPITarget) - awfHelpersLog.Printf("Added --copilot-api-target=%s", copilotAPITarget) + // Add Copilot API target for custom Copilot endpoints (GHEC, GHES, or custom) + // This uses the engine.api-target field if configured + if config.WorkflowData.EngineConfig != nil && config.WorkflowData.EngineConfig.APITarget != "" { + awfArgs = append(awfArgs, "--copilot-api-target", config.WorkflowData.EngineConfig.APITarget) + awfHelpersLog.Printf("Added --copilot-api-target=%s", config.WorkflowData.EngineConfig.APITarget) } // Add SSL Bump support for HTTPS content inspection (v0.9.0+) @@ -333,74 +332,3 @@ func extractAPITargetHost(workflowData *WorkflowData, envVar string) string { awfHelpersLog.Printf("Extracted API target host from %s: %s", envVar, host) return host } - -// extractCopilotAPITarget extracts the Copilot API target from enterprise configuration. -// This supports GitHub Enterprise Cloud (GHEC) and GitHub Enterprise Server (GHES) deployments. -// -// The function checks enterprise configuration in the following priority order: -// 1. engine.enterprise.copilot-api-target - Manual override (highest priority) -// 2. engine.enterprise.server-url - Exported as COPILOT_SERVER_URL environment variable, -// AWF automatically detects GHEC (*.ghe.com) or GHES (custom domains) and routes appropriately -// 3. No enterprise config - AWF uses GitHub Actions GITHUB_SERVER_URL or defaults to api.githubcopilot.com -// -// Parameters: -// - workflowData: The workflow data containing engine configuration -// -// Returns: -// - string: The Copilot API target hostname, or empty string if not configured -// -// Example frontmatter configurations: -// -// # GHEC with automatic detection (recommended) -// engine: -// id: copilot -// enterprise: -// server-url: "https://acme.ghe.com" -// # AWF automatically routes to api.acme.ghe.com -// -// # GHES with automatic detection (recommended) -// engine: -// id: copilot -// enterprise: -// server-url: "https://github.company.com" -// # AWF automatically routes to api.enterprise.githubcopilot.com -// -// # Manual override (for custom configurations) -// engine: -// id: copilot -// enterprise: -// copilot-api-target: "api.custom.endpoint.com" -// # AWF uses specified endpoint directly -func extractCopilotAPITarget(workflowData *WorkflowData) string { - // Check if engine config is available - if workflowData == nil || workflowData.EngineConfig == nil { - return "" - } - - // Check if enterprise config is present - enterpriseConfig := workflowData.EngineConfig.Enterprise - if enterpriseConfig == nil { - return "" - } - - // Priority 1: Manual override via copilot-api-target (highest priority) - if enterpriseConfig.CopilotAPITarget != "" { - awfHelpersLog.Printf("Using manual Copilot API target override: %s", enterpriseConfig.CopilotAPITarget) - return enterpriseConfig.CopilotAPITarget - } - - // Priority 2: server-url will be exported as GITHUB_SERVER_URL environment variable - // AWF's api-proxy will automatically detect GHEC (*.ghe.com) or GHES (custom domains) - // and route to the appropriate endpoint. We don't return the server-url directly here - // because AWF needs the full URL for detection, not just the hostname. - // The server-url is handled by exporting GITHUB_SERVER_URL in the engine execution steps. - if enterpriseConfig.ServerURL != "" { - awfHelpersLog.Printf("Enterprise server-url configured: %s (will be exported as GITHUB_SERVER_URL)", enterpriseConfig.ServerURL) - // Return empty string - the server-url will be passed via environment variable - // and AWF will automatically derive the correct Copilot API endpoint - return "" - } - - // No enterprise configuration - return "" -} diff --git a/pkg/workflow/copilot_engine_execution.go b/pkg/workflow/copilot_engine_execution.go index 2fb4c013339..ceeeb0d406e 100644 --- a/pkg/workflow/copilot_engine_execution.go +++ b/pkg/workflow/copilot_engine_execution.go @@ -260,15 +260,6 @@ COPILOT_CLI_INSTRUCTION="$(cat /tmp/gh-aw/aw-prompts/prompt.txt)" // Always add GH_AW_PROMPT for agentic workflows env["GH_AW_PROMPT"] = "/tmp/gh-aw/aw-prompts/prompt.txt" - // Override GITHUB_SERVER_URL if engine.enterprise.server-url is configured. - // This allows workflow authors to explicitly set the enterprise server URL for - // GitHub Enterprise Cloud (GHEC) or Server (GHES) environments. - // AWF's api-proxy will automatically detect GHEC (*.ghe.com) or GHES (custom domains) - // and route Copilot API traffic to the appropriate endpoint. - if workflowData.EngineConfig != nil && workflowData.EngineConfig.Enterprise != nil && workflowData.EngineConfig.Enterprise.ServerURL != "" { - env["GITHUB_SERVER_URL"] = workflowData.EngineConfig.Enterprise.ServerURL - copilotExecLog.Printf("Overriding GITHUB_SERVER_URL with enterprise server-url: %s", workflowData.EngineConfig.Enterprise.ServerURL) - } // Tag the step as a GitHub AW agentic execution for discoverability by agents env["GITHUB_AW"] = "true" // Indicate the phase: "agent" for the main run, "detection" for threat detection diff --git a/pkg/workflow/engine.go b/pkg/workflow/engine.go index e59196a7a35..76955165d6e 100644 --- a/pkg/workflow/engine.go +++ b/pkg/workflow/engine.go @@ -24,9 +24,9 @@ type EngineConfig struct { Env map[string]string Config string Args []string - Firewall *FirewallConfig // AWF firewall configuration - Agent string // Agent identifier for copilot --agent flag (copilot engine only) - Enterprise *EnterpriseConfig // Enterprise/GHE configuration (copilot engine only) + Firewall *FirewallConfig // AWF firewall configuration + Agent string // Agent identifier for copilot --agent flag (copilot engine only) + APITarget string // Custom API endpoint hostname (e.g., "api.acme.ghe.com" or "api.enterprise.githubcopilot.com") // Inline definition fields (populated when engine.runtime is specified in frontmatter) IsInlineDefinition bool // true when the engine is defined inline via engine.runtime + optional engine.provider @@ -42,32 +42,6 @@ type EngineConfig struct { InlineProviderRequest *RequestShape // request shaping parsed from engine.provider.request } -// EnterpriseConfig represents GitHub Enterprise Cloud (GHEC) or Server (GHES) configuration -// for the Copilot engine. This configuration is passed to AWF to route API traffic correctly. -// -// Examples: -// -// 1. GHEC - Automatic detection (recommended): -// enterprise: -// server-url: "https://acme.ghe.com" -// Result: AWF automatically routes to api.acme.ghe.com -// -// 2. GHES - Automatic detection (recommended): -// enterprise: -// server-url: "https://github.company.com" -// Result: AWF automatically routes to api.enterprise.githubcopilot.com -// -// 3. Manual override (for custom configurations): -// enterprise: -// copilot-api-target: "api.custom.endpoint.com" -// Result: AWF uses specified endpoint directly -// -// Priority: copilot-api-target > server-url detection > GitHub Actions GITHUB_SERVER_URL > default (api.githubcopilot.com) -type EnterpriseConfig struct { - ServerURL string `yaml:"server-url,omitempty"` // GitHub Enterprise Server URL (e.g., "https://acme.ghe.com" or "https://github.company.com") - CopilotAPITarget string `yaml:"copilot-api-target,omitempty"` // Manual override for Copilot API endpoint (e.g., "api.acme.ghe.com" or "api.enterprise.githubcopilot.com") -} - // NetworkPermissions represents network access permissions for workflow execution // Controls which domains the workflow can access during execution. // @@ -341,34 +315,11 @@ func (c *Compiler) ExtractEngineConfig(frontmatter map[string]any) (string, *Eng } } - // Extract optional 'enterprise' field (object format - copilot engine only) - if enterprise, hasEnterprise := engineObj["enterprise"]; hasEnterprise { - if enterpriseObj, ok := enterprise.(map[string]any); ok { - enterpriseConfig := &EnterpriseConfig{} - hasEnterpriseConfig := false - - // Extract server-url field - if serverURL, hasServerURL := enterpriseObj["server-url"]; hasServerURL { - if serverURLStr, ok := serverURL.(string); ok && serverURLStr != "" { - enterpriseConfig.ServerURL = serverURLStr - hasEnterpriseConfig = true - engineLog.Printf("Extracted enterprise server-url: %s", serverURLStr) - } - } - - // Extract copilot-api-target field (manual override) - if copilotAPITarget, hasCopilotAPITarget := enterpriseObj["copilot-api-target"]; hasCopilotAPITarget { - if copilotAPITargetStr, ok := copilotAPITarget.(string); ok && copilotAPITargetStr != "" { - enterpriseConfig.CopilotAPITarget = copilotAPITargetStr - hasEnterpriseConfig = true - engineLog.Printf("Extracted enterprise copilot-api-target: %s", copilotAPITargetStr) - } - } - - if hasEnterpriseConfig { - config.Enterprise = enterpriseConfig - engineLog.Print("Extracted enterprise configuration") - } + // Extract optional 'api-target' field (custom API endpoint for any engine) + if apiTarget, hasAPITarget := engineObj["api-target"]; hasAPITarget { + if apiTargetStr, ok := apiTarget.(string); ok && apiTargetStr != "" { + config.APITarget = apiTargetStr + engineLog.Printf("Extracted api-target: %s", apiTargetStr) } } diff --git a/pkg/workflow/engine_test.go b/pkg/workflow/engine_test.go index 104fcee054c..d8f65c5c837 100644 --- a/pkg/workflow/engine_test.go +++ b/pkg/workflow/engine_test.go @@ -251,95 +251,62 @@ func TestEngineCommandField(t *testing.T) { } } -// TestEnterpriseConfigExtraction tests that enterprise configuration is correctly -// extracted from frontmatter for GitHub Enterprise Cloud (GHEC) and Server (GHES). -func TestEnterpriseConfigExtraction(t *testing.T) { +// TestAPITargetExtraction tests that the api-target configuration is correctly +// extracted from frontmatter for custom API endpoints (GHEC, GHES, or custom AI endpoints). +func TestAPITargetExtraction(t *testing.T) { tests := []struct { - name string - frontmatter map[string]any - expectedServerURL string - expectedCopilotAPITarget string - shouldHaveEnterpriseConfig bool + name string + frontmatter map[string]any + expectedAPITarget string }{ { - name: "GHEC with server-url", + name: "GHEC api-target", frontmatter: map[string]any{ "engine": map[string]any{ - "id": "copilot", - "enterprise": map[string]any{ - "server-url": "https://acme.ghe.com", - }, - }, - }, - expectedServerURL: "https://acme.ghe.com", - expectedCopilotAPITarget: "", - shouldHaveEnterpriseConfig: true, - }, - { - name: "GHES with server-url", - frontmatter: map[string]any{ - "engine": map[string]any{ - "id": "copilot", - "enterprise": map[string]any{ - "server-url": "https://github.company.com", - }, + "id": "copilot", + "api-target": "api.acme.ghe.com", }, }, - expectedServerURL: "https://github.company.com", - expectedCopilotAPITarget: "", - shouldHaveEnterpriseConfig: true, + expectedAPITarget: "api.acme.ghe.com", }, { - name: "manual copilot-api-target override", + name: "GHES api-target", frontmatter: map[string]any{ "engine": map[string]any{ - "id": "copilot", - "enterprise": map[string]any{ - "copilot-api-target": "api.custom.endpoint.com", - }, + "id": "copilot", + "api-target": "api.enterprise.githubcopilot.com", }, }, - expectedServerURL: "", - expectedCopilotAPITarget: "api.custom.endpoint.com", - shouldHaveEnterpriseConfig: true, + expectedAPITarget: "api.enterprise.githubcopilot.com", }, { - name: "both server-url and copilot-api-target", + name: "custom api-target", frontmatter: map[string]any{ "engine": map[string]any{ - "id": "copilot", - "enterprise": map[string]any{ - "server-url": "https://acme.ghe.com", - "copilot-api-target": "api.acme.ghe.com", - }, + "id": "codex", + "api-target": "api.custom.endpoint.com", }, }, - expectedServerURL: "https://acme.ghe.com", - expectedCopilotAPITarget: "api.acme.ghe.com", - shouldHaveEnterpriseConfig: true, + expectedAPITarget: "api.custom.endpoint.com", }, { - name: "no enterprise config", + name: "no api-target", frontmatter: map[string]any{ "engine": map[string]any{ "id": "copilot", }, }, - expectedServerURL: "", - expectedCopilotAPITarget: "", - shouldHaveEnterpriseConfig: false, + expectedAPITarget: "", }, { - name: "empty enterprise config", + name: "empty api-target", frontmatter: map[string]any{ "engine": map[string]any{ "id": "copilot", - "enterprise": map[string]any{}, + "api-target": "", }, }, - expectedServerURL: "", - expectedCopilotAPITarget: "", - shouldHaveEnterpriseConfig: true, + expectedAPITarget: "", }, } @@ -353,22 +320,8 @@ func TestEnterpriseConfigExtraction(t *testing.T) { t.Fatal("Expected config to be non-nil") } - if tt.shouldHaveEnterpriseConfig { - if config.Enterprise == nil { - t.Fatal("Expected Enterprise config to be non-nil") - } - - if config.Enterprise.ServerURL != tt.expectedServerURL { - t.Errorf("Expected server-url %q, got %q", tt.expectedServerURL, config.Enterprise.ServerURL) - } - - if config.Enterprise.CopilotAPITarget != tt.expectedCopilotAPITarget { - t.Errorf("Expected copilot-api-target %q, got %q", tt.expectedCopilotAPITarget, config.Enterprise.CopilotAPITarget) - } - } else { - if config.Enterprise != nil { - t.Errorf("Expected Enterprise config to be nil, got %+v", config.Enterprise) - } + if config.APITarget != tt.expectedAPITarget { + t.Errorf("Expected api-target %q, got %q", tt.expectedAPITarget, config.APITarget) } }) }