github.com/google/osv-scalibr@v0.4.1/veles/secrets/azuretoken/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 azuretoken_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/azuretoken"
    25  )
    26  
    27  // Valid Azure access/id token with the following issuer:
    28  //   - "https://login.microsoftonline.com/{tenant}/v2.0"
    29  //   - "https://sts.windows.net/{tenant}"
    30  const (
    31  	testAccessTokenMicrosoftIssuer = "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6Ik1uQ19WWmNBVGZNNXBPWWlKSE1iYTlnb0VLWSIsImtpZCI6Ik1uQ19WWmNBVGZNNXBPWWlKSE1iYTlnb0VLWSJ9.eyJhdWQiOiJodHRwczovL2dyYXBoLm1pY3Jvc29mdC5jb20iLCJpc3MiOiJodHRwczovL3N0cy53aW5kb3dzLm5ldC83MmY5ODhiZi04NmYxLTQxYWYtOTFhYi0yZDdjZDAxMWRiNDcvIiwiaWF0IjoxNjk0MTE5MjAwLCJuYmYiOjE2OTQxMTkyMDAsImV4cCI6MTY5NDEyMzEwMCwiYWNyIjoiMSIsImFpbyI6IkFVUUF1LzhVQUFBQWlqVElRWkV5QUc2TXE1Q3U4emdDbElSdldqZmJ4WHJZL0lSb0hDQkZxY0JYeU01dzJkSUxpMzZuWVNLSXlKUWVCMGh5ckx6cTVqWTdqMkVpMnlrOEE9PSIsImFtciI6WyJwd2QiXSwiYXBwaWQiOiIwNGIwNzc5NS04ZGRiLTQ2MWEtYmJlZS0wMmY5ZTFiZjdiNDYiLCJhcHBpZGFjciI6IjAiLCJmYW1pbHlfbmFtZSI6IkRvZSIsImdpdmVuX25hbWUiOiJKb2huIiwiaXBhZGRyIjoiMTk4LjUxLjEwMC4xIiwibmFtZSI6IkpvaG4gRG9lIiwib2lkIjoiMDAwMDAwMDAtMDAwMC0wMDAwLWMwMDAtMDAwMDAwMDAwMDAwIiwib25wcmVtX3NpZCI6IlMtMS01LTIxLTEyMzQ1Njc4OTAtMTIzNDU2Nzg5LTEyMzQ1Njc4OS0xMDAwIiwicmgiOiIwLkFWWUEtNGo1Y3ZHR3IwR1JxeTE4MFFIYlI1VjNzWlRibmhwS3V1NEMtZUc3OSIsInNjcCI6IkZpbGVzLlJlYWQuQWxsIERpcmVjdG9yeS5SZWFkLkFsbCIsInN1YiI6InZJR01mcXREX2V3b2hWS2VvSS1KamFzOHJ4cEJQNTh1WEdPNWJTZm5IaU0iLCJ0ZW5hbnRfcmVnaW9uX3Njb3BlIjoiTkEiLCJ0aWQiOiI3MmY5ODhiZi04NmYxLTQxYWYtOTFhYi0yZDdjZDAxMWRiNDciLCJ1bmlxdWVfbmFtZSI6ImpvaG5kb2VAZXhhbXBsZS5jb20iLCJ1cG4iOiJqb2huZG9lQGV4YW1wbGUuY29tIiwidXRpIjoiVWN6eE1McVNpVVNzTTZGdHlJaFhBUSIsInZlciI6IjEuMCJ9.GJZ9j8qO3X5fKJZ2j8qO3X5fKJZ2j8qO3X5fKJZ2j8qO3X5fKJZ2j8qO3X5fKJZ2j8qO3X5fKJZ2j8qO3X5fKJZ2j8qO3X5fKJZ2j8qO3X5fKJZ2j8qO3X5fKJZ2j8qO3X5fKJZ2j8qO3X5fKJZ2j8qO3X5fKJZ2j8qO3X5fKJZ2j8qO3X5fKJZ2j8qO3X5fKJZ2j8qO3X5fKJZ2j8qO3X5fKJZ2j8qO3X5fKJZ2j8qO3X5fKJZ2j8qO3X5fKJZ2j8qO3X5fKJZ2j8qO3X5fKJZ2j8qO3X5fKJZ2j8qO3X5fKJZ2j8qO3X5fKJZ2j8qO3X5fKJZ2j8qO3X5fKJZ2j8qO3X5fKJZ2j8qO3X5fKJZ2j8qO3X5fKJZ2j8qO3X5fKJZ2j8qO3X5fKJZ2j8qO3X5fKJZ2j8qO3X5fKJZ2j8qO3X5fKJZ2j8qO3X5fKJZ2j8qO3X5fKJZ2j8qO3X5fKJZ2j8qO3X5fKJZ2j8qO3X5f"
    32  	testAccessTokenWindowsIssuer   = "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6Ik1uQ19WWmNBVGZNNXBPWWlKSE1iYTlnb0VLWSIsImtpZCI6Ik1uQ19WWmNBVGZNNXBPWWlKSE1iYTlnb0VLWSJ9.eyJhdWQiOiJodHRwczovL2dyYXBoLm1pY3Jvc29mdC5jb20iLCJpc3MiOiJodHRwczovL2xvZ2luLm1pY3Jvc29mdG9ubGluZS5jb20vNzJmOTg4YmYtODZmMS00MWFmLTkxYWItMmQ3Y2QwMTFkYjQ3L3YyLjAiLCJpYXQiOjE2OTQxMTkyMDAsIm5iZiI6MTY5NDExOTIwMCwiZXhwIjoxNjk0MTIzMTAwLCJhY3IiOiIxIiwiYWlvIjoiQVVRQXUvOFVBQUFBaWpUSVFaRXlBRzZNcTVDdTh6Z0NsSVJ2V2pmYnhYclkvSVJvSENCRnFjQlh5TTV3MmRJTGkzNm5ZU0tJeUpRZUIwaHlyTHpxNWpZN2oyRWkyeWs4QT09IiwiYW1yIjpbInB3ZCJdLCJhcHBpZCI6IjA0YjA3Nzk1LThkZGItNDYxYS1iYmVlLTAyZjllMWJmN2I0NiIsImFwcGlkYWNyIjoiMCIsImZhbWlseV9uYW1lIjoiRG9lIiwiZ2l2ZW5fbmFtZSI6IkpvaG4iLCJpcGFkZHIiOiIxOTguNTEuMTAwLjEiLCJuYW1lIjoiSm9obiBEb2UiLCJvaWQiOiIwMDAwMDAwMC0wMDAwLTAwMDAtYzAwMC0wMDAwMDAwMDAwMDAiLCJvbnByZW1fc2lkIjoiUy0xLTUtMjEtMTIzNDU2Nzg5MC0xMjM0NTY3ODktMTIzNDU2Nzg5LTEwMDAiLCJyaCI6IjAuQVZZQS00ajVjdkdHcjBHUnF5MTgwUUhiUjVWM3NaVGJuaHBLdXU0Qy1lRzc5Iiwic2NwIjoiRmlsZXMuUmVhZC5BbGwgRGlyZWN0b3J5LlJlYWQuQWxsIiwic3ViIjoidklHTWZxdERfZXdvaFZLZW9JLUpqYXM4cnhwQlA1OHVYR081YlNmbkhpTSIsInRlbmFudF9yZWdpb25fc2NvcGUiOiJOQSIsInRpZCI6IjcyZjk4OGJmLTg2ZjEtNDFhZi05MWFiLTJkN2NkMDExZGI0NyIsInVuaXF1ZV9uYW1lIjoiam9obmRvZUBleGFtcGxlLmNvbSIsInVwbiI6ImpvaG5kb2VAZXhhbXBsZS5jb20iLCJ1dGkiOiJVY3p4TUxxU2lVU3NNNkZ0eUloWEFRIiwidmVyIjoiMS4wIn0.signature"
    33  	testIDTokenMicrosoftIssuer     = "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6IjFMVE16YWtpaGlSbGFfOHoyQkVKVlhlV01xbyJ9.eyJ2ZXIiOiIyLjAiLCJpc3MiOiJodHRwczovL2xvZ2luLm1pY3Jvc29mdG9ubGluZS5jb20vOTEyMjA0MGQtNmM2Ny00YzViLWIxMTItMzZhMzA0YjY2ZGFkL3YyLjAiLCJzdWIiOiJBQUFBQUFBQUFBQUFBQUFBQUFBQUFJa3pxRlZyU2FTYUZIeTc4MmJidGFRIiwiYXVkIjoiNmNiMDQwMTgtYTNmNS00NmE3LWI5OTUtOTQwYzc4ZjVhZWYzIiwiZXhwIjoxNTM2MzYxNDExLCJpYXQiOjE1MzYyNzQ3MTEsIm5iZiI6MTUzNjI3NDcxMSwibmFtZSI6IkFiZSBMaW5jb2xuIiwicHJlZmVycmVkX3VzZXJuYW1lIjoiQWJlTGlAbWljcm9zb2Z0LmNvbSIsIm9pZCI6IjAwMDAwMDAwLTAwMDAtMDAwMC02NmYzLTMzMzJlY2E3ZWE4MSIsInRpZCI6IjkxMjIwNDBkLTZjNjctNGM1Yi1iMTEyLTM2YTMwNGI2NmRhZCIsIm5vbmNlIjoiMTIzNTIzIiwiYWlvIjoiRGYyVVZYTDFpeCFsTUNXTVNPSkJjRmF0emNHZnZGR2hqS3Y4cTVnMHg3MzJkUjVNQjVCaXN2R1FPN1lXQnlqZDhpUURMcSFlR2JJRGFreXA1bW5PcmNkcUhlWVNubHRlcFFtUnA2QUlaOGpZIn0.1AFWW-Ck5nROwSlltm7GzZvDwUkqvhSQpm55TQsmVo9Y59cLhRXpvB8n-55HCr9Z6G_31_UbeUkoz612I2j_Sm9FFShSDDjoaLQr54CreGIJvjtmS3EkK9a7SJBbcpL1MpUtlfygow39tFjY7EVNW9plWUvRrTgVk7lYLprvfzw-CIqw3gHC-T7IK_m_xkr08INERBtaecwhTeN4chPC4W3jdmw_lIxzC48YoQ0dB1L9-ImX98Egypfrlbm0IBL5spFzL6JDZIRRJOu8vecJvj1mq-IUhGt0MacxX8jdxYLP-KUu2d9MbNKpCKJuZ7p8gwTL5B7NlUdh_dmSviPWrw"
    34  	testIDTokenWindowsIssuer       = "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6IjdfWnVmMXR2a3dMeFlhSFMzcTZsVWpVWUlHdyIsImtpZCI6IjdfWnVmMXR2a3dMeFlhSFMzcTZsVWpVWUlHdyJ9.eyJhdWQiOiJiMTRhNzUwNS05NmU5LTQ5MjctOTFlOC0wNjAxZDBmYzljYWEiLCJpc3MiOiJodHRwczovL3N0cy53aW5kb3dzLm5ldC9mYTE1ZDY5Mi1lOWM3LTQ0NjAtYTc0My0yOWYyOTU2ZmQ0MjkvIiwiaWF0IjoxNTM2Mjc1MTI0LCJuYmYiOjE1MzYyNzUxMjQsImV4cCI6MTUzNjI3OTAyNCwiYWlvIjoiQVhRQWkvOElBQUFBcXhzdUIrUjREMnJGUXFPRVRPNFlkWGJMRDlrWjh4ZlhhZGVBTTBRMk5rTlQ1aXpmZzN1d2JXU1hodVNTajZVVDVoeTJENldxQXBCNWpLQTZaZ1o5ay9TVTI3dVY5Y2V0WGZMT3RwTnR0Z2s1RGNCdGsrTExzdHovSmcrZ1lSbXY5YlVVNFhscGhUYzZDODZKbWoxRkN3PT0iLCJhbXIiOlsicnNhIl0sImVtYWlsIjoiYWJlbGlAbWljcm9zb2Z0LmNvbSIsImZhbWlseV9uYW1lIjoiTGluY29sbiIsImdpdmVuX25hbWUiOiJBYmUiLCJpZHAiOiJodHRwczovL3N0cy53aW5kb3dzLm5ldC83MmY5ODhiZi04NmYxLTQxYWYtOTFhYi0yZDdjZDAxMWRiNDcvIiwiaXBhZGRyIjoiMTMxLjEwNy4yMjIuMjIiLCJuYW1lIjoiYWJlbGkiLCJub25jZSI6IjEyMzUyMyIsIm9pZCI6IjA1ODMzYjZiLWFhMWQtNDJkNC05ZWMwLTFiMmJiOTE5NDQzOCIsInJoIjoiSSIsInN1YiI6IjVfSjlyU3NzOC1qdnRfSWN1NnVlUk5MOHhYYjhMRjRGc2dfS29vQzJSSlEiLCJ0aWQiOiJmYTE1ZDY5Mi1lOWM3LTQ0NjAtYTc0My0yOWYyOTU2ZmQ0MjkiLCJ1bmlxdWVfbmFtZSI6IkFiZUxpQG1pY3Jvc29mdC5jb20iLCJ1dGkiOiJMeGVfNDZHcVRrT3BHU2ZUbG40RUFBIiwidmVyIjoiMS4wIn0.UJQrCA6qn2bXq57qzGX_-D3HcPHqBMOKDPx4su1yKRLNErVD8xkxJLNLVRdASHqEcpyDctbdHccu6DPpkq5f0ibcaQFhejQNcABidJCTz0Bb2AbdUCTqAzdt9pdgQvMBnVH1xk3SCM6d4BbT4BkLLj10ZLasX7vRknaSjE_C5DI7Fg4WrZPwOhII1dB0HEZ_qpNaYXEiy-o94UJ94zCr07GgrqMsfYQqFR7kn-mn68AjvLcgwSfZvyR_yIK75S_K37vC3QryQ7cNoafDe9upql_6pB2ybMVlgWPs_DmbJ8g0om-sPlwyn74Cc1tW3ze-Xptw_2uVdPgWyqfuWAfq6Q"
    35  )
    36  
    37  // TestDetector_truePositives tests for cases where we know the Detector
    38  // will find an Azure token.
    39  func TestDetector_truePositives(t *testing.T) {
    40  	engine, err := veles.NewDetectionEngine([]veles.Detector{azuretoken.NewDetector()})
    41  	if err != nil {
    42  		t.Fatal(err)
    43  	}
    44  
    45  	cases := []struct {
    46  		name  string
    47  		input string
    48  		want  []veles.Secret
    49  	}{
    50  		{
    51  			name:  "access token with login.microsoftonline.com issuer",
    52  			input: testAccessTokenMicrosoftIssuer,
    53  			want: []veles.Secret{
    54  				azuretoken.AzureAccessToken{Token: testAccessTokenMicrosoftIssuer},
    55  			},
    56  		},
    57  		{
    58  			name:  "id token with login.microsoftonline.com issuer",
    59  			input: testIDTokenMicrosoftIssuer,
    60  			want: []veles.Secret{
    61  				azuretoken.AzureIdentityToken{Token: testIDTokenMicrosoftIssuer},
    62  			},
    63  		},
    64  		{
    65  			name:  "access token with sts.windows.net issuer",
    66  			input: testAccessTokenWindowsIssuer,
    67  			want: []veles.Secret{
    68  				azuretoken.AzureAccessToken{Token: testAccessTokenWindowsIssuer},
    69  			},
    70  		},
    71  		{
    72  			name:  "id token with sts.windows.net issuer",
    73  			input: testIDTokenWindowsIssuer,
    74  			want: []veles.Secret{
    75  				azuretoken.AzureIdentityToken{Token: testIDTokenWindowsIssuer},
    76  			},
    77  		},
    78  		{
    79  			name:  "access token in middle of text",
    80  			input: "prefix " + testAccessTokenMicrosoftIssuer + " suffix",
    81  			want: []veles.Secret{
    82  				azuretoken.AzureAccessToken{Token: testAccessTokenMicrosoftIssuer},
    83  			},
    84  		},
    85  		{
    86  			name:  "multiple tokens",
    87  			input: testAccessTokenMicrosoftIssuer + " " + testIDTokenMicrosoftIssuer + " " + testIDTokenWindowsIssuer,
    88  			want: []veles.Secret{
    89  				azuretoken.AzureAccessToken{Token: testAccessTokenMicrosoftIssuer},
    90  				azuretoken.AzureIdentityToken{Token: testIDTokenMicrosoftIssuer},
    91  				azuretoken.AzureIdentityToken{Token: testIDTokenWindowsIssuer},
    92  			},
    93  		},
    94  		{
    95  			name:  "access token in long buffer",
    96  			input: strings.Repeat("a", 64*veles.KiB) + " " + testAccessTokenMicrosoftIssuer + " " + strings.Repeat("a", 64*veles.KiB),
    97  			want: []veles.Secret{
    98  				azuretoken.AzureAccessToken{Token: testAccessTokenMicrosoftIssuer},
    99  			},
   100  		},
   101  	}
   102  	for _, tc := range cases {
   103  		t.Run(tc.name, func(t *testing.T) {
   104  			got, err := engine.Detect(t.Context(), strings.NewReader(tc.input))
   105  			if err != nil {
   106  				t.Errorf("Detect() error: %v, want nil", err)
   107  			}
   108  			if diff := cmp.Diff(tc.want, got, cmpopts.EquateEmpty()); diff != "" {
   109  				t.Errorf("Detect() diff (-want +got):\n%s", diff)
   110  			}
   111  		})
   112  	}
   113  }
   114  
   115  // TestDetector_trueNegatives tests for cases where we know the Detector
   116  // will not find an Azure token.
   117  func TestDetector_trueNegatives(t *testing.T) {
   118  	engine, err := veles.NewDetectionEngine([]veles.Detector{azuretoken.NewDetector()})
   119  	if err != nil {
   120  		t.Fatal(err)
   121  	}
   122  
   123  	cases := []struct {
   124  		name  string
   125  		input string
   126  		want  []veles.Secret
   127  	}{{
   128  		name:  "empty input",
   129  		input: "",
   130  	}, {
   131  		name:  "invalid JWT - only header",
   132  		input: "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9",
   133  	}, {
   134  		name:  "invalid JWT - malformed payload",
   135  		input: "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.invalid-payload.signature",
   136  	}, {
   137  		name:  "JWT without Azure issuer",
   138  		input: "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJpc3MiOiJodHRwczovL2V4YW1wbGUuY29tIiwic3ViIjoiMTIzNCIsIm5hbWUiOiJKb2huIERvZSIsImlhdCI6MTUxNjIzOTAyMn0.signature",
   139  	}, {
   140  		name:  "invalid JWT",
   141  		input: "not.a.jwt",
   142  	}, {
   143  		name:  "token exceeding max length",
   144  		input: strings.Repeat("a", 8193),
   145  	}}
   146  	for _, tc := range cases {
   147  		t.Run(tc.name, func(t *testing.T) {
   148  			got, err := engine.Detect(t.Context(), strings.NewReader(tc.input))
   149  			if err != nil {
   150  				t.Errorf("Detect() error: %v, want nil", err)
   151  			}
   152  			if diff := cmp.Diff(tc.want, got, cmpopts.EquateEmpty()); diff != "" {
   153  				t.Errorf("Detect() diff (-want +got):\n%s", diff)
   154  			}
   155  		})
   156  	}
   157  }