EKS on FargateでArgoWorkflowを利用したJob実行基盤の構築


f:id:cloudfish:20201215101142p:plain:w300
 遅ればせながらEKS on Fargateを使ってバッチ処理を行うことになったので検証を行いました。
 通常、EKS上でジョブを実行する場合、Webサービス等の実行ノードと同じかもしくは分けてバッチジョブ用のノードを用意することになるかと思いますが、ノードを分けない場合は、バッチ処理による負荷がサービスへ影響を与える可能性がありますし、ノードを分ける場合は、利用しない間も起動しておく必要があることからコスト面が課題になってきます。
 そのため、バッチ処理をFargate上で実行することで、処理の負荷の影響を切り離してかつ必要な時に必要なリソースだけ利用することでコストの最適化も行えるのではないかと考え検証を行いました。今回は検証過程で得た知見を紹介したいと思います。

バッチ処理でEKS on Fargateを使う理由

 同一ノード上でバッチ処理を行うとどうしても一定時間、CPUやメモリを占有するということが起きてしまいます。そのためどうにかして負荷を下げようとすると、まずはバッチ専用ノードを立てるという案が出てきますが、これはバッチ処理が1日に数回程度の利用状況だとコスト的に見合わないことになります。
 次に考えられる案として、AWS前提の話となりますが、LambdaやAWS Batchの利用かと思います。現状、弊社では開発プロセスkubernetesに寄せた形にしているため、それに関する知見も多く蓄積されているので、ここであえて開発者にLambdaやAWS Batchを学習してもらう必要もないと考えました。
 これを検討し始めた時点ではLambdaのコンテナイメージ利用やAWS BatchのFargate利用などの機能がまだ出ていなかったので上記のような結論になりましたが、こうした機能が使えるのであれば、社内にある知見を活用しつつうまく利用できる方法を検討していきたいと考えています。
 こうした状況を踏まえて、kubernetesの知見を活かしつつバッチ処理の負荷を切り離す方法としてFargateの利用が最も効果的ではないかと考えました。

EKS on FargateでのJobの実行方法

 Fargate上でPodを実行するための設定はそれほど難しくありません。Fargate Profileを設定し、そこで指定したNamespaceでJobをapplyするだけでFargateノードでJobが実行されます。
 また、FargateProfileは既存のクラスタに対して後から追加可能となっており、Fargateノードで起動するかどうかはNamespaceだけでなくlabelで制御することも可能です。
 以下はeksctlのcluster.yamlへの設定方法になります。対象のnamespaceがfargate-testとなり、labelにfargate: onが指定されたJobのみFargateノードで起動される設定になります。

  :
fargateProfiles:
  - name: fargate-profile
    selectors:
      - namespace: fargate-test
        labels:
          fargate: 'on'

FargateProfileの作成

eksctl create fargatargateprofile -f cluster.yaml

EKS on FargateでJobを実行する時の問題点

 テスト用のJobをFargate上で動かしたところ、本番運用にあたっていくつかの問題に気づきました。

ジョブの実行完了後に誰がジョブを削除するか?

 Jobの実行後にステータスがCompletedとなったPodは意図的に削除しない限り残り続けることとなります。EC2ノード上だと実行が完了しているので、それでリソースが使われるということはなく単にゴミが残るだけとなりますが、Fargateの場合、Fargateノードも残ることとなりその分課金され続けてしまいます。EKSではPodの終了後に自動で削除してくれるttlSecondsAfterFinishedを使うことはできないので、Podの削除方法については自前で仕組みを検討する必要があります。

モニタリング、ログの取得

 EKSの監視はDatadogなどのagentをDaemonsetで起動させてモニタリングしてるケースが多いかと思いますが、Fargateではコンテナのみの提供となるためDaemonsetは使えません。そのため、sidecar方式でPodにagentを同居させる必要があります。しかしながら、こうしたagent系のコンテナをsidecarとして実行させると、メインのJobの実行は完了したもののagentが終了しないのでJobがいつまでも起動したままとなります。そのため、どうにかしてagentをJobの完了に合わせて終了させる必要があります。
以下のブログはアプリの終了時にsidecarをkillするということをやっているようです。
tech.recruit-mp.co.jp

そしてこのブログを書いている間に、reinvent2020が始まり以下の機能がリリースされました。ログに関してはsidecarで取得することなくcloudwatch logsに送ることが可能になりました。
aws.amazon.com

起動時間の考慮

 Fargateノードで起動する場合、ノードを起動するまで思ったよりも時間がかかりました。検証時には毎回2-3分かかりました。調べたところ以下のブログで検証されているのですが、起動時間の大半はイメージのpullにかかっているため、主にイメージサイズに依存します。そのため、時間どおり正確に処理を実行したいなどの要件がある場合は、実行基盤としてはそぐわない場合もあると思います。
qiita.com

DaemonsetでデプロイされたPodが起動しようとする

 検証時のクラスターにはEFS CNIドライバーが起動されていました。テスト用のJobを実行すると、Fargateノードの起動を検知して同様にEFS CNDドライバもFargateノードで起動しようとする動きが見られました。EFS CNIドライバーはFargateノードではエラーで起動できなかったのでメインのPodの削除とともに消えましたが、ものによっては起動が成功するかもしれないので、環境によっては注意が必要になります。

開発者とインフラ担当者の責任分界点

 弊社では、モニタリングやログ取得については、インフラ担当の責任範囲としており、アプリケーションに関するManifestは開発者の責任範囲としています。しかし、Fargateを利用するとモニタリングやログの取得はsidecar方式での利用となるため、設定を開発者へ委ねることになります。また、Fargateノードで起動するしないの設定も開発者に意識してもらう必要がありますので、こうした点を意識して設定してもらう必要があります。

その他

他にも、現状では、SecurityGroupが任意のものを設定できないといった問題がありました。このあたりは、今後の改善に期待ですね。

Argo Workflowの利用

 EKS on FargateでJobを実行する場合の大きな問題点は、上記で書いたようにJob実行後のPodをどう削除するかとsidecarとなるコンテナをどのようにkillするかという点になるかと思います。これらを解決できないとJob実行後もFargateノードが残り続けることとなるためFargateノードを使う意味がなくなります。何か良い方法がないか調べていたところ、Argo Workflowを利用することで解決できそうなことがわかりました。
 Argo WorkflowとはオープンソースのJobのワークフローエンジンとなり、UIでジョブ管理ができるツールになります。詳細は以下のリンクを参照してください。Argo WorkflowでJobを管理、実行することで完了後のPodも自動で削除してくれますし、メインのコンテナ終了後にsidecarコンテナもあわせてkillしてくれるので、Jobを実行するにあたっての大きな懸念点が解消されました。
argoproj.github.io

インストール及び設定

インストール方法

 インストール方法については、cluster-installとnamespace-installの大きく2種類があります。
cluster-installとはクラスタのどのnamespaceでもJobを実行できるようにするインストール方式です。一方でnamespace-installは、argo workflowをインストールしたnamespaceと同じnamespaceでしかJobを実行できません。
 現状ではargoworkflowはユーザーごとの権限制御があまり細かく設定できなさそうなので、namespaceごとにユーザーを限定したいのであればnamespace-installを利用すればいいかと思います。今回はnamespaceごとにインストールしたくはないのとあまり細かい制御は必要ないのでcluster-installとしました。

