-
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.
test(downscope): add integration tests for token downscoping with Cre…
…dential Access Boundaries (#1124) Testing the code present in `oauth2/google/downscope`. I also caught a typo in the comments of a previous push- given that I only needed to add a single space, I bundled that change into this PR.
- Loading branch information
Showing
3 changed files
with
227 additions
and
2 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,145 @@ | ||
// Copyright 2021 Google LLC. | ||
// Use of this source code is governed by a BSD-style | ||
// license that can be found in the LICENSE file. | ||
|
||
package downscope | ||
|
||
import ( | ||
"context" | ||
"flag" | ||
"fmt" | ||
"io/ioutil" | ||
"log" | ||
"os" | ||
"testing" | ||
"time" | ||
|
||
"google.golang.org/api/option" | ||
|
||
"golang.org/x/oauth2" | ||
"golang.org/x/oauth2/google" | ||
"golang.org/x/oauth2/google/downscope" | ||
storage "google.golang.org/api/storage/v1" | ||
"google.golang.org/api/transport" | ||
) | ||
|
||
const ( | ||
rootTokenScope = "https://www.googleapis.com/auth/cloud-platform" | ||
envServiceAccountFile = "GCLOUD_TESTS_GOLANG_KEY" | ||
object1 = "cab-first-c45wknuy.txt" | ||
object2 = "cab-second-c45wknuy.txt" | ||
bucket = "dulcet-port-762" | ||
) | ||
|
||
var ( | ||
rootCredential *google.Credentials | ||
) | ||
|
||
// 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) { | ||
flag.Parse() | ||
if testing.Short() { | ||
// This line runs all of our individual tests | ||
os.Exit(m.Run()) | ||
} | ||
ctx := context.Background() | ||
credentialFileName := os.Getenv(envServiceAccountFile) | ||
|
||
var err error | ||
rootCredential, err = transport.Creds(ctx, option.WithCredentialsFile(credentialFileName), option.WithScopes(rootTokenScope)) | ||
|
||
if err != nil { | ||
log.Fatalf("failed to construct root credential: %v", err) | ||
} | ||
|
||
// This line runs all of our individual tests | ||
os.Exit(m.Run()) | ||
|
||
} | ||
|
||
// downscopeTest holds the parameters necessary for running a test of the token downscoping capabilities implemented in `oauth2/google/downscope` | ||
type downscopeTest struct { | ||
name string | ||
availableResource string | ||
availablePermissions []string | ||
condition downscope.AvailabilityCondition | ||
objectName string | ||
rootSource oauth2.TokenSource | ||
expectError bool | ||
} | ||
|
||
func TestDownscopedToken(t *testing.T) { | ||
if testing.Short() { | ||
t.Skip("skipping integration test") | ||
} | ||
|
||
var downscopeTests = []downscopeTest{ | ||
{ | ||
name: "successfulDownscopedRead", | ||
availableResource: "//storage.googleapis.com/projects/_/buckets/" + bucket, | ||
availablePermissions: []string{"inRole:roles/storage.objectViewer"}, | ||
condition: downscope.AvailabilityCondition{ | ||
Expression: "resource.name.startsWith('projects/_/buckets/" + bucket + "/objects/" + object1 + "')", | ||
}, | ||
rootSource: rootCredential.TokenSource, | ||
objectName: object1, | ||
expectError: false, | ||
}, | ||
{ | ||
name: "readWithoutPermission", | ||
availableResource: "//storage.googleapis.com/projects/_/buckets/" + bucket, | ||
availablePermissions: []string{"inRole:roles/storage.objectViewer"}, | ||
condition: downscope.AvailabilityCondition{ | ||
Expression: "resource.name.startsWith('projects/_/buckets/" + bucket + "/objects/" + object1 + "')", | ||
}, | ||
rootSource: rootCredential.TokenSource, | ||
objectName: object2, | ||
expectError: true, | ||
}, | ||
} | ||
|
||
for _, tt := range downscopeTests { | ||
t.Run(tt.name, func(t *testing.T) { | ||
err := downscopeQuery(t, tt) | ||
// If a test isn't supposed to fail, it shouldn't fail. | ||
if !tt.expectError && err != nil { | ||
t.Errorf("test case %v should have succeeded, but instead returned %v", tt.name, err) | ||
} else if tt.expectError && err == nil { // If a test is supposed to fail, it should return a non-nil error. | ||
t.Errorf(" test case %v should have returned an error, but instead returned nil", tt.name) | ||
} | ||
}) | ||
} | ||
} | ||
|
||
// I'm not sure what I should name this according to convention. | ||
func downscopeQuery(t *testing.T, tt downscopeTest) error { | ||
t.Helper() | ||
ctx := context.Background() | ||
|
||
// Initializes an accessBoundary | ||
var AccessBoundaryRules []downscope.AccessBoundaryRule | ||
AccessBoundaryRules = append(AccessBoundaryRules, downscope.AccessBoundaryRule{AvailableResource: tt.availableResource, AvailablePermissions: tt.availablePermissions, Condition: &tt.condition}) | ||
|
||
downscopedTokenSource, err := downscope.NewTokenSource(context.Background(), downscope.DownscopingConfig{RootSource: tt.rootSource, Rules: AccessBoundaryRules}) | ||
if err != nil { | ||
return fmt.Errorf("failed to create the initial token source: %v", err) | ||
} | ||
downscopedTokenSource = oauth2.ReuseTokenSource(nil, downscopedTokenSource) | ||
|
||
ctx, cancel := context.WithTimeout(ctx, time.Second*30) | ||
defer cancel() | ||
storageService, err := storage.NewService(ctx, option.WithTokenSource(downscopedTokenSource)) | ||
if err != nil { | ||
return fmt.Errorf("failed to create the storage service: %v", err) | ||
} | ||
resp, err := storageService.Objects.Get(bucket, tt.objectName).Download() | ||
if err != nil { | ||
return fmt.Errorf("failed to retrieve object from GCP project with error: %v", err) | ||
} | ||
defer resp.Body.Close() | ||
_, err = ioutil.ReadAll(resp.Body) | ||
if err != nil { | ||
return fmt.Errorf("ioutil.ReadAll: %v", err) | ||
} | ||
return nil | ||
} |
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,81 @@ | ||
#!/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 script is used to generate the project configurations needed to | ||
# end-to-end test Downscoping with Credential Access Boundaries in the Auth | ||
# library. This script only needs to be run once. | ||
# | ||
# In order to run this script, you need to fill in the project_id and | ||
# service_account_email variables. | ||
# | ||
# If an argument is provided, the script will use the provided argument | ||
# as the bucket name. Otherwise, it will create a new bucket. | ||
# | ||
# This script needs to be run once. It will do the following: | ||
# 1. Sets the current project to the one specified. | ||
# 2. If no bucket name was provided, creates a GCS bucket in the specified project. | ||
# 3. Gives the specified service account the objectAdmin role for this bucket. | ||
# 4. Creates two text files to be uploaded to the created bucket. | ||
# 5. Uploads both text files. | ||
# 6. Prints out the identifiers (bucket ID, first object ID, second object ID) | ||
# to be used in the accompanying tests. | ||
# 7. Deletes the created text files in the current directory. | ||
# | ||
# The same service account used for this setup script should be used for | ||
# the integration tests. | ||
# | ||
# It is safe to run the setup script again. A new bucket is created along with | ||
# new objects. If run multiple times, it is advisable to delete | ||
# unused buckets. | ||
|
||
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 | ||
|
||
first_object="cab-first-"${suffix}.txt | ||
second_object="cab-second-"${suffix}.txt | ||
|
||
# Fill in. | ||
project_id="dulcet-port-762" | ||
service_account_email="[email protected]" | ||
|
||
gcloud config set project ${project_id} | ||
|
||
if (( $# != 1 )) | ||
then | ||
# Create the GCS bucket. | ||
bucket_id="cab-int-bucket-"${suffix} | ||
gsutil mb -b on -l us-east1 gs://${bucket_id} | ||
else | ||
bucket_id="$1" | ||
fi | ||
|
||
# Give the specified service account the objectAdmin role for this bucket. | ||
gsutil iam ch serviceAccount:${service_account_email}:objectAdmin gs://${bucket_id} | ||
|
||
# Create both objects. | ||
echo "first" >> ${first_object} | ||
echo "second" >> ${second_object} | ||
|
||
# Upload the created objects to the bucket. | ||
gsutil cp ${first_object} gs://${bucket_id} | ||
gsutil cp ${second_object} gs://${bucket_id} | ||
|
||
echo "Bucket ID: "${bucket_id} | ||
echo "First object ID: "${first_object} | ||
echo "Second object ID: "${second_object} | ||
|
||
# Cleanup | ||
rm ${first_object} | ||
rm ${second_object} |
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