diff --git a/cmd/hub/main.go b/cmd/hub/main.go index a9f8bc1..313d260 100644 --- a/cmd/hub/main.go +++ b/cmd/hub/main.go @@ -44,6 +44,7 @@ func main() { al := registrar.NewApprovalListener( pubsub, nil, + repo, registrar.OptionLog(log.New(os.Stdout, "approvalListener: ", log.LstdFlags|log.Lshortfile)), ) go al.Run(ctx) diff --git a/cmd/spoke-agent/main.go b/cmd/spoke-agent/main.go new file mode 100644 index 0000000..dd0a66e --- /dev/null +++ b/cmd/spoke-agent/main.go @@ -0,0 +1,107 @@ +package main + +import ( + "crypto/tls" + "crypto/x509" + "encoding/json" + "fmt" + "io" + "log" + "net/http" + "os" + "time" +) + +func main() { + logger := log.New(os.Stdout, "main: ", log.LstdFlags|log.Lshortfile) + + client, err := HubClient() + if err != nil { + logger.Fatal(err) + } + + registered := false + for !registered { + state := getCurrentState(client, logger) + switch state { + case "pending_approval": + logger.Println("waiting for approval") + case "registered": + registered = true + logger.Println("application registered!") + default: + logger.Println("retrying") + + } + time.Sleep(3 * time.Second) + } + +} + +func HubClient() (*http.Client, error) { + caFile, err := os.Open("./certs/ca.pem") + if err != nil { + return nil, fmt.Errorf("failed to open ca cert: %w", err) + } + caCert, err := io.ReadAll(caFile) + if err != nil { + return nil, fmt.Errorf("failed to read the ca cert: %w", err) + } + + pool := x509.NewCertPool() + pool.AppendCertsFromPEM(caCert) + + certPath := "./certs/app1.pem" + keyPath := "./certs/app1-key.pem" + + cert, err := tls.LoadX509KeyPair(certPath, keyPath) + if err != nil { + return nil, fmt.Errorf("failed to load keypair: %w", err) + } + + client := &http.Client{ + Transport: &http.Transport{ + TLSClientConfig: &tls.Config{ + RootCAs: pool, + Certificates: []tls.Certificate{cert}, + }, + }, + } + + return client, nil +} + +func getCurrentState(client *http.Client, logger *log.Logger) string { + resp, err := client.Post("https://example.net:3001/register", "application/json", nil) + if err != nil { + logger.Fatalf("registration failed: %v", err) + } + + if resp.StatusCode > 399 { + logger.Printf("ERROR: getCurrentState: failed to get status [%d]", resp.StatusCode) + return "" + } + + out := map[string]string{} + body, err := io.ReadAll(resp.Body) + if err != nil { + log.Printf("ERROR: getCurrentState: parsing response body: %v", err) + return "" + } + + err = json.Unmarshal(body, &out) + if err != nil { + log.Printf("ERROR: getCurrentState: failed to unmarshal response: %s '%s'", err, body) + return "" + } + + switch resp.StatusCode { + case http.StatusCreated: + logger.Printf("successfully started application registration") + return "pending_approval" + case http.StatusOK: + return out["status"] + default: + return "" + } +} diff --git a/pkg/registrar/approval.go b/pkg/registrar/approval.go index 9c79eb6..61ae6e9 100644 --- a/pkg/registrar/approval.go +++ b/pkg/registrar/approval.go @@ -26,10 +26,11 @@ func OptionLog(l logger) option { } } -func NewApprovalListener(ps svc.PubSub, requester svc.ApprovalRequester, options ...option) ApprovalListener { +func NewApprovalListener(ps svc.PubSub, requester svc.ApprovalRequester, repo svc.AppRepo, options ...option) ApprovalListener { out := ApprovalListener{ PubSub: ps, ApprovalRequester: requester, + AppRepo: repo, } for _, opt := range options { diff --git a/pkg/registrar/endpoints.go b/pkg/registrar/endpoints.go index 1ef3a05..0b11c5c 100644 --- a/pkg/registrar/endpoints.go +++ b/pkg/registrar/endpoints.go @@ -44,10 +44,9 @@ func (r Registrar) HandleRegistration(resp http.ResponseWriter, req *http.Reques cert := req.TLS.PeerCertificates[0] name := cert.DNSNames[0] - if r.repo.IsRegistered(name) { - resp.WriteHeader(http.StatusOK) - resp.Write([]byte(`{"status": "registered"}`)) - } else { + app, err := r.repo.GetApp(name) + switch err { + case svc.ErrApplicationNotFound: id := r.repo.StartAppRegistration(name) event := map[string]string{ "id": id.String(), @@ -59,6 +58,12 @@ func (r Registrar) HandleRegistration(resp http.ResponseWriter, req *http.Reques resp.WriteHeader(http.StatusCreated) fmt.Fprintf(resp, `{"status": "pending_approval"}`) + case nil: + resp.WriteHeader(http.StatusOK) + fmt.Fprintf(resp, `{"status": "%s"}`, app.State) + default: + resp.WriteHeader(http.StatusInternalServerError) + fmt.Fprintf(resp, `{"errors": ["%s"]}`, err) } } diff --git a/pkg/registrar/internal/persistence/fake_repo.go b/pkg/registrar/internal/persistence/fake_repo.go index 2aabf61..4d38208 100644 --- a/pkg/registrar/internal/persistence/fake_repo.go +++ b/pkg/registrar/internal/persistence/fake_repo.go @@ -23,6 +23,16 @@ func (repo FakeRepo) IsRegistered(name string) bool { return false } +func (repo FakeRepo) GetApp(name string) (svc.Application, error) { + for _, app := range repo.apps { + if name == app.Name { + return *app, nil + } + } + + return svc.Application{}, svc.ErrApplicationNotFound +} + func (repo FakeRepo) PendingApprovalCount() int { count := 0 for _, app := range repo.apps { @@ -34,26 +44,23 @@ func (repo FakeRepo) PendingApprovalCount() int { } func (repo *FakeRepo) StartAppRegistration(name string) uuid.UUID { - repo.apps = append(repo.apps, &svc.Application{ + newApp := &svc.Application{ + ID: uuid.New(), Name: name, State: "pending_approval", - }) - return uuid.New() + } + repo.apps = append(repo.apps, newApp) + return newApp.ID } func (repo *FakeRepo) ApproveApp(id uuid.UUID) (svc.Application, error) { - var app *svc.Application - for _, application := range repo.apps { if application.ID == id { application.State = "registered" + return *application, nil } } - if app == nil { - return svc.Application{}, svc.ErrApplicationNotFound - } - - return *app, nil + return svc.Application{}, svc.ErrApplicationNotFound } diff --git a/pkg/registrar/internal/services/repo.go b/pkg/registrar/internal/services/repo.go index 150c7cf..3f70ef1 100644 --- a/pkg/registrar/internal/services/repo.go +++ b/pkg/registrar/internal/services/repo.go @@ -21,6 +21,7 @@ type Application struct { type AppRepo interface { IsRegistered(name string) bool PendingApprovalCount() int + GetApp(name string) (Application, error) StartAppRegistration(name string) uuid.UUID ApproveApp(id uuid.UUID) (Application, error) }