url_provider.go raw

   1  // Copyright 2023 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 externalaccount
  16  
  17  import (
  18  	"context"
  19  	"encoding/json"
  20  	"errors"
  21  	"fmt"
  22  	"log/slog"
  23  	"net/http"
  24  
  25  	"cloud.google.com/go/auth/internal"
  26  	"cloud.google.com/go/auth/internal/credsfile"
  27  	"github.com/googleapis/gax-go/v2/internallog"
  28  )
  29  
  30  const (
  31  	fileTypeText             = "text"
  32  	fileTypeJSON             = "json"
  33  	urlProviderType          = "url"
  34  	programmaticProviderType = "programmatic"
  35  	x509ProviderType         = "x509"
  36  )
  37  
  38  type urlSubjectProvider struct {
  39  	URL     string
  40  	Headers map[string]string
  41  	Format  *credsfile.Format
  42  	Client  *http.Client
  43  	Logger  *slog.Logger
  44  }
  45  
  46  func (sp *urlSubjectProvider) subjectToken(ctx context.Context) (string, error) {
  47  	req, err := http.NewRequestWithContext(ctx, "GET", sp.URL, nil)
  48  	if err != nil {
  49  		return "", fmt.Errorf("credentials: HTTP request for URL-sourced credential failed: %w", err)
  50  	}
  51  
  52  	for key, val := range sp.Headers {
  53  		req.Header.Add(key, val)
  54  	}
  55  	sp.Logger.DebugContext(ctx, "url subject token request", "request", internallog.HTTPRequest(req, nil))
  56  	resp, body, err := internal.DoRequest(sp.Client, req)
  57  	if err != nil {
  58  		return "", fmt.Errorf("credentials: invalid response when retrieving subject token: %w", err)
  59  	}
  60  	sp.Logger.DebugContext(ctx, "url subject token response", "response", internallog.HTTPResponse(resp, body))
  61  	if c := resp.StatusCode; c < http.StatusOK || c >= http.StatusMultipleChoices {
  62  		return "", fmt.Errorf("credentials: status code %d: %s", c, body)
  63  	}
  64  
  65  	if sp.Format == nil {
  66  		return string(body), nil
  67  	}
  68  	switch sp.Format.Type {
  69  	case "json":
  70  		jsonData := make(map[string]interface{})
  71  		err = json.Unmarshal(body, &jsonData)
  72  		if err != nil {
  73  			return "", fmt.Errorf("credentials: failed to unmarshal subject token file: %w", err)
  74  		}
  75  		val, ok := jsonData[sp.Format.SubjectTokenFieldName]
  76  		if !ok {
  77  			return "", errors.New("credentials: provided subject_token_field_name not found in credentials")
  78  		}
  79  		token, ok := val.(string)
  80  		if !ok {
  81  			return "", errors.New("credentials: improperly formatted subject token")
  82  		}
  83  		return token, nil
  84  	case fileTypeText:
  85  		return string(body), nil
  86  	default:
  87  		return "", errors.New("credentials: invalid credential_source file format type: " + sp.Format.Type)
  88  	}
  89  }
  90  
  91  func (sp *urlSubjectProvider) providerType() string {
  92  	return urlProviderType
  93  }
  94