Service AccountのSecretについて

こんにちは!Red Hatの石川と申します。

昨年よりOpenShiftのテクニカルサポートエンジニアとして働いております。

まだまだOpenShift勉強中の身ですが、日々の業務で気付いたことなどを少しずつ記事にしていけたらと考えております。

今回はService Accountの認証の仕組みがどのようになっているかについて触れてみたいと思います。

Service Accountって?

Service Accountは通常のUserとは別に、Podや各種コンポーネントがAPI呼び出しを行うために設計されたKubernetesのオブジェクトです。JenkinsやTravis CIなどでアプリケーションを自動化する際にも利用されます。

Service AccountはUserと違い、特定のタスクを実行することを目的に作られていますので、特定のnamespaceに紐づいています。ただし、namespaceを跨げば名前の重複が可能です。

普段使っているPodにも必ず1つのService Accountが紐づいています。Pod作成時にService Accountを明示的に指定しなかった場合そのnamespaceのdefaultという名前のService Accountが紐づけられます。

PodはService Accountの持つ認証情報を利用することでAPI serverやイメージレジストリへのアクセスが可能になります。

Service Accountを作成する

さっそくService Accountを作成してみます。

なお、当記事ではOpenShift Container Platform 4.3を利用しています。

$ oc create sa shishika
serviceaccount/shishika created
$ oc describe sa shishika
Name:                shishika
Namespace:           default
Labels:              <none>
Annotations:         <none>
Image pull secrets:  shishika-dockercfg-nklpv
Mountable secrets:   shishika-dockercfg-nklpv
                     shishika-token-nrm7j
Tokens:              shishika-token-h69f4
                     shishika-token-nrm7j
Events:              <none>

default namespaceに shishika という名前のService Accountを作成しました。

するとImage pull secrets以下に何やら色々と作成されていることがわかります。

oc get secret コマンドを実行すると、secretが合計3つ作成されていることがわかります( shishika-dockercfg-nklpv , shishika-token-nrm7j, shishika-token-h69f4)。

1つ1つ見ていきましょう。

それぞれのsecretについて

Image pull secretsに作成された shishika-dockercfg-nklpv はOpenShiftの内部イメージレジストリにアクセスしてイメージストリームからイメージをpullするために使用されます。

shishika-token-nrm7j はBuilt-in(組み込み) secretと呼ばれるもので、APIを呼び出すための認証情報として利用されます。

これらはMountable secretsとして登録され、Podにマウントして利用されます。

ちなみにKubernetesの場合、内部イメージレジストリが無いのでImage pull secretsは自動作成されません。

  • Kubernetesで作成した場合
$ kubectl describe sa shishika
Name:                shishika
Namespace:           default
Labels:              <none>
Annotations:         <none>
Image pull secrets:  <none>
Mountable secrets:   shishika-token-c4jz2
Tokens:              shishika-token-c4jz2
Events:              <none>

OpenShiftのマニュアルには以下のように記載されています。

各サービスアカウントには、2 つのシークレットが自動的に含まれます。

  • API トークン
  • OpenShift Container レジストリーの認証情報

Built-in secretの shishika-token-nrm7j は「APIトークン」、Image pull secretの shishika-dockercfg-nklpv は「OpenShift Container レジストリーの認証情報」に該当します。

それでは、3つ目の shishika-token-h69f4 は何に利用されているのでしょう?

検証してみましょう。

secretを削除してみる

まずはBuilt-in secret( shishika-token-nrm7j )から消してみます。

$ oc delete secret shishika-token-nrm7j
secret "shishika-token-nrm7j" deleted
$ oc describe sa shishika
Name:                shishika
Namespace:           default
Labels:              <none>
Annotations:         <none>
Image pull secrets:  shishika-dockercfg-nklpv
Mountable secrets:   shishika-dockercfg-nklpv
-                     shishika-token-nrm7j
+                     shishika-token-l2tv4
Tokens:              shishika-token-h69f4
-                     shishika-token-nrm7j
+                     shishika-token-l2tv4
Events:              <none>

shishika-token-l2tv4 という新しいsecretが登録されました。

次にImage pull secret shishika-dockercfg-nklpv を削除してみます。

$ oc delete secret shishika-dockercfg-nklpv
secret "shishika-dockercfg-nklpv" deleted
$ oc describe sa shishika
Name:                shishika
Namespace:           default
Labels:              <none>
Annotations:         <none>
-Image pull secrets:  shishika-dockercfg-nklpv
+Image pull secrets:  shishika-dockercfg-kwrbf
Mountable secrets:   shishika-token-l2tv4
-                     shishika-dockercfg-nklpv
+                     shishika-dockercfg-kwrbf
-Tokens:              shishika-token-h69f4
+Tokens:              shishika-token-4nnbp
                     shishika-token-l2tv4
Events:              <none>

すると、Image pull secretsが shishika-dockercfg-kwrbf に変更された上、 3つ目のsecretも shishika-token-h69f4 から shishika-token-4nnbp というsecretに変更されています。

