github.com/google/osv-scalibr@v0.4.1/veles/secrets/hashicorpvault/validator_test.go (about)

     1  // Copyright 2025 Google LLC
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //      http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package hashicorpvault
    16  
    17  import (
    18  	"context"
    19  	"net/http"
    20  	"net/http/httptest"
    21  	"testing"
    22  
    23  	"github.com/google/osv-scalibr/veles"
    24  )
    25  
    26  func TestTokenValidator_Validate(t *testing.T) {
    27  	tests := []struct {
    28  		name           string
    29  		statusCode     int
    30  		expectedStatus veles.ValidationStatus
    31  		expectError    bool
    32  	}{
    33  		{
    34  			name:           "valid token",
    35  			statusCode:     http.StatusOK,
    36  			expectedStatus: veles.ValidationValid,
    37  			expectError:    false,
    38  		},
    39  		{
    40  			name:           "invalid token - unauthorized",
    41  			statusCode:     http.StatusUnauthorized,
    42  			expectedStatus: veles.ValidationInvalid,
    43  			expectError:    false,
    44  		},
    45  		{
    46  			name:           "invalid token - forbidden",
    47  			statusCode:     http.StatusForbidden,
    48  			expectedStatus: veles.ValidationInvalid,
    49  			expectError:    false,
    50  		},
    51  		{
    52  			name:           "server error",
    53  			statusCode:     http.StatusInternalServerError,
    54  			expectedStatus: veles.ValidationFailed,
    55  			expectError:    true,
    56  		},
    57  		{
    58  			name:           "bad gateway",
    59  			statusCode:     http.StatusBadGateway,
    60  			expectedStatus: veles.ValidationFailed,
    61  			expectError:    true,
    62  		},
    63  	}
    64  
    65  	for _, test := range tests {
    66  		t.Run(test.name, func(t *testing.T) {
    67  			server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    68  				// Verify the correct endpoint and headers
    69  				if r.URL.Path != "/v1/auth/token/lookup-self" {
    70  					t.Errorf("Expected path /v1/auth/token/lookup-self, got %s", r.URL.Path)
    71  				}
    72  				if r.Method != http.MethodGet {
    73  					t.Errorf("Expected GET method, got %s", r.Method)
    74  				}
    75  				if token := r.Header.Get("X-Vault-Token"); token != "hvs.test-token" {
    76  					t.Errorf("Expected X-Vault-Token header with test token, got %s", token)
    77  				}
    78  
    79  				w.WriteHeader(test.statusCode)
    80  			}))
    81  			defer server.Close()
    82  
    83  			serverURL := server.URL
    84  			validator := NewTokenValidator(serverURL)
    85  			validator.HTTPC = server.Client()
    86  
    87  			token := Token{Token: "hvs.test-token"}
    88  			status, err := validator.Validate(t.Context(), token)
    89  
    90  			if test.expectError && err == nil {
    91  				t.Fatal("Expected error, got nil")
    92  			}
    93  			if !test.expectError && err != nil {
    94  				t.Fatalf("Unexpected error: %v", err)
    95  			}
    96  
    97  			if status != test.expectedStatus {
    98  				t.Errorf("Expected status %v, got %v", test.expectedStatus, status)
    99  			}
   100  		})
   101  	}
   102  }
   103  
   104  func TestAppRoleValidator_Validate(t *testing.T) {
   105  	tests := []struct {
   106  		name           string
   107  		credentials    AppRoleCredentials
   108  		statusCode     int
   109  		expectedStatus veles.ValidationStatus
   110  		expectError    bool
   111  	}{
   112  		{
   113  			name: "valid_credentials",
   114  			credentials: AppRoleCredentials{
   115  				RoleID:   "12345678-1234-1234-1234-123456789012",
   116  				SecretID: "87654321-4321-4321-4321-210987654321",
   117  			},
   118  			statusCode:     http.StatusOK,
   119  			expectedStatus: veles.ValidationValid,
   120  			expectError:    false,
   121  		},
   122  		{
   123  			name: "invalid_credentials_-_unauthorized",
   124  			credentials: AppRoleCredentials{
   125  				RoleID:   "12345678-1234-1234-1234-123456789012",
   126  				SecretID: "invalid-secret",
   127  			},
   128  			statusCode:     http.StatusUnauthorized,
   129  			expectedStatus: veles.ValidationInvalid,
   130  			expectError:    false,
   131  		},
   132  		{
   133  			name: "invalid_credentials_-_bad_request",
   134  			credentials: AppRoleCredentials{
   135  				RoleID:   "invalid-role-id",
   136  				SecretID: "87654321-4321-4321-4321-210987654321",
   137  			},
   138  			statusCode:     http.StatusBadRequest,
   139  			expectedStatus: veles.ValidationInvalid,
   140  			expectError:    false,
   141  		},
   142  		{
   143  			name: "server_error",
   144  			credentials: AppRoleCredentials{
   145  				RoleID:   "12345678-1234-1234-1234-123456789012",
   146  				SecretID: "87654321-4321-4321-4321-210987654321",
   147  			},
   148  			statusCode:     http.StatusInternalServerError,
   149  			expectedStatus: veles.ValidationFailed,
   150  			expectError:    true,
   151  		},
   152  		{
   153  			name: "missing_role_id",
   154  			credentials: AppRoleCredentials{
   155  				RoleID:   "",
   156  				SecretID: "87654321-4321-4321-4321-210987654321",
   157  			},
   158  			statusCode:     0, // Won't make HTTP request
   159  			expectedStatus: veles.ValidationFailed,
   160  			expectError:    true,
   161  		},
   162  		{
   163  			name: "missing_secret_id",
   164  			credentials: AppRoleCredentials{
   165  				RoleID:   "12345678-1234-1234-1234-123456789012",
   166  				SecretID: "",
   167  			},
   168  			statusCode:     0, // Won't make HTTP request
   169  			expectedStatus: veles.ValidationFailed,
   170  			expectError:    true,
   171  		},
   172  	}
   173  
   174  	for _, test := range tests {
   175  		t.Run(test.name, func(t *testing.T) {
   176  			server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   177  				// Verify the correct endpoint and headers
   178  				if r.URL.Path != "/v1/auth/approle/login" {
   179  					t.Errorf("Expected path /v1/auth/approle/login, got %s", r.URL.Path)
   180  				}
   181  				if r.Method != http.MethodPost {
   182  					t.Errorf("Expected POST method, got %s", r.Method)
   183  				}
   184  				if contentType := r.Header.Get("Content-Type"); contentType != "application/json" {
   185  					t.Errorf("Expected Content-Type application/json, got %s", contentType)
   186  				}
   187  
   188  				w.WriteHeader(test.statusCode)
   189  			}))
   190  			defer server.Close()
   191  
   192  			serverURL := server.URL
   193  			validator := NewAppRoleValidator(serverURL)
   194  			validator.HTTPC = server.Client()
   195  
   196  			status, err := validator.Validate(t.Context(), test.credentials)
   197  
   198  			if test.expectError && err == nil {
   199  				t.Fatal("Expected error, got nil")
   200  			}
   201  			if !test.expectError && err != nil {
   202  				t.Fatalf("Unexpected error: %v", err)
   203  			}
   204  
   205  			if status != test.expectedStatus {
   206  				t.Errorf("Expected status %v, got %v", test.expectedStatus, status)
   207  			}
   208  		})
   209  	}
   210  }
   211  
   212  func TestValidator_InvalidVaultURL(t *testing.T) {
   213  	validator := NewTokenValidator("://invalid-url")
   214  	token := Token{Token: "hvs.test-token"}
   215  	status, err := validator.Validate(t.Context(), token)
   216  
   217  	if err == nil {
   218  		t.Fatal("Expected error for invalid URL, got nil")
   219  	}
   220  	if status != veles.ValidationFailed {
   221  		t.Errorf("Expected ValidationFailed status, got %v", status)
   222  	}
   223  }
   224  
   225  func TestValidator_NetworkError(t *testing.T) {
   226  	// Use a URL that will cause a network error
   227  	validator := NewTokenValidator("http://localhost:1")
   228  	token := Token{Token: "hvs.test-token"}
   229  	status, err := validator.Validate(t.Context(), token)
   230  
   231  	if err == nil {
   232  		t.Fatal("Expected network error, got nil")
   233  	}
   234  	if status != veles.ValidationFailed {
   235  		t.Errorf("Expected ValidationFailed status, got %v", status)
   236  	}
   237  }
   238  
   239  func TestValidator_ContextCancellation(t *testing.T) {
   240  	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   241  		// This handler will never respond, allowing us to test context cancellation
   242  		select {}
   243  	}))
   244  	defer server.Close()
   245  
   246  	serverURL := server.URL
   247  	validator := NewTokenValidator(serverURL)
   248  	validator.HTTPC = server.Client()
   249  
   250  	ctx, cancel := context.WithCancel(t.Context())
   251  	cancel() // Cancel immediately
   252  
   253  	token := Token{Token: "hvs.test-token"}
   254  	status, err := validator.Validate(ctx, token)
   255  
   256  	if err == nil {
   257  		t.Fatal("Expected context cancellation error, got nil")
   258  	}
   259  	if status != veles.ValidationFailed {
   260  		t.Errorf("Expected ValidationFailed status, got %v", status)
   261  	}
   262  }