|
@@ -23,6 +23,7 @@ import (
|
|
|
"net/http"
|
|
"net/http"
|
|
|
"time"
|
|
"time"
|
|
|
|
|
|
|
|
|
|
+ "cloud.google.com/go/compute/metadata"
|
|
|
iam "cloud.google.com/go/iam/credentials/apiv1"
|
|
iam "cloud.google.com/go/iam/credentials/apiv1"
|
|
|
"cloud.google.com/go/iam/credentials/apiv1/credentialspb"
|
|
"cloud.google.com/go/iam/credentials/apiv1/credentialspb"
|
|
|
secretmanager "cloud.google.com/go/secretmanager/apiv1"
|
|
secretmanager "cloud.google.com/go/secretmanager/apiv1"
|
|
@@ -52,6 +53,7 @@ const (
|
|
|
errFetchPodToken = "unable to fetch pod token: %w"
|
|
errFetchPodToken = "unable to fetch pod token: %w"
|
|
|
errFetchIBToken = "unable to fetch identitybindingtoken: %w"
|
|
errFetchIBToken = "unable to fetch identitybindingtoken: %w"
|
|
|
errGenAccessToken = "unable to generate gcp access token: %w"
|
|
errGenAccessToken = "unable to generate gcp access token: %w"
|
|
|
|
|
+ errLookupIdentity = "unable to lookup workload identity: %w"
|
|
|
errNoProjectID = "unable to find ProjectID in storeSpec"
|
|
errNoProjectID = "unable to find ProjectID in storeSpec"
|
|
|
)
|
|
)
|
|
|
|
|
|
|
@@ -59,6 +61,7 @@ const (
|
|
|
// to create a gcp oauth token.
|
|
// to create a gcp oauth token.
|
|
|
type workloadIdentity struct {
|
|
type workloadIdentity struct {
|
|
|
iamClient IamClient
|
|
iamClient IamClient
|
|
|
|
|
+ metadataClient MetadataClient
|
|
|
idBindTokenGenerator idBindTokenGenerator
|
|
idBindTokenGenerator idBindTokenGenerator
|
|
|
saTokenGenerator saTokenGenerator
|
|
saTokenGenerator saTokenGenerator
|
|
|
clusterProjectID string
|
|
clusterProjectID string
|
|
@@ -70,6 +73,12 @@ type IamClient interface {
|
|
|
Close() error
|
|
Close() error
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+// interface to GCP Metadata API.
|
|
|
|
|
+type MetadataClient interface {
|
|
|
|
|
+ InstanceAttributeValueWithContext(ctx context.Context, attr string) (string, error)
|
|
|
|
|
+ ProjectIDWithContext(ctx context.Context) (string, error)
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
// interface to securetoken/identitybindingtoken API.
|
|
// interface to securetoken/identitybindingtoken API.
|
|
|
type idBindTokenGenerator interface {
|
|
type idBindTokenGenerator interface {
|
|
|
Generate(context.Context, *http.Client, string, string, string) (*oauth2.Token, error)
|
|
Generate(context.Context, *http.Client, string, string, string) (*oauth2.Token, error)
|
|
@@ -91,12 +100,46 @@ func newWorkloadIdentity(ctx context.Context, projectID string) (*workloadIdenti
|
|
|
}
|
|
}
|
|
|
return &workloadIdentity{
|
|
return &workloadIdentity{
|
|
|
iamClient: iamc,
|
|
iamClient: iamc,
|
|
|
|
|
+ metadataClient: newMetadataClient(),
|
|
|
idBindTokenGenerator: newIDBindTokenGenerator(),
|
|
idBindTokenGenerator: newIDBindTokenGenerator(),
|
|
|
saTokenGenerator: satg,
|
|
saTokenGenerator: satg,
|
|
|
clusterProjectID: projectID,
|
|
clusterProjectID: projectID,
|
|
|
}, nil
|
|
}, nil
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+func (w *workloadIdentity) gcpWorkloadIdentity(ctx context.Context, id *esv1beta1.GCPWorkloadIdentity) (string, string, error) {
|
|
|
|
|
+ var err error
|
|
|
|
|
+
|
|
|
|
|
+ projectID := id.ClusterProjectID
|
|
|
|
|
+ if projectID == "" {
|
|
|
|
|
+ if projectID, err = w.metadataClient.ProjectIDWithContext(ctx); err != nil {
|
|
|
|
|
+ return "", "", fmt.Errorf("unable to get project id: %w", err)
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ clusterLocation := id.ClusterLocation
|
|
|
|
|
+ if clusterLocation == "" {
|
|
|
|
|
+ if clusterLocation, err = w.metadataClient.InstanceAttributeValueWithContext(ctx, "cluster-location"); err != nil {
|
|
|
|
|
+ return "", "", fmt.Errorf("unable to determine cluster location: %w", err)
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ clusterName := id.ClusterName
|
|
|
|
|
+ if clusterName == "" {
|
|
|
|
|
+ if clusterName, err = w.metadataClient.InstanceAttributeValueWithContext(ctx, "cluster-name"); err != nil {
|
|
|
|
|
+ return "", "", fmt.Errorf("unable to determine cluster name: %w", err)
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ idPool := fmt.Sprintf("%s.svc.id.goog", projectID)
|
|
|
|
|
+ idProvider := fmt.Sprintf("https://container.googleapis.com/v1/projects/%s/locations/%s/clusters/%s",
|
|
|
|
|
+ projectID,
|
|
|
|
|
+ clusterLocation,
|
|
|
|
|
+ clusterName,
|
|
|
|
|
+ )
|
|
|
|
|
+ return idPool, idProvider, nil
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
func (w *workloadIdentity) TokenSource(ctx context.Context, auth esv1beta1.GCPSMAuth, isClusterKind bool, kube kclient.Client, namespace string) (oauth2.TokenSource, error) {
|
|
func (w *workloadIdentity) TokenSource(ctx context.Context, auth esv1beta1.GCPSMAuth, isClusterKind bool, kube kclient.Client, namespace string) (oauth2.TokenSource, error) {
|
|
|
wi := auth.WorkloadIdentity
|
|
wi := auth.WorkloadIdentity
|
|
|
if wi == nil {
|
|
if wi == nil {
|
|
@@ -118,11 +161,11 @@ func (w *workloadIdentity) TokenSource(ctx context.Context, auth esv1beta1.GCPSM
|
|
|
return nil, err
|
|
return nil, err
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- idProvider := fmt.Sprintf("https://container.googleapis.com/v1/projects/%s/locations/%s/clusters/%s",
|
|
|
|
|
- w.clusterProjectID,
|
|
|
|
|
- wi.ClusterLocation,
|
|
|
|
|
- wi.ClusterName)
|
|
|
|
|
- idPool := fmt.Sprintf("%s.svc.id.goog", w.clusterProjectID)
|
|
|
|
|
|
|
+ idPool, idProvider, err := w.gcpWorkloadIdentity(ctx, wi)
|
|
|
|
|
+ if err != nil {
|
|
|
|
|
+ return nil, fmt.Errorf(errLookupIdentity, err)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
audiences := []string{idPool}
|
|
audiences := []string{idPool}
|
|
|
if len(wi.ServiceAccountRef.Audiences) > 0 {
|
|
if len(wi.ServiceAccountRef.Audiences) > 0 {
|
|
|
audiences = append(audiences, wi.ServiceAccountRef.Audiences...)
|
|
audiences = append(audiences, wi.ServiceAccountRef.Audiences...)
|
|
@@ -181,6 +224,12 @@ func newIAMClient(ctx context.Context) (IamClient, error) {
|
|
|
return iam.NewIamCredentialsClient(ctx, iamOpts...)
|
|
return iam.NewIamCredentialsClient(ctx, iamOpts...)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+func newMetadataClient() MetadataClient {
|
|
|
|
|
+ return metadata.NewClient(&http.Client{
|
|
|
|
|
+ Timeout: 5 * time.Second,
|
|
|
|
|
+ })
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
type k8sSATokenGenerator struct {
|
|
type k8sSATokenGenerator struct {
|
|
|
corev1 clientcorev1.CoreV1Interface
|
|
corev1 clientcorev1.CoreV1Interface
|
|
|
}
|
|
}
|