github.com/google/osv-scalibr@v0.4.1/veles/secrets/hcp/validator.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
    16  
    17  import (
    18  	"net/http"
    19  	"net/url"
    20  	"strings"
    21  
    22  	sv "github.com/google/osv-scalibr/veles/secrets/common/simplevalidate"
    23  )
    24  
    25  // Defaults derived from public HCP API docs.
    26  const (
    27  	defaultTokenURL = "https://auth.idp.hashicorp.com/oauth2/token"
    28  	// Use the Cloud API host for identity introspection.
    29  	defaultAPIBase         = "https://api.cloud.hashicorp.com"
    30  	callerIdentityEndpoint = "/iam/2019-12-10/caller-identity"
    31  )
    32  
    33  type cc = ClientCredentials
    34  
    35  // NewClientCredentialsValidator creates a new HCP client credential pair validator.
    36  // It validates ClientCredentials by attempting a client_credentials OAuth2
    37  // token exchange against the configured token endpoint. A 200 response indicates
    38  // valid credentials; 400/401 indicates invalid; other responses are treated as
    39  // validation failures.
    40  func NewClientCredentialsValidator(opts ...ClientCredentialsOption) *sv.Validator[cc] {
    41  	v := &sv.Validator[cc]{
    42  		Endpoint:   defaultTokenURL,
    43  		HTTPMethod: http.MethodPost,
    44  		HTTPHeaders: func(_ cc) map[string]string {
    45  			return map[string]string{"Content-Type": "application/x-www-form-urlencoded"}
    46  		},
    47  		Body: func(s cc) (string, error) {
    48  			form := url.Values{}
    49  			form.Set("grant_type", "client_credentials")
    50  			form.Set("client_id", s.ClientID)
    51  			form.Set("client_secret", s.ClientSecret)
    52  			return form.Encode(), nil
    53  		},
    54  		ValidResponseCodes:   []int{http.StatusOK},
    55  		InvalidResponseCodes: []int{http.StatusBadRequest, http.StatusUnauthorized},
    56  	}
    57  	for _, opt := range opts {
    58  		opt(v)
    59  	}
    60  	return v
    61  }
    62  
    63  // ClientCredentialsOption configures a ClientCredentialsValidator.
    64  type ClientCredentialsOption func(*sv.Validator[cc])
    65  
    66  // WithTokenURL overrides the token endpoint URL.
    67  func WithTokenURL(u string) ClientCredentialsOption {
    68  	return func(v *sv.Validator[cc]) { v.Endpoint = u }
    69  }
    70  
    71  type at = AccessToken
    72  
    73  // NewAccessTokenValidator creates a new validator for HCP access tokens.
    74  // It validates an AccessToken by sending a GET request to the HCP
    75  // caller-identity endpoint with the token as a Bearer credential.
    76  //
    77  // Documentation: https://developer.hashicorp.com/hcp/api-docs/identity#IamService_GetCallerIdentity
    78  //
    79  // - 200: token is valid (identity returned)
    80  // - 401: token is invalid
    81  // - any other response: treated as ValidationFailed
    82  func NewAccessTokenValidator(opts ...AccessTokenOption) *sv.Validator[at] {
    83  	v := &sv.Validator[at]{
    84  		Endpoint:   defaultAPIBase + callerIdentityEndpoint,
    85  		HTTPMethod: http.MethodGet,
    86  		HTTPHeaders: func(s at) map[string]string {
    87  			return map[string]string{"Authorization": "Bearer " + s.Token}
    88  		},
    89  		ValidResponseCodes:   []int{http.StatusOK},
    90  		InvalidResponseCodes: []int{http.StatusUnauthorized},
    91  	}
    92  	for _, opt := range opts {
    93  		opt(v)
    94  	}
    95  	return v
    96  }
    97  
    98  // AccessTokenOption configures an AccessTokenValidator.
    99  type AccessTokenOption func(*sv.Validator[at])
   100  
   101  // WithAPIBase overrides the base API URL (default: https://api.cloud.hashicorp.com).
   102  func WithAPIBase(base string) AccessTokenOption {
   103  	return func(v *sv.Validator[at]) {
   104  		v.Endpoint = strings.TrimRight(base, "/") + callerIdentityEndpoint
   105  	}
   106  }