LoginSignup
20
21

More than 5 years have passed since last update.

Google App Engine(Go)を使ってAWSのマネージメントコンソールへGoogle AppsのID/PasswordでSSOする

Last updated at Posted at 2014-07-31

今回はAWS STSのFederation Loginを利用して、Google App Engine(Go)から、Google AppsのID/Passを利用してAWSへSSOする仕組の話です。

コード

ここにあります。
設定すれば多分どこでも使えますが、
IAMのポリシーがベタ書きです。(めんどかった)

経緯

先日土曜日を使って弊社の全エンジニア200名ぐらいを集めて、AWSの勉強会をしました。

色々あってEC2の初心者ハンズオンの講師をしなければならなくて、
約150名ぐらいの人たちをAWSのマネージメントコンソールにログインさせる必要が出てきました。

ここで色々問題が有ります。

  • クレジットカードを持ってない人がいる可能性もあるためAWSアカウントを作ってもらう事は不可
    • 無料枠との兼ね合いで自分のタイミングで作りたいってのも有るだろうし。
    • 当日作るのは時間的にも無理
    • プリペイド式クレカを配るのはコストが高過ぎる
  • IAMユーザを全員分作るってものできるけど、ID/Passwordを発行した場合に、それを全員に配布 & 当日利用してもらうのは混乱を招く可能性がある。
    • 絶対忘れる。
    • 漏れた時のセキュリティ懸念

弊社ではGoogle Appsを利用していてます。
多分そのID/Passwordを忘れている人はいないと思われる。
そこでAWSからSTSを利用してFederation Loginをさせることにしました。

なお、Google Apps Scriptでも同じ仕組を作れます。
というか作った人がいます。

ただGASではFederation Login用URL作成後直接マネージメントコンソールへログイン出来ないので、今回はまじめにappengine上で作ることにしました。

問題

色々と...

  1. AWS SDKもあるのでJavaでやろうと思いましたがappengine上ではAWSの公式SDKは使えない
  2. GoでAWSの公式SDK無い
  3. Goでサードパーティ製のAWS ライブラリあるけどSTS対応してない
    • 最初バグってて他の人が出しているPRを取り込んでなかった...(白目
    • やってくれるのは認証部分のみでパラメータ用のXMLは自分で作る必要がある...(白目

でなんだかんだ出来上がりました。

技術的には...

特段面白いところは無いと思います。
Goに関してはXMLの扱いが楽だったぐらいですねぇ

sts.go
type GetFederationTokenResponse struct {
    RequestId   string      `xml:"ResponseMetadata>RequestId"`
    Credentials Credentials `xml:"GetFederationTokenResult>Credentials"`
}
type Credentials struct {
    SessionToken    string
    SecretAccessKey string
    Expiration      time.Time
    AccessKeyId     string
}

上記のようにタグを利用して定義します。

Google側の認証はappengineに任せています。
つまり何も作っていません。
appengineを認証必須でドメイン限定にする設定とかは他の記事を見ると良いと思います。

またAWS側はSTSで以下の2段階のAPIが必要です
* GetFederationToken
* GetSigninToken

一つ目のAPIでFederation用のTokenを取得して、APIを叩くためのTokenを取得します。
この段階でIAMのアクセスポリシーを設定します。
ちなみに今回の勉強会では以下の様な設定です。

  • 建てられるEC2はt2のみ
  • 作れるEBSは8GB以下(要は8GBになるはず)

実際の環境ではこれにさらにTokyoリージョン限定なども含まれています。

そして、二つ目のAPIでマネージメントコンソールへのログイン用トークンを取得します。

このSTS周りのフルコードは下の方に乗っけておきます。

まとめ

実際勉強会自体でこのマネージメントコンソールへのログインで問題は発生しませんでした。
また後々書きますが、Cloud Trailで全員分の操作ログも取ることができたので非常に良かったです。

Google Appsを使っている企業で、ユーザにマネージメントコンソールを触らせる場合は有りかもしれません。

コード

sts.go
package main

import (
    "github.com/bridger/aws4"
    "time"
    "net/url"
    "encoding/xml"
    "strconv"
    "encoding/json"
    "fmt"
)

const (
    signinUrl = "https://signin.aws.amazon.com/federation"
)

type GetFederationTokenResponse struct {
    RequestId   string      `xml:"ResponseMetadata>RequestId"`
    Credentials Credentials `xml:"GetFederationTokenResult>Credentials"`
}
type Credentials struct {
    SessionToken    string
    SecretAccessKey string
    Expiration      time.Time
    AccessKeyId     string
}

type SigninToken struct {
    SigninToken string
}

type Session struct {
    SessionId    string `json:"sessionId"`
    SessionToken string `json:"sessionToken"`
    SessionKey   string `json:"sessionKey"`
}


const (
    DEFAULT_DURATION_SECONDS = 43200
    DEFAULT_DESTINATION_URL = "https://console.aws.amazon.com/console/home"
)

type Sts struct {
    Client *aws4.Client
}

func (t *Sts) GetFederationToken(username , policy string, durationSeconds int) (result *GetFederationTokenResponse, err error) {

    vals := make(url.Values)

    vals.Set("Version", "2011-06-15")
    vals.Set("Action", "GetFederationToken")
    vals.Set("Name", username)
    vals.Set("Policy", policy)
    vals.Set("DurationSeconds", strconv.Itoa(durationSeconds))

    res, err := t.Client.PostForm("https://sts.amazonaws.com/", vals)

    if err != nil {
        return
    }

    result = new(GetFederationTokenResponse)
    if err = xml.NewDecoder(res.Body).Decode(&result); err != nil {
        return
    }

    return
}

func (t *Sts) GetSigninToken(session *Session) (result *SigninToken, err error){
    sessionJson, err := json.Marshal(session)

    if err != nil {
        return
    }

    vals := make(url.Values)

    vals.Set("Action", "getSigninToken")
    vals.Set("SessionType", "json")
    vals.Set("Session", string(sessionJson))

    res, err := t.Client.Get(signinUrl + "?" + vals.Encode())

    if err != nil {
        return
    }

    result = new(SigninToken)
    if err = json.NewDecoder(res.Body).Decode(&result); err != nil {
        return
    }
    return
}

func (t *Sts) GenerateFederatedLoginUrl(signinToken *SigninToken, issuer, destination string) string {
    vals := make(url.Values)

    vals.Set("Action", "login")
    vals.Set("SigninToken", signinToken.SigninToken)
    vals.Set("Issuer", issuer)
    vals.Set("Destination", destination)

    return fmt.Sprintf("%s?%s", signinUrl, vals.Encode())
}
20
21
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
20
21