From 1f586b4e2ea7763407115faac68d0952687a58d7 Mon Sep 17 00:00:00 2001 From: Alan Moran Date: Fri, 27 Dec 2024 16:22:58 -0300 Subject: [PATCH] Updated VSCode settings and added HTTP configuration for scheduler MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit • Changed VSCode Java build configuration setting from automatic to interactive • Added new CFHTTPConfiguration class to configure additional Tomcat connector for HTTP on port 8090 • Set server port to 8083 and added HTTP port configuration in application.yml --- jobs/scalingengine/spec | 4 +- jobs/scheduler/templates/scheduler.yml.erb | 3 +- operations/use-cf-services.yml | 29 ++++- src/autoscaler/api/cmd/api/api_test.go | 102 +++++++++++------- .../api/publicapiserver/public_api_server.go | 11 +- src/autoscaler/build-extension-file.sh | 3 + src/autoscaler/helpers/auth/xfcc_auth.go | 68 ++++++++---- .../cmd/scalingengine/scalingengine_test.go | 88 +-------------- .../scalingengine/server/server_test.go | 3 +- src/autoscaler/testhelpers/certs.go | 61 ++++++++--- src/scheduler/.vscode/settings.json | 2 +- .../scheduler/conf/CFHTTPConfiguration.java | 21 ++++ .../src/main/resources/application.yml | 4 +- 13 files changed, 224 insertions(+), 175 deletions(-) create mode 100644 src/scheduler/src/main/java/org/cloudfoundry/autoscaler/scheduler/conf/CFHTTPConfiguration.java diff --git a/jobs/scalingengine/spec b/jobs/scalingengine/spec index f92c0bd40f..08eddd5219 100644 --- a/jobs/scalingengine/spec +++ b/jobs/scalingengine/spec @@ -174,11 +174,11 @@ properties: default: 8080 autoscaler.cf_server.xfcc.valid_org_guid: - description: approve org guid for xfcc endpoint + description: allowed org guid for xfcc endpoint default: '' autoscaler.cf_server.xfcc.valid_space_guid: - description: approve space guid for xfcc endpoint + description: allowed space guid for xfcc endpoint default: '' autoscaler.scalingengine.health.port: diff --git a/jobs/scheduler/templates/scheduler.yml.erb b/jobs/scheduler/templates/scheduler.yml.erb index f4ed0a27da..a225a4fb78 100644 --- a/jobs/scheduler/templates/scheduler.yml.erb +++ b/jobs/scheduler/templates/scheduler.yml.erb @@ -164,6 +164,8 @@ scheduler: ############################################################ server: port: <%=p('autoscaler.scheduler.port') %> + http: + port: <%=p('autoscaler.cf_server.port') %> ssl: ciphers: TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 enabled-protocols: TLSv1.3 @@ -171,7 +173,6 @@ server: client-auth: NEED cf-server: - port: <%=p('autoscaler.cf_server.port') %> validOrgGuid: <%= p("autoscaler.cf_server.xfcc.valid_org_guid") %> validSpaceGuid: <%= p("autoscaler.cf_server.xfcc.valid_space_guid") %> diff --git a/operations/use-cf-services.yml b/operations/use-cf-services.yml index a68fbf48ed..4f38f12413 100644 --- a/operations/use-cf-services.yml +++ b/operations/use-cf-services.yml @@ -20,8 +20,8 @@ client_secret: ((routing_api_client_secret)) client_private_key: ((!routing_api_tls_client_private_key)) server_ca_cert: ((!routing_api_server_ca_cert)) - api_url: https://api.((system_domain)):443 - oauth_url: https://uaa.((system_domain)):443 + api_url: "https://api.((system_domain)):443" + oauth_url: "https://uaa.((system_domain)):443" routes: - name: ((deployment_name))_postgres registration_interval: 5s @@ -77,3 +77,28 @@ uris: - ((deployment_name))-cf-scalingengine.((system_domain)) +## EVENTGENERATOR - Enable cf Server to receive calls from api running on cf -- + +- type: replace + path: /instance_groups/name=eventgenerator/jobs/name=eventgenerator/properties/autoscaler/eventgenerator/cf_server?/xfcc?/valid_org_guid? + value: ((autoscaler_cf_server_xfcc_valid_org_guid)) + +- type: replace + path: /instance_groups/name=eventgenerator/jobs/name=eventgenerator/properties/autoscaler/eventgenerator/cf_server?/xfcc?/valid_space_guid? + value: ((autoscaler_cf_server_xfcc_valid_space_guid)) + + +- type: replace + path: /instance_groups/name=eventgenerator/jobs/name=eventgenerator/properties/autoscaler/eventgenerator/cf_server?/port? + value: &EventGeneratorCfPort 6205 + +- type: replace + path: /instance_groups/name=postgres/jobs/name=route_registrar/properties/route_registrar/routes/- + value: + name: ((deployment_name))-cf-eventgenerator + registration_interval: 20s + port: *EventGeneratorCfPort + tags: + component: autoscaler_cf_eventgenerator + uris: + - ((deployment_name))-cf-eventgenerator.((system_domain)) diff --git a/src/autoscaler/api/cmd/api/api_test.go b/src/autoscaler/api/cmd/api/api_test.go index 67e13684db..c8c349a740 100644 --- a/src/autoscaler/api/cmd/api/api_test.go +++ b/src/autoscaler/api/cmd/api/api_test.go @@ -1,6 +1,8 @@ package main_test import ( + "crypto/rand" + "crypto/rsa" "fmt" "io" "net/http" @@ -9,9 +11,9 @@ import ( "strings" "code.cloudfoundry.org/app-autoscaler/src/autoscaler/api/config" + "code.cloudfoundry.org/app-autoscaler/src/autoscaler/configutil" "code.cloudfoundry.org/app-autoscaler/src/autoscaler/db" - - . "code.cloudfoundry.org/app-autoscaler/src/autoscaler/testhelpers" + "code.cloudfoundry.org/app-autoscaler/src/autoscaler/testhelpers" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" @@ -44,9 +46,9 @@ var _ = Describe("Api", func() { vcapPort = 8080 + GinkgoParallelProcess() - brokerHttpClient = NewServiceBrokerClient() + brokerHttpClient = testhelpers.NewServiceBrokerClient() healthHttpClient = &http.Client{} - apiHttpClient = NewPublicApiClient() + apiHttpClient = testhelpers.NewPublicApiClient() cfServerHttpClient = &http.Client{} serverURL, err = url.Parse(fmt.Sprintf("https://127.0.0.1:%d", cfg.Server.Port)) @@ -166,7 +168,7 @@ var _ = Describe("Api", func() { bodyBytes, err := io.ReadAll(rsp.Body) - FailOnError("Read failed", err) + testhelpers.FailOnError("Read failed", err) if len(bodyBytes) == 0 { Fail("body empty") } @@ -297,50 +299,72 @@ var _ = Describe("Api", func() { }) When("running CF server", func() { - XWhen("running in outside cf", func() {}) - When("running in CF", func() { + var ( + cfInstanceKeyFile string + cfInstanceCertFile string + ) - BeforeEach(func() { - os.Setenv("VCAP_APPLICATION", "{}") - os.Setenv("VCAP_SERVICES", getVcapServices()) - os.Setenv("PORT", fmt.Sprintf("%d", vcapPort)) - runner.Start() - }) - AfterEach(func() { - runner.Interrupt() - Eventually(runner.Session, 5).Should(Exit(0)) - os.Unsetenv("VCAP_APPLICATION") - os.Unsetenv("VCAP_SERVICES") - os.Unsetenv("PORT") - }) + BeforeEach(func() { + rsaPrivateKey, err := rsa.GenerateKey(rand.Reader, 2048) + Expect(err).NotTo(HaveOccurred()) - It("should start a cf server", func() { - req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("%s/v1/info", cfServerURL), nil) - Expect(err).NotTo(HaveOccurred()) + cfInstanceCert, err := testhelpers.GenerateClientCertWithPrivateKey("org-guid", "space-guid", rsaPrivateKey) + Expect(err).NotTo(HaveOccurred()) - rsp, err = cfServerHttpClient.Do(req) - Expect(err).ToNot(HaveOccurred()) + certTmpDir := os.TempDir() - bodyBytes, err := io.ReadAll(rsp.Body) - Expect(err).ToNot(HaveOccurred()) - Expect(bodyBytes).To(ContainSubstring("Automatically increase or decrease the number of application instances based on a policy you define.")) + cfInstanceCertFile, err := configutil.MaterializeContentInFile(certTmpDir, "eventgenerator.crt", string(cfInstanceCert)) + Expect(err).NotTo(HaveOccurred()) + os.Setenv("CF_INSTANCE_CERT", string(cfInstanceCertFile)) - req, err = http.NewRequest(http.MethodGet, fmt.Sprintf("%s/v2/catalog", cfServerURL), nil) - Expect(err).NotTo(HaveOccurred()) - req.SetBasicAuth(username, password) + cfInstanceKey := testhelpers.GenerateClientKeyWithPrivateKey(rsaPrivateKey) + cfInstanceKeyFile, err = configutil.MaterializeContentInFile(certTmpDir, "eventgenerator.key", string(cfInstanceKey)) + Expect(err).NotTo(HaveOccurred()) + os.Setenv("CF_INSTANCE_KEY", string(cfInstanceKeyFile)) - rsp, err = cfServerHttpClient.Do(req) - Expect(err).ToNot(HaveOccurred()) - Expect(rsp.StatusCode).To(Equal(http.StatusOK)) + os.Setenv("VCAP_APPLICATION", "{}") + os.Setenv("VCAP_SERVICES", getVcapServices()) + os.Setenv("PORT", fmt.Sprintf("%d", vcapPort)) + runner.Start() + }) + AfterEach(func() { + runner.Interrupt() + Eventually(runner.Session, 5).Should(Exit(0)) - bodyBytes, err = io.ReadAll(rsp.Body) - Expect(err).ToNot(HaveOccurred()) - Expect(bodyBytes).To(ContainSubstring("autoscaler-free-plan-id")) - }) + os.Remove(cfInstanceKeyFile) + os.Remove(cfInstanceCertFile) + os.Unsetenv("CF_INSTANCE_KEY") + os.Unsetenv("CF_INSTANCE_CERT") + os.Unsetenv("VCAP_APPLICATION") + os.Unsetenv("VCAP_SERVICES") + os.Unsetenv("PORT") }) - }) + It("should start a cf server", func() { + req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("%s/v1/info", cfServerURL), nil) + Expect(err).NotTo(HaveOccurred()) + + rsp, err = cfServerHttpClient.Do(req) + Expect(err).ToNot(HaveOccurred()) + + bodyBytes, err := io.ReadAll(rsp.Body) + Expect(err).ToNot(HaveOccurred()) + Expect(bodyBytes).To(ContainSubstring("Automatically increase or decrease the number of application instances based on a policy you define.")) + + req, err = http.NewRequest(http.MethodGet, fmt.Sprintf("%s/v2/catalog", cfServerURL), nil) + Expect(err).NotTo(HaveOccurred()) + req.SetBasicAuth(username, password) + + rsp, err = cfServerHttpClient.Do(req) + Expect(err).ToNot(HaveOccurred()) + Expect(rsp.StatusCode).To(Equal(http.StatusOK)) + + bodyBytes, err = io.ReadAll(rsp.Body) + Expect(err).ToNot(HaveOccurred()) + Expect(bodyBytes).To(ContainSubstring("autoscaler-free-plan-id")) + }) + }) }) func getVcapServices() (result string) { diff --git a/src/autoscaler/api/publicapiserver/public_api_server.go b/src/autoscaler/api/publicapiserver/public_api_server.go index 5bb3765b6c..71f268ec3b 100644 --- a/src/autoscaler/api/publicapiserver/public_api_server.go +++ b/src/autoscaler/api/publicapiserver/public_api_server.go @@ -75,7 +75,7 @@ func (s *PublicApiServer) CreateHealthServer() (ifrit.Runner, error) { return nil, err } - return helpers.NewHTTPServer(s.logger, s.conf.Health.ServerConfig, s.healthRouter) + return helpers.NewHTTPServer(s.logger.Session("HealthServer"), s.conf.Health.ServerConfig, s.healthRouter) } func (s *PublicApiServer) setupBrokerRouter() error { @@ -102,14 +102,9 @@ func (s *PublicApiServer) CreateCFServer() (ifrit.Runner, error) { return nil, err } - mainRouter := mux.NewRouter() r := s.autoscalerRouter.GetRouter() - mainRouter.PathPrefix("/v2").Handler(r) - mainRouter.PathPrefix("/v1").Handler(r) - mainRouter.PathPrefix("/health").Handler(r) - mainRouter.PathPrefix("/").Handler(s.healthRouter) - return helpers.NewHTTPServer(s.logger, s.conf.VCAPServer, mainRouter) + return helpers.NewHTTPServer(s.logger.Session("CfServer"), s.conf.VCAPServer, r) } func (s *PublicApiServer) CreateMtlsServer() (ifrit.Runner, error) { @@ -117,7 +112,7 @@ func (s *PublicApiServer) CreateMtlsServer() (ifrit.Runner, error) { return nil, err } - return helpers.NewHTTPServer(s.logger, s.conf.Server, s.autoscalerRouter.GetRouter()) + return helpers.NewHTTPServer(s.logger.Session("MtlsServer"), s.conf.Server, s.autoscalerRouter.GetRouter()) } func (s *PublicApiServer) setupApiProtectedRoutes(pah *PublicApiHandler, scalingHistoryHandler http.Handler) { diff --git a/src/autoscaler/build-extension-file.sh b/src/autoscaler/build-extension-file.sh index 96a16bd082..a6979851ab 100755 --- a/src/autoscaler/build-extension-file.sh +++ b/src/autoscaler/build-extension-file.sh @@ -21,6 +21,7 @@ export POSTGRES_EXTERNAL_PORT="${PR_NUMBER:-5432}" export METRICSFORWARDER_HOST="${METRICSFORWARDER_HOST:-"${DEPLOYMENT_NAME}-metricsforwarder"}" export METRICSFORWARDER_MTLS_HOST="${METRICSFORWARDER_MTLS_HOST:-"${DEPLOYMENT_NAME}-metricsforwarder-mtls"}" export SCALINGENGINE_HOST="${SCALINGENGINE_HOST:-"${DEPLOYMENT_NAME}-cf-scalingengine"}" +export EVENTGENERATOR_HOST="${EVENTGENERATOR_HOST:-"${DEPLOYMENT_NAME}-cf-eventgenerator"}" export PUBLICAPISERVER_HOST="${PUBLICAPISERVER_HOST:-"${DEPLOYMENT_NAME}"}" export SERVICEBROKER_HOST="${SERVICEBROKER_HOST:-"${DEPLOYMENT_NAME}servicebroker"}" @@ -116,4 +117,6 @@ resources: metrics_forwarder_mtls_url: ${METRICSFORWARDER_MTLS_HOST}.\${default-domain} scaling_engine: scaling_engine_url: ${SCALINGENGINE_HOST}.\${default-domain} + event_generator: + event_generator_url: ${EVENTGENERATOR_HOST}.\${default-domain} EOF diff --git a/src/autoscaler/helpers/auth/xfcc_auth.go b/src/autoscaler/helpers/auth/xfcc_auth.go index 6eaaa7735a..f40055794e 100644 --- a/src/autoscaler/helpers/auth/xfcc_auth.go +++ b/src/autoscaler/helpers/auth/xfcc_auth.go @@ -1,8 +1,10 @@ package auth import ( + "crypto/sha256" "crypto/x509" "encoding/base64" + "encoding/pem" "errors" "fmt" "net/http" @@ -21,10 +23,31 @@ type XFCCAuthMiddleware interface { XFCCAuthenticationMiddleware(next http.Handler) http.Handler } +type Cert struct { + FullChainPem string + Sha256 [32]byte + Base64 string +} + +func NewCert(fullChainPem string) *Cert { + block, _ := pem.Decode([]byte(fullChainPem)) + if block == nil { + return nil + } + return &Cert{ + FullChainPem: fullChainPem, + Sha256: sha256.Sum256(block.Bytes), + Base64: base64.StdEncoding.EncodeToString(block.Bytes), + } +} + +func (c *Cert) GetXFCCHeader() string { + return fmt.Sprintf("Hash=%x;Cert=%s", c.Sha256, c.Base64) +} + type xfccAuthMiddleware struct { - logger lager.Logger - spaceGuid string - orgGuid string + logger lager.Logger + xfccAuth *models.XFCCAuth } func (m *xfccAuthMiddleware) checkAuth(r *http.Request) error { @@ -33,7 +56,13 @@ func (m *xfccAuthMiddleware) checkAuth(r *http.Request) error { return ErrXFCCHeaderNotFound } - data, err := base64.StdEncoding.DecodeString(removeQuotes(xfccHeader)) + attrs := make(map[string]string) + for _, v := range strings.Split(xfccHeader, ";") { + attr := strings.SplitN(v, "=", 2) + attrs[attr[0]] = attr[1] + } + + data, err := base64.StdEncoding.DecodeString(attrs["Cert"]) if err != nil { return fmt.Errorf("base64 parsing failed: %w", err) } @@ -43,11 +72,11 @@ func (m *xfccAuthMiddleware) checkAuth(r *http.Request) error { return fmt.Errorf("failed to parse certificate: %w", err) } - if getSpaceGuid(cert) != m.spaceGuid { + if m.getSpaceGuid(cert) != m.xfccAuth.ValidSpaceGuid { return ErrorWrongSpace } - if getOrgGuid(cert) != m.orgGuid { + if m.getOrgGuid(cert) != m.xfccAuth.ValidOrgGuid { return ErrorWrongOrg } @@ -70,17 +99,16 @@ func (m *xfccAuthMiddleware) XFCCAuthenticationMiddleware(next http.Handler) htt func NewXfccAuthMiddleware(logger lager.Logger, xfccAuth models.XFCCAuth) XFCCAuthMiddleware { return &xfccAuthMiddleware{ - logger: logger, - orgGuid: xfccAuth.ValidOrgGuid, - spaceGuid: xfccAuth.ValidSpaceGuid, + logger: logger, + xfccAuth: &xfccAuth, } } -func getSpaceGuid(cert *x509.Certificate) string { +func (m *xfccAuthMiddleware) getSpaceGuid(cert *x509.Certificate) string { var certSpaceGuid string for _, ou := range cert.Subject.OrganizationalUnit { if strings.Contains(ou, "space:") { - kv := mapFrom(ou) + kv := m.mapFrom(ou) certSpaceGuid = kv["space"] break } @@ -88,34 +116,28 @@ func getSpaceGuid(cert *x509.Certificate) string { return certSpaceGuid } -func mapFrom(input string) map[string]string { +func (m *xfccAuthMiddleware) mapFrom(input string) map[string]string { result := make(map[string]string) - r := regexp.MustCompile(`(\w+):(\w+-\w+)`) + r := regexp.MustCompile(`(\w+):((\w+-)*\w+)`) matches := r.FindAllStringSubmatch(input, -1) for _, match := range matches { result[match[1]] = match[2] } + + m.logger.Debug("parseCertOrganizationalUnit", lager.Data{"input": input, "result": result}) return result } -func getOrgGuid(cert *x509.Certificate) string { +func (m *xfccAuthMiddleware) getOrgGuid(cert *x509.Certificate) string { var certOrgGuid string for _, ou := range cert.Subject.OrganizationalUnit { - // capture from string k:v with regex if strings.Contains(ou, "org:") { - kv := mapFrom(ou) + kv := m.mapFrom(ou) certOrgGuid = kv["org"] break } } return certOrgGuid } - -func removeQuotes(xfccHeader string) string { - if xfccHeader[0] == '"' { - xfccHeader = xfccHeader[1 : len(xfccHeader)-1] - } - return xfccHeader -} diff --git a/src/autoscaler/scalingengine/cmd/scalingengine/scalingengine_test.go b/src/autoscaler/scalingengine/cmd/scalingengine/scalingengine_test.go index ef08d8b933..b160d8e98f 100644 --- a/src/autoscaler/scalingengine/cmd/scalingengine/scalingengine_test.go +++ b/src/autoscaler/scalingengine/cmd/scalingengine/scalingengine_test.go @@ -3,7 +3,6 @@ package main_test import ( "io" "strconv" - "time" "code.cloudfoundry.org/app-autoscaler/src/autoscaler/cf" "code.cloudfoundry.org/app-autoscaler/src/autoscaler/models" @@ -16,9 +15,7 @@ import ( "github.com/onsi/gomega/gbytes" "bytes" - "encoding/base64" "encoding/json" - "encoding/pem" "fmt" "net/http" "net/url" @@ -56,54 +53,7 @@ var _ = Describe("Main", func() { runner.KillWithFire() }) - Describe("with a correct config", func() { - When("starting 1 scaling engine instance", func() { - It("scaling engine should start", func() { - Eventually(runner.Session.Buffer, 2*time.Second).Should(gbytes.Say(runner.startCheck)) - Consistently(runner.Session).ShouldNot(Exit()) - }) - - It("http server starts directly", func() { - Eventually(runner.Session.Buffer, 2*time.Second).Should(gbytes.Say("scalingengine.http-server.new-http-server")) - }) - }) - - When("starting multiple scaling engine instances", func() { - var ( - secondRunner *ScalingEngineRunner - ) - - JustBeforeEach(func() { - secondRunner = NewScalingEngineRunner() - secondConf := conf - - secondConf.Server.Port += 500 - secondConf.Health.ServerConfig.Port += 500 - secondConf.CFServer.Port += 500 - secondRunner.configPath = writeConfig(&secondConf).Name() - secondRunner.Start() - }) - - AfterEach(func() { - secondRunner.KillWithFire() - }) - - It("2 http server instances start", func() { - Eventually(runner.Session.Buffer, 2*time.Second).Should(gbytes.Say("scalingengine.http-server.new-http-server")) - Eventually(secondRunner.Session.Buffer, 2*time.Second).Should(gbytes.Say("scalingengine.http-server.new-http-server")) - Eventually(runner.Session.Buffer, 2*time.Second).Should(gbytes.Say("scalingengine.started")) - Eventually(secondRunner.Session.Buffer, 2*time.Second).Should(gbytes.Say("scalingengine.started")) - - Consistently(runner.Session).ShouldNot(Exit()) - Consistently(secondRunner.Session).ShouldNot(Exit()) - }) - - }) - - }) - Describe("With incorrect config", func() { - Context("with a missing config file", func() { BeforeEach(func() { runner.startCheck = "" @@ -164,11 +114,6 @@ var _ = Describe("Main", func() { }) Describe("when http server is ready to serve RESTful API", func() { - - JustBeforeEach(func() { - Eventually(runner.Session.Buffer, 2).Should(gbytes.Say("scalingengine.started")) - }) - When("a request to trigger scaling comes", func() { It("returns with a 200", func() { body, err := json.Marshal(models.Trigger{Adjustment: "+1"}) @@ -228,10 +173,6 @@ var _ = Describe("Main", func() { runner.configPath = writeConfig(&basicAuthConfig).Name() }) - JustBeforeEach(func() { - Eventually(runner.Session.Buffer, 2).Should(gbytes.Say("scalingengine.started")) - }) - When("a request to query health comes", func() { It("returns with a 200", func() { rsp, err := httpClient.Get(healthURL.String()) @@ -256,10 +197,6 @@ var _ = Describe("Main", func() { healthURL.Path = "/health" }) - JustBeforeEach(func() { - Eventually(runner.Session.Buffer, 2).Should(gbytes.Say("scalingengine.started")) - }) - When("username and password are incorrect for basic authentication during health check", func() { It("should return 401", func() { req, err := http.NewRequest(http.MethodGet, healthURL.String(), nil) @@ -293,10 +230,6 @@ var _ = Describe("Main", func() { healthURL.Path = "/health" }) - JustBeforeEach(func() { - Eventually(runner.Session.Buffer, 2).Should(gbytes.Say("scalingengine.started")) - }) - When("username and password are incorrect for basic authentication during health check", func() { It("should return 401", func() { @@ -325,34 +258,23 @@ var _ = Describe("Main", func() { }) }) }) - When("running CF server", func() { - JustBeforeEach(func() { - Eventually(runner.Session.Buffer, 2).Should(gbytes.Say("scalingengine.started")) - }) - When("running outside cf", func() { - It("/v1/liveness should return 200", func() { + When("running CF server", func() { + Describe("GET /v1/liveness", func() { + It("should return 200", func() { cfServerURL.Path = "/v1/liveness" req, err := http.NewRequest(http.MethodGet, cfServerURL.String(), nil) Expect(err).NotTo(HaveOccurred()) - setXFCCCertHeader(req, conf.CFServer.XFCC.ValidOrgGuid, conf.CFServer.XFCC.ValidSpaceGuid) + err = SetXFCCCertHeader(req, conf.CFServer.XFCC.ValidOrgGuid, conf.CFServer.XFCC.ValidSpaceGuid) + Expect(err).NotTo(HaveOccurred()) rsp, err := healthHttpClient.Do(req) Expect(err).ToNot(HaveOccurred()) Expect(rsp.StatusCode).To(Equal(http.StatusOK)) - }) }) }) }) - -func setXFCCCertHeader(req *http.Request, orgGuid, spaceGuid string) { - xfccClientCert, err := GenerateClientCert(orgGuid, spaceGuid) - block, _ := pem.Decode(xfccClientCert) - Expect(err).NotTo(HaveOccurred()) - Expect(block).ShouldNot(BeNil()) - req.Header.Add("X-Forwarded-Client-Cert", base64.StdEncoding.EncodeToString(block.Bytes)) -} diff --git a/src/autoscaler/scalingengine/server/server_test.go b/src/autoscaler/scalingengine/server/server_test.go index 691aa174da..28a8cc8fb7 100644 --- a/src/autoscaler/scalingengine/server/server_test.go +++ b/src/autoscaler/scalingengine/server/server_test.go @@ -1,7 +1,6 @@ package server_test import ( - "fmt" "strconv" "strings" @@ -71,7 +70,6 @@ var _ = Describe("Server", func() { }) JustBeforeEach(func() { - fmt.Println("serverUrl: ", serverUrl.String()) req, err = http.NewRequest(method, serverUrl.String(), bodyReader) Expect(err).NotTo(HaveOccurred()) rsp, err = http.DefaultClient.Do(req) @@ -279,6 +277,7 @@ var _ = Describe("Server", func() { httpServer, err := server.CreateCFServer(xfccAuthMiddleware) Expect(err).NotTo(HaveOccurred()) serverProcess = ginkgomon_v2.Invoke(httpServer) + serverUrl, err = url.Parse("http://127.0.0.1:" + strconv.Itoa(conf.CFServer.Port)) Expect(err).ToNot(HaveOccurred()) }) diff --git a/src/autoscaler/testhelpers/certs.go b/src/autoscaler/testhelpers/certs.go index ef61c5d375..f8aeaec0bf 100644 --- a/src/autoscaler/testhelpers/certs.go +++ b/src/autoscaler/testhelpers/certs.go @@ -8,39 +8,74 @@ import ( "encoding/pem" "fmt" "math/big" + "net/http" + "time" + + "code.cloudfoundry.org/app-autoscaler/src/autoscaler/helpers/auth" ) -// generateClientCert generates a client certificate with the specified spaceGUID and orgGUID -// included in the organizational unit string. -func GenerateClientCert(orgGUID, spaceGUID string) ([]byte, error) { - // Generate a random serial number for the certificate - // +func GenerateClientCertWithPrivateKeyExpiring(orgGUID, spaceGUID string, privateKey *rsa.PrivateKey, notAfter time.Time) ([]byte, error) { serialNumber, err := rand.Int(rand.Reader, new(big.Int).Lsh(big.NewInt(1), 128)) if err != nil { return nil, err } - privateKey, err := rsa.GenerateKey(rand.Reader, 2048) - if err != nil { - return nil, err - } - - // Create a new X.509 certificate template template := x509.Certificate{ SerialNumber: serialNumber, + NotBefore: time.Now(), + NotAfter: notAfter, Subject: pkix.Name{ Organization: []string{"My Organization"}, OrganizationalUnit: []string{fmt.Sprintf("space:%s org:%s", spaceGUID, orgGUID)}, }, } - // Generate the certificate + + if privateKey == nil { + return nil, fmt.Errorf("private key is nil") + } + certDER, err := x509.CreateCertificate(rand.Reader, &template, &template, &privateKey.PublicKey, privateKey) if err != nil { return nil, err } - // Encode the certificate to PEM format certPEM := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: certDER}) return certPEM, nil } + +func GenerateClientCertWithPrivateKey(orgGUID, spaceGUID string, privateKey *rsa.PrivateKey) ([]byte, error) { + notAfter := time.Now().AddDate(1, 0, 0) + return GenerateClientCertWithPrivateKeyExpiring(orgGUID, spaceGUID, privateKey, notAfter) +} + +func GenerateClientCert(orgGUID, spaceGUID string) ([]byte, error) { + privateKey, err := rsa.GenerateKey(rand.Reader, 2048) + if err != nil { + return nil, err + } + + return GenerateClientCertWithPrivateKey(orgGUID, spaceGUID, privateKey) +} + +func GenerateClientKeyWithPrivateKey(privateKey *rsa.PrivateKey) []byte { + privateKeyBytes := x509.MarshalPKCS1PrivateKey(privateKey) + pemBlock := &pem.Block{ + Type: "RSA PRIVATE KEY", + Bytes: privateKeyBytes, + } + + return pem.EncodeToMemory(pemBlock) +} + +func SetXFCCCertHeader(req *http.Request, orgGuid, spaceGuid string) error { + xfccClientCert, err := GenerateClientCert(orgGuid, spaceGuid) + if err != nil { + return err + } + + cert := auth.NewCert(string(xfccClientCert)) + + req.Header.Add("X-Forwarded-Client-Cert", cert.GetXFCCHeader()) + return nil +} diff --git a/src/scheduler/.vscode/settings.json b/src/scheduler/.vscode/settings.json index 04cd618865..050505ce79 100644 --- a/src/scheduler/.vscode/settings.json +++ b/src/scheduler/.vscode/settings.json @@ -1,3 +1,3 @@ { - "java.configuration.updateBuildConfiguration": "automatic" + "java.configuration.updateBuildConfiguration": "interactive" } diff --git a/src/scheduler/src/main/java/org/cloudfoundry/autoscaler/scheduler/conf/CFHTTPConfiguration.java b/src/scheduler/src/main/java/org/cloudfoundry/autoscaler/scheduler/conf/CFHTTPConfiguration.java new file mode 100644 index 0000000000..db6bff3142 --- /dev/null +++ b/src/scheduler/src/main/java/org/cloudfoundry/autoscaler/scheduler/conf/CFHTTPConfiguration.java @@ -0,0 +1,21 @@ +package org.cloudfoundry.autoscaler.scheduler.conf; + +import org.apache.catalina.connector.Connector; +import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory; +import org.springframework.boot.web.server.WebServerFactoryCustomizer; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class CFHTTPConfiguration { + + @Bean + public WebServerFactoryCustomizer httpConnectorCustomizer() { + return factory -> { + Connector connector = new Connector(TomcatServletWebServerFactory.DEFAULT_PROTOCOL); + connector.setPort(8090); + connector.setSecure(false); // Set to false for HTTP + factory.addAdditionalTomcatConnectors(connector); + }; + } +} diff --git a/src/scheduler/src/main/resources/application.yml b/src/scheduler/src/main/resources/application.yml index 49e8b1f2a9..77edef09e4 100644 --- a/src/scheduler/src/main/resources/application.yml +++ b/src/scheduler/src/main/resources/application.yml @@ -124,6 +124,9 @@ scheduler: ############################################################ server: + port: 8083 + http: + port: 8090 ssl: ciphers: TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 enabled-protocols: TLSv1.3 @@ -136,6 +139,5 @@ server: ############################################################ cf-server: - port: 8082 validOrgGuid: "some-org-guid" validSpaceGuid: "some-space-guid"