1package api_test
2
3import (
4	"encoding/json"
5	"errors"
6	"io/ioutil"
7	"net/http"
8
9	"code.cloudfoundry.org/lager"
10	"github.com/aws/aws-sdk-go/aws/awserr"
11	awssecretsmanager "github.com/aws/aws-sdk-go/service/secretsmanager"
12	"github.com/aws/aws-sdk-go/service/secretsmanager/secretsmanageriface"
13	awsssm "github.com/aws/aws-sdk-go/service/ssm"
14	"github.com/aws/aws-sdk-go/service/ssm/ssmiface"
15	"github.com/concourse/concourse/atc/creds/credhub"
16	"github.com/concourse/concourse/atc/creds/secretsmanager"
17	"github.com/concourse/concourse/atc/creds/ssm"
18	"github.com/concourse/concourse/atc/creds/vault"
19	. "github.com/concourse/concourse/atc/testhelpers"
20	vaultapi "github.com/hashicorp/vault/api"
21	. "github.com/onsi/ginkgo"
22	. "github.com/onsi/gomega"
23	"github.com/onsi/gomega/ghttp"
24)
25
26type MockSsmService struct {
27	ssmiface.SSMAPI
28
29	stubGetParameter func(input *awsssm.GetParameterInput) (*awsssm.GetParameterOutput, error)
30}
31
32func (m *MockSsmService) GetParameter(input *awsssm.GetParameterInput) (*awsssm.GetParameterOutput, error) {
33	return m.stubGetParameter(input)
34}
35
36type MockSecretsManagerService struct {
37	secretsmanageriface.SecretsManagerAPI
38
39	stubGetSecretValue func(input *awssecretsmanager.GetSecretValueInput) (*awssecretsmanager.GetSecretValueOutput, error)
40}
41
42func (m *MockSecretsManagerService) GetSecretValue(input *awssecretsmanager.GetSecretValueInput) (*awssecretsmanager.GetSecretValueOutput, error) {
43	return m.stubGetSecretValue(input)
44}
45
46var _ = Describe("Pipelines API", func() {
47	Describe("GET /api/v1/info", func() {
48		var response *http.Response
49
50		JustBeforeEach(func() {
51			var err error
52
53			response, err = client.Get(server.URL + "/api/v1/info")
54			Expect(err).NotTo(HaveOccurred())
55		})
56
57		It("returns Content-Type 'application/json'", func() {
58			expectedHeaderEntries := map[string]string{
59				"Content-Type": "application/json",
60			}
61			Expect(response).Should(IncludeHeaderEntries(expectedHeaderEntries))
62		})
63
64		It("contains the version", func() {
65			body, err := ioutil.ReadAll(response.Body)
66			Expect(err).NotTo(HaveOccurred())
67
68			Expect(body).To(MatchJSON(`{
69				"version": "1.2.3",
70				"worker_version": "4.5.6",
71				"external_url": "https://example.com",
72				"cluster_name": "Test Cluster"
73			}`))
74		})
75	})
76
77	Describe("GET /api/v1/info/creds", func() {
78		var (
79			response   *http.Response
80			credServer *ghttp.Server
81			body       []byte
82		)
83
84		JustBeforeEach(func() {
85			req, err := http.NewRequest("GET", server.URL+"/api/v1/info/creds", nil)
86			Expect(err).NotTo(HaveOccurred())
87			req.Header.Set("Content-Type", "application/json")
88
89			response, err = client.Do(req)
90			Expect(err).NotTo(HaveOccurred())
91
92			Expect(response.StatusCode).To(Equal(http.StatusOK))
93			expectedHeaderEntries := map[string]string{
94				"Content-Type": "application/json",
95			}
96			Expect(response).Should(IncludeHeaderEntries(expectedHeaderEntries))
97
98			body, err = ioutil.ReadAll(response.Body)
99			Expect(err).NotTo(HaveOccurred())
100		})
101
102		Context("SSM", func() {
103			var mockService MockSsmService
104
105			BeforeEach(func() {
106				fakeAccess.IsAuthenticatedReturns(true)
107				fakeAccess.IsAdminReturns(true)
108
109				ssmAccess := ssm.NewSsm(lager.NewLogger("ssm_test"), &mockService, nil)
110				ssmManager := &ssm.SsmManager{
111					AwsAccessKeyID:         "",
112					AwsSecretAccessKey:     "",
113					AwsSessionToken:        "",
114					AwsRegion:              "blah",
115					PipelineSecretTemplate: "pipeline-secret-template",
116					TeamSecretTemplate:     "team-secret-template",
117					Ssm:                    ssmAccess,
118				}
119
120				credsManagers["ssm"] = ssmManager
121			})
122
123			Context("returns configured ssm manager", func() {
124				Context("get ssm manager info returns error", func() {
125
126					BeforeEach(func() {
127						mockService.stubGetParameter = func(input *awsssm.GetParameterInput) (*awsssm.GetParameterOutput, error) {
128							return nil, errors.New("some error occured")
129						}
130					})
131
132					It("includes the error in json response", func() {
133						Expect(body).To(MatchJSON(`{
134          "ssm": {
135						"aws_region": "blah",
136						"health": {
137							"error": "some error occured",
138							"method": "GetParameter"
139						},
140						"pipeline_secret_template": "pipeline-secret-template",
141						"team_secret_template": "team-secret-template"
142          }
143        }`))
144					})
145				})
146
147				Context("get ssm manager info", func() {
148
149					BeforeEach(func() {
150						mockService.stubGetParameter = func(input *awsssm.GetParameterInput) (*awsssm.GetParameterOutput, error) {
151							return nil, awserr.New(awsssm.ErrCodeParameterNotFound, "dontcare", nil)
152						}
153					})
154
155					It("includes the ssm health info in json response", func() {
156						Expect(body).To(MatchJSON(`{
157          "ssm": {
158						"aws_region": "blah",
159						"health": {
160							"response": {
161								"status": "UP"
162							},
163							"method": "GetParameter"
164						},
165						"pipeline_secret_template": "pipeline-secret-template",
166						"team_secret_template": "team-secret-template"
167          }
168        }`))
169					})
170				})
171			})
172		})
173
174		Context("vault", func() {
175			BeforeEach(func() {
176				fakeAccess.IsAuthenticatedReturns(true)
177				fakeAccess.IsAdminReturns(true)
178
179				authConfig := vault.AuthConfig{
180					Backend:       "backend-server",
181					BackendMaxTTL: 20,
182					RetryMax:      5,
183					RetryInitial:  2,
184				}
185
186				tls := vault.TLSConfig{
187					CACert:     "",
188					ServerName: "server-name",
189				}
190
191				credServer = ghttp.NewServer()
192				vaultManager := &vault.VaultManager{
193					URL:             credServer.URL(),
194					Namespace:       "testnamespace",
195					PathPrefix:      "testpath",
196					LookupTemplates: []string{"/{{.Team}}/{{.Pipeline}}/{{.Secret}}", "/{{.Team}}/{{.Secret}}"},
197					TLS:             tls,
198					Auth:            authConfig,
199				}
200
201				err := vaultManager.Init(lager.NewLogger("test"))
202				Expect(err).ToNot(HaveOccurred())
203
204				credsManagers["vault"] = vaultManager
205
206				credServer.RouteToHandler("GET", "/v1/sys/health", ghttp.RespondWithJSONEncoded(
207					http.StatusOK,
208					&vaultapi.HealthResponse{
209						Initialized:                true,
210						Sealed:                     false,
211						Standby:                    false,
212						ReplicationPerformanceMode: "foo",
213						ReplicationDRMode:          "blah",
214						ServerTimeUTC:              0,
215						Version:                    "1.0.0",
216					},
217				))
218			})
219
220			Context("get vault health info returns error", func() {
221				BeforeEach(func() {
222					credServer.RouteToHandler("GET", "/v1/sys/health", ghttp.RespondWithJSONEncoded(
223						http.StatusInternalServerError,
224						"some error occurred",
225					))
226				})
227
228				It("returns configured creds manager with error", func() {
229					var errorBody struct {
230						Vault struct {
231							Health struct {
232								Error  string `json:"error"`
233								Method string `json:"method"`
234							} `json:"health"`
235						} `json:"vault"`
236					}
237
238					err := json.Unmarshal(body, &errorBody)
239					Expect(err).ToNot(HaveOccurred())
240
241					Expect(errorBody.Vault.Health.Error).To(ContainSubstring("some error occurred"))
242				})
243			})
244
245			Context("get vault health info", func() {
246				BeforeEach(func() {
247					credServer.RouteToHandler("GET", "/v1/sys/health", ghttp.RespondWithJSONEncoded(
248						http.StatusOK,
249						&vaultapi.HealthResponse{
250							Initialized:                true,
251							Sealed:                     false,
252							Standby:                    false,
253							ReplicationPerformanceMode: "foo",
254							ReplicationDRMode:          "blah",
255							ServerTimeUTC:              0,
256							Version:                    "1.0.0",
257						},
258					))
259				})
260
261				It("returns configured creds manager", func() {
262					Expect(body).To(MatchJSON(`{
263          "vault": {
264            "url": "` + credServer.URL() + `",
265            "path_prefix": "testpath",
266            "lookup_templates": ["/{{.Team}}/{{.Pipeline}}/{{.Secret}}", "/{{.Team}}/{{.Secret}}"],
267			"shared_path": "",
268			"namespace": "testnamespace",
269            "ca_cert": "",
270            "server_name": "server-name",
271						"auth_backend": "backend-server",
272						"auth_max_ttl": 20,
273						"auth_retry_max": 5,
274						"auth_retry_initial": 2,
275						"health": {
276							"response": {
277                  "initialized": true,
278                  "sealed": false,
279                  "standby": false,
280				  "performance_standby": false,
281                  "replication_performance_mode": "foo",
282                  "replication_dr_mode": "blah",
283                  "server_time_utc": 0,
284                  "version": "1.0.0"
285                },
286                "method": "/v1/sys/health"
287						}
288          }
289        }`))
290				})
291			})
292		})
293
294		Context("credhub", func() {
295			var (
296				tls credhub.TLS
297				uaa credhub.UAA
298			)
299
300			BeforeEach(func() {
301				fakeAccess.IsAuthenticatedReturns(true)
302				fakeAccess.IsAdminReturns(true)
303
304				tls = credhub.TLS{
305					CACerts: []string{},
306				}
307				uaa = credhub.UAA{
308					ClientId:     "client-id",
309					ClientSecret: "client-secret",
310				}
311			})
312
313			Context("get credhub help info succeeds", func() {
314				BeforeEach(func() {
315					credServer = ghttp.NewServer()
316					credServer.RouteToHandler("GET", "/health", ghttp.RespondWithJSONEncoded(
317						http.StatusOK, map[string]string{
318							"status": "UP",
319						},
320					))
321
322					credhubManager := &credhub.CredHubManager{
323						URL:        credServer.URL(),
324						PathPrefix: "some-prefix",
325						TLS:        tls,
326						UAA:        uaa,
327						Client:     &credhub.LazyCredhub{},
328					}
329
330					credsManagers["credhub"] = credhubManager
331				})
332
333				It("returns configured creds manager with response", func() {
334					Expect(body).To(MatchJSON(`{
335					"credhub": {
336						"url": "` + credServer.URL() + `",
337						"ca_certs": [],
338						"health": {
339							"response": {
340								"status": "UP"
341							},
342							"method": "/health"
343						},
344						"path_prefix": "some-prefix",
345						"uaa_client_id": "client-id"
346						}
347					}`))
348				})
349			})
350
351			Context("get credhub health info returns error", func() {
352				type responseSkeleton struct {
353					CredHub struct {
354						Url     string   `json:"url"`
355						CACerts []string `json:"ca_certs"`
356						Health  struct {
357							Error    string `json:"error"`
358							Response struct {
359								Status string `json:"status"`
360							} `json:"response"`
361							Method string `json:"method"`
362						} `json:"health"`
363						PathPrefix  string `json:"path_prefix"`
364						UAAClientId string `json:"uaa_client_id"`
365					} `json:"credhub"`
366				}
367
368				BeforeEach(func() {
369					credhubManager := &credhub.CredHubManager{
370						URL:        "http://wrong.inexistent.tld",
371						PathPrefix: "some-prefix",
372						TLS:        tls,
373						UAA:        uaa,
374						Client:     &credhub.LazyCredhub{},
375					}
376
377					credsManagers["credhub"] = credhubManager
378				})
379
380				It("returns configured creds manager with error", func() {
381					var parsedResponse responseSkeleton
382
383					err := json.Unmarshal(body, &parsedResponse)
384					Expect(err).ToNot(HaveOccurred())
385
386					Expect(parsedResponse.CredHub.Url).To(Equal("http://wrong.inexistent.tld"))
387					Expect(parsedResponse.CredHub.CACerts).To(BeEmpty())
388					Expect(parsedResponse.CredHub.PathPrefix).To(Equal("some-prefix"))
389					Expect(parsedResponse.CredHub.UAAClientId).To(Equal("client-id"))
390					Expect(parsedResponse.CredHub.Health.Response).ToNot(BeNil())
391					Expect(parsedResponse.CredHub.Health.Response.Status).To(BeEmpty())
392					Expect(parsedResponse.CredHub.Health.Method).To(Equal("/health"))
393					Expect(parsedResponse.CredHub.Health.Error).To(ContainSubstring("no such host"))
394				})
395			})
396		})
397
398		Context("SecretsManager", func() {
399			var mockService MockSecretsManagerService
400
401			BeforeEach(func() {
402				fakeAccess.IsAuthenticatedReturns(true)
403				fakeAccess.IsAdminReturns(true)
404
405				secretsManagerAccess := secretsmanager.NewSecretsManager(lager.NewLogger("ssm_test"), &mockService, nil)
406
407				secretsManager := &secretsmanager.Manager{
408					AwsAccessKeyID:         "",
409					AwsSecretAccessKey:     "",
410					AwsSessionToken:        "",
411					AwsRegion:              "blah",
412					PipelineSecretTemplate: "pipeline-secret-template",
413					TeamSecretTemplate:     "team-secret-template",
414					SecretManager:          secretsManagerAccess,
415				}
416
417				credsManagers["secretsmanager"] = secretsManager
418			})
419
420			Context("returns configured secretsmanager manager", func() {
421				Context("get secretsmanager info returns error", func() {
422
423					BeforeEach(func() {
424						mockService.stubGetSecretValue = func(input *awssecretsmanager.GetSecretValueInput) (*awssecretsmanager.GetSecretValueOutput, error) {
425							return nil, errors.New("some error occurred")
426						}
427					})
428
429					It("includes the error in json response", func() {
430						Expect(body).To(MatchJSON(`{
431					"secretsmanager": {
432						"aws_region": "blah",
433						"pipeline_secret_template": "pipeline-secret-template",
434						"team_secret_template": "team-secret-template",
435						"health": {
436							"error": "some error occurred",
437							"method": "GetSecretValue"
438						}
439					}
440				}`))
441					})
442
443				})
444
445				Context("get secretsmanager info", func() {
446
447					BeforeEach(func() {
448						mockService.stubGetSecretValue = func(input *awssecretsmanager.GetSecretValueInput) (*awssecretsmanager.GetSecretValueOutput, error) {
449
450							return nil, awserr.New(awssecretsmanager.ErrCodeResourceNotFoundException, "dontcare", nil)
451						}
452					})
453
454					It("include sthe secretsmanager info in json response", func() {
455						Expect(body).To(MatchJSON(`{
456					"secretsmanager": {
457						"aws_region": "blah",
458						"pipeline_secret_template": "pipeline-secret-template",
459						"team_secret_template": "team-secret-template",
460						"health": {
461							"response": {
462								"status": "UP"
463							},
464							"method": "GetSecretValue"
465						}
466					}
467				}`))
468					})
469				})
470			})
471
472		})
473	})
474})
475