From c75e79efadcfc844deb4fb46e6eb228a1b1f3d2a Mon Sep 17 00:00:00 2001 From: fiftin Date: Tue, 2 Jul 2024 23:45:59 +0500 Subject: [PATCH] refactor(secrets): move secrets to environment entity --- api/projects/environment.go | 162 +++++++++++-------------- api/router.go | 3 - db/AccessKey.go | 4 + db/Environment.go | 28 ++++- db/Migration.go | 1 + db/sql/access_key.go | 9 +- db/sql/environment.go | 2 +- web/src/components/EnvironmentForm.vue | 112 ++++++++++++++++- 8 files changed, 215 insertions(+), 106 deletions(-) diff --git a/api/projects/environment.go b/api/projects/environment.go index 8f155095..494b87f4 100644 --- a/api/projects/environment.go +++ b/api/projects/environment.go @@ -10,6 +10,55 @@ import ( "github.com/gorilla/context" ) +func updateEnvironmentSecrets(store db.Store, env db.Environment) error { + for _, secret := range env.Secrets { + var err error + + var key db.AccessKey + + switch secret.Operation { + case db.EnvironmentSecretCreate: + key, err = store.CreateAccessKey(db.AccessKey{ + Name: secret.Name, + String: secret.Secret, + EnvironmentID: &env.ID, + ProjectID: &env.ProjectID, + Type: db.AccessKeyString, + }) + case db.EnvironmentSecretDelete: + key, err = store.GetAccessKey(env.ProjectID, secret.ID) + + if err != nil { + continue + } + + if key.EnvironmentID == nil && *key.EnvironmentID == env.ID { + continue + } + + err = store.DeleteAccessKey(env.ProjectID, secret.ID) + case db.EnvironmentSecretUpdate: + key, err = store.GetAccessKey(env.ProjectID, secret.ID) + + if err != nil { + continue + } + + if key.EnvironmentID == nil && *key.EnvironmentID == env.ID { + continue + } + + err = store.UpdateAccessKey(db.AccessKey{ + Name: secret.Name, + String: secret.Secret, + Type: db.AccessKeyString, + }) + } + } + + return nil +} + // EnvironmentMiddleware ensures an environment exists and loads it to the context func EnvironmentMiddleware(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { @@ -27,6 +76,20 @@ func EnvironmentMiddleware(next http.Handler) http.Handler { return } + keys, err := helpers.Store(r).GetEnvironmentSecrets(env.ProjectID, env.ID) + + if err != nil { + helpers.WriteError(w, err) + return + } + + for _, k := range keys { + env.Secrets = append(env.Secrets, db.EnvironmentSecret{ + ID: k.ID, + Name: k.Name, + }) + } + context.Set(r, "environment", env) next.ServeHTTP(w, r) }) @@ -99,6 +162,11 @@ func UpdateEnvironment(w http.ResponseWriter, r *http.Request) { Description: fmt.Sprintf("Environment %s updated", env.Name), }) + if err := updateEnvironmentSecrets(helpers.Store(r), env); err != nil { + helpers.WriteError(w, err) + return + } + w.WriteHeader(http.StatusNoContent) } @@ -131,6 +199,11 @@ func AddEnvironment(w http.ResponseWriter, r *http.Request) { Description: fmt.Sprintf("Environment %s created", newEnv.Name), }) + if err = updateEnvironmentSecrets(helpers.Store(r), newEnv); err != nil { + //helpers.WriteError(w, err) + //return + } + w.WriteHeader(http.StatusNoContent) } @@ -162,92 +235,3 @@ func RemoveEnvironment(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusNoContent) } - -type environmentSecretOperation string - -const ( - environmentSecretCreate environmentSecretOperation = "create" - environmentSecretUpdate environmentSecretOperation = "update" - environmentSecretDelete environmentSecretOperation = "delete" -) - -type environmentSecret struct { - ID int `json:"id"` - Name string `json:"name"` - Secret string `json:"secret"` - Operation environmentSecretOperation `json:"operation"` -} - -func GetEnvironmentSecrets(w http.ResponseWriter, r *http.Request) { - env := context.Get(r, "environment").(db.Environment) - - store := helpers.Store(r) - - keys, err := store.GetEnvironmentSecrets(env.ProjectID, env.ID) - - if err != nil { - helpers.WriteError(w, err) - return - } - - helpers.WriteJSON(w, http.StatusOK, keys) -} - -func UpdateEnvironmentSecrets(w http.ResponseWriter, r *http.Request) { - env := context.Get(r, "environment").(db.Environment) - - var secrets []environmentSecret - - if !helpers.Bind(w, r, &secrets) { - return - } - - store := helpers.Store(r) - - for _, secret := range secrets { - var err error - - var key db.AccessKey - - switch secret.Operation { - case environmentSecretCreate: - key, err = store.CreateAccessKey(db.AccessKey{ - Name: secret.Name, - String: secret.Secret, - EnvironmentID: &env.ID, - ProjectID: &env.ProjectID, - Type: db.AccessKeyString, - }) - case environmentSecretDelete: - key, err = store.GetAccessKey(env.ProjectID, secret.ID) - - if err != nil { - continue - } - - if key.EnvironmentID == nil && *key.EnvironmentID == env.ID { - continue - } - - err = store.DeleteAccessKey(env.ProjectID, secret.ID) - case environmentSecretUpdate: - key, err = store.GetAccessKey(env.ProjectID, secret.ID) - - if err != nil { - continue - } - - if key.EnvironmentID == nil && *key.EnvironmentID == env.ID { - continue - } - - err = store.UpdateAccessKey(db.AccessKey{ - Name: secret.Name, - String: secret.Secret, - Type: db.AccessKeyString, - }) - } - } - - w.WriteHeader(http.StatusNoContent) -} diff --git a/api/router.go b/api/router.go index a8a09643..31f7fdbd 100644 --- a/api/router.go +++ b/api/router.go @@ -249,9 +249,6 @@ func Route() *mux.Router { projectEnvManagement.HandleFunc("/{environment_id}", projects.UpdateEnvironment).Methods("PUT") projectEnvManagement.HandleFunc("/{environment_id}", projects.RemoveEnvironment).Methods("DELETE") - projectEnvManagement.HandleFunc("/{environment_id}/secrets", projects.GetEnvironmentSecrets).Methods("GET", "HEAD") - projectEnvManagement.HandleFunc("/{environment_id}/secrets", projects.UpdateEnvironmentSecrets).Methods("POST") - projectTmplManagement := projectUserAPI.PathPrefix("/templates").Subrouter() projectTmplManagement.Use(projects.TemplatesMiddleware) diff --git a/db/AccessKey.go b/db/AccessKey.go index 41c9ab82..b9e83ad9 100644 --- a/db/AccessKey.go +++ b/db/AccessKey.go @@ -171,6 +171,8 @@ func (key *AccessKey) SerializeSecret() error { var err error switch key.Type { + case AccessKeyString: + plaintext = []byte(key.String) case AccessKeySSH: plaintext, err = json.Marshal(key.SshKey) if err != nil { @@ -225,6 +227,8 @@ func (key *AccessKey) SerializeSecret() error { func (key *AccessKey) unmarshalAppropriateField(secret []byte) (err error) { switch key.Type { + case AccessKeyString: + key.String = string(secret) case AccessKeySSH: sshKey := SshKey{} err = json.Unmarshal(secret, &sshKey) diff --git a/db/Environment.go b/db/Environment.go index 05374284..3cdefd99 100644 --- a/db/Environment.go +++ b/db/Environment.go @@ -4,14 +4,30 @@ import ( "encoding/json" ) +type EnvironmentSecretOperation string + +const ( + EnvironmentSecretCreate EnvironmentSecretOperation = "create" + EnvironmentSecretUpdate EnvironmentSecretOperation = "update" + EnvironmentSecretDelete EnvironmentSecretOperation = "delete" +) + +type EnvironmentSecret struct { + ID int `json:"id"` + Name string `json:"name"` + Secret string `json:"secret"` + Operation EnvironmentSecretOperation `json:"operation"` +} + // Environment is used to pass additional arguments, in json form to ansible type Environment struct { - ID int `db:"id" json:"id"` - Name string `db:"name" json:"name" binding:"required"` - ProjectID int `db:"project_id" json:"project_id"` - Password *string `db:"password" json:"password"` - JSON string `db:"json" json:"json" binding:"required"` - ENV *string `db:"env" json:"env" binding:"required"` + ID int `db:"id" json:"id"` + Name string `db:"name" json:"name" binding:"required"` + ProjectID int `db:"project_id" json:"project_id"` + Password *string `db:"password" json:"password"` + JSON string `db:"json" json:"json" binding:"required"` + ENV *string `db:"env" json:"env" binding:"required"` + Secrets []EnvironmentSecret `db:"-" json:"secrets"` } func (env *Environment) Validate() error { diff --git a/db/Migration.go b/db/Migration.go index 09097b9b..8d5f78c0 100644 --- a/db/Migration.go +++ b/db/Migration.go @@ -69,6 +69,7 @@ func GetMigrations() []Migration { {Version: "2.9.97"}, {Version: "2.9.100"}, {Version: "2.10.12"}, + {Version: "2.10.15"}, } } diff --git a/db/sql/access_key.go b/db/sql/access_key.go index b85d5048..f6b9c8bd 100644 --- a/db/sql/access_key.go +++ b/db/sql/access_key.go @@ -17,8 +17,8 @@ func (d *SqlDb) GetAccessKeyRefs(projectID int, keyID int) (db.ObjectReferrers, func (d *SqlDb) GetAccessKeys(projectID int, params db.RetrieveQueryParams) (keys []db.AccessKey, err error) { keys = make([]db.AccessKey, 0) - - q := d.makeObjectsQuery(projectID, db.AccessKeyProps, params).Where("pe.environment_is IS NULL") + + q := d.makeObjectsQuery(projectID, db.AccessKeyProps, params).Where("pe.environment_id IS NULL") query, args, err := q.ToSql() @@ -75,11 +75,12 @@ func (d *SqlDb) CreateAccessKey(key db.AccessKey) (newKey db.AccessKey, err erro insertID, err := d.insert( "id", - "insert into access_key (name, type, project_id, secret) values (?, ?, ?, ?)", + "insert into access_key (name, type, project_id, secret, environment_id) values (?, ?, ?, ?, ?)", key.Name, key.Type, key.ProjectID, - key.Secret) + key.Secret, + key.EnvironmentID) if err != nil { return diff --git a/db/sql/environment.go b/db/sql/environment.go index cebc8030..4a47a126 100644 --- a/db/sql/environment.go +++ b/db/sql/environment.go @@ -68,7 +68,7 @@ func (d *SqlDb) DeleteEnvironment(projectID int, environmentID int) error { func (d *SqlDb) GetEnvironmentSecrets(projectID int, environmentID int) (keys []db.AccessKey, err error) { keys = make([]db.AccessKey, 0) - q := d.makeObjectsQuery(projectID, db.AccessKeyProps, db.RetrieveQueryParams{}).Where("pe.environment_is = ?", environmentID) + q := d.makeObjectsQuery(projectID, db.AccessKeyProps, db.RetrieveQueryParams{}).Where("pe.environment_id = ?", environmentID) query, args, err := q.ToSql() diff --git a/web/src/components/EnvironmentForm.vue b/web/src/components/EnvironmentForm.vue index a3549d97..dcad074c 100644 --- a/web/src/components/EnvironmentForm.vue +++ b/web/src/components/EnvironmentForm.vue @@ -55,7 +55,6 @@ />
- {{ $t('environmentVariables') }} @@ -71,7 +70,6 @@ Variables passed as process environment variables. - -
+
+ + {{ $t('Secrets') }} + + + + Secrets. + + + + + + + +
+ New Secret +
+
+ @@ -157,8 +220,10 @@ export default { 'dind-runner:latest', ], advancedOptions: false, + json: '{}', env: [], + secrets: [], cmOptions: { tabSize: 2, @@ -186,6 +251,21 @@ export default { } }, + addSecret(name = '', value = '') { + this.secrets.push({ name, value, new: true }); + }, + + removeSecret(val) { + const i = this.secrets.findIndex((v) => v.id === val.id); + if (i > -1) { + if (this.secrets[i].new) { + this.secrets.splice(i, 1); + } else { + this.secrets[i].remove = true; + } + } + }, + setExtraVar(name, value) { try { const obj = JSON.parse(this.json || '{}'); @@ -212,7 +292,25 @@ export default { env[predefinedVar.name] = predefinedVar.value; }); + const secrets = (this.secrets || []).map((s) => { + let operation; + if (s.new) { + operation = 'create'; + } else if (s.remove) { + operation = 'remove'; + } else if (s.value !== '') { + operation = 'update'; + } + return { + id: s.id, + name: s.name, + secret: s.value, + operation, + }; + }).filter((s) => s.operation != null); + this.item.env = JSON.stringify(env); + this.item.secrets = secrets; }, afterLoadData() { @@ -220,6 +318,8 @@ export default { const env = JSON.parse(this.item?.env || '{}'); + const secrets = this.item?.secrets || []; + this.env = Object.keys(env) .filter((x) => { const index = PREDEFINED_ENV_VARS.findIndex((v) => v.name === x); @@ -230,6 +330,12 @@ export default { value: env[x], })); + this.secrets = secrets.map((x) => ({ + id: x.id, + name: x.name, + value: '', + })); + Object.keys(env).forEach((x) => { const index = PREDEFINED_ENV_VARS.findIndex((v) => v.name === x); if (index !== -1 && PREDEFINED_ENV_VARS[index].value === env[x]) {