0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

AWSのクレデンシャルをGCP側に渡さず、GCPからAWSのリソースにアクセス

Posted at

目的

Google CloudからAWSのリソースを操作したい。
当然ながら、AWSのアクセスキーやシークレットを、環境変数に保存して扱うや、Google CloudのSecret Managerに保存して利用するなどは、よろしくない。
ので、Google Cloudのサービスアカウントに対して、許可を与えて操作できるようにする。

方法

まずは、Google Cloudでサービスアカウントを作成する。このサービスアカウントでCloud Functionsなどを動かすようにする。
サービスアカウントには、アカウントID(メールアドレスではない)がある。この値を使って、AWS側で許可設定を行う。

AWSでGCPのサービスアカウントに対して、リソース操作の許可を付与する。

locals {
  role_name              = "google-cloud-service-account" // Edit
  service_account_id     = "" // e.g. 123456789
  policies               = [
    "arn:aws:iam::aws:policy/AmazonSESReadOnlyAccess",
  ]
}

resource "aws_iam_role" "this" {
  name               = local.role_name
  assume_role_policy = data.aws_iam_policy_document.this.json
}

data "aws_iam_policy_document" "this" {
  statement {
    actions = ["sts:AssumeRoleWithWebIdentity"]
    effect  = "Allow"
    principals {
      identifiers = ["accounts.google.com"]
      type        = "federated"
    }
    condition {
      test     = "StringEquals"
      variable = "accounts.google.com:sub"
      values   = [local.service_account_id]
    }
  }
}

resource "aws_iam_role_policy_attachment" "this" {
  for_each   = toset(local.policies)
  role       = aws_iam_role.this.name
  policy_arn = each.value
}

次に、Cloud Functionsで以下を作成する。ここで使用するサービスアカウトは、最初で作成したサービスアカウント。
以下のサンプルでは、SESの一覧を取得している。

package handle

import (
	"context"
	"fmt"
	"log/slog"
	"net/http"
	"os"
	"strings"

	"github.com/GoogleCloudPlatform/functions-framework-go/functions"
	"github.com/aws/aws-sdk-go-v2/aws"
	"github.com/aws/aws-sdk-go-v2/config"
	"github.com/aws/aws-sdk-go-v2/credentials"
	"github.com/aws/aws-sdk-go-v2/service/ses"
	"github.com/aws/aws-sdk-go-v2/service/sts"
	"github.com/samber/mo"
	"google.golang.org/api/idtoken"
)

func init() {
	functions.HTTP("handle", handle)
}

func handle(w http.ResponseWriter, _ *http.Request) {
	if err := listSES(); err != nil {
		slog.Error("ses check", slog.String("error", err.Error()))
		http.Error(w, "internal server error", http.StatusInternalServerError)
		return
	}
	w.WriteHeader(http.StatusOK)
}

// listSES SESの一覧を取得
func listSES() error {
	ctx := context.Background()
	cfg, err := config.LoadDefaultConfig(ctx, config.WithRegion("ap-northeast-1"))
	if err != nil {
		return err
	}

	token, err := getToken(ctx).Get()
	if err != nil {
		return fmt.Errorf("get token from metadata: %w", err)
	}

	cred, err := assume(ctx, cfg, token).Get()
	if err != nil {
		return fmt.Errorf("assume role: %w", err)
	}

	cfg, err = config.LoadDefaultConfig(ctx, config.WithRegion("ap-northeast-1"), config.WithCredentialsProvider(cred))
	if err != nil {
		return err
	}
	client := ses.NewFromConfig(cfg)
	output, err := client.ListIdentities(ctx, &ses.ListIdentitiesInput{})
	if err != nil {
		return err
	}

	var result []string
	for _, identity := range output.Identities {
		result = append(result, identity)
	}
	slog.Info("check", slog.String("result", strings.Join(result, ",")))
	return nil
}

// metadataからtokenを取得
func getToken(ctx context.Context) mo.Result[string] {
	s, err := idtoken.NewTokenSource(ctx, "accounts.google.com")
	if err != nil {
		return mo.Err[string](err)
	}
	token, err := s.Token()
	if err != nil {
		return mo.Err[string](err)
	}
	return mo.Ok(token.AccessToken)
}

// tokenを使ってAWSに接続するためのcredentialsを取得
func assume(ctx context.Context, cfg aws.Config, token string) mo.Result[credentials.StaticCredentialsProvider] {
	client := sts.NewFromConfig(cfg)
	out, err := client.AssumeRoleWithWebIdentity(ctx, &sts.AssumeRoleWithWebIdentityInput{
		RoleArn:          aws.String(os.Getenv("ARN")),
		WebIdentityToken: aws.String(token),
		RoleSessionName:  aws.String("cloud-functions"),
	})
	if err != nil {
		return mo.Err[credentials.StaticCredentialsProvider](err)
	}
	return mo.Ok(credentials.NewStaticCredentialsProvider(
		*out.Credentials.AccessKeyId,
		*out.Credentials.SecretAccessKey,
		*out.Credentials.SessionToken,
	))
}
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?