github.com/google/osv-scalibr@v0.4.1/veles/secrets/common/simplevalidate/simplevalidate.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 simplevalidate contains a Validator for secrets that can be validated with 16 // simple HTTP queries and result code comparison. 17 package simplevalidate 18 19 import ( 20 "context" 21 "errors" 22 "fmt" 23 "io" 24 "net/http" 25 "slices" 26 "strings" 27 28 "github.com/google/osv-scalibr/veles" 29 ) 30 31 // Validator validates a secret of a given type by sending HTTP requests and 32 // checking the response status code. 33 // It implements veles.Validator. 34 type Validator[S veles.Secret] struct { 35 // The API endpoint to query. 36 Endpoint string 37 // Function that constructs the endpoint for a given secret. 38 // Exactly one of Endpoint or EndpointFunc must be provided. 39 // If EndpointFunc returns an error, Validate returns ValidationFailed and the error. 40 EndpointFunc func(S) (string, error) 41 // The HTTP request method to send (e.g. http.MethodGet, http.MethodPost) 42 HTTPMethod string 43 // HTTP headers to set in the query based on the secret. 44 HTTPHeaders func(S) map[string]string 45 // The body to set in the query based on the secret. 46 // If Body returns an error, Validate returns ValidationFailed and the error. 47 Body func(S) (string, error) 48 // Status codes that should result in a "ValidationValid" validation result. 49 ValidResponseCodes []int 50 // Status codes that should result in a "ValidationInvalid" validation result. 51 InvalidResponseCodes []int 52 // Additional custom validation logic to perform on the response body. Will run if none of the 53 // status codes from ValidResponseCodes and InvalidResponseCodes have been found. 54 StatusFromResponseBody func(body io.Reader) (veles.ValidationStatus, error) 55 // The HTTP client to use for the network queries. Uses http.DefaultClient if nil. 56 HTTPC *http.Client 57 } 58 59 // Validate validates a secret with a simple HTTP request. 60 func (v *Validator[S]) Validate(ctx context.Context, secret S) (veles.ValidationStatus, error) { 61 if v.HTTPC == nil { 62 v.HTTPC = http.DefaultClient 63 } 64 65 if (v.Endpoint == "" && v.EndpointFunc == nil) || (v.Endpoint != "" && v.EndpointFunc != nil) { 66 return veles.ValidationFailed, errors.New("exactly one of Endpoint or EndpointFunc must be specified") 67 } 68 69 endpoint := v.Endpoint 70 if v.EndpointFunc != nil { 71 endpointURL, err := v.EndpointFunc(secret) 72 if err != nil { 73 return veles.ValidationFailed, err 74 } 75 endpoint = endpointURL 76 } 77 78 var reqBodyReader io.Reader 79 if v.Body != nil { 80 reqBody, err := v.Body(secret) 81 if err != nil { 82 return veles.ValidationFailed, err 83 } 84 if len(reqBody) > 0 { 85 reqBodyReader = strings.NewReader(reqBody) 86 } 87 } 88 req, err := http.NewRequestWithContext(ctx, v.HTTPMethod, endpoint, reqBodyReader) 89 if err != nil { 90 return veles.ValidationFailed, fmt.Errorf("http.NewRequestWithContext: %w", err) 91 } 92 if v.HTTPHeaders != nil { 93 for key, val := range v.HTTPHeaders(secret) { 94 req.Header.Set(key, val) 95 } 96 } 97 res, err := v.HTTPC.Do(req) 98 if err != nil { 99 return veles.ValidationFailed, fmt.Errorf("HTTP %s failed: %w", v.HTTPMethod, err) 100 } 101 defer res.Body.Close() 102 103 if slices.Contains(v.ValidResponseCodes, res.StatusCode) { 104 return veles.ValidationValid, nil 105 } 106 if slices.Contains(v.InvalidResponseCodes, res.StatusCode) { 107 return veles.ValidationInvalid, nil 108 } 109 110 if err != nil { 111 return veles.ValidationFailed, fmt.Errorf("failed to read response body: %w", err) 112 } 113 if v.StatusFromResponseBody != nil { 114 return v.StatusFromResponseBody(res.Body) 115 } 116 117 return veles.ValidationFailed, fmt.Errorf("unexpected HTTP status: %d", res.StatusCode) 118 }