Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add secret service interface #164

Merged
merged 1 commit into from
Aug 12, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .github/testing/core.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -67,3 +67,9 @@ items:
request:
api: /GetVersion
method: POST
- name: secrets
request:
api: /GetSecrets
method: POST
expect:
statusCode: 500
5 changes: 5 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,11 @@ grpc-gw:
--grpc-gateway_opt paths=source_relative \
--grpc-gateway_opt generate_unbound_methods=true \
pkg/server/server.proto
grpc-java:
protoc --plugin=protoc-gen-grpc-java \
--grpc-java_out=bin --proto_path=. \
pkg/server/server.proto \
pkg/testing/remote/loader.proto
grpc-js:
protoc -I=pkg/server server.proto \
--js_out=import_style=commonjs:bin \
Expand Down
14 changes: 13 additions & 1 deletion cmd/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (
_ "embed"

"github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
template "github.com/linuxsuren/api-testing/pkg/render"
"github.com/linuxsuren/api-testing/pkg/server"
"github.com/linuxsuren/api-testing/pkg/testing"
"github.com/linuxsuren/api-testing/pkg/testing/remote"
Expand Down Expand Up @@ -44,6 +45,7 @@ func createServerCmd(execer fakeruntime.Execer, gRPCServer gRPCServer, httpServe
flags.StringArrayVarP(&opt.localStorage, "local-storage", "", []string{"*.yaml"}, "The local storage path")
flags.StringVarP(&opt.consolePath, "console-path", "", "", "The path of the console")
flags.StringVarP(&opt.configDir, "config-dir", "", "$HOME/.config/atest", "The config directory")
flags.StringVarP(&opt.secretServer, "secret-server", "", "", "The secret server URL")
return
}

Expand All @@ -57,6 +59,7 @@ type serverOption struct {
printProto bool
localStorage []string
consolePath string
secretServer string
configDir string
}

Expand Down Expand Up @@ -95,7 +98,16 @@ func (o *serverOption) runE(cmd *cobra.Command, args []string) (err error) {
}
}

removeServer := server.NewRemoteServer(loader, remote.NewGRPCloaderFromStore(), o.configDir)
var secretServer remote.SecretServiceServer
if o.secretServer != "" {
if secretServer, err = remote.NewGRPCSecretFrom(o.secretServer); err != nil {
return
}

template.SetSecretGetter(remote.NewGRPCSecretGetter(secretServer))
}

