5
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?

More than 3 years have passed since last update.

Kubernetesで利用可能なセキュリティ設定(Pod Security Context, Security Context, Pod Security Policy) について

Last updated at Posted at 2020-02-11

はじめに

この記事では、Kubernetesで利用できるセキュリティの設定(Pod Security Context, Security Context)、セキュリティポリシー (Pod Security Policy) に関する説明と検証結果をまとめています。

Kubernetesにおけるセキュリティの設定 / ポリシーについて

セキュリティ設定 / ポリシーの全体像

Kubernetesで適用可能なセキュリティの設定 / ポリシーとして、下記の3種類の機能が提供されています。PodSecurityPolicyに関してはv1.17時点で beta 機能として提供されています。

PodSecurityPolicyはポリシーを定義したオブジェクト(podSecurityPolicy)を作成する必要があります。
Pod Security ContextとSecurity Contextに関しては、podのマニュフェストファイルの中でsecurityContextを指定して設定します。
(指定する名前は同じですがマニュフェストファイルの中で指定する箇所が異なるので、注意が必要です。)

Admission Controlについて

PodSecurityPolicyの利用においては、Admission Controllが利用できることが前提となります。
(Kubernetesのドキュメントでは、Pod security policy control is implemented as an optional (but recommended) admission controller.と説明されています。)

Admission Controllとは、リクエストの内容をチェックした上で制御する機能です。
任意のプラグインを有効化することで、特定の条件に該当するリクエストに対する処理 (リクエストの拒否・リクエストの変更 etc.) を有効化します。

podSecurityPolicyを利用する場合は、クラスタ作成時にAdmission ControllerのプラグインとしてpodSecurityPolicyを指定した上で有効化します。

Role / Rolebindingについて

KubernetesのRBACの機能を利用してpodSecurityPolicyをユーザに紐づけることも可能です。

KubernetesのDocsで提供されている例では、SA(ServiceAccount)を作成した上でそのユーザーにRoleを紐づけています。
下記の例ではSAに対してeditの権限のみを付与しています。

$ kubectl create namespace psp-example

$ kubectl create serviceaccount -n psp-example fake-user

$ kubectl create rolebinding -n psp-example fake-editor \
             --clusterrole=edit \
             --serviceaccount=psp-example:fake-user

ユーザーのリクエストにポリシーを適用する際には、指定したリソース(ここでは作成したpodSecurityPolicyに)のuse権限をユーザーに付与する必要があります。
use権限が付与されていないユーザーは、podを作成することができません。)

$ kubectl create role psp:unprivileged \
    --verb=use \
    --resource=podsecuritypolicy \
    --resource-name=example \
    --as=system:serviceaccount:psp-example:fake-user \
    -n psp-example

$ kubectl create rolebinding fake-user:psp:unprivileged \
    --role=psp:unprivileged \
    --serviceaccount=psp-example:fake-user

use権限を付与したRoleをSAに紐付けた後は「ユーザーがポリシーを利用できるかどうか」を下記のコマンドで確認できます。

$ kubectl auth can-i use podsecuritypolicy/example \
        --as=system:serviceaccount:psp-example:fake-user \
        -n psp-example

各マネージドサービスの対応状況

各マネージドサービスでは、基本的に上記のいずれのポリシーも利用可能となっています。
podSecurityPolicyに関しては、デフォルトでポリシーが定義されていたり、kubectl以外のCLI経由で有効/無効を設定できるサービスもあるため、各サービスの仕様を確認する必要があります。
(確認できていないサービスもあるので、必要に応じてこの部分は追記していきます)

  • pod Security Policy
    • Amazon EKS:デフォルトでpodSecurityPolicyが作成済
    • Google Kubernetes Engine:CLIを利用してpodSecurityPolicyのプラグインを有効化 / 無効化することが可能

各機能の検証

ここからは、各ポリシーの検証を行っていきます。
バージョン情報は下記の通りです。今回はIBM Cloud Kubernetes Serviceの無料クラスタ (Master: 1、Worker: 1) を利用して検証を行います。
(EKS・GKEなど、その他のマネージドサービスを利用して検証することも可能です。)

