forked from GoogleCloudPlatform/golang-samples
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
downscope(examples): show credential downscoping with CAB (GoogleClou…
…dPlatform#2181) This push adds some examples showing how to downscope credentials using CredentialAccessBoundaries (documented [here](https://cloud.google.com/iam/docs/downscoping-short-lived-credentials)). The added examples use the code stored [here](https://github.com/golang/oauth2/tree/master/google/downscope).
- Loading branch information
Showing
5 changed files
with
382 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,52 @@ | ||
// Copyright 2021 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 | ||
// | ||
// https://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 downscopedoverview contains Google Cloud auth snippets showing how to | ||
// downscope credentials with Credential Access Boundaries. | ||
// https://cloud.google.com/iam/docs/downscoping-short-lived-credentials | ||
package downscopedoverview | ||
|
||
// [START auth_downscoping_initialize_downscoped_cred] | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
|
||
"golang.org/x/oauth2/google" | ||
"golang.org/x/oauth2/google/downscope" | ||
) | ||
|
||
// initializeCredentials will generate a downscoped token using the provided Access Boundary Rules. | ||
func initializeCredentials(accessBoundary []downscope.AccessBoundaryRule) error { | ||
ctx := context.Background() | ||
|
||
// You must provide the "https://www.googleapis.com/auth/cloud-platform" scope. | ||
// This Source can be initialized in multiple ways; the following example uses | ||
// Application Default Credentials. | ||
rootSource, err := google.DefaultTokenSource(ctx, "https://www.googleapis.com/auth/cloud-platform") | ||
if err != nil { | ||
return fmt.Errorf("failed to generate rootSource: %v", err) | ||
} | ||
|
||
// downscope.NewTokenSource constructs the token source with the configuration provided. | ||
dts, err := downscope.NewTokenSource(ctx, downscope.DownscopingConfig{RootSource: rootSource, Rules: accessBoundary}) | ||
if err != nil { | ||
return fmt.Errorf("failed to generate downscoped token source: %v", err) | ||
} | ||
_ = dts | ||
// You can now use dts to access Google Storage resources. | ||
return nil | ||
} | ||
|
||
// [END auth_downscoping_initialize_downscoped_cred] |
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,95 @@ | ||
// Copyright 2021 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 | ||
// | ||
// https://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 downscopedoverview | ||
|
||
import ( | ||
"bytes" | ||
"context" | ||
"io" | ||
"strings" | ||
"testing" | ||
"time" | ||
|
||
"cloud.google.com/go/storage" | ||
"github.com/GoogleCloudPlatform/golang-samples/internal/testutil" | ||
"github.com/google/uuid" | ||
"golang.org/x/oauth2/google/downscope" | ||
) | ||
|
||
func TestInitializeCredentials(t *testing.T) { | ||
accessBoundary := []downscope.AccessBoundaryRule{ | ||
{ | ||
AvailableResource: "//storage.googleapis.com/projects/_/buckets/foo", | ||
AvailablePermissions: []string{"inRole:roles/storage.objectViewer"}, | ||
}, | ||
} | ||
err := initializeCredentials(accessBoundary) | ||
if err != nil { | ||
t.Errorf("got %v; wanted nil", err) | ||
} | ||
} | ||
|
||
func TestReadObjectContents(t *testing.T) { | ||
ctx := context.Background() | ||
tc := testutil.SystemTest(t) | ||
|
||
ctx, cancel := context.WithTimeout(ctx, time.Second*30) | ||
defer cancel() | ||
client, err := storage.NewClient(ctx) | ||
if err != nil { | ||
t.Errorf("%v", err) | ||
return | ||
} | ||
randSuffix, err := uuid.NewRandom() | ||
if err != nil { | ||
t.Fatalf("Failed to generate random UUID suffix: %v", err) | ||
} | ||
bucketName := "bucket-downscoping-test-golang-" + randSuffix.String()[:8] | ||
objectName := "object-downscoping-test-golang-" + randSuffix.String()[:8] | ||
content := "CONTENT" | ||
bucket := client.Bucket(bucketName) | ||
|
||
if err := bucket.Create(ctx, tc.ProjectID, nil); err != nil { | ||
t.Errorf("Failed to create bucket: %v", err) | ||
return | ||
} | ||
defer bucket.Delete(ctx) | ||
obj := bucket.Object(objectName) | ||
write := obj.NewWriter(ctx) | ||
_, err = io.Copy(write, strings.NewReader(content)) | ||
if err != nil { | ||
t.Errorf("failed to copy the text: %v", err) | ||
return | ||
} | ||
defer obj.Delete(ctx) | ||
|
||
err = write.Close() | ||
if err != nil { | ||
t.Errorf("failed to close the writer: %v", err) | ||
return | ||
} | ||
|
||
buf := new(bytes.Buffer) | ||
err = getObjectContents(buf, bucketName, objectName) | ||
if err != nil { | ||
t.Errorf("failed to retrieve object contents: %v", err) | ||
return | ||
} | ||
out := buf.String() | ||
if out != content { | ||
t.Errorf("got %v but want "+content, out) | ||
return | ||
} | ||
} |
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,49 @@ | ||
// Copyright 2021 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 | ||
// | ||
// https://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 downscopedoverview | ||
|
||
// [START auth_downscoping_rules] | ||
|
||
import "golang.org/x/oauth2/google/downscope" | ||
|
||
// constructCAB shows how to initialize a Credential Access Boundary for downscoping tokens. | ||
func constructCAB(bucketName string, prefix string) { | ||
// bucketName := "foo" | ||
// prefix := "profile-picture-" | ||
|
||
// A condition can optionally be provided to further restrict access permissions. | ||
// Note that the "profile-picture-" prefix is an arbitrary example to show how to | ||
// construct an AvailabilityCondition; it can be changed to anything. | ||
condition := downscope.AvailabilityCondition{ | ||
Expression: "resource.name.startsWith('projects/_/buckets/" + bucketName + "/objects/" + prefix + "'", | ||
Title: prefix + " Only", | ||
Description: "Restricts a token to only be able to access objects that start with `" + prefix + "`", | ||
} | ||
// Initializes an accessBoundary with one Rule which restricts the downscoped | ||
// token to only be able to access the passed in bucket and only grants it the | ||
// permission "storage.objectViewer". | ||
accessBoundary := []downscope.AccessBoundaryRule{ | ||
{ | ||
AvailableResource: "//storage.googleapis.com/projects/_/buckets/" + bucketName, | ||
AvailablePermissions: []string{"inRole:roles/storage.objectViewer"}, | ||
Condition: &condition, // Optional | ||
}, | ||
} | ||
|
||
// You can now use this accessBoundary to generate a downscoped token. | ||
_ = accessBoundary | ||
} | ||
|
||
// [END auth_downscoping_rules] |
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,78 @@ | ||
// Copyright 2021 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 | ||
// | ||
// https://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 downscopedoverview | ||
|
||
// [START auth_downscoping_token_broker] | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
|
||
"golang.org/x/oauth2" | ||
"golang.org/x/oauth2/google" | ||
"golang.org/x/oauth2/google/downscope" | ||
) | ||
|
||
// createDownscopedToken would be run on the token broker in order to generate | ||
// a downscoped access token that only grants access to objects whose name begins with prefix. | ||
// The token broker would then pass the newly created token to the requesting token consumer for use. | ||
func createDownscopedToken(bucketName string, prefix string) error { | ||
// bucketName := "foo" | ||
// prefix := "profile-picture-" | ||
|
||
ctx := context.Background() | ||
// A condition can optionally be provided to further restrict access permissions. | ||
condition := downscope.AvailabilityCondition{ | ||
Expression: "resource.name.startsWith('projects/_/buckets/" + bucketName + "/objects/" + prefix + "'", | ||
Title: prefix + " Only", | ||
Description: "Restricts a token to only be able to access objects that start with `" + prefix + "`", | ||
} | ||
// Initializes an accessBoundary with one Rule which restricts the downscoped | ||
// token to only be able to access the bucket "bucketName" and only grants it the | ||
// permission "storage.objectViewer". | ||
accessBoundary := []downscope.AccessBoundaryRule{ | ||
{ | ||
AvailableResource: "//storage.googleapis.com/projects/_/buckets/" + bucketName, | ||
AvailablePermissions: []string{"inRole:roles/storage.objectViewer"}, | ||
Condition: &condition, // Optional | ||
}, | ||
} | ||
|
||
// This Source can be initialized in multiple ways; the following example uses | ||
// Application Default Credentials. | ||
var rootSource oauth2.TokenSource | ||
|
||
// You must provide the "https://www.googleapis.com/auth/cloud-platform" scope. | ||
rootSource, err := google.DefaultTokenSource(ctx, "https://www.googleapis.com/auth/cloud-platform") | ||
if err != nil { | ||
return fmt.Errorf("failed to generate rootSource: %v", err) | ||
} | ||
|
||
// downscope.NewTokenSource constructs the token source with the configuration provided. | ||
dts, err := downscope.NewTokenSource(ctx, downscope.DownscopingConfig{RootSource: rootSource, Rules: accessBoundary}) | ||
if err != nil { | ||
return fmt.Errorf("failed to generate downscoped token source: %v", err) | ||
} | ||
// Token() uses the previously declared TokenSource to generate a downscoped token. | ||
tok, err := dts.Token() | ||
if err != nil { | ||
return fmt.Errorf("failed to generate token: %v", err) | ||
} | ||
// Pass this token back to the token consumer. | ||
_ = tok | ||
return nil | ||
} | ||
|
||
// [END auth_downscoping_token_broker] |
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,108 @@ | ||
// Copyright 2021 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 | ||
// | ||
// https://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 downscopedoverview | ||
|
||
// [START auth_downscoping_token_consumer] | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"io" | ||
"io/ioutil" | ||
|
||
"golang.org/x/oauth2/google" | ||
"golang.org/x/oauth2/google/downscope" | ||
|
||
"cloud.google.com/go/storage" | ||
"golang.org/x/oauth2" | ||
"google.golang.org/api/option" | ||
) | ||
|
||
// A token consumer should define their own tokenSource. In the Token() method, | ||
// it should send a query to a token broker requesting a downscoped token. | ||
// The token broker holds the root credential that is used to generate the | ||
// downscoped token. | ||
type localTokenSource struct { | ||
ctx context.Context | ||
bucketName string | ||
brokerURL string | ||
} | ||
|
||
func (lts localTokenSource) Token() (*oauth2.Token, error) { | ||
var remoteToken *oauth2.Token | ||
// Usually you would now retrieve remoteToken, an oauth2.Token, from token broker. | ||
// This snippet performs the same functionality locally. | ||
accessBoundary := []downscope.AccessBoundaryRule{ | ||
{ | ||
AvailableResource: "//storage.googleapis.com/projects/_/buckets/" + lts.bucketName, | ||
AvailablePermissions: []string{"inRole:roles/storage.objectViewer"}, | ||
}, | ||
} | ||
rootSource, err := google.DefaultTokenSource(lts.ctx, "https://www.googleapis.com/auth/cloud-platform") | ||
if err != nil { | ||
return nil, fmt.Errorf("failed to generate rootSource: %v", err) | ||
} | ||
dts, err := downscope.NewTokenSource(lts.ctx, downscope.DownscopingConfig{RootSource: rootSource, Rules: accessBoundary}) | ||
if err != nil { | ||
return nil, fmt.Errorf("failed to generate downscoped token source: %v", err) | ||
} | ||
// Token() uses the previously declared TokenSource to generate a downscoped token. | ||
remoteToken, err = dts.Token() | ||
if err != nil { | ||
return nil, fmt.Errorf("failed to generate token: %v", err) | ||
} | ||
|
||
return remoteToken, nil | ||
} | ||
|
||
// getObjectContents will read the contents of an object in Google Storage | ||
// named objectName, contained in the bucket "bucketName". | ||
func getObjectContents(output io.Writer, bucketName string, objectName string) error { | ||
// bucketName := "foo" | ||
// prefix := "profile-picture-" | ||
|
||
ctx := context.Background() | ||
|
||
thisTokenSource := localTokenSource{ | ||
ctx: ctx, | ||
bucketName: bucketName, | ||
brokerURL: "yourURL.com/internal/broker", | ||
} | ||
|
||
// Wrap the TokenSource in an oauth2.ReuseTokenSource to enable automatic refreshing. | ||
refreshableTS := oauth2.ReuseTokenSource(nil, thisTokenSource) | ||
// You can now use the token source to access Google Cloud Storage resources as follows. | ||
storageClient, err := storage.NewClient(ctx, option.WithTokenSource(refreshableTS)) | ||
if err != nil { | ||
return fmt.Errorf("failed to create the storage client: %v", err) | ||
} | ||
defer storageClient.Close() | ||
bkt := storageClient.Bucket(bucketName) | ||
obj := bkt.Object(objectName) | ||
rc, err := obj.NewReader(ctx) | ||
if err != nil { | ||
return fmt.Errorf("failed to retrieve the object: %v", err) | ||
} | ||
defer rc.Close() | ||
data, err := ioutil.ReadAll(rc) | ||
if err != nil { | ||
return fmt.Errorf("could not read the object's contents: %v", err) | ||
} | ||
// Data now contains the contents of the requested object. | ||
output.Write(data) | ||
return nil | ||
} | ||
|
||
// [END auth_downscoping_token_consumer] |