Skip to content

Commit

Permalink
feat(api,cli): new feature to list and check encrypted secrets (#5648)
Browse files Browse the repository at this point in the history
Signed-off-by: francois  samin <francois.samin@corp.ovh.com>
  • Loading branch information
fsamin committed Jan 25, 2021
1 parent 28b6826 commit 44eae68
Show file tree
Hide file tree
Showing 11 changed files with 139 additions and 9 deletions.
22 changes: 21 additions & 1 deletion cli/cdsctl/encrypt.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ my-data: 01234567890987654321`,
}

func encrypt() *cobra.Command {
return cli.NewCommand(encryptCmd, encryptRun, nil, withAllCommandModifiers()...)
return cli.NewCommand(encryptCmd, encryptRun, cli.SubCommands{encryptList()}, withAllCommandModifiers()...)
}

func encryptRun(v cli.Values) error {
Expand All @@ -76,3 +76,23 @@ func encryptRun(v cli.Values) error {
fmt.Printf("%s: %s\n", variable.Name, variable.Value)
return nil
}

var encryptListCmd = cli.Command{
Name: "list",
Short: "List all the encrypted variable of your CDS project",
Ctx: []cli.Arg{
{Name: _ProjectKey},
},
}

func encryptList() *cobra.Command {
return cli.NewListCommand(encryptListCmd, encryptListRun, nil, withAllCommandModifiers()...)
}

func encryptListRun(v cli.Values) (cli.ListResult, error) {
secrets, err := client.VariableListEncrypt(v.GetString(_ProjectKey))
if err != nil {
return nil, err
}
return cli.AsListResult(secrets), nil
}
1 change: 1 addition & 0 deletions engine/api/api_routes.go
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,7 @@ func (api *API) InitRouter() {
r.Handle("/project/{permProjectKey}/group/{groupName}", Scope(sdk.AuthConsumerScopeProject), r.PUT(api.putGroupRoleOnProjectHandler), r.DELETE(api.deleteGroupFromProjectHandler))
r.Handle("/project/{permProjectKey}/variable", Scope(sdk.AuthConsumerScopeProject), r.GET(api.getVariablesInProjectHandler))
r.Handle("/project/{permProjectKey}/encrypt", Scope(sdk.AuthConsumerScopeProject), r.POST(api.postEncryptVariableHandler))
r.Handle("/project/{permProjectKey}/encrypt/list", Scope(sdk.AuthConsumerScopeProject), r.GET(api.getListEncryptVariableHandler))
r.Handle("/project/{permProjectKey}/variable/audit", Scope(sdk.AuthConsumerScopeProject), r.GET(api.getVariablesAuditInProjectnHandler))
r.Handle("/project/{permProjectKey}/variable/{name}", Scope(sdk.AuthConsumerScopeProject), r.GET(api.getVariableInProjectHandler), r.POST(api.addVariableInProjectHandler), r.PUT(api.updateVariableInProjectHandler), r.DELETE(api.deleteVariableFromProjectHandler))
r.Handle("/project/{permProjectKey}/variable/{name}/audit", Scope(sdk.AuthConsumerScopeProject), r.GET(api.getVariableAuditInProjectHandler))
Expand Down
2 changes: 1 addition & 1 deletion engine/api/environment/environment_importer.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ func Import(db gorpmapper.SqlExecutorWithTx, proj sdk.Project, env *sdk.Environm
//ImportInto import variables and groups on an existing environment
func ImportInto(ctx context.Context, db gorpmapper.SqlExecutorWithTx, env *sdk.Environment, into *sdk.Environment, msgChan chan<- sdk.Message, u sdk.Identifiable) error {
var updateVar = func(v *sdk.EnvironmentVariable) {
log.Debug(ctx, "ImportInto> Updating var %s", v.Name)
log.Debug(ctx, "ImportInto> Updating var %q with value %q", v.Name, v.Value)

varBefore, errV := LoadVariable(db, into.ID, v.Name)
if errV != nil {
Expand Down
14 changes: 13 additions & 1 deletion engine/api/project/key.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package project
import (
"bytes"
"compress/gzip"
"context"
"crypto/rand"
"database/sql"
"encoding/base64"
Expand All @@ -18,7 +19,9 @@ import (
)

func init() {
gorpmapping.Register(gorpmapping.New(dbEncryptedData{}, "encrypted_data", false, "token"))
gorpmapping.Register(gorpmapping.New(dbEncryptedData{}, "encrypted_data", false, "project_id", "content_name"))
gorpmapping.Register(gorpmapping.New(sdk.Secret{}, "encrypted_data", false, "project_id", "content_name"))

}

type dbEncryptedData struct {
Expand All @@ -28,6 +31,15 @@ type dbEncryptedData struct {
EncyptedContent []byte `db:"encrypted_content"`
}

func ListEncryptedData(ctx context.Context, db gorp.SqlExecutor, projectID int64) ([]sdk.Secret, error) {
var res []sdk.Secret
query := gorpmapping.NewQuery("select content_name, token from encrypted_data where project_id = $1").Args(projectID)
if err := gorpmapping.GetAll(ctx, db, query, &res); err != nil {
return nil, sdk.WithStack(err)
}
return res, nil
}

// EncryptWithBuiltinKey encrypt a content with the builtin gpg key encode, compress it and encode with base64
func EncryptWithBuiltinKey(db gorp.SqlExecutor, projectID int64, name, content string) (string, error) {
existingToken, err := db.SelectStr("select token from encrypted_data where project_id = $1 and content_name = $2", projectID, name)
Expand Down
34 changes: 34 additions & 0 deletions engine/api/project_variable.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,47 @@ import (
"net/http"

"github.com/gorilla/mux"
"github.com/rockbears/log"

"github.com/ovh/cds/engine/api/event"
"github.com/ovh/cds/engine/api/project"
"github.com/ovh/cds/engine/service"
"github.com/ovh/cds/sdk"
)

func (api *API) getListEncryptVariableHandler() service.Handler {
return func(ctx context.Context, w http.ResponseWriter, r *http.Request) error {
vars := mux.Vars(r)
key := vars[permProjectKey]

p, err := project.Load(ctx, api.mustDB(), key)
if err != nil {
return err
}

res, err := project.ListEncryptedData(ctx, api.mustDB(), p.ID)
if err != nil {
return err
}

for i := range res {
decryptedData, err := project.DecryptWithBuiltinKey(api.mustDB(), p.ID, res[i].Token)
if err != nil {
log.Error(ctx, "unable to decrypt data %s: %v", res[i].Token, err)
res[i].Status = "decryption failed"
}

if decryptedData == sdk.PasswordPlaceholder {
res[i].Status = "password placeholder detected"
}

res[i].Status = "OK"
}

return service.WriteJSON(w, res, http.StatusOK)
}
}

func (api *API) postEncryptVariableHandler() service.Handler {
return func(ctx context.Context, w http.ResponseWriter, r *http.Request) error {
vars := mux.Vars(r)
Expand Down
4 changes: 3 additions & 1 deletion engine/cdn/storage/storageunit_run.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@ func (x *RunningStorageUnits) FillSyncItemChannel(ctx context.Context, s Storage
if err := x.cache.ScoredSetRevRange(ctx, cache.Key(KeyBackendSync, s.Name()), 0, nbItem, &itemIDs); err != nil {
return err
}
log.Info(ctx, "FillSyncItemChannel> Item to sync for %s: %d", s.Name(), len(itemIDs))
if len(itemIDs) > 0 {
log.Info(ctx, "FillSyncItemChannel> Item to sync for %s: %d", s.Name(), len(itemIDs))
}
for _, id := range itemIDs {
select {
case s.SyncItemChannel() <- id:
Expand Down
8 changes: 8 additions & 0 deletions sdk/cdsclient/client_project_variable.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,3 +49,11 @@ func (c *client) VariableEncrypt(projectKey string, varName string, content stri
}
return variable, nil
}

func (c *client) VariableListEncrypt(projectKey string) ([]sdk.Secret, error) {
secrets := []sdk.Secret{}
if _, err := c.GetJSON(context.Background(), "/project/"+projectKey+"/encrypt/list", &secrets, nil); err != nil {
return nil, err
}
return secrets, nil
}
1 change: 1 addition & 0 deletions sdk/cdsclient/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,7 @@ type ProjectVariablesClient interface {
ProjectVariableGet(projectKey string, varName string) (*sdk.Variable, error)
ProjectVariableUpdate(projectKey string, variable *sdk.Variable) error
VariableEncrypt(projectKey string, varName string, content string) (*sdk.Variable, error)
VariableListEncrypt(projectKey string) ([]sdk.Secret, error)
}

// QueueClient exposes queue related functions
Expand Down
53 changes: 49 additions & 4 deletions sdk/cdsclient/mock_cdsclient/interface_mock.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 7 additions & 0 deletions sdk/variable.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,13 @@ import (
"time"
)

type Secret struct {
ProjectID int64 `json:"project_id" db:"project_id" cli:"-"`
Name string `json:"name" db:"content_name" cli:"name,key"`
Token string `json:"token" db:"token" cli:"token"`
Status string `json:"status" db:"-" cli:"status"`
}

// Variable represent a variable for a project or pipeline
type Variable struct {
ID int64 `json:"id,omitempty" cli:"-"`
Expand Down
2 changes: 1 addition & 1 deletion ui/src/app/shared/parameter/value/parameter.value.html
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
<label>{{ ' '}}</label>
</div>
<div *ngSwitchCase="'ssh-key'">
<ng-container *ngIf="keys && keys.ssh && (keyExist(editableValue.toString()) || !editableValue)">
<ng-container *ngIf="keys && keys.ssh && (!editableValue || keyExist(editableValue.toString()))">
<sui-select class="selection" name="type" [(ngModel)]="editableValue"
(ngModelChange)="valueChanged();sendValueChanged()" [options]="keys.ssh" labelField="name"
valueField="name" [isSearchable]="true" #sshkey>
Expand Down

0 comments on commit 44eae68

Please sign in to comment.