ugcp

package
v0.0.0-...-0569425 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Nov 1, 2025 License: Apache-2.0 Imports: 19 Imported by: 1

README

Minimal GCP integration

Package micro GCP includes dependency free code to get tokens and interact with GCP.

This includes a GCP (minimally compatible) MDS server for testing and using apps expecting GCP MDS.

STS

This is one of the more complicated pieces, getting google access tokens based on a GKE token.

  1. STS authentication starts with a GKE JWT with 'PROJECT.svc.id.goog' scope. You can mount it, or get it in exchange for the default token.
  2. 'securetoken' API can exchange the token with a 'federated access token' This token can be used by some services, including in IAM policy bindings. In particular, it can be used with "workloadIdentiyUser" permission, to get tokens for another GSA.
  3. Get token for a GSA, using https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/%s - either generateAccessToken or generateIdToken

It requires 3 round-trips, but can be cached and pre-fetched.

The most important is the federated exchange, which requires a token with PROJECT_ID.svc.id.goog audience issued by a GKE cluster. Other IDP providers can be used as well - with an associated federation config.

The federated token is a google access token associated with the 'foreign' K8S identity which can be used directly by some services, or exchanged with a regular GSA that allows delegation.



$ kubectl -n validation-temp-ns -c istio-proxy exec sleep-6758c4cb78-2gtpp -- \
  cat /var/run/secrets/tokens/istio-token >  istio-token

$ curl -v https://securetoken.googleapis.com/v1/identitybindingtoken -HContent-Type:application/json -d @exch.json


{"audience":"identitynamespace:costin-istio.svc.id.goog:https://container.googleapis.com/v1/projects/costin-istio/locations/us-west1-c/clusters/istio-test",
"subjectToken":"$(cat ISTIO_TOKEN)",
"grantType":"urn:ietf:params:oauth:grant-type:token-exchange",
"requestedTokenType":"urn:ietf:params:oauth:token-type:access_token",
"scope":"https://www.googleapis.com/auth/cloud-platform",
"subjectTokenType":"urn:ietf:params:oauth:token-type:jwt"}


Response:
{"access_token":"ya29.d.Ks...",
"issued_token_type":"urn:ietf:params:oauth:token-type:access_token",
"token_type":"Bearer",
"expires_in":3600}



Documentation

Index

Constants

This section is empty.

Variables

View Source
var (
	GCP_SCOPE = "https://www.googleapis.com/auth/cloud-platform"
)

Functions

func GetSecret

func GetSecret(ctx context.Context, token, p, n, v string) ([]byte, error)

Get a GCP secrets - used for bootstraping the credentials and provisioning.

Example for creating a secret:

gcloud secrets create ca \
  --data-file <PATH-TO-SECRET-FILE> \
  --replication-policy automatic \
  --project $PROJECT_ID \
  --format json \
  --quiet

For MCP/ASM, grant service-$PROJECT_NUMBER@gcp-sa-meshdataplane.iam.gserviceaccount.com secret manager viewer.

Types

type Cluster

type Cluster struct {
	Name string

	// nodeConfig
	MasterAuth struct {
		ClusterCaCertificate []byte
	}
	Location string

	Endpoint string

	ResourceLabels map[string]string

	// loggingService, monitoringService
	//Network string "default"
	//Subnetwork string
	ClusterIpv4Cidr  string
	ServicesIpv4Cidr string

	// For regional clusters - each zone.
	// For zonal - one entry, equal with location
	Locations []string
	// ipAllocationPolicy - clusterIpv4Cider, serviceIpv4Cider...
	// masterAuthorizedNetworksConfig
	// maintenancePolicy
	// autoscaling
	NetworkConfig struct {
		// projects/NAME/global/networks/default
		Network    string
		Subnetwork string
	}
}

type Clusters

type Clusters struct {
	Clusters []*Cluster
}

Clusters return the list of GKE clusters.

type Duration

type Duration struct {
	// Signed seconds of the span of time. Must be from -315,576,000,000
	// to +315,576,000,000 inclusive. Note: these bounds are computed from:
	// 60 sec/min * 60 min/hr * 24 hr/day * 365.25 days/year * 10000 years
	Seconds int64 `json:"seconds"`
}

type Error

type Error struct {
	// Code is the HTTP response status code.
	Code int
	// Message is the server response message.
	Message string
}

Error contains an error response from the server.

func (*Error) Error

func (e *Error) Error() string

type GCPAuth

type GCPAuth struct {
	TokenCache tokens.TokenCache

	TokenProvider tokens.TokenSource
	ProjectID     string

	Debug bool
}

STS provides token exchanges. Implements grpc and golang.org/x/oauth2.TokenSource

The source of trust is the K8S or other IDP token with TrustDomain audience, it is exchanged with access or WorkloadID tokens.

func (*GCPAuth) GKECluster

func (gcp *GCPAuth) GKECluster(ctx context.Context, token string, path string) (*meshauth.Dest, error)

