urlcredsource.go raw

   1  // Copyright 2020 The Go Authors. All rights reserved.
   2  // Use of this source code is governed by a BSD-style
   3  // license that can be found in the LICENSE file.
   4  
   5  package externalaccount
   6  
   7  import (
   8  	"context"
   9  	"encoding/json"
  10  	"errors"
  11  	"fmt"
  12  	"io"
  13  	"net/http"
  14  
  15  	"golang.org/x/oauth2"
  16  )
  17  
  18  type urlCredentialSource struct {
  19  	URL     string
  20  	Headers map[string]string
  21  	Format  Format
  22  	ctx     context.Context
  23  }
  24  
  25  func (cs urlCredentialSource) credentialSourceType() string {
  26  	return "url"
  27  }
  28  
  29  func (cs urlCredentialSource) subjectToken() (string, error) {
  30  	client := oauth2.NewClient(cs.ctx, nil)
  31  	req, err := http.NewRequest("GET", cs.URL, nil)
  32  	if err != nil {
  33  		return "", fmt.Errorf("oauth2/google/externalaccount: HTTP request for URL-sourced credential failed: %v", err)
  34  	}
  35  	req = req.WithContext(cs.ctx)
  36  
  37  	for key, val := range cs.Headers {
  38  		req.Header.Add(key, val)
  39  	}
  40  	resp, err := client.Do(req)
  41  	if err != nil {
  42  		return "", fmt.Errorf("oauth2/google/externalaccount: invalid response when retrieving subject token: %v", err)
  43  	}
  44  	defer resp.Body.Close()
  45  
  46  	respBody, err := io.ReadAll(io.LimitReader(resp.Body, 1<<20))
  47  	if err != nil {
  48  		return "", fmt.Errorf("oauth2/google/externalaccount: invalid body in subject token URL query: %v", err)
  49  	}
  50  	if c := resp.StatusCode; c < 200 || c > 299 {
  51  		return "", fmt.Errorf("oauth2/google/externalaccount: status code %d: %s", c, respBody)
  52  	}
  53  
  54  	switch cs.Format.Type {
  55  	case "json":
  56  		jsonData := make(map[string]any)
  57  		err = json.Unmarshal(respBody, &jsonData)
  58  		if err != nil {
  59  			return "", fmt.Errorf("oauth2/google/externalaccount: failed to unmarshal subject token file: %v", err)
  60  		}
  61  		val, ok := jsonData[cs.Format.SubjectTokenFieldName]
  62  		if !ok {
  63  			return "", errors.New("oauth2/google/externalaccount: provided subject_token_field_name not found in credentials")
  64  		}
  65  		token, ok := val.(string)
  66  		if !ok {
  67  			return "", errors.New("oauth2/google/externalaccount: improperly formatted subject token")
  68  		}
  69  		return token, nil
  70  	case "text":
  71  		return string(respBody), nil
  72  	case "":
  73  		return string(respBody), nil
  74  	default:
  75  		return "", errors.New("oauth2/google/externalaccount: invalid credential_source file format type")
  76  	}
  77  
  78  }
  79