Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Agent license inspect command #4813

Merged
merged 9 commits into from
Jan 27, 2025

Conversation

jamlo
Copy link
Contributor

@jamlo jamlo commented Jan 16, 2025

New Command: bacalhau agent license inspect

This PR introduces a new command that allows users to inspect the license information of a Bacalhau orchestrator node without exposing the license itself. The command provides a secure way to verify license status, capabilities, and metadata while maintaining the confidentiality of the underlying license file.

Features

  • New command: bacalhau agent license inspect
  • Supports multiple output formats (default, JSON, YAML)
  • Displays key license information:
    • Product name
    • License ID
    • Customer ID
    • Expiration date
    • Version
    • Capabilities
    • Custom metadata

Add License Manager for Node Orchestration

Introduce a LicenseManager component that handles license validation for orchestrator nodes. Key features:

  • Validates node count against licensed limits
  • Simple API for license verification and capability checks

Usage example:

// Initialize license manager (config is bacalhau License config)
// IF any issues, other than expiry date validity, appears during initialization, it will fail
licenseManager, err := licensing.NewLicenseManager(config)

// Get current license claims, it will be nil if no license is configured for the orchestrator
licenseClaims := licenseManager.License()

// Then these helper functions can be called on the licenseClaims struct
licenseClaims.IsExpired() // Returns only a boolean
licenseClaims.MaxNumberOfNodes() // Returns only a number

Security Considerations

  • Does not expose the raw license file or cryptographic material
  • Only returns parsed, necessary information for verification
  • Maintains license confidentiality while providing essential details

License File Structure

The license file, fed to the orchestrator node config, uses a JSON format to support future extensibility and dynamic configuration. The structure is intentionally simple:

{
    "license": "your_license_token_here"
}

We chose JSON format for the license file because:

  • It allows for easy addition of future configuration options
  • Provides a structured way to include additional metadata if needed

Configuration

To add a license to an orchestrator, you need to configure your orchestrator node. Here's a sample configuration example:

NameProvider: "uuid"
API:
  Port: 1234
Orchestrator:
  Enabled: true
  Auth:
    Token: "your_secret_token_here"
  License:
    LocalPath: "/path/to/your/license.json"
Labels:
  label1: label1Value
  label2: label2Value

Key configuration points:

  • The Orchestrator.License.LocalPath field specifies the path to your license file
  • If no license is configured, the command will return an error message saying that no license was configured
  • If the license if expired, the inspect command will return the same license details, but will not that it is expired.

Example Usage

# Default format
$ bacalhau agent license inspect

# JSON format
$ bacalhau agent license inspect --output=json

# YAML format
$ bacalhau agent license inspect --output=yaml

Example Output

For bacalhau agent license inspect:

Product      = Bacalhau
License ID   = 2d58c7c9-ec29-45a5-a5cd-cb8f7fee6678
Customer ID  = test-customer-id-123
Valid Until  = 2045-07-28
Version      = v1
Expired      = false
Capabilities = max_nodes=1
Metadata     = someMetadata=valueOfSomeMetadata

For bacalhau agent license inspect --output=yaml:

capabilities:
  max_nodes: "1"
customer_id: test-customer-id-123
exp: 2384889682
iat: 1736889682
iss: https://expanso.io/
jti: 2d58c7c9-ec29-45a5-a5cd-cb8f7fee6678
license_id: 2d58c7c9-ec29-45a5-a5cd-cb8f7fee6678
license_type: standard
license_version: v1
metadata:
  someMetadata: valueOfSomeMetadata
product: Bacalhau
sub: test-customer-id-123

Documentation

  • Added command documentation with usage examples
  • Included field descriptions in help text

Linear: https://linear.app/expanso/issue/ENG-498/license-path-configuration

Summary by CodeRabbit

  • New Features

    • Added a new CLI command to inspect agent license information.
    • Introduced a new API endpoint to retrieve agent license details.
    • Implemented license configuration support for orchestrator nodes.
  • Configuration

    • Added a new configuration option for specifying local license file path.
    • Enhanced configuration to support orchestrator settings with license metadata.
  • API Enhancements

    • Created a new method to retrieve license information via API client.
    • Updated Swagger documentation to include license-related endpoints.
  • Testing

    • Added comprehensive integration tests for license inspection scenarios, including expired licenses.
    • Included test cases for various license configuration states and error handling.
    • Enhanced tests for validating license output formats and error messages.

Copy link

linear bot commented Jan 16, 2025

Copy link
Contributor

coderabbitai bot commented Jan 16, 2025

Walkthrough

This pull request introduces a comprehensive license management feature for the Bacalhau project, focusing on agent license inspection and configuration. The changes span multiple components, including CLI commands, API endpoints, configuration types, and integration tests. The new functionality allows users to retrieve and inspect license details for the orchestrator, with support for various output formats and error handling scenarios.

Changes

