Skip to content

Commit 6c095d8

Browse files
committed
chore(ai): add copilot instructions
Signed-off-by: Jeremy JACQUE <[email protected]>
1 parent 5d7483d commit 6c095d8

File tree

1 file changed

+341
-0
lines changed

1 file changed

+341
-0
lines changed

.github/copilot-instructions.md

Lines changed: 341 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,341 @@
1+
# OpenVPN Okta Authentication Plugin - Copilot Instructions
2+
3+
## Project Overview
4+
5+
This is a **hybrid C/Go security plugin** for OpenVPN Community Edition that authenticates users against Okta's Authentication API with MFA support (TOTP and PUSH). The project builds both a C shared object plugin and a standalone Go binary, targeting Linux systems with strict security requirements.
6+
7+
**Critical Distinction**: This supports OpenVPN Community Edition only, NOT OpenVPN Access Server (OpenVPN-AS).
8+
9+
**Project Statistics**:
10+
- Language: Go 1.25.4 + C
11+
- Files: 19 Go source files (including tests)
12+
- Test Coverage: 97.3%
13+
- License: MPL 2.0
14+
- Module: `gopkg.in/algolia/openvpn-auth-okta.v2`
15+
16+
## Architecture
17+
18+
### Dual-Mode Operation
19+
The plugin operates in two distinct modes with different authentication flows:
20+
21+
1. **Deferred Plugin Mode** (C shared object): OpenVPN loads `openvpn-plugin-auth-okta.so`, which dynamically links to `libokta-auth-validator.so` (Go c-shared library). C plugin extracts environment variables and passes to Go via CGO bridge in `lib/libokta-auth-validator.go`. Results are written to OpenVPN control files.
22+
23+
2. **Script Plugin Mode** (standalone binary): OpenVPN executes `okta-auth-validator` binary directly, passing credentials via environment or temporary files. Results are returned via exit codes.
24+
25+
### Component Boundaries
26+
- **C Layer** (`openvpn-plugin-auth-okta.c`): OpenVPN plugin interface, environment variable extraction, shared library loading via `dlopen/dlsym`
27+
- **CGO Bridge** (`lib/libokta-auth-validator.go`): Exports Go functions to C via `//export` directives, handles struct marshalling between C and Go using embedded C code in comments
28+
- **Validator Core** (`pkg/validator/`):
29+
- `validator.go`: Main authentication orchestration (`OktaOpenVPNValidator` struct)
30+
- `config.go`: Configuration file parsing (INI format via `gopkg.in/ini.v1`)
31+
- `loading.go`: Credential extraction (via-file and environment methods)
32+
- `utils.go`: Password parsing, permission checks, logging setup
33+
- **API Client** (`pkg/oktaApiAuth/`):
34+
- `api.go`: HTTP client, TLS pinning, Okta API requests
35+
- `oktaApiAuth.go`: MFA verification logic (`Auth()`, `verifyFactors()`, `validateUserMFA()`)
36+
- `types.go`: Configuration structs (`OktaAPIConfig`, `OktaUserConfig`, `OktaApiAuth`)
37+
- `api_types.go`: API response structs (all from Okta documentation)
38+
- `utils.go`: Group validation, factor parsing, pre-checks
39+
- **Binary Entry** (`cmd/okta-auth-validator/main.go`): CLI with comprehensive help text, flag parsing (`-d`, `-dd`, `-deferred`), exit codes
40+
41+
### Security-Critical Patterns
42+
43+
**TLS Certificate Pinning**: The `InitPool()` function in `pkg/oktaApiAuth/api.go` performs mandatory public key pinning. It connects to Okta, extracts peer certificates, computes SHA256 digest of public keys, and validates against `pinset.cfg`. Never bypass or mock this in production code—tests use `gock` to intercept HTTP before TLS.
44+
45+
**Credential Flow**: In deferred mode, credentials never touch disk as environment variables. In script mode via-file, credentials are written to `tmp-dir` which MUST be on tmpfs. See `pkg/validator/loading.go` for credential extraction patterns.
46+
47+
**Control File Permissions**: `pkg/validator/utils.go` contains `checkControlFilePerm()` which validates that control file directories are not group/world writable (security requirement for OpenVPN deferred plugins).
48+
49+
## Build System
50+
51+
The Makefile builds three distinct artifacts with different compilation strategies:
52+
53+
```bash
54+
make binary # Go binary with -buildmode=pie, static linking, stripped
55+
make plugin # Both .so files (C plugin + Go c-shared library)
56+
make # Builds both binary and plugin
57+
```
58+
59+
**Platform-Specific Flags**: CGO is enabled (CGO=1) only on Raspbian for armv7l, disabled elsewhere for static linking. Linux uses `-Wl,-soname` for shared library naming, macOS uses `-Wl,-install_name`.
60+
61+
**Security Compiler Flags**: All builds use `-D_FORTIFY_SOURCE=2 -fstack-protector-strong -Wformat-security` (see `SEC_CFLAGGS` in Makefile).
62+
63+
## Testing Conventions
64+
65+
### Test Structure
66+
Tests use table-driven patterns with `gock` for HTTP mocking. See `pkg/oktaApiAuth/oktaApiAuth_test.go` for the canonical pattern:
67+
- Define test struct (e.g., `authTest`) with test cases including expected requests/responses
68+
- Use helper functions (e.g., `commonAuthTest()`) to iterate test cases
69+
- Mock HTTP with `gock.New(oktaEndpoint)` chaining request/response definitions
70+
- Verify with `assert.False(t, gock.HasUnmatchedRequest())`
71+
72+
**Example Pattern**:
73+
```go
74+
type authTest struct {
75+
testName string
76+
username string
77+
password string
78+
response string
79+
expectedError error
80+
}
81+
82+
tests := []authTest{
83+
{testName: "Valid TOTP", username: "[email protected]", password: "pass123456", response: readFixture("auth_success.json"), expectedError: nil},
84+
{testName: "Invalid TOTP", username: "[email protected]", password: "passwrong", response: readFixture("auth_invalid_totp.json"), expectedError: errTOTPFailed},
85+
}
86+
87+
for _, test := range tests {
88+
t.Run(test.testName, func(t *testing.T) {
89+
defer gock.Off()
90+
gock.New(oktaEndpoint).Post("/api/v1/authn").Reply(200).BodyString(test.response)
91+
// test logic
92+
assert.False(t, gock.HasUnmatchedRequest())
93+
})
94+
}
95+
```
96+
97+
### Running Tests
98+
```bash
99+
make test # Run tests with coverage, generates build/cover.out
100+
make coverage # Generate build/coverage.html
101+
make badge # Update README coverage badge (requires gobadge)
102+
```
103+
104+
**Fixture Hygiene**: Tests validate file permissions before running (`chmod -R g-w,o-w testing/fixtures` in Makefile). All JSON fixtures are extracted from actual Okta API documentation.
105+
106+
**Test Organization**:
107+
- `pkg/oktaApiAuth/*_test.go`: API client tests with HTTP mocking
108+
- `pkg/validator/*_test.go`: Validator logic tests (config, loading, utils)
109+
- `testing/fixtures/oktaApi/*.json`: Okta API responses (preauth, auth, groups)
110+
- `testing/fixtures/validator/*.cfg`: Configuration file test cases (valid/invalid formats)
111+
112+
**Coverage Requirements**: Current coverage is 97.3%. Maintain this level when adding new code.
113+
114+
## Configuration Files
115+
116+
- **`config/api.ini.inc`**: Template for production config with all available options documented. Copied to `/etc/okta-auth-validator/api.ini` on install if missing (mode 640 due to API token sensitivity).
117+
- **`config/pinset.cfg`**: TLS certificate pinning configuration. Contains base64-encoded SHA256 public key digests. Updated when Okta rotates certificates.
118+
119+
Configuration is parsed via `gopkg.in/ini.v1` with struct tag validation (see `pkg/validator/config.go`).
120+
121+
## Code Conventions
122+
123+
### File Headers
124+
All Go files MUST include SPDX headers:
125+
```go
126+
// SPDX-FileCopyrightText: 2023-Present Algolia
127+
//
128+
// SPDX-License-Identifier: MPL-2.0
129+
```
130+
131+
### Documentation Standards
132+
**All public functions and types have comprehensive godoc comments** following this format:
133+
- Purpose statement (what it does)
134+
- Detailed behavior explanation (how it works)
135+
- Parameters description (with types and examples)
136+
- Return values explanation (including error cases)
137+
- Security implications (where relevant)
138+
- Usage examples (for complex functions)
139+
- API endpoint references (for Okta API calls)
140+
141+
See `pkg/validator/validator.go` and `pkg/oktaApiAuth/api.go` for canonical examples.
142+
143+
### Logging
144+
Uses `github.com/phuslu/log` with structured logging and UUIDs per authentication session:
145+
```go
146+
log.Info().Msgf("authenticated with %s %s MFA", factor.Provider, factorType)
147+
```
148+
Available levels: TRACE, DEBUG, INFO, WARN, ERROR (set via `api.ini` or validator constructor).
149+
150+
**Session Correlation**: Each authentication attempt gets a UUID that appears in all log messages:
151+
```
152+
Mon Jan 2 15:04:05 2006 [okta-auth-validator:uuid-123](INFO): [[email protected]] authenticated with Okta TOTP MFA
153+
```
154+
155+
### Error Handling
156+
Custom error types in `pkg/oktaApiAuth/api_types.go` for specific Okta failure modes:
157+
- `errPushFailed`, `errTOTPFailed`, `errMFAUnavailable`
158+
- `errMFARequired`, `errUserLocked`, `errPasswordExpired`, `errEnrollNeeded`
159+
160+
Use `parseOktaError()` for smart multi-factor error handling (suppresses non-final factor failures).
161+
162+
### Package Organization
163+
- Public types use alias declarations: `type OktaApiAuth = oktaApiAuth.OktaApiAuth`
164+
- Exported CGO functions use `//export FunctionName` comment directives
165+
- Test helpers in `*_test.go` files (white-box testing)
166+
- All structs have validation tags: `json:"fieldName" validate:"required"`
167+
168+
## Common Development Tasks
169+
170+
### Adding New Configuration Options
171+
1. Update `OktaAPIConfig` struct in `pkg/oktaApiAuth/api_types.go` with struct tags (`json:"fieldName" validate:"required"`)
172+
2. Add to `config/api.ini.inc` with comment documentation
173+
3. Add validation in `pkg/validator/config.go` `readConfigFile()`
174+
4. Add test case in `pkg/validator/config_test.go`
175+
176+
### Adding New MFA Factor Types
177+
1. Define factor verification in `pkg/oktaApiAuth/oktaApiAuth.go` (follow `verifyFactors()` pattern)
178+
2. Add API endpoint handlers in `pkg/oktaApiAuth/api.go`
179+
3. Add test fixtures to `testing/fixtures/oktaApi/` (use actual Okta API responses)
180+
4. Update `doAuthFirstStep()` and `waitForPush()` logic if needed
181+
5. Add error types to `api_types.go` for factor-specific failures
182+
183+
### Writing Comprehensive Godoc Comments
184+
All public functions/types require detailed godoc following these patterns (see enhanced documentation from 2024):
185+
186+
**For Complex Functions** (validator methods, auth flows):
187+
```go
188+
// Authenticate performs the complete authentication flow for an OpenVPN user against Okta.
189+
//
190+
// This method orchestrates the entire authentication process:
191+
// 1. Extracts username/password from OpenVPN environment variables or credential files
192+
// 2. Validates password format (supports appended TOTP codes)
193+
// 3. Calls Okta Authentication API with MFA support
194+
// 4. Writes authentication result to OpenVPN control file (deferred mode only)
195+
//
196+
// Parameters:
197+
// - uuid: Unique session identifier for log correlation
198+
// - pluginEnv: OpenVPN plugin environment containing credentials and control file paths
199+
//
200+
// Returns:
201+
// - bool: true if authentication succeeded, false otherwise
202+
// - error: nil on success, error details on failure (credential errors, API errors, I/O errors)
203+
//
204+
// Security:
205+
// - Control file directory permissions are validated before writing
206+
// - Credentials are never logged or persisted beyond this function scope
207+
// - Session UUID enables audit trail correlation without exposing sensitive data
208+
func (validator *OktaOpenVPNValidator) Authenticate(uuid string, pluginEnv *PluginEnv) (bool, error) {
209+
```
210+
211+
**For API Functions**:
212+
```go
213+
// InitPool initializes the TLS connection pool with certificate pinning validation.
214+
//
215+
// This function performs mandatory public key pinning to prevent MITM attacks:
216+
// 1. Connects to Okta API endpoint via TLS
217+
// 2. Extracts peer certificates from the connection
218+
// 3. Computes SHA256 digest of each certificate's public key
219+
// 4. Validates at least one digest matches the configured pinset
220+
//
221+
// Security:
222+
// - NEVER bypass this validation in production environments
223+
// - Pin updates require manual pinset.cfg modifications
224+
// - Connection fails if no certificates match the pinset
225+
//
226+
// Okta API Endpoint: https://{oktaEndpoint} (from config)
227+
//
228+
// Returns:
229+
// - error: nil if pinning validation succeeds, error if connection fails or no pins match
230+
func (okta *OktaApiAuth) InitPool() error {
231+
```
232+
233+
**For Configuration Structs**:
234+
```go
235+
// OktaAPIConfig holds all configuration parameters for Okta API authentication.
236+
//
237+
// This structure is populated from api.ini configuration file and controls:
238+
// - Okta API endpoint and credentials
239+
// - TLS certificate pinning configuration
240+
// - MFA behavior (allowed groups, factor selection)
241+
// - Logging verbosity and session tracking
242+
//
243+
// Security-Sensitive Fields:
244+
// - Token: API token with authentication privileges (never log this value)
245+
// - PinsetFile: Path to certificate pinning configuration (validates TLS connections)
246+
//
247+
// Required Fields (validated by go-playground/validator):
248+
// - OktaEndpoint, Token, PinsetFile
249+
//
250+
// Optional Fields:
251+
// - Groups (empty = all users allowed)
252+
// - LogLevel (defaults to INFO)
253+
// - UsernameFormat (defaults to email)
254+
type OktaAPIConfig struct {
255+
OktaEndpoint string `json:"oktaEndpoint" validate:"required,url"`
256+
Token string `json:"token" validate:"required"`
257+
// ... remaining 9 fields with inline comments
258+
}
259+
```
260+
261+
### Debugging Authentication Flows
262+
Set `LogLevel: TRACE` in `api.ini` to see full request/response bodies. Session IDs (UUID) correlate log lines for single auth attempts. Test with binary directly:
263+
```bash
264+
# Script mode simulation
265+
export username="[email protected]"
266+
export password="pass123456" # password+TOTP
267+
./build/okta-auth-validator -dd # double -d for TRACE level
268+
```
269+
270+
### Running Test Suites
271+
```bash
272+
make test # Run tests with coverage, generates build/cover.out
273+
make coverage # Generate build/coverage.html
274+
make badge # Update README coverage badge (requires gobadge)
275+
```
276+
277+
**Adding New Tests**: Follow table-driven pattern with `gock` for HTTP mocking:
278+
```go
279+
func TestNewFeature(t *testing.T) {
280+
defer gock.Off()
281+
282+
tests := []struct {
283+
name string
284+
expectedError error
285+
mockRequest func()
286+
mockResponse string
287+
}{
288+
// test cases
289+
}
290+
291+
for _, tt := range tests {
292+
t.Run(tt.name, func(t *testing.T) {
293+
gock.New(oktaEndpoint).Post("/api/v1/authn").MatchType("json").Reply(200).BodyString(tt.mockResponse)
294+
// test logic
295+
assert.False(t, gock.HasUnmatchedRequest())
296+
})
297+
}
298+
}
299+
```
300+
301+
## Installation Paths
302+
Default install locations (override with `DESTDIR=/custom/path`):
303+
- Binary: `/usr/bin/okta-auth-validator`
304+
- C Plugin: `/usr/lib/openvpn/plugins/openvpn-plugin-auth-okta.so`
305+
- Go Library: `/usr/lib/libokta-auth-validator.so`
306+
- Config: `/etc/okta-auth-validator/api.ini`, `pinset.cfg`
307+
308+
## Linting
309+
```bash
310+
make lint # Runs golangci-lint and cppcheck with exhaustive checks
311+
```
312+
Requires `golangci-lint` and `cppcheck` installed separately.
313+
314+
## Recent Enhancements (2024-2025)
315+
316+
### Comprehensive Godoc Documentation
317+
Over 1100 lines of documentation added across all packages:
318+
- **pkg/validator/**: 5 files enhanced with detailed function documentation
319+
- `validator.go`: OktaOpenVPNValidator struct, New(), Setup(), Authenticate(), WriteControlFile()
320+
- `loading.go`: PluginEnv struct, loadViaFile(), loadEnvVars()
321+
- `utils.go`: parsePassword(), checkControlFilePerm(), all utility functions
322+
- `config.go`: readConfigFile(), loadPinset()
323+
324+
- **pkg/oktaApiAuth/**: 5 files enhanced with detailed function and type documentation
325+
- `types.go`: OktaAPIConfig (11 fields), OktaUserConfig, OktaApiAuth
326+
- `api_types.go`: ErrorResponse, PreAuthResponse, AuthFactor, AuthResponse, OktaGroups
327+
- `api.go`: InitPool(), oktaReq(), preAuth(), doAuth(), cancelAuth(), parseAuthResponse(), parseOktaError(), doAuthFirstStep(), waitForPush()
328+
- `oktaApiAuth.go`: New(), Auth(), verifyFactors(), validateUserMFA()
329+
- `utils.go`: checkAllowedGroups(), getUserFactors(), preChecks()
330+
331+
### Documentation Patterns Established
332+
All public functions now include:
333+
- Purpose statement (what it does)
334+
- Detailed behavior explanation (how it works, multi-step flows)
335+
- Parameters with types and examples
336+
- Return values with error cases
337+
- Security implications where relevant
338+
- Usage examples for complex functions
339+
- Okta API endpoint references
340+
341+
See `pkg/validator/validator.go` and `pkg/oktaApiAuth/api.go` for canonical examples of comprehensive godoc.

0 commit comments

Comments
 (0)