gitからargo workflowの取得
git clone https://github.com/argoproj
configmapを編集

 Fargate上で動かすためには、ランタイムの設定であるcontainerRuntimeExecutorをk8sapiに変更する必要があります。ここが少しハマったポイントなのですが、デフォルト設定のdockerのままだと実行したJobのステータスがPendingのまま実行されませんでした。
 また、workflowのデフォルト設定もできるので、デフォルトの値を設定しておくことで実行時のミスを減らせるかと思います。他の項目については必要に応じて編集してください。

cd argo/manifests/
vim base/workflow-controller/workflow-controller-configmap.yaml
workflow-controller-configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: workflow-controller-configmap
data:
  config: | 

      :
      :
    containerRuntimeExecutor: k8sapi
      :
      :

kustomizeでビルドし、applyしてください。

cd cluster-install
kustomize build . > install.yaml
kubectl apply -f install.yaml -n argo

ポートフォワードします。必要に応じてALBなどを設定してください。

kubectl -n argo port-forward deployment/argo-server 2746:2746

以下のURLにアクセスするとArgo WorkflowのUIにアクセスできます。本番用途ではALBの利用やSSOなどの利用を検討してもいいかと思います。
http://localhost:2746

f:id:cloudfish:20201215141548p:plain

サービスアカウント

 Argo WorkflowでJobを実行するには、以下の権限を持ったサービスアカウントが必要になります。以下の内容を保存してapplyしてください。
 AWSのリソースにアクセスする必要がある場合は、eksctlなどでサービスアカウントを作成しているかと思いますが、そこで作成したサービスアカウントに以下のロールをバインドする必要があります。このあたりはeksctlで一括管理したいですが、今のところいい方法はなさそうです。

vim fargate-serviceaccount.yaml

apiVersion: v1
kind: ServiceAccount
metadata:
  name: fargate-serviceaccount
  namespace: fargate-test

---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: workflow-role
rules:
- apiGroups:
  - ""
  resources:
  - pods
  verbs:
  - get
  - watch
  - patch
- apiGroups:
  - ""
  resources:
  - pods/log
  verbs:
  - get
  - watch

---
kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: argo-batch-binding
  namespace: fargate-test
subjects:
  - kind: ServiceAccount
    name: fargate-serviceaccount
  namespace: fargate-test
roleRef:
  kind: Role
  name: workflow-role
  apiGroup: rbac.authorization.k8s.io
fluentbitの設定

Jobを実行するnamespaceでfluentbit用のConfigMapを設定しておきます。
以下はログをdatadogに送る場合のサンプルになります。DatadogのAPIキーが必要になります。

apiVersion: v1
kind: ConfigMap
metadata:
  name: fluentbit-config
  namespace: fargate-test
