transformation_testcase.go 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261
  1. /*
  2. Copyright 2017 The Kubernetes Authors.
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. package master
  14. import (
  15. "bytes"
  16. "context"
  17. "fmt"
  18. "io/ioutil"
  19. "os"
  20. "path"
  21. "strconv"
  22. "strings"
  23. "testing"
  24. "github.com/coreos/etcd/clientv3"
  25. "github.com/prometheus/client_golang/prometheus"
  26. "sigs.k8s.io/yaml"
  27. corev1 "k8s.io/api/core/v1"
  28. metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  29. apiserverconfigv1 "k8s.io/apiserver/pkg/apis/config/v1"
  30. "k8s.io/apiserver/pkg/storage/storagebackend"
  31. "k8s.io/apiserver/pkg/storage/value"
  32. "k8s.io/client-go/kubernetes"
  33. kubeapiservertesting "k8s.io/kubernetes/cmd/kube-apiserver/app/testing"
  34. "k8s.io/kubernetes/test/integration"
  35. "k8s.io/kubernetes/test/integration/framework"
  36. )
  37. const (
  38. secretKey = "api_key"
  39. secretVal = "086a7ffc-0225-11e8-ba89-0ed5f89f718b"
  40. encryptionConfigFileName = "encryption.conf"
  41. testNamespace = "secret-encryption-test"
  42. testSecret = "test-secret"
  43. metricsPrefix = "apiserver_storage_"
  44. )
  45. type unSealSecret func(cipherText []byte, ctx value.Context, config apiserverconfigv1.ProviderConfiguration) ([]byte, error)
  46. type transformTest struct {
  47. logger kubeapiservertesting.Logger
  48. storageConfig *storagebackend.Config
  49. configDir string
  50. transformerConfig string
  51. kubeAPIServer kubeapiservertesting.TestServer
  52. restClient *kubernetes.Clientset
  53. ns *corev1.Namespace
  54. secret *corev1.Secret
  55. }
  56. func newTransformTest(l kubeapiservertesting.Logger, transformerConfigYAML string) (*transformTest, error) {
  57. e := transformTest{
  58. logger: l,
  59. transformerConfig: transformerConfigYAML,
  60. storageConfig: framework.SharedEtcd(),
  61. }
  62. var err error
  63. if transformerConfigYAML != "" {
  64. if e.configDir, err = e.createEncryptionConfig(); err != nil {
  65. return nil, fmt.Errorf("error while creating KubeAPIServer encryption config: %v", err)
  66. }
  67. }
  68. if e.kubeAPIServer, err = kubeapiservertesting.StartTestServer(l, nil, e.getEncryptionOptions(), e.storageConfig); err != nil {
  69. return nil, fmt.Errorf("failed to start KubeAPI server: %v", err)
  70. }
  71. if e.restClient, err = kubernetes.NewForConfig(e.kubeAPIServer.ClientConfig); err != nil {
  72. return nil, fmt.Errorf("error while creating rest client: %v", err)
  73. }
  74. if e.ns, err = e.createNamespace(testNamespace); err != nil {
  75. return nil, err
  76. }
  77. if e.secret, err = e.createSecret(testSecret, e.ns.Name); err != nil {
  78. return nil, err
  79. }
  80. return &e, nil
  81. }
  82. func (e *transformTest) cleanUp() {
  83. os.RemoveAll(e.configDir)
  84. e.restClient.CoreV1().Namespaces().Delete(e.ns.Name, metav1.NewDeleteOptions(0))
  85. e.kubeAPIServer.TearDownFn()
  86. }
  87. func (e *transformTest) run(unSealSecretFunc unSealSecret, expectedEnvelopePrefix string) {
  88. response, err := e.readRawRecordFromETCD(e.getETCDPath())
  89. if err != nil {
  90. e.logger.Errorf("failed to read from etcd: %v", err)
  91. return
  92. }
  93. if !bytes.HasPrefix(response.Kvs[0].Value, []byte(expectedEnvelopePrefix)) {
  94. e.logger.Errorf("expected secret to be prefixed with %s, but got %s",
  95. expectedEnvelopePrefix, response.Kvs[0].Value)
  96. return
  97. }
  98. // etcd path of the key is used as the authenticated context - need to pass it to decrypt
  99. ctx := value.DefaultContext([]byte(e.getETCDPath()))
  100. // Envelope header precedes the payload
  101. sealedData := response.Kvs[0].Value[len(expectedEnvelopePrefix):]
  102. transformerConfig, err := e.getEncryptionConfig()
  103. if err != nil {
  104. e.logger.Errorf("failed to parse transformer config: %v", err)
  105. }
  106. v, err := unSealSecretFunc(sealedData, ctx, *transformerConfig)
  107. if err != nil {
  108. e.logger.Errorf("failed to unseal secret: %v", err)
  109. return
  110. }
  111. if !strings.Contains(string(v), secretVal) {
  112. e.logger.Errorf("expected %q after decryption, but got %q", secretVal, string(v))
  113. }
  114. // Secrets should be un-enveloped on direct reads from Kube API Server.
  115. s, err := e.restClient.CoreV1().Secrets(testNamespace).Get(testSecret, metav1.GetOptions{})
  116. if secretVal != string(s.Data[secretKey]) {
  117. e.logger.Errorf("expected %s from KubeAPI, but got %s", secretVal, string(s.Data[secretKey]))
  118. }
  119. }
  120. func (e *transformTest) benchmark(b *testing.B) {
  121. for i := 0; i < b.N; i++ {
  122. _, err := e.createSecret(e.secret.Name+strconv.Itoa(i), e.ns.Name)
  123. if err != nil {
  124. b.Fatalf("failed to create a secret: %v", err)
  125. }
  126. }
  127. }
  128. func (e *transformTest) getETCDPath() string {
  129. return fmt.Sprintf("/%s/secrets/%s/%s", e.storageConfig.Prefix, e.ns.Name, e.secret.Name)
  130. }
  131. func (e *transformTest) getRawSecretFromETCD() ([]byte, error) {
  132. secretETCDPath := e.getETCDPath()
  133. etcdResponse, err := e.readRawRecordFromETCD(secretETCDPath)
  134. if err != nil {
  135. return nil, fmt.Errorf("failed to read %s from etcd: %v", secretETCDPath, err)
  136. }
  137. return etcdResponse.Kvs[0].Value, nil
  138. }
  139. func (e *transformTest) getEncryptionOptions() []string {
  140. if e.transformerConfig != "" {
  141. return []string{"--encryption-provider-config", path.Join(e.configDir, encryptionConfigFileName)}
  142. }
  143. return nil
  144. }
  145. func (e *transformTest) createEncryptionConfig() (string, error) {
  146. tempDir, err := ioutil.TempDir("", "secrets-encryption-test")
  147. if err != nil {
  148. return "", fmt.Errorf("failed to create temp directory: %v", err)
  149. }
  150. encryptionConfig := path.Join(tempDir, encryptionConfigFileName)
  151. if err := ioutil.WriteFile(encryptionConfig, []byte(e.transformerConfig), 0644); err != nil {
  152. os.RemoveAll(tempDir)
  153. return "", fmt.Errorf("error while writing encryption config: %v", err)
  154. }
  155. return tempDir, nil
  156. }
  157. func (e *transformTest) getEncryptionConfig() (*apiserverconfigv1.ProviderConfiguration, error) {
  158. var config apiserverconfigv1.EncryptionConfiguration
  159. err := yaml.Unmarshal([]byte(e.transformerConfig), &config)
  160. if err != nil {
  161. return nil, fmt.Errorf("failed to extract transformer key: %v", err)
  162. }
  163. return &config.Resources[0].Providers[0], nil
  164. }
  165. func (e *transformTest) createNamespace(name string) (*corev1.Namespace, error) {
  166. ns := &corev1.Namespace{
  167. ObjectMeta: metav1.ObjectMeta{
  168. Name: name,
  169. },
  170. }
  171. if _, err := e.restClient.CoreV1().Namespaces().Create(ns); err != nil {
  172. return nil, fmt.Errorf("unable to create testing namespace %v", err)
  173. }
  174. return ns, nil
  175. }
  176. func (e *transformTest) createSecret(name, namespace string) (*corev1.Secret, error) {
  177. secret := &corev1.Secret{
  178. ObjectMeta: metav1.ObjectMeta{
  179. Name: name,
  180. Namespace: namespace,
  181. },
  182. Data: map[string][]byte{
  183. secretKey: []byte(secretVal),
  184. },
  185. }
  186. if _, err := e.restClient.CoreV1().Secrets(secret.Namespace).Create(secret); err != nil {
  187. return nil, fmt.Errorf("error while writing secret: %v", err)
  188. }
  189. return secret, nil
  190. }
  191. func (e *transformTest) readRawRecordFromETCD(path string) (*clientv3.GetResponse, error) {
  192. _, etcdClient, err := integration.GetEtcdClients(e.kubeAPIServer.ServerOpts.Etcd.StorageConfig.Transport)
  193. if err != nil {
  194. return nil, fmt.Errorf("failed to create etcd client: %v", err)
  195. }
  196. response, err := etcdClient.Get(context.Background(), path, clientv3.WithPrefix())
  197. if err != nil {
  198. return nil, fmt.Errorf("failed to retrieve secret from etcd %v", err)
  199. }
  200. return response, nil
  201. }
  202. func (e *transformTest) printMetrics() error {
  203. e.logger.Logf("Transformation Metrics:")
  204. metrics, err := prometheus.DefaultGatherer.Gather()
  205. if err != nil {
  206. return fmt.Errorf("failed to gather metrics: %s", err)
  207. }
  208. for _, mf := range metrics {
  209. if strings.HasPrefix(*mf.Name, metricsPrefix) {
  210. e.logger.Logf("%s", *mf.Name)
  211. for _, metric := range mf.GetMetric() {
  212. e.logger.Logf("%v", metric)
  213. }
  214. }
  215. }
  216. return nil
  217. }