diff --git a/Kerberos_Configuration_Guide.md b/Kerberos_Configuration_Guide.md new file mode 100644 index 000000000..18e17bf15 --- /dev/null +++ b/Kerberos_Configuration_Guide.md @@ -0,0 +1,97 @@ +# Kerberos Authentication - Configuration Guide + +## Configuration Parameters + +### Required Parameters + +| Parameter | Flag | Environment Variable | Required | Description | +|-----------|------|---------------------|----------|-------------| +| **Proxy URL** | `--proxy` | `CX_HTTP_PROXY` | ✅ Yes | Proxy server URL | +| **Auth Type** | `--proxy-auth-type kerberos` | `CX_PROXY_AUTH_TYPE=kerberos` | ✅ Yes | Enable Kerberos | +| **Service Principal** | `--proxy-kerberos-spn` | `CX_PROXY_KERBEROS_SPN` | ✅ Yes | Proxy SPN | + +### Optional Parameters + +| Parameter | Flag | Environment Variable | Default | Description | +|-----------|------|---------------------|---------|-------------| +| **krb5.conf Path** | `--proxy-kerberos-krb5-conf` | `CX_PROXY_KERBEROS_KRB5_CONF` | `/etc/krb5.conf` (Linux) `C:\Windows\krb5.ini` (Windows) | Kerberos config file | +| **Credential Cache** | `--proxy-kerberos-ccache` | `CX_PROXY_KERBEROS_CCACHE` | `/tmp/krb5cc_$(id -u)` (Linux) Windows Credential Manager (Windows) | Ticket cache location | + +### Standard Kerberos Variables + +| Variable | Purpose | When to Use | +|----------|---------|-------------| +| `KRB5CCNAME` | Custom credential cache location | Alternative cache path | +| `KRB5_CONFIG` | Custom krb5.conf file location | Alternative config file | + +## Configuration Precedence + +| Priority | Source | Example | +|----------|--------|---------| +| **1 (Highest)** | Command-Line Flags | `--proxy-kerberos-spn HTTP/proxy.company.com` | +| **2** | Environment Variables | `export CX_PROXY_KERBEROS_SPN=HTTP/proxy.company.com` | +| **3** | Configuration File | `cx_proxy_kerberos_spn: HTTP/proxy.company.com` | +| **4 (Lowest)** | Default Values | Platform defaults | + +## Examples + +### Example 1: Project List with Flags + +```bash +# Get Kerberos tickets first +kinit user@COMPANY.COM + +# Run command with flags +cx project list \ + --proxy "http://proxy.company.com:8080" \ + --proxy-auth-type kerberos \ + --proxy-kerberos-spn "HTTP/proxy.company.com" +``` + +### Example 2: Scan with Environment Variables + +```bash +# Get Kerberos tickets +kinit user@COMPANY.COM + +# Set environment variables +export CX_HTTP_PROXY="http://proxy.company.com:8080" +export CX_PROXY_AUTH_TYPE=kerberos +export CX_PROXY_KERBEROS_SPN="HTTP/proxy.company.com" + +# Run scan (environment variables applied automatically) +cx scan create \ + --project-name "projectName" \ + -s "codePath" \ + --branch "branchName" +``` + +### Example 3: Mixed Configuration + +```bash +# Get tickets +kinit user@COMPANY.COM + +# Set base configuration via environment +export CX_HTTP_PROXY="http://proxy.company.com:8080" +export CX_PROXY_AUTH_TYPE=kerberos + +# Override SPN with flag (higher precedence) +cx project list --proxy-kerberos-spn "HTTP/specific-proxy.company.com" +``` + +## Setup Steps + +1. **Get Kerberos tickets**: `kinit user@COMPANY.COM` +2. **Verify tickets**: `klist` +3. **Configure proxy settings** (flags or environment variables) +4. **Run AST CLI commands** + +## Quick Reference + +| Action | Command | +|--------|---------| +| **Get tickets** | `kinit user@COMPANY.COM` | +| **Check tickets** | `klist` | +| **Renew tickets** | `kinit -R` | +| **Test connectivity** | `curl --proxy "http://proxy.company.com:8080" http://httpbin.org/ip` | diff --git a/PROXY_AUTHENTICATION_GUIDE.md b/PROXY_AUTHENTICATION_GUIDE.md new file mode 100644 index 000000000..b07526381 --- /dev/null +++ b/PROXY_AUTHENTICATION_GUIDE.md @@ -0,0 +1,383 @@ +# Proxy Authentication Guide + +This guide explains how to configure proxy authentication using NTLM and Kerberos protocols with the AST CLI tool. + +## Overview + +The AST CLI supports three types of proxy authentication: +- **Basic**: Standard username/password authentication +- **NTLM**: Windows NT LAN Manager authentication +- **Kerberos**: MIT Kerberos authentication protocol + +## Configuration Methods + +You can configure proxy authentication using either **command-line flags** or **environment variables**. + +--- + +## 🔧 Basic Proxy Authentication + +### Command Line Flags +```bash +cx scan create --proxy http://username:password@proxy.company.com:8080 +``` + +### Environment Variables +```bash +export HTTP_PROXY=http://username:password@proxy.company.com:8080 +# or +export CX_HTTP_PROXY=http://username:password@proxy.company.com:8080 +``` + +--- + +## 🔐 NTLM Proxy Authentication + +### When to Use NTLM +- Corporate environments using Windows-based proxy servers +- Active Directory domain authentication required +- Windows NTLM challenge-response authentication + +### Command Line Flags +```bash +cx scan create \ + --proxy http://proxy.company.com:8080 \ + --proxy-auth-type ntlm \ + --proxy-ntlm-domain COMPANY_DOMAIN +``` + +### Environment Variables +```bash +export CX_HTTP_PROXY=http://username:password@proxy.company.com:8080 +export CX_PROXY_AUTH_TYPE=ntlm +export CX_PROXY_NTLM_DOMAIN=COMPANY_DOMAIN +``` + +### NTLM Configuration Details + +| Flag | Environment Variable | Description | Required | +|------|---------------------|-------------|----------| +| `--proxy` | `CX_HTTP_PROXY` | Proxy URL with credentials | ✅ Yes | +| `--proxy-auth-type ntlm` | `CX_PROXY_AUTH_TYPE=ntlm` | Enable NTLM authentication | ✅ Yes | +| `--proxy-ntlm-domain` | `CX_PROXY_NTLM_DOMAIN` | Windows domain name | ✅ Yes | + +### NTLM Example +```bash +# Full NTLM configuration +cx scan create \ + --proxy http://john.doe:mypassword@proxy.company.com:8080 \ + --proxy-auth-type ntlm \ + --proxy-ntlm-domain COMPANY \ + --source-dir /path/to/source +``` + +--- + +## 🎫 Kerberos Proxy Authentication + +### Prerequisites +1. **Kerberos tickets**: Obtain valid Kerberos tickets using `kinit` +2. **SPN configuration**: Know the Service Principal Name for your proxy +3. **krb5.conf**: Proper Kerberos configuration file + +### Command Line Flags +```bash +cx scan create \ + --proxy http://proxy.company.com:8080 \ + --proxy-auth-type kerberos \ + --proxy-kerberos-spn HTTP/proxy.company.com +``` + +### Environment Variables +```bash +export CX_HTTP_PROXY=http://proxy.company.com:8080 +export CX_PROXY_AUTH_TYPE=kerberos +export CX_PROXY_KERBEROS_SPN=HTTP/proxy.company.com +``` + +### Kerberos Configuration Details + +| Flag | Environment Variable | Description | Required | +|------|---------------------|-------------|----------| +| `--proxy` | `CX_HTTP_PROXY` | Proxy URL (no credentials needed) | ✅ Yes | +| `--proxy-auth-type kerberos` | `CX_PROXY_AUTH_TYPE=kerberos` | Enable Kerberos authentication | ✅ Yes | +| `--proxy-kerberos-spn` | `CX_PROXY_KERBEROS_SPN` | Service Principal Name for proxy | ✅ Yes | +| `--proxy-kerberos-krb5-conf` | `CX_PROXY_KERBEROS_KRB5_CONF` | Path to krb5.conf file | ❌ Optional | +| `--proxy-kerberos-ccache` | `CX_PROXY_KERBEROS_CCACHE` | Path to credential cache | ❌ Optional | + +### Kerberos Setup Steps + +#### 1. Obtain Kerberos Tickets +```bash +# Get Kerberos tickets for your user +kinit username@REALM.COM + +# Verify tickets are available +klist +``` + +#### 2. Configure SPN +```bash +# Example SPN format for HTTP proxy +--proxy-kerberos-spn HTTP/proxy.company.com + +# Example SPN for specific port +--proxy-kerberos-spn HTTP/proxy.company.com:8080 +``` + +#### 3. Run with Kerberos Authentication +```bash +cx scan create \ + --proxy http://proxy.company.com:8080 \ + --proxy-auth-type kerberos \ + --proxy-kerberos-spn HTTP/proxy.company.com \ + --source-dir /path/to/source +``` + +### Advanced Kerberos Configuration + +#### Custom krb5.conf Location +```bash +cx scan create \ + --proxy http://proxy.company.com:8080 \ + --proxy-auth-type kerberos \ + --proxy-kerberos-spn HTTP/proxy.company.com \ + --proxy-kerberos-krb5-conf /etc/custom/krb5.conf +``` + +#### Custom Credential Cache +```bash +cx scan create \ + --proxy http://proxy.company.com:8080 \ + --proxy-auth-type kerberos \ + --proxy-kerberos-spn HTTP/proxy.company.com \ + --proxy-kerberos-ccache /tmp/custom_krb5cc +``` + +--- + +## 🌍 Default Locations + +### Kerberos Configuration Files + +**Linux/macOS:** +- krb5.conf: `/etc/krb5.conf` +- Credential cache: `/tmp/krb5cc_$(id -u)` + +**Windows:** +- krb5.conf: `C:\Windows\krb5.ini` +- Credential cache: Managed by Windows credential manager + +### Environment Variables for Kerberos +```bash +# Override default credential cache location +export KRB5CCNAME=/path/to/custom/ccache + +# Standard Kerberos environment variable +export KRB5_CONFIG=/path/to/custom/krb5.conf +``` + +--- + +## 📋 Complete Examples + +### NTLM Corporate Environment +```bash +#!/bin/bash +# NTLM proxy authentication example + +export CX_HTTP_PROXY=http://jdoe:password123@proxy.corp.com:8080 +export CX_PROXY_AUTH_TYPE=ntlm +export CX_PROXY_NTLM_DOMAIN=CORP + +cx scan create \ + --project-name "MyProject" \ + --source-dir /workspace/myapp \ + --branch main +``` + +### Kerberos Enterprise Environment +```bash +#!/bin/bash +# Kerberos proxy authentication example + +# 1. Get Kerberos tickets +kinit jdoe@CORP.COM + +# 2. Configure proxy with Kerberos +export CX_HTTP_PROXY=http://proxy.corp.com:8080 +export CX_PROXY_AUTH_TYPE=kerberos +export CX_PROXY_KERBEROS_SPN=HTTP/proxy.corp.com + +# 3. Run scan +cx scan create \ + --project-name "MyProject" \ + --source-dir /workspace/myapp \ + --branch main +``` + +### Mixed Configuration (Environment + Flags) +```bash +# Set proxy in environment +export CX_HTTP_PROXY=http://proxy.company.com:8080 + +# Use Kerberos with command-line flags +cx scan create \ + --proxy-auth-type kerberos \ + --proxy-kerberos-spn HTTP/proxy.company.com \ + --project-name "MyProject" \ + --source-dir . +``` + +--- + +## 🚨 Troubleshooting + +### Common NTLM Issues + +**Problem**: Authentication fails with 407 Proxy Authentication Required +``` +Solution: Verify domain name and credentials +- Check --proxy-ntlm-domain matches your Windows domain +- Ensure username/password in proxy URL are correct +- Test domain format: try both "DOMAIN" and "domain.com" +``` + +**Problem**: Connection timeout +``` +Solution: Check proxy URL format +- Ensure proxy URL includes protocol: http:// or https:// +- Verify proxy server address and port are correct +``` + +### Common Kerberos Issues + +**Problem**: "Kerberos SPN is required" error +``` +Solution: Always provide the SPN +--proxy-kerberos-spn HTTP/proxy.company.com + +Check with your system administrator for the correct SPN format. +``` + +**Problem**: "Kerberos credential cache not found" +``` +Solution: Obtain Kerberos tickets first +kinit username@REALM.COM + +Verify tickets exist: +klist +``` + +**Problem**: "Failed to generate SPNEGO token" +``` +Solution: Check SPN format and proxy configuration +- Verify SPN matches proxy server configuration +- Ensure proxy server supports Kerberos authentication +- Check krb5.conf file is properly configured +``` + +**Problem**: "Kerberos configuration file not found" +``` +Solution: Specify krb5.conf location +--proxy-kerberos-krb5-conf /path/to/krb5.conf + +Or ensure krb5.conf exists in default location (/etc/krb5.conf) +``` + +### Testing Authentication + +#### Test NTLM +```bash +# Enable verbose logging to see authentication details +cx scan create --verbose \ + --proxy http://user:pass@proxy.com:8080 \ + --proxy-auth-type ntlm \ + --proxy-ntlm-domain DOMAIN \ + --project-name test +``` + +#### Test Kerberos +```bash +# Enable verbose logging for Kerberos +cx project list create --verbose \ + --proxy http://proxy.com:8080 \ + --proxy-auth-type kerberos \ + --proxy-kerberos-spn HTTP/proxy.com \ + --project-name test +``` + +--- + +## 🔒 Security Best Practices + +### For NTLM +1. **Use HTTPS proxies** when possible to encrypt credentials +2. **Avoid hardcoding passwords** in scripts - use environment variables +3. **Rotate passwords regularly** according to company policy +4. **Limit proxy access** to necessary users only + +### For Kerberos +1. **Secure credential cache** - ensure proper file permissions (600) +2. **Regular ticket renewal** - use kinit periodically for long-running processes +3. **SPN verification** - confirm SPN with proxy administrator +4. **Network security** - ensure Kerberos traffic is protected + +### General +1. **Use environment variables** instead of command-line flags for sensitive data +2. **Enable verbose logging** only for troubleshooting +3. **Test authentication** in non-production environments first +4. **Monitor proxy logs** for authentication attempts + +--- + +## 📞 Support + +If you encounter issues: + +1. **Check logs** with `--verbose` flag +2. **Verify proxy server** supports the chosen authentication method +3. **Contact system administrator** for SPN/domain configuration +4. **Test proxy connectivity** outside of AST CLI first + +### System Administrator Checklist + +For NTLM: +- [ ] Proxy supports NTLM authentication +- [ ] User account has proxy access permissions +- [ ] Windows domain is correctly configured + +For Kerberos: +- [ ] Proxy server has SPN registered in KDC +- [ ] Proxy supports SPNEGO/Kerberos authentication +- [ ] Client machine can reach KDC +- [ ] krb5.conf is properly configured + +--- + +## 📚 Reference + +### All Available Flags +``` +--proxy Proxy server URL +--proxy-auth-type Authentication type (basic|ntlm|kerberos) +--proxy-ntlm-domain Windows domain for NTLM +--proxy-kerberos-spn Service Principal Name for Kerberos +--proxy-kerberos-krb5-conf Path to krb5.conf file +--proxy-kerberos-ccache Path to Kerberos credential cache +--ignore-proxy Ignore all proxy settings +``` + +### All Environment Variables +``` +HTTP_PROXY Standard proxy environment variable +CX_HTTP_PROXY Checkmarx proxy URL +CX_PROXY_AUTH_TYPE Authentication type +CX_PROXY_NTLM_DOMAIN NTLM domain name +CX_PROXY_KERBEROS_SPN Kerberos Service Principal Name +CX_PROXY_KERBEROS_KRB5_CONF Kerberos configuration file path +CX_PROXY_KERBEROS_CCACHE Kerberos credential cache path +KRB5CCNAME Standard Kerberos cache environment variable +``` + + diff --git a/go.mod b/go.mod index a3c82a9e5..464088386 100644 --- a/go.mod +++ b/go.mod @@ -17,6 +17,7 @@ require ( github.com/gomarkdown/markdown v0.0.0-20241102151059-6bc1ffdc6e8c github.com/google/uuid v1.6.0 github.com/gookit/color v1.5.4 + github.com/jcmturner/gokrb5/v8 v8.4.4 github.com/jsumners/go-getport v1.0.0 github.com/mssola/user_agent v0.6.0 github.com/pkg/errors v0.9.1 @@ -35,6 +36,12 @@ require ( ) require ( + github.com/hashicorp/go-uuid v1.0.3 // indirect + github.com/jcmturner/aescts/v2 v2.0.0 // indirect + github.com/jcmturner/dnsutils/v2 v2.0.0 // indirect + github.com/jcmturner/gofork v1.7.6 // indirect + github.com/jcmturner/goidentity/v6 v6.0.1 // indirect + github.com/jcmturner/rpc/v2 v2.0.3 // indirect dario.cat/mergo v1.0.1 // indirect github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6 // indirect github.com/AdamKorcz/go-118-fuzz-build v0.0.0-20240914100643-eb91380d8434 // indirect diff --git a/go.sum b/go.sum index 8f7971bce..8ae718cc1 100644 --- a/go.sum +++ b/go.sum @@ -543,6 +543,10 @@ github.com/gorilla/handlers v1.5.2 h1:cLTUSsNkgcwhgRqvCNmdbRWG0A3N4F+M2nWKdScwyE github.com/gorilla/handlers v1.5.2/go.mod h1:dX+xVpaxdSw+q0Qek8SSsl3dfMk3jNddUkMzo0GtH0w= github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= +github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ= +github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4= +github.com/gorilla/sessions v1.2.1 h1:DHd3rPN5lE3Ts3D8rKkQ8x/0kqfeNmBAaiSi+o7FsgI= +github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 h1:JeSE6pjso5THxAzdVpqr6/geYxZytqFMBCOtn/ujyeo= github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674/go.mod h1:r4w70xmWCQKmi1ONH4KIaBptdivuRPyosB9RmPlGEwA= @@ -579,6 +583,9 @@ github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerX github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8= +github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc= @@ -611,6 +618,18 @@ github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2 github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= +github.com/jcmturner/aescts/v2 v2.0.0 h1:9YKLH6ey7H4eDBXW8khjYslgyqG2xZikXP0EQFKrle8= +github.com/jcmturner/aescts/v2 v2.0.0/go.mod h1:AiaICIRyfYg35RUkr8yESTqvSy7csK90qZ5xfvvsoNs= +github.com/jcmturner/dnsutils/v2 v2.0.0 h1:lltnkeZGL0wILNvrNiVCR6Ro5PGU/SeBvVO/8c/iPbo= +github.com/jcmturner/dnsutils/v2 v2.0.0/go.mod h1:b0TnjGOvI/n42bZa+hmXL+kFJZsFT7G4t3HTlQ184QM= +github.com/jcmturner/gofork v1.7.6 h1:QH0l3hzAU1tfT3rZCnW5zXl+orbkNMMRGJfdJjHVETg= +github.com/jcmturner/gofork v1.7.6/go.mod h1:1622LH6i/EZqLloHfE7IeZ0uEJwMSUyQ/nDd82IeqRo= +github.com/jcmturner/goidentity/v6 v6.0.1 h1:VKnZd2oEIMorCTsFBnJWbExfNN7yZr3EhJAxwOkZg6o= +github.com/jcmturner/goidentity/v6 v6.0.1/go.mod h1:X1YW3bgtvwAXju7V3LCIMpY0Gbxyjn/mY9zx4tFonSg= +github.com/jcmturner/gokrb5/v8 v8.4.4 h1:x1Sv4HaTpepFkXbt2IkL29DXRf8sOfZXo8eRKh687T8= +github.com/jcmturner/gokrb5/v8 v8.4.4/go.mod h1:1btQEpgT6k+unzCwX1KdWMEwPPkkgBtP+F6aCACiMrs= +github.com/jcmturner/rpc/v2 v2.0.3 h1:7FXXj8Ti1IaVFpSAziCZWNzbNuZmnvw/i6CqLNdWfZY= +github.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc= github.com/jinzhu/copier v0.4.0 h1:w3ciUoD19shMCRargcpm0cm91ytaBhDvuRpz1ODO/U8= github.com/jinzhu/copier v0.4.0/go.mod h1:DfbEm0FYsaqBcKcFuvmOZb218JkPGtvSHsKg8S8hyyg= github.com/jmoiron/sqlx v1.4.0 h1:1PLqN7S1UYp5t4SrVVnt4nUVNemrDAtxlulVe+Qgm3o= @@ -1090,6 +1109,7 @@ golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM= golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -1176,6 +1196,8 @@ golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qx golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs= golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -1284,12 +1306,14 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA= golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.33.0 h1:NuFncQrRcaRvVmgRkvM3j/F00gWIAlcmlB8ACEKmGIg= golang.org/x/term v0.33.0/go.mod h1:s18+ql9tYWp1IfpV9DmCtQDDSRBUjKaw9M1eAv5UeF0= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= diff --git a/internal/commands/root.go b/internal/commands/root.go index 1613c5fbd..143eee549 100644 --- a/internal/commands/root.go +++ b/internal/commands/root.go @@ -92,6 +92,9 @@ func NewAstCLI( rootCmd.PersistentFlags().Bool(params.IgnoreProxyFlag, false, params.IgnoreProxyFlagUsage) rootCmd.PersistentFlags().String(params.ProxyTypeFlag, "", params.ProxyTypeFlagUsage) rootCmd.PersistentFlags().String(params.NtlmProxyDomainFlag, "", params.NtlmProxyDomainFlagUsage) + rootCmd.PersistentFlags().String(params.KerberosProxySPNFlag, "", params.KerberosProxySPNFlagUsage) + rootCmd.PersistentFlags().String(params.KerberosKrb5ConfFlag, "", params.KerberosKrb5ConfFlagUsage) + rootCmd.PersistentFlags().String(params.KerberosCcacheFlag, "", params.KerberosCcacheFlagUsage) rootCmd.PersistentFlags().String(params.TimeoutFlag, "", params.TimeoutFlagUsage) rootCmd.PersistentFlags().String(params.BaseURIFlag, params.BaseURI, params.BaseURIFlagUsage) rootCmd.PersistentFlags().String(params.BaseAuthURIFlag, params.BaseIAMURI, params.BaseAuthURIFlagUsage) @@ -135,6 +138,9 @@ func NewAstCLI( _ = viper.BindPFlag(params.ProxyKey, rootCmd.PersistentFlags().Lookup(params.ProxyFlag)) _ = viper.BindPFlag(params.ProxyTypeKey, rootCmd.PersistentFlags().Lookup(params.ProxyTypeFlag)) _ = viper.BindPFlag(params.ProxyDomainKey, rootCmd.PersistentFlags().Lookup(params.NtlmProxyDomainFlag)) + _ = viper.BindPFlag(params.ProxyKerberosSPNKey, rootCmd.PersistentFlags().Lookup(params.KerberosProxySPNFlag)) + _ = viper.BindPFlag(params.ProxyKerberosKrb5ConfKey, rootCmd.PersistentFlags().Lookup(params.KerberosKrb5ConfFlag)) + _ = viper.BindPFlag(params.ProxyKerberosCcacheKey, rootCmd.PersistentFlags().Lookup(params.KerberosCcacheFlag)) _ = viper.BindPFlag(params.ClientTimeoutKey, rootCmd.PersistentFlags().Lookup(params.TimeoutFlag)) _ = viper.BindPFlag(params.BaseAuthURIKey, rootCmd.PersistentFlags().Lookup(params.BaseAuthURIFlag)) _ = viper.BindPFlag(params.AstAPIKey, rootCmd.PersistentFlags().Lookup(params.AstAPIKeyFlag)) diff --git a/internal/params/binds.go b/internal/params/binds.go index e8d4140af..414aa656b 100644 --- a/internal/params/binds.go +++ b/internal/params/binds.go @@ -8,6 +8,9 @@ var EnvVarsBinds = []struct { {BaseURIKey, BaseURIEnv, ""}, {ProxyTypeKey, ProxyTypeEnv, "basic"}, {ProxyDomainKey, ProxyDomainEnv, ""}, + {ProxyKerberosSPNKey, ProxyKerberosSPNEnv, ""}, + {ProxyKerberosKrb5ConfKey, ProxyKerberosKrb5ConfEnv, ""}, + {ProxyKerberosCcacheKey, ProxyKerberosCcacheEnv, ""}, {BaseAuthURIKey, BaseAuthURIEnv, ""}, {AstAPIKey, AstAPIKeyEnv, ""}, {IgnoreProxyKey, IgnoreProxyEnv, ""}, diff --git a/internal/params/envs.go b/internal/params/envs.go index ef3bf06ef..19dc0f3c7 100644 --- a/internal/params/envs.go +++ b/internal/params/envs.go @@ -11,6 +11,9 @@ const ( CxProxyEnv = "CX_HTTP_PROXY" ProxyTypeEnv = "CX_PROXY_AUTH_TYPE" ProxyDomainEnv = "CX_PROXY_NTLM_DOMAIN" + ProxyKerberosSPNEnv = "CX_PROXY_KERBEROS_SPN" + ProxyKerberosKrb5ConfEnv = "CX_PROXY_KERBEROS_KRB5_CONF" + ProxyKerberosCcacheEnv = "CX_PROXY_KERBEROS_CCACHE" BaseAuthURIEnv = "CX_BASE_AUTH_URI" AstAPIKeyEnv = "CX_APIKEY" AccessKeyIDEnv = "CX_CLIENT_ID" diff --git a/internal/params/flags.go b/internal/params/flags.go index 85bfb4576..8667ad85f 100644 --- a/internal/params/flags.go +++ b/internal/params/flags.go @@ -67,15 +67,21 @@ const ( IgnoreProxyFlag = "ignore-proxy" IgnoreProxyFlagUsage = "Ignore proxy configuration" ProxyTypeFlag = "proxy-auth-type" - ProxyTypeFlagUsage = "Proxy authentication type, (basic or ntlm)" + ProxyTypeFlagUsage = "Proxy authentication type (supported types: basic, ntlm or Kerberos)" TimeoutFlag = "timeout" TimeoutFlagUsage = "Timeout for network activity, (default 5 seconds)" NtlmProxyDomainFlag = "proxy-ntlm-domain" + KerberosProxySPNFlag = "proxy-kerberos-spn" + KerberosKrb5ConfFlag = "proxy-kerberos-krb5-conf" + KerberosCcacheFlag = "proxy-kerberos-ccache" SastFastScanFlag = "sast-fast-scan" SastLightQueriesFlag = "sast-light-queries" BranchPrimaryFlag = "branch-primary" SastRecommendedExclusionsFlags = "sast-recommended-exclusions" NtlmProxyDomainFlagUsage = "Window domain when using NTLM proxy" + KerberosProxySPNFlagUsage = "Service Principal Name (SPN) for Kerberos proxy authentication" + KerberosKrb5ConfFlagUsage = "Path to Kerberos configuration file(default: /etc/krb5.conf on linux and C:\\Windows\\krb5.ini on windows)" + KerberosCcacheFlagUsage = "Path to Kerberos credential cache (optional, default uses KRB5CCNAME env or OS default)" BaseURIFlagUsage = "The base system URI" BaseAuthURIFlag = "base-auth-uri" BaseAuthURIFlagUsage = "The base system IAM URI" diff --git a/internal/params/keys.go b/internal/params/keys.go index 90bb6a09b..839b13e53 100644 --- a/internal/params/keys.go +++ b/internal/params/keys.go @@ -10,6 +10,9 @@ var ( ProxyKey = strings.ToLower(ProxyEnv) ProxyTypeKey = strings.ToLower(ProxyTypeEnv) ProxyDomainKey = strings.ToLower(ProxyDomainEnv) + ProxyKerberosSPNKey = strings.ToLower(ProxyKerberosSPNEnv) + ProxyKerberosKrb5ConfKey = strings.ToLower(ProxyKerberosKrb5ConfEnv) + ProxyKerberosCcacheKey = strings.ToLower(ProxyKerberosCcacheEnv) BaseAuthURIKey = strings.ToLower(BaseAuthURIEnv) ClientTimeoutKey = strings.ToLower(ClientTimeoutEnv) AstAPIKey = strings.ToLower(AstAPIKeyEnv) diff --git a/internal/wrappers/client.go b/internal/wrappers/client.go index ffd38322d..e239aa97e 100644 --- a/internal/wrappers/client.go +++ b/internal/wrappers/client.go @@ -11,6 +11,7 @@ import ( "net/http" "net/http/httptrace" "net/url" + "os" "strings" "sync" "time" @@ -23,6 +24,7 @@ import ( "github.com/spf13/viper" commonParams "github.com/checkmarx/ast-cli/internal/params" + "github.com/checkmarx/ast-cli/internal/wrappers/kerberos" "github.com/checkmarx/ast-cli/internal/wrappers/ntlm" ) @@ -30,6 +32,7 @@ const ( expiryGraceSeconds = 10 NoTimeout = 0 ntlmProxyToken = "ntlm" + kerberosProxyToken = "kerberos" checkmarxURLError = "Could not reach provided Checkmarx server" invalidCredentialsError = "Provided credentials are invalid" APIKeyDecodeErrorFormat = "Token decoding error: %s" @@ -43,6 +46,7 @@ const ( contentTypeHeader = "Content-Type" formURLContentType = "application/x-www-form-urlencoded" jsonContentType = "application/json" + defaultDialerDuration = 30 * time.Second ) var ( @@ -139,6 +143,8 @@ func GetClient(timeout uint) *http.Client { client = basicProxyClient(timeout, "") } else if proxyTypeStr == ntlmProxyToken { client = ntmlProxyClient(timeout, proxyStr) + } else if proxyTypeStr == kerberosProxyToken { + client = kerberosProxyClient(timeout, proxyStr) } else { client = basicProxyClient(timeout, proxyStr) } @@ -182,8 +188,8 @@ func basicProxyClient(timeout uint, proxyStr string) *http.Client { func ntmlProxyClient(timeout uint, proxyStr string) *http.Client { dialer := &net.Dialer{ - Timeout: 30 * time.Second, - KeepAlive: 30 * time.Second, + Timeout: defaultDialerDuration, + KeepAlive: defaultDialerDuration, } u, _ := url.Parse(proxyStr) domainStr := viper.GetString(commonParams.ProxyDomainKey) @@ -200,6 +206,61 @@ func ntmlProxyClient(timeout uint, proxyStr string) *http.Client { } } +func kerberosProxyClient(timeout uint, proxyStr string) *http.Client { + dialer := &net.Dialer{ + Timeout: defaultDialerDuration, + KeepAlive: defaultDialerDuration, + } + u, _ := url.Parse(proxyStr) + + // Get Kerberos configuration from viper + proxySPN := viper.GetString(commonParams.ProxyKerberosSPNKey) + + // Validate required SPN parameter + if proxySPN == "" { + logger.PrintIfVerbose("Error: Kerberos SPN is required for Kerberos proxy authentication.") + logger.Print("Error: Kerberos SPN is required for Kerberos proxy authentication.") + logger.PrintIfVerbose("Please provide SPN using: --proxy-kerberos-spn 'HTTP/proxy.example.com' or set CX_PROXY_KERBEROS_SPN environment variable") + logger.PrintIfVerbose("Falling back to basic proxy authentication") + // Return a basic client that will fail gracefully + return basicProxyClient(timeout, proxyStr) + } + + krb5ConfPath := viper.GetString(commonParams.ProxyKerberosKrb5ConfKey) + if krb5ConfPath == "" { + krb5ConfPath = kerberos.GetDefaultKrb5ConfPath() + } + + ccachePath := viper.GetString(commonParams.ProxyKerberosCcacheKey) + + // Early validation: Check Kerberos setup before creating client + // This ensures errors are caught immediately during client creation, not during HTTP requests + if err := kerberos.ValidateKerberosSetup(krb5ConfPath, ccachePath, proxySPN); err != nil { + logger.PrintIfVerbose("Error: Kerberos proxy authentication setup failed: " + err.Error()) + logger.Printf("Error: Kerberos proxy authentication setup failed: %v", err.Error()) + os.Exit(0) + } + + logger.PrintIfVerbose("Creating HTTP client using Kerberos Proxy using: " + proxyStr) + logger.PrintIfVerbose("Kerberos SPN: " + proxySPN) + logger.PrintIfVerbose("Kerberos krb5 configuration file: " + krb5ConfPath) + + kerberosConfig := kerberos.KerberosConfig{ + ProxySPN: proxySPN, + Krb5ConfPath: krb5ConfPath, + CcachePath: ccachePath, + } + + kerberosDialContext := kerberos.NewKerberosProxyDialContext(dialer, u, kerberosConfig, nil) + return &http.Client{ + Transport: &http.Transport{ + Proxy: nil, + DialContext: kerberosDialContext, + }, + Timeout: time.Duration(timeout) * time.Second, + } +} + func getURLAndAccessToken(path string) (urlFromPath, accessToken string, err error) { accessToken, err = GetAccessToken() if err != nil { @@ -540,7 +601,7 @@ func getNewToken(credentialsPayload, authServerURI string) (string, error) { if req.Body != nil { body, err = io.ReadAll(req.Body) if err != nil { - fmt.Errorf("failed to read request body: %w", err) + return "", fmt.Errorf("failed to read request body: %w", err) } if req.Body != nil { req.Body.Close() @@ -665,6 +726,11 @@ func request(client *http.Client, req *http.Request, responseBody bool) (*http.R Domains = AppendIfNotExists(Domains, req.URL.Host) if err != nil { logger.PrintIfVerbose(err.Error()) + // Check if this is a non-retryable error (e.g., wrong Kerberos SPN) + if kerberos.IsNonRetryable(err) { + logger.PrintIfVerbose("Non-retryable error detected, skipping retries") + return nil, err + } } if resp != nil && err == nil { if hasRedirectStatusCode(resp) { diff --git a/internal/wrappers/kerberos/proxy-kerberos.go b/internal/wrappers/kerberos/proxy-kerberos.go new file mode 100644 index 000000000..649e50ece --- /dev/null +++ b/internal/wrappers/kerberos/proxy-kerberos.go @@ -0,0 +1,250 @@ +package kerberos + +import ( + "bufio" + "context" + "crypto/tls" + "fmt" + "log" + "net" + "net/http" + "net/url" + "os" + "path/filepath" + "runtime" + + "github.com/jcmturner/gokrb5/v8/client" //nolint + "github.com/jcmturner/gokrb5/v8/config" //nolint + "github.com/jcmturner/gokrb5/v8/credentials" //nolint + "github.com/jcmturner/gokrb5/v8/spnego" //nolint + "github.com/pkg/errors" +) + +const osWindows = "windows" + +// NonRetryableError represents an error that should not trigger HTTP request retries +type NonRetryableError struct { + Message string +} + +func (e *NonRetryableError) Error() string { + return e.Message +} + +// IsNonRetryable returns true if the error should not trigger retries +func IsNonRetryable(err error) bool { + _, ok := err.(*NonRetryableError) + return ok +} + +// DialContext is the DialContext function that should be wrapped with a +// Kerberos Authentication. +type DialContext func(ctx context.Context, network, addr string) (net.Conn, error) + +// KerberosConfig holds the configuration for Kerberos authentication +type KerberosConfig struct { + ProxySPN string // SPN configured in proxy keytab/KDC (must match) + Krb5ConfPath string // path to krb5.conf file + CcachePath string // path to credential cache (optional, will use KRB5CCNAME or default) +} + +// NewKerberosProxyDialContext creates a new DialContext that uses Kerberos authentication +// for proxy connections. Unlike NTLM, it describes the proxy location with a full URL, +// whose scheme can be HTTP or HTTPS. +func NewKerberosProxyDialContext(dialer *net.Dialer, proxyURL *url.URL, + kerberosConfig KerberosConfig, tlsConfig *tls.Config) DialContext { + if dialer == nil { + dialer = &net.Dialer{} + } + return func(ctx context.Context, network, addr string) (net.Conn, error) { + dialProxy := func() (net.Conn, error) { + if proxyURL.Scheme == "https" { + return tls.DialWithDialer(dialer, "tcp", proxyURL.Host, tlsConfig) + } + return dialer.DialContext(ctx, network, proxyURL.Host) + } + return dialAndNegotiate(addr, kerberosConfig, dialProxy) + } +} + +func dialAndNegotiate(addr string, kerberosConfig KerberosConfig, baseDial func() (net.Conn, error)) (net.Conn, error) { + conn, err := baseDial() + if err != nil { + log.Printf("Could not call dial context with proxy: %s", err) + return conn, err + } + + // Use default krb5.conf path if not specified + krb5ConfPath := kerberosConfig.Krb5ConfPath + + // Load krb5.conf + krb5cfg, err := config.Load(krb5ConfPath) + if err != nil { + log.Printf("Failed to load krb5 configuration file from %s: %s", krb5ConfPath, err) + return conn, errors.New("failed to load Kerberos configuration. Please check the krb5 configuration file") + } + + // Load credential cache + ccPath := kerberosConfig.CcachePath + + cc, err := credentials.LoadCCache(ccPath) + if err != nil { + log.Printf("Failed to load Kerberos credential cache from %s: %s", ccPath, err) + return conn, errors.New("failed to load Kerberos credential cache. Please run 'kinit' to obtain valid Kerberos tickets") + } + + // Create Kerberos client from cache + krbClient, err := client.NewFromCCache(cc, krb5cfg) + if err != nil { + log.Printf("Failed to create Kerberos client: %s", err) + return conn, errors.New("failed to create Kerberos client. Please check your Kerberos tickets with 'klist'") + } + + // Kerberos Step 1: Send CONNECT with SPNEGO token directly (like NTLM does) + header := make(http.Header) //nolint:gosec + header.Set("Proxy-Connection", "Keep-Alive") + connect := &http.Request{ + Method: "CONNECT", + URL: &url.URL{Opaque: addr}, + Host: addr, + Header: header, + } + + // Build SPNEGO token for the proxy SPN + if err := spnego.SetSPNEGOHeader(krbClient, connect, kerberosConfig.ProxySPN); err != nil { + log.Printf("Failed to generate SPNEGO token for SPN '%s': %s", kerberosConfig.ProxySPN, err) + return conn, &NonRetryableError{Message: "failed to generate SPNEGO token. Please check if the SPN is correct"} + } + + // spnego.SetSPNEGOHeader sets Authorization: Negotiate + authVal := connect.Header.Get("Authorization") + if authVal == "" { + log.Printf("SPNEGO did not generate an Authorization header") + return conn, errors.New("failed to generate SPNEGO token. Please check Kerberos configuration") + } + + // Move Authorization -> Proxy-Authorization (proxy level) + connect.Header.Set("Proxy-Authorization", authVal) + connect.Header.Del("Authorization") // don't forward to origin + + log.Printf("Sending CONNECT with Kerberos SPNEGO token") + err = connect.Write(conn) + if err != nil { + log.Printf("Could not write Kerberos auth request to proxy: %s", err) + return conn, err + } + + // Kerberos Step 2: Read response + br := bufio.NewReader(conn) + resp, err := http.ReadResponse(br, connect) + if err != nil { + log.Printf("Could not read response from proxy: %s", err) + return conn, err + } + + if resp.StatusCode != http.StatusOK { + log.Printf("Expected %d as return status, got: %d", http.StatusOK, resp.StatusCode) + if resp.StatusCode == http.StatusProxyAuthRequired { + log.Printf("Proxy returned 407 after sending Negotiate token. Check SPN and proxy keytab/KDC.") + // Print Proxy-Authenticate for diagnostics + for _, v := range resp.Header.Values("Proxy-Authenticate") { + log.Printf("Proxy-Authenticate: %s", v) + } + _ = resp.Body.Close() + return conn, &NonRetryableError{Message: "proxy authentication failed. Check SPN and proxy keytab/KDC configuration"} + } + _ = resp.Body.Close() + return conn, errors.New(http.StatusText(resp.StatusCode)) + } + + // Successfully authorized with Kerberos + _ = resp.Body.Close() + log.Printf("Successfully authenticated with proxy using Kerberos") + return conn, nil +} + +// ValidateKerberosSetup validates Kerberos configuration early to fail fast +// This function performs all the same checks as dialAndNegotiate but without actually +// making network connections, allowing early detection of configuration problems +func ValidateKerberosSetup(krb5ConfPath, ccachePath, proxySPN string) error { + if krb5ConfPath == "" { + krb5ConfPath = GetDefaultKrb5ConfPath() // Use default krb5.conf path if not specified + } + + // Check if krb5.conf exists + if _, err := os.Stat(krb5ConfPath); os.IsNotExist(err) { + return errors.New("Kerberos proxy authentication setup failed because no valid Kerberos config file was found. Please ensure that a properly configured krb5.conf/krb5.ini file is available at the specified location.") + } + + // Load krb5.conf to validate it's readable + krb5cfg, err := config.Load(krb5ConfPath) + if err != nil { + return errors.New("Kerberos proxy authentication setup failed because no valid Kerberos config file was found. Please ensure that a properly configured krb5.conf/krb5.ini file is available at the specified location.") + } + + // Get default credential cache path if not specified + if ccachePath == "" { + ccachePath = getDefaultCCachePath() + } + + // Check if credential cache exists + if ccachePath != "" { + if _, err := os.Stat(ccachePath); os.IsNotExist(err) { + return errors.New("Kerberos proxy authentication setup failed because no Kerberos credential cache was found. Make sure to run 'kinit' to populate the cache before running this command.") + } + } + + // Try to load credential cache to validate it's usable + cc, err := credentials.LoadCCache(ccachePath) + if err != nil { + return errors.New("Kerberos proxy authentication setup failed because no Kerberos credential cache was found. Make sure to run 'kinit' to populate the cache before running this command.") + } + + _, err = client.NewFromCCache(cc, krb5cfg) + if err != nil { + return errors.New("Failed to create Kerberos client. Please check your Kerberos tickets with 'klist'") + } + + return nil +} + +// GetDefaultKrb5ConfPath returns the default krb5.conf path for the current platform +func GetDefaultKrb5ConfPath() string { + switch runtime.GOOS { + case osWindows: + // Windows typically uses krb5.ini + if windir := os.Getenv("WINDIR"); windir != "" { + return filepath.Join(windir, "krb5.ini") + } + // Fallback locations + locations := []string{ + "C:\\ProgramData\\MIT\\Kerberos5\\krb5.ini", + "C:\\Windows\\krb5.ini", + } + for _, loc := range locations { + if _, err := os.Stat(loc); err == nil { + return loc + } + } + return "C:\\Windows\\krb5.ini" // Default fallback + default: + // Linux, macOS, and other Unix-like systems + return "/etc/krb5.conf" + } +} + +// getDefaultCCachePath returns the default credential cache path for the current platform +func getDefaultCCachePath() string { + // Check KRB5CCNAME environment variable first + if ccname := os.Getenv("KRB5CCNAME"); ccname != "" { + return ccname + } + + switch runtime.GOOS { + case osWindows: + return "" + default: + // Linux, macOS, and other Unix-like systems + return fmt.Sprintf("/tmp/krb5cc_%d", os.Getuid()) + } +}