AppManager: Support exposing cross-cluster ports

Change-Id: I4bdb3573209935f6777656ec2f3481e79d84a9c9
diff --git a/core/installer/server/appmanager/server.go b/core/installer/server/appmanager/server.go
index b3a0883..3360ff4 100644
--- a/core/installer/server/appmanager/server.go
+++ b/core/installer/server/appmanager/server.go
@@ -41,6 +41,7 @@
 type Server struct {
 	l            sync.Locker
 	port         int
+	ssClient     soft.Client
 	repo         soft.RepoIO
 	m            *installer.AppManager
 	r            installer.AppRepository
@@ -98,6 +99,7 @@
 
 func NewServer(
 	port int,
+	ssClient soft.Client,
 	repo soft.RepoIO,
 	m *installer.AppManager,
 	r installer.AppRepository,
@@ -114,6 +116,7 @@
 	return &Server{
 		l:            &sync.Mutex{},
 		port:         port,
+		ssClient:     ssClient,
 		repo:         repo,
 		m:            m,
 		r:            r,
@@ -187,7 +190,7 @@
 		return
 	}
 	appDir := filepath.Join("/dodo-app", req.Id)
-	namespace := "dodo-app-test" // TODO(gio)
+	namespace := "dodo-app-testttt" // TODO(gio)
 	if _, err := s.m.Install(app, req.Id, appDir, namespace, map[string]any{
 		"managerAddr":   "", // TODO(gio)
 		"appId":         req.Id,
@@ -238,7 +241,7 @@
 		http.Error(w, err.Error(), http.StatusBadRequest)
 		return
 	}
-	if err := s.cnc.AddProxy(req.From, req.To); err != nil {
+	if err := s.cnc.AddIngressProxy(req.From, req.To); err != nil {
 		http.Error(w, err.Error(), http.StatusInternalServerError)
 		return
 	}
@@ -250,7 +253,7 @@
 		http.Error(w, err.Error(), http.StatusBadRequest)
 		return
 	}
-	if err := s.cnc.RemoveProxy(req.From, req.To); err != nil {
+	if err := s.cnc.RemoveIngressProxy(req.From, req.To); err != nil {
 		http.Error(w, err.Error(), http.StatusInternalServerError)
 		return
 	}
@@ -891,6 +894,17 @@
 			if err != nil {
 				return installer.ReleaseResources{}, err
 			}
+			keys, err := installer.NewSSHKeyPair("port-allocator")
+			if err != nil {
+				return installer.ReleaseResources{}, err
+			}
+			user := fmt.Sprintf("%s-cluster-%s-port-allocator", env.Id, name)
+			if err := s.ssClient.AddUser(user, keys.AuthorizedKey()); err != nil {
+				return installer.ReleaseResources{}, err
+			}
+			if err := s.ssClient.AddReadWriteCollaborator("config", user); err != nil {
+				return installer.ReleaseResources{}, err
+			}
 			instanceId := fmt.Sprintf("%s-%s", app.Slug(), name)
 			appDir := fmt.Sprintf("/clusters/%s/ingress", name)
 			namespace := fmt.Sprintf("%scluster-%s-network", env.NamespacePrefix, name)
@@ -903,6 +917,7 @@
 				// TODO(gio): remove hardcoded user
 				"vpnUser":          vpnUser,
 				"vpnProxyHostname": hostname,
+				"sshPrivateKey":    string(keys.RawPrivateKey()),
 			})
 			if err != nil {
 				return installer.ReleaseResources{}, err