From 31f6cc0a0d9dbc4f91a9c37c0ab0ecb29c6ef8bc Mon Sep 17 00:00:00 2001 From: Adam Mohammed Date: Tue, 4 Jul 2023 22:05:24 -0400 Subject: [PATCH] Add some conifguration and logging for the hub --- appconfig/endpoints.go | 14 ++ appconfig/log.go | 33 +++++ appconfig/provider.go | 31 +++++ cmd/hub/main.go | 79 ++++++++---- cmd/hub/middlewares.go | 46 +++++++ pkg/discord/client.go | 39 ------ pkg/registrar/repo.go | 10 -- {pkg/pubsub => pubsub}/pubsub.go | 0 {pkg/registrar => registrar}/approval.go | 7 +- {pkg/registrar => registrar}/endpoints.go | 10 +- .../registrar => registrar}/endpoints_test.go | 120 ++++++++---------- .../internal/persistence/fake_repo.go | 2 +- .../internal/services/app_registration.go | 0 .../internal/services/approvals.go | 0 .../internal/services/pubsub.go | 0 .../internal/services/repo.go | 0 {pkg/registrar => registrar}/logging.go | 0 registrar/repo.go | 10 ++ 18 files changed, 250 insertions(+), 151 deletions(-) create mode 100644 appconfig/endpoints.go create mode 100644 appconfig/log.go create mode 100644 appconfig/provider.go create mode 100644 cmd/hub/middlewares.go delete mode 100644 pkg/discord/client.go delete mode 100644 pkg/registrar/repo.go rename {pkg/pubsub => pubsub}/pubsub.go (100%) rename {pkg/registrar => registrar}/approval.go (90%) rename {pkg/registrar => registrar}/endpoints.go (92%) rename {pkg/registrar => registrar}/endpoints_test.go (76%) rename {pkg/registrar => registrar}/internal/persistence/fake_repo.go (94%) rename {pkg/registrar => registrar}/internal/services/app_registration.go (100%) rename {pkg/registrar => registrar}/internal/services/approvals.go (100%) rename {pkg/registrar => registrar}/internal/services/pubsub.go (100%) rename {pkg/registrar => registrar}/internal/services/repo.go (100%) rename {pkg/registrar => registrar}/logging.go (100%) create mode 100644 registrar/repo.go diff --git a/appconfig/endpoints.go b/appconfig/endpoints.go new file mode 100644 index 0000000..ec46d90 --- /dev/null +++ b/appconfig/endpoints.go @@ -0,0 +1,14 @@ +package appconfig + +import ( + "fmt" + "net/http" +) + +func handleAuthzConfig(w http.ResponseWriter, req *http.Request) { + headers := w.Header() + headers.Set("content-type", "text/plain") + + w.WriteHeader(http.StatusOK) + fmt.Fprintf(w, "hello, world") +} diff --git a/appconfig/log.go b/appconfig/log.go new file mode 100644 index 0000000..1306573 --- /dev/null +++ b/appconfig/log.go @@ -0,0 +1,33 @@ +package appconfig + +import "fmt" + +type logger interface { + Output(int, string) error +} + +type ilogger interface { + logger + Print(...interface{}) + Printf(string, ...interface{}) + Println(...interface{}) +} + +type internalLog struct { + logger +} + +// Println replicates the behaviour of the standard logger. +func (t internalLog) Println(v ...interface{}) { + t.Output(2, fmt.Sprintln(v...)) +} + +// Printf replicates the behaviour of the standard logger. +func (t internalLog) Printf(format string, v ...interface{}) { + t.Output(2, fmt.Sprintf(format, v...)) +} + +// Print replicates the behaviour of the standard logger. +func (t internalLog) Print(v ...interface{}) { + t.Output(2, fmt.Sprint(v...)) +} diff --git a/appconfig/provider.go b/appconfig/provider.go new file mode 100644 index 0000000..301a799 --- /dev/null +++ b/appconfig/provider.go @@ -0,0 +1,31 @@ +package appconfig + +import ( + "log" + "net/http" + "os" +) + +type Provider struct { + handler http.Handler + log ilogger +} + +func NewProvider() Provider { + return Provider{ + handler: router(), + log: log.New(os.Stderr, "servicedemon/appconfig - ", log.LstdFlags|log.Lshortfile), + } +} + +func (p Provider) Handler() http.Handler { + return p.handler +} + +func router() *http.ServeMux { + mux := http.NewServeMux() + + mux.HandleFunc("/config/authz", handleAuthzConfig) + + return mux +} diff --git a/cmd/hub/main.go b/cmd/hub/main.go index 313d260..f8dfe69 100644 --- a/cmd/hub/main.go +++ b/cmd/hub/main.go @@ -4,28 +4,17 @@ import ( "context" "crypto/tls" "crypto/x509" + "fmt" "io" "log" "net/http" "os" - "go.fixergrid.net/servicedemon/pkg/pubsub" - "go.fixergrid.net/servicedemon/pkg/registrar" + "go.fixergrid.net/servicedemon/appconfig" + "go.fixergrid.net/servicedemon/pubsub" + "go.fixergrid.net/servicedemon/registrar" ) -type noopHandler struct { - http.HandlerFunc -} - -func wrapHandlefunc(h http.HandlerFunc) noopHandler { - return noopHandler{ - HandlerFunc: h, - } -} - -func (h noopHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) { - h.HandlerFunc(w, req) -} func main() { ctx, cancel := context.WithCancel(context.Background()) defer cancel() @@ -39,41 +28,79 @@ func main() { r := registrar.NewRegistrar( pubsub, repo, - registrar.WithLogger(log.New(os.Stdout, "registrar: ", log.LstdFlags|log.Lshortfile)), ) + al := registrar.NewApprovalListener( pubsub, nil, repo, - registrar.OptionLog(log.New(os.Stdout, "approvalListener: ", log.LstdFlags|log.Lshortfile)), ) go al.Run(ctx) + appConfig := appconfig.NewProvider() + mux := http.NewServeMux() + logger.Println("Registering endpoints...") + mux.HandleFunc("/register", postjson(r.HandleRegistration)) + logger.Println("POST /register") - mux.HandleFunc("/register", r.HandleRegistration) - mux.Handle("/approvals/", http.StripPrefix("/approvals/", wrapHandlefunc(r.HandleApproval))) + mux.Handle("/approvals/", http.StripPrefix("/approvals/", wrapHandleFunc(postjson(r.HandleApproval)))) + logger.Println("POST /approvals/:id") - certFile, err := os.Open("./certs/ca.pem") + mux.Handle("/application/", http.StripPrefix("/application", appConfig.Handler())) + logger.Println("GET /application/config/authz") + + server, err := newServer() if err != nil { - logger.Fatalf("failed to open ca.pem: %v", err) + logger.Fatal(err) + } + + server.Handler = mux + log.Println(server.ListenAndServeTLS("", "")) +} + +func newServer() (*http.Server, error) { + // "./certs/combined.pem", "./certs/server-key.pem" + requiredVars := map[string]string{ + "HUB_CA_CERT_FILE": "", + "HUB_SERVER_CERT_FILE": "", + "HUB_SERVER_KEY_FILE": "", + } + + for k, _ := range requiredVars { + val, isSet := os.LookupEnv(k) + if !isSet { + return nil, fmt.Errorf("hub: required environment variable is unset: %s", k) + } + requiredVars[k] = val + } + + certFile, err := os.Open(requiredVars["HUB_CA_CERT_FILE"]) + if err != nil { + return nil, fmt.Errorf("hub: failed to open ca.pem: %w", err) } caCert, err := io.ReadAll(certFile) if err != nil { - logger.Fatalf("failed to read in ca: %v", err) + return nil, fmt.Errorf("hub: failed to read in ca: %w", err) } pool := x509.NewCertPool() pool.AppendCertsFromPEM(caCert) + serverCert, err := tls.LoadX509KeyPair(requiredVars["HUB_SERVER_CERT_FILE"], requiredVars["HUB_SERVER_KEY_FILE"]) + if err != nil { + return nil, fmt.Errorf("hub: failed to load server certs: %w", err) + } + server := &http.Server{ Addr: ":3001", TLSConfig: &tls.Config{ - ClientCAs: pool, - ClientAuth: tls.RequireAndVerifyClientCert, + ClientCAs: pool, + ClientAuth: tls.RequireAndVerifyClientCert, + Certificates: []tls.Certificate{serverCert}, }, } - server.Handler = mux - log.Println(server.ListenAndServeTLS("./certs/combined.pem", "./certs/server-key.pem")) + + return server, nil } diff --git a/cmd/hub/middlewares.go b/cmd/hub/middlewares.go new file mode 100644 index 0000000..4406892 --- /dev/null +++ b/cmd/hub/middlewares.go @@ -0,0 +1,46 @@ +package main + +import ( + "fmt" + "net/http" +) + +func postjson(f http.HandlerFunc) http.HandlerFunc { + return contentJSON(methodPost(f)) +} + +func methodPost(f http.HandlerFunc) http.HandlerFunc { + return func(w http.ResponseWriter, req *http.Request) { + if req.Method != http.MethodPost { + w.WriteHeader(http.StatusMethodNotAllowed) + fmt.Fprintf(w, `{"errors": ["method not allowed"]}`) + return + } + + f(w, req) + } + +} + +func contentJSON(f http.HandlerFunc) http.HandlerFunc { + return func(w http.ResponseWriter, req *http.Request) { + headers := w.Header() + headers.Set("content-type", "application/json") + + f(w, req) + } +} + +type noopHandler struct { + http.HandlerFunc +} + +func wrapHandleFunc(h http.HandlerFunc) noopHandler { + return noopHandler{ + HandlerFunc: h, + } +} + +func (h noopHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) { + h.HandlerFunc(w, req) +} diff --git a/pkg/discord/client.go b/pkg/discord/client.go deleted file mode 100644 index 4182fbd..0000000 --- a/pkg/discord/client.go +++ /dev/null @@ -1,39 +0,0 @@ -package discord - -import ( - "context" - "io" - "net/http" - - "golang.org/x/oauth2" -) - -type ChannelInfo struct{} - -type DiscordClient struct { - client *http.Client - defaultChannel string - baseUrl string - apiVersion string -} - -func NewClient(ctx context.Context, tokenSrc oauth2.TokenSource) DiscordClient { - return DiscordClient{ - client: oauth2.NewClient(ctx, tokenSrc), - baseUrl: "https://discord.com", - apiVersion: "v10", - } -} - -func (dc DiscordClient) WithDefaultChannel(channelID string) DiscordClient { - dc.defaultChannel = channelID - return dc -} - -func (dc DiscordClient) GetChannel() ChannelInfo { - return ChannelInfo{} -} - -func (dc DiscordClient) doRequest(path string, method string, body io.Reader) (*http.Response, error) { - return dc.client.Do(method, path, body) -} diff --git a/pkg/registrar/repo.go b/pkg/registrar/repo.go deleted file mode 100644 index ab22772..0000000 --- a/pkg/registrar/repo.go +++ /dev/null @@ -1,10 +0,0 @@ -package registrar - -import ( - persist "go.fixergrid.net/servicedemon/pkg/registrar/internal/persistence" - svc "go.fixergrid.net/servicedemon/pkg/registrar/internal/services" -) - -func NewRepo() svc.AppRepo { - return persist.NewFakeRepo() -} diff --git a/pkg/pubsub/pubsub.go b/pubsub/pubsub.go similarity index 100% rename from pkg/pubsub/pubsub.go rename to pubsub/pubsub.go diff --git a/pkg/registrar/approval.go b/registrar/approval.go similarity index 90% rename from pkg/registrar/approval.go rename to registrar/approval.go index 61ae6e9..c591724 100644 --- a/pkg/registrar/approval.go +++ b/registrar/approval.go @@ -4,9 +4,11 @@ import ( "context" "encoding/json" "fmt" + "log" + "os" "github.com/google/uuid" - svc "go.fixergrid.net/servicedemon/pkg/registrar/internal/services" + svc "go.fixergrid.net/servicedemon/registrar/internal/services" ) const APPROVAL_TOPIC = "net.fixergrid.events.app.approved" @@ -15,7 +17,7 @@ type ApprovalListener struct { svc.ApprovalRequester svc.PubSub svc.AppRepo - log internalLog + log ilogger } type option func(r *ApprovalListener) @@ -31,6 +33,7 @@ func NewApprovalListener(ps svc.PubSub, requester svc.ApprovalRequester, repo sv PubSub: ps, ApprovalRequester: requester, AppRepo: repo, + log: log.New(os.Stderr, "servicedemon/registrar - ", log.LstdFlags|log.Lshortfile), } for _, opt := range options { diff --git a/pkg/registrar/endpoints.go b/registrar/endpoints.go similarity index 92% rename from pkg/registrar/endpoints.go rename to registrar/endpoints.go index 0b11c5c..27b1283 100644 --- a/pkg/registrar/endpoints.go +++ b/registrar/endpoints.go @@ -3,10 +3,12 @@ package registrar import ( "encoding/json" "fmt" + "log" "net/http" + "os" "github.com/google/uuid" - svc "go.fixergrid.net/servicedemon/pkg/registrar/internal/services" + svc "go.fixergrid.net/servicedemon/registrar/internal/services" ) const ( @@ -18,7 +20,7 @@ var validApprover string = "Nautilus Admins" type Registrar struct { svc.Publisher repo svc.AppRepo - log internalLog + log ilogger } func WithLogger(l logger) RegistrarOpt { @@ -33,6 +35,7 @@ func NewRegistrar(publisher svc.Publisher, repo svc.AppRepo, options ...Registra r := Registrar{ Publisher: publisher, repo: repo, + log: log.New(os.Stderr, "servicedemon/registrar - ", log.LstdFlags|log.Lshortfile), } for _, opt := range options { opt(&r) @@ -68,9 +71,6 @@ func (r Registrar) HandleRegistration(resp http.ResponseWriter, req *http.Reques } func (r Registrar) HandleApproval(resp http.ResponseWriter, req *http.Request) { - headers := resp.Header() - headers.Set("content-type", "application/json") - in := req.URL.Path cert := req.TLS.PeerCertificates[0] OUs := cert.Subject.OrganizationalUnit diff --git a/pkg/registrar/endpoints_test.go b/registrar/endpoints_test.go similarity index 76% rename from pkg/registrar/endpoints_test.go rename to registrar/endpoints_test.go index 0b4b00e..374b9cb 100644 --- a/pkg/registrar/endpoints_test.go +++ b/registrar/endpoints_test.go @@ -10,71 +10,11 @@ import ( "testing" "time" - "github.com/google/uuid" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "go.fixergrid.net/servicedemon/pkg/pubsub" + "go.fixergrid.net/servicedemon/pubsub" ) -type TestRequest http.Request - -func NewTestRequest(t *testing.T, method string, url string, body io.Reader) TestRequest { - req, err := http.NewRequest(method, url, body) - require.NoError(t, err) - return TestRequest(*req) -} -func (tr TestRequest) WithFakeTLSState(dnsName string) TestRequest { - cert := x509.Certificate{ - DNSNames: []string{ - dnsName, - }, - } - - tr.TLS = &tls.ConnectionState{ - PeerCertificates: []*x509.Certificate{&cert}, - } - - return tr -} - -func (tr TestRequest) Create() *http.Request { - req := http.Request(tr) - return &req -} - -type AppState struct { - Name string - State string -} -type TestAppRepo []AppState - -func (repo TestAppRepo) IsRegistered(name string) bool { - for _, app := range repo { - if name == app.Name && app.State == "registered" { - return true - } - } - return false -} - -func (repo TestAppRepo) PendingApprovalCount() int { - count := 0 - for _, app := range repo { - if app.State == "pending_approval" { - count += 1 - } - } - return count -} - -func (repo *TestAppRepo) StartAppRegistration(name string) uuid.UUID { - *repo = append(*repo, AppState{ - Name: name, - State: "pending_approval", - }) - return uuid.New() -} - func TestHandleRegisterPendingApproval(t *testing.T) { resp := httptest.NewRecorder() @@ -82,7 +22,7 @@ func TestHandleRegisterPendingApproval(t *testing.T) { WithFakeTLSState("dev-app-1.delivery.engineering"). Create() - repo := &TestAppRepo{} + repo := NewRepo() pubsub := pubsub.New() subscriber := pubsub.Subscribe(REGISTRATION_TOPIC) @@ -112,12 +52,9 @@ func TestHandleRegisterAlreadyRegistered(t *testing.T) { Create() pubsub := pubsub.New() - repo := &TestAppRepo{ - AppState{ - Name: "dev-app-1.delivery.engineering", - State: "registered", - }, - } + repo := NewRepo() + appID := repo.StartAppRegistration("dev-app-1.delivery.engineering") + repo.ApproveApp(appID) NewRegistrar(pubsub, repo).HandleRegistration(resp, req) @@ -129,6 +66,27 @@ func TestHandleRegisterAlreadyRegistered(t *testing.T) { assert.Equal(t, []byte(`{"status": "registered"}`), body) } +func TestHandleRegisterNotYetApproved(t *testing.T) { + resp := httptest.NewRecorder() + + req := NewTestRequest(t, http.MethodPost, "http://example.com/v1/register", nil). + WithFakeTLSState("dev-app-1.delivery.engineering"). + Create() + + pubsub := pubsub.New() + repo := NewRepo() + repo.StartAppRegistration("dev-app-1.delivery.engineering") + + NewRegistrar(pubsub, repo).HandleRegistration(resp, req) + + assert.Equal(t, http.StatusOK, resp.Code) + + body, err := io.ReadAll(resp.Body) + require.NoError(t, err) + + assert.Equal(t, []byte(`{"status": "pending_approval"}`), body) +} + func assertMessage(t *testing.T, expected map[string]string, sender <-chan string) { timer := time.After(2 * time.Second) @@ -153,3 +111,29 @@ func compareMessage(t *testing.T, expected map[string]string, observed string) { assert.Equal(t, v, observedEvent[k]) } } + +type TestRequest http.Request + +func NewTestRequest(t *testing.T, method string, url string, body io.Reader) TestRequest { + req, err := http.NewRequest(method, url, body) + require.NoError(t, err) + return TestRequest(*req) +} +func (tr TestRequest) WithFakeTLSState(dnsName string) TestRequest { + cert := x509.Certificate{ + DNSNames: []string{ + dnsName, + }, + } + + tr.TLS = &tls.ConnectionState{ + PeerCertificates: []*x509.Certificate{&cert}, + } + + return tr +} + +func (tr TestRequest) Create() *http.Request { + req := http.Request(tr) + return &req +} diff --git a/pkg/registrar/internal/persistence/fake_repo.go b/registrar/internal/persistence/fake_repo.go similarity index 94% rename from pkg/registrar/internal/persistence/fake_repo.go rename to registrar/internal/persistence/fake_repo.go index 4d38208..6c12f82 100644 --- a/pkg/registrar/internal/persistence/fake_repo.go +++ b/registrar/internal/persistence/fake_repo.go @@ -2,7 +2,7 @@ package persistence import ( "github.com/google/uuid" - svc "go.fixergrid.net/servicedemon/pkg/registrar/internal/services" + svc "go.fixergrid.net/servicedemon/registrar/internal/services" ) type FakeRepo struct { diff --git a/pkg/registrar/internal/services/app_registration.go b/registrar/internal/services/app_registration.go similarity index 100% rename from pkg/registrar/internal/services/app_registration.go rename to registrar/internal/services/app_registration.go diff --git a/pkg/registrar/internal/services/approvals.go b/registrar/internal/services/approvals.go similarity index 100% rename from pkg/registrar/internal/services/approvals.go rename to registrar/internal/services/approvals.go diff --git a/pkg/registrar/internal/services/pubsub.go b/registrar/internal/services/pubsub.go similarity index 100% rename from pkg/registrar/internal/services/pubsub.go rename to registrar/internal/services/pubsub.go diff --git a/pkg/registrar/internal/services/repo.go b/registrar/internal/services/repo.go similarity index 100% rename from pkg/registrar/internal/services/repo.go rename to registrar/internal/services/repo.go diff --git a/pkg/registrar/logging.go b/registrar/logging.go similarity index 100% rename from pkg/registrar/logging.go rename to registrar/logging.go diff --git a/registrar/repo.go b/registrar/repo.go new file mode 100644 index 0000000..67990c9 --- /dev/null +++ b/registrar/repo.go @@ -0,0 +1,10 @@ +package registrar + +import ( + persist "go.fixergrid.net/servicedemon/registrar/internal/persistence" + svc "go.fixergrid.net/servicedemon/registrar/internal/services" +) + +func NewRepo() svc.AppRepo { + return persist.NewFakeRepo() +}