Skip to content
Open
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
88 changes: 81 additions & 7 deletions pkg/local_workflows/connectivity_check_extension/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ A Go-based extension for the Snyk CLI that performs comprehensive network connec
- **Comprehensive Endpoint Testing**: Tests connectivity to all Snyk API endpoints and services
- **Proxy Detection & Validation**: Automatically detects and validates proxy configurations
- **Organization Listing**: Displays your Snyk organizations when authenticated (with default organization highlighted)
- **Directory Permission Checks**: Verifies permissions for directories used by Snyk (CLI downloads, config, cache, temp)
- **Multiple Output Formats**: Human-readable (with color support) and JSON formats
- **Actionable Diagnostics**: Provides specific recommendations based on connectivity issues
- **Integration Ready**: Built as a workflow extension for the Snyk CLI using go-application-framework
Expand All @@ -30,6 +31,10 @@ The tool respects standard proxy environment variables:
For custom certificates:
- `NODE_EXTRA_CA_CERTS` - Path to additional CA certificates bundle

For proxy authentication (Kerberos on Linux/macOS):
- `KRB5_CONFIG` - Kerberos configuration file path
- `KRB5CCNAME` - Kerberos credential cache

### Authentication

The tool uses Snyk authentication from the go-application-framework configuration:
Expand All @@ -54,12 +59,15 @@ When authenticated, the tool will display your organizations with their IDs. The
Checking for proxy configuration...

Environment variables:
HTTPS_PROXY: (not set)
https_proxy: (not set)
HTTP_PROXY: (not set)
http_proxy: (not set)
NO_PROXY: (not set)
no_proxy: (not set)
HTTPS_PROXY: (not set)
https_proxy: (not set)
HTTP_PROXY: (not set)
http_proxy: (not set)
NO_PROXY: (not set)
no_proxy: (not set)
NODE_EXTRA_CA_CERTS: (not set)
KRB5_CONFIG: (not set)
KRB5CCNAME: (not set)

ℹ No proxy detected - Testing direct connection...

Expand Down Expand Up @@ -99,6 +107,22 @@ Group ID Org ID Name
---------------------------------------------------------------------------------------------------------------------------------------
a1b2c3d4-e5f6-7890-abcd-ef1234567890 d4e5f6a7-b890-cdef-1234-567890abcdef My Organization my-organization Yes
b2c3d4e5-f6a7-8901-bcde-f23456789012 e5f6a7b8-c901-def2-3456-7890abcdef12 Another Org another-org

--- Current User Information ---
Username: john.doe

--- Potential Snyk Used Configuration and CLI Download Directories ---

Directory: /Users/john/.local/share/snyk-ls (Purpose: Default CLI Download Location for Language Server)
✓ Exists
✓ Writable (permissions: 0755)
Found 1 potential Snyk CLI binary/binaries:
• snyk-macos (permissions: 0755)

