-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
fd239af
commit 63d6b3e
Showing
2 changed files
with
272 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,204 @@ | ||
// Copyright 2021 Google LLC. | ||
// Use of this source code is governed by a BSD-style | ||
// license that can be found in the LICENSE file. | ||
|
||
// +build integration | ||
|
||
// To run this test locally, you will need to do the following: | ||
// • Navigate to your Google Cloud Project | ||
// • Get a copy of the Service Account Key File from somebody | ||
// • If you are unable to obtain an existing key file, create one: | ||
// • > IAM and Admin > Service Accounts | ||
// • Under the needed Service Account > Actions > Manage Keys | ||
// • Add Key > Create New Key | ||
// • Select JSON, and the click Create | ||
// • > Compute > Compute Engine > VM Instances | ||
// • Look for an available VM Instance, or create one | ||
// • On the VM Instance, click the SSH Button | ||
// • Upload your Service Account Key File | ||
// • Upload this script, along with setup.sh | ||
// • Get a copy of the needed environment variables from somebody, and upload those too | ||
// • Set your environment variables (Usually this will be `source env.conf`) | ||
// • If the setup script has not yet been run, then run it | ||
// • `go test -tags integration` | ||
|
||
package byoid | ||
|
||
import ( | ||
"context" | ||
"encoding/json" | ||
"fmt" | ||
"io/ioutil" | ||
"log" | ||
"os" | ||
"testing" | ||
|
||
"google.golang.org/api/dns/v1" | ||
"google.golang.org/api/idtoken" | ||
"google.golang.org/api/option" | ||
) | ||
|
||
const ( | ||
envCredentials = "GCLOUD_TESTS_GOLANG_KEY" | ||
envAudienceOIDC = "GCLOUD_TESTS_GOLANG_AUDIENCE_OIDC" | ||
envProject = "GCLOUD_TESTS_GOLANG_PROJECT_ID" | ||
) | ||
|
||
var ( | ||
oidcAudience string | ||
oidcToken string | ||
awsToken string | ||
clientID string | ||
projectID string | ||
) | ||
|
||
// TestMain contains all of the setup code that needs to be run once before any of the tests are run | ||
func TestMain(m *testing.M) { | ||
keyFileName := os.Getenv(envCredentials) | ||
if keyFileName == "" { | ||
log.Fatalf("Please set %s to your keyfile", envCredentials) | ||
} | ||
|
||
projectID = os.Getenv(envProject) | ||
if projectID == "" { | ||
log.Fatalf("Please set %s to the ID of the project", envProject) | ||
} | ||
|
||
oidcAudience = os.Getenv(envAudienceOIDC) | ||
if oidcAudience == "" { | ||
log.Fatalf("Please set %s to the OIDC Audience", envAudienceOIDC) | ||
} | ||
|
||
var err error | ||
|
||
clientID, err = getClientID(keyFileName) | ||
if err != nil { | ||
log.Fatalf("Error getting Client ID: %v", err) | ||
} | ||
|
||
oidcToken, err = generateGoogleToken(keyFileName) | ||
if err != nil { | ||
log.Fatalf("Error generating Google token: %v", err) | ||
} | ||
|
||
// This line runs all of our individual tests | ||
os.Exit(m.Run()) | ||
} | ||
|
||
// keyFile is a struct to extract the relevant json fields for our ServiceAccount KeyFile | ||
type keyFile struct { | ||
ClientEmail string `json:"client_email"` | ||
ClientID string `json:"client_id"` | ||
} | ||
|
||
func getClientID(keyFileName string) (string, error) { | ||
kf, err := os.Open(keyFileName) | ||
if err != nil { | ||
return "", err | ||
} | ||
defer kf.Close() | ||
|
||
decoder := json.NewDecoder(kf) | ||
var keyFileSettings keyFile | ||
err = decoder.Decode(&keyFileSettings) | ||
if err != nil { | ||
return "", err | ||
} | ||
|
||
return fmt.Sprintf("projects/-/serviceAccounts/%s", keyFileSettings.ClientEmail), nil | ||
} | ||
|
||
func generateGoogleToken(keyFileName string) (string, error) { | ||
ts, err := idtoken.NewTokenSource(context.Background(), oidcAudience, option.WithCredentialsFile(keyFileName)) | ||
if err != nil { | ||
return "", nil | ||
} | ||
|
||
token, err := ts.Token() | ||
if err != nil { | ||
return "", nil | ||
} | ||
|
||
return token.AccessToken, nil | ||
} | ||
|
||
// testBYOID makes sure that the default credentials works for | ||
// whatever preconditions have been set beforehand | ||
// by using those credentials to run our client libraries. | ||
// | ||
// In each test we will set up whatever preconditions we need, | ||
// and then use this function. | ||
func testBYOID(t *testing.T, c config) { | ||
t.Helper() | ||
|
||
// Set up config file. | ||
configFile, err := ioutil.TempFile("", "config.json") | ||
if err != nil { | ||
t.Fatalf("Error creating config file: %v", err) | ||
} | ||
defer os.Remove(configFile.Name()) | ||
|
||
err = json.NewEncoder(configFile).Encode(c) | ||
if err != nil { | ||
t.Errorf("Error writing to config file: %v", err) | ||
} | ||
configFile.Close() | ||
|
||
// Once the default credentials are obtained, | ||
// we should be able to access Google Cloud resources. | ||
dnsService, err := dns.NewService(context.Background(), option.WithCredentialsFile(configFile.Name())) | ||
if err != nil { | ||
t.Fatalf("Could not establish DNS Service: %v", err) | ||
} | ||
|
||
_, err = dnsService.Projects.Get(projectID).Do() | ||
if err != nil { | ||
t.Fatalf("DNS Service failed: %v", err) | ||
} | ||
} | ||
|
||
// These structs makes writing our config as json to a file much easier. | ||
type config struct { | ||
Type string `json:"type"` | ||
Audience string `json:"audience"` | ||
SubjectTokenType string `json:"subject_token_type"` | ||
TokenURL string `json:"token_url"` | ||
ServiceAccountImpersonationURL string `json:"service_account_impersonation_url"` | ||
CredentialSource credentialSource `json:"credential_source"` | ||
} | ||
|
||
type credentialSource struct { | ||
File string `json:"file,omitempty"` | ||
|
||
URL string `json:"url,omitempty"` | ||
Headers map[string]string `json:"headers,omitempty"` | ||
|
||
EnvironmentID string `json:"environment_id,omitempty"` | ||
RegionalCredVerificationURL string `json:"regional_cred_verification_url,omitempty"` | ||
Format string `json:"format,omitempty"` | ||
} | ||
|
||
// Tests to make sure File based external credentials continues to work. | ||
func TestFileBasedCredentials(t *testing.T) { | ||
// Set up Token as a file | ||
tokenFile, err := ioutil.TempFile("", "token.txt") | ||
if err != nil { | ||
t.Fatalf("Error creating token file:") | ||
} | ||
defer os.Remove(tokenFile.Name()) | ||
|
||
tokenFile.WriteString(oidcToken) | ||
tokenFile.Close() | ||
|
||
// Run our test! | ||
testBYOID(t, config{ | ||
Type: "external_account", | ||
Audience: oidcAudience, | ||
SubjectTokenType: "urn:ietf:params:oauth:token-type:jwt", | ||
TokenURL: "https://sts.googleapis.com/v1beta/token", | ||
ServiceAccountImpersonationURL: fmt.Sprintf("https://iamcredentials.googleapis.com/v1/%s:generateAccessToken", clientID), | ||
CredentialSource: credentialSource{ | ||
File: tokenFile.Name(), | ||
}, | ||
}) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
#!/bin/bash | ||
# Copyright 2021 Google LLC. | ||
# Use of this source code is governed by a BSD-style | ||
# license that can be found in the LICENSE file. | ||
|
||
# This file is a mostly common setup file to ensure all BYOID integration tests | ||
# are set up in a consistent fashion. | ||
# It assumes that the current user has the relevant permissions to run each of | ||
# the commands listed. | ||
|
||
suffix="" | ||
|
||
function generate_random_string () { | ||
local valid_chars=abcdefghijklmnopqrstuvwxyz0123456789 | ||
for i in {1..8} ; do | ||
suffix+="${valid_chars:RANDOM%${#valid_chars}:1}" | ||
done | ||
} | ||
|
||
generate_random_string | ||
|
||
pool_id="pool-"$suffix | ||
oidc_provider_id="oidc-"$suffix | ||
aws_provider_id="aws-"$suffix | ||
|
||
# Fill in. | ||
project_id=$GCLOUD_TESTS_GOLANG_PROJECT_ID | ||
project_number=$GCLOUD_TESTS_GOLANG_PROJECT_NUMBER | ||
aws_account_id=$GCLOUD_TESTS_GOLANG_AWS_ACCOUNT_ID | ||
aws_role_name=$GCLOUD_TESTS_GOLANG_AWS_ROLE_NAME | ||
service_account_email=$GCLOUD_TESTS_GOLANG_SERVICE_ACCOUNT_EMAIL | ||
sub=$GCLOUD_TESTS_GOLANG_SERVICE_ACCOUNT_CLIENT_ID | ||
|
||
oidc_aud="//iam.googleapis.com/projects/$project_number/locations/global/workloadIdentityPools/$pool_id/providers/$oidc_provider_id" | ||
aws_aud="//iam.googleapis.com/projects/$project_number/locations/global/workloadIdentityPools/$pool_id/providers/$aws_provider_id" | ||
|
||
gcloud config set project $project_id | ||
|
||
# Create the Workload Identity Pool. | ||
gcloud beta iam workload-identity-pools create $pool_id \ | ||
--location="global" \ | ||
--description="Test pool" \ | ||
--display-name="Test pool for Go" | ||
|
||
# Create the OIDC Provider. | ||
gcloud beta iam workload-identity-pools providers create-oidc $oidc_provider_id \ | ||
--workload-identity-pool=$pool_id \ | ||
--issuer-uri="https://accounts.google.com" \ | ||
--location="global" \ | ||
--attribute-mapping="google.subject=assertion.sub" | ||
|
||
# Create the AWS Provider. | ||
gcloud beta iam workload-identity-pools providers create-aws $aws_provider_id \ | ||
--workload-identity-pool=$pool_id \ | ||
--account-id=$aws_account_id \ | ||
--location="global" | ||
|
||
# Give permission to impersonate the service account. | ||
gcloud iam service-accounts add-iam-policy-binding $service_account_email \ | ||
--role roles/iam.workloadIdentityUser \ | ||
--member "principal://iam.googleapis.com/projects/$project_number/locations/global/workloadIdentityPools/$pool_id/subject/$sub" | ||
|
||
gcloud iam service-accounts add-iam-policy-binding $service_account_email \ | ||
--role roles/iam.workloadIdentityUser \ | ||
--member "principalSet://iam.googleapis.com/projects/$project_number/locations/global/workloadIdentityPools/$pool_id/attribute.aws_role/arn:aws:sts::$aws_account_id:assumed-role/$aws_role_name" | ||
|
||
echo "OIDC audience: "$oidc_aud | ||
echo "AWS audience: "$aws_aud |