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