Edited at

googleapis/google-cloud-goライブラリを使って署名付きURLを発行する


概要


  • 検証に必要なサービスアカウントを準備する

  • Signed URLの発行

  • 失効時にURLにアクセスしたときの挙動

  • Signed URLの再発行

  • gsutilでSigned URLを発行する


Signed URLとは


バケットとオブジェクトに対するクエリ文字列認証のためのメカニズムです。署名付き URLは、時間制限のある読み取りや書き込みのアクセス権を、Googleアカウントを持っているかどうかにかかわらず、URLを知っている全員に許可するための手段です。


署名付き URLより

GCSに置いたファイルをGoogleアカウントがないユーザーに提供したい。ただ、期間に制限を設けたい。

などに使いたくて調べました。ダウンロードURLだけでなく、アップロード用URLの生成も可能です。

Amazon S3ではこの辺(他ユーザーとのオブジェクトの共有)のお話です。


検証


検証に必要なサービスアカウントを準備する

GCSに限った話ではないですが、GCPのAPIを利用するには必要な権限を持ったサービスアカウントが必要です。

GCPの認証・認可全般のお話はこのドキュメントにまとまっています。

https://cloud.google.com/docs/authentication/production

プログラムからサービスアカウント情報を取得するにあたってはどこで実行するか、どう認証情報を保存しているかによって変わります。

ローカルで実行するにあたってはローカル実行用のサービスアカウントを用意し、必要な権限を付与しました。



# サービスアカウントの作成
# Service account name must be between 6 and 30 characters (inclusive), must begin with a lowercase letter, and consist of lowercase alphanumeric characters that can be separated by hyphens
$ gcloud iam service-accounts keys create cli-cred.json --iam-account test-cli@test-p-toshi0607.iam.gserviceaccount.com

# サービスアカウントに対する権限の付与
# roleはできる限り絞ってください
$ gcloud projects add-iam-policy-binding test-p-toshi0607 --member "serviceAccount:test-cli@test-p-toshi0607.iam.gserviceaccount.com" --role "roles/owner"

# 認証情報をファイルとして保存
$ gcloud iam service-accounts keys create cli-cred.json --iam-account test-cli@test-p-toshi0607.iam.gserviceaccount.com

# 環境変数にクレデンシャルの保存場所を設定
#
$ mv cli-cred.json $HOME/.config/gcloud/cli-cred.json
$ export GOOGLE_APPLICATION_CREDENTIALS=$HOME/.config/gcloud/cli-cred.json

認証情報を取得するための方法もライブラリでいくつか提供されています。

たとえば、golang.org/x/oauth2パッケージのgoogle.FindDefaultCredentiralsなど。

https://github.com/golang/oauth2/blob/master/google/default.go#L76

つぎの優先優先順位で認証情報を探してくれます。

上記のように環境変数に設定されたものが最優先されます。

実行環境やクレデンシャルの保存方法によって使い分けてください。

// FindDefaultCredentials searches for "Application Default Credentials".

//
// It looks for credentials in the following places,
// preferring the first location found:
//
// 1. A JSON file whose path is specified by the
// GOOGLE_APPLICATION_CREDENTIALS environment variable.
// 2. A JSON file in a location known to the gcloud command-line tool.
// On Windows, this is %APPDATA%/gcloud/application_default_credentials.json.
// On other systems, $HOME/.config/gcloud/application_default_credentials.json.
// 3. On Google App Engine standard first generation runtimes (<= Go 1.9) it uses
// the appengine.AccessToken function.
// 4. On Google Compute Engine, Google App Engine standard second generation runtimes
// (>= Go 1.11), and Google App Engine flexible environment, it fetches
// credentials from the metadata server.

https://github.com/golang/oauth2/blob/master/google/default.go#L61


Signed URLの発行

簡素なサンプルコードを載せます。

package main

import (
"context"
"fmt"
"net/http"
"time"

"cloud.google.com/go/storage"
"golang.org/x/oauth2/google"
)

const (
bucketName = "surl"
fileName = "test1/cat_haru.jpg"
)

func main() {
ctx := context.Background()

// 必要最低限のスコープを指定してください。
// ScopeFullControl grants permissions to manage your
// data and permissions in Google Cloud Storage.
// ScopeFullControl = raw.DevstorageFullControlScope

// ScopeReadOnly grants permissions to
// view your data in Google Cloud Storage.
// ScopeReadOnly = raw.DevstorageReadOnlyScope

// ScopeReadWrite grants permissions to manage your
// data in Google Cloud Storage.
// ScopeReadWrite = raw.DevstorageReadWriteScope
creds, err := google.FindDefaultCredentials(ctx, storage.ScopeReadOnly)
if err != nil {
// サンプル用です。適切にエラーハンドリングしてください。
panic(err)
}
conf, err := google.JWTConfigFromJSON(creds.JSON, storage.ScopeReadOnly)
if err != nil {
// サンプル用です。適切にエラーハンドリングしてください。
panic(err)
}

expires, _ := time.Parse(time.RFC3339, "2019-05-27T09:33:00-00:00")
// オプションの意味はこのドキュメントを参照してください。
// https://cloud.google.com/storage/docs/access-control/signed-urls
opts := &storage.SignedURLOptions{
GoogleAccessID: conf.Email,
PrivateKey: conf.PrivateKey,
Method: http.MethodGet,
Expires: expires,
}
url, err := storage.SignedURL(bucketName, fileName, opts)
if err != nil {
// サンプル用です。適切にエラーハンドリングしてください。
panic(err)
}
fmt.Println(url)
}