File Change Summary
cmd/cli/agent/license/inspect.go Added CLI command for agent license inspection with support for different output formats.
cmd/cli/agent/license/root.go Created root command for license-related CLI operations.
cmd/cli/agent/root.go Integrated license command into the main CLI structure.
pkg/config/types/* Added configuration constants and types for license management, including License struct and OrchestratorLicenseLocalPathKey.
pkg/publicapi/apimodels/agent.go Introduced new response type GetAgentLicenseResponse for agent license retrieval.
pkg/publicapi/client/v2/api_agent.go Added License() method to API client for fetching license information.
pkg/publicapi/endpoint/agent/endpoint.go Implemented license endpoint for retrieving license information, including validation logic.
pkg/publicapi/endpoint/agent/endpoint_test.go Added tests for various license endpoint scenarios.
pkg/licensing/license_manager.go Introduced LicenseManager struct for managing license validation.
pkg/licensing/license_manager_test.go Added unit tests for LicenseManager functionality.
test_integration/* Added comprehensive test suites for license inspection scenarios, including metadata handling and absence of license.
webui/lib/api/schema/swagger.json Updated Swagger documentation to include new license endpoint and response schema.
pkg/swagger/docs.go Updated API documentation for the new license endpoint.

Sequence Diagram

sequenceDiagram
    participant CLI as Agent License CLI
    participant Endpoint as License Endpoint
    participant Config as Configuration
    participant Validator as License Validator

    CLI->>Endpoint: GET /api/v1/agent/license
    Endpoint->>Config: Check License Path
    Config-->>Endpoint: Return License Path
    Endpoint->>Endpoint: Read License File
    Endpoint->>Validator: Validate License Token
    Validator-->>Endpoint: Validation Result
    Endpoint-->>CLI: Return License Claims
Loading

Possibly related PRs

Suggested reviewers

  • wdbaruni
  • markkuhn

Poem

🐰 A License to Thrive
In the realm of Bacalhau's might,
A new command shines ever so bright
Inspect your license with grace
JSON, YAML, in every case
Code hopping with licensing delight! 🚀

✨ Finishing Touches
  • 📝 Generate Docstrings (Beta)

🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR. (Beta)
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@jamlo jamlo requested review from wdbaruni and markkuhn January 16, 2025 18:58
@jamlo jamlo marked this pull request as ready for review January 16, 2025 20:53
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🔭 Outside diff range comments (1)
pkg/swagger/swagger.json (1)

Line range hint 1-3: Eliminate duplicate Swagger files.

The same Swagger definition exists in both:

  • webui/lib/api/schema/swagger.json
  • pkg/swagger/swagger.json

This duplication can lead to maintenance issues and inconsistencies. Consider:

  1. Keeping a single source of truth
  2. Generating the second file if needed
  3. Using symbolic links if both locations are required
🧹 Nitpick comments (15)
pkg/publicapi/apimodels/agent.go (1)

43-46: LGTM! Consider adding documentation.

The GetAgentLicenseResponse type follows the established pattern and correctly embeds the necessary types. Consider adding documentation comments to describe the purpose and fields of this type, similar to other response types in this file.

Add documentation like this:

+// GetAgentLicenseResponse is the response to the License request.
 type GetAgentLicenseResponse struct {
     BaseGetResponse
     *license.LicenseClaims
 }
cmd/cli/agent/license/root.go (2)

9-11: Consider enhancing the command description.

While the description is clear, it could be more detailed to help users understand the available subcommands and their purposes.

-    Short: "Commands to interact with the orchestrator license",
+    Short: "Commands to interact with the orchestrator license",
+    Long: `Commands to interact with the orchestrator license.
+
+Available Commands:
+  inspect     Inspect the orchestrator license information without revealing the license file`,

7-15: Consider adding PreRun and PostRun hooks.

For consistency with the parent command, consider adding the same hooks that are defined in the agent root command.

 func NewAgentLicenseRootCmd() *cobra.Command {
     cmd := &cobra.Command{
         Use:   "license",
         Short: "Commands to interact with the orchestrator license",
+        PersistentPreRunE:  hook.AfterParentPreRunHook(hook.RemoteCmdPreRunHooks),
+        PersistentPostRunE: hook.AfterParentPostRunHook(hook.RemoteCmdPostRunHooks),
     }
test_integration/15_agent_license_inspect_with_no_license_suite_test.go (1)

41-49: Enhance error handling test coverage.

Consider improving the test coverage by:

  1. Verifying the specific error type in addition to the error message.
  2. Testing different output formats (JSON, YAML) to ensure consistent error handling.
 func (s *AgentLicenseInspectWithNoLicenseSuite) TestValidateRemoteLicenseWithNoLicense() {
-	_, err := s.executeCommandInDefaultJumpbox(
+	// Test default output format
+	_, err := s.executeCommandInDefaultJumpbox(
 		[]string{
 			"bacalhau", "agent", "license", "inspect",
 		},
 	)
-
 	s.Require().ErrorContains(err, "Node license not configured")
+
+	// Test JSON output format
+	_, err = s.executeCommandInDefaultJumpbox(
+		[]string{
+			"bacalhau", "agent", "license", "inspect",
+			"--output=json",
+		},
+	)
+	s.Require().ErrorContains(err, "Node license not configured")
+
+	// Test YAML output format
+	_, err = s.executeCommandInDefaultJumpbox(
+		[]string{
+			"bacalhau", "agent", "license", "inspect",
+			"--output=yaml",
+		},
+	)
+	s.Require().ErrorContains(err, "Node license not configured")
 }
test_integration/13_agent_license_inspect_suite_test.go (2)

49-57: Consider using relative dates for test data.

The test uses a hardcoded far-future date (2045-07-28). Consider using a relative date calculation to make the test more maintainable.

+	// Calculate a date 20 years in the future for testing
+	futureDate := time.Now().AddDate(20, 0, 0).Format("2006-01-02")
 	expectedOutput := `Product      = Bacalhau
 License ID   = e66d1f3a-a8d8-4d57-8f14-00722844afe2
 Customer ID  = test-customer-id-123
-Valid Until  = 2045-07-28
+Valid Until  = ` + futureDate + `
 Version      = v1
 Capabilities = max_nodes=1
 Metadata     = {}`

72-94: Consider using structured JSON parsing for more robust tests.

Instead of using string queries which can be fragile, consider parsing the JSON response into a struct for more reliable validation.

+	type LicenseResponse struct {
+		Product       string            `json:"product"`
+		LicenseID     string            `json:"license_id"`
+		CustomerID    string            `json:"customer_id"`
+		LicenseVersion string           `json:"license_version"`
+		Capabilities  map[string]string `json:"capabilities"`
+	}
+
-	output, err := s.convertStringToDynamicJSON(agentLicenseInspectionOutput)
+	var response LicenseResponse
+	err = json.Unmarshal([]byte(agentLicenseInspectionOutput), &response)
 	s.Require().NoError(err)
 
-	productName, err := output.Query("$.product")
-	s.Require().NoError(err)
-	s.Require().Equal("Bacalhau", productName.String())
+	s.Require().Equal("Bacalhau", response.Product)
+	s.Require().Equal("e66d1f3a-a8d8-4d57-8f14-00722844afe2", response.LicenseID)
+	s.Require().Equal("test-customer-id-123", response.CustomerID)
+	s.Require().Equal("v1", response.LicenseVersion)
+	s.Require().Equal("1", response.Capabilities["max_nodes"])
cmd/cli/agent/license/inspect.go (2)

31-46: Add usage examples to command help text.

The command would benefit from usage examples in the help text to improve user experience.

 	licenseCmd := &cobra.Command{
 		Use:   "inspect",
 		Short: "Get the agent license information",
+		Long: `Get the agent license information.
+
+Examples:
+  # Get license information in default format
+  bacalhau agent license inspect
+
+  # Get license information in JSON format
+  bacalhau agent license inspect --output=json
+
+  # Get license information in YAML format
+  bacalhau agent license inspect --output=yaml`,
 		Args:  cobra.NoArgs,

73-99: Reduce code duplication in map formatting logic.

The code duplicates the map iteration logic for capabilities and metadata. Consider extracting this into a helper function.

+	// Helper function to format map as string
+	formatMapAsString := func(m map[string]string) string {
+		if len(m) == 0 {
+			return "{}"
+		}
+		var items []string
+		for k, v := range m {
+			items = append(items, fmt.Sprintf("%s=%s", k, v))
+		}
+		return strings.Join(items, ", ")
+	}
+
-	// Always show Capabilities
-	capabilitiesStr := "{}"
-	if len(response.Capabilities) > 0 {
-		var caps []string
-		for k, v := range response.Capabilities {
-			caps = append(caps, fmt.Sprintf("%s=%s", k, v))
-		}
-		capabilitiesStr = strings.Join(caps, ", ")
-	}
 	headerData = append(headerData, collections.Pair[string, any]{
 		Left:  "Capabilities",
-		Right: capabilitiesStr,
+		Right: formatMapAsString(response.Capabilities),
 	})
 
-	// Always show Metadata
-	metadataStr := "{}"
-	if len(response.Metadata) > 0 {
-		var meta []string
-		for k, v := range response.Metadata {
-			meta = append(meta, fmt.Sprintf("%s=%s", k, v))
-		}
-		metadataStr = strings.Join(meta, ", ")
-	}
 	headerData = append(headerData, collections.Pair[string, any]{
 		Left:  "Metadata",
-		Right: metadataStr,
+		Right: formatMapAsString(response.Metadata),
 	})
test_integration/14_agent_license_inspect_with_metadata_suite_test.go (1)

60-84: Use structured YAML parsing for more robust tests.

Instead of using multiple string contains assertions which can be fragile, consider parsing the YAML response into a struct for more reliable validation.

+	type LicenseResponse struct {
+		Metadata       map[string]string `yaml:"metadata"`
+		JTI            string            `yaml:"jti"`
+		Issuer         string            `yaml:"iss"`
+		IssuedAt       int64             `yaml:"iat"`
+		ExpiresAt      int64             `yaml:"exp"`
+		CustomerID     string            `yaml:"customer_id"`
+		LicenseID      string            `yaml:"license_id"`
+		LicenseType    string            `yaml:"license_type"`
+		Subject        string            `yaml:"sub"`
+		Product        string            `yaml:"product"`
+		LicenseVersion string            `yaml:"license_version"`
+		Capabilities   map[string]string `yaml:"capabilities"`
+	}
+
 	agentLicenseInspectionOutput, err := s.executeCommandInDefaultJumpbox(
 		[]string{
 			"bacalhau",
@@ -69,16 +83,21 @@
 	)
 	s.Require().NoErrorf(err, "Error inspecting license: %q", err)
 
-	s.Require().Contains(agentLicenseInspectionOutput, "someMetadata: valueOfSomeMetadata")
-	s.Require().Contains(agentLicenseInspectionOutput, "jti: 2d58c7c9-ec29-45a5-a5cd-cb8f7fee6678")
-	s.Require().Contains(agentLicenseInspectionOutput, "iss: https://expanso.io/")
-	s.Require().Contains(agentLicenseInspectionOutput, "iat: 1736889682")
-	s.Require().Contains(agentLicenseInspectionOutput, "exp: 2384889682")
-	s.Require().Contains(agentLicenseInspectionOutput, "customer_id: test-customer-id-123")
-	s.Require().Contains(agentLicenseInspectionOutput, "license_id: 2d58c7c9-ec29-45a5-a5cd-cb8f7fee6678")
-	s.Require().Contains(agentLicenseInspectionOutput, "license_type: standard")
-	s.Require().Contains(agentLicenseInspectionOutput, "sub: test-customer-id-123")
-	s.Require().Contains(agentLicenseInspectionOutput, "product: Bacalhau")
-	s.Require().Contains(agentLicenseInspectionOutput, "license_version: v1")
-	s.Require().Contains(agentLicenseInspectionOutput, "max_nodes: \"1\"")
+	var response LicenseResponse
+	err = yaml.Unmarshal([]byte(agentLicenseInspectionOutput), &response)
+	s.Require().NoError(err)
+
+	s.Require().Equal("valueOfSomeMetadata", response.Metadata["someMetadata"])
+	s.Require().Equal("2d58c7c9-ec29-45a5-a5cd-cb8f7fee6678", response.JTI)
+	s.Require().Equal("https://expanso.io/", response.Issuer)
+	s.Require().Equal(int64(1736889682), response.IssuedAt)
+	s.Require().Equal(int64(2384889682), response.ExpiresAt)
+	s.Require().Equal("test-customer-id-123", response.CustomerID)
+	s.Require().Equal("2d58c7c9-ec29-45a5-a5cd-cb8f7fee6678", response.LicenseID)
+	s.Require().Equal("standard", response.LicenseType)
+	s.Require().Equal("test-customer-id-123", response.Subject)
+	s.Require().Equal("Bacalhau", response.Product)
+	s.Require().Equal("v1", response.LicenseVersion)
+	s.Require().Equal("1", response.Capabilities["max_nodes"])
pkg/publicapi/endpoint/agent/endpoint.go (2)

164-175: Add file size limit and improve JSON handling.

Consider these improvements for robustness:

  1. Add a size limit when reading the license file to prevent memory issues
  2. Use a more strict JSON decoder with DisallowUnknownFields()
 // Read license file
+const maxLicenseSize = 1 << 20 // 1MB limit
+fi, err := os.Stat(licensePath)
+if err != nil {
+    return echo.NewHTTPError(http.StatusInternalServerError, fmt.Sprintf("failed to stat license file: %s", err))
+}
+if fi.Size() > maxLicenseSize {
+    return echo.NewHTTPError(http.StatusInternalServerError, "license file too large")
+}
 licenseData, err := os.ReadFile(licensePath)

 // Parse license JSON
 var licenseFile struct {
     License string `json:"license"`
 }
-if err := json.Unmarshal(licenseData, &licenseFile); err != nil {
+decoder := json.NewDecoder(bytes.NewReader(licenseData))
+decoder.DisallowUnknownFields()
+if err := decoder.Decode(&licenseFile); err != nil {

156-191: Improve error handling consistency.

The error handling is good but could be more consistent. Consider wrapping all errors with a common error type that includes:

  • Error code
  • User-friendly message
  • Internal error details (for logging)
+type licenseError struct {
+    Code    int
+    Message string
+    Err     error
+}
+
+func (e *licenseError) Error() string {
+    return e.Message
+}
+
 func (e *Endpoint) license(c echo.Context) error {
     // Get license path from config
     licensePath := e.bacalhauConfig.Orchestrator.License.LocalPath
     if licensePath == "" {
-        return echo.NewHTTPError(http.StatusNotFound, "Node license not configured")
+        return &licenseError{
+            Code:    http.StatusNotFound,
+            Message: "Node license not configured",
+        }
     }
pkg/publicapi/endpoint/agent/endpoint_test.go (1)

60-268: LGTM! Comprehensive test coverage with room for enhancement.

The test suite thoroughly covers the main scenarios. Consider adding these edge cases:

  1. License file with valid JSON but empty license string
  2. License file with permissions issues
  3. License file exceeding size limit (after implementing the size limit)

Would you like me to provide the additional test cases?

pkg/swagger/docs.go (1)

3178-3179: Consider documenting the reason for template delimiter changes.

While the changes to SwaggerInfo's template delimiters look correct, it would be helpful to document why these specific delimiters were chosen.

test_integration/common_assets/nodes_configs/14_orchestrator_config_with_license_with_metadata.yaml (1)

9-9: Use relative paths in test configurations.

Using absolute paths in test configurations can cause portability issues across different environments. Consider using a path relative to the repository root:

-    LocalPath: "/bacalhau_integration_tests/common_assets/licenses/test-license-with-metadata.json"
+    LocalPath: "test_integration/common_assets/licenses/test-license-with-metadata.json"
webui/lib/api/schema/swagger.json (1)

1223-1298: Enhance license claims schema validation.

Consider adding these validations to the license.LicenseClaims schema:

  1. Required fields
  2. String format validations
  3. Enum constraints for license_type

Apply these schema enhancements:

         "license.LicenseClaims": {
             "type": "object",
+            "required": [
+                "license_id",
+                "customer_id",
+                "license_type",
+                "license_version",
+                "product"
+            ],
             "properties": {
                 "license_type": {
-                    "type": "string"
+                    "type": "string",
+                    "enum": ["standard", "enterprise", "trial"]
                 },
                 "license_version": {
-                    "type": "string"
+                    "type": "string",
+                    "pattern": "^v\\d+$"
                 }
             }
         }
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between bba0b6b and 1d3b71d.

📒 Files selected for processing (20)
  • cmd/cli/agent/license/inspect.go (1 hunks)
  • cmd/cli/agent/license/root.go (1 hunks)
  • cmd/cli/agent/root.go (2 hunks)
  • pkg/config/types/generated_constants.go (1 hunks)
  • pkg/config/types/generated_descriptions.go (1 hunks)
  • pkg/config/types/orchestrator.go (2 hunks)
  • pkg/publicapi/apimodels/agent.go (2 hunks)
  • pkg/publicapi/client/v2/api_agent.go (1 hunks)
  • pkg/publicapi/endpoint/agent/endpoint.go (3 hunks)
  • pkg/publicapi/endpoint/agent/endpoint_test.go (3 hunks)
  • pkg/swagger/docs.go (6 hunks)
  • pkg/swagger/swagger.json (4 hunks)
  • test_integration/13_agent_license_inspect_suite_test.go (1 hunks)
  • test_integration/14_agent_license_inspect_with_metadata_suite_test.go (1 hunks)
  • test_integration/15_agent_license_inspect_with_no_license_suite_test.go (1 hunks)
  • test_integration/common_assets/licenses/test-license-with-metadata.json (1 hunks)
  • test_integration/common_assets/nodes_configs/13_orchestrator_config_with_license.yaml (1 hunks)
  • test_integration/common_assets/nodes_configs/14_orchestrator_config_with_license_with_metadata.yaml (1 hunks)
  • test_integration/common_assets/nodes_configs/15_orchestrator_config_with_no_license.yaml (1 hunks)
  • webui/lib/api/schema/swagger.json (4 hunks)
🧰 Additional context used
🪛 Gitleaks (8.21.2)
test_integration/common_assets/licenses/test-license-with-metadata.json

2-2: Uncovered a JSON Web Token, which may lead to unauthorized access to web applications and sensitive user data.

(jwt)

pkg/publicapi/endpoint/agent/endpoint_test.go

23-23: Uncovered a JSON Web Token, which may lead to unauthorized access to web applications and sensitive user data.

(jwt)

⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: testcontainers-suite / tests
🔇 Additional comments (15)
pkg/publicapi/client/v2/api_agent.go (1)

40-45: LGTM! Implementation follows established patterns.

The License method is well-implemented, following the same patterns as other methods in this file. The documentation and error handling are appropriate.

cmd/cli/agent/root.go (1)

4-4: LGTM! Command registration is correct.

The license command is properly integrated into the agent CLI command structure, following the established pattern.

Also applies to: 21-21

pkg/config/types/orchestrator.go (2)

22-23: LGTM! Well-structured configuration type.

The License field is properly documented and follows the established patterns for configuration types.


82-85: LGTM! Clean and extensible struct design.

The License struct is minimal yet allows for future extension. The omitempty tags ensure backward compatibility.

pkg/publicapi/endpoint/agent/endpoint.go (2)

50-50: LGTM! Clean route registration.

The license endpoint is properly registered with the API group.


146-155: LGTM! Well-documented endpoint.

The Swagger documentation is comprehensive and accurately describes the endpoint's behavior, including success and error responses.

pkg/config/types/generated_constants.go (1)

89-89: LGTM! Consistent constant definition.

The new constant follows the established naming pattern and integrates well with existing constants.

pkg/publicapi/endpoint/agent/endpoint_test.go (1)

22-23: Review the test license token.

The hardcoded JWT token was flagged by security scanning. While it appears to be a test token, please verify that:

  1. It's not a real production license
  2. The expiration date (2384881638 = Jan 19, 2045) is intentionally far in the future

Consider moving test tokens to a separate test data file or using a helper function to generate test tokens.

🧰 Tools
🪛 Gitleaks (8.21.2)

23-23: Uncovered a JSON Web Token, which may lead to unauthorized access to web applications and sensitive user data.

(jwt)

pkg/config/types/generated_descriptions.go (1)

91-91: LGTM! Description follows the established pattern.

The description for OrchestratorLicenseLocalPathKey is clear, concise, and follows the same style as other configuration descriptions in the map.

pkg/swagger/docs.go (2)

116-147: LGTM! Well-defined API endpoint with appropriate response codes.

The endpoint is properly documented with:

  • Clear summary and operationId
  • Appropriate response codes (200, 404, 500)
  • Proper schema references

1227-1302: LGTM! Comprehensive license claims schema.

The schema includes all essential JWT fields with proper descriptions and references to RFC 7519, plus custom license-specific fields:

  • Standard JWT claims (aud, exp, iat, iss, jti, nbf, sub)
  • License-specific claims (capabilities, customer_id, license_id, license_type, license_version, metadata, product)
test_integration/common_assets/nodes_configs/15_orchestrator_config_with_no_license.yaml (1)

1-10: LGTM! Valid test configuration for no-license scenario.

The configuration provides all necessary fields for testing the orchestrator without a license:

  • Unique node identification (uuid)
  • API configuration
  • Authentication token
  • Test labels
test_integration/common_assets/nodes_configs/13_orchestrator_config_with_license.yaml (2)

1-12: LGTM! Valid test configuration for license scenario.

The configuration includes all necessary fields including the license configuration.


8-9: Verify the license file path in the test environment.

The license file path is hardcoded to /bacalhau_integration_tests/common_assets/licenses/test-license.json. Ensure this path is correctly set up in the test environment.

✅ Verification successful

License file path is correctly configured

The path follows the established pattern where test_integration is mounted as /bacalhau_integration_tests in the test environment, and the license file exists at the expected location.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify the existence of the test license file
# Test: Check if the license file exists in the test assets
fd -t f "test-license.json" test_integration/common_assets/licenses/

Length of output: 126


Script:

#!/bin/bash
# Search for similar path patterns in yaml files
rg -g "*.yaml" -g "*.yml" "/bacalhau_integration_tests" test_integration/

Length of output: 2980

test_integration/common_assets/licenses/test-license-with-metadata.json (1)

1-3: Verify test JWT token validity and expiration.

The file contains a JWT token that could pose a security risk if valid. Please ensure:

  1. This is a test-only token with no production credentials
  2. The token is expired or invalidated
  3. Consider using a shorter token or placeholder in test files

Run the following script to check the token's validity and expiration:

✅ Verification successful

Confirmed: Test-only license token verified

This is a valid test license file based on:

  • Located in test assets directory
  • Contains test-specific identifiers (bacalhau-integration-tests, test-customer-id-123)
  • Properly isolated in test fixtures
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify if the JWT token is expired and test-only
# Note: This script only checks the token structure and claims, not its cryptographic validity

echo "Decoding JWT token to verify claims..."
TOKEN=$(cat test_integration/common_assets/licenses/test-license-with-metadata.json | jq -r .license)
echo $TOKEN | awk -F. '{print $2}' | base64 -d 2>/dev/null | jq .

Length of output: 2274


Script:

#!/bin/bash
# Description: Verify JWT token with proper base64 padding
echo "Decoding JWT token to verify claims..."
TOKEN=$(cat test_integration/common_assets/licenses/test-license-with-metadata.json | jq -r .license)
echo $TOKEN | awk -F. '{print $2}' | base64 -d 2>/dev/null | python3 -m json.tool 2>/dev/null || \
(echo $TOKEN | awk -F. '{print $2 "=="}' | base64 -d 2>/dev/null | python3 -m json.tool)

Length of output: 3475


Script:

#!/bin/bash
# Description: Simple JWT token decode using only base64
echo "Decoding JWT token to verify claims..."
TOKEN=$(cat test_integration/common_assets/licenses/test-license-with-metadata.json | jq -r .license)
echo "JWT Payload:"
echo $TOKEN | cut -d"." -f2 | { base64 -d 2>/dev/null || base64 -D 2>/dev/null; }

Length of output: 2305


Script:

#!/bin/bash
# Description: Decode URL-safe base64 JWT token
echo "Decoding JWT token to verify claims..."
TOKEN=$(cat test_integration/common_assets/licenses/test-license-with-metadata.json | jq -r .license)
echo "JWT Payload:"
echo $TOKEN | cut -d"." -f2 | tr '_-' '/+' | tr -d '\n' | fold -w 4 | sed '$ s/$/==/' | head -c -4 | base64 -d

Length of output: 2552

🧰 Tools
🪛 Gitleaks (8.21.2)

2-2: Uncovered a JSON Web Token, which may lead to unauthorized access to web applications and sensitive user data.

(jwt)

webui/lib/api/schema/swagger.json Outdated Show resolved Hide resolved
Copy link
Member

@wdbaruni wdbaruni left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just one comment. The rest looks great

pkg/publicapi/endpoint/agent/endpoint.go Outdated Show resolved Hide resolved
@jamlo jamlo requested a review from wdbaruni January 21, 2025 22:22
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

♻️ Duplicate comments (1)
pkg/publicapi/endpoint/agent/endpoint.go (1)

147-172: 🛠️ Refactor suggestion

Add authentication and rate limiting to the license endpoint.

The license endpoint exposes sensitive information and should be protected. Consider:

  1. Adding authentication requirement
  2. Implementing rate limiting
  3. Adding appropriate security headers

Additionally, as suggested in a previous review, consider moving the license validation logic to a dedicated license provider that can be used by both the API and other components.

🧹 Nitpick comments (3)
pkg/licensing/license_manager.go (1)

65-72: Ensure sensitive error information is not exposed

When validating the license token in ValidateLicense, consider whether the wrapped error from l.licenseValidator.ValidateToken might expose sensitive information. It's important to sanitize error messages to prevent leakage of internal details.

Consider returning a more generic error message or sanitizing the wrapped error to avoid revealing sensitive information.

test_integration/15_agent_license_inspect_with_no_license_suite_test.go (1)

41-49: Consider adding more test cases.

The test suite currently only verifies the error message when no license is configured. Consider adding test cases for:

  1. Invalid license file path
  2. Empty license file
  3. Malformed license file
🧰 Tools
🪛 GitHub Actions: Main Pipeline

[error] Test suite failed with exit code 1. One test suite (TestLocalLicenseInspectSuite) failed while others passed.

pkg/swagger/swagger.json (1)

1285-1292: Add validation constraints for NumericDate.

Consider adding validation constraints to ensure the time value is within acceptable bounds for JWT timestamps (e.g., not before Unix epoch, not too far in the future).

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1d3b71d and 7ca9a5b.

📒 Files selected for processing (9)
  • pkg/licensing/license_manager.go (1 hunks)
  • pkg/licensing/license_manager_test.go (1 hunks)
  • pkg/node/node.go (5 hunks)
  • pkg/publicapi/endpoint/agent/endpoint.go (5 hunks)
  • pkg/publicapi/endpoint/agent/endpoint_test.go (2 hunks)
  • pkg/swagger/docs.go (7 hunks)
  • pkg/swagger/swagger.json (5 hunks)
  • test_integration/15_agent_license_inspect_with_no_license_suite_test.go (1 hunks)
  • webui/lib/api/schema/swagger.json (5 hunks)
🧰 Additional context used
🪛 Gitleaks (8.21.2)
pkg/licensing/license_manager_test.go

14-14: Uncovered a JSON Web Token, which may lead to unauthorized access to web applications and sensitive user data.

(jwt)

pkg/publicapi/endpoint/agent/endpoint_test.go

24-24: Uncovered a JSON Web Token, which may lead to unauthorized access to web applications and sensitive user data.

(jwt)


25-25: Uncovered a JSON Web Token, which may lead to unauthorized access to web applications and sensitive user data.

(jwt)


229-229: Uncovered a JSON Web Token, which may lead to unauthorized access to web applications and sensitive user data.

(jwt)

🪛 GitHub Actions: Main Pipeline
test_integration/15_agent_license_inspect_with_no_license_suite_test.go

[error] Test suite failed with exit code 1. One test suite (TestLocalLicenseInspectSuite) failed while others passed.

🔇 Additional comments (15)
pkg/licensing/license_manager.go (4)

24-26: Good nil configuration check

The code correctly checks for a nil configuration and returns an appropriate error message.


28-31: Proper error handling when initializing LicenseValidator

The initialization of the licenseValidator handles errors appropriately by wrapping them with context, enhancing debuggability.


36-53: Robust license file reading and JSON parsing

The code correctly reads the license file and validates its JSON structure, ensuring that the required "license" field is present. Error handling is comprehensive at each step.


78-100: Comprehensive validation in ValidateLicenseWithNodeCount

The method correctly validates the license and checks the nodeCount against the max_nodes capability. Error handling covers scenarios where the capability is missing or invalid.

pkg/licensing/license_manager_test.go (2)

16-270: Comprehensive test coverage for LicenseManager

The unit tests provide thorough coverage of the LicenseManager functionality, testing various scenarios including valid and invalid tokens, missing licenses, invalid JSON structures, file not found errors, and node count validations.


14-14: ⚠️ Potential issue

Avoid hardcoding JWT tokens in test code

Line 14 contains a hardcoded JWT token. Even in test code, including real tokens can pose security risks.

Replace the hardcoded token with a mock or dummy token generated during test execution to ensure it doesn't contain any sensitive information. Verify that the token is not valid and cannot be used to access any systems.

🧰 Tools
🪛 Gitleaks (8.21.2)

14-14: Uncovered a JSON Web Token, which may lead to unauthorized access to web applications and sensitive user data.

(jwt)

pkg/node/node.go (3)

89-89: Addition of LicenseManager to Node struct

Integrating LicenseManager into the Node struct enables centralized management of licensing within the node, enhancing maintainability and modularity.


118-123: Proper initialization of LicenseManager in NewNode

The LicenseManager is initialized correctly within the NewNode function, and errors during initialization are handled appropriately by returning the error to the caller.


205-205: Integration of LicenseManager with agent endpoint

Passing the LicenseManager to the agent endpoint ensures that licensing checks are performed when handling agent-related API requests, which is essential for enforcing license restrictions.

pkg/publicapi/endpoint/agent/endpoint_test.go (1)

62-116: LGTM! Comprehensive test coverage.

The test case thoroughly validates the license endpoint's success scenario by checking all relevant fields in the response.

webui/lib/api/schema/swagger.json (2)

980-1055: LGTM! Well-documented response schema.

The GetAgentLicenseResponse schema is thoroughly documented with clear descriptions for each field.


112-137: 🛠️ Refactor suggestion

Add security definitions to the API schema.

The license endpoint's OpenAPI specification should include:

  1. Security scheme definitions (e.g., ApiKeyAuth)
  2. Security requirements for the endpoint
  3. Rate limiting headers in the response schema

Apply this diff to add security definitions:

+ "securityDefinitions": {
+   "ApiKeyAuth": {
+     "type": "apiKey",
+     "in": "header",
+     "name": "X-API-Key"
+   }
+ },
  "paths": {
    "/api/v1/agent/license": {
      "get": {
+       "security": [
+         {
+           "ApiKeyAuth": []
+         }
+       ],

Likely invalid or redundant comment.

pkg/swagger/swagger.json (2)

112-137: LGTM! Well-structured API endpoint definition.

The new endpoint follows REST API best practices with proper content types, status codes, and error handling.


980-1055: Consider security implications of exposing JWT claims.

While the response structure is well-defined, consider if exposing all JWT claims is necessary. Some claims might contain sensitive information that should be filtered out before returning to the client.

Run this script to check for any sensitive information in the JWT claims:

pkg/swagger/docs.go (1)

3172-3173: LGTM! Template delimiters properly configured.

The addition of explicit template delimiters (LeftDelim and RightDelim) helps prevent conflicts with other template processors.

pkg/publicapi/endpoint/agent/endpoint_test.go Show resolved Hide resolved
pkg/swagger/swagger.json Show resolved Hide resolved
pkg/licensing/license_manager.go Outdated Show resolved Hide resolved
pkg/licensing/license_manager.go Outdated Show resolved Hide resolved
pkg/node/node.go Outdated Show resolved Hide resolved
pkg/publicapi/endpoint/agent/endpoint.go Outdated Show resolved Hide resolved
Comment on lines 45 to 48
// Verify the JSON structure
var licenseFile struct {
License string `json:"license"`
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't like having inline structs for such behaviour. First, why can't we have license content at the root of the json object instead of having a license field? If it is a must, then lets introduce a LicenseWrapper type and not make it inline

Copy link
Contributor Author

@jamlo jamlo Jan 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@wdbaruni

First, why can't we have license content at the root of the json object instead of having a license field?

Not sure what you meant here, but the license content is already at the root of the json object. The Json file structure of the license:

{
  "license": "the-license-jwt-token"
}

If it is a must, then lets introduce a LicenseWrapper type and not make it inline

Hmm, It is not used anywhere else, do you think it is still worth it ?
The unit tests already cover all the edge cases for the format.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if the file only contains the license jwt token and nothing else, why do you need a json wrapping the token?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@wdbaruni I would rather have a JSON object as the structure of a license even if it has one item in it. It will easily allow for any future additions or migrations, compared to just an opaque string.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🔭 Outside diff range comments (2)
cmd/cli/license/inspect.go (1)

Line range hint 101-101: Handle potential nil ExpiresAt when formatting date

Accessing claims.ExpiresAt without checking for nil may cause a panic if ExpiresAt is nil. Ensure that ExpiresAt is not nil before calling Format, or handle the nil case appropriately.

Apply this diff to handle nil ExpiresAt:

-	{Left: "Valid Until", Right: claims.ExpiresAt.Format(time.DateOnly)},
+	{Left: "Valid Until", Right: func() string {
+		if claims.ExpiresAt != nil {
+			return claims.ExpiresAt.Format(time.DateOnly)
+		}
+		return "N/A"
+	}()},
cmd/cli/license/inspect_test.go (1)

Line range hint 231-231: Security: Consider removing or obfuscating test tokens.

The test file contains exposed JWT tokens that could potentially be misused. Consider:

  1. Using shorter, obviously fake tokens for basic validation tests
  2. Moving sensitive test data to a separate test fixtures file
  3. Adding a clear comment that these are test-only tokens

Also applies to: 238-238

🧹 Nitpick comments (10)
pkg/lib/license/license_validator.go (1)

40-52: Consider logging conversion errors in MaxNumberOfNodes

In the MaxNumberOfNodes method, if the "max_nodes" capability value is not a valid integer, the function silently returns 0. Logging a warning or returning an error could help identify misconfigurations or data issues.

test_integration/16_agent_license_inspect_expired_suite_test.go (1)

41-59: Enhance test robustness by parsing output

The test compares the entire output string to verify license details. This approach can be brittle if the output format changes. Consider parsing the command output and asserting individual fields to make the test more resilient and maintainable.

pkg/licensing/license_manager.go (2)

48-54: Consider extracting the license file structure to a named type.

The inline struct for the license file format could be moved to a named type for better reusability and documentation.

+// LicenseFile represents the structure of the license file
+type LicenseFile struct {
+    License string `json:"license"`
+}

 // Verify the JSON structure of the license file
-var licenseFile struct {
-    License string `json:"license"`
-}
+var licenseFile LicenseFile

70-73: Consider adding documentation for the License method.

The method would benefit from godoc explaining that it returns nil when no license is configured.

+// License returns the current license claims.
+// Returns nil if no license is configured.
 func (l *LicenseManager) License() *license.LicenseClaims {
     return l.licenseClaims
 }
test_integration/13_agent_license_inspect_suite_test.go (2)

49-56: Consider using constants for expected values.

The hardcoded expected values could be moved to constants or test data structures for better maintainability.

+const (
+    expectedProduct    = "Bacalhau"
+    expectedLicenseID  = "e66d1f3a-a8d8-4d57-8f14-00722844afe2"
+    expectedCustomerID = "test-customer-id-123"
+    expectedVersion    = "v1"
+    expectedMaxNodes   = "1"
+)

-expectedOutput := `Product      = Bacalhau
-License ID   = e66d1f3a-a8d8-4d57-8f14-00722844afe2
-Customer ID  = test-customer-id-123
-Valid Until  = 2045-07-28
-Version      = v1
-Expired      = false
-Capabilities = max_nodes=1
-Metadata     = {}`
+expectedOutput := fmt.Sprintf(
+    "Product      = %s\n"+
+    "License ID   = %s\n"+
+    "Customer ID  = %s\n"+
+    "Valid Until  = 2045-07-28\n"+
+    "Version      = %s\n"+
+    "Expired      = false\n"+
+    "Capabilities = max_nodes=%s\n"+
+    "Metadata     = {}",
+    expectedProduct,
+    expectedLicenseID,
+    expectedCustomerID,
+    expectedVersion,
+    expectedMaxNodes,
+)

76-94: Consider using table-driven tests for JSON field validation.

The JSON field validation could be simplified using a table-driven test approach.

+type jsonFieldTest struct {
+    path     string
+    expected string
+}
+
+tests := []jsonFieldTest{
+    {path: "$.product", expected: expectedProduct},
+    {path: "$.license_id", expected: expectedLicenseID},
+    {path: "$.customer_id", expected: expectedCustomerID},
+    {path: "$.license_version", expected: expectedVersion},
+    {path: "$.capabilities.max_nodes", expected: expectedMaxNodes},
+}
+
+for _, test := range tests {
+    value, err := output.Query(test.path)
+    s.Require().NoError(err)
+    s.Require().Equal(test.expected, value.String())
+}
pkg/publicapi/endpoint/agent/endpoint.go (1)

152-173: Consider adding more detailed error information.

The error message could provide more context about why the license might not be configured and potential remediation steps.

-		return echo.NewHTTPError(
-			http.StatusNotFound,
-			"Error inspecting orchestrator license: No license configured for orchestrator.",
-		)
+		return echo.NewHTTPError(
+			http.StatusNotFound,
+			"Error inspecting orchestrator license: No license configured for orchestrator. "+
+				"Please ensure a valid license file is specified in the orchestrator configuration.",
+		)
pkg/lib/license/license_validator_test.go (3)

Line range hint 15-157: Consider moving sensitive test data to separate test fixture files.

The test data contains sensitive information like JWKs and tokens. Moving this data to separate test fixture files would:

  1. Improve maintainability by separating test data from test logic
  2. Enhance security by making it easier to manage sensitive test data
  3. Allow for better version control of test fixtures

500-514: Consider adding more assertions for expired token validation.

While the test verifies basic functionality, consider adding assertions for:

  • The exact expiration time
  • Other time-related claims (IssuedAt, NotBefore)
  • Error scenarios with malformed expiration dates

516-559: Add edge cases for MaxNumberOfNodes testing.

Consider adding test cases for:

  • Negative numbers
  • Very large numbers
  • Floating-point numbers
  • Special characters in the number string
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7ca9a5b and ff9542f.

📒 Files selected for processing (17)
  • cmd/cli/agent/license/inspect.go (1 hunks)
  • cmd/cli/license/inspect.go (2 hunks)
  • cmd/cli/license/inspect_test.go (6 hunks)
  • pkg/lib/license/license_validator.go (3 hunks)
  • pkg/lib/license/license_validator_test.go (8 hunks)
  • pkg/licensing/license_manager.go (1 hunks)
  • pkg/licensing/license_manager_test.go (1 hunks)
  • pkg/node/node.go (3 hunks)
  • pkg/publicapi/endpoint/agent/endpoint.go (4 hunks)
  • pkg/publicapi/endpoint/agent/endpoint_test.go (3 hunks)
  • test_integration/12_local_license_inspect_suite_test.go (2 hunks)
  • test_integration/13_agent_license_inspect_suite_test.go (1 hunks)
  • test_integration/14_agent_license_inspect_with_metadata_suite_test.go (1 hunks)
  • test_integration/15_agent_license_inspect_with_no_license_suite_test.go (1 hunks)
  • test_integration/16_agent_license_inspect_expired_suite_test.go (1 hunks)
  • test_integration/common_assets/licenses/test-license-valid-but-expired.json (1 hunks)
  • test_integration/common_assets/nodes_configs/16_orchestrator_config_with_expired_license.yaml (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • cmd/cli/agent/license/inspect.go
  • test_integration/14_agent_license_inspect_with_metadata_suite_test.go
  • test_integration/15_agent_license_inspect_with_no_license_suite_test.go
🧰 Additional context used
🪛 Gitleaks (8.21.2)
test_integration/common_assets/licenses/test-license-valid-but-expired.json

2-2: Uncovered a JSON Web Token, which may lead to unauthorized access to web applications and sensitive user data.

(jwt)

pkg/lib/license/license_validator_test.go

231-231: Uncovered a JSON Web Token, which may lead to unauthorized access to web applications and sensitive user data.

(jwt)


238-238: Uncovered a JSON Web Token, which may lead to unauthorized access to web applications and sensitive user data.

(jwt)

pkg/publicapi/endpoint/agent/endpoint_test.go

24-24: Uncovered a JSON Web Token, which may lead to unauthorized access to web applications and sensitive user data.

(jwt)


25-25: Uncovered a JSON Web Token, which may lead to unauthorized access to web applications and sensitive user data.

(jwt)

pkg/licensing/license_manager_test.go

14-14: Uncovered a JSON Web Token, which may lead to unauthorized access to web applications and sensitive user data.

(jwt)

🔇 Additional comments (23)
pkg/lib/license/license_validator.go (1)

146-165: Ensure safe usage when parsing unverified tokens

The method validateToken parses JWT tokens without verification when the token is expired, and verifyExpiry is false. Parsing unverified tokens can be risky as it might expose the application to security vulnerabilities if the token is tampered with. Please review to ensure that no sensitive operations depend on unverified data and that this approach aligns with security best practices.

test_integration/12_local_license_inspect_suite_test.go (2)

63-83: LGTM! Well-structured test for expired license inspection.

The test case is well-implemented with:

  • Clear setup and execution
  • Comprehensive assertions
  • Proper error handling

132-132: LGTM! Error message update maintains consistency.

The error message update aligns with the new error handling structure across the codebase.

cmd/cli/license/inspect_test.go (7)

Line range hint 197-241: LGTM! Comprehensive test coverage for license validation.

The test cases thoroughly cover:

  • Valid and invalid scenarios
  • Expiration handling
  • Error message verification

Also applies to: 433-443


Line range hint 500-514: LGTM! Good test for expired but valid token handling.

The test ensures that expired licenses can still be inspected for their contents while correctly reporting their expired status.


Line range hint 516-559: LGTM! Thorough testing of MaxNumberOfNodes capability.

The test cases cover all relevant scenarios for the max_nodes capability:

  • Valid numeric values
  • Empty values
  • Missing capability
  • Invalid formats

Line range hint 561-596: LGTM! Well-structured expiration testing.

The test cases properly verify the expiration logic using time-based scenarios:

  • Nil expiration
  • Future expiration
  • Past expiration

Line range hint 598-637: LGTM! Good coverage for strict validation.

The test cases verify that strict validation properly handles both valid and expired tokens.


Line range hint 639-674: LGTM! Comprehensive JWKS malformation testing.

The test cases cover various JWKS error scenarios:

  • Missing required fields
  • Invalid key types
  • Missing key types

Line range hint 676-700: LGTM! Good test for invalid capabilities format.

The test ensures proper error handling for malformed capability values.

test_integration/common_assets/licenses/test-license-valid-but-expired.json (1)

1-3: LGTM!

The test license file is properly structured and serves its purpose for testing expired license scenarios.

🧰 Tools
🪛 Gitleaks (8.21.2)

2-2: Uncovered a JSON Web Token, which may lead to unauthorized access to web applications and sensitive user data.

(jwt)

pkg/licensing/license_manager.go (1)

13-18: LGTM!

The LicenseManager struct is well-designed with clear responsibilities and minimal dependencies.

pkg/licensing/license_manager_test.go (2)

13-14: LGTM!

The test token is properly documented and used only for testing purposes.

🧰 Tools
🪛 Gitleaks (8.21.2)

14-14: Uncovered a JSON Web Token, which may lead to unauthorized access to web applications and sensitive user data.

(jwt)


16-149: LGTM!

The test suite is comprehensive and well-structured, covering all important scenarios including:

  • Invalid token handling
  • Valid token verification
  • No license configuration
  • Invalid JSON handling
  • File not found scenarios
  • Nil configuration handling
pkg/publicapi/endpoint/agent/endpoint_test.go (5)

31-34: LGTM!

The LicenseManager initialization is correctly added to support the new license functionality while maintaining the original test's purpose.


23-25: Consider moving test tokens to fixtures.

While these are test-specific tokens, it's a good practice to:

  1. Move tokens to a dedicated test fixtures file
  2. Add a comment explaining their test-specific nature
  3. Document the claims encoded in these tokens
🧰 Tools
🪛 Gitleaks (8.21.2)

24-24: Uncovered a JSON Web Token, which may lead to unauthorized access to web applications and sensitive user data.

(jwt)


25-25: Uncovered a JSON Web Token, which may lead to unauthorized access to web applications and sensitive user data.

(jwt)


69-126: LGTM! Comprehensive test coverage.

The test thoroughly validates the license endpoint:

  • Safe handling of temporary files
  • Comprehensive verification of license claims
  • Proper cleanup of test resources

128-161: LGTM! Good error handling test.

The test properly validates the error scenario when no license is configured.


163-235: LGTM! Good edge case coverage.

The tests thoroughly validate important edge cases:

  • License manager configuration errors
  • Expired license handling
pkg/node/node.go (2)

117-121: LGTM! Clean initialization of license manager.

The license manager is properly initialized with appropriate error handling.


199-208: LGTM! Clean integration with agent endpoint.

The license manager is correctly passed to the agent endpoint, following the feedback about not storing it in the node struct.

pkg/lib/license/license_validator_test.go (2)

Line range hint 197-257: Well-structured test cases with good coverage.

The addition of the wantExpired field and corresponding assertions improves the test coverage by explicitly verifying token expiration behavior. The test cases cover various scenarios including valid tokens, invalid formats, and error conditions.

🧰 Tools
🪛 Gitleaks (8.21.2)

231-231: Uncovered a JSON Web Token, which may lead to unauthorized access to web applications and sensitive user data.

(jwt)


238-238: Uncovered a JSON Web Token, which may lead to unauthorized access to web applications and sensitive user data.

(jwt)


231-241: Review security implications of embedded test tokens.

While these are test tokens, consider:

  1. Adding clear comments indicating these are test-only tokens
  2. Using shorter token examples where possible
  3. Documenting the token generation process for maintainers

Run this script to verify these are indeed test tokens:

✅ Verification successful

JWT tokens are properly confined to test files ✓

All JWT tokens are correctly isolated to test files, test fixtures, and integration test assets. The tokens contain test-specific identifiers and are not used in production code.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify if the tokens are used only in tests
# Test: Search for token usage outside of test files
rg -g '!*_test.go' "eyJhbGciOiJSUzI1NiI"

Length of output: 4827

🧰 Tools
🪛 Gitleaks (8.21.2)

231-231: Uncovered a JSON Web Token, which may lead to unauthorized access to web applications and sensitive user data.

(jwt)


238-238: Uncovered a JSON Web Token, which may lead to unauthorized access to web applications and sensitive user data.

(jwt)

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (2)
webui/lib/api/schema/swagger.json (1)

2781-2789: Add validation constraints to License type.

The License type should include:

  • Required field marker for LocalPath
  • Pattern for valid file paths
  • Example value

Apply this diff:

         "types.License": {
             "type": "object",
             "properties": {
                 "LocalPath": {
                     "description": "LocalPath specifies the local license file path",
-                    "type": "string"
+                    "type": "string",
+                    "pattern": "^[\\w\\-./]+$",
+                    "example": "/etc/bacalhau/license.json"
                 }
-            }
+            },
+            "required": ["LocalPath"]
         },
pkg/swagger/docs.go (1)

2785-2793: Consider enhancing the license configuration type.

While the types.License type is well-structured, consider adding:

  1. Path validation to ensure the license file exists and is readable
  2. Default path configuration for easier setup

Example enhancement:

 type License struct {
     // LocalPath specifies the local license file path
     LocalPath string
+    // DefaultPath specifies the default path to look for license file if LocalPath is empty
+    DefaultPath string `default:"/etc/bacalhau/license.json"`
 }
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ff9542f and 4b793d9.

📒 Files selected for processing (4)
  • pkg/publicapi/endpoint/agent/endpoint.go (4 hunks)
  • pkg/swagger/docs.go (7 hunks)
  • pkg/swagger/swagger.json (5 hunks)
  • webui/lib/api/schema/swagger.json (5 hunks)
🔇 Additional comments (8)
pkg/publicapi/endpoint/agent/endpoint.go (2)

11-11: Clean dependency injection of the license manager!

Good use of dependency injection pattern to make the license manager available to the endpoint.

Also applies to: 23-23, 31-31


Line range hint 34-57: Well-structured constructor with proper validation!

Good practices observed:

  • Early validation of required dependencies
  • Clear error message
  • Proper initialization of the license manager field
webui/lib/api/schema/swagger.json (2)

980-1055: Well-structured and documented license response schema!

The schema:

  • Follows JWT standards with proper RFC references
  • Includes all necessary license claims
  • Has clear field descriptions

112-137: 🛠️ Refactor suggestion

Add security scheme to the API definition.

The API definition should include:

  1. Security scheme definition (e.g., ApiKeyAuth)
  2. Authentication requirement for the endpoint
  3. 401 response for unauthorized access

Apply this diff:

 {
+    "securityDefinitions": {
+        "ApiKeyAuth": {
+            "type": "apiKey",
+            "name": "Authorization",
+            "in": "header"
+        }
+    },
     "paths": {
         "/api/v1/agent/license": {
             "get": {
                 "produces": [
                     "application/json"
                 ],
+                "security": [
+                    {
+                        "ApiKeyAuth": []
+                    }
+                ],
                 "tags": [
                     "Ops"
                 ],
                 "responses": {
                     "200": {
                         "description": "OK",
                         "schema": {
                             "$ref": "#/definitions/apimodels.GetAgentLicenseResponse"
                         }
                     },
+                    "401": {
+                        "description": "Unauthorized",
+                        "schema": {
+                            "type": "string"
+                        }
+                    },
                     "404": {
                         "description": "Not Found",
                         "schema": {
                             "type": "string"
                         }
                     }
                 }
             }
         }
     }
 }

Likely invalid or redundant comment.

pkg/swagger/swagger.json (1)

Line range hint 1-3500: Verify swagger files stay in sync.

This file appears to be a duplicate of webui/lib/api/schema/swagger.json. Ensure both files stay synchronized.

Run this script to verify the files are identical:

✅ Verification successful

Swagger files are properly synchronized

The swagger.json files in webui/lib/api/schema/ and pkg/swagger/ are identical, confirming they are properly maintained in sync.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check if swagger files are in sync
# Expected: Files should be identical

diff webui/lib/api/schema/swagger.json pkg/swagger/swagger.json
if [ $? -eq 0 ]; then
    echo "Swagger files are in sync"
else
    echo "Warning: Swagger files are out of sync"
fi

Length of output: 144

pkg/swagger/docs.go (3)

116-141: Well-structured API endpoint definition!

The /api/v1/agent/license endpoint follows REST API best practices with clear path structure, appropriate HTTP method, well-defined response codes, and proper documentation.


984-1059: Comprehensive license response model!

The GetAgentLicenseResponse type:

  • Properly implements JWT standard claims
  • Includes essential license information
  • Supports extensibility through metadata
  • Has clear field descriptions

3172-3173: Appropriate template delimiter configuration!

The addition of explicit template delimiters helps avoid conflicts between Go templates and JSON syntax.

pkg/publicapi/endpoint/agent/endpoint.go Show resolved Hide resolved
@jamlo jamlo requested a review from wdbaruni January 23, 2025 20:56
@jamlo jamlo merged commit a088691 into main Jan 27, 2025
14 checks passed
@jamlo jamlo deleted the jamlo/ENG-498/license-orchestrator-configuration branch January 27, 2025 16:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants