mirror of
https://github.com/semaphoreui/semaphore.git
synced 2024-12-03 14:51:05 +01:00
refactor(secrets): move secrets to environment entity
This commit is contained in:
parent
891b27649e
commit
c75e79efad
@ -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)
|
||||
}
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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 {
|
||||
|
@ -69,6 +69,7 @@ func GetMigrations() []Migration {
|
||||
{Version: "2.9.97"},
|
||||
{Version: "2.9.100"},
|
||||
{Version: "2.10.12"},
|
||||
{Version: "2.10.15"},
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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()
|
||||
|
||||
|
@ -55,7 +55,6 @@
|
||||
/>
|
||||
|
||||
<div>
|
||||
|
||||
<v-subheader class="px-0 mt-4">
|
||||
{{ $t('environmentVariables') }}
|
||||
|
||||
@ -71,7 +70,6 @@
|
||||
<span>Variables passed as process environment variables.</span>
|
||||
</v-tooltip>
|
||||
</v-subheader>
|
||||
|
||||
<v-data-table
|
||||
:items="env"
|
||||
:items-per-page="-1"
|
||||
@ -111,7 +109,6 @@
|
||||
</tr>
|
||||
</template>
|
||||
</v-data-table>
|
||||
|
||||
<div class="text-right mt-2 mb-4">
|
||||
<v-btn
|
||||
color="primary"
|
||||
@ -120,6 +117,72 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<v-subheader class="px-0 mt-4">
|
||||
{{ $t('Secrets') }}
|
||||
|
||||
<v-tooltip bottom color="black" open-delay="300">
|
||||
<template v-slot:activator="{ on, attrs }">
|
||||
<v-icon
|
||||
class="ml-1"
|
||||
v-bind="attrs"
|
||||
v-on="on"
|
||||
color="lightgray"
|
||||
>mdi-help-circle</v-icon>
|
||||
</template>
|
||||
<span>Secrets.</span>
|
||||
</v-tooltip>
|
||||
</v-subheader>
|
||||
|
||||
<v-data-table
|
||||
:items="secrets.filter(s => !s.removed)"
|
||||
:items-per-page="-1"
|
||||
class="elevation-1"
|
||||
hide-default-footer
|
||||
no-data-text="No values"
|
||||
>
|
||||
<template v-slot:item="props">
|
||||
<tr>
|
||||
<td class="pa-1">
|
||||
<v-text-field
|
||||
solo-inverted
|
||||
flat
|
||||
hide-details
|
||||
v-model="props.item.name"
|
||||
class="v-text-field--solo--no-min-height"
|
||||
></v-text-field>
|
||||
</td>
|
||||
<td class="pa-1">
|
||||
<v-text-field
|
||||
solo-inverted
|
||||
flat
|
||||
hide-details
|
||||
v-model="props.item.value"
|
||||
placeholder="*******"
|
||||
class="v-text-field--solo--no-min-height"
|
||||
></v-text-field>
|
||||
</td>
|
||||
<td style="width: 38px;">
|
||||
<v-icon
|
||||
small
|
||||
class="pa-1"
|
||||
@click="removeSecret(props.item)"
|
||||
>
|
||||
mdi-delete
|
||||
</v-icon>
|
||||
</td>
|
||||
</tr>
|
||||
</template>
|
||||
</v-data-table>
|
||||
|
||||
<div class="text-right mt-2 mb-4">
|
||||
<v-btn
|
||||
color="primary"
|
||||
@click="addSecret()"
|
||||
>New Secret</v-btn>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</v-form>
|
||||
</template>
|
||||
|
||||
@ -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]) {
|
||||
|
Loading…
Reference in New Issue
Block a user