removeServer := server.NewRemoteServer(loader, remote.NewGRPCloaderFromStore(), secretServer, o.configDir)
s := o.gRPCServer
go func() {
if gRPCServer, ok := s.(reflection.GRPCServer); ok {
Expand Down
3 changes: 2 additions & 1 deletion cmd/server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@ func TestPrintProto(t *testing.T) {
},
}, {
name: "random port",
args: []string{"server", "-p=0", "--http-port=0", "--local-storage=./*"},
args: []string{"server", "-p=0", "--http-port=0",
"--local-storage=./*", "--secret-server=localhost:7073"},
verify: func(t *testing.T, buf *bytes.Buffer, err error) {
assert.Nil(t, err)
},
Expand Down
20 changes: 16 additions & 4 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,16 @@ Currently, it supports the following kinds of services:

* Operate System services
* Linux, and Darwin
* Podman, and Docker
* [Podman](https://github.com/containers/podman), and Docker

Please see the following example usage:

```shell
atest service start -m podman --version master
sudo atest service install -m podman --version master
```

the default web server port is `8080`. So you can visit it via: http://localhost:8080

## Run in k3s

```shell
Expand All @@ -51,6 +53,7 @@ There are multiple storage backends supported. See the status from the list:
| Name | Status |
|---|---|
| Local Storage | Ready |
| S3 | Ready |
| ORM DataBase | Developing |
| Etcd DataBase | Developing |

Expand Down Expand Up @@ -108,7 +111,7 @@ podman run -p 7071:7071 \
ghcr.io/linuxsuren/api-testing:master atest-store-orm
```

### ORM S3 Storage
### S3 Storage
You can use a S3 compatible storage as the storage backend.

```shell
Expand All @@ -134,8 +137,17 @@ See also the expected configuration below:
region: cn
```

## Secret Server
You can put the sensitive information into a secret server. For example, [Vault](https://www.github.com/hashicorp/vault).

Connect to [a vault extension](https://github.com/LinuxSuRen/api-testing-secret-extension) via flag: `--secret-server`. Such as:

```shell
atest server --secret-server localhost:7073
```

## Extensions
Developers could have a storage extension. Implement a gRPC server according to [loader.proto](../pkg/testing/remote/loader.proto) is required.
Developers could have storage, secret extensions. Implement a gRPC server according to [loader.proto](../pkg/testing/remote/loader.proto) is required.

## Official Images
You could find the official images from both [Docker Hub](https://hub.docker.com/r/linuxsuren/api-testing) and [GitHub Images](https://github.com/users/LinuxSuRen/packages/container/package/api-testing). See the image path:
Expand Down
17 changes: 17 additions & 0 deletions pkg/render/secret.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package render

import (
"github.com/linuxsuren/api-testing/pkg/secret"
)

type nonSecretGetter struct {
value string
err error
}

func (n *nonSecretGetter) GetSecret(name string) (s secret.Secret, err error) {
s.Value = n.value
s.Name = name
err = n.err
return
}
23 changes: 22 additions & 1 deletion pkg/render/template.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,22 @@ import (
"strings"

"github.com/Masterminds/sprig/v3"
"github.com/linuxsuren/api-testing/pkg/secret"
"github.com/linuxsuren/api-testing/pkg/util"
)

var secretGetter secret.SecretGetter

// SetSecretGetter set the secret getter
func SetSecretGetter(getter secret.SecretGetter) {
if getter == nil {
getter = &nonSecretGetter{
err: fmt.Errorf("no secret server"),
}
}
secretGetter = getter
}

// Render render then return the result
func Render(name, text string, ctx interface{}) (result string, err error) {
var tpl *template.Template
Expand Down Expand Up @@ -69,6 +82,15 @@ var advancedFuncs = []AdvancedFunc{{
writeWithContext(ctx, `{{randAlpha `+fields+`}}`)
return
},
}, {
FuncName: "secretValue",
Func: func(name string) string {
val, err := secretGetter.GetSecret(name)
if err == nil {
return val.Value
}
return err.Error()
},
}}

// GetAdvancedFuncs returns all the advanced functions
Expand Down Expand Up @@ -112,7 +134,6 @@ func writeWithContext(ctx context.Context, text string) {
if ok && writer != nil {
_, _ = writer.Write([]byte(text))
}
return
}

// AdvancedFunc represents an advanced function
Expand Down
15 changes: 15 additions & 0 deletions pkg/render/template_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -154,3 +154,18 @@ func TestGenerateJSONString(t *testing.T) {
result := generateJSONString([]string{"name", "age"})
assert.Equal(t, `{"age":"random","name":"random"}`, result)
}

func TestSecret(t *testing.T) {
SetSecretGetter(nil)
result, err := Render("", `{{secretValue "pass"}}`, nil)
assert.NoError(t, err)
assert.Equal(t, "no secret server", result)

expected := "password"
SetSecretGetter(&nonSecretGetter{
value: expected,
})
result, err = Render("", `{{secretValue "pass"}}`, nil)
assert.NoError(t, err)
assert.Equal(t, expected, result)
}
30 changes: 30 additions & 0 deletions pkg/secret/types.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/*
MIT License
Copyright (c) 2023 API Testing Authors.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/

package secret

type Secret struct {
Name string
Value string
}

type SecretGetter interface {
GetSecret(name string) (secret Secret, err error)
}
58 changes: 57 additions & 1 deletion pkg/server/remote_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,14 +31,56 @@ type server struct {
loader testing.Writer
storeWriterFactory testing.StoreWriterFactory
configDir string

secretServer SecretServiceServer
}

type SecretServiceServer interface {
GetSecrets(context.Context, *Empty) (*Secrets, error)
CreateSecret(context.Context, *Secret) (*CommonResult, error)
DeleteSecret(context.Context, *Secret) (*CommonResult, error)
UpdateSecret(context.Context, *Secret) (*CommonResult, error)
}

type SecertServiceGetable interface {
GetSecret(context.Context, *Secret) (*Secret, error)
}

type fakeSecretServer struct{}

var errNoSecretService = errors.New("no secret service found")

func (f *fakeSecretServer) GetSecrets(ctx context.Context, in *Empty) (reply *Secrets, err error) {
err = errNoSecretService
return
}

func (f *fakeSecretServer) CreateSecret(ctx context.Context, in *Secret) (reply *CommonResult, err error) {
err = errNoSecretService
return
}

func (f *fakeSecretServer) DeleteSecret(ctx context.Context, in *Secret) (reply *CommonResult, err error) {
err = errNoSecretService
return
}

func (f *fakeSecretServer) UpdateSecret(ctx context.Context, in *Secret) (reply *CommonResult, err error) {
err = errNoSecretService
return
}

// NewRemoteServer creates a remote server instance
func NewRemoteServer(loader testing.Writer, storeWriterFactory testing.StoreWriterFactory, configDir string) RunnerServer {
func NewRemoteServer(loader testing.Writer, storeWriterFactory testing.StoreWriterFactory, secretServer SecretServiceServer, configDir string) RunnerServer {
if secretServer == nil {
secretServer = &fakeSecretServer{}
}

return &server{
loader: loader,
storeWriterFactory: storeWriterFactory,
configDir: configDir,
secretServer: secretServer,
}
}

Expand Down Expand Up @@ -645,6 +687,20 @@ func (s *server) VerifyStore(ctx context.Context, in *SimpleQuery) (reply *Commo
return
}

// secret related interfaces
func (s *server) GetSecrets(ctx context.Context, in *Empty) (reply *Secrets, err error) {
return s.secretServer.GetSecrets(ctx, in)
}
func (s *server) CreateSecret(ctx context.Context, in *Secret) (reply *CommonResult, err error) {
return s.secretServer.CreateSecret(ctx, in)
}
func (s *server) DeleteSecret(ctx context.Context, in *Secret) (reply *CommonResult, err error) {
return s.secretServer.DeleteSecret(ctx, in)
}
func (s *server) UpdateSecret(ctx context.Context, in *Secret) (reply *CommonResult, err error) {
return s.secretServer.UpdateSecret(ctx, in)
}

func (s *server) getLoaderByStoreName(storeName string) (loader testing.Writer, err error) {
var store *testing.Store
store, err = testing.NewStoreFactory(s.configDir).GetStore(storeName)
Expand Down
Loading
Loading