Jenkinsに統合されたRundeckをNGINXとOauth2でSSO

"[NGINX Ingress ControllerとOauth2でJenkinsにSSO](https://qiita.com/dg4101/items/7fdcf0e14d00b3fa0c7e "NGINX Ingress ControllerとOauth2でJenkinsにSSO")"でJenkinsとSSOについてまとめましたが、今回は同じ要領でRundeckにSSOを実装します。

#使用するもの
* NGINX Ingress Controller - https://www.nginx.co.jp/products/products-nginx/kubernetes-ingress-controller/
* Kubernetes - https://kubernetes.io/ja/
* OAuth2 Proxy - https://github.com/oauth2-proxy/oauth2-proxy, https://oauth2-proxy.github.io/oauth2-proxy/docs/
* Rundeck Docker container - https://hub.docker.com/r/rundeck/rundeck/
* Rundeck Jenkins plugin - https://plugins.jenkins.io/rundeck/

#Oauth2実装に関して
基本的な流れはJenkinの時と同じです。Oauth2の設定は前回の内容とほぼ同じですので、詳細は省略します。詳しくは前回の記事を参考にしてください。こちらでは、Jenkinsの時と異なる点をいくつか挙げていきます。

##Idpについて
Idpの設定は前回同様必要です。`Redirect URIs`はRundeckのURLになります。Idpにもよりますが、複数の`Redirect URIs`を同じアプリケーションの内に設定できるものもありますので、最小の場合は`Redirect URIs`の追加だけで十分の可能性もあります。

```
Redirect URIs = https://rundeck_url.name.company/oauth2/callback
Logout URIs = https://rundeck_url.name.company/oauth2/sign_out
```
##Nginx Ingress Controllerの設定
```nginx.yaml
# ------------------- Nginx Ingress ------------------- #
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: rundeck-ingress
  namespace: rundeck
  annotations:
....
    kubernetes.io/ingress.class: "nginx"
    nginx.ingress.kubernetes.io/auth-signin: 'https://$host/oauth2/start?rd=$escaped_request_uri'
    nginx.ingress.kubernetes.io/auth-url: 'https://$host/oauth2/auth'
    nginx.ingress.kubernetes.io/configuration-snippet: |
      auth_request_set $user   $upstream_http_x_auth_request_user;
      proxy_set_header X-Forwarded-UserID  $user;
      proxy_set_header X-Forwarded-GroupID "dummy"; 
    nginx.ingress.kubernetes.io/ssl-redirect: 'true'
    nginx.ingress.kubernetes.io/server-snippet: |
      location ~* ^/api/([0-9]+)/(?!(execution)) {
        allow    xxx.xxx.xxx.xxx;
        # drop rest of the world 
        deny    all;
        more_clear_input_headers 'X-Forwarded-UserID' 'X-Forwarded-GroupID';
        proxy_pass http://rundeck.rundeck:4440;
      }
     # APIのパスをSSOの対象外に設定
      #グループ情報はIdpより提供されないので適当にダミーの情報を設定
...
spec:
  tls:
    - secretName: rundeck_cert
  rules:
    - host: rundeck_url.name.company
      http:
        paths:
          - backend:
              serviceName: rundeck-service
              servicePort: 8080
            path: /
```
Nginxの設置はJenkinsの時と同様です。ただ、Rundeckは複数のシステムに同時にバッチジョブなどを実行するために使われている場合が多く、API経由で自動化している場合もあるはずです。その場合`nginx.ingress.kubernetes.io/server-snippet`を使ってAPIをSSOの対象外に設定するとこともできます。ただし、APIはTokenなどで認証を必ず行うようにする必要があります。

##OAuth2の設定
こちらも基本的に前回とほぼ同じで、流れは下の図のようにな感じになります。
Ingressを2つ用意します。
* Oauth2 - Rundeck用のトラフィック。/oauth2
* Nginx - 通常のアプリケーションのトラフィック。/oauth2のパス以外のすべて
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/799224/d11fa3b3-c54d-b6df-b725-34534fed156f.png)
`App Namespace`ですが、前回はJenkins専用のNamespaceを作りました。今回も同様にRundeck専用のNamespaceを作っています。
ただ、同じNamespace内でJenkinsとRundeck両方を運用したい場合はサービスとIngressを以下のように変更すると可能です。

```rundeck-service-ingress.yaml
kind: Ingress
apiVersion: extensions/v1beta1
metadata:
  name: oauth2-proxy
  namespace: cicd
  annotations:
    kubernetes.io/ingress.class: "nginx"
    nginx.ingress.kubernetes.io/ssl-redirect: 'true'
spec:
  tls:
  - hosts:
    - jenkins_url.name.company
    secretName: jenkins_cert
  - hosts:
    - rundeck_url.name.company
    secretName: rundeck_cert
  rules:
    - host: jenkins_url.name.company
      http:
        paths:
          - backend:
              serviceName: oauth2-proxy-jenkins
              servicePort: 4180
            path: /oauth2
    - host: rundeck_url.name.company
      http:
        paths:
          - backend:
              serviceName: oauth2-proxy-rundeck
              servicePort: 4180
            path: /oauth2
```
#Rundeckの認可の設定
前回同様、使用しているIdpがグループ情報を提供しないと言うのが前提です。Rundeckも`preauthenticated`を使用しJenkinsと同様に、Idpで認証(Authentication)されたユーザーをヘッダーで渡すことができます。Jenkinsの場合はその後LDAPなどで検索してグループ情報を取得し、認可(Authorization)の設定ができましたが、Rundeckでは`preauthenticated`を使用するとLDAPを併用するこはできませんが。ですので、Rundeck内でグループ情報を管理する必要があります。もちろん、Idpがグループ情報を提供する場合は、それをヘッダーに格納することによって解決できます。

##rundeck-config.propertiesの設定
```rundeck-config.properties
# Pre Auth mode settings
rundeck.security.authorization.preauthenticated.enabled=true
rundeck.security.authorization.preauthenticated.attributeName=REMOTE_USER_GROUPS
rundeck.security.authorization.preauthenticated.delimiter=,
# Header from which to obtain user name
rundeck.security.authorization.preauthenticated.userNameHeader=X-Forwarded-UserID
# Header from which to obtain list of roles
rundeck.security.authorization.preauthenticated.userRolesHeader=X-Forwarded-GroupID
```
`preauthenticated.enabled`で認証をRundeck外で行う設定をします。`X-Forwarded-UserID`、`X-Forwarded-GroupID`はNginxで設定したヘッダーです。`X-Forwarded-GroupID`には"dummy"という架空のグループ情報をいれて、そのグループはRundeck側では存在しないようにしています。ですので、ヘッダーから渡されたグループ情報をもとに権限が付与されることはありません。

##admin.aclpolicyの設定
ユーザーに管理者権限を付与する場合は`admin.aclpolicy`にユーザーを追加すると可能です。Rundeckには既定の`admin`ユーザーが初期設定で存在しますので、それを使わない場合は削除するかコメントアウトすればいいでしょう。

```admin.aclpolicy
username: [userA, userB]
#group: admin
```
##RundeckをK8Sにデプロイ
これまで設定してきたrundeck-config.propertiesなどのファイルはRundeckをK8Sにデプロイするときに反映する必要があります。方法はいくつかあります。
https://docs.rundeck.com/docs/administration/configuration/docker/extending-configuration.html#special-destination-directories
こちらで紹介されている例ですと、`rundeck/rundeck`をベースに新たに作り直し、その時に変更した設定ファイルを上書きしています。

他にも独自にコンテナを作成して、ConfigMapを設定してそのファイルをマウントして使用することも可能です。

```Rundeck.Dockerfile
apt-get update
apt-get install rundeck
```

```k8s_sample.yaml
  volumeMounts:
    - name: rundeck
      mountPath: /rundeck/conf
      subPath: rundeck-config.properties
volumes:
- name: rundeck
  configMap:
    name: rundeck-conf
```
##RundeckをJenkinsに統合

Rundeck Jenkins pluginをJenkinsにインストールすると、JenkinsでRundeckとの統合の設定ができるようになります。これにより、JenkisのパイプラインからRundeckのジョブを開始して複数のサーバーに同時に変更などをかけることが可能です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/799224/22765d4a-8f5c-3f2c-0568-bf36e4e2b359.png)
Rundeckを`namespace: rundeck`にデプロイして以下のようなサービスを設定するとK8Sのクラスター内では`http://rundeck.rundeck:4440`のURLでアクセスできます。

```rundeck-service.yaml
apiVersion: v1
kind: Service
metadata:
  name: rundeck
  namespace: rundeck
spec:
  ports:
    - name: http
      protocol: TCP
      port: 4440
      targetPort: 4440
  selector:
    myapp: rundeck
```
はじめに設定したSSOはK8Sの外からNginxを通してアクセスする場合に行われるので、ユーザーを認証する場合に行われます。JenkinsとRundeckの間は同じK8Sのクラスター内でNginxを通さないので、SSOの認証なしでJenkinsからRundeckのジョブなの度を実行できます。
Jenkins側の認証も同様にNginxでSSOの設定ができるので([NGINX Ingress ControllerとOauth2でJenkinsにSSO](https://qiita.com/dg4101/items/7fdcf0e14d00b3fa0c7e "NGINX Ingress ControllerとOauth2でJenkinsにSSO"))、誰がJenkinsからRundeckを実行できるかコントロールすることも可能です。


#まとめと課題
* 前回JenkinsのSSOを設定するときにIdpをセットアップしているので、今回のIdp側の設定は楽で`Redirect URIs`を追加するだけで終了です。
* ユーザー情報もIdp側ですでに持っているのでRundeckのSSOを追加する場合に、新たに作り直す必要はありません。（認可はRundeck側で設定が必要です）
* ユーザーが直接Rundeckからジョブなどを実行する場合は、SSOを通して認証を行うことができ、Jenkinsと統合することによって、JenkinsからRundeckのジョブを実行する場合はJenkins側で認証と認可を行うことができます。
* RundeckはNodeと呼ばれるサーバーに対して直接OSのコマンドなどを実行できるので、Jenkins以上にMFA(多要素認証)が重要になってきます。
* そのほか、前回で述べたIdp周りの課題などは基本的にはSSOの対象が何になっても同じです。