feat(secret): transition the secret service to isolated pattern (#18340)
* feat(secret): transition the secret service to isolated pattern We needed a secret service handler that would be pluggable in the org service and building it right is better then doing it with messy code.pull/18357/head
parent
d21fce5f6d
commit
ab2f4ecdbe
2
go.sum
2
go.sum
|
@ -149,6 +149,7 @@ github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd h1:qMd81Ts1T
|
|||
github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI=
|
||||
github.com/coreos/bbolt v1.3.2 h1:wZwiHHUieZCquLkDL0B8UhzreNWsPHooDAG3q34zk0s=
|
||||
github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
|
||||
github.com/coreos/etcd v3.3.10+incompatible h1:jFneRYjIvLMLhDLCzuTuU4rSJUjRplcJQ7pD7MnhC04=
|
||||
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
|
||||
github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
|
||||
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
|
||||
|
@ -471,6 +472,7 @@ github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af h1:pmfjZENx5i
|
|||
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
|
||||
github.com/jmoiron/sqlx v1.2.1-0.20190826204134-d7d95172beb5/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks=
|
||||
github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
|
||||
github.com/jonboulle/clockwork v0.1.0 h1:VKV+ZcuP6l3yW9doeqz6ziZGgcynBVQO+obU0+0hcPo=
|
||||
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
|
||||
github.com/jpillora/backoff v0.0.0-20180909062703-3050d21c67d7/go.mod h1:2iMrUgbbvHEiQClaW2NsSzMyGHqN+rDFqY705q49KG0=
|
||||
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||
|
|
|
@ -0,0 +1,88 @@
|
|||
package secret
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/influxdata/influxdb/v2"
|
||||
"github.com/influxdata/influxdb/v2/kit/tracing"
|
||||
"github.com/influxdata/influxdb/v2/pkg/httpc"
|
||||
)
|
||||
|
||||
type Client struct {
|
||||
Client *httpc.Client
|
||||
}
|
||||
|
||||
// LoadSecret is not implemented for http
|
||||
func (s *Client) LoadSecret(ctx context.Context, orgID influxdb.ID, k string) (string, error) {
|
||||
return "", &influxdb.Error{
|
||||
Code: influxdb.EMethodNotAllowed,
|
||||
Msg: "load secret is not implemented for http",
|
||||
}
|
||||
}
|
||||
|
||||
// PutSecret is not implemented for http.
|
||||
func (s *Client) PutSecret(ctx context.Context, orgID influxdb.ID, k string, v string) error {
|
||||
return &influxdb.Error{
|
||||
Code: influxdb.EMethodNotAllowed,
|
||||
Msg: "put secret is not implemented for http",
|
||||
}
|
||||
}
|
||||
|
||||
// GetSecretKeys get all secret keys mathing an org ID via HTTP.
|
||||
func (s *Client) GetSecretKeys(ctx context.Context, orgID influxdb.ID) ([]string, error) {
|
||||
span, _ := tracing.StartSpanFromContext(ctx)
|
||||
defer span.Finish()
|
||||
|
||||
span.LogKV("org-id", orgID)
|
||||
|
||||
path := fmt.Sprintf("/api/v2/orgs/%s/secrets", orgID.String())
|
||||
|
||||
var ss secretsResponse
|
||||
err := s.Client.
|
||||
Get(path).
|
||||
DecodeJSON(&ss).
|
||||
Do(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return ss.Secrets, nil
|
||||
}
|
||||
|
||||
// PutSecrets is not implemented for http.
|
||||
func (s *Client) PutSecrets(ctx context.Context, orgID influxdb.ID, m map[string]string) error {
|
||||
return &influxdb.Error{
|
||||
Code: influxdb.EMethodNotAllowed,
|
||||
Msg: "put secrets is not implemented for http",
|
||||
}
|
||||
}
|
||||
|
||||
// PatchSecrets will update the existing secret with new via http.
|
||||
func (s *Client) PatchSecrets(ctx context.Context, orgID influxdb.ID, m map[string]string) error {
|
||||
span, _ := tracing.StartSpanFromContext(ctx)
|
||||
defer span.Finish()
|
||||
|
||||
if orgID != 0 {
|
||||
span.LogKV("org-id", orgID)
|
||||
}
|
||||
|
||||
path := fmt.Sprintf("/api/v2/orgs/%s/secrets", orgID.String())
|
||||
|
||||
return s.Client.
|
||||
PatchJSON(m, path).
|
||||
Do(ctx)
|
||||
}
|
||||
|
||||
// DeleteSecret removes a single secret via HTTP.
|
||||
func (s *Client) DeleteSecret(ctx context.Context, orgID influxdb.ID, ks ...string) error {
|
||||
span, _ := tracing.StartSpanFromContext(ctx)
|
||||
defer span.Finish()
|
||||
|
||||
path := fmt.Sprintf("/api/v2/orgs/%s/secrets/delete", orgID.String())
|
||||
return s.Client.
|
||||
PostJSON(secretsDeleteBody{
|
||||
Secrets: ks,
|
||||
}, path).
|
||||
Do(ctx)
|
||||
}
|
|
@ -0,0 +1,134 @@
|
|||
package secret
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/go-chi/chi"
|
||||
"github.com/influxdata/influxdb/v2"
|
||||
kithttp "github.com/influxdata/influxdb/v2/kit/transport/http"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
type handler struct {
|
||||
log *zap.Logger
|
||||
svc influxdb.SecretService
|
||||
api *kithttp.API
|
||||
|
||||
idLookupKey string
|
||||
}
|
||||
|
||||
// NewHandler creates a new handler for the secret service
|
||||
func NewHandler(log *zap.Logger, idLookupKey string, svc influxdb.SecretService) http.Handler {
|
||||
h := &handler{
|
||||
log: log,
|
||||
svc: svc,
|
||||
api: kithttp.NewAPI(kithttp.WithLog(log)),
|
||||
|
||||
idLookupKey: idLookupKey,
|
||||
}
|
||||
|
||||
r := chi.NewRouter()
|
||||
|
||||
r.Get("/", h.handleGetSecrets)
|
||||
r.Patch("/", h.handlePatchSecrets)
|
||||
// TODO: this shouldn't be a post to delete
|
||||
r.Post("/delete", h.handleDeleteSecrets)
|
||||
return r
|
||||
}
|
||||
|
||||
// handleGetSecrets is the HTTP handler for the GET /api/v2/orgs/:id/secrets route.
|
||||
func (h *handler) handleGetSecrets(w http.ResponseWriter, r *http.Request) {
|
||||
orgID, err := h.decodeOrgID(r)
|
||||
if err != nil {
|
||||
h.api.Err(w, r, err)
|
||||
}
|
||||
|
||||
ks, err := h.svc.GetSecretKeys(r.Context(), orgID)
|
||||
if err != nil && influxdb.ErrorCode(err) != influxdb.ENotFound {
|
||||
h.api.Err(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
h.api.Respond(w, r, http.StatusOK, newSecretsResponse(orgID, ks))
|
||||
}
|
||||
|
||||
type secretsResponse struct {
|
||||
Links map[string]string `json:"links"`
|
||||
Secrets []string `json:"secrets"`
|
||||
}
|
||||
|
||||
func newSecretsResponse(orgID influxdb.ID, ks []string) *secretsResponse {
|
||||
if ks == nil {
|
||||
ks = []string{}
|
||||
}
|
||||
return &secretsResponse{
|
||||
Links: map[string]string{
|
||||
"org": fmt.Sprintf("/api/v2/orgs/%s", orgID),
|
||||
"self": fmt.Sprintf("/api/v2/orgs/%s/secrets", orgID),
|
||||
},
|
||||
Secrets: ks,
|
||||
}
|
||||
}
|
||||
|
||||
// handleGetPatchSecrets is the HTTP handler for the PATCH /api/v2/orgs/:id/secrets route.
|
||||
func (h *handler) handlePatchSecrets(w http.ResponseWriter, r *http.Request) {
|
||||
orgID, err := h.decodeOrgID(r)
|
||||
if err != nil {
|
||||
h.api.Err(w, r, err)
|
||||
}
|
||||
|
||||
var secrets map[string]string
|
||||
if err := h.api.DecodeJSON(r.Body, &secrets); err != nil {
|
||||
h.api.Err(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.svc.PatchSecrets(r.Context(), orgID, secrets); err != nil {
|
||||
h.api.Err(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
h.api.Respond(w, r, http.StatusNoContent, nil)
|
||||
}
|
||||
|
||||
type secretsDeleteBody struct {
|
||||
Secrets []string `json:"secrets"`
|
||||
}
|
||||
|
||||
// handleDeleteSecrets is the HTTP handler for the DELETE /api/v2/orgs/:id/secrets route.
|
||||
func (h *handler) handleDeleteSecrets(w http.ResponseWriter, r *http.Request) {
|
||||
orgID, err := h.decodeOrgID(r)
|
||||
if err != nil {
|
||||
h.api.Err(w, r, err)
|
||||
}
|
||||
|
||||
var reqBody secretsDeleteBody
|
||||
|
||||
if err := h.api.DecodeJSON(r.Body, &reqBody); err != nil {
|
||||
h.api.Err(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.svc.DeleteSecret(r.Context(), orgID, reqBody.Secrets...); err != nil {
|
||||
h.api.Err(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
h.api.Respond(w, r, http.StatusNoContent, nil)
|
||||
}
|
||||
|
||||
func (h *handler) decodeOrgID(r *http.Request) (influxdb.ID, error) {
|
||||
org := chi.URLParam(r, h.idLookupKey)
|
||||
if org == "" {
|
||||
return influxdb.InvalidID(), &influxdb.Error{
|
||||
Code: influxdb.EInvalid,
|
||||
Msg: "url missing id",
|
||||
}
|
||||
}
|
||||
id, err := influxdb.IDFromString(org)
|
||||
if err != nil {
|
||||
return influxdb.InvalidID(), err
|
||||
}
|
||||
return *id, nil
|
||||
}
|
|
@ -0,0 +1,337 @@
|
|||
package secret
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/go-chi/chi"
|
||||
"github.com/influxdata/influxdb/v2"
|
||||
influxdbhttp "github.com/influxdata/influxdb/v2/http"
|
||||
"github.com/influxdata/influxdb/v2/inmem"
|
||||
"github.com/influxdata/influxdb/v2/mock"
|
||||
influxdbtesting "github.com/influxdata/influxdb/v2/testing"
|
||||
"go.uber.org/zap/zaptest"
|
||||
)
|
||||
|
||||
func initSecretService(f influxdbtesting.SecretServiceFields, t *testing.T) (influxdb.SecretService, func()) {
|
||||
t.Helper()
|
||||
s := inmem.NewKVStore()
|
||||
|
||||
storage, err := NewStore(s)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
svc := NewService(storage)
|
||||
|
||||
for _, s := range f.Secrets {
|
||||
if err := svc.PutSecrets(context.Background(), s.OrganizationID, s.Env); err != nil {
|
||||
t.Fatalf("failed to populate users")
|
||||
}
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
for _, ss := range f.Secrets {
|
||||
if err := svc.PutSecrets(ctx, ss.OrganizationID, ss.Env); err != nil {
|
||||
t.Fatalf("failed to populate secrets")
|
||||
}
|
||||
}
|
||||
|
||||
handler := NewHandler(zaptest.NewLogger(t), "id", svc)
|
||||
router := chi.NewRouter()
|
||||
router.Mount("/api/v2/orgs/{id}/secrets", handler)
|
||||
server := httptest.NewServer(router)
|
||||
httpClient, err := influxdbhttp.NewHTTPClient(server.URL, "", false)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
client := Client{
|
||||
Client: httpClient,
|
||||
}
|
||||
return &client, server.Close
|
||||
}
|
||||
|
||||
func TestSecretService(t *testing.T) {
|
||||
t.Parallel()
|
||||
influxdbtesting.GetSecretKeys(initSecretService, t)
|
||||
influxdbtesting.PatchSecrets(initSecretService, t)
|
||||
influxdbtesting.DeleteSecrets(initSecretService, t)
|
||||
}
|
||||
|
||||
func TestSecretService_handleGetSecrets(t *testing.T) {
|
||||
type fields struct {
|
||||
SecretService influxdb.SecretService
|
||||
}
|
||||
type args struct {
|
||||
orgID influxdb.ID
|
||||
}
|
||||
type wants struct {
|
||||
statusCode int
|
||||
contentType string
|
||||
body string
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
args args
|
||||
wants wants
|
||||
}{
|
||||
{
|
||||
name: "get basic secrets",
|
||||
fields: fields{
|
||||
&mock.SecretService{
|
||||
GetSecretKeysFn: func(ctx context.Context, orgID influxdb.ID) ([]string, error) {
|
||||
return []string{"hello", "world"}, nil
|
||||
},
|
||||
},
|
||||
},
|
||||
args: args{
|
||||
orgID: 1,
|
||||
},
|
||||
wants: wants{
|
||||
statusCode: http.StatusOK,
|
||||
contentType: "application/json; charset=utf-8",
|
||||
body: "{\n\t\"links\": {\n\t\t\"org\": \"/api/v2/orgs/0000000000000001\",\n\t\t\"self\": \"/api/v2/orgs/0000000000000001/secrets\"\n\t},\n\t\"secrets\": [\n\t\t\"hello\",\n\t\t\"world\"\n\t]\n}",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "get secrets when there are none",
|
||||
fields: fields{
|
||||
&mock.SecretService{
|
||||
GetSecretKeysFn: func(ctx context.Context, orgID influxdb.ID) ([]string, error) {
|
||||
return []string{}, nil
|
||||
},
|
||||
},
|
||||
},
|
||||
args: args{
|
||||
orgID: 1,
|
||||
},
|
||||
wants: wants{
|
||||
statusCode: http.StatusOK,
|
||||
contentType: "application/json; charset=utf-8",
|
||||
body: "{\n\t\"links\": {\n\t\t\"org\": \"/api/v2/orgs/0000000000000001\",\n\t\t\"self\": \"/api/v2/orgs/0000000000000001/secrets\"\n\t},\n\t\"secrets\": []\n}",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "get secrets when organization has no secret keys",
|
||||
fields: fields{
|
||||
&mock.SecretService{
|
||||
GetSecretKeysFn: func(ctx context.Context, orgID influxdb.ID) ([]string, error) {
|
||||
return []string{}, &influxdb.Error{
|
||||
Code: influxdb.ENotFound,
|
||||
Msg: "organization has no secret keys",
|
||||
}
|
||||
|
||||
},
|
||||
},
|
||||
},
|
||||
args: args{
|
||||
orgID: 1,
|
||||
},
|
||||
wants: wants{
|
||||
statusCode: http.StatusOK,
|
||||
contentType: "application/json; charset=utf-8",
|
||||
body: "{\n\t\"links\": {\n\t\t\"org\": \"/api/v2/orgs/0000000000000001\",\n\t\t\"self\": \"/api/v2/orgs/0000000000000001/secrets\"\n\t},\n\t\"secrets\": []\n}",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
h := NewHandler(zaptest.NewLogger(t), "id", tt.fields.SecretService)
|
||||
router := chi.NewRouter()
|
||||
router.Mount("/api/v2/orgs/{id}/secrets", h)
|
||||
|
||||
u := fmt.Sprintf("http://any.url/api/v2/orgs/%s/secrets", tt.args.orgID)
|
||||
r := httptest.NewRequest("GET", u, nil)
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
router.ServeHTTP(w, r)
|
||||
|
||||
res := w.Result()
|
||||
content := res.Header.Get("Content-Type")
|
||||
body, _ := ioutil.ReadAll(res.Body)
|
||||
|
||||
if res.StatusCode != tt.wants.statusCode {
|
||||
t.Errorf("handleGetSecrets() = %v, want %v", res.StatusCode, tt.wants.statusCode)
|
||||
}
|
||||
if tt.wants.contentType != "" && content != tt.wants.contentType {
|
||||
t.Errorf("handleGetSecrets() = %v, want %v", content, tt.wants.contentType)
|
||||
}
|
||||
if tt.wants.body != "" {
|
||||
if string(body) != tt.wants.body {
|
||||
t.Errorf("%q. handleGetSecrets() invalid body: %q", tt.name, body)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSecretService_handlePatchSecrets(t *testing.T) {
|
||||
type fields struct {
|
||||
SecretService influxdb.SecretService
|
||||
}
|
||||
type args struct {
|
||||
orgID influxdb.ID
|
||||
secrets map[string]string
|
||||
}
|
||||
type wants struct {
|
||||
statusCode int
|
||||
contentType string
|
||||
body string
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
args args
|
||||
wants wants
|
||||
}{
|
||||
{
|
||||
name: "get basic secrets",
|
||||
fields: fields{
|
||||
&mock.SecretService{
|
||||
PatchSecretsFn: func(ctx context.Context, orgID influxdb.ID, s map[string]string) error {
|
||||
return nil
|
||||
},
|
||||
},
|
||||
},
|
||||
args: args{
|
||||
orgID: 1,
|
||||
secrets: map[string]string{
|
||||
"abc": "123",
|
||||
},
|
||||
},
|
||||
wants: wants{
|
||||
statusCode: http.StatusNoContent,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
h := NewHandler(zaptest.NewLogger(t), "id", tt.fields.SecretService)
|
||||
router := chi.NewRouter()
|
||||
router.Mount("/api/v2/orgs/{id}/secrets", h)
|
||||
|
||||
b, err := json.Marshal(tt.args.secrets)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to marshal secrets: %v", err)
|
||||
}
|
||||
|
||||
buf := bytes.NewReader(b)
|
||||
u := fmt.Sprintf("http://any.url/api/v2/orgs/%s/secrets", tt.args.orgID)
|
||||
r := httptest.NewRequest("PATCH", u, buf)
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
router.ServeHTTP(w, r)
|
||||
|
||||
res := w.Result()
|
||||
content := res.Header.Get("Content-Type")
|
||||
body, _ := ioutil.ReadAll(res.Body)
|
||||
|
||||
if res.StatusCode != tt.wants.statusCode {
|
||||
t.Errorf("handlePatchSecrets() = %v, want %v", res.StatusCode, tt.wants.statusCode)
|
||||
}
|
||||
if tt.wants.contentType != "" && content != tt.wants.contentType {
|
||||
t.Errorf("handlePatchSecrets() = %v, want %v", content, tt.wants.contentType)
|
||||
}
|
||||
if tt.wants.body != "" {
|
||||
if string(body) != tt.wants.body {
|
||||
t.Errorf("%q. handlePatchSecrets() invalid body", tt.name)
|
||||
}
|
||||
}
|
||||
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSecretService_handleDeleteSecrets(t *testing.T) {
|
||||
type fields struct {
|
||||
SecretService influxdb.SecretService
|
||||
}
|
||||
type args struct {
|
||||
orgID influxdb.ID
|
||||
secrets []string
|
||||
}
|
||||
type wants struct {
|
||||
statusCode int
|
||||
contentType string
|
||||
body string
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
args args
|
||||
wants wants
|
||||
}{
|
||||
{
|
||||
name: "get basic secrets",
|
||||
fields: fields{
|
||||
&mock.SecretService{
|
||||
DeleteSecretFn: func(ctx context.Context, orgID influxdb.ID, s ...string) error {
|
||||
return nil
|
||||
},
|
||||
},
|
||||
},
|
||||
args: args{
|
||||
orgID: 1,
|
||||
secrets: []string{
|
||||
"abc",
|
||||
},
|
||||
},
|
||||
wants: wants{
|
||||
statusCode: http.StatusNoContent,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
h := NewHandler(zaptest.NewLogger(t), "id", tt.fields.SecretService)
|
||||
router := chi.NewRouter()
|
||||
router.Mount("/api/v2/orgs/{id}/secrets", h)
|
||||
|
||||
b, err := json.Marshal(struct {
|
||||
Secrets []string `json:"secrets"`
|
||||
}{
|
||||
Secrets: tt.args.secrets,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("failed to marshal secrets: %v", err)
|
||||
}
|
||||
|
||||
buf := bytes.NewReader(b)
|
||||
u := fmt.Sprintf("http://any.url/api/v2/orgs/%s/secrets/delete", tt.args.orgID)
|
||||
r := httptest.NewRequest("POST", u, buf)
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
router.ServeHTTP(w, r)
|
||||
|
||||
res := w.Result()
|
||||
content := res.Header.Get("Content-Type")
|
||||
body, _ := ioutil.ReadAll(res.Body)
|
||||
|
||||
if res.StatusCode != tt.wants.statusCode {
|
||||
t.Errorf("handleDeleteSecrets() = %v, want %v", res.StatusCode, tt.wants.statusCode)
|
||||
}
|
||||
if tt.wants.contentType != "" && content != tt.wants.contentType {
|
||||
t.Errorf("handleDeleteSecrets() = %v, want %v", content, tt.wants.contentType)
|
||||
}
|
||||
if tt.wants.body != "" {
|
||||
if string(body) != tt.wants.body {
|
||||
t.Errorf("%q. handleDeleteSecrets() invalid body", tt.name)
|
||||
}
|
||||
}
|
||||
|
||||
})
|
||||
}
|
||||
}
|
|
@ -0,0 +1,100 @@
|
|||
package secret
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/influxdata/influxdb/v2"
|
||||
"github.com/influxdata/influxdb/v2/authorizer"
|
||||
)
|
||||
|
||||
var _ influxdb.SecretService = (*AuthedSvc)(nil)
|
||||
|
||||
// AuthedSvc wraps a influxdb.AuthedSvc and authorizes actions
|
||||
// against it appropriately.
|
||||
type AuthedSvc struct {
|
||||
s influxdb.SecretService
|
||||
}
|
||||
|
||||
// NewAuthedService constructs an instance of an authorizing secret service.
|
||||
func NewAuthedService(s influxdb.SecretService) *AuthedSvc {
|
||||
return &AuthedSvc{
|
||||
s: s,
|
||||
}
|
||||
}
|
||||
|
||||
// LoadSecret checks to see if the authorizer on context has read access to the secret key provided.
|
||||
func (s *AuthedSvc) LoadSecret(ctx context.Context, orgID influxdb.ID, key string) (string, error) {
|
||||
if _, _, err := authorizer.AuthorizeOrgReadResource(ctx, influxdb.SecretsResourceType, orgID); err != nil {
|
||||
return "", err
|
||||
}
|
||||
secret, err := s.s.LoadSecret(ctx, orgID, key)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return secret, nil
|
||||
}
|
||||
|
||||
// GetSecretKeys checks to see if the authorizer on context has read access to all the secrets belonging to orgID.
|
||||
func (s *AuthedSvc) GetSecretKeys(ctx context.Context, orgID influxdb.ID) ([]string, error) {
|
||||
if _, _, err := authorizer.AuthorizeOrgReadResource(ctx, influxdb.SecretsResourceType, orgID); err != nil {
|
||||
return []string{}, err
|
||||
}
|
||||
secrets, err := s.s.GetSecretKeys(ctx, orgID)
|
||||
if err != nil {
|
||||
return []string{}, err
|
||||
}
|
||||
return secrets, nil
|
||||
}
|
||||
|
||||
// PutSecret checks to see if the authorizer on context has write access to the secret key provided.
|
||||
func (s *AuthedSvc) PutSecret(ctx context.Context, orgID influxdb.ID, key string, val string) error {
|
||||
if _, _, err := authorizer.AuthorizeCreate(ctx, influxdb.SecretsResourceType, orgID); err != nil {
|
||||
return err
|
||||
}
|
||||
err := s.s.PutSecret(ctx, orgID, key, val)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// PutSecrets checks to see if the authorizer on context has read and write access to the secret keys provided.
|
||||
func (s *AuthedSvc) PutSecrets(ctx context.Context, orgID influxdb.ID, m map[string]string) error {
|
||||
// PutSecrets operates on intersection between m and keys beloging to orgID.
|
||||
// We need to have read access to those secrets since it deletes the secrets (within the intersection) that have not be overridden.
|
||||
if _, _, err := authorizer.AuthorizeOrgReadResource(ctx, influxdb.SecretsResourceType, orgID); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, _, err := authorizer.AuthorizeOrgWriteResource(ctx, influxdb.SecretsResourceType, orgID); err != nil {
|
||||
return err
|
||||
}
|
||||
err := s.s.PutSecrets(ctx, orgID, m)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// PatchSecrets checks to see if the authorizer on context has write access to the secret keys provided.
|
||||
func (s *AuthedSvc) PatchSecrets(ctx context.Context, orgID influxdb.ID, m map[string]string) error {
|
||||
if _, _, err := authorizer.AuthorizeOrgWriteResource(ctx, influxdb.SecretsResourceType, orgID); err != nil {
|
||||
return err
|
||||
}
|
||||
err := s.s.PatchSecrets(ctx, orgID, m)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeleteSecret checks to see if the authorizer on context has write access to the secret keys provided.
|
||||
func (s *AuthedSvc) DeleteSecret(ctx context.Context, orgID influxdb.ID, keys ...string) error {
|
||||
if _, _, err := authorizer.AuthorizeOrgWriteResource(ctx, influxdb.SecretsResourceType, orgID); err != nil {
|
||||
return err
|
||||
}
|
||||
err := s.s.DeleteSecret(ctx, orgID, keys...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,698 @@
|
|||
package secret_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/influxdata/influxdb/v2"
|
||||
influxdbcontext "github.com/influxdata/influxdb/v2/context"
|
||||
"github.com/influxdata/influxdb/v2/mock"
|
||||
"github.com/influxdata/influxdb/v2/secret"
|
||||
influxdbtesting "github.com/influxdata/influxdb/v2/testing"
|
||||
)
|
||||
|
||||
var secretCmpOptions = cmp.Options{
|
||||
cmp.Comparer(func(x, y []byte) bool {
|
||||
return bytes.Equal(x, y)
|
||||
}),
|
||||
}
|
||||
|
||||
func TestSecretService_LoadSecret(t *testing.T) {
|
||||
type fields struct {
|
||||
SecretService influxdb.SecretService
|
||||
}
|
||||
type args struct {
|
||||
permission influxdb.Permission
|
||||
org influxdb.ID
|
||||
key string
|
||||
}
|
||||
type wants struct {
|
||||
err error
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
args args
|
||||
wants wants
|
||||
}{
|
||||
{
|
||||
name: "authorized to access secret within org",
|
||||
fields: fields{
|
||||
SecretService: &mock.SecretService{
|
||||
LoadSecretFn: func(ctx context.Context, orgID influxdb.ID, k string) (string, error) {
|
||||
if k == "key" {
|
||||
return "val", nil
|
||||
}
|
||||
return "", &influxdb.Error{
|
||||
Code: influxdb.ENotFound,
|
||||
Msg: influxdb.ErrSecretNotFound,
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
args: args{
|
||||
permission: influxdb.Permission{
|
||||
Action: influxdb.ReadAction,
|
||||
Resource: influxdb.Resource{
|
||||
Type: influxdb.SecretsResourceType,
|
||||
OrgID: influxdbtesting.IDPtr(10),
|
||||
},
|
||||
},
|
||||
key: "key",
|
||||
org: influxdb.ID(10),
|
||||
},
|
||||
wants: wants{
|
||||
err: nil,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "cannot access not existing secret",
|
||||
fields: fields{
|
||||
SecretService: &mock.SecretService{
|
||||
LoadSecretFn: func(ctx context.Context, orgID influxdb.ID, k string) (string, error) {
|
||||
if k == "key" {
|
||||
return "val", nil
|
||||
}
|
||||
return "", &influxdb.Error{
|
||||
Code: influxdb.ENotFound,
|
||||
Msg: influxdb.ErrSecretNotFound,
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
args: args{
|
||||
permission: influxdb.Permission{
|
||||
Action: influxdb.ReadAction,
|
||||
Resource: influxdb.Resource{
|
||||
Type: influxdb.SecretsResourceType,
|
||||
OrgID: influxdbtesting.IDPtr(10),
|
||||
},
|
||||
},
|
||||
key: "not existing",
|
||||
org: influxdb.ID(10),
|
||||
},
|
||||
wants: wants{
|
||||
err: &influxdb.Error{
|
||||
Code: influxdb.ENotFound,
|
||||
Msg: influxdb.ErrSecretNotFound,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "unauthorized to access secret within org",
|
||||
fields: fields{
|
||||
SecretService: &mock.SecretService{
|
||||
LoadSecretFn: func(ctx context.Context, orgID influxdb.ID, k string) (string, error) {
|
||||
if k == "key" {
|
||||
return "val", nil
|
||||
}
|
||||
return "", &influxdb.Error{
|
||||
Code: influxdb.ENotFound,
|
||||
Msg: influxdb.ErrSecretNotFound,
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
args: args{
|
||||
permission: influxdb.Permission{
|
||||
Action: influxdb.ReadAction,
|
||||
Resource: influxdb.Resource{
|
||||
Type: influxdb.SecretsResourceType,
|
||||
ID: influxdbtesting.IDPtr(10),
|
||||
},
|
||||
},
|
||||
org: influxdb.ID(2),
|
||||
key: "key",
|
||||
},
|
||||
wants: wants{
|
||||
err: &influxdb.Error{
|
||||
Msg: "read:orgs/0000000000000002/secrets is unauthorized",
|
||||
Code: influxdb.EUnauthorized,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
s := secret.NewAuthedService(tt.fields.SecretService)
|
||||
|
||||
ctx := context.Background()
|
||||
ctx = influxdbcontext.SetAuthorizer(ctx, mock.NewMockAuthorizer(false, []influxdb.Permission{tt.args.permission}))
|
||||
|
||||
_, err := s.LoadSecret(ctx, tt.args.org, tt.args.key)
|
||||
influxdbtesting.ErrorsEqual(t, err, tt.wants.err)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSecretService_GetSecretKeys(t *testing.T) {
|
||||
type fields struct {
|
||||
SecretService influxdb.SecretService
|
||||
}
|
||||
type args struct {
|
||||
permission influxdb.Permission
|
||||
org influxdb.ID
|
||||
}
|
||||
type wants struct {
|
||||
err error
|
||||
secrets []string
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
args args
|
||||
wants wants
|
||||
}{
|
||||
{
|
||||
name: "authorized to see all secrets within an org",
|
||||
fields: fields{
|
||||
SecretService: &mock.SecretService{
|
||||
GetSecretKeysFn: func(ctx context.Context, orgID influxdb.ID) ([]string, error) {
|
||||
return []string{
|
||||
"0000000000000001secret1",
|
||||
"0000000000000001secret2",
|
||||
"0000000000000001secret3",
|
||||
}, nil
|
||||
},
|
||||
},
|
||||
},
|
||||
args: args{
|
||||
permission: influxdb.Permission{
|
||||
Action: influxdb.ReadAction,
|
||||
Resource: influxdb.Resource{
|
||||
Type: influxdb.SecretsResourceType,
|
||||
OrgID: influxdbtesting.IDPtr(1),
|
||||
},
|
||||
},
|
||||
org: influxdb.ID(1),
|
||||
},
|
||||
wants: wants{
|
||||
secrets: []string{
|
||||
"0000000000000001secret1",
|
||||
"0000000000000001secret2",
|
||||
"0000000000000001secret3",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "unauthorized to see all secrets within an org",
|
||||
fields: fields{
|
||||
SecretService: &mock.SecretService{
|
||||
GetSecretKeysFn: func(ctx context.Context, orgID influxdb.ID) ([]string, error) {
|
||||
return []string{
|
||||
"0000000000000002secret1",
|
||||
"0000000000000002secret2",
|
||||
"0000000000000002secret3",
|
||||
}, nil
|
||||
},
|
||||
},
|
||||
},
|
||||
args: args{
|
||||
permission: influxdb.Permission{
|
||||
Action: influxdb.ReadAction,
|
||||
Resource: influxdb.Resource{
|
||||
Type: influxdb.SecretsResourceType,
|
||||
OrgID: influxdbtesting.IDPtr(1),
|
||||
},
|
||||
},
|
||||
org: influxdb.ID(2),
|
||||
},
|
||||
wants: wants{
|
||||
err: &influxdb.Error{
|
||||
Code: influxdb.EUnauthorized,
|
||||
Msg: "read:orgs/0000000000000002/secrets is unauthorized",
|
||||
},
|
||||
secrets: []string{},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "errors when there are not secret into an org",
|
||||
fields: fields{
|
||||
SecretService: &mock.SecretService{
|
||||
GetSecretKeysFn: func(ctx context.Context, orgID influxdb.ID) ([]string, error) {
|
||||
return []string(nil), &influxdb.Error{
|
||||
Code: influxdb.ENotFound,
|
||||
Msg: "organization has no secret keys",
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
args: args{
|
||||
permission: influxdb.Permission{
|
||||
Action: influxdb.ReadAction,
|
||||
Resource: influxdb.Resource{
|
||||
Type: influxdb.SecretsResourceType,
|
||||
OrgID: influxdbtesting.IDPtr(10),
|
||||
},
|
||||
},
|
||||
org: influxdb.ID(10),
|
||||
},
|
||||
wants: wants{
|
||||
err: &influxdb.Error{
|
||||
Code: influxdb.ENotFound,
|
||||
Msg: "organization has no secret keys",
|
||||
},
|
||||
secrets: []string{},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
s := secret.NewAuthedService(tt.fields.SecretService)
|
||||
|
||||
ctx := context.Background()
|
||||
ctx = influxdbcontext.SetAuthorizer(ctx, mock.NewMockAuthorizer(false, []influxdb.Permission{tt.args.permission}))
|
||||
|
||||
secrets, err := s.GetSecretKeys(ctx, tt.args.org)
|
||||
influxdbtesting.ErrorsEqual(t, err, tt.wants.err)
|
||||
|
||||
if diff := cmp.Diff(secrets, tt.wants.secrets, secretCmpOptions...); diff != "" {
|
||||
t.Errorf("secrets are different -got/+want\ndiff %s", diff)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSecretService_PatchSecrets(t *testing.T) {
|
||||
type fields struct {
|
||||
SecretService influxdb.SecretService
|
||||
}
|
||||
type args struct {
|
||||
org influxdb.ID
|
||||
permissions []influxdb.Permission
|
||||
}
|
||||
type wants struct {
|
||||
err error
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
args args
|
||||
wants wants
|
||||
}{
|
||||
{
|
||||
name: "authorized to patch secrets",
|
||||
fields: fields{
|
||||
SecretService: &mock.SecretService{
|
||||
PatchSecretsFn: func(ctx context.Context, orgID influxdb.ID, m map[string]string) error {
|
||||
return nil
|
||||
},
|
||||
},
|
||||
},
|
||||
args: args{
|
||||
org: influxdb.ID(1),
|
||||
permissions: []influxdb.Permission{
|
||||
{
|
||||
Action: influxdb.WriteAction,
|
||||
Resource: influxdb.Resource{
|
||||
Type: influxdb.SecretsResourceType,
|
||||
OrgID: influxdbtesting.IDPtr(1),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
wants: wants{
|
||||
err: nil,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "unauthorized to update secret",
|
||||
fields: fields{
|
||||
SecretService: &mock.SecretService{
|
||||
PatchSecretsFn: func(ctx context.Context, orgID influxdb.ID, m map[string]string) error {
|
||||
return nil
|
||||
},
|
||||
},
|
||||
},
|
||||
args: args{
|
||||
org: influxdb.ID(1),
|
||||
permissions: []influxdb.Permission{
|
||||
{
|
||||
Action: influxdb.ReadAction,
|
||||
Resource: influxdb.Resource{
|
||||
Type: influxdb.SecretsResourceType,
|
||||
OrgID: influxdbtesting.IDPtr(10),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
wants: wants{
|
||||
err: &influxdb.Error{
|
||||
Msg: "write:orgs/0000000000000001/secrets is unauthorized",
|
||||
Code: influxdb.EUnauthorized,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
s := secret.NewAuthedService(tt.fields.SecretService)
|
||||
|
||||
ctx := context.Background()
|
||||
ctx = influxdbcontext.SetAuthorizer(ctx, mock.NewMockAuthorizer(false, tt.args.permissions))
|
||||
|
||||
patches := make(map[string]string)
|
||||
err := s.PatchSecrets(ctx, tt.args.org, patches)
|
||||
influxdbtesting.ErrorsEqual(t, err, tt.wants.err)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSecretService_DeleteSecret(t *testing.T) {
|
||||
type fields struct {
|
||||
SecretService influxdb.SecretService
|
||||
}
|
||||
type args struct {
|
||||
org influxdb.ID
|
||||
permissions []influxdb.Permission
|
||||
}
|
||||
type wants struct {
|
||||
err error
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
args args
|
||||
wants wants
|
||||
}{
|
||||
{
|
||||
name: "authorized to delete secret",
|
||||
fields: fields{
|
||||
SecretService: &mock.SecretService{
|
||||
DeleteSecretFn: func(ctx context.Context, orgID influxdb.ID, keys ...string) error {
|
||||
return nil
|
||||
},
|
||||
},
|
||||
},
|
||||
args: args{
|
||||
org: influxdb.ID(1),
|
||||
permissions: []influxdb.Permission{
|
||||
{
|
||||
Action: influxdb.WriteAction,
|
||||
Resource: influxdb.Resource{
|
||||
Type: influxdb.SecretsResourceType,
|
||||
OrgID: influxdbtesting.IDPtr(1),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
wants: wants{
|
||||
err: nil,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "unauthorized to delete secret",
|
||||
fields: fields{
|
||||
SecretService: &mock.SecretService{
|
||||
DeleteSecretFn: func(ctx context.Context, orgID influxdb.ID, keys ...string) error {
|
||||
return nil
|
||||
},
|
||||
},
|
||||
},
|
||||
args: args{
|
||||
org: 10,
|
||||
permissions: []influxdb.Permission{
|
||||
{
|
||||
Action: influxdb.ReadAction,
|
||||
Resource: influxdb.Resource{
|
||||
Type: influxdb.SecretsResourceType,
|
||||
OrgID: influxdbtesting.IDPtr(1),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
wants: wants{
|
||||
err: &influxdb.Error{
|
||||
Msg: "write:orgs/000000000000000a/secrets is unauthorized",
|
||||
Code: influxdb.EUnauthorized,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
s := secret.NewAuthedService(tt.fields.SecretService)
|
||||
|
||||
ctx := context.Background()
|
||||
ctx = influxdbcontext.SetAuthorizer(ctx, mock.NewMockAuthorizer(false, tt.args.permissions))
|
||||
|
||||
err := s.DeleteSecret(ctx, tt.args.org)
|
||||
influxdbtesting.ErrorsEqual(t, err, tt.wants.err)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSecretService_PutSecret(t *testing.T) {
|
||||
type fields struct {
|
||||
SecretService influxdb.SecretService
|
||||
}
|
||||
type args struct {
|
||||
permission influxdb.Permission
|
||||
orgID influxdb.ID
|
||||
}
|
||||
type wants struct {
|
||||
err error
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
args args
|
||||
wants wants
|
||||
}{
|
||||
{
|
||||
name: "authorized to put a secret",
|
||||
fields: fields{
|
||||
SecretService: &mock.SecretService{
|
||||
PutSecretFn: func(ctx context.Context, orgID influxdb.ID, key string, val string) error {
|
||||
return nil
|
||||
},
|
||||
},
|
||||
},
|
||||
args: args{
|
||||
orgID: influxdb.ID(10),
|
||||
permission: influxdb.Permission{
|
||||
Action: influxdb.WriteAction,
|
||||
Resource: influxdb.Resource{
|
||||
Type: influxdb.SecretsResourceType,
|
||||
OrgID: influxdbtesting.IDPtr(10),
|
||||
},
|
||||
},
|
||||
},
|
||||
wants: wants{
|
||||
err: nil,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "unauthorized to put a secret",
|
||||
fields: fields{
|
||||
SecretService: &mock.SecretService{
|
||||
PutSecretFn: func(ctx context.Context, orgID influxdb.ID, key string, val string) error {
|
||||
return nil
|
||||
},
|
||||
},
|
||||
},
|
||||
args: args{
|
||||
orgID: 10,
|
||||
permission: influxdb.Permission{
|
||||
Action: influxdb.WriteAction,
|
||||
Resource: influxdb.Resource{
|
||||
Type: influxdb.SecretsResourceType,
|
||||
ID: influxdbtesting.IDPtr(1),
|
||||
},
|
||||
},
|
||||
},
|
||||
wants: wants{
|
||||
err: &influxdb.Error{
|
||||
Msg: "write:orgs/000000000000000a/secrets is unauthorized",
|
||||
Code: influxdb.EUnauthorized,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
s := secret.NewAuthedService(tt.fields.SecretService)
|
||||
|
||||
ctx := context.Background()
|
||||
ctx = influxdbcontext.SetAuthorizer(ctx, mock.NewMockAuthorizer(false, []influxdb.Permission{tt.args.permission}))
|
||||
|
||||
err := s.PutSecret(ctx, tt.args.orgID, "", "")
|
||||
influxdbtesting.ErrorsEqual(t, err, tt.wants.err)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSecretService_PutSecrets(t *testing.T) {
|
||||
type fields struct {
|
||||
SecretService influxdb.SecretService
|
||||
}
|
||||
type args struct {
|
||||
permissions []influxdb.Permission
|
||||
orgID influxdb.ID
|
||||
}
|
||||
type wants struct {
|
||||
err error
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
args args
|
||||
wants wants
|
||||
}{
|
||||
{
|
||||
name: "authorized to put secrets",
|
||||
fields: fields{
|
||||
SecretService: &mock.SecretService{
|
||||
PutSecretsFn: func(ctx context.Context, orgID influxdb.ID, m map[string]string) error {
|
||||
return nil
|
||||
},
|
||||
},
|
||||
},
|
||||
args: args{
|
||||
orgID: influxdb.ID(10),
|
||||
permissions: []influxdb.Permission{
|
||||
{
|
||||
Action: influxdb.WriteAction,
|
||||
Resource: influxdb.Resource{
|
||||
Type: influxdb.SecretsResourceType,
|
||||
OrgID: influxdbtesting.IDPtr(10),
|
||||
},
|
||||
},
|
||||
{
|
||||
Action: influxdb.ReadAction,
|
||||
Resource: influxdb.Resource{
|
||||
Type: influxdb.SecretsResourceType,
|
||||
OrgID: influxdbtesting.IDPtr(10),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
wants: wants{
|
||||
err: nil,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "unauthorized to put secrets",
|
||||
fields: fields{
|
||||
SecretService: &mock.SecretService{
|
||||
PutSecretsFn: func(ctx context.Context, orgID influxdb.ID, m map[string]string) error {
|
||||
return nil
|
||||
},
|
||||
},
|
||||
},
|
||||
args: args{
|
||||
orgID: influxdb.ID(2),
|
||||
permissions: []influxdb.Permission{
|
||||
{
|
||||
Action: influxdb.WriteAction,
|
||||
Resource: influxdb.Resource{
|
||||
Type: influxdb.SecretsResourceType,
|
||||
OrgID: influxdbtesting.IDPtr(1),
|
||||
},
|
||||
},
|
||||
{
|
||||
Action: influxdb.ReadAction,
|
||||
Resource: influxdb.Resource{
|
||||
Type: influxdb.SecretsResourceType,
|
||||
OrgID: influxdbtesting.IDPtr(2),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
wants: wants{
|
||||
err: &influxdb.Error{
|
||||
Msg: "write:orgs/0000000000000002/secrets is unauthorized",
|
||||
Code: influxdb.EUnauthorized,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "unauthorized to put secrets without read access to their org",
|
||||
fields: fields{
|
||||
SecretService: &mock.SecretService{
|
||||
PutSecretFn: func(ctx context.Context, orgID influxdb.ID, key string, val string) error {
|
||||
return nil
|
||||
},
|
||||
PutSecretsFn: func(ctx context.Context, orgID influxdb.ID, m map[string]string) error {
|
||||
return nil
|
||||
},
|
||||
},
|
||||
},
|
||||
args: args{
|
||||
orgID: 10,
|
||||
permissions: []influxdb.Permission{
|
||||
{
|
||||
Action: influxdb.WriteAction,
|
||||
Resource: influxdb.Resource{
|
||||
Type: influxdb.SecretsResourceType,
|
||||
OrgID: influxdbtesting.IDPtr(10),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
wants: wants{
|
||||
err: &influxdb.Error{
|
||||
Msg: "read:orgs/000000000000000a/secrets is unauthorized",
|
||||
Code: influxdb.EUnauthorized,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "unauthorized to put secrets without write access to their org",
|
||||
fields: fields{
|
||||
SecretService: &mock.SecretService{
|
||||
PutSecretFn: func(ctx context.Context, orgID influxdb.ID, key string, val string) error {
|
||||
return nil
|
||||
},
|
||||
PutSecretsFn: func(ctx context.Context, orgID influxdb.ID, m map[string]string) error {
|
||||
return nil
|
||||
},
|
||||
},
|
||||
},
|
||||
args: args{
|
||||
orgID: 10,
|
||||
permissions: []influxdb.Permission{
|
||||
{
|
||||
Action: influxdb.ReadAction,
|
||||
Resource: influxdb.Resource{
|
||||
Type: influxdb.SecretsResourceType,
|
||||
OrgID: influxdbtesting.IDPtr(10),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
wants: wants{
|
||||
err: &influxdb.Error{
|
||||
Msg: "write:orgs/000000000000000a/secrets is unauthorized",
|
||||
Code: influxdb.EUnauthorized,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
s := secret.NewAuthedService(tt.fields.SecretService)
|
||||
|
||||
ctx := context.Background()
|
||||
ctx = influxdbcontext.SetAuthorizer(ctx, mock.NewMockAuthorizer(false, tt.args.permissions))
|
||||
|
||||
secrets := make(map[string]string)
|
||||
err := s.PutSecrets(ctx, tt.args.orgID, secrets)
|
||||
influxdbtesting.ErrorsEqual(t, err, tt.wants.err)
|
||||
})
|
||||
}
|
||||
}
|
|
@ -0,0 +1,109 @@
|
|||
package secret
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/influxdata/influxdb/v2"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
// Logger is a logger service middleware for secrets
|
||||
type Logger struct {
|
||||
logger *zap.Logger
|
||||
secretService influxdb.SecretService
|
||||
}
|
||||
|
||||
var _ influxdb.SecretService = (*Logger)(nil)
|
||||
|
||||
// NewLogger returns a logging service middleware for the User Service.
|
||||
func NewLogger(log *zap.Logger, s influxdb.SecretService) *Logger {
|
||||
return &Logger{
|
||||
logger: log,
|
||||
secretService: s,
|
||||
}
|
||||
}
|
||||
|
||||
// LoadSecret retrieves the secret value v found at key k for organization orgID.
|
||||
func (l *Logger) LoadSecret(ctx context.Context, orgID influxdb.ID, key string) (str string, err error) {
|
||||
defer func(start time.Time) {
|
||||
dur := zap.Duration("took", time.Since(start))
|
||||
if err != nil {
|
||||
l.logger.Debug("failed to load secret", zap.Error(err), dur)
|
||||
return
|
||||
}
|
||||
l.logger.Debug("secret load", dur)
|
||||
}(time.Now())
|
||||
return l.secretService.LoadSecret(ctx, orgID, key)
|
||||
|
||||
}
|
||||
|
||||
// GetSecretKeys retrieves all secret keys that are stored for the organization orgID.
|
||||
func (l *Logger) GetSecretKeys(ctx context.Context, orgID influxdb.ID) (strs []string, err error) {
|
||||
defer func(start time.Time) {
|
||||
dur := zap.Duration("took", time.Since(start))
|
||||
if err != nil {
|
||||
l.logger.Debug("failed to get secret keys", zap.Error(err), dur)
|
||||
return
|
||||
}
|
||||
l.logger.Debug("secret get keys", dur)
|
||||
}(time.Now())
|
||||
return l.secretService.GetSecretKeys(ctx, orgID)
|
||||
|
||||
}
|
||||
|
||||
// PutSecret stores the secret pair (k,v) for the organization orgID.
|
||||
func (l *Logger) PutSecret(ctx context.Context, orgID influxdb.ID, key string, val string) (err error) {
|
||||
defer func(start time.Time) {
|
||||
dur := zap.Duration("took", time.Since(start))
|
||||
if err != nil {
|
||||
l.logger.Debug("failed to put secret", zap.Error(err), dur)
|
||||
return
|
||||
}
|
||||
l.logger.Debug("secret put", dur)
|
||||
}(time.Now())
|
||||
return l.secretService.PutSecret(ctx, orgID, key, val)
|
||||
|
||||
}
|
||||
|
||||
// PutSecrets puts all provided secrets and overwrites any previous values.
|
||||
func (l *Logger) PutSecrets(ctx context.Context, orgID influxdb.ID, m map[string]string) (err error) {
|
||||
defer func(start time.Time) {
|
||||
dur := zap.Duration("took", time.Since(start))
|
||||
if err != nil {
|
||||
l.logger.Debug("failed to put secrets", zap.Error(err), dur)
|
||||
return
|
||||
}
|
||||
l.logger.Debug("secret puts", dur)
|
||||
}(time.Now())
|
||||
return l.secretService.PutSecrets(ctx, orgID, m)
|
||||
|
||||
}
|
||||
|
||||
// PatchSecrets patches all provided secrets and updates any previous values.
|
||||
func (l *Logger) PatchSecrets(ctx context.Context, orgID influxdb.ID, m map[string]string) (err error) {
|
||||
defer func(start time.Time) {
|
||||
dur := zap.Duration("took", time.Since(start))
|
||||
if err != nil {
|
||||
l.logger.Debug("failed to patch secret", zap.Error(err), dur)
|
||||
return
|
||||
}
|
||||
l.logger.Debug("secret patch", dur)
|
||||
}(time.Now())
|
||||
return l.secretService.PatchSecrets(ctx, orgID, m)
|
||||
|
||||
}
|
||||
|
||||
// DeleteSecret removes a single secret from the secret store.
|
||||
func (l *Logger) DeleteSecret(ctx context.Context, orgID influxdb.ID, keys ...string) (err error) {
|
||||
defer func(start time.Time) {
|
||||
dur := zap.Duration("took", time.Since(start))
|
||||
if err != nil {
|
||||
l.logger.Debug("failed to delete secret", zap.Error(err), dur)
|
||||
return
|
||||
}
|
||||
l.logger.Debug("secret delete", dur)
|
||||
}(time.Now())
|
||||
return l.secretService.DeleteSecret(ctx, orgID, keys...)
|
||||
|
||||
}
|
|
@ -0,0 +1,69 @@
|
|||
package secret
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/influxdata/influxdb/v2"
|
||||
"github.com/influxdata/influxdb/v2/kit/metric"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
)
|
||||
|
||||
// SecreteService is a metrics middleware system for the secret service
|
||||
type SecreteService struct {
|
||||
// RED metrics
|
||||
rec *metric.REDClient
|
||||
|
||||
secretSvc influxdb.SecretService
|
||||
}
|
||||
|
||||
var _ influxdb.SecretService = (*SecreteService)(nil)
|
||||
|
||||
// NewMetricService creates a new secret metrics middleware
|
||||
func NewMetricService(reg prometheus.Registerer, s influxdb.SecretService) *SecreteService {
|
||||
return &SecreteService{
|
||||
rec: metric.New(reg, "secret"),
|
||||
secretSvc: s,
|
||||
}
|
||||
}
|
||||
|
||||
// LoadSecret retrieves the secret value v found at key k for organization orgID.
|
||||
func (ms *SecreteService) LoadSecret(ctx context.Context, orgID influxdb.ID, key string) (string, error) {
|
||||
rec := ms.rec.Record("load_secret")
|
||||
secret, err := ms.secretSvc.LoadSecret(ctx, orgID, key)
|
||||
return secret, rec(err)
|
||||
}
|
||||
|
||||
// GetSecretKeys retrieves all secret keys that are stored for the organization orgID.
|
||||
func (ms *SecreteService) GetSecretKeys(ctx context.Context, orgID influxdb.ID) ([]string, error) {
|
||||
rec := ms.rec.Record("get_secret_keys")
|
||||
secrets, err := ms.secretSvc.GetSecretKeys(ctx, orgID)
|
||||
return secrets, rec(err)
|
||||
}
|
||||
|
||||
// PutSecret stores the secret pair (k,v) for the organization orgID.
|
||||
func (ms *SecreteService) PutSecret(ctx context.Context, orgID influxdb.ID, key string, val string) error {
|
||||
rec := ms.rec.Record("put_secret")
|
||||
err := ms.secretSvc.PutSecret(ctx, orgID, key, val)
|
||||
return rec(err)
|
||||
}
|
||||
|
||||
// PutSecrets puts all provided secrets and overwrites any previous values.
|
||||
func (ms *SecreteService) PutSecrets(ctx context.Context, orgID influxdb.ID, m map[string]string) error {
|
||||
rec := ms.rec.Record("put_secrets")
|
||||
err := ms.secretSvc.PutSecrets(ctx, orgID, m)
|
||||
return rec(err)
|
||||
}
|
||||
|
||||
// PatchSecrets patches all provided secrets and updates any previous values.
|
||||
func (ms *SecreteService) PatchSecrets(ctx context.Context, orgID influxdb.ID, m map[string]string) error {
|
||||
rec := ms.rec.Record("patch_secrets")
|
||||
err := ms.secretSvc.PatchSecrets(ctx, orgID, m)
|
||||
return rec(err)
|
||||
}
|
||||
|
||||
// DeleteSecret removes a single secret from the secret store.
|
||||
func (ms *SecreteService) DeleteSecret(ctx context.Context, orgID influxdb.ID, keys ...string) error {
|
||||
rec := ms.rec.Record("delete_secret")
|
||||
err := ms.secretSvc.DeleteSecret(ctx, orgID, keys...)
|
||||
return rec(err)
|
||||
}
|
|
@ -0,0 +1,89 @@
|
|||
package secret
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/influxdata/influxdb/v2"
|
||||
"github.com/influxdata/influxdb/v2/kv"
|
||||
)
|
||||
|
||||
type Service struct {
|
||||
s *Storage
|
||||
}
|
||||
|
||||
// NewService creates a new service implementaiton for secrets
|
||||
func NewService(s *Storage) *Service {
|
||||
return &Service{s}
|
||||
}
|
||||
|
||||
// LoadSecret retrieves the secret value v found at key k for organization orgID.
|
||||
func (s *Service) LoadSecret(ctx context.Context, orgID influxdb.ID, k string) (string, error) {
|
||||
var v string
|
||||
err := s.s.View(ctx, func(tx kv.Tx) error {
|
||||
var err error
|
||||
v, err = s.s.GetSecret(ctx, tx, orgID, k)
|
||||
return err
|
||||
})
|
||||
return v, err
|
||||
}
|
||||
|
||||
// GetSecretKeys retrieves all secret keys that are stored for the organization orgID.
|
||||
func (s *Service) GetSecretKeys(ctx context.Context, orgID influxdb.ID) ([]string, error) {
|
||||
var v []string
|
||||
err := s.s.View(ctx, func(tx kv.Tx) error {
|
||||
var err error
|
||||
v, err = s.s.ListSecret(ctx, tx, orgID)
|
||||
return err
|
||||
})
|
||||
return v, err
|
||||
}
|
||||
|
||||
// PutSecret stores the secret pair (k,v) for the organization orgID.
|
||||
func (s *Service) PutSecret(ctx context.Context, orgID influxdb.ID, k, v string) error {
|
||||
err := s.s.Update(ctx, func(tx kv.Tx) error {
|
||||
return s.s.PutSecret(ctx, tx, orgID, k, v)
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
// PutSecrets puts all provided secrets and overwrites any previous values.
|
||||
func (s *Service) PutSecrets(ctx context.Context, orgID influxdb.ID, m map[string]string) error {
|
||||
// put secretes expects to replace all existing secretes
|
||||
keys, err := s.GetSecretKeys(ctx, orgID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.DeleteSecret(ctx, orgID, keys...); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return s.PatchSecrets(ctx, orgID, m)
|
||||
}
|
||||
|
||||
// PatchSecrets patches all provided secrets and updates any previous values.
|
||||
func (s *Service) PatchSecrets(ctx context.Context, orgID influxdb.ID, m map[string]string) error {
|
||||
err := s.s.Update(ctx, func(tx kv.Tx) error {
|
||||
for k, v := range m {
|
||||
err := s.s.PutSecret(ctx, tx, orgID, k, v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
// DeleteSecret removes a single secret from the secret store.
|
||||
func (s *Service) DeleteSecret(ctx context.Context, orgID influxdb.ID, ks ...string) error {
|
||||
err := s.s.Update(ctx, func(tx kv.Tx) error {
|
||||
for _, k := range ks {
|
||||
err := s.s.DeleteSecret(ctx, tx, orgID, k)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
return err
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
package secret_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/influxdata/influxdb/v2"
|
||||
"github.com/influxdata/influxdb/v2/inmem"
|
||||
"github.com/influxdata/influxdb/v2/secret"
|
||||
influxdbtesting "github.com/influxdata/influxdb/v2/testing"
|
||||
)
|
||||
|
||||
func TestBoltSecretService(t *testing.T) {
|
||||
influxdbtesting.SecretService(initSvc, t)
|
||||
}
|
||||
|
||||
func initSvc(f influxdbtesting.SecretServiceFields, t *testing.T) (influxdb.SecretService, func()) {
|
||||
s := inmem.NewKVStore()
|
||||
|
||||
storage, err := secret.NewStore(s)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
svc := secret.NewService(storage)
|
||||
|
||||
for _, s := range f.Secrets {
|
||||
if err := svc.PutSecrets(context.Background(), s.OrganizationID, s.Env); err != nil {
|
||||
t.Fatalf("failed to populate users")
|
||||
}
|
||||
}
|
||||
|
||||
return svc, func() {}
|
||||
}
|
|
@ -0,0 +1,193 @@
|
|||
package secret
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
|
||||
"github.com/influxdata/influxdb/v2"
|
||||
"github.com/influxdata/influxdb/v2/kv"
|
||||
)
|
||||
|
||||
var secretBucket = []byte("secretsv1")
|
||||
|
||||
// Storage is a store translation layer between the data storage unit and the
|
||||
// service layer.
|
||||
type Storage struct {
|
||||
store kv.Store
|
||||
}
|
||||
|
||||
// NewStore creates a new storage system
|
||||
func NewStore(s kv.Store) (*Storage, error) {
|
||||
err := s.Update(context.Background(), func(tx kv.Tx) error {
|
||||
if _, err := tx.Bucket(secretBucket); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &Storage{s}, nil
|
||||
}
|
||||
|
||||
func (s *Storage) View(ctx context.Context, fn func(kv.Tx) error) error {
|
||||
return s.store.View(ctx, fn)
|
||||
}
|
||||
|
||||
func (s *Storage) Update(ctx context.Context, fn func(kv.Tx) error) error {
|
||||
return s.store.Update(ctx, fn)
|
||||
}
|
||||
|
||||
// GetSecret Returns the value of a secret
|
||||
func (s *Storage) GetSecret(ctx context.Context, tx kv.Tx, orgID influxdb.ID, k string) (string, error) {
|
||||
key, err := encodeSecretKey(orgID, k)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
b, err := tx.Bucket(secretBucket)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
val, err := b.Get(key)
|
||||
if kv.IsNotFound(err) {
|
||||
return "", &influxdb.Error{
|
||||
Code: influxdb.ENotFound,
|
||||
Msg: influxdb.ErrSecretNotFound,
|
||||
}
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
v, err := decodeSecretValue(val)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return v, nil
|
||||
}
|
||||
|
||||
// ListSecrets returns a list of secret keys
|
||||
func (s *Storage) ListSecret(ctx context.Context, tx kv.Tx, orgID influxdb.ID) ([]string, error) {
|
||||
b, err := tx.Bucket(secretBucket)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
prefix, err := orgID.Encode()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cur, err := b.ForwardCursor(prefix, kv.WithCursorPrefix(prefix))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
keys := []string{}
|
||||
|
||||
err = kv.WalkCursor(ctx, cur, func(k, v []byte) error {
|
||||
id, key, err := decodeSecretKey(k)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if id != orgID {
|
||||
// We've reached the end of the keyspace for the provided orgID
|
||||
return nil
|
||||
}
|
||||
|
||||
keys = append(keys, key)
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return keys, nil
|
||||
}
|
||||
|
||||
// PutSecret sets a secret in the db.
|
||||
func (s *Storage) PutSecret(ctx context.Context, tx kv.Tx, orgID influxdb.ID, k, v string) error {
|
||||
key, err := encodeSecretKey(orgID, k)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
val := encodeSecretValue(v)
|
||||
|
||||
b, err := tx.Bucket(secretBucket)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := b.Put(key, val); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeleteSecret removes a secret for the db
|
||||
func (s *Storage) DeleteSecret(ctx context.Context, tx kv.Tx, orgID influxdb.ID, k string) error {
|
||||
key, err := encodeSecretKey(orgID, k)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
b, err := tx.Bucket(secretBucket)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return b.Delete(key)
|
||||
}
|
||||
|
||||
func encodeSecretKey(orgID influxdb.ID, k string) ([]byte, error) {
|
||||
buf, err := orgID.Encode()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
key := make([]byte, 0, influxdb.IDLength+len(k))
|
||||
key = append(key, buf...)
|
||||
key = append(key, k...)
|
||||
|
||||
return key, nil
|
||||
}
|
||||
|
||||
func decodeSecretKey(key []byte) (influxdb.ID, string, error) {
|
||||
if len(key) < influxdb.IDLength {
|
||||
// This should not happen.
|
||||
return influxdb.InvalidID(), "", errors.New("provided key is too short to contain an ID (please report this error)")
|
||||
}
|
||||
|
||||
var id influxdb.ID
|
||||
if err := id.Decode(key[:influxdb.IDLength]); err != nil {
|
||||
return influxdb.InvalidID(), "", err
|
||||
}
|
||||
|
||||
k := string(key[influxdb.IDLength:])
|
||||
|
||||
return id, k, nil
|
||||
}
|
||||
|
||||
func decodeSecretValue(val []byte) (string, error) {
|
||||
// store the secret value base64 encoded so that it's marginally better than plaintext
|
||||
v, err := base64.StdEncoding.DecodeString(string(val))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return string(v), nil
|
||||
}
|
||||
|
||||
func encodeSecretValue(v string) []byte {
|
||||
val := make([]byte, base64.StdEncoding.EncodedLen(len(v)))
|
||||
base64.StdEncoding.Encode(val, []byte(v))
|
||||
return val
|
||||
}
|
Loading…
Reference in New Issue