$ k version
  Client Version: version.Info{Major:"1", Minor:"17", GitVersion:"v1.17.2", GitCommit:"59603c6e503c87169aea6106f57b9f242f64df89", GitTreeState:"clean", BuildDate:"2020-01-18T23:30:10Z", GoVersion:"go1.13.5", Compiler:"gc", Platform:"darwin/amd64"}
  Server Version: version.Info{Major:"1", Minor:"15", GitVersion:"v1.15.8+IKS", GitCommit:"0a5a9d717cf2cedaf9b7ab8ce758fd3ab2638d90", GitTreeState:"clean", BuildDate:"2020-01-16T19:49:53Z", GoVersion:"go1.12.12", Compiler:"gc", Platform:"linux/amd64"}

Pod Security Policy検証

事前準備として、Namespace, SA, Rolebindingを作成します。

  • Namespaceに紐付けたSA(fake-user)を作成する
  • SAに対してeditの権限を有するClusterRoleを紐づける
    • kubectl create rolebinding -n psp-example fake-editor --clusterrole=edit --serviceaccount=psp-example:fake-user

続いて、PodSecurityPolicyを作成します。
ここでは、privilegedの部分をfalseにすることで特権コンテナの作成を許可しないというポリシーを定義します。

apiVersion: policy/v1beta1
kind: PodSecurityPolicy
metadata:
  name: example
spec:
  privileged: false  # Don't allow privileged pods!
  seLinux:
    rule: RunAsAny
  supplementalGroups:
    rule: RunAsAny
  runAsUser:
    rule: RunAsAny
  fsGroup:
    rule: RunAsAny
  volumes:
  - '*'

また、クラスタ作成時に作成されているPodSecurityPolicy(ibm-privileged-psp)では、PRIV=trueとなっているので、検証のために削除しておきます。
PodSecurityPolicyに関しては、複数のポリシーの重ねがけの場合に明示的なAllowが優先されるため、このようなデフォルトのポリシーが存在する場合は注意が必要です。

# デフォルトで作成されている複数のPodSecurityPolicyが作成されている
# PodSecurityPolicy(ibm-privileged-psp)では、PRIV=trueとなっている
$ k get psp
NAME                        PRIV    CAPS                                                                                                                  SELINUX    RUNASUSER          FSGROUP     SUPGROUP    READONLYROOTFS   VOLUMES
example                     false                                                                                                                         RunAsAny   RunAsAny           RunAsAny    RunAsAny    false            *
ibm-anyuid-hostaccess-psp   false   SETPCAP,AUDIT_WRITE,CHOWN,NET_RAW,DAC_OVERRIDE,FOWNER,FSETID,KILL,SETUID,SETGID,NET_BIND_SERVICE,SYS_CHROOT,SETFCAP   RunAsAny   RunAsAny           RunAsAny    RunAsAny    false            *
ibm-anyuid-hostpath-psp     false   SETPCAP,AUDIT_WRITE,CHOWN,NET_RAW,DAC_OVERRIDE,FOWNER,FSETID,KILL,SETUID,SETGID,NET_BIND_SERVICE,SYS_CHROOT,SETFCAP   RunAsAny   RunAsAny           RunAsAny    RunAsAny    false            *
ibm-anyuid-psp              false   SETPCAP,AUDIT_WRITE,CHOWN,NET_RAW,DAC_OVERRIDE,FOWNER,FSETID,KILL,SETUID,SETGID,NET_BIND_SERVICE,SYS_CHROOT,SETFCAP   RunAsAny   RunAsAny           RunAsAny    RunAsAny    false            configMap,emptyDir,projected,secret,downwardAPI,persistentVolumeClaim
ibm-privileged-psp          true    *                                                                                                                     RunAsAny   RunAsAny           RunAsAny    RunAsAny    false            *
ibm-restricted-psp          false                                                                                                                         RunAsAny   MustRunAsNonRoot   MustRunAs   MustRunAs   false            configMap,emptyDir,projected,secret,downwardAPI,persistentVolumeClaim

# 検証のためにibm-privileged-pspを削除する
$ k delete psp ibm-privileged-psp
podsecuritypolicy.extensions "ibm-privileged-psp" deleted
    
