github.com/pf-qiu/concourse/v6@v6.7.3-0.20201207032516-1f455d73275f/topgun/core/vault_test.go (about) 1 package topgun_test 2 3 import ( 4 "fmt" 5 "io/ioutil" 6 "os" 7 "regexp" 8 "strings" 9 "sync" 10 "time" 11 12 "github.com/onsi/gomega/gbytes" 13 "github.com/onsi/gomega/gexec" 14 15 . "github.com/pf-qiu/concourse/v6/topgun" 16 . "github.com/pf-qiu/concourse/v6/topgun/common" 17 . "github.com/onsi/ginkgo" 18 . "github.com/onsi/gomega" 19 ) 20 21 var _ = Describe("Vault", func() { 22 var tokenDuration = 30 * time.Second 23 24 BeforeEach(func() { 25 if !strings.Contains(string(Bosh("releases").Out.Contents()), "vault") { 26 Skip("vault release not uploaded") 27 } 28 }) 29 30 Describe("A deployment with vault", func() { 31 var ( 32 v *vault 33 varsStore *os.File 34 vaultInstance *BoshInstance 35 ) 36 37 BeforeEach(func() { 38 var err error 39 40 varsStore, err = ioutil.TempFile("", "vars-store.yml") 41 Expect(err).ToNot(HaveOccurred()) 42 Expect(varsStore.Close()).To(Succeed()) 43 44 Deploy( 45 "deployments/concourse.yml", 46 "-o", "operations/add-vault.yml", 47 "-v", "web_instances=0", 48 "-v", "vault_url=dontcare", 49 "-v", "vault_client_token=dontcare", 50 "-v", "vault_auth_backend=dontcare", 51 "-v", "vault_auth_params=dontcare", 52 ) 53 54 vaultInstance = JobInstance("vault") 55 v = newVault(vaultInstance.IP) 56 57 v.Run("policy-write", "concourse", "vault/concourse-policy.hcl") 58 }) 59 60 AfterEach(func() { 61 Expect(os.Remove(varsStore.Name())).To(Succeed()) 62 }) 63 64 testTokenRenewal := func() { 65 Context("when enough time has passed such that token would have expired", func() { 66 BeforeEach(func() { 67 v.Run("write", "concourse/main/team_secret", "value=some_team_secret") 68 v.Run("write", "concourse/main/resource_version", "value=some_exposed_version_secret") 69 70 By("waiting for long enough that the initial token would have expired") 71 time.Sleep(tokenDuration) 72 }) 73 74 It("renews the token", func() { 75 watch := Fly.StartWithEnv( 76 []string{ 77 "EXPECTED_TEAM_SECRET=some_team_secret", 78 "EXPECTED_RESOURCE_VERSION_SECRET=some_exposed_version_secret", 79 }, 80 "execute", "-c", "tasks/credential-management.yml", 81 ) 82 Wait(watch) 83 Expect(watch).To(gbytes.Say("all credentials matched expected values")) 84 }) 85 }) 86 } 87 88 Context("with token auth", func() { 89 BeforeEach(func() { 90 By("creating a periodic token") 91 create := v.Run("token-create", "-period", tokenDuration.String(), "-policy", "concourse") 92 content := string(create.Out.Contents()) 93 token := regexp.MustCompile(`token\s+(.*)`).FindStringSubmatch(content)[1] 94 95 By("renewing the token throughout the deploy") 96 renewing := new(sync.WaitGroup) 97 stopRenewing := make(chan struct{}) 98 99 defer func() { 100 By("not renewing the token anymore, leaving it to Concourse") 101 close(stopRenewing) 102 renewing.Wait() 103 }() 104 105 renewTicker := time.NewTicker(5 * time.Second) 106 renewing.Add(1) 107 go func() { 108 defer renewing.Done() 109 defer GinkgoRecover() 110 111 for { 112 select { 113 case <-renewTicker.C: 114 v.Run("token-renew", token) 115 case <-stopRenewing: 116 return 117 } 118 } 119 }() 120 121 By("deploying concourse with the token") 122 Deploy( 123 "deployments/concourse.yml", 124 "-o", "operations/add-vault.yml", 125 "--vars-store", varsStore.Name(), 126 "-v", "vault_url="+v.URI(), 127 "-v", "vault_ip="+v.IP(), 128 "-v", "web_instances=1", 129 "-v", "vault_client_token="+token, 130 "-v", `vault_auth_backend=""`, 131 "-v", "vault_auth_params={}", 132 ) 133 }) 134 135 testCredentialManagement(func() { 136 v.Run("write", "concourse/main/team_secret", "value=some_team_secret") 137 v.Run("write", "concourse/main/pipeline-creds-test/assertion_script", "value="+assertionScript) 138 v.Run("write", "concourse/main/pipeline-creds-test/canary", "value=some_canary") 139 v.Run("write", "concourse/main/pipeline-creds-test/resource_type_secret", "value=some_resource_type_secret") 140 v.Run("write", "concourse/main/pipeline-creds-test/resource_secret", "value=some_resource_secret") 141 v.Run("write", "concourse/main/pipeline-creds-test/job_secret", "username=some_username", "password=some_password") 142 v.Run("write", "concourse/main/pipeline-creds-test/resource_version", "value=some_exposed_version_secret") 143 }, func() { 144 v.Run("write", "concourse/main/team_secret", "value=some_team_secret") 145 v.Run("write", "concourse/main/resource_version", "value=some_exposed_version_secret") 146 }) 147 148 testTokenRenewal() 149 }) 150 151 Context("with TLS auth", func() { 152 BeforeEach(func() { 153 Deploy( 154 "deployments/concourse.yml", 155 "-o", "operations/add-vault.yml", 156 "--vars-store", varsStore.Name(), 157 "-o", "operations/enable-vault-tls.yml", 158 "-v", "vault_url="+v.URI(), 159 "-v", "vault_ip="+v.IP(), 160 "-v", "vault_client_token=dontcare", 161 "-v", `vault_auth_backend=""`, 162 "-v", "vault_auth_params={}", 163 "-v", "web_instances=0", 164 ) 165 166 vaultCACertFile, err := ioutil.TempFile("", "vault-ca.cert") 167 Expect(err).ToNot(HaveOccurred()) 168 169 vaultCACert := vaultCACertFile.Name() 170 171 session := Bosh("interpolate", "--path", "/vault_ca/certificate", varsStore.Name()) 172 _, err = fmt.Fprintf(vaultCACertFile, "%s", session.Out.Contents()) 173 Expect(err).ToNot(HaveOccurred()) 174 Expect(vaultCACertFile.Close()).To(Succeed()) 175 176 v.SetCA(vaultCACert) 177 v.Unseal() 178 179 v.Run("auth-enable", "cert") 180 v.Run( 181 "write", 182 "auth/cert/certs/concourse", 183 "display_name=concourse", 184 "certificate=@"+vaultCACert, 185 "policies=concourse", 186 fmt.Sprintf("ttl=%d", tokenDuration/time.Second), 187 ) 188 189 Deploy( 190 "deployments/concourse.yml", 191 "-o", "operations/add-vault.yml", 192 "--vars-store", varsStore.Name(), 193 "-o", "operations/enable-vault-tls.yml", 194 "-v", "vault_url="+v.URI(), 195 "-v", "vault_ip="+v.IP(), 196 "-v", "web_instances=1", 197 "-v", `vault_client_token=""`, 198 "-v", "vault_auth_backend=cert", 199 "-v", "vault_auth_params={}", 200 ) 201 }) 202 203 testTokenRenewal() 204 }) 205 206 Context("with approle auth", func() { 207 BeforeEach(func() { 208 v.Run("auth-enable", "approle") 209 210 v.Run( 211 "write", 212 "auth/approle/role/concourse", 213 "policies=concourse", 214 fmt.Sprintf("period=%d", tokenDuration/time.Second), 215 ) 216 217 getRole := v.Run("read", "auth/approle/role/concourse/role-id") 218 content := string(getRole.Out.Contents()) 219 roleID := regexp.MustCompile(`role_id\s+(.*)`).FindStringSubmatch(content)[1] 220 221 createSecret := v.Run("write", "-f", "auth/approle/role/concourse/secret-id") 222 content = string(createSecret.Out.Contents()) 223 secretID := regexp.MustCompile(`secret_id\s+(.*)`).FindStringSubmatch(content)[1] 224 225 Deploy( 226 "deployments/concourse.yml", 227 "-o", "operations/add-vault.yml", 228 "--vars-store", varsStore.Name(), 229 "-v", "vault_url="+v.URI(), 230 "-v", "vault_ip="+v.IP(), 231 "-v", "web_instances=1", 232 "-v", `vault_client_token=""`, 233 "-v", "vault_auth_backend=approle", 234 "-v", `vault_auth_params={"role_id":"`+roleID+`","secret_id":"`+secretID+`"}`, 235 ) 236 }) 237 238 testTokenRenewal() 239 }) 240 }) 241 }) 242 243 type vault struct { 244 ip string 245 key1, key2, key3 string 246 token string 247 caCert string 248 } 249 250 func newVault(ip string) *vault { 251 v := &vault{ 252 ip: ip, 253 } 254 v.init() 255 return v 256 } 257 258 func (v *vault) SetCA(filename string) { v.caCert = filename } 259 func (v *vault) IP() string { return v.ip } 260 func (v *vault) ClientToken() string { return v.token } 261 func (v *vault) URI() string { 262 if v.caCert == "" { 263 return "http://" + v.ip + ":8200" 264 } 265 266 return "https://" + v.ip + ":8200" 267 } 268 269 func (v *vault) Run(command string, args ...string) *gexec.Session { 270 env := append( 271 os.Environ(), 272 "VAULT_ADDR="+v.URI(), 273 "VAULT_TOKEN="+v.token, 274 ) 275 if v.caCert != "" { 276 env = append( 277 env, 278 "VAULT_CACERT="+v.caCert, 279 "VAULT_SKIP_VERIFY=true", 280 ) 281 } 282 session := Start(env, "vault", append([]string{command}, args...)...) 283 Wait(session) 284 return session 285 } 286 287 func (v *vault) init() { 288 init := v.Run("init") 289 content := string(init.Out.Contents()) 290 v.key1 = regexp.MustCompile(`Unseal Key 1: (.*)`).FindStringSubmatch(content)[1] 291 v.key2 = regexp.MustCompile(`Unseal Key 2: (.*)`).FindStringSubmatch(content)[1] 292 v.key3 = regexp.MustCompile(`Unseal Key 3: (.*)`).FindStringSubmatch(content)[1] 293 v.token = regexp.MustCompile(`Initial Root Token: (.*)`).FindStringSubmatch(content)[1] 294 v.Unseal() 295 v.Run("mount", "-path", "concourse/main", "generic") 296 } 297 298 func (v *vault) Unseal() { 299 v.Run("unseal", v.key1) 300 v.Run("unseal", v.key2) 301 v.Run("unseal", v.key3) 302 }