逆もまた然りで、3つ目のsecretを削除してもImage pull secretが再作成されます。どうやら、3つ目のsecretはImage pull secretと関係しているようです。

どのように実装されているか、ソースコードを確認してみましょう。

3つ目のsecretについて

泥臭くソースコードを解析していきます。

余談ですが、Githubは2019年6月頃からGo、Python、Rubyなど一部の開発言語で関数名をクリックすることで定義元にジャンプすることが可能になりました。

Go言語で書かれたOpenShiftやKubernetesのコードは、Github上で静的解析が可能です。

help.github.com

ソースコードはopenshift-controller-managerにあります。

github.com

Service AccountはserviceAccountControllerによって監視され、Image pull secretが見つからなくなると syncServiceAccount 関数によって再作成されています。

func (e *DockercfgController) syncServiceAccount(key string) error {
(省略)
    serviceAccount := obj.(*v1.ServiceAccount).DeepCopyObject().(*v1.ServiceAccount)

    mountableDockercfgSecrets, imageDockercfgPullSecrets := getGeneratedDockercfgSecretNames(serviceAccount)

    // If we have a pull secret in one list, use it for the other.  It must only be in one list because
    // otherwise we wouldn't "needsDockercfgSecret"
    foundPullSecret := len(imageDockercfgPullSecrets) > 0
    foundMountableSecret := len(mountableDockercfgSecrets) > 0
    if foundPullSecret || foundMountableSecret {
        switch {
        case foundPullSecret:
            serviceAccount.Secrets = append(serviceAccount.Secrets, v1.ObjectReference{Name: imageDockercfgPullSecrets.List()[0]})
        case foundMountableSecret:
            serviceAccount.ImagePullSecrets = append(serviceAccount.ImagePullSecrets, v1.LocalObjectReference{Name: mountableDockercfgSecrets.List()[0]})
        }
        // Clear the pending token annotation when updating
        delete(serviceAccount.Annotations, PendingTokenAnnotation)

        updatedSA, err := e.client.CoreV1().ServiceAccounts(serviceAccount.Namespace).Update(serviceAccount)
        if err == nil {
            e.serviceAccountCache.Mutation(updatedSA)
        }
        return err
    }

    dockercfgSecret, created, err := e.createDockerPullSecret(serviceAccount)
(省略)
}

https://github.com/openshift/openshift-controller-manager/blob/release-4.3/pkg/serviceaccounts/controllers/create_dockercfg_secrets.go#L316-L401

secret作成部分の createDockerPullSecret 関数を見てみます。3つ目のsecretはpull secretを作成する際のアノテーションやパスワードのために作成されていることがわかります。

// createDockerPullSecret creates a dockercfg secret based on the token secret
func (e *DockercfgController) createDockerPullSecret(serviceAccount *v1.ServiceAccount) (*v1.Secret, bool, error) {
    tokenSecret, isPopulated, err := e.createTokenSecret(serviceAccount)
(省略)
        dockercfgSecret := &v1.Secret{
        ObjectMeta: metav1.ObjectMeta{
            Name:      secret.Strategy.GenerateName(getDockercfgSecretNamePrefix(serviceAccount.Name)),
            Namespace: tokenSecret.Namespace,
            Annotations: map[string]string{
                v1.ServiceAccountNameKey:           serviceAccount.Name,
                v1.ServiceAccountUIDKey:            string(serviceAccount.UID),
                ServiceAccountTokenSecretNameKey:   string(tokenSecret.Name),
                ServiceAccountTokenValueAnnotation: string(tokenSecret.Data[v1.ServiceAccountTokenKey]),
            },
        },
        Type: v1.SecretTypeDockercfg,
        Data: map[string][]byte{},
    }
    glog.V(4).Infof("Creating dockercfg secret %q for service account %s/%s", dockercfgSecret.Name, serviceAccount.Namespace, serviceAccount.Name)
(省略)
    dockercfg := credentialprovider.DockerConfig{}
    for _, dockerURL := range e.dockerURLs {
        dockercfg[dockerURL] = credentialprovider.DockerConfigEntry{
            Username: "serviceaccount",
            Password: string(tokenSecret.Data[v1.ServiceAccountTokenKey]), // token keyをPasswordとして設定
            Email:    "serviceaccount@example.org",
        }
    }
(省略)
        // Save the secret
    createdSecret, err := e.client.Core().Secrets(tokenSecret.Namespace).Create(dockercfgSecret)
    return createdSecret, err == nil, err
}

https://github.com/openshift/openshift-controller-manager/blob/release-4.3/pkg/serviceaccounts/controllers/create_dockercfg_secrets.go#L462-L514

上記のソースコードは元々originにありましたが、OpenShift4からOperator管理となったため移動されています。

おわりに

Service Accountのsecretについて見てきました。

OpenShiftはKubernetesをベースとしながらも、各種組み込みコンポーネントやそれにアクセスする仕組みがデフォルトで用意されています。

是非皆様もService Accountを駆使してアプリケーションの自動化に取り組んでみてください!

以上です。

* 各記事は著者の見解によるものでありその所属組織を代表する公式なものではありません。その内容については非公式見解を含みます。