こんにちは!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上で静的解析が可能です。
ソースコードはopenshift-controller-managerにあります。
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) (省略) }
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 }
上記のソースコードは元々originにありましたが、OpenShift4からOperator管理となったため移動されています。
おわりに
Service Accountのsecretについて見てきました。
OpenShiftはKubernetesをベースとしながらも、各種組み込みコンポーネントやそれにアクセスする仕組みがデフォルトで用意されています。
是非皆様もService Accountを駆使してアプリケーションの自動化に取り組んでみてください!
以上です。