DodoApp: Restrict users to one domain
Change-Id: I4d09d5ee61d0ec712fd9dfa848c0af0c8e550d68
diff --git a/charts/dodo-app/templates/install.yaml b/charts/dodo-app/templates/install.yaml
index f94202b..d49a6d1 100644
--- a/charts/dodo-app/templates/install.yaml
+++ b/charts/dodo-app/templates/install.yaml
@@ -121,13 +121,14 @@
- --port={{ .Values.port }}
- --api-port={{ .Values.apiPort }}
- --self={{ .Values.self }}
+ - --repo-public-addr={{ .Values.repoPublicAddr }}
- --namespace={{ .Values.namespace }} # TODO(gio): maybe use .Release.Namespace ?
- --env-app-manager-addr={{ .Values.envAppManagerAddr }}
- --env-config=/pcloud/env-config/config.json
- - --app-admin-key={{ .Values.appAdminKey }}
- --git-repo-public-key={{ .Values.gitRepoPublicKey }}
- --db=/dodo-app/db/apps.db
- --networks={{ .Values.allowedNetworks }}
+ - --external={{ .Values.external }}
volumeMounts:
- name: ssh-key
readOnly: true
diff --git a/charts/dodo-app/values.yaml b/charts/dodo-app/values.yaml
index abb976a..2fba879 100644
--- a/charts/dodo-app/values.yaml
+++ b/charts/dodo-app/values.yaml
@@ -8,10 +8,11 @@
repoAddr: 192.168.0.11
sshPrivateKey: key
self: ""
+repoPublicAddr: ""
namespace: ""
envAppManagerAddr: ""
envConfig: ""
-appAdminKey: ""
gitRepoPublicKey: ""
persistentVolumeClaimName: ""
allowedNetworks: ""
+external: false
diff --git a/core/installer/app_configs/app_global_env.cue b/core/installer/app_configs/app_global_env.cue
index 47af9a0..a9edbde 100644
--- a/core/installer/app_configs/app_global_env.cue
+++ b/core/installer/app_configs/app_global_env.cue
@@ -65,6 +65,7 @@
if auth.enabled {
"auth-proxy": {
chart: charts.authProxy
+ info: "Installing authentication proxy"
values: {
image: {
repository: images.authProxy.fullName
diff --git a/core/installer/cmd/dodo_app.go b/core/installer/cmd/dodo_app.go
index ea5e7fd..5fc6e3e 100644
--- a/core/installer/cmd/dodo_app.go
+++ b/core/installer/cmd/dodo_app.go
@@ -17,15 +17,16 @@
)
var dodoAppFlags struct {
+ external bool
port int
apiPort int
sshKey string
repoAddr string
self string
+ repoPublicAddr string
namespace string
envAppManagerAddr string
envConfig string
- appAdminKey string
gitRepoPublicKey string
db string
networks []string
@@ -36,6 +37,12 @@
Use: "dodo-app",
RunE: dodoAppCmdRun,
}
+ cmd.Flags().BoolVar(
+ &dodoAppFlags.external,
+ "external",
+ false,
+ "",
+ )
cmd.Flags().IntVar(
&dodoAppFlags.port,
"port",
@@ -73,6 +80,12 @@
"",
)
cmd.Flags().StringVar(
+ &dodoAppFlags.repoPublicAddr,
+ "repo-public-addr",
+ "",
+ "",
+ )
+ cmd.Flags().StringVar(
&dodoAppFlags.namespace,
"namespace",
"",
@@ -91,12 +104,6 @@
"",
)
cmd.Flags().StringVar(
- &dodoAppFlags.appAdminKey,
- "app-admin-key",
- "",
- "",
- )
- cmd.Flags().StringVar(
&dodoAppFlags.gitRepoPublicKey,
"git-repo-public-key",
"",
@@ -157,17 +164,34 @@
if err != nil {
return err
}
+ var nf welcome.NetworkFilter
+ if len(dodoAppFlags.networks) == 0 {
+ nf = welcome.NewNoNetworkFilter()
+ } else {
+ nf = welcome.NewAllowListFilter(dodoAppFlags.networks)
+ }
+ if dodoAppFlags.external {
+ nf = welcome.NewCombinedFilter(welcome.NewNetworkFilterByOwner(st), nf)
+ }
+ var ug welcome.UserGetter
+ if dodoAppFlags.external {
+ ug = welcome.NewExternalUserGetter()
+ } else {
+ ug = welcome.NewInternalUserGetter()
+ }
s, err := welcome.NewDodoAppServer(
st,
+ nf,
+ ug,
dodoAppFlags.port,
dodoAppFlags.apiPort,
dodoAppFlags.self,
+ dodoAppFlags.repoPublicAddr,
string(sshKey),
dodoAppFlags.gitRepoPublicKey,
softClient,
dodoAppFlags.namespace,
dodoAppFlags.envAppManagerAddr,
- dodoAppFlags.networks,
nsc,
jc,
env,
@@ -175,10 +199,5 @@
if err != nil {
return err
}
- if dodoAppFlags.appAdminKey != "" {
- if _, err := s.CreateApp("app", dodoAppFlags.appAdminKey, "Private"); err != nil {
- return err
- }
- }
return s.Start()
}
diff --git a/core/installer/soft/client.go b/core/installer/soft/client.go
index 4a5021e..a5cfa31 100644
--- a/core/installer/soft/client.go
+++ b/core/installer/soft/client.go
@@ -96,7 +96,7 @@
}
func (ss *realClient) UserExists(name string) (bool, error) {
- log.Printf("Adding user %s", name)
+ log.Printf("Checking user exists %s", name)
out, err := ss.RunCommand("user", "list")
if err != nil {
return false, err
diff --git a/core/installer/values-tmpl/dodo-app.cue b/core/installer/values-tmpl/dodo-app.cue
index ac688c9..ee96044 100644
--- a/core/installer/values-tmpl/dodo-app.cue
+++ b/core/installer/values-tmpl/dodo-app.cue
@@ -8,8 +8,8 @@
network: #Network @name(Network)
subdomain: string @name(Subdomain)
sshPort: int @name(SSH Port) @role(port)
- adminKey: string | *"" @name(Admin SSH Public Key)
allowedNetworks: string | *"" @name(Allowed Networks)
+ external: bool | *false @name(External)
// TODO(gio): auto generate
ssKeys: #SSHKey
@@ -23,6 +23,7 @@
description: "Deploy app by pushing to Git repository"
icon: "<svg xmlns='http://www.w3.org/2000/svg' width='50' height='50' viewBox='0 0 48 48'><path fill='none' stroke='currentColor' stroke-linecap='round' stroke-linejoin='round' d='M2.837 27.257c3.363 2.45 11.566 3.523 12.546 1.4s.424-10.94.424-10.94s-1.763 1.192-2.302.147s.44-2.433 2.319-2.858c-1.96.05-2.221-.571-2.205-.93s.67-1.878 3.527-1.241c-1.6-.751-1.943-2.956 2.352-1.568c-1.421-.735-.36-2.825 1.649-.62c-.261-1.323 1.584-1.46 2.694.907M10.648 34.633a19 19 0 0 0-4.246.719'/><path fill='none' stroke='currentColor' stroke-linecap='round' stroke-linejoin='round' d='M15.144 43.402c3.625-2.482 7.685-6.32 7.293-13.406s-1.6-6.368-.523-7.577s6.924-.99 10.712 3.353c.032-2.874-2.504-5.508-2.504-5.508a33 33 0 0 1 5.53.163c2.852.49 2.394 2.514 3.58 2.035s.971-3.472-.39-5.377c-1.666-2.33-3.223-2.83-6.358-2.188s-4.474.458-5.54-.587s-2.026-3.538-4.605-2.515c-2.935 1.164-4.398 2.438-3.767 5.04s2.34 4.558 2.972 6.844'/><path fill='none' stroke='currentColor' stroke-linecap='round' stroke-linejoin='round' d='M22.001 16.552c-.925-.043-1.894.055-1.709 1.328'/><path fill='none' stroke='currentColor' stroke-linecap='round' stroke-linejoin='round' d='M20.662 16.763c1.72 2.695 3.405 3.643 9.46 3.501'/><path fill='none' stroke='currentColor' stroke-linecap='round' stroke-linejoin='round' d='M32.14 14.966c-1.223.879-2.18 3.781-2.496 5.307M23.1 14.908c.48 1.209 1.23.728 1.315.283a1.552 1.552 0 0 0-1.543-1.883m-.408 17.472c5.328 2.71 11.631.229 16.269-2.123c-1.176 4.572-5.911 5.585-8.916 6.107'/><path fill='none' stroke='currentColor' stroke-linecap='round' stroke-linejoin='round' d='M29.099 37.115c4.376-.294 8.024-1.578 7.833-5.296'/><path fill='none' stroke='currentColor' stroke-linecap='round' stroke-linejoin='round' d='M20.27 38.702c6.771 3.834 12.505.798 13.786-2.615'/><circle cx='24' cy='24' r='21.5' fill='none' stroke='currentColor' stroke-linecap='round' stroke-linejoin='round'/></svg>"
_domain: "\(input.subdomain).\(input.network.domain)"
+url: "https://\(_domain)"
images: {
softserve: {
@@ -58,7 +59,14 @@
ingress: {
"dodo-app": {
- auth: enabled: false
+ auth: {
+ if input.external {
+ enabled: false
+ }
+ if !input.external {
+ enabled: true
+ }
+ }
network: input.network
subdomain: input.subdomain
service: {
@@ -113,13 +121,14 @@
repoAddr: "soft-serve.\(release.namespace).svc.cluster.local:22"
sshPrivateKey: base64.Encode(null, input.dAppKeys.private)
self: "api.\(release.namespace).svc.cluster.local"
+ repoPublicAddr: "ssh://\(_domain):\(input.sshPort)"
namespace: release.namespace
envAppManagerAddr: "http://appmanager.\(global.namespacePrefix)appmanager.svc.cluster.local"
envConfig: base64.Encode(null, json.Marshal(global))
- appAdminKey: input.adminKey
gitRepoPublicKey: input.ssKeys.public
persistentVolumeClaimName: volumes.db.name
allowedNetworks: input.allowedNetworks
+ external: input.external
}
}
}
@@ -173,10 +182,3 @@
}
}
}
-
-help: [{
- title: "How to use"
- "contents": """
- Clone: git clone ssh://\(_domain):\(input.sshPort)/app <div onClick='copyToClipboard(this, "git clone ssh://\(_domain):\(input.sshPort)/app")' style='display: inline-block; cursor: pointer;'> <svg width='26px' height='26px' viewBox='-0 -0 28.80 28.80' fill='#7f9f7f' xmlns='http://www.w3.org/2000/svg' style='outline: none;'> <g id='SVGRepo_bgCarrier' stroke-width='0'></g> <g id='SVGRepo_tracerCarrier' stroke-linecap='round' stroke-linejoin='round'></g> <g id='SVGRepo_iconCarrier'> <path fill-rule='evenodd' clip-rule='evenodd' d='M19.5 16.5L19.5 4.5L18.75 3.75H9L8.25 4.5L8.25 7.5L5.25 7.5L4.5 8.25V20.25L5.25 21H15L15.75 20.25V17.25H18.75L19.5 16.5ZM15.75 15.75L15.75 8.25L15 7.5L9.75 7.5V5.25L18 5.25V15.75H15.75ZM6 9L14.25 9L14.25 19.5L6 19.5L6 9Z' fill='#7f9f7f'></path> </g> </svg> </div> Server public key: \(input.ssKeys.public)
- """
-}]
diff --git a/core/installer/welcome/dodo-app-tmpl/index.html b/core/installer/welcome/dodo-app-tmpl/index.html
index e9a9b11..af82173 100644
--- a/core/installer/welcome/dodo-app-tmpl/index.html
+++ b/core/installer/welcome/dodo-app-tmpl/index.html
@@ -5,6 +5,15 @@
<meta charset='utf-8'>
</head>
<body>
+ <form action="" method="POST">
+ <select name="network">
+ {{ range .Networks }}
+ <option value="{{ .Name }}">{{ .Name }} - {{ .Domain }}</option>
+ {{ end }}
+ </select>
+ <input type="text" name="admin-public-key" placeholder="Admin Public Key" />
+ <button type="submit" name="create-app">Create App</button>
+ </form>
{{ range .Apps }}
<a href="/{{ . }}">{{ . }}</a>
{{ end }}
diff --git a/core/installer/welcome/dodo_app.go b/core/installer/welcome/dodo_app.go
index 1c91cbc..1315266 100644
--- a/core/installer/welcome/dodo_app.go
+++ b/core/installer/welcome/dodo_app.go
@@ -49,15 +49,17 @@
type DodoAppServer struct {
l sync.Locker
st Store
+ nf NetworkFilter
+ ug UserGetter
port int
apiPort int
self string
+ repoPublicAddr string
sshKey string
gitRepoPublicKey string
client soft.Client
namespace string
envAppManagerAddr string
- networks []string
env installer.EnvConfig
nsc installer.NamespaceCreator
jc installer.JobCreator
@@ -70,15 +72,17 @@
// TODO(gio): Initialize appNs on startup
func NewDodoAppServer(
st Store,
+ nf NetworkFilter,
+ ug UserGetter,
port int,
apiPort int,
self string,
+ repoPublicAddr string,
sshKey string,
gitRepoPublicKey string,
client soft.Client,
namespace string,
envAppManagerAddr string,
- networks []string,
nsc installer.NamespaceCreator,
jc installer.JobCreator,
env installer.EnvConfig,
@@ -94,15 +98,17 @@
s := &DodoAppServer{
&sync.Mutex{},
st,
+ nf,
+ ug,
port,
apiPort,
self,
+ repoPublicAddr,
sshKey,
gitRepoPublicKey,
client,
namespace,
envAppManagerAddr,
- networks,
env,
nsc,
jc,
@@ -137,6 +143,7 @@
r.HandleFunc("/{app-name}"+loginPath, s.handleLogin).Methods(http.MethodPost)
r.HandleFunc("/{app-name}", s.handleAppStatus).Methods(http.MethodGet)
r.HandleFunc("/", s.handleStatus).Methods(http.MethodGet)
+ r.HandleFunc("/", s.handleCreateApp).Methods(http.MethodPost)
e <- http.ListenAndServe(fmt.Sprintf(":%d", s.port), r)
}()
go func() {
@@ -150,14 +157,48 @@
return <-e
}
+type UserGetter interface {
+ Get(r *http.Request) string
+}
+
+type externalUserGetter struct {
+ sc *securecookie.SecureCookie
+}
+
+func NewExternalUserGetter() UserGetter {
+ return &externalUserGetter{}
+}
+
+func (ug *externalUserGetter) Get(r *http.Request) string {
+ cookie, err := r.Cookie(sessionCookie)
+ if err != nil {
+ return ""
+ }
+ var user string
+ if err := ug.sc.Decode(sessionCookie, cookie.Value, &user); err != nil {
+ return ""
+ }
+ return user
+}
+
+type internalUserGetter struct{}
+
+func NewInternalUserGetter() UserGetter {
+ return internalUserGetter{}
+}
+
+func (ug internalUserGetter) Get(r *http.Request) string {
+ return r.Header.Get("X-User")
+}
+
func (s *DodoAppServer) mwAuth(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if strings.HasSuffix(r.URL.Path, loginPath) || strings.HasPrefix(r.URL.Path, logoutPath) {
next.ServeHTTP(w, r)
return
}
- cookie, err := r.Cookie(sessionCookie)
- if err != nil {
+ user := s.ug.Get(r)
+ if user == "" {
vars := mux.Vars(r)
appName, ok := vars["app-name"]
if !ok || appName == "" {
@@ -167,11 +208,6 @@
http.Redirect(w, r, fmt.Sprintf("/%s%s", appName, loginPath), http.StatusSeeOther)
return
}
- var user string
- if err := s.sc.Decode(sessionCookie, cookie.Value, &user); err != nil {
- http.Error(w, "unauthorized", http.StatusUnauthorized)
- return
- }
next.ServeHTTP(w, r.WithContext(context.WithValue(r.Context(), userCtx, user)))
})
}
@@ -251,7 +287,8 @@
}
type statusData struct {
- Apps []string
+ Apps []string
+ Networks []installer.Network
}
func (s *DodoAppServer) handleStatus(w http.ResponseWriter, r *http.Request) {
@@ -265,7 +302,12 @@
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
- data := statusData{apps}
+ networks, err := s.getNetworks(user.(string))
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+ data := statusData{apps, networks}
if err := s.tmplts.index.Execute(w, data); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
@@ -279,6 +321,7 @@
http.Error(w, "missing app-name", http.StatusBadRequest)
return
}
+ fmt.Fprintf(w, "git clone %s/%s\n\n\n", s.repoPublicAddr, appName)
commits, err := s.st.GetCommitHistory(appName)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
@@ -313,7 +356,11 @@
}
// TODO(gio): Create commit record on app init as well
go func() {
- networks, err := s.getNetworks()
+ owner, err := s.st.GetAppOwner(req.Repository.Name)
+ if err != nil {
+ return
+ }
+ networks, err := s.getNetworks(owner)
if err != nil {
return
}
@@ -357,9 +404,60 @@
s.workers[appName][req.Address] = struct{}{}
}
+func (s *DodoAppServer) handleCreateApp(w http.ResponseWriter, r *http.Request) {
+ u := r.Context().Value(userCtx)
+ if u == nil {
+ http.Error(w, "unauthorized", http.StatusUnauthorized)
+ return
+ }
+ user, ok := u.(string)
+ if !ok {
+ http.Error(w, "could not get user", http.StatusInternalServerError)
+ return
+ }
+ network := r.FormValue("network")
+ if network == "" {
+ http.Error(w, "missing network", http.StatusBadRequest)
+ return
+ }
+ adminPublicKey := r.FormValue("admin-public-key")
+ if network == "" {
+ http.Error(w, "missing admin public key", http.StatusBadRequest)
+ return
+ }
+ g := installer.NewFixedLengthRandomNameGenerator(3)
+ appName, err := g.Generate()
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+ if ok, err := s.client.UserExists(user); err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ } else if !ok {
+ if err := s.client.AddUser(user, adminPublicKey); err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+ }
+ if err := s.st.CreateUser(user, nil, adminPublicKey, network); err != nil && !errors.Is(err, ErrorAlreadyExists) {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+ if err := s.st.CreateApp(appName, user); err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+ if err := s.CreateApp(user, appName, adminPublicKey, network); err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+ http.Redirect(w, r, fmt.Sprintf("/%s", appName), http.StatusSeeOther)
+}
+
type apiCreateAppReq struct {
AdminPublicKey string `json:"adminPublicKey"`
- NetworkName string `json:"networkName"`
+ Network string `json:"network"`
}
type apiCreateAppResp struct {
@@ -379,11 +477,38 @@
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
- password, err := s.CreateApp(appName, req.AdminPublicKey, req.NetworkName)
+ user, err := s.client.FindUser(req.AdminPublicKey)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
+ if user != "" {
+ http.Error(w, "public key already registered", http.StatusBadRequest)
+ return
+ }
+ user = appName
+ if err := s.client.AddUser(user, req.AdminPublicKey); err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+ password := generatePassword()
+ hashed, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+ if err := s.st.CreateUser(user, hashed, req.AdminPublicKey, req.Network); err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+ if err := s.st.CreateApp(appName, user); err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+ if err := s.CreateApp(user, appName, req.AdminPublicKey, req.Network); err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
resp := apiCreateAppResp{
AppName: appName,
Password: password,
@@ -394,81 +519,52 @@
}
}
-func (s *DodoAppServer) CreateApp(appName, adminPublicKey, networkName string) (string, error) {
+func (s *DodoAppServer) CreateApp(user, appName, adminPublicKey, network string) error {
s.l.Lock()
defer s.l.Unlock()
fmt.Printf("Creating app: %s\n", appName)
if ok, err := s.client.RepoExists(appName); err != nil {
- return "", err
+ return err
} else if ok {
- return "", nil
- }
- user, err := s.client.FindUser(adminPublicKey)
- if err != nil {
- return "", err
- }
- if user == "" {
- user = appName
- if err := s.client.AddUser(user, adminPublicKey); err != nil {
- return "", err
- }
- }
- password := generatePassword()
- // TODO(gio): take admin password for initial application as input
- if appName == "app" {
- password = "app"
- }
- hashed, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
- if err != nil {
- return "", err
- }
- if err := s.st.CreateUser(user, hashed); err != nil {
- if !errors.Is(err, ErrorAlreadyExists) {
- return "", err
- } else {
- password = ""
- }
- }
- if err := s.st.CreateApp(appName, user); err != nil {
- return "", err
+ return nil
}
if err := s.client.AddRepository(appName); err != nil {
- return "", err
+ return err
}
appRepo, err := s.client.GetRepo(appName)
if err != nil {
- return "", err
+ return err
}
- if err := InitRepo(appRepo, networkName); err != nil {
- return "", err
+ if err := InitRepo(appRepo, network); err != nil {
+ return err
}
apps := installer.NewInMemoryAppRepository(installer.CreateAllApps())
app, err := installer.FindEnvApp(apps, "dodo-app-instance")
if err != nil {
- return "", err
+ return err
}
suffixGen := installer.NewFixedLengthRandomSuffixGenerator(3)
suffix, err := suffixGen.Generate()
if err != nil {
- return "", err
+ return err
}
namespace := fmt.Sprintf("%s%s%s", s.env.NamespacePrefix, app.Namespace(), suffix)
s.appNs[appName] = namespace
- networks, err := s.getNetworks()
+ networks, err := s.getNetworks(user)
if err != nil {
- return "", err
+ return err
}
if err := s.updateDodoApp(appName, namespace, networks); err != nil {
- return "", err
+ return err
}
repo, err := s.client.GetRepo(ConfigRepoName)
if err != nil {
- return "", err
+ return err
}
hf := installer.NewGitHelmFetcher()
m, err := installer.NewAppManager(repo, s.nsc, s.jc, hf, "/")
if err != nil {
- return "", err
+ return err
}
if err := repo.Do(func(fs soft.RepoFS) (string, error) {
w, err := fs.Writer(namespacesFile)
@@ -498,41 +594,41 @@
}
return fmt.Sprintf("Installed app: %s", appName), nil
}); err != nil {
- return "", err
+ return err
}
cfg, err := m.FindInstance(appName)
if err != nil {
- return "", err
+ return err
}
fluxKeys, ok := cfg.Input["fluxKeys"]
if !ok {
- return "", fmt.Errorf("Fluxcd keys not found")
+ return fmt.Errorf("Fluxcd keys not found")
}
fluxPublicKey, ok := fluxKeys.(map[string]any)["public"]
if !ok {
- return "", fmt.Errorf("Fluxcd keys not found")
+ return fmt.Errorf("Fluxcd keys not found")
}
if ok, err := s.client.UserExists("fluxcd"); err != nil {
- return "", err
+ return err
} else if ok {
if err := s.client.AddPublicKey("fluxcd", fluxPublicKey.(string)); err != nil {
- return "", err
+ return err
}
} else {
if err := s.client.AddUser("fluxcd", fluxPublicKey.(string)); err != nil {
- return "", err
+ return err
}
}
if err := s.client.AddReadOnlyCollaborator(appName, "fluxcd"); err != nil {
- return "", err
+ return err
}
if err := s.client.AddWebhook(appName, fmt.Sprintf("http://%s/update", s.self), "--active=true", "--events=push", "--content-type=json"); err != nil {
- return "", err
+ return err
}
if err := s.client.AddReadWriteCollaborator(appName, user); err != nil {
- return "", err
+ return err
}
- return password, nil
+ return nil
}
type apiAddAdminKeyReq struct {
@@ -630,7 +726,7 @@
}
`
-func InitRepo(repo soft.RepoIO, networkName string) error {
+func InitRepo(repo soft.RepoIO, network string) error {
return repo.Do(func(fs soft.RepoFS) (string, error) {
{
w, err := fs.Writer("go.mod")
@@ -654,7 +750,7 @@
return "", err
}
defer w.Close()
- fmt.Fprintf(w, appCue, networkName)
+ fmt.Fprintf(w, appCue, network)
}
return "go web app template", nil
})
@@ -664,7 +760,7 @@
return "foo"
}
-func (s *DodoAppServer) getNetworks() ([]installer.Network, error) {
+func (s *DodoAppServer) getNetworks(user string) ([]installer.Network, error) {
addr := fmt.Sprintf("%s/api/networks", s.envAppManagerAddr)
resp, err := http.Get(addr)
if err != nil {
@@ -674,14 +770,88 @@
if json.NewDecoder(resp.Body).Decode(&networks); err != nil {
return nil, err
}
- if len(s.networks) == 0 {
- return networks, nil
+ return s.nf.Filter(user, networks)
+}
+
+func pickNetwork(networks []installer.Network, network string) []installer.Network {
+ for _, n := range networks {
+ if n.Name == network {
+ return []installer.Network{n}
+ }
+ }
+ return []installer.Network{}
+}
+
+type NetworkFilter interface {
+ Filter(user string, networks []installer.Network) ([]installer.Network, error)
+}
+
+type noNetworkFilter struct{}
+
+func NewNoNetworkFilter() NetworkFilter {
+ return noNetworkFilter{}
+}
+
+func (f noNetworkFilter) Filter(app string, networks []installer.Network) ([]installer.Network, error) {
+ return networks, nil
+}
+
+type filterByOwner struct {
+ st Store
+}
+
+func NewNetworkFilterByOwner(st Store) NetworkFilter {
+ return &filterByOwner{st}
+}
+
+func (f *filterByOwner) Filter(user string, networks []installer.Network) ([]installer.Network, error) {
+ network, err := f.st.GetUserNetwork(user)
+ if err != nil {
+ return nil, err
}
ret := []installer.Network{}
for _, n := range networks {
- if slices.Contains(s.networks, n.Name) {
+ if n.Name == network {
ret = append(ret, n)
}
}
return ret, nil
}
+
+type allowListFilter struct {
+ allowed []string
+}
+
+func NewAllowListFilter(allowed []string) NetworkFilter {
+ return &allowListFilter{allowed}
+}
+
+func (f *allowListFilter) Filter(app string, networks []installer.Network) ([]installer.Network, error) {
+ ret := []installer.Network{}
+ for _, n := range networks {
+ if slices.Contains(f.allowed, n.Name) {
+ ret = append(ret, n)
+ }
+ }
+ return ret, nil
+}
+
+type combinedNetworkFilter struct {
+ filters []NetworkFilter
+}
+
+func NewCombinedFilter(filters ...NetworkFilter) NetworkFilter {
+ return &combinedNetworkFilter{filters}
+}
+
+func (f *combinedNetworkFilter) Filter(app string, networks []installer.Network) ([]installer.Network, error) {
+ ret := networks
+ var err error
+ for _, f := range f.filters {
+ ret, err = f.Filter(app, ret)
+ if err != nil {
+ return nil, err
+ }
+ }
+ return ret, nil
+}
diff --git a/core/installer/welcome/store.go b/core/installer/welcome/store.go
index 857045c..e93c03e 100644
--- a/core/installer/welcome/store.go
+++ b/core/installer/welcome/store.go
@@ -10,7 +10,7 @@
)
const (
- errorUniqueConstraintViolation = 2067
+ errorConstraintPrimaryKeyViolation = 1555
)
var (
@@ -23,8 +23,10 @@
}
type Store interface {
- CreateUser(username string, password []byte) error
+ // TODO(gio): Remove publicKey once auto user sync is implemented
+ CreateUser(username string, password []byte, publicKey, network string) error
GetUserPassword(username string) ([]byte, error)
+ GetUserNetwork(username string) (string, error)
GetApps() ([]string, error)
GetUserApps(username string) ([]string, error)
CreateApp(name, username string) error
@@ -50,7 +52,9 @@
_, err := s.db.Exec(`
CREATE TABLE IF NOT EXISTS users (
username TEXT PRIMARY KEY,
- password BLOB
+ password BLOB,
+ public_key TEXT,
+ network TEXT
);
CREATE TABLE IF NOT EXISTS apps (
name TEXT PRIMARY KEY,
@@ -66,12 +70,12 @@
}
-func (s *storeImpl) CreateUser(username string, password []byte) error {
- query := `INSERT INTO users (username, password) VALUES (?, ?)`
- _, err := s.db.Exec(query, username, password)
+func (s *storeImpl) CreateUser(username string, password []byte, publicKey, network string) error {
+ query := `INSERT INTO users (username, password, public_key, network) VALUES (?, ?, ?, ?)`
+ _, err := s.db.Exec(query, username, password, publicKey, network)
if err != nil {
sqliteErr, ok := err.(*sqlite3.Error)
- if ok && sqliteErr.ExtendedCode() == errorUniqueConstraintViolation {
+ if ok && sqliteErr.ExtendedCode() == errorConstraintPrimaryKeyViolation {
return ErrorAlreadyExists
}
}
@@ -91,6 +95,22 @@
return ret, nil
}
+func (s *storeImpl) GetUserNetwork(username string) (string, error) {
+ query := `SELECT network FROM users WHERE username = ?`
+ row := s.db.QueryRow(query, username)
+ if err := row.Err(); err != nil {
+ return "", err
+ }
+ var ret string
+ if err := row.Scan(&ret); err != nil {
+ if errors.Is(sql.ErrNoRows, err) {
+ return "", nil
+ }
+ return "", err
+ }
+ return ret, nil
+}
+
func (s *storeImpl) CreateApp(name, username string) error {
query := `INSERT INTO apps (name, username) VALUES (?, ?)`
_, err := s.db.Exec(query, name, username)