Directory: /Users/john/Library/Caches/snyk/snyk-cli (Purpose: CLI Download Cache for GAF)
⚠ Does not exist
Nearest existing parent: /Users/john/Library/Caches
✓ Writable (permissions: 0755)
```

### JSON Output
Expand All @@ -113,7 +137,10 @@ snyk tools connectivity-check --json --experimental
"detected": false,
"url": "",
"variable": "",
"noProxy": ""
"noProxy": "",
"nodeExtraCACerts": "",
"krb5Config": "",
"krb5CCName": ""
},
"hostResults": [
{
Expand Down Expand Up @@ -143,6 +170,30 @@ snyk tools connectivity-check --json --experimental
}
],
"tokenPresent": true,
"currentUser": "john.doe",
"directoryResults": [
{
"pathWanted": "/Users/john/.local/share/snyk-ls",
"purpose": "Default CLI Download Location for Language Server",
"pathFound": "/Users/john/.local/share/snyk-ls",
"isWritable": true,
"permissions": "0755",
"binariesFound": [
{
"name": "snyk-macos",
"permissions": "0755"
}
]
},
{
"pathWanted": "/Users/john/Library/Caches/snyk/snyk-cli",
"purpose": "CLI Download Cache for GAF",
"pathFound": "/Users/john/Library/Caches",
"isWritable": true,
"permissions": "0755",
"binariesFound": []
}
],
"startTime": "2024-01-15T10:00:00Z",
"endTime": "2024-01-15T10:00:05Z"
}
Expand Down Expand Up @@ -175,6 +226,29 @@ If authenticated but no organizations shown:
- Check if you belong to any organizations
- Try re-authenticating with `snyk auth`

#### Directory Permission Issues
If directories show as not writable:
- **Linux/macOS**: Check directory ownership with `ls -ld <path>`
- **Windows**: Check folder properties and security settings
- **Common fix**: Create the directory manually with appropriate permissions
- **IDE extensions**: If CLI downloads or config writes fail, the tool will highlight which directory lacks write permissions

The tool checks the following default directories used by Snyk:
- **Default VS Code Extension CLI Download Location**:
- macOS: `~/Library/Application Support/snyk/vscode-cli` and `$XDG_DATA_HOME/snyk/vscode-cli` (defaults to `~/.local/share/snyk/vscode-cli`)
- Linux: `$XDG_DATA_HOME/snyk/vscode-cli` (defaults to `~/.local/share/snyk/vscode-cli`)
- Windows: `%LOCALAPPDATA%\snyk\vscode-cli`
- **Default Eclipse Plugin CLI Download Location**: `~/.snyk` (Linux/macOS) or `%LOCALAPPDATA%\Snyk` (Windows)
- **Default Visual Studio Plugin CLI Download Location**: `%LOCALAPPDATA%\Snyk` (Windows)
- **Default Language Server CLI Download Location**: `$XDG_DATA_HOME/snyk-ls` (defaults to `~/.local/share/snyk-ls` on Linux/macOS)
- **Language Server Config**:
- macOS: `~/Library/Application Support/snyk`
- Linux: `~/.config/snyk`
- Windows: `%LOCALAPPDATA%\snyk`
- **Runtime Cache for Temporary Files**: `{UserCacheDir}/snyk/snyk-cli` (OS-specific cache location)

**Note**: Callers may pass additional directories to check via the `additional-check-dirs` configuration parameter.

### Status Meanings

- **OK**: Full connectivity verified
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,16 +20,17 @@ import (

// Checker performs connectivity checks to Snyk endpoints
type Checker struct {
networkAccess networking.NetworkAccess
logger *zerolog.Logger
config configuration.Configuration
timeout time.Duration
apiClient api.ApiClient
ui ui.UserInterface
networkAccess networking.NetworkAccess
logger *zerolog.Logger
config configuration.Configuration
timeout time.Duration
apiClient api.ApiClient
ui ui.UserInterface
additionalDirs []UsedDirectory
}

// NewChecker creates a new connectivity checker
func NewChecker(networkAccess networking.NetworkAccess, logger *zerolog.Logger, config configuration.Configuration, ui ...ui.UserInterface) *Checker {
// NewChecker creates a new connectivity checker with optional additional directories
func NewChecker(networkAccess networking.NetworkAccess, logger *zerolog.Logger, config configuration.Configuration, additionalDirs []UsedDirectory, ui ...ui.UserInterface) *Checker {
timeout := time.Duration(config.GetInt("timeout")) * time.Second
if timeout == 0 {
timeout = 10 * time.Second
Expand All @@ -44,11 +45,12 @@ func NewChecker(networkAccess networking.NetworkAccess, logger *zerolog.Logger,
apiClient := api.NewApi(apiUrl, httpClient)

checker := &Checker{
networkAccess: networkAccess,
logger: logger,
config: config,
timeout: timeout,
apiClient: apiClient,
networkAccess: networkAccess,
logger: logger,
config: config,
timeout: timeout,
apiClient: apiClient,
additionalDirs: additionalDirs,
}

// Set UI if provided
Expand All @@ -60,18 +62,19 @@ func NewChecker(networkAccess networking.NetworkAccess, logger *zerolog.Logger,
}

// NewCheckerWithApiClient creates a new connectivity checker with a custom API client (mainly for testing)
func NewCheckerWithApiClient(networkAccess networking.NetworkAccess, logger *zerolog.Logger, config configuration.Configuration, apiClient api.ApiClient, ui ...ui.UserInterface) *Checker {
func NewCheckerWithApiClient(networkAccess networking.NetworkAccess, logger *zerolog.Logger, config configuration.Configuration, additionalDirs []UsedDirectory, apiClient api.ApiClient, ui ...ui.UserInterface) *Checker {
timeout := time.Duration(config.GetInt("timeout")) * time.Second
if timeout == 0 {
timeout = 10 * time.Second
}

checker := &Checker{
networkAccess: networkAccess,
logger: logger,
config: config,
timeout: timeout,
apiClient: apiClient,
networkAccess: networkAccess,
logger: logger,
config: config,
timeout: timeout,
apiClient: apiClient,
additionalDirs: additionalDirs,
}

// Set UI if provided
Expand Down Expand Up @@ -105,6 +108,19 @@ func (c *Checker) DetectProxyConfig() ProxyConfig {
config.NoProxy = noProxy
}

// Check NODE_EXTRA_CA_CERTS
if nodeExtraCACerts := os.Getenv("NODE_EXTRA_CA_CERTS"); nodeExtraCACerts != "" {
config.NodeExtraCACerts = nodeExtraCACerts
}

// Check Kerberos environment variables
if krb5Config := os.Getenv("KRB5_CONFIG"); krb5Config != "" {
config.KRB5Config = krb5Config
}
if krb5CCName := os.Getenv("KRB5CCNAME"); krb5CCName != "" {
config.KRB5CCName = krb5CCName
}

return config
}

Expand Down Expand Up @@ -244,15 +260,37 @@ func (c *Checker) CheckConnectivity() (*ConnectivityCheckResult, error) {
}()
}

// Step 1: Detect proxy configuration
c.updateProgress(progressBar, 0.1, "Detecting proxy configuration...")
// Initial step 1: Detect current user
c.updateProgress(progressBar, 0.01, "Detecting current user...")
result.CurrentUser = GetCurrentUser()

// Initial step 2: Detect proxy configuration
c.updateProgress(progressBar, 0.02, "Detecting proxy configuration...")
result.ProxyConfig = c.DetectProxyConfig()

// Step 2: Check connectivity to hosts
// Initial step 3: Get directories to check for progress calculation
c.updateProgress(progressBar, 0.03, "Determining potential configuration and CLI download directories to check...")
usedDirectories := GetDefaultUsedDirectories(c.logger)
// Append additional directories if provided
usedDirectories = append(usedDirectories, c.additionalDirs...)
// Deduplicate all directories (combines purposes for duplicates)
usedDirectories = DedupeDirectories(usedDirectories)

// Initial step 4: Get Snyk hosts to check
c.updateProgress(progressBar, 0.04, "Determining Snyk hosts to check...")
hosts := GetSnykHosts()
totalSteps := float64(len(hosts) + 3) // hosts + proxy + auth + orgs
currentStep := 1.0

// Determine total steps for progress calculation
currentStep := 1.0 // initial quick steps combined count as 1 step
totalSteps := float64(
int(currentStep) +
len(hosts) +
1 /* auth */ +
1 /* orgs */ +
len(usedDirectories),
)

// Step 1: Check connectivity to hosts
for i, host := range hosts {
progress := (currentStep + float64(i)) / totalSteps
c.updateProgress(progressBar, progress, fmt.Sprintf("Checking connectivity to %s...", host))
Expand All @@ -263,14 +301,13 @@ func (c *Checker) CheckConnectivity() (*ConnectivityCheckResult, error) {
}
currentStep += float64(len(hosts))

// Step 3: Check authentication
// Step 2: Check authentication
c.updateProgress(progressBar, currentStep/totalSteps, "Checking authentication...")
currentStep++

token := c.checkAuthentication()
result.TokenPresent = token != ""
currentStep++

// Step 4: Check organizations (if authenticated)
// Step 3: Check organizations (if authenticated)
if result.TokenPresent {
c.updateProgress(progressBar, currentStep/totalSteps, "Fetching organizations...")

Expand All @@ -288,6 +325,17 @@ func (c *Checker) CheckConnectivity() (*ConnectivityCheckResult, error) {
result.Organizations = orgs
}
}
currentStep++

// Step 4: Check Snyk used directory permissions (CLI, config, cache, temp)
for i, dir := range usedDirectories {
progress := (currentStep + float64(i)) / totalSteps
c.updateProgress(progressBar, progress, fmt.Sprintf("Checking potential Snyk used directory: %s (Purpose: %s)", dir.PathWanted, dir.Purpose))

dirResult := CheckDirectory(dir)
result.DirectoryResults = append(result.DirectoryResults, dirResult)
}
//currentStep += float64(len(usedDirectories))

// Complete progress
c.updateProgress(progressBar, 1.0, "")
Expand Down
Loading