blob: db3d0498875f08ec46abdbdafea14a03f0814aa8 [file] [edit]
// Copyright 2023 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Package idtoken provides functionality for generating and validating ID
// tokens, with configurable options for audience, custom claims, and token
// formats.
//
// For more information on ID tokens, see
// https://cloud.google.com/docs/authentication/token-types#id.
package idtoken
import (
"errors"
"log/slog"
"net/http"
"os"
"cloud.google.com/go/auth"
"cloud.google.com/go/auth/credentials"
"cloud.google.com/go/auth/internal"
"cloud.google.com/go/auth/internal/credsfile"
"cloud.google.com/go/compute/metadata"
)
// ComputeTokenFormat dictates the the token format when requesting an ID token
// from the compute metadata service.
type ComputeTokenFormat int
const (
// ComputeTokenFormatDefault means the same as [ComputeTokenFormatFull].
ComputeTokenFormatDefault ComputeTokenFormat = iota
// ComputeTokenFormatStandard mean only standard JWT fields will be included
// in the token.
ComputeTokenFormatStandard
// ComputeTokenFormatFull means the token will include claims about the
// virtual machine instance and its project.
ComputeTokenFormatFull
// ComputeTokenFormatFullWithLicense means the same as
// [ComputeTokenFormatFull] with the addition of claims about licenses
// associated with the instance.
ComputeTokenFormatFullWithLicense
)
var (
defaultScopes = []string{
"https://iamcredentials.googleapis.com/",
"https://www.googleapis.com/auth/cloud-platform",
}
errMissingOpts = errors.New("idtoken: opts must be provided")
errMissingAudience = errors.New("idtoken: Audience must be provided")
errBothFileAndJSON = errors.New("idtoken: CredentialsFile and CredentialsJSON must not both be provided")
)
// Options for the configuration of creation of an ID token with
// [NewCredentials].
type Options struct {
// Audience is the `aud` field for the token, such as an API endpoint the
// token will grant access to. Required.
Audience string
// ComputeTokenFormat dictates the the token format when requesting an ID
// token from the compute metadata service. Optional.
ComputeTokenFormat ComputeTokenFormat
// CustomClaims specifies private non-standard claims for an ID token.
// Optional.
CustomClaims map[string]interface{}
// CredentialsFile sources a JSON credential file from the provided
// filepath. If provided, do not provide CredentialsJSON. Optional.
//
// Deprecated: This field is deprecated because of a potential security risk.
// It does not validate the credential configuration. The security risk occurs
// when a credential configuration is accepted from a source that is not
// under your control and used without validation on your side.
//
// If you know that you will be loading credential configurations of a
// specific type, it is recommended to use a credential-type-specific
// NewCredentialsFromFile method. This will ensure that an unexpected
// credential type with potential for malicious intent is not loaded
// unintentionally. You might still have to do validation for certain
// credential types. Please follow the recommendation for that method. For
// example, if you want to load only service accounts, you can use
//
// creds, err := idtoken.NewCredentialsFromFile(ctx, credentials.ServiceAccount, filename, opts)
//
// If you are loading your credential configuration from an untrusted source
// and have not mitigated the risks (e.g. by validating the configuration
// yourself), make these changes as soon as possible to prevent security
// risks to your environment.
//
// Regardless of the method used, it is always your responsibility to
// validate configurations received from external sources.
//
// For more details see:
// https://cloud.google.com/docs/authentication/external/externally-sourced-credentials
CredentialsFile string
// CredentialsJSON sources a JSON credential file from the provided bytes.
// If provided, do not provide CredentialsJSON. Optional.
//
// Deprecated: This field is deprecated because of a potential security risk.
// It does not validate the credential configuration. The security risk occurs
// when a credential configuration is accepted from a source that is not
// under your control and used without validation on your side.
//
// If you know that you will be loading credential configurations of a
// specific type, it is recommended to use a credential-type-specific
// NewCredentialsFromJSON method. This will ensure that an unexpected
// credential type with potential for malicious intent is not loaded
// unintentionally. You might still have to do validation for certain
// credential types. Please follow the recommendation for that method. For
// example, if you want to load only service accounts, you can use
//
// creds, err := idtoken.NewCredentialsFromJSON(ctx, credentials.ServiceAccount, json, opts)
//
// If you are loading your credential configuration from an untrusted source
// and have not mitigated the risks (e.g. by validating the configuration
// yourself), make these changes as soon as possible to prevent security
// risks to your environment.
//
// Regardless of the method used, it is always your responsibility to
// validate configurations received from external sources.
//
// For more details see:
// https://cloud.google.com/docs/authentication/external/externally-sourced-credentials
CredentialsJSON []byte
// Client configures the underlying client used to make network requests
// when fetching tokens. If provided this should be a fully-authenticated
// client. Optional.
Client *http.Client
// UniverseDomain is the default service domain for a given Cloud universe.
// The default value is "googleapis.com". This is the universe domain
// configured for the client, which will be compared to the universe domain
// that is separately configured for the credentials. Optional.
UniverseDomain string
// Logger is used for debug logging. If provided, logging will be enabled
// at the loggers configured level. By default logging is disabled unless
// enabled by setting GOOGLE_SDK_GO_LOGGING_LEVEL in which case a default
// logger will be used. Optional.
Logger *slog.Logger
}
func (o *Options) client() *http.Client {
if o == nil || o.Client == nil {
return internal.DefaultClient()
}
return o.Client
}
func (o *Options) validate() error {
if o == nil {
return errMissingOpts
}
if o.Audience == "" {
return errMissingAudience
}
if o.CredentialsFile != "" && len(o.CredentialsJSON) > 0 {
return errBothFileAndJSON
}
return nil
}
// NewCredentials creates a [cloud.google.com/go/auth.Credentials] that returns
// ID tokens configured by the opts provided. The parameter opts.Audience must
// not be empty. If both opts.CredentialsFile and opts.CredentialsJSON are
// empty, an attempt will be made to detect credentials from the environment
// (see [cloud.google.com/go/auth/credentials.DetectDefault]). Only service
// account, impersonated service account, external account and Compute
// credentials are supported. Note: If the provided external account
// configuration (e.g., Workload Identity Federation) is configured to
// impersonate a service account, the returned credentials will yield ID tokens
// representing that target service account, rather than the external workload
// principal that initiated the request.
func NewCredentials(opts *Options) (*auth.Credentials, error) {
if err := opts.validate(); err != nil {
return nil, err
}
b := opts.jsonBytes()
if b == nil && metadata.OnGCE() {
return computeCredentials(opts)
}
creds, err := credentials.DetectDefault(&credentials.DetectOptions{
Scopes: defaultScopes,
CredentialsJSON: b,
Client: opts.client(),
UseSelfSignedJWT: true,
})
if err != nil {
return nil, err
}
return credsFromDefault(creds, opts)
}
func (o *Options) jsonBytes() []byte {
if len(o.CredentialsJSON) > 0 {
return o.CredentialsJSON
}
var fnOverride string
if o != nil {
fnOverride = o.CredentialsFile
}
filename := credsfile.GetFileNameFromEnv(fnOverride)
if filename != "" {
b, _ := os.ReadFile(filename)
return b
}
return nil
}
// NewCredentialsFromJSON creates a [cloud.google.com/go/auth.Credentials] that
// returns ID tokens from the provided JSON bytes. The credType argument
// specifies the expected credential type. If the JSON does not match the
// expected type, an error is returned.
//
// This method is safe to use with untrusted credential configurations if the
// expected credType is NOT [credentials.ExternalAccount] or
// [credentials.ImpersonatedServiceAccount].
//
// **IMPORTANT**: If you use [credentials.ExternalAccount] or
// [credentials.ImpersonatedServiceAccount], you must validate the credential
// configuration before providing it to this method. Providing an unvalidated
// credential configuration to Google APIs can compromise the security of your
// systems and data. For more information, refer to [Validate credential
// configurations from external sources](https://cloud.google.com/docs/authentication/external/externally-sourced-credentials).
func NewCredentialsFromJSON(credType credentials.CredType, b []byte, opts *Options) (*auth.Credentials, error) {
if err := opts.validate(); err != nil {
return nil, err
}
// Use credentials.NewCredentialsFromJSON to validate type and create base credentials.
creds, err := credentials.NewCredentialsFromJSON(credType, b, &credentials.DetectOptions{
Client: opts.Client,
Logger: opts.Logger,
UniverseDomain: opts.UniverseDomain,
})
if err != nil {
return nil, err
}
return credsFromDefault(creds, opts)
}
// NewCredentialsFromFile creates a [cloud.google.com/go/auth.Credentials] that
// returns ID tokens from the provided file. The credType argument specifies the
// expected credential type. If the file content does not match the expected
// type, an error is returned.
//
// This method is safe to use with untrusted credential configurations if the
// expected credType is NOT [credentials.ExternalAccount] or
// [credentials.ImpersonatedServiceAccount].
//
// **IMPORTANT**: If you use [credentials.ExternalAccount] or
// [credentials.ImpersonatedServiceAccount], you must validate the credential
// configuration before providing it to this method. Providing an unvalidated
// credential configuration to Google APIs can compromise the security of your
// systems and data. For more information, refer to [Validate credential
// configurations from external sources](https://cloud.google.com/docs/authentication/external/externally-sourced-credentials).
func NewCredentialsFromFile(credType credentials.CredType, filename string, opts *Options) (*auth.Credentials, error) {
b, err := os.ReadFile(filename)
if err != nil {
return nil, err
}
return NewCredentialsFromJSON(credType, b, opts)
}