1package k8s_test 2 3import ( 4 "encoding/json" 5 6 . "github.com/concourse/concourse/topgun" 7 . "github.com/onsi/ginkgo" 8 . "github.com/onsi/gomega" 9) 10 11var _ = Describe("Kubernetes credential management", func() { 12 var ( 13 atc Endpoint 14 username, password = "test", "test" 15 ) 16 17 BeforeEach(func() { 18 setReleaseNameAndNamespace("k8s-cm") 19 }) 20 21 AfterEach(func() { 22 atc.Close() 23 cleanupReleases() 24 }) 25 26 JustBeforeEach(func() { 27 atc = waitAndLogin(namespace, releaseName+"-web") 28 }) 29 30 Context("/api/v1/info/creds", func() { 31 var parsedResponse struct { 32 Kubernetes struct { 33 ConfigPath string `json:"config_path"` 34 InClusterConfig bool `json:"in_cluster_config"` 35 NamespaceConfig string `json:"namespace_config"` 36 } `json:"kubernetes"` 37 } 38 39 BeforeEach(func() { 40 deployConcourseChart(releaseName, "--set=worker.replicas=1") 41 }) 42 43 It("Contains kubernetes config", func() { 44 token, err := FetchToken("http://"+atc.Address(), username, password) 45 Expect(err).ToNot(HaveOccurred()) 46 47 body, err := RequestCredsInfo("http://"+atc.Address(), token.AccessToken) 48 Expect(err).ToNot(HaveOccurred()) 49 50 err = json.Unmarshal(body, &parsedResponse) 51 Expect(err).ToNot(HaveOccurred()) 52 53 Expect(parsedResponse.Kubernetes.ConfigPath).To(BeEmpty()) 54 Expect(parsedResponse.Kubernetes.InClusterConfig).To(BeTrue()) 55 Expect(parsedResponse.Kubernetes.NamespaceConfig).To(Equal(releaseName + "-")) 56 }) 57 }) 58 59 Context("Consuming k8s credentials", func() { 60 var cachingSetup = func() { 61 deployConcourseChart(releaseName, "--set=worker.replicas=1", 62 "--set=concourse.web.secretCacheEnabled=true", 63 "--set=concourse.web.secretCacheDuration=600s", 64 ) 65 } 66 67 var disableTeamNamespaces = func() { 68 By("creating a namespace made by the user instead of the chart") 69 Run(nil, "kubectl", "create", "namespace", releaseName+"-main") 70 71 By("binding the role that grants access to the secrets in our newly created namespace ") 72 Run(nil, 73 "kubectl", "create", 74 "--namespace", releaseName+"-main", 75 "rolebinding", "rb", 76 "--clusterrole", releaseName+"-web", 77 "--serviceaccount", releaseName+":"+releaseName+"-web", 78 ) 79 80 deployConcourseChart(releaseName, "--set=worker.replicas=1", 81 "--set=concourse.web.secretCacheEnabled=true", 82 "--set=concourse.web.secretCacheDuration=600s", 83 "--set=concourse.web.kubernetes.createTeamNamespaces=false", 84 ) 85 } 86 87 Context("using per-team credentials", func() { 88 89 const ( 90 secretNameFoo = "foo" 91 secretNameCaz = "caz" 92 ) 93 94 Context("using the default namespace created by the chart", func() { 95 BeforeEach(func() { 96 deployConcourseChart(releaseName, "--set=worker.replicas=1") 97 }) 98 99 It("succeeds", func() { 100 runsBuildWithCredentialsResolved(secretNameFoo, secretNameCaz) 101 }) 102 }) 103 104 Context("with caching enabled", func() { 105 BeforeEach(cachingSetup) 106 107 It("gets cached credentials", func() { 108 runGetsCachedCredentials(secretNameFoo, secretNameCaz) 109 }) 110 }) 111 112 Context("using a user-provided namespace", func() { 113 BeforeEach(disableTeamNamespaces) 114 115 It("succeeds", func() { 116 runsBuildWithCredentialsResolved(secretNameFoo, secretNameCaz) 117 }) 118 119 AfterEach(func() { 120 Run(nil, "kubectl", "delete", "namespace", releaseName+"-main", "--wait=false") 121 }) 122 }) 123 124 }) 125 126 Context("using per-pipeline credentials", func() { 127 128 const ( 129 secretNameFoo = "pipeline.foo" 130 secretNameCaz = "pipeline.caz" 131 ) 132 133 Context("using the default namespace created by the chart", func() { 134 BeforeEach(func() { 135 deployConcourseChart(releaseName, "--set=worker.replicas=1") 136 }) 137 138 It("succeeds", func() { 139 runsBuildWithCredentialsResolved(secretNameFoo, secretNameCaz) 140 }) 141 }) 142 143 Context("with caching enabled", func() { 144 BeforeEach(cachingSetup) 145 146 It("gets cached credentials", func() { 147 runGetsCachedCredentials(secretNameFoo, secretNameCaz) 148 }) 149 }) 150 151 Context("using a user-provided namespace", func() { 152 BeforeEach(disableTeamNamespaces) 153 154 It("succeeds", func() { 155 runsBuildWithCredentialsResolved(secretNameFoo, secretNameCaz) 156 }) 157 158 AfterEach(func() { 159 Run(nil, "kubectl", "delete", "namespace", releaseName+"-main", "--wait=false") 160 }) 161 }) 162 }) 163 }) 164 165 Context("one-off build", func() { 166 BeforeEach(func() { 167 deployConcourseChart(releaseName, "--set=worker.replicas=1") 168 }) 169 170 It("runs the one-off build successfully", func() { 171 By("creating the secret in the main team") 172 createCredentialSecret(releaseName, "some-secret", "main", map[string]string{"value": "mysecret"}) 173 174 By("successfully running the one-off build") 175 fly.Run("execute", 176 "-c", "tasks/simple-secret.yml") 177 }) 178 179 It("one-off build fails", func() { 180 By("not creating the secret") 181 sess := fly.Start("execute", 182 "-c", "tasks/simple-secret.yml") 183 <-sess.Exited 184 Expect(sess.ExitCode()).NotTo(Equal(0)) 185 }) 186 }) 187 188}) 189 190func deleteSecret(releaseName, team, secretName string) { 191 Run(nil, "kubectl", "--namespace="+releaseName+"-main", "delete", "secret", secretName) 192} 193 194func createCredentialSecret(releaseName, secretName, team string, kv map[string]string) { 195 args := []string{ 196 "create", 197 "secret", 198 "generic", 199 secretName, 200 "--namespace=" + releaseName + "-" + team, 201 } 202 203 for key, value := range kv { 204 args = append(args, "--from-literal="+key+"="+value) 205 } 206 207 Run(nil, "kubectl", args...) 208} 209 210func runsBuildWithCredentialsResolved(normalSecret string, specialKeySecret string) { 211 By("creating credentials in k8s credential manager") 212 createCredentialSecret(releaseName, normalSecret, "main", map[string]string{"value": "bar"}) 213 createCredentialSecret(releaseName, specialKeySecret, "main", map[string]string{"baz": "zaz"}) 214 215 fly.Run("set-pipeline", "-n", 216 "-c", "pipelines/minimal-credential-management.yml", 217 "-p", "pipeline", 218 ) 219 220 fly.Run("unpause-pipeline", "-p", "pipeline") 221 222 session := fly.Start("trigger-job", "-j", "pipeline/unit", "-w") 223 Wait(session) 224 225 By("seeing the credentials were resolved by concourse") 226 Expect(string(session.Out.Contents())).To(ContainSubstring("bar")) 227 Expect(string(session.Out.Contents())).To(ContainSubstring("zaz")) 228} 229 230func runGetsCachedCredentials(secretNameFoo string, secretNameCaz string) { 231 runsBuildWithCredentialsResolved(secretNameFoo, secretNameCaz) 232 deleteSecret(releaseName, "main", secretNameFoo) 233 deleteSecret(releaseName, "main", secretNameCaz) 234 By("seeing that concourse uses the cached credentials") 235 runsBuildWithCredentialsResolved(secretNameFoo, secretNameCaz) 236} 237