surlというバケットにtest1/cat_haru.jpgというオブジェクト名で画像ファイルを保存してある前提です。


権限不足で作成したURLにアクセスしたときの挙動

URL付与対象のオブジェクトにアクセスする権限がなくてもそれっぽいURLは発行できますが、サービスアカウントにオブジェクトにアクセスする権限がない旨が表示されます。

insufficient_role.png

test-cli@kouzoh-p-toshi0607.iam.gserviceaccount.comにroles/ownerを割り当てたあとにそのままroles/ownerを剥奪した状態で試せます。

$ gcloud projects remove-iam-policy-binding test-p-toshi0607 \

--member='serviceAccount:test-cli@kouzoh-p-toshi0607.iam.gserviceaccount.com' --role='roles/owner'

最低限必要な権限はroles/storage.adminです。roles/storage.objectViewerroles/storage.objectAdminでもAccessDeniedエラーになります。


失効時にURLにアクセスしたときの挙動

expired.png


Signed URLの再発行

バケットのオブジェクトを指定し、新しい期限を設定して再度storage.SignedURL()を実行します。

同一設定のSigned URLを再発行しても発行されるURLは変わりません。

同一のオブジェクトに対して有効な複数のSigned URLも存在可能です。

指すオブジェクトは同じだが有効期限が異なるなど。


gsutilでSigned URLを発行する

コマンドでも発行できます。コマンドのヘルプで必要十分な情報が提供されているので実行結果をそのまま貼ります。



$ gsutil help signurl

NAME
signurl - Create a signed url

SYNOPSIS

gsutil signurl [-c <content_type>] [-d <duration>] [-m <http_method>] \
[-p <password>] [-r <region>] keystore-file url...

DESCRIPTION
The signurl command will generate a signed URL that embeds authentication data
so the URL can be used by someone who does not have a Google account. Please
see the `Signed URLs documentation
<https://cloud.google.com/storage/docs/access-control/signed-urls>`_ for
background about signed URLs.

Multiple gs:// urls may be provided and may contain wildcards. A signed url
will be produced for each provided url, authorized
for the specified HTTP method and valid for the given duration.

Note: Unlike the gsutil ls command, the signurl command does not support
operations on sub-directories. For example, if you run the command:

gsutil signurl <private-key-file> gs://some-bucket/some-object/

The signurl command uses the private key for a service account (the
'<private-key-file>' argument) to generate the cryptographic
signature for the generated URL. The private key file must be in PKCS12
or JSON format. If the private key is encrypted the signed url command will
prompt for the passphrase used to protect the private key file
(default 'notasecret'). For more information regarding generating a private
key for use with the signurl command please see the `Authentication
documentation.
<https://cloud.google.com/storage/docs/authentication#generating-a-private-key>`_

gsutil will look up information about the object "some-object/" (with a
trailing slash) inside bucket "some-bucket", as opposed to operating on
objects nested under gs://some-bucket/some-object. Unless you actually
have an object with that name, the operation will fail.

OPTIONS
-m Specifies the HTTP method to be authorized for use
with the signed url, default is GET. You may also specify
RESUMABLE to create a signed resumable upload start URL. When
using a signed URL to start a resumable upload session, you will
need to specify the 'x-goog-resumable:start' header in the
request or else signature validation will fail.

-d Specifies the duration that the signed url should be valid
for, default duration is 1 hour.

Times may be specified with no suffix (default hours), or
with s = seconds, m = minutes, h = hours, d = days.

This option may be specified multiple times, in which case
the duration the link remains valid is the sum of all the
duration options.

The max duration allowed is 7d.

-c Specifies the content type for which the signed url is
valid for.

-p Specify the keystore password instead of prompting.

-r <region> Specifies the `region
<https://cloud.google.com/storage/docs/locations>`_ in
which the resources for which you are creating signed URLs are
stored.

Default value is 'auto' which will cause gsutil to fetch the
region for the resource. When auto-detecting the region, the
current gsutil user's credentials, not the credentials from the
private-key-file, are used to fetch the bucket's metadata.

This option must be specified and not 'auto' when generating a
signed URL to create a bucket.

USAGE
Create a signed url for downloading an object valid for 10 minutes:

gsutil signurl -d 10m <private-key-file> gs://<bucket>/<object>

Create a signed url, valid for one hour, for uploading a plain text
file via HTTP PUT:

gsutil signurl -m PUT -d 1h -c text/plain <private-key-file> \
gs://<bucket>/<obj>

To construct a signed URL that allows anyone in possession of
the URL to PUT to the specified bucket for one day, creating
an object of Content-Type image/jpg, run:

gsutil signurl -m PUT -d 1d -c image/jpg <private-key-file> \
gs://<bucket>/<obj>

To construct a signed URL that allows anyone in possession of
the URL to POST a resumable upload to the specified bucket for one day,
creating an object of Content-Type image/jpg, run:

gsutil signurl -m RESUMABLE -d 1d -c image/jpg <private-key-file> \
gs://bucket/<obj>


参考