data:
  # Configuration files: server, input, filters and output
  # ======================================================
  fluent-bit.conf: |
    [INPUT]
        Name              tail
        Tag               *.logs
        Path              /var/log/*.log
        DB                /var/log/logs.db
        Mem_Buf_Limit     5MB
        Skip_Long_Lines   On
        Refresh_Interval  10
    [OUTPUT]
        Name        datadog
        Match       *
        compress    gzip
        apikey      [your datadog api key]
        dd_service  hello-world
        dd_source   job
        dd_tags     vendor:sample,env:stg
Jobの実行

以下はJobのサンプルになります。5秒ごとに「hello world」を出力するだけのサンプルです。

apiVersion: argoproj.io/v1alpha1
kind: Workflow
metadata:
  generateName: hello-world-
spec:
  entrypoint: helloworld
  templates:
  - name: helloworld
    serviceAccountName: fargate-serviceaccount
    metadata:
      labels:
        fargate: "on"
    container:
      image: busybox
      args:
      - /bin/sh
      - -c
      - >
        for i in 1 2 3 4 5;
        do 
          echo hello world >> /var/log/app.log;
          sleep 5;
        done
      volumeMounts:
        - name: varlog
          mountPath: /var/log
      resources:
        requests:
          memory: 512Mi
          cpu: 500m
    sidecars:
    - name: fluentbit-agent
      image: amazon/aws-for-fluent-bit:latest
      ports:
        - containerPort: 2020
      env:
      - name: FLUENTD_HOST
        value: "fluentd"
      - name: FLUENTD_PORT
        value: "24224"
      - name: POD_NAME
        valueFrom:
          fieldRef:
            fieldPath: metadata.name
      - name: POD_NAMESPACE
        valueFrom:
          fieldRef:
            fieldPath: metadata.namespace
      volumeMounts:
      - name: varlog
        mountPath: /var/log
      - name: fluentbit-config
        mountPath: /fluent-bit/etc/
  volumes:
  - name: varlog
    emptyDir: {}
  - name: fluentbit-config
    configMap:
      name: fluentbit-config

  ttlStrategy:
    secondsAfterCompletion: 30
    secondsAfterSuccess: 30
    secondsAfterFailure: 30

以下設定ポイントになります。

  • namesapceとlabelsの[fargate: 'on']設定がFargateで起動するかどうかを制御します。
  • アプリケーションからログを/var/log/app.logにリダイレクトすることでfluentbitがログを収集します。
  • resourcesでのリソース要求設定で必要なFargateのスペックが割り当てられます。
  • ttlStrategyでJobが完了した場合などで、何秒後にPodを削除するか設定します。

fargateラベルやttlStrategyなどは設定が漏れると困るのでデフォルト値として設定しておいてもいいかもしれません。

上記のマニフェストファイルを適当な名前で保存してUIから実行するか以下のコマンドを実行するとジョブが実行されます。

argo submit helloworld.yaml

ArgoWorkflowによるsidecarのkill制御

 ArgoWorkflowでは、mainコンテナが終了するとsidecarコンテナもkillしてJobを終了させてくれますが、sidecarコンテナが複数あるとtimeoutが発生し、一つしかkillされませんでした。コントローラ用のコンテナからはシグナルを全てのコンテナに送っているものの片方しか終了させれない状況となっていました。そのため、当初はfluent-bitとdatadog-agentをsidecarとして配置し、ログとメトリクスを取得する予定でしたが、メトリクスの取得は断念しました。
 しかし、上記でも紹介しましたがEKS on Fargateで直接Fargateのログをcloudwatch logsなどに送ってくれる機能追加が行われたため、これを利用しつつdatadog-agentをsidecarとして配置することでメトリクスを取得できそうです。ただし、現時点では利用中の環境のEKSプラットフォームバージョンが上がってないためこの機能を利用できないので、プラットフォームバージョンが上がるまで待つこととしました。ちなみにEKSのプラットフォームバージョンは利用者が意図的にあげることはできないようです。AWS側で定期的にプラットフォームのバージョンを更新しているためそれを待つ必要があります。どうしてもアップさせたい場合は、新しくクラスタを作り直すかkubernetesのメジャーバージョンをあげる必要があります。

まとめ

 EKS on Fargateでのバッチ処理については、ある程度のデータ量を集計する必要があるなど、まとまったリソースが必要な処理には適していると思います。
向いていない処理としては、起動時間に数分必要となるので、正確な時間に実行する必要のある処理や毎分実行する処理、またイベント駆動的な処理などにもあまり適していないと感じました。
 また、ログはEKSの標準機能でCloudWatchlogsから取得できるようになりましたが、メトリクスを取得するにはsidecarで取得せざるを得ない状況です。sidecarをArgoWorkflowで制御できるとはいえ、Jobごとに書く必要があるため冗長になってしまいます。できればメトリクスについてもcloudwatchから取得できるようにして欲しいところです。AWSコンソールでもkubernetesのワークロードが見れるようになったので、近いうちに対応してくれるのではないかと期待しています。
 バッチ処理のようなバックグラウンドで行われる処理の負荷をどう下げるかというのは頭を悩ませていましたが、Fargateをうまく活用することで一時的な負荷を切り離しつつかつコストも抑えれることが分かりましたので、今後は色々な環境で利用していきたいと思います。

GuacamoleがSAML対応したのでOneloginと連携してみた


f:id:cloudfish:20200714223943p:plain
全国のGuacamoleファンの皆さん、2020/06/28にGuacamoleのバージョン1.2.0がリリースされました。バージョン1.2.0の主な特徴としてはSAML2.0がサポートがされたことが、個人的には一番嬉しい機能でした。2016年にSAML対応のissue登録されていたのでかなり対応は大変だったのかもしれません。早速検証してみたのですが、SAMLモジュールについても他の認証モジュールと同様にGuacamole特有のjarファイルのアドオン方式となっているため、少し手こずりましたが、無事動作確認ができましたので、今回はSAMLの設定方法について紹介したいと思います。

前提

以前のブログで構築したGucamole環境(Docker)をベースに設定を進めますので、基本の環境についてはこのブログを参照してください。

構成

ELB + EC2構成とします。
f:id:cloudfish:20200714224027p:plain

設定手順

SAMLモジュールの取り込み

公式で提供されているguacamole(1.2.0)のDockerイメージについては、ldapモジュールのように起動時に自動で取り込みしてくれるような設定となっていません。そのため、SAMLモジュールを取り込んだイメージを作成する必要があります。このイメージについては、こちらにも置いてありますので、直接参照してもらってもいいのですが、ここではローカルでビルドする想定で手順を記載しています。

Dockerイメージの作成

公式のイメージをベースにstart.shにsamlの起動設定を追加しました。環境変数SAML_CALLBACK_URLが設定されていればSAMLモジュールを取り込むようにしています。

以下のコマンドでgithubからDockerfile等を取得します。

git clone https://github.com/cloudfish7/guacamole-client-saml.git

イメージを作成します。

cd guacamole-client-saml
docker build -t guacamole-client-saml:1.2.0 .

Oneloginの設定

「Applications」タブを選択し、「Add App」をクリックして、「SAML Test Connector」を検索してください。一覧から「SAML Test Connector (IdP w/ attr w/ sign response)」を選択します。
Display Nameに適当な名前を入力し保存します。
f:id:cloudfish:20200714110623p:plain

保存後に右上の「More Actions」からSAML Metadataをダウンロードします。
f:id:cloudfish:20200714110712p:plain:w300

左ペインの「Configuration」を選択し、「Recipient」、「ACS (Consumer) URL Validator」、「ACS (Consumer) URL」に以下のURLを設定し、保存します。

http://[YOUR_ELB_DNS]/guacamole/api/ext/saml/callback

f:id:cloudfish:20200714110844p:plain

Usersでログイン可能なユーザーを追加しておいてください。

docker-composeの作成

 以下の通りdocker-composeファイルを作成してください。guacamoleのイメージについては、Docker Hubから取得するかlocalから取得するかによって内容を変更してください。
 SAML_IDP_URLは利用しているのIDPのURLになります。oneloginの場合、以下のようなURLとなります。

https://[YOUR_SUBDOMAIN].onelogin.com/

 SAML_ENTITY_IDについては、oneloginから取得したmetadataファイルにENTITY_IDがありますので、それを記載します。
 SAML_CALLBACK_URLについては、guacamoleのURLを記載してください。なぜこの設定が必要なのかはドキュメントを読んでもよく分かりませんでした。また、設定項目としてSAML_IDP_METADATA_URLという項目があるので、これだけ設定すればいけるかと思いましたが、うまく動作しなかったので最終的に上記の設定項目としました。

version: "3"

services:
  guacd:
    image: guacamole/guacd:1.2.0
    restart: always
    expose:
    - "4822"
    environment:
    - GUACD_LOG_LEVEL=debug
  guacamole:
    # From Docker Hub
    #image: cloudfish/guacamole-client-saml:1.2.0
    # From local
    image: guacamole-client-saml:1.2.0
    restart: always
    links:
      - guacd:guacd
    ports:
    - "8080:8080"
    environment:
    - GUACD_HOSTNAME=guacd      
    - SAML_IDP_URL=[YOUR_IDP_URL]
    - SAML_ENTITY_ID=[YOUR_ENTITY_ID]
    - SAML_CALLBACK_URL=http://ELB_DNS/guacamole
    - MYSQL_HOSTNAME=mysql
    - MYSQL_DATABASE=guacamole
    - MYSQL_USER=guacamole
    - MYSQL_PASSWORD=guacamole
  mysql:
    image: mysql/mysql-server:5.7
    restart: always
    volumes:
    - "./mysql/data:/var/lib/mysql"
    - "./mysql/init:/docker-entrypoint-initdb.d"
    expose:
    - "3306"
    ports:
    - "3306:3306"
    environment:
    - MYSQL_DATABASE=guacamole
    - MYSQL_USER=guacamole
    - MYSQL_PASSWORD=guacamole||<

** ディレクトリの作成
docker-compose.yamlファイルと同じ階層に以下のディレクトリを作成します。
>|csh|
mkdir -p mysql/data
mkdir -p mysql/init
tree -d mysql/
mysql/
├── data
└── init

初期データ作成

以下のコマンドで初期化用のスクリプトを作成します。

docker run --rm guacamole-client-saml:1.2.0 /opt/guacamole/bin/initdb.sh --mysql > mysql/init/initdb.sql

起動

docker-compose up -d

ログを確認して正常に起動しているかと、SAMLモジュールが読み込まれていることを確認しておいてください。

ログイン確認

以下のURLにアクセスするとOneloginのログイン画面にリダイレクトされます。
http://ELB_DNS/guacamole/
ポータル画面から「Guacamole」選択します。
f:id:cloudfish:20200715101256p:plain
正しく設定できていれば、Guacamoleにログインできます。
f:id:cloudfish:20200715101458p:plain

SAML設定をすると管理者ユーザー(guacadmin)でログインできなくなります。そのため、予めSAML設定を外して起動することでパスワードログインが可能になりますので、Oneloginユーザーと同名のユーザーを作成して管理者権限を付与しておく必要があります。

まとめ

これまでGuacamoleの認証については、独自管理するかIDPなどと連携させたい場合はLDAPを使うしかありませんでしたが、SAML連携が可能となったことでよりセキュアに運用できるようになったと思います。ここではOneloginとの連携方法について書きましたが、G-Suiteでも設定できたとの報告もありましたので恐らく他のIDPにおいても利用可能だと思います。ぜひ利用を検討してみてください。

OneloginでRedashのSAML設定


f:id:cloudfish:20200629103518p:plain
前回に続いてOneloginネタになります。社内ツールとしてデータの可視化にRedashを使っていますので、今回はOneloginとRedashをSAML連携させてみました。

Redashの構築

まずはRedashを構築しましょう。Redash公式でAMIを用意してくれていますので今回はそれを使います。

構成図

構成については、通常のELB + EC2(Redash)で構築します。
f:id:cloudfish:20200620172432p:plain

ELB

今回は検証のため簡単な構成とするためELBは80番ポートで受けてそのまま80番でインスタンスに流します。本番で使うときはSSLを使うことを検討してください。

Redash

ブログ執筆時点の東京リージョンでのAMIはami-060741a96307668beになりますので、これを起動します。
各リージョンで提供されているAMIは以下を参照してください。正常に起動できればELBにアタッチしておいてください。
redash.io

NATが用意されているのであれば、SSMも使えるのでプライベートサブネットで起動する方がセキュアだと思います。

Onelogin側でSAML設定

「Application」から「Add App」をクリックし、「SAML Test Connector (IdP w/ attr w/ sign response)」を選択します。
Display Nameを入力しSaveします。
f:id:cloudfish:20200620180259p:plain

Configuration

Recipient、ACS (Consumer) URL Validator、ACS (Consumer) URLに以下を設定します。
http://ELB_DNS/saml/callback?org_slug=default

f:id:cloudfish:20200620201751p:plain

Parameter

カスタムパラメータとしてFirstNameとLastNameを追加します

FirstName

f:id:cloudfish:20200620202340p:plain:w300

LastName

f:id:cloudfish:20200620202458p:plain:w300

Metadataの取得

「More Actions」の「SAML Metadata」を右クリックでリンク先のURLをコピーして控えたうえでMetadataをダウンロードする。
f:id:cloudfish:20200621154430p:plain:w300
ダウンロードしたMetadataを開いてentityIDとNameIDFormatの値を控えておきます。

RedashのSAML設定

管理者でSettingから以下の設定を行う。

SAML Enabled チェック
SAML Metadata URL metadataのURLをセット
SAML Entity ID metadata内のentityIDをセット
SAML NameID Format metadata内のNameIDFormatをセット

f:id:cloudfish:20200621155329p:plain:w300

ログイン確認

設定完了後にSAMLでログインすると、以下のようなエラーが発生します。
f:id:cloudfish:20200622120958p:plain:w300

コンテナのログを確認すると以下のようなエラーが発生していました。

server_1            | [2020-06-22 03:08:51,313][PID:9][INFO][saml2.entity] HTTP REDIRECT
server_1            | [2020-06-22 03:08:51,314][PID:9][INFO][metrics] method=GET path=/saml/login endpoint=saml_auth_sp_initiated status=302 content_type=text/html; charset=utf-8 content_length=1723 duration=441.55 query_count=1 query_duration=1.04
nginx_1             | 139.101.233.32 - - [22/Jun/2020:03:08:51 +0000] "GET /saml/login?next=%2Fsettings%2Forganization HTTP/1.1" 302 1723 "http://ec2-54-249-199-98.ap-northeast-1.compute.amazonaws.com/login?next=http%3A%2F%2Fec2-54-249-199-98.ap-northeast-1.compute.amazonaws.com%2Fsettings%2Forganization" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.61 Safari/537.36" "-"
server_1            | [2020-06-22 03:08:53,456][PID:10][INFO][saml2.response] status: <?xml version='1.0' encoding='UTF-8'?>
server_1            | <ns0:Status xmlns:ns0="urn:oasis:names:tc:SAML:2.0:protocol"><ns0:StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:Success" /></ns0:Status>
server_1            | [2020-06-22 03:08:53,458][PID:10][ERROR][saml2.client_base] XML parse error: Signature missing for assertion
server_1            | [2020-06-22 03:08:53,459][PID:10][ERROR][saml_auth] Failed to parse SAML response
server_1            | Traceback (most recent call last):
server_1            |   File "/app/redash/authentication/saml_auth.py", line 73, in idp_initiated
server_1            |     entity.BINDING_HTTP_POST)
server_1            |   File "/usr/local/lib/python2.7/site-packages/saml2/client_base.py", line 702, in parse_authn_request_response
server_1            |     binding, **kwargs)
server_1            |   File "/usr/local/lib/python2.7/site-packages/saml2/entity.py", line 1170, in _parse_response
server_1            |     response = response.verify(keys)
server_1            |   File "/usr/local/lib/python2.7/site-packages/saml2/response.py", line 1018, in verify
server_1            |     if self.parse_assertion(keys):
server_1            |   File "/usr/local/lib/python2.7/site-packages/saml2/response.py", line 930, in parse_assertion
server_1            |     if not self._assertion(assertion, False):
server_1            |   File "/usr/local/lib/python2.7/site-packages/saml2/response.py", line 781, in _assertion
server_1            |     raise SignatureError("Signature missing for assertion")
server_1            | SignatureError: Signature missing for assertion
server_1            | [2020-06-22 03:08:53,465][PID:10][INFO][metrics] method=POST path=/saml/callback endpoint=saml_auth_idp_initiated status=302 content_type=text/html; charset=utf-8 content_length=219 duration=560.91 query_count=1 query_duration=1.02

エラーでググってみたところ以下のような対処方法が見つかりましたのでソースを修正します。
stackoverflow.com

Redashの修正

上記のエラーを回避するためソースを修正する必要があります。そのため、Redashコンテナのイメージを作り直します。

ソースの取得

まずは、Redash用サーバーにsshログインします。公式AMIはv8.0.0がベースになっていますので、v8系のソースを取得します。以下のコマンドでv8.0.2をが取得できます。

git clone https://github.com/getredash/redash.git
git checkout a16f551e22c6288df6f067aa12caa5afd9a8f1dd

saml_auth.pyの修正

以下のソースを修正します。

/redash/redash/authentication/saml_auth.py

以下のオプションをTrueからFalseに修正してください。

'want_assertions_signed': False,

コンテナのビルド

インスタンスタイプがt2.smallだとビルドが途中で失敗しました。t2.mediumかt2.largeくらいあった方がいいかもしれません。

docker build -t redash_onelogin .

docker-composeの修正

以下のファイルのdockerイメージを修正します。
/opt/redash/docker-compose.yml

version: "2"
x-redash-service: &redash-service
  #image: redash/redash:8.0.0.b32245
  image: redash_onelogin
  depends_on:
    - postgres
    - redis
  env_file: /opt/redash/env
  restart: always
services:
  server:
    <<: *redash-service
    command: server
    ports:
      - "5000:5000"
    environment:
      REDASH_WEB_WORKERS: 4
  scheduler:
    <<: *redash-service
    command: scheduler
    environment:
      QUEUES: "celery"
      WORKERS_COUNT: 1
  scheduled_worker:
    <<: *redash-service
    command: worker
    environment:
      QUEUES: "scheduled_queries,schemas"
      WORKERS_COUNT: 1
  adhoc_worker:
    <<: *redash-service
    command: worker
    environment:
      QUEUES: "queries"
      WORKERS_COUNT: 2
  redis:
    image: redis:5.0-alpine
    restart: always
  postgres:
    image: postgres:9.6-alpine
    env_file: /opt/redash/env
    volumes:
      - /opt/redash/postgres-data:/var/lib/postgresql/data
    restart: always
  nginx:
    image: redash/nginx:latest
    ports:
      - "80:80"
    depends_on:
      - server
    links:
      - server:redash
    restart: always
||< 

以下のコマンドで一旦ストップさせてコンテナを再起動させます。
>|csh|
docker-compose stop
docker-compose up -d

ログイン確認

再度ログイン確認したところ無事ログインできました。
f:id:cloudfish:20200622121830p:plain:w300
f:id:cloudfish:20200622121800p:plain:w400

まとめ

RedashのSAML連携は簡単にできそうでしたが、意外とハマりどころも多く、またOneloginの設定方法もわかっていなかったので思ったより時間がかかりました。もし同じような組み合わせで検討されているようであれば参考にしていただけたらと思います。

OneloginとGuacamoleでSSO設定


f:id:cloudfish:20200417102427p:plain:w200
最近社内のユーザー認証周りの整理を実施しており、その一環でIDPの導入を検討しています。現在Oneloginの動作検証を行っており、Guacamoleのユーザー認証をOneloginと連携してSSOできるか検証を行いました。
Gucamoleについて詳しく知りたい方は、以前書いたブログを参照してみてください。Guacamoleの現在のバージョン(v1.1.0)では、残念ながらSAML連携はできません。(実装は進められているようです。)そのため、OneloginのVirtual LDAPを使ってSSO連携させたいと思います。

前提

以前のブログで構築したGucamole環境をベースに設定を進めます。

OneloginでVLDAPを設定する

以下のようにVLDAPサービスを有効化します。
f:id:cloudfish:20200613143623p:plain:w400

stunnelコンテナの構築

OneloginのVLDAPにはLDAPS(SSLまたはTLSを使用してLDAP通信する)での接続となります。Guacamoleから直接LDAPSで接続するのは少し大変そうなのでstunnelにプロキシさせることとしました。そのためGuacamoleからはstunnelにLDAP接続します。
まずはstunnelコンテナを作ります。前回立てたサーバーにssh接続を行い、適当なディレクトリで以下の2つのファイルを作成します。

Dockerfileの作成

FROM ubuntu
RUN apt-get -y update && apt-get -y upgrade
RUN apt-get -y install stunnel4

COPY docker-entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh
CMD ["/entrypoint.sh"]

docker-entrypoint.shの作成

#! /bin/sh

# Create configulation
cat <<EOF > /etc/stunnel/ldap.conf
foreground = yes
[ldap]
client = yes
accept = $ACCEPT_IP_ADDR:$ACCEPT_PORT
connect = $LDAP_CONNECT_URL:$LDAP_CONNECT_PORT
EOF

# enable stunnel
echo ENABLED=1 >> /etc/default/stunnel4

# start stunnel4
echo "Start stunnel4"
/usr/bin/stunnel4 /etc/stunnel/ldap.conf

イメージのビルド

以下のコマンドでイメージをビルドします。正常に作成できていればstunnel_oneloginという名前でイメージが作成されるはずです。

docker build -t stunnel_onelogin .

docker-composeの作成

${your_domain}には自身のOneloginのサブドメインを設定してください。
stunnelコンテナは、ポート1636でguacamoleコンテナから接続を待ち受けを行い、接続があればOneloginのVLDAPのエンドポイント(ldap.us.onelogin.com)のポート636に接続します。

version: "3"

services:
  guacd:
    image: guacamole/guacd:latest
    restart: always
    expose:
    - "4822"
    environment:
    - GUACD_LOG_LEVEL=debug
  guacamole:
    image: guacamole_latest
    restart: always
    links:
      - guacd:guacd
    ports:
    - "8080:8080"
    environment:
    - GUACD_HOSTNAME=guacd
    - GUACD_LOG_LEVEL=debug
    - LDAP_HOSTNAME=stunnel
    - LDAP_PORT=1636
    - LDAP_ENCRYPTION_METHOD=none
    - LDAP_USER_BASE_DN=cn=email,ou=users,dc=${your_domain},dc=onelogin,dc=com
    - LDAP_USERNAME_ATTRIBUTE=cn
    - LDAP_CONFIG_BASE_DN=ou=users,dc=${your_domain},dc=onelogin,dc=com
    #- TOTP_ENABLED=true
    - MYSQL_HOSTNAME=mysql
    - MYSQL_DATABASE=guacamole
    - MYSQL_USER=guacamole
    - MYSQL_PASSWORD=guacamole
  mysql:
    image: mysql/mysql-server:5.7
    restart: always
    volumes:
    - "./mysql/data:/var/lib/mysql"
    - "./mysql/init:/docker-entrypoint-initdb.d"
    expose:
    - "3306"
    ports:
    - "3306:3306"
    environment:
    - MYSQL_DATABASE=guacamole
    - MYSQL_USER=guacamole
    - MYSQL_PASSWORD=guacamole

  stunnel:
    image: stunnel_onelogin
    restart: always
    ports:
    - "1636:1636"
    environment:
    - ACCEPT_IP_ADDR=0.0.0.0
    - ACCEPT_PORT=1636
    - LDAP_CONNECT_PORT=636
    - LDAP_CONNECT_URL=ldap.us.onelogin.com

起動

以下のコマンドで起動します。

docker-compose up -d

ログイン確認

ユーザー名はOneloginで登録しているメールアドレスになります。パスワードもOneloginで登録したものになります。
f:id:cloudfish:20200420190016p:plain:w300

ログインできない場合は、ログの内容を確認して対処してください。

まとめ

Guacamoleは現状SAML連携ができないため、SSOを行うにはLDAPを使うしかありませんが、IDPによってはLDAP接続を提供しているものもあるため検討の余地はあると思います。
また、MFAについては、Onelogin側で設定も可能となっています。Guacamole側でMFA設定も可能ですが、端末を紛失した際の初期化対応が少し手間なので、MFA設定が必要であればOneloginで設定するのもありだと思います。また、VLDAPのエンドポイントに対して接続元のIP制限もかけれるため、よりセキュアにすることも可能です。
Gucamoleのユーザー認証の方法の一つとしてこういったIDPを検討してもいいのではないでしょうか。

Kubeviousでkubernetesの状態を確認する

Kubevious is 何?

kubeviousとはkubernetestの設定をビジュアルに確認できるオープンソースソフトウェアです。これだけだと他にも似たようなツールはあるのですが、いくつか面白い機能があったので触ってみました。

実際に動作するデモが用意されていますのでインストールする必要もありません。なにわともあれ触ってみましょう。
基本的には状態を参照するだけのツールとなっており、今のところ設定の変更はできないようです。
demo.kubevious.io

Namespaceごとに構成しているリソースががまとめられて表示されています。見たいNamespaceを選択するとDeployment、Service、ServiceAccount、Config等々の各リソースの詳細を確認することが可能です。
例えばDeploymentを選択するとManifestの内容が確認できます。
f:id:cloudfish:20200427172400p:plain
f:id:cloudfish:20200427172549p:plain

主な機能

Detects Configuration Errors

設定エラーや使われていないリソースがあったりするとAler欄にその内容を表示してくれます。
f:id:cloudfish:20200427175424p:plain
f:id:cloudfish:20200427175500p:plain

Enables Full Text Search

設定内容から全文検索が可能です。例えば8080で検索すると以下のようにポート8080を利用している箇所がヒットします。Grepよりインタラクティブに結果が確認できるのでサクッと設定内容を検索したい場合は便利だと思います。
f:id:cloudfish:20200428081234p:plain

Radioactive & Overprivileged Workloads

特権コンテナや、ホストのネットワークを利用しているリソースなどを放射能マークで知らせてくれます。
f:id:cloudfish:20200428081906p:plain
f:id:cloudfish:20200428091949p:plain

Time Machine

Kubeviousで一番面白そうな機能がこのTIme Machineです。
過去の設定内容を時間を遡って確認することが可能です。例えばreplica数の変更タイミングと以前の設定値をTimelineを遡ることで確認が可能です。githubマニフェストを管理してGitOpsを実践していれば差分や変更タイミングは把握できると思いますが、こういった形でビジュアルに確認できるのは面白いですね。
f:id:cloudfish:20200428091823p:plain
f:id:cloudfish:20200428091421p:plain

Kubeviousのデプロイ

インストールは特にハマることはなく以下のコマンドデプロイできました。

kubectl create namespace kubevious
helm repo add kubevious https://helm.kubevious.io
helm upgrade --atomic -i kubevious kubevious/kubevious --version 0.5.9 -n kubevious

ポートフォワードでアクセスします。

kubectl port-forward $(kubectl get pod -l k8s-app=kubevious-ui -n kubevious -o jsonpath="{.items[0].metadata.name}") 3000:3000 -n kubevious

以下のURLにアクセスするとKubeviousが表示されます。
http://localhost:3000
f:id:cloudfish:20200428092103p:plain

まとめ

Production環境で使うほどのツールではないかもしれませんが、Staging環境などで設定漏れや使ってないリソースの確認など俯瞰的に状態を確認したりするのにはすごく便利なツールではないでしょうか。できればクラスタを一括管理できればもっと便利に使えるかもしれませんね。みなさん興味があれば是非使ってみてください。

参考

github.com

Building Apache Guacamole with AWS Fargate and Aurora Serverless


f:id:cloudfish:20200417102427p:plain:w200
Gucamole is very useful tool as Windows Bastion host.I'd like to introduce to build Apache Guacamole with AWS Fargate.
I can build Gucamole with docker-compose, but I considered scale out If there are more increase RDP connections.So, I have choosen building with AWS Fargate because it's easy to scale out.
In the first place ,Can the Guacamole scale out ?I didn't find it at the official documentation.I found that somebody ask it in mailing list, the answer was yes because of using proper transactions. I believe this answer, I decided to build with AWS Fargate for scaling.
AWS Fargate is a serverless compute engine for containers without need to manage Fargate Hosts.
Unlike EKS, we don't need to pay for control plane. you only pay amount of using fargate container.We can focus building application.
And the reason of choosing Aurora Serverless is cheap of the cost, because it will stop if you don't access by constantly. Also it's scaled out if neccesarry. Currently, the number of users is low, don't need high performance. threfore, the first access is acceptable even if it is late.
So let's get started!

Prerequisite

Overview

The point of this configuration.

  • Host guacd and gucamole on Fargate
  • Host mysql on Aurora Serverless
  • Manage user info on SimpleAD

Note that the recording function is not used this time.

Diagram

f:id:cloudfish:20200425085325p:plain

1. SImpleAD

Create SimpleAD as follows

DirectoryType small
Domain any name

You can add users from the Workspaces service without launching Workspaces.

2.Aurora Serverless

2.1 Create Database instance

Create it with the following settings. Here are the main settings

Engine options Amazon Aurora
Edition Amazon Aurora with MySQL compatibility
Version latest
Database Location Regional
Database features Serverless
DB cluster identifie any name
Minimum Aurora capacity unitInfo 1
Maximum Aurora capacity unitInfo 1
Additional scaling configuration check 'Pause compute capacity after consecutive minutes of inactivity'
Web Service Data API check 'Data API'

When you're done creating,you memo RDS Endpoint.

2.2 Save the mysql password to Parameter Store

Setting parameter store for mysql password.
This is referenced from fargate container.

Name /guacamole/mysql_password
Tier Standard
Type SecureString
Value {YOUE_MYSQL_PASSWORD}
2.3 Initialize DB

Executing the following command for creating initialize SQL.

docker run --rm guacamole/guacamole /opt/guacamole/bin/initdb.sh --mysql > initdb.sql

Open the Query Editor in AWS Console.
Paste and execute the contents of initdb.sql.
f:id:cloudfish:20200425113621p:plain

3. Application Load Balancer

3.1 Create Target Group

Create target group as follows

Target Type IP
Protocol HTTP
Port 8080

HealthCheck

Protocol HTTP
Path /guacamole
Stickiness Enable
Stickiness duration 3600
3.2 Create Application Load Balancer
Load Balancer Type Application Load Balancer
Scheme internet-facing

Listener

HTTPS 443 Created TG by 1.1
HTTP 8080 Created TG by 1.1

4. Create Fargate Cluster

4.1 Create Security Group

Create new SecurityGroup as follows for fargate task.

Protocol Port Source
TCP 8080 SecurityGroup of ALB

When it complete, you modify SecurityGroup of RDS.
Add the following setting.

Protocol Port Source
TCP 3306 SecurityGroup of Fargate task
4.2 Create Fargate Cluster.

Execute the following command for creating cluster.

ecs-cli configure \
--region ${YOUR_REGION_CODE} \
--cluster ${CLUSTER_NAME} \
--default-launch-type FARGATE \

ecs-cli up \
--cluster-config $CLUSTER_NAME \
--vpc ${VPI_ID} \
--subnets ${SUBNET_ID_1},${SUBNET_ID_2}
4.3 Run the Guacamole Service

ecs-params.yml
create ecs-params.yml file as follows.
Set the variables according to your environment.

version: 1
task_definition:
  task_role_arn: ${TASK_ROLE_ARN}
  task_execution_role: ${TASK_EXECUTION_ROLE}
  ecs_network_mode: awsvpc
  task_size:
    mem_limit: 0.5GB
    cpu_limit: 256
  services:
    guacamole:
      secrets:
        - value_from: /guacamole/mysql_password
          name: MYSQL_PASSWORD

run_params:
  network_configuration:
    awsvpc_configuration:
      subnets:
        - ${SUBNET_ID_1}
        - ${SUBNET_ID_2}
      security_groups:
        - ${SECURITY_GROUP_ID_FOR_TASK}

docker-compose.yml
create docker-compose.yml file as follows.
Also set the variables according to your environment.

version: "3"

services:
  guacd:
    image: guacamole/guacd:latest
    expose:
    - "4822"
    ports:
    - "4822:4822"
    logging:
      driver: awslogs
      options:
        awslogs-group: guacamole
        awslogs-region: ap-northeast-1
        awslogs-stream-prefix: guacd
    environment:
    - GUACD_LOG_LEVEL=debug
  guacamole:
    image: cloudfish/guacamole:latest
    ports:
    - "8080:8080"
    logging:
      driver: awslogs
      options:
        awslogs-group: guacamole
        awslogs-region: ap-northeast-1
        awslogs-stream-prefix: guacamole
    environment:
    - GUACD_HOSTNAME=localhost
    - LDAP_HOSTNAME=${SIMPLE_AD_IP}
    - LDAP_PORT=389
    - LDAP_ENCRYPTION_METHOD=none
    - LDAP_USER_BASE_DN=CN=Users,DC=guacamoleDC=local
    - LDAP_USERNAME_ATTRIBUTE=CN
    - LDAP_CONFIG_BASE_DN=CN=Users,DC=guacamole,DC=local
    - TOTP_ENABLED=false  # If you want to use MFA, set true
    - MYSQL_HOSTNAME=${RDS_ENDPOINT}
    - MYSQL_DATABASE=guacamole
    - MYSQL_USER=guacamole

Launching the Service
Execute the following command and start Gucamole service.

ecs-cli compose \
--file docker-compose.yml \
--ecs-params ecs-params.yml \
--project-name  guacamole-service \
service up \
--force-deployment \
--target-group-arn ${TARGET_GROUP_ARN} \
--container-name guacamole \
--container-port 8080

When If it's running properly, you can see the screen as follows on ECS Task screen. If it's not running, you check cloudwatch logs.
f:id:cloudfish:20200425143921p:plain

Login
Now, Let's get access to login screen.Access the following URL.If Aurora Serverless is stopped,It's going to take some time.
https://YOUR_DOMAIN/guacamole
You can see the this screen.
f:id:cloudfish:20200425213044p:plain:w300

You can logged in by the following ID/PASS.
USER:guacadmin
PASSWORD:guacadmin

f:id:cloudfish:20200425214230p:plain

Register target device you want to connect, you try to connect with RDP. But I won't go into how to connect it in detail.

In this configuration, the recording function is not available, because this architecture don't have storage.But Fargate can be available EFS in platform 1.4. therefore, I'm sure you can use the EFS to record.

Apache Guacamoleを使ってWindowsServerにセキュアにログイン


f:id:cloudfish:20200417102427p:plain:w200
 諸事情により社内で複数担当者が共通ユーザーでWindowsServerにアクセスして実施している業務がありました。こうした運用はよくあるのではないでしょうか。当然ながら共通ユーザーでアクセスすることに問題があることは認識していますが、アクセスについては社内からのみとアクセス制限を行っていたため現状では特に対応を行っていませんでした。
 しかしながら、昨今の急激なリモートワークへの移行に伴って、自宅からの接続も許可する必要が出てきましたが、現状オフィスへのVPN接続環境はないため、このままだと対象のWindowsマシンのセキュリティグループ設定でRDP接続の接続元をフル解放するか都度作業者のIPを許可する必要に迫られました。とはいえどちらの方法もあまり現実的ではないので、別な手段を探していたところ、GuacamoleというブラウザからRDP接続ができるツールを見つけたので、これをRDP接続するための踏み台サーバとして利用することで共通ユーザー問題を解決できると考え導入を進めています。今回はこれについて紹介したいと思います。

Apache Guacamoleとは

Guacamole(ワカモレもしくはガカモレと読むそうです)とはクライアントレスなリモートデスクトップゲートウェイです。また、接続クライアントに特別なプラグインやソフトウェアなどをインストールせずに、ブラウザからVNC、RDP、SSH接続などが可能となっています。

Guacamoleの主な機能

主に以下のような機能が提供されており、作業端末やサーバーへのアクセスにおいて、セキュリティの強化や問題が発生した際のトレースが行いやすくなっています。また、今回Guacamoleを使う大きな理由の一つとして、管理者側で接続可能な端末を登録して誰に接続させるかをコントロールできる機能があります。そのため利用者側は接続情報(ユーザー、パスワード)を意識する必要がありません。言い換えると利用者側はユーザー、パスワードを知ることができないことになります。

  • ブラウザからVNC、RDP、SSH、SFTP、TELNET接続が可能
  • DBでの認証、LDAPOpenIDなどで認証やMFA(多要素認証)が利用可能
  • 端末へのアクセスログが保存されているため、いつ誰がどの端末へアクセスしたのかトレースが可能
  • 管理者と一般ユーザーの権限分離が可能
  • 画面操作内容の録画

Gucamoleの構成

詳細は公式ドキュメントを確認してもらうとして、主に二つのコンポーネントで構成されています。

guacamole

ユーザーからのアクセスを受け付けるWebアプリケーション

guacd

RDP、VNCSSH接続などを行うコンポーネント
guacamole.apache.org

現状の課題と解決策

本来であればWindowsをAD運用することが一番いいのですが、現状すぐにはその対応はできないというのが前提にあります。

1.ユーザー管理

現状では、ユーザー管理ができていないため、SimpleADを使ってGucamoleのユーザー管理を行います。もい担当者が異動や退職となって業務を外れた場合は、対象ユーザーを削除します。削除されたユーザーはGucamoleにログインできないため、業務用のWindowsServerへもログインでき無くなります。

2.WindowsServerへ共通ユーザーでログイン

WindowsServerへの接続情報はGucamoleだけが保持する事になるので、利用者が知ることはできません。そのため、担当者の退職などによりパスワードの再設定、再配布などの対応が不要になります。

3.いつ誰がアクセスしたか分からない

Gucamole側でいつ誰がどの端末にアクセスしたかログを保持しているため、Windows側は共通ユーザーでログインされていたとしてもGucamole側のログを確認することでトレースが可能となります。

4.社外からもアクセスする必要がある(オフィスにVPN環境がないためIP制限できない)

WindowsへのアクセスはGucamoleサーバからのみアクセス可能として、Gucamoleサーバーに対してはどこからでもログイン可能とする。認証方法をSImpleAD+MFAとすることでログイン時のセキュリティ向上を行います。

構成

実際の構成はVPCが別れていたりするのでもう少し複雑にはなるのですが、ここではシンプルな構成とします。以下のようにGucamoleサーバーを踏み台として配置することでWindowsServerをPrivateSubnetに配置することが可能になります。
f:id:cloudfish:20200420152133p:plain:w300

構築手順

Gucamoleのインストール方法については、サーバーにwarファイルを展開して直接インストールする方法もありますが、Dockerでインストールする方が簡単なので今回はこちらの方法で構築を行います。また、有償にはなりますがMarketPlaceでイメージ(Guacamole Bastion Host)が公開されているものもありますので興味のある方はそちらもご覧ください。

SimpleADの構築

ユーザー管理用のSimpleADを構築します。SimpleADの構築方法については以下のブログを参照してください。
以下の設定で構築してください。ドメイン名は適当な名称を設定してください。

DirectoryType small
ドメイン guacamole.local
VPC Gucamoleを構築するVPC
Subnet 上記のVPCのPrivateSubnet

ユーザーの作成については、WorkSpacesの設定から追加が可能なのでGucamoleのログイン用ユーザーを作成しておいてください。こちらも以下のブログに追加方法がありますので参照してください。間違ってWorkspacesの立ち上げまではしないようにしてください。
Simple ADを利用したWorkSpaces構築手順 – サーバーワークスエンジニアブログ

ELBの準備

ELBはApplicationLoadBalancerを利用します。こちらも詳細は他のブログを参照してください。また、カスタムドメインを使用する場合は、必要に応じてACMなり自己証明書なりを準備しておいてください。

TargeteGroupの設定
ターゲットグループ名 任意
ターゲットの種類 インスタンス
プロトコル HTTP
ポート 8080
VPC Gucamoleを構築するVPC

■ヘルスチェック

プロトコル HTTP
パス /gucamole
ALBの設定
ELBのタイプ Application Load Balancer
VPC Gucamoleを構築するVPCを設定
Subnet 上記のVPCのPublicSubnet

■リスナー設定

Protocol Port 転送先TG
HTTPS 443 上記ターゲーットグループ
HTTP 8080 上記ターゲーットグループ

※証明書を準備していない場合はHTTPのみ設定してください。

Amazon Linuxの準備

起動

Gucamole用のEC2を作成します。Amazon Linuxで構築します。

AMI: 最新のAmazonLinux2
インスタンスタイプ: t3.micro
VPC: Gucamoleを構築するVPCを設定
Subnet: 上記のVPCのPublicSubnet
SecurityGroup: 8080がALBから疎通可能となるよう設定

起動が完了したら作成したターゲットグループにアタッチしてください。なお、この時点ではhealthyになりません。

Dockerインストール

次にDockerをインストールします。
以下のコマンドを実行してdockerとdocker-composeをインストールします。

dockerのインストール

sudo yum install -y docker
sudo service docker start
sudo usermod -a -G docker ec2-user
sudo systemctl enable docker

docker-composeのインストール

sudo curl -L https://github.com/docker/compose/releases/download/1.24.1/docker-compose-`uname -s`-`uname -m` -o /usr/local/bin/docker-compose
sudo chmod +x /usr/local/bin/docker-compose

gucamoleのイメージ構築

guacamole(gucamole-client)のイメージは公式からDocker Hubでバージョン1.1.0提供されていますが、1.1.0にはMFAの設定が組み込まれていません。そのため、githubから最新のコードをチェックアウトして自分でイメージのビルドを行います。

以下のコマンドを実行してイメージを作成します。
ビルドにはかなりの時間がかかりました。

git clone https://github.com/apache/guacamole-client.git
cd guacamole-client
docker build -t guacamole_120 .

docker-composeの作成

Dockerで構築する場合、以下の3つのコンテナが必要となります。

  • guacamole
  • guacd
  • mysql(or postresql)

以下の内容でdocker-compos.yamlを作成します。
$SIMPLE_AD_IPには作成したSimpleADのIPを設定します。
LDAP_USER_BASE_DN、LDAP_CONFIG_BASE_DNのDCにはSimpleAD作成時に設定したドメインを設定してください。

version: "3"

services:
  guacd:
    image: guacamole/guacd:latest
    expose:
    - "4822"
    environment:
    - GUACD_LOG_LEVEL=debug
  guacamole:
    image: guacamole_120
    links:
      - guacd:guacd
    ports:
    - "8080:8080"
    environment:
    - GUACD_HOSTNAME=guacd
    - LDAP_HOSTNAME=$SIMPLE_AD_IP
    - LDAP_PORT=389
    - LDAP_ENCRYPTION_METHOD=none
    - LDAP_USER_BASE_DN=CN=Users,DC=guacamole,DC=local
    - LDAP_USERNAME_ATTRIBUTE=CN
    - LDAP_CONFIG_BASE_DN=CN=Users,DC=guacamole,DC=local
    - TOTP_ENABLED=true
    - MYSQL_HOSTNAME=mysql
    - MYSQL_DATABASE=guacamole
    - MYSQL_USER=guacamole
    - MYSQL_PASSWORD=guacamole

  mysql:
    image: mysql/mysql-server:5.7
    volumes:
    - "./mysql/data:/var/lib/mysql"
    - "./mysql/init:/docker-entrypoint-initdb.d"
    environment:
    - MYSQL_DATABASE=guacamole
    - MYSQL_USER=guacamole
    - MYSQL_PASSWORD=guacamole

ディレクトリの作成

docker-compose.yamlファイルと同じ階層に以下のディレクトリを作成します。

mkdir -p mysql/data
mkdir -p mysql/init
tree -d mysql/
mysql/
├── data
└── init

初期データ作成

以下のコマンドで初期化用のスクリプトを作成します。

docker run --rm guacamole_120 /opt/guacamole/bin/initdb.sh --mysql > mysql/init/initdb.sql

起動

docker-compose up -d

Gucamoleの使い方

接続用のWindowsServerについては事前に用意し、Gucamoleサーバーからポート3389で疎通できるようにしておいてください。

ログイン画面にアクセス

カスタムドメインを設定していなければ、以下のいずれかのURLでアクセスしてください。
https://ELB_DNS/guacamole/
http://ELB_DNS:8080/guacamole/
正常に起動されていれば以下のようなログイン画面が表示されます。もし表示されていなければ、ログを確認してみてください。

以下のユーザー、パスワードでログインできます。
ログインユーザー: guacadmin
パスワード: guacadmin
f:id:cloudfish:20200420190016p:plain:w200

MFA設定

MFAが有効になっていますのでGoogle Authenticatorなどで設定してください。
f:id:cloudfish:20200420194326p:plain:w200

初期画面

以下のような初期画面が表示されます。
f:id:cloudfish:20200420200648p:plain

接続設定の追加

右上のユーザー名から「設定」を選択し、「接続」タブを開いて「接続の追加」をクリックします。
以下の通り入力を行い保存します。
f:id:cloudfish:20200421090135p:plain

接続情報の選択

右上のユーザー名からホーム画面に戻ると、先ほど登録した接続設定が表示されていますので、登録した接続情報を選択します。
f:id:cloudfish:20200421091255p:plain

RDP接続

以下のようにリモートデスクトップが開きます。
f:id:cloudfish:20200421092011p:plain

RDPの切断

切断するにはMacの場合は、「Ctrl + Shift + Option」でサイドバーが表示されますので「切断」を選択してください。
f:id:cloudfish:20200421092248p:plain:w300

他のユーザーが利用中の場合

また、別ユーザーが接続中の場合、以下のように利用中であることが分かります。ただし、日本語表記はバグのためが表示されないため、英語表記とする必要があります。日本語だと他にも微妙な動作があったりもしたので基本は英語表記の方がいいかもしれません。
なお、接続設定において最大接続数を1とすると他のユーザーが利用中の場合は接続できないような制限をかけることが可能です。
f:id:cloudfish:20200421094831p:plain

MFAの再設定

多要素認証の機能を利用するので、スマホの故障や紛失による再設定をどのように対応するのか調べてみました。現状では管理者がUIから設定する方法はなさそうです。
そのため、DBを直接いじる必要があるようです。

以下のSQLで対象のユーザーIDを特定し

select 
    gu.user_id 
from 
    guacamole_entity ge inner join guacamole_user gu 
on ge.entity_id = gu.entity_id 
where ge.name='guacadmin'

対象ユーザーの「guac-totp-key-confirmed」属性をfalseに戻すことで、MFAの再設定が可能となります。

update 
    guacamole_user_attribute 
set attribute_value='false' 
where 
    attribute_name='guac-totp-key-confirmed' 
and user_id=${USER_ID}

Workspacesについて

検証しながら気が付いたのですが、この接続の仕組みをWorkSpacesでも利用できることに思い至りました。
WorkSpacesについては、そもそも業務都合上、利用者が自宅でも接続できる必要があるため、IP制限ができず専用のクライアントツールがあればどこからでもユーザー、パスワードでログイン可能となることが悩みとしてありました。
そのため、WorkSpacesについてもGucamole経由でアクセスすることで認証をMFAを使ってセキュアにすることができかつアクセス状況もトレースが可能となります。
設定方法の詳細は割愛させていただきますが、WorkSpacesに割り当てられているENIにアタッチされたセキュリティグループにGucamoleサーバーから3389で疎通可能となるルールを設定することで、RDPで接続が可能となります。さらにIPアクセスコントールでルールなしの状態で設定すると、クライアントツールからは接続ができません。ただし、この方法は実行モードが「AutoStop」モードでは利用できないので注意してください。

まとめ

Apache Gucamoleについては、Windowsマシンへアクセスするための踏み台としてすごく便利だと思いました。特に管理側としてアクセス状況のトレースがしやすいというのは非常にありがたい機能だと思います。今回は、共通ユーザーの問題があったので、Gucamoleにたどり着きましたが、全てのAWS上へのWindowsマシンへのアクセスは基本的にこれを利用する方式にしようかと考えています。もし同じような悩みを抱えている場合は、利用を検討してみてください。