GetDest returns a cluster config using the GKE API. Path must follow GKE API spec: /projects/P/locations/L/l

func (*GCPAuth) GKEClusters

func (gcp *GCPAuth) GKEClusters(ctx context.Context) ([]*meshauth.Dest, error)

GKE2RestCluster gets all the clusters for a project, and returns Cluster object.

func (*GCPAuth) HubClusters

func (gcp *GCPAuth) HubClusters(ctx context.Context) ([]*meshauth.Dest, error)

func (*GCPAuth) Provision

func (g *GCPAuth) Provision(ctx context.Context) error

type HubCluster

type HubCluster struct {
	// Full name - projects/wlhe-cr/locations/global/memberships/asm-cr
	//Name     string
	Endpoint *struct {
		GkeCluster *struct {
			// //container.googleapis.com/projects/wlhe-cr/locations/us-central1-c/clusters/asm-cr
			ResourceLink string
		}
	}
	State *struct {
		// READY
		Code string
	}

	Authority struct {
		Issuer               string `json:"issuer"`
		WorkloadIdentityPool string `json:"workloadIdentityPool"`
		IdentityProvider     string `json:"identityProvider"`
	} `json:"authority"`

	// Membership labels - different from GKE labels
	Labels map[string]string
}

type HubClusters

type HubClusters struct {
	Resources []HubCluster
}

HubClusters return the list of clusters registered in GKE Hub.

type IAMServiceAccount

type IAMServiceAccount struct {
	// The source of the access token - MDS or K8S federated or GAC
	//
	AccessTokenSource tokens.TokenSource

	// The service account.
	GSA string
	// contains filtered or unexported fields
}

IAMServiceAccount is a google 'service account' - managed as part of a project, with attached IAM permissions.

It can generate OIDC tokens signed by google.

Regular user accounts (gmail or 'apps' or federated) can't usually get OIDC tokens except via OpenID flows. They can get 'access' tokens for google services, including the exchange with service account tokens (acting as). This is the 'AccessTokenSource'. GKE is a federated source, auto-registered.

With gcloud command, JWTs with audience set to gcloud project can be retrieved because the OAuth2 flow is used.

Requires:

gcloud iam service-accounts add-iam-policy-binding \
SERVICE_ACCOUNT_B-email@project-id.iam.gserviceaccount.com \
--member=user:ACCOUNT_A-email@gmail.com \
--role=roles/iam.serviceAccountTokenCreator

func (*IAMServiceAccount) GetToken

func (iamServiceAccount *IAMServiceAccount) GetToken(ctx context.Context, aud string) (string, error)

Google-specific OIDC token for the GSA

func (*IAMServiceAccount) TokenGSA

func (s *IAMServiceAccount) TokenGSA(ctx context.Context, federatedToken string, audience string) (string, error)

Using a federated or user access token (with proper IAM permissions on the GSA), return the OIDC JWT.

If aud is empty or is for googleapis.com - return access token instead.

type MDS

type MDS struct {

	// Addr is the address of the MDS server, including http:// or https://
	// Will detect a GCP/GKE server
	Addr string `json:"addr,omitempty"`
	// contains filtered or unexported fields
}

MDS represents the workload metadata. It is extracted from environment: env variables, mesh config, local metadata server. It implements the TokenSource interface, by default it should return tokens signed by platform (google) CA including access tokens.

func New

func New() *MDS

func (*MDS) Get

func (c *MDS) Get(suffix string) (string, error)

Get returns a value from the metadata service. The suffix is appended to "http://${GCE_METADATA_HOST}/computeMetadata/v1/".

If the GCE_METADATA_HOST environment variable is not defined, a default of 169.254.169.254 will be used instead.

If the requested metadata is not defined, the returned error will be of type NotDefinedError.

func (*MDS) GetToken

func (s *MDS) GetToken(ctx context.Context, aud string) (string, error)

Get an WorkloadID token from platform (GCP, etc) using metadata server.

curl  -H "Metadata-Flavor: Google" "http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/identity?audience=[AUDIENCE]" \

On GKE requires annotation: iam.gke.io/gcp-service-account=[GSA_NAME]@[PROJECT_ID] May fail and need retry

func (*MDS) MetadataGet

func (m *MDS) MetadataGet(path string) (string, error)

GetMDS returns MDS info:

For GCP: instance/hostname - node name.c.PROJECT.internal instance/attributes/cluster-name, cluster-location project/project-id, numeric-project-id

Auth: instance/service-accounts/ - default, PROJECTID.svc.id.goog instance/service-accounts/default/identity - requires the iam.gke.io/gcp-service-account=gsa@project annotation and IAM instance/service-accounts/default/token - access token for the KSA

func (*MDS) NumericProjectID

func (s *MDS) NumericProjectID() string

func (*MDS) ProjectID

func (s *MDS) ProjectID() string

func (*MDS) Provision

func (m *MDS) Provision(ctx context.Context) error

func (*MDS) Subscribe

func (c *MDS) Subscribe(suffix string, fn func(v string, ok bool) error) error

Subscribe subscribes to a value from the metadata service. The suffix is appended to "http://${GCE_METADATA_HOST}/computeMetadata/v1/". The suffix may contain query parameters.

Subscribe calls fn with the latest metadata value indicated by the provided suffix. If the metadata value is deleted, fn is called with the empty string and ok false. Subscribe blocks until fn returns a non-nil error or the value is deleted. Subscribe returns the error value returned from the last call to fn, which may be nil when ok == false.

type MDSD

type MDSD struct {

	// "gcp" or "gcp_fed" access token providers.
	// Latter is used if no gcp tokens are available, and we exchange a K8S or
	// other token
	GCPTokenProvider TokenSource

	// ID token provider. "gcp" or "k8s" or any other source.
	// Note that gmail accounts can't generate JWT tokens (except the gcloud project,
	// which happens to work with CloudRun). Service Accounts can - and a GSA can
	// allow a google account to get tokens.
	TokenProvider TokenSource

	Metadata Metadata

	Addr string
	Mux  *http.ServeMux `json:"-"`
}

func NewServer

func NewServer() *MDSD

func (*MDSD) HandleMDS

func (mdsd *MDSD) HandleMDS(w http.ResponseWriter, r *http.Request)

MDS emulates the GCP metadata server. MDS address is 169.254.169.254:80 - can be intercepted with iptables, or set using GCE_METADATA_HOST https://googleapis.dev/python/google-auth/latest/reference/google.auth.environment_vars.html https://pkg.go.dev/cloud.google.com/go/compute/metadata#Client.Get

gRPC library will use it if: - the env variable is set - a probe to the IP and URL / returns the proper flavor. - DNS resolves metadata.google.internal to the IP

func (*MDSD) Start

func (mdsd *MDSD) Start() error

type Metadata

type Metadata struct {
	Instance struct {
		Attributes struct {
			// Only GKE
			ClusterLocation string
			ClusterName     string
			ClusterUid      string

			// Only GCP
			// Full authorized_hosts with \n separators
			SSHKeys string
		}

		//     "hostname": "gke-CLUSTER_NAME-pool-1-1b6cad60-1l3a.c.costin-asm1.internal",
		// This is the FQDN hostname of the node !
		Hostname string

		ID int

		// Local part of the hostname.
		Name string

		Zone string

		// Default is present and the service account running the node/VM
		ServiceAccounts map[string]struct {
			Aliases []string // "default"
			Email   string   // Based on annotation on the KSA
			Scopes  []string
		}

		NetworkInterfaces map[string]struct {
			IPV6s string

			// Only GCP
			AccessConfigs struct {
				ExternalIP string
				Type       string // ONE_TO_ONE_NAT
			}
			Gateway           string
			IP                string
			Mac               string
			Mtu               string
			Network           string // projects/NUMBER/network/NAME
			Subnetmask        string
			TargetInstanceIps []string
			DNSServers        []string
		}
		Tags []string
	}

	Project struct {
		NumericProjectId int
		ProjectId        string

		// Only on GCP VMs
		Attributes map[string]string

		SSHKeys string `json:"sshKeys"`
	}
}

Metadata represents info about an instance, as reported by the GCP MDS.

Some info is only available on VMs or CloudRun.

type NotDefinedError

type NotDefinedError string

func (NotDefinedError) Error

func (suffix NotDefinedError) Error() string

type OAuth2Source

type OAuth2Source struct {
	Type string `json:"type"` // serviceAccountKey or userCredentialsKey

	// Service Account fields
	ClientEmail  string `json:"client_email"`
	PrivateKeyID string `json:"private_key_id"`
	PrivateKey   string `json:"private_key"`
	TokenURL     string `json:"token_uri"`
	ProjectID    string `json:"project_id"`

	// User Credential fields
	// (These typically come from gcloud auth.)
	ClientSecret string `json:"client_secret"`
	ClientID     string `json:"client_id"`
	RefreshToken string `json:"refresh_token"`
}

OAuth2Source is the unmarshalled representation of a credentials file.

func FindDefaultCredentials

func FindDefaultCredentials() *OAuth2Source

WIP - if GOOGLE_APPLICATION_CREDENTIALS is present, load it and use it as a source of access tokens instead of k8s. Same for MDS

func (*OAuth2Source) GetToken

func (oa *OAuth2Source) GetToken(ctx context.Context, aud string) (string, error)

type Operation

type Operation struct {
}

GCP has a slightly different concept for 'long operations'. - result is the same for all - there is a 'wait' - result may indicate completion (no need to wait) - list, delete supported

AIP-151

longrunning.google.com/v1/ - List, Get, Delete - Wait - but only as gRPC

type TokenSource

type TokenSource interface {
	// GetToken for a given audience.
	GetToken(context.Context, string) (string, error)
}

TokenSource is a common interface for anything returning Bearer or other kind of tokens.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL