github.com/google/osv-scalibr@v0.4.1/veles/secrets/hcp/detector_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 hcp_test
    16  
    17  import (
    18  	"strings"
    19  	"testing"
    20  
    21  	"github.com/google/go-cmp/cmp"
    22  	"github.com/google/go-cmp/cmp/cmpopts"
    23  	"github.com/google/osv-scalibr/veles"
    24  	"github.com/google/osv-scalibr/veles/secrets/hcp"
    25  )
    26  
    27  func TestPairDetector(t *testing.T) {
    28  	engine, err := veles.NewDetectionEngine([]veles.Detector{hcp.NewPairDetector()})
    29  	if err != nil {
    30  		t.Fatal(err)
    31  	}
    32  
    33  	id := "53au9oDSqR8SBzIy6QJASHnyC1SMQxE2"                                  // 32 chars
    34  	sec := "GGoNkaj1uVBWLO5Lk0-G3duEBK2Mi-w8kUpIJfX7u93fgWqnbMiaKYJgKrO2F6Vc" // 64 chars
    35  
    36  	cases := []struct {
    37  		name  string
    38  		input string
    39  		want  []veles.Secret
    40  	}{
    41  		{"param_pair", "hcp_client_id=" + id + "\nhcp_client_secret=" + sec, []veles.Secret{hcp.ClientCredentials{ClientID: id, ClientSecret: sec}}},
    42  		{"env_pair", "HCP_CLIENT_ID=" + id + "\nHCP_CLIENT_SECRET=" + sec, []veles.Secret{hcp.ClientCredentials{ClientID: id, ClientSecret: sec}}},
    43  		{"kv_colon_pair", "hcp_client_id: '" + id + "'\nhcp_client_secret: \"" + sec + "\"", []veles.Secret{hcp.ClientCredentials{ClientID: id, ClientSecret: sec}}},
    44  		{"id_only", "hcp_client_id=" + id, []veles.Secret{hcp.ClientCredentials{ClientID: id}}},
    45  		{"secret_only", "hcp_client_secret=" + sec, []veles.Secret{hcp.ClientCredentials{ClientSecret: sec}}},
    46  		{"wrong_secret", "client_secret=" + id, []veles.Secret{}},
    47  		{"secret_before_id_within_window", "hcp_client_secret=" + sec + "\n...\nHCP_CLIENT_ID=" + id, []veles.Secret{hcp.ClientCredentials{ClientID: id, ClientSecret: sec}}},
    48  		{"too_far_apart", func() string {
    49  			filler := strings.Repeat("a", 10*10*1<<10) // 100 KiB
    50  			return "HCP_CLIENT_ID=" + id + "\n" + filler + "\nHCP_CLIENT_SECRET=" + sec
    51  		}(), []veles.Secret{hcp.ClientCredentials{ClientID: id}, hcp.ClientCredentials{ClientSecret: sec}}},
    52  		{"secret_before_id_too_far", func() string {
    53  			filler := strings.Repeat("b", 10*10*1<<10) // 100 KiB
    54  			return "HCP_CLIENT_SECRET=" + sec + "\n" + filler + "\nHCP_CLIENT_ID=" + id
    55  		}(), []veles.Secret{hcp.ClientCredentials{ClientSecret: sec}, hcp.ClientCredentials{ClientID: id}}},
    56  	}
    57  
    58  	for _, tc := range cases {
    59  		t.Run(tc.name, func(t *testing.T) {
    60  			got, err := engine.Detect(t.Context(), strings.NewReader(tc.input))
    61  			if err != nil {
    62  				t.Fatalf("Detect() error: %v", err)
    63  			}
    64  			if diff := cmp.Diff(tc.want, got, cmpopts.EquateEmpty()); diff != "" {
    65  				t.Errorf("Detect() diff (-want +got):\n%s", diff)
    66  			}
    67  		})
    68  	}
    69  }
    70  
    71  func TestAccessTokenDetector(t *testing.T) {
    72  	engine, err := veles.NewDetectionEngine([]veles.Detector{hcp.NewAccessTokenDetector()})
    73  	if err != nil {
    74  		t.Fatal(err)
    75  	}
    76  
    77  	// JWT with strict HCP-like fields in payload: header.payload.signature (base64url)
    78  	payload := "eyJpc3MiOiJodHRwczovL2F1dGguaWRwLmhhc2hpY29ycC5jb20vIiwiYXVkIjpbImh0dHBzOi8vYXBpLmhhc2hpY29ycC5jbG91ZCJdLCJndHkiOiJjbGllbnQtY3JlZGVudGlhbHMifQ"
    79  	header := "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9"
    80  	sig := "SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c"
    81  	jwt := header + "." + payload + "." + sig
    82  
    83  	// Same structure but different issuer (should be rejected)
    84  	payloadWrongIss := "eyJpc3MiOiJodHRwczovL3NvbWUtcmFuZG9tLWlkcC5pbnRlcm5hbC8iLCJhdWQiOlsiaHR0cHM6Ly9hcGkuaGFzaGljb3JwLmNsb3VkIl0sImd0eSI6ImNsaWVudC1jcmVkZW50aWFscyJ9"
    85  	jtwWrong := header + "." + payloadWrongIss + "." + sig
    86  
    87  	cases := []struct {
    88  		name  string
    89  		input string
    90  		want  []veles.Secret
    91  	}{
    92  		{"bare_jwt_hcp", jwt, []veles.Secret{hcp.AccessToken{Token: jwt}}},
    93  		{"wrong_issuer", jtwWrong, nil},
    94  	}
    95  
    96  	for _, tc := range cases {
    97  		t.Run(tc.name, func(t *testing.T) {
    98  			got, err := engine.Detect(t.Context(), strings.NewReader(tc.input))
    99  			if err != nil {
   100  				t.Fatalf("Detect() error: %v", err)
   101  			}
   102  			if diff := cmp.Diff(tc.want, got, cmpopts.EquateEmpty()); diff != "" {
   103  				t.Errorf("Detect() diff (-want +got):\n%s", diff)
   104  			}
   105  		})
   106  	}
   107  }