$ k get psp
NAME                        PRIV    CAPS                                                                                                                  SELINUX    RUNASUSER          FSGROUP     SUPGROUP    READONLYROOTFS   VOLUMES
example                     false                                                                                                                         RunAsAny   RunAsAny           RunAsAny    RunAsAny    false            *
ibm-anyuid-hostaccess-psp   false   SETPCAP,AUDIT_WRITE,CHOWN,NET_RAW,DAC_OVERRIDE,FOWNER,FSETID,KILL,SETUID,SETGID,NET_BIND_SERVICE,SYS_CHROOT,SETFCAP   RunAsAny   RunAsAny           RunAsAny    RunAsAny    false            *
ibm-anyuid-hostpath-psp     false   SETPCAP,AUDIT_WRITE,CHOWN,NET_RAW,DAC_OVERRIDE,FOWNER,FSETID,KILL,SETUID,SETGID,NET_BIND_SERVICE,SYS_CHROOT,SETFCAP   RunAsAny   RunAsAny           RunAsAny    RunAsAny    false            *
ibm-anyuid-psp              false   SETPCAP,AUDIT_WRITE,CHOWN,NET_RAW,DAC_OVERRIDE,FOWNER,FSETID,KILL,SETUID,SETGID,NET_BIND_SERVICE,SYS_CHROOT,SETFCAP   RunAsAny   RunAsAny           RunAsAny    RunAsAny    false            configMap,emptyDir,projected,secret,downwardAPI,persistentVolumeClaim
ibm-restricted-psp          false                                                                                                                         RunAsAny   MustRunAsNonRoot   MustRunAs   MustRunAs   false            configMap,emptyDir,projected,secret,downwardAPI,persistentVolumeClaim

この状態では、ユーザー(fake-user)に対してeditの権限のみを付与しているため、特権コンテナ以外のコンテナも作成することができない状態になります。

# Adminユーザーはuse権限を有しているのでコンテナは作成できる
$ kubectl apply -n psp-example -f- <<EOF
> apiVersion: v1
> kind: Pod
> metadata:
>   name:      privileged
> spec:
>   containers:
>     - name:  pause
>       image: k8s.gcr.io/pause
>       securityContext:
>         privileged: true
> EOF
pod/pause created

# edit権限しか有していないユーザーではコンテナを作成できない
$ kubectl --as=system:serviceaccount:psp-example:fake-user -n psp-example create -f- <<EOF
> apiVersion: v1
> kind: Pod
> metadata:
>   name:      privileged
> spec:
>   containers:
>     - name:  pause
>       image: k8s.gcr.io/pause
>       securityContext:
>         privileged: true
> EOF
Error from server (Forbidden): error when creating "STDIN": pods "privileged" is forbidden: unable to validate against any pod security policy: [spec.containers[0].securityContext.privileged: Invalid value: true: Privileged containers are not allowed]

次に、useの権限を有したロールをユーザー(fake-user)に紐づけます。

$ kubectl -n psp-example create role psp:unprivileged \
>     --verb=use \
>     --resource=podsecuritypolicy \
>     --resource-name=example
role.rbac.authorization.k8s.io/psp:unprivileged created

$ kubectl -n psp-example create rolebinding fake-user:psp:unprivileged \
>     --role=psp:unprivileged \
>     --serviceaccount=psp-example:fake-user
rolebinding.rbac.authorization.k8s.io/fake-user:psp:unprivileged created

useの権限を持つユーザーでprivileged: trueの設定をしたpodを作成しようとすると、作成時にエラーとなります。

# リクエストの検証はされているが、ポリシーに準拠しているので拒否はされない
$ kubectl --as=system:serviceaccount:psp-example:fake-user -n psp-example create -f- <<EOF
> apiVersion: v1
> kind: Pod
> metadata:
>   name:      pause
> spec:
>   containers:
>     - name:  pause
>       image: k8s.gcr.io/pause
> EOF
pod/pause created

# 特権コンテナを作成しようとしているのでリクエストが拒否される
$ kubectl --as=system:serviceaccount:psp-example:fake-user -n psp-example create -f- <<EOF
> apiVersion: v1
> kind: Pod
> metadata:
>   name:      privileged
> spec:
>   containers:
>     - name:  pause
>       image: k8s.gcr.io/pause
>       securityContext:
>         privileged: true
> EOF
Error from server (Forbidden): error when creating "STDIN": pods "privileged" is forbidden: unable to validate against any pod security policy: [spec.containers[0].securityContext.privileged: Invalid value: true: Privileged containers are not allowed]

Pod Security Context検証

RunAsUser / fsGroup

下記の2つの設定を用いて、Pod Security Contextを検証します。
fsGroupと似た設定項目としてrunAsGroupもありますが、runAsGroupはプロセスに対してGIDを指定するという設定なのでGIDを設定する対象が異なります。

  • RunAsUser:UIDを指定して特定のユーザーでコンテナ内のプロセスを実行する
  • fsGroup:GIDを指定してコンテナのボリュームやボリューム内で作成されたファイルにGIDをセットする

今回は下記のマニュフェストファイルを利用します。
pod起動時に、sleep 1hのコマンドを実行することで、プロセスのUIDを確認します。
また、Pod用の一時的なディスク領域として/data/demoのパスでemptyDirを作成して、作成されたボリュームのGIDを確認します。

security-context.yaml
apiVersion: v1
kind: Pod
metadata:
  name: security-context-demo
spec:
  securityContext:
    runAsUser: 1000
    runAsGroup: 3000
    fsGroup: 2000
  volumes:
  - name: sec-ctx-vol
    emptyDir: {}
  containers:
  - name: sec-ctx-demo
    image: busybox
    command: [ "sh", "-c", "sleep 1h" ]
    volumeMounts:
    - name: sec-ctx-vol
      mountPath: /data/demo

podを作成して、コンテナのシェルを取得してプロセスのUIDを確認します。
全てのプロセスに対して、UID=1000がセットされていることがわかります。

$ kubectl apply -f https://k8s.io/examples/pods/security/security-context.yaml
pod/security-context-demo created

$ kubectl exec -it security-context-demo -- sh

/ $ ps aux
PID   USER     TIME  COMMAND
    1 1000      0:00 sleep 1h
    7 1000      0:00 sh
   14 1000      0:00 ps aux

続いて、作成されたボリュームのGIDを確認します。
作成したdata/demoに関して、所有グループの箇所にGID=2000がセットされていることがわかります。(ボリュームの所有者はrootのままです。)

/ $ ls -al
total 48
drwxr-xr-x    1 root     root          4096 Feb 11 05:19 .
drwxr-xr-x    1 root     root          4096 Feb 11 05:19 ..
drwxr-xr-x    2 root     root         12288 Dec 23 19:21 bin
drwxr-xr-x    3 root     root          4096 Feb 11 05:19 data
drwxr-xr-x    5 root     root           360 Feb 11 05:19 dev
drwxr-xr-x    1 root     root          4096 Feb 11 05:19 etc
drwxr-xr-x    2 nobody   nogroup       4096 Dec 23 19:21 home
dr-xr-xr-x  218 root     root             0 Feb 11 05:19 proc
drwx------    2 root     root          4096 Dec 23 19:21 root
dr-xr-xr-x   13 root     root             0 Feb  9 06:13 sys
drwxrwxrwt    2 root     root          4096 Dec 23 19:21 tmp
drwxr-xr-x    3 root     root          4096 Dec 23 19:21 usr
drwxr-xr-x    1 root     root          4096 Feb 11 05:19 var
/ $ ls -al /data
total 12
drwxr-xr-x    3 root     root          4096 Feb 11 05:19 .
drwxr-xr-x    1 root     root          4096 Feb 11 05:19 ..
drwxrwsrwx    2 root     2000          4096 Feb 11 05:18 demo

また、作成したボリュームでファイルを作成して確認すると、所有者としてUID=1000、所有グループとしてGID=2000がセットされていることがわかります。

/ $ cd data/demo
/data/demo $ echo hello > demofile
/data/demo $ ls -al
total 12
drwxrwsrwx    2 root     2000          4096 Feb 11 05:22 .
drwxr-xr-x    3 root     root          4096 Feb 11 05:19 ..
-rw-r--r--    1 1000     2000             6 Feb 11 05:22 demofile

Security Context検証

Linux Capabilities

今回はSecurity Contextとして設定できる項目の中でも、Linux Capabilitiesを設定します。
Linux Capabilitiesは、rootに紐づいた権限を分割・グループ化して必要な権限を付与する仕組みのことを指します。

参考: https://gihyo.jp/admin/serial/01/linux_containers/0042

マニュフェストファイルの中では、pod.spec.containers.securityContextの部分でCapabilitiesを設定します。

security-context-demo-4
apiVersion: v1
kind: Pod
metadata:
  name: security-context-demo-4
spec:
  containers:
  - name: sec-ctx-4
    image: gcr.io/google-samples/node-hello:1.0
    securityContext:
      capabilities:
        add: ["NET_ADMIN", "SYS_TIME"]

コンテナに紐づく設定としてLinux Capabilitiesを設定しているため、コンテナ作成後にコンテナのシェルを起動して確認します。

コンテナ内のプロセスが持つCapabilitiesはcat proc/1/status | grep Cap*で確認することができます。
Kubernetesが提供しているExamples内のsecurity-context-3.yamlでは、Capabilitiesを設定していないため下記のようになります。

$ k apply -f https://k8s.io/examples/pods/security/security-context-3.yaml
pod/security-context-demo-3 created

$ k exec security-context-demo-3 -it bash

root@security-context-demo-3:/# ps aux
USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root         1  0.0  0.0   4332   764 ?        Ss   09:51   0:00 /bin/sh -c node server.js
root         6  0.1  0.5 772120 22796 ?        Sl   09:51   0:00 node server.js
root        11  0.0  0.0  20240  3220 pts/0    Ss   09:52   0:00 bash
root        16  0.0  0.0  17496  2120 pts/0    R+   09:52   0:00 ps aux

root@security-context-demo-3:/# cat proc/1/status | grep Cap*
CapInh:	00000000a80425fb
CapPrm:	00000000a80425fb
CapEff:	00000000a80425fb
CapBnd:	00000000a80425fb
CapAmb:	0000000000000000

root@security-context-demo-3:/# getpcaps 1
Capabilities for `1': = cap_chown,cap_dac_override,cap_fowner,cap_fsetid,cap_kill,cap_setgid,cap_setuid,cap_setpcap,cap_net_bind_service,cap_net_raw,cap_sys_chroot,cap_mknod,cap_audit_write,cap_setfcap+eip

注意点として、IKSでデフォルトで作成されているPodSecurityPolicyの中で今回検証したいCapabilitiesが許可されていません。
Admin権限のユーザーで検証を行う際にはデフォルトのポリシーを削除することをお勧めします。

# CAPSの箇所でCapabilitiesが設定されている
$ k get psp
NAME                        PRIV    CAPS                                                                                                                  SELINUX    RUNASUSER          FSGROUP     SUPGROUP    READONLYROOTFS   VOLUMES
example                     false                                                                                                                         RunAsAny   RunAsAny           RunAsAny    RunAsAny    false            *
ibm-anyuid-hostaccess-psp   false   SETPCAP,AUDIT_WRITE,CHOWN,NET_RAW,DAC_OVERRIDE,FOWNER,FSETID,KILL,SETUID,SETGID,NET_BIND_SERVICE,SYS_CHROOT,SETFCAP   RunAsAny   RunAsAny           RunAsAny    RunAsAny    false            *
ibm-anyuid-hostpath-psp     false   SETPCAP,AUDIT_WRITE,CHOWN,NET_RAW,DAC_OVERRIDE,FOWNER,FSETID,KILL,SETUID,SETGID,NET_BIND_SERVICE,SYS_CHROOT,SETFCAP   RunAsAny   RunAsAny           RunAsAny    RunAsAny    false            *
ibm-anyuid-psp              false   SETPCAP,AUDIT_WRITE,CHOWN,NET_RAW,DAC_OVERRIDE,FOWNER,FSETID,KILL,SETUID,SETGID,NET_BIND_SERVICE,SYS_CHROOT,SETFCAP   RunAsAny   RunAsAny           RunAsAny    RunAsAny    false            configMap,emptyDir,projected,secret,downwardAPI,persistentVolumeClaim
ibm-restricted-psp          false                                                                                                                         RunAsAny   MustRunAsNonRoot   MustRunAs   MustRunAs   false            configMap,emptyDir,projected,secret,downwardAPI,persistentVolumeClaim

# PodSecurityPolicyの中でCapabilitiesが設定されていないのでpodの作成自体ができない
$ k apply -f security-context-pod4.yaml
  Error from server (Forbidden): error when creating "security-context-pod4.yaml": pods "security-context-demo-4" is forbidden: unable to validate against any pod security policy: 
  [`spec.containers[0].securityContext.capabilities.add: Invalid value: "NET_ADMIN": capability may not be added
    spec.containers[0].securityContext.capabilities.add: Invalid value: "SYS_TIME": capability may not be added 
    spec.containers[0].securityContext.capabilities.add: Invalid value: "NET_ADMIN": capability may not be added 
    spec.containers[0].securityContext.capabilities.add: Invalid value: "SYS_TIME": capability may not be added 
    spec.containers[0].securityContext.capabilities.add: Invalid value: "NET_ADMIN": capability may not be added 
    spec.containers[0].securityContext.capabilities.add: Invalid value: "SYS_TIME": capability may not be added 
    spec.containers[0].securityContext.capabilities.add: Invalid value: "NET_ADMIN": capability may not be added 
    spec.containers[0].securityContext.capabilities.add: Invali`d value: "SYS_TIME": capability may not be added]

PodSecurityPolicy削除後に、security-context-4.yamlを利用してpodを作成した上で、先ほどと同様に、proc/1/statusを確認します。

ビットが変化していることがわかりますが、どのようなCapabilitiesが付与されているかは不明です。
確認する際には、getpcaps <PID>コマンドを利用することでプロセスに付与されているCapabilitiesを確認できます。
これにより、プロセスにcap_net_admincap_sys_timeがCapabilitiesとして付与されていることがわかります。

$ k apply -f https://k8s.io/examples/pods/security/security-context-4.yaml
pod/security-context-demo-4 created

$ k exec security-context-demo-4 -it bash

$ k get po
NAME                      READY   STATUS    RESTARTS   AGE
security-context-demo-4   1/1     Running   0          9s

root@security-context-demo-4:/# cat /proc/1/status | grep Cap
CapInh:	00000000aa0435fb
CapPrm:	00000000aa0435fb
CapEff:	00000000aa0435fb
CapBnd:	00000000aa0435fb
CapAmb:	0000000000000000

root@security-context-demo-4:/# getpcaps 1
Capabilities for `1': = cap_chown,cap_dac_override,cap_fowner,cap_fsetid,cap_kill,cap_setgid,cap_setuid,cap_setpcap,cap_net_bind_service,cap_net_admin,cap_net_raw,cap_sys_chroot,cap_sys_time,cap_mknod,cap_audit_write,cap_setfcap+eip

PodSecurityPolicyのマニュフェストファイル内でCapabilitiesの許可設定をすることも可能です。
allowedCapabilitiesで指定したCapabilitiesを許可したり、RequiredDropCapabilitiesで任意のCapabilitiesを拒否することも可能です。
https://kubernetes.io/docs/concepts/policy/pod-security-policy/#capabilities

allowAllCapabilities.yaml
apiVersion: policy/v1beta1
kind: PodSecurityPolicy
metadata:
  name: example
spec:
  privileged: false  
  # Required to prevent escalations to root.
  allowPrivilegeEscalation: false
  # This is redundant with non-root + disallow privilege escalation,
  # but we can provide it for defense in depth.
  allowedCapabilities:
  - '*'
  seLinux:
    rule: RunAsAny
  supplementalGroups:
    rule: RunAsAny
  runAsUser:
    rule: RunAsAny
  fsGroup:
    rule: RunAsAny
  volumes:
    - '*'

Pod Security Policyに関する注意点

Pod Security Policy (PSP) は、v1.17時点では beta 機能として提供されています。
ただし、2020年1月末時点で、Pod Security Policy(PSP)はGA化されない方向で進んでいます。
https://github.com/kubernetes/enhancements/issues/5

代替策として Gatekeeper が挙がっており、現在も議論が続いています。
特に、ポリシーの方式の違いを踏まえてどのように移行をスムーズに進めるかという部分が議論されています。
(許可設定を追加するPSPと違い、Gatekeeper は拒否設定を追加する方式のようです)

5
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
5
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?