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 }