* feat: kubesphere 4.0 Signed-off-by: ci-bot <ci-bot@kubesphere.io> * feat: kubesphere 4.0 Signed-off-by: ci-bot <ci-bot@kubesphere.io> --------- Signed-off-by: ci-bot <ci-bot@kubesphere.io> Co-authored-by: ks-ci-bot <ks-ci-bot@example.com> Co-authored-by: joyceliu <joyceliu@yunify.com>
174 lines
4.5 KiB
Go
174 lines
4.5 KiB
Go
// Copyright 2020 The OPA Authors. All rights reserved.
|
|
// Use of this source code is governed by an Apache2
|
|
// license that can be found in the LICENSE file.
|
|
|
|
package rest
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
var (
|
|
defaultGCPMetadataEndpoint = "http://metadata.google.internal"
|
|
defaultAccessTokenPath = "/computeMetadata/v1/instance/service-accounts/default/token"
|
|
defaultIdentityTokenPath = "/computeMetadata/v1/instance/service-accounts/default/identity"
|
|
)
|
|
|
|
// AccessToken holds a GCP access token.
|
|
type AccessToken struct {
|
|
AccessToken string `json:"access_token"`
|
|
ExpiresIn int64 `json:"expires_in"`
|
|
TokenType string `json:"token_type"`
|
|
}
|
|
|
|
type gcpMetadataError struct {
|
|
err error
|
|
endpoint string
|
|
statusCode int
|
|
}
|
|
|
|
func (e *gcpMetadataError) Error() string {
|
|
return fmt.Sprintf("error retrieving gcp ID token from %s %d: %v", e.endpoint, e.statusCode, e.err)
|
|
}
|
|
|
|
func (e *gcpMetadataError) Unwrap() error { return e.err }
|
|
|
|
var (
|
|
errGCPMetadataNotFound = errors.New("not found")
|
|
errGCPMetadataInvalidRequest = errors.New("invalid request")
|
|
errGCPMetadataUnexpected = errors.New("unexpected error")
|
|
)
|
|
|
|
// gcpMetadataAuthPlugin represents authentication via GCP metadata service.
|
|
type gcpMetadataAuthPlugin struct {
|
|
AccessTokenPath string `json:"access_token_path"`
|
|
Audience string `json:"audience"`
|
|
Endpoint string `json:"endpoint"`
|
|
IdentityTokenPath string `json:"identity_token_path"`
|
|
Scopes []string `json:"scopes"`
|
|
}
|
|
|
|
func (ap *gcpMetadataAuthPlugin) NewClient(c Config) (*http.Client, error) {
|
|
if ap.Audience == "" && len(ap.Scopes) == 0 {
|
|
return nil, errors.New("audience or scopes is required when gcp metadata is enabled")
|
|
}
|
|
|
|
if ap.Audience != "" && len(ap.Scopes) > 0 {
|
|
return nil, errors.New("either audience or scopes can be set, not both, when gcp metadata is enabled")
|
|
}
|
|
|
|
if ap.Endpoint == "" {
|
|
ap.Endpoint = defaultGCPMetadataEndpoint
|
|
}
|
|
|
|
if ap.AccessTokenPath == "" {
|
|
ap.AccessTokenPath = defaultAccessTokenPath
|
|
}
|
|
|
|
if ap.IdentityTokenPath == "" {
|
|
ap.IdentityTokenPath = defaultIdentityTokenPath
|
|
}
|
|
|
|
t, err := DefaultTLSConfig(c)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return DefaultRoundTripperClient(t, *c.ResponseHeaderTimeoutSeconds), nil
|
|
}
|
|
|
|
func (ap *gcpMetadataAuthPlugin) Prepare(req *http.Request) error {
|
|
var err error
|
|
var token string
|
|
|
|
if ap.Audience != "" {
|
|
token, err = identityTokenFromMetadataService(ap.Endpoint, ap.IdentityTokenPath, ap.Audience)
|
|
if err != nil {
|
|
return fmt.Errorf("error retrieving identity token from gcp metadata service: %w", err)
|
|
}
|
|
}
|
|
|
|
if len(ap.Scopes) != 0 {
|
|
token, err = accessTokenFromMetadataService(ap.Endpoint, ap.AccessTokenPath, ap.Scopes)
|
|
if err != nil {
|
|
return fmt.Errorf("error retrieving access token from gcp metadata service: %w", err)
|
|
}
|
|
}
|
|
|
|
req.Header.Add("Authorization", fmt.Sprintf("Bearer %v", token))
|
|
return nil
|
|
}
|
|
|
|
// accessTokenFromMetadataService returns an access token based on the scopes.
|
|
func accessTokenFromMetadataService(endpoint, path string, scopes []string) (string, error) {
|
|
s := strings.Join(scopes, ",")
|
|
|
|
e := fmt.Sprintf("%s%s?scopes=%s", endpoint, path, s)
|
|
|
|
data, err := gcpMetadataServiceRequest(e)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
var accessToken AccessToken
|
|
err = json.Unmarshal(data, &accessToken)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
return accessToken.AccessToken, nil
|
|
}
|
|
|
|
// identityTokenFromMetadataService returns an identity token based on the audience.
|
|
func identityTokenFromMetadataService(endpoint, path, audience string) (string, error) {
|
|
e := fmt.Sprintf("%s%s?audience=%s", endpoint, path, audience)
|
|
|
|
data, err := gcpMetadataServiceRequest(e)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return string(data), nil
|
|
}
|
|
|
|
func gcpMetadataServiceRequest(endpoint string) ([]byte, error) {
|
|
request, err := http.NewRequest("GET", endpoint, nil)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
request.Header.Add("Metadata-Flavor", "Google")
|
|
|
|
timeout := time.Duration(5) * time.Second
|
|
httpClient := http.Client{Timeout: timeout}
|
|
|
|
response, err := httpClient.Do(request)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer response.Body.Close()
|
|
|
|
switch s := response.StatusCode; s {
|
|
case 200:
|
|
break
|
|
case 400:
|
|
return nil, &gcpMetadataError{errGCPMetadataInvalidRequest, endpoint, s}
|
|
case 404:
|
|
return nil, &gcpMetadataError{errGCPMetadataNotFound, endpoint, s}
|
|
default:
|
|
return nil, &gcpMetadataError{errGCPMetadataUnexpected, endpoint, s}
|
|
}
|
|
|
|
data, err := io.ReadAll(response.Body)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return data, nil
|
|
}
|