Skip to content

Commit f51af88

Browse files
authored
Implement Secret storage backend as gRPC server (#644)
- Add Protocol Buffers schema - Generate gRPC client + server code from Protocol Buffers - Implement gRPC server for secret storage backend - Support initially just AWS Secrets Manager. Tested also `dotenv` provider. The `vault` provider needs to be tested - Add tests for the gRPC server with fake secret provider
1 parent f6f487e commit f51af88

20 files changed

+3720
-122
lines changed

Makefile

+6-2
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ all: generate build-all-images test-unit test-lint ## Default: generate all, bui
1717
# Building #
1818
############
1919

20-
APPS = gateway k8s-engine hub-js argo-runner helm-runner cloudsql-runner populator terraform-runner argo-actions gitlab-api-runner
20+
APPS = gateway k8s-engine hub-js argo-runner helm-runner cloudsql-runner populator terraform-runner argo-actions gitlab-api-runner secret-storage-backend
2121
TESTS = e2e
2222
INFRA = json-go-gen graphql-schema-linter jinja2 merger
2323

@@ -148,7 +148,7 @@ image-security-scan: build-all-images ## Build the docker images and check for v
148148
# Generating #
149149
##############
150150

151-
generate: gen-go-api-from-ocf-spec gen-k8s-resources gen-graphql-resources gen-go-source-code gen-docs ## Run all generators
151+
generate: gen-go-api-from-ocf-spec gen-k8s-resources gen-graphql-resources gen-go-source-code gen-docs gen-grpc-resources ## Run all generators
152152
.PHONY: generate
153153

154154
gen-go-api-from-ocf-spec: ## Generate Go code from OCF JSON Schemas
@@ -163,6 +163,10 @@ gen-graphql-resources: ## Generate code from GraphQL schema
163163
./hack/gen-graphql-resources.sh
164164
.PHONY: gen-graphql-resources
165165

166+
gen-grpc-resources: ## Generate gRPC + ProtoBuf Go code for client and server
167+
./hack/gen-grpc-resources.sh
168+
.PHONY: gen-proto-source-code
169+
166170
gen-go-source-code:
167171
go generate -x ./...
168172
.PHONY: gen-go-source-code

cmd/k8s-engine/README.md

+20-20
Original file line numberDiff line numberDiff line change
@@ -41,26 +41,26 @@ query {
4141

4242
## Configuration
4343

44-
| Name | Required | Default | Description |
45-
|---------------------------------|----------|----------------------------------|--------------------------------------------------------------------------------------------------------------|
46-
| APP_ENABLE_LEADER_ELECTION | no | `false` | Enable leader election for Kubernetes controller. This ensures only 1 controller is active at any time point |
47-
| APP_LEADER_ELECTION_NAMESPACE | no | | Set the Kubernetes namespace, in which the leader election ConfigMap is created |
48-
| APP_GRAPHQL_ADDR | no | `:8080` | TCP address the metrics endpoint binds to |
49-
| APP_GRAPHQL_ADDR | no | `8081` | TCP address the metrics endpoint binds to |
50-
| APP_HEALTHZ_ADDR | no | `:8082` | TCP address the health probes endpoint binds to |
51-
| APP_LOGGER_DEV_MODE | no | `false` | Enable development mode logging |
52-
| APP_MAX_CONCURRENT_RECONCILES | no | `1` | Maximum number of concurrent reconcile loops in the controller |
53-
| APP_MAX_RETRY_FOR_FAILED_ACTION | no | `15` | Maximum number of retries for failed Action reconcile process |
54-
| APP_GRAPHQLGATEWAY_ENDPOINT | no | `http://capact-gateway/graphql` | Endpoint of the Capact Gateway |
55-
| APP_GRAPHQLGATEWAY_USERNAME | yes | | Basic auth username used to authenticate at the Capact Gateway |
56-
| APP_GRAPHQLGATEWAY_PASSWORD | yes | | Basic auth password used to authenticate at the Capact Gateway |
57-
| APP_BUILTIN_RUNNER_TIMEOUT | no | `30m` | Set the timeout for the workflow execution of the builtin runners |
58-
| APP_BUILTIN_RUNNER_IMAGE | yes | | Set the image of the builtin runner |
59-
| APP_CLUSTER_POLICY_NAME | no | `capact-engine-cluster-policy` | Name of the ConfigMap with cluster policy |
60-
| APP_CLUSTER_POLICY_NAMESPACE | no | `capact-system` | Namespace of the ConfigMap with cluster policy |
61-
| APP_RENDERER_RENDER_TIMEOUT | no | `10m` | Maximum time for rendering process. Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h". |
62-
| APP_RENDERER_MAX_DEPTH | no | `50` | Maximum number of allowed nested workflows to be processed. |
63-
| KUBECONFIG | no | `~/.kube/config` | Path to kubeconfig file |
44+
| Name | Required | Default | Description |
45+
|---------------------------------|----------|---------------------------------|--------------------------------------------------------------------------------------------------------------|
46+
| APP_ENABLE_LEADER_ELECTION | no | `false` | Enable leader election for Kubernetes controller. This ensures only 1 controller is active at any time point |
47+
| APP_LEADER_ELECTION_NAMESPACE | no | | Set the Kubernetes namespace, in which the leader election ConfigMap is created |
48+
| APP_GRAPHQL_ADDR | no | `:8080` | TCP address the GraphQL endpoint binds to |
49+
| APP_METRICS_ADDR | no | `:8081` | TCP address the metrics endpoint binds to |
50+
| APP_HEALTHZ_ADDR | no | `:8082` | TCP address the health probes endpoint binds to |
51+
| APP_LOGGER_DEV_MODE | no | `false` | Enable development mode logging |
52+
| APP_MAX_CONCURRENT_RECONCILES | no | `1` | Maximum number of concurrent reconcile loops in the controller |
53+
| APP_MAX_RETRY_FOR_FAILED_ACTION | no | `15` | Maximum number of retries for failed Action reconcile process |
54+
| APP_GRAPHQLGATEWAY_ENDPOINT | no | `http://capact-gateway/graphql` | Endpoint of the Capact Gateway |
55+
| APP_GRAPHQLGATEWAY_USERNAME | yes | | Basic auth username used to authenticate at the Capact Gateway |
56+
| APP_GRAPHQLGATEWAY_PASSWORD | yes | | Basic auth password used to authenticate at the Capact Gateway |
57+
| APP_BUILTIN_RUNNER_TIMEOUT | no | `30m` | Set the timeout for the workflow execution of the builtin runners |
58+
| APP_BUILTIN_RUNNER_IMAGE | yes | | Set the image of the builtin runner |
59+
| APP_CLUSTER_POLICY_NAME | no | `capact-engine-cluster-policy` | Name of the ConfigMap with cluster policy |
60+
| APP_CLUSTER_POLICY_NAMESPACE | no | `capact-system` | Namespace of the ConfigMap with cluster policy |
61+
| APP_RENDERER_RENDER_TIMEOUT | no | `10m` | Maximum time for rendering process. Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h". |
62+
| APP_RENDERER_MAX_DEPTH | no | `50` | Maximum number of allowed nested workflows to be processed. |
63+
| KUBECONFIG | no | `~/.kube/config` | Path to kubeconfig file |
6464

6565
## Development
6666

cmd/secret-storage-backend/README.md

+57
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
# Secret Storage Backend
2+
3+
## Overview
4+
5+
Secret Storage Backend is a service which handles multiple secret storages for TypeInstances.
6+
7+
## Prerequisites
8+
9+
- [Go](https://golang.org)
10+
- (Optional - if AWS Secrets Manager provider should be used) an AWS account with **AdministratorAccess** permissions on it
11+
12+
## Usage
13+
14+
### AWS Secrets Manager provider
15+
16+
By default, the Secret Storage Backend has the `aws_secretsmanager` provider enabled.
17+
18+
1. Create AWS security credentials with `SecretsManagerReadWrite` policy.
19+
2. Export environment variables:
20+
21+
```bash
22+
export AWS_ACCESS_KEY_ID="{accessKey}"
23+
export AWS_SECRET_ACCESS_KEY="{secretKey}"
24+
```
25+
3. Run the server:
26+
27+
```bash
28+
APP_LOGGER_DEV_MODE=true go run ./cmd/secret-storage-backend/main.go
29+
```
30+
31+
The server listens to gRPC calls according to the [Storage Backend Protocol Buffers schema](../../pkg/hub/api/grpc/storage_backend.proto).
32+
To perform such calls, you can use e.g. [Insomnia](https://insomnia.rest/) tool.
33+
34+
### Dotenv provider
35+
36+
To run the server with `dotenv` provider enabled, which stores data in files, execute:
37+
38+
```bash
39+
APP_SUPPORTED_PROVIDERS=dotenv,aws_secretsmanager APP_LOGGER_DEV_MODE=true go run ./cmd/secret-storage-backend/main.go
40+
```
41+
42+
> **NOTE:** You can enable multiple providers, separating them by comma, such as: `APP_SUPPORTED_PROVIDERS=aws_secretsmanager,dotenv`.
43+
44+
## Configuration
45+
46+
| Name | Required | Default | Description |
47+
|-------------------------|----------|----------------------|-------------------------------------------------------------------------------------------------------------------------------|
48+
| APP_GRPC_ADDR | no | `:50051` | TCP address the gRPC server binds to. |
49+
| APP_HEALTHZ_ADDR | no | `:8082` | TCP address the health probes endpoint binds to. |
50+
| APP_SUPPORTED_PROVIDERS | no | `aws_secretsmanager` | Supported secret providers separated by `,`. A given provider must be passed in additional parameters of gRPC request inputs. |
51+
| APP_LOGGER_DEV_MODE | no | `false` | Enable development mode logging. |
52+
53+
To configure providers, use environmental variables described in the [Providers](https://github.com/SpectralOps/teller#providers) paragraph for Teller's Readme.
54+
55+
## Development
56+
57+
To read more about development, see the [Development guide](https://capact.io/community/development/development-guide).

cmd/secret-storage-backend/main.go

+104
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
package main
2+
3+
import (
4+
"log"
5+
"net"
6+
7+
"capact.io/capact/internal/healthz"
8+
"capact.io/capact/internal/logger"
9+
secret_storage_backend "capact.io/capact/internal/secret-storage-backend"
10+
"capact.io/capact/pkg/hub/api/grpc/storage_backend"
11+
"github.com/pkg/errors"
12+
tellerpkg "github.com/spectralops/teller/pkg"
13+
tellercore "github.com/spectralops/teller/pkg/core"
14+
"github.com/vrischmann/envconfig"
15+
"go.uber.org/zap"
16+
"golang.org/x/sync/errgroup"
17+
"google.golang.org/grpc"
18+
"sigs.k8s.io/controller-runtime/pkg/manager/signals"
19+
)
20+
21+
// Config holds application related configuration.
22+
type Config struct {
23+
// GRPCAddr is the TCP address the gRPC server binds to.
24+
GRPCAddr string `envconfig:"default=:50051"`
25+
26+
// HealthzAddr is the TCP address the health probes endpoint binds to.
27+
HealthzAddr string `envconfig:"default=:8082"`
28+
29+
// SupportedProviders holds enabled secret providers separated by comma.
30+
SupportedProviders []string `envconfig:"default=aws_secretsmanager"`
31+
32+
Logger logger.Config
33+
}
34+
35+
const appName = "secret-storage-backend"
36+
37+
func main() {
38+
var cfg Config
39+
err := envconfig.InitWithPrefix(&cfg, "APP")
40+
exitOnError(err, "while loading configuration")
41+
42+
ctx := signals.SetupSignalHandler()
43+
44+
// setup logger
45+
unnamedLogger, err := logger.New(cfg.Logger)
46+
exitOnError(err, "while creating zap logger")
47+
48+
logger := unnamedLogger.Named(appName)
49+
50+
// setup servers
51+
parallelServers := new(errgroup.Group)
52+
53+
healthzServer := healthz.NewHTTPServer(logger, cfg.HealthzAddr, appName)
54+
parallelServers.Go(func() error { return healthzServer.Start(ctx) })
55+
56+
providers, err := loadProviders(cfg.SupportedProviders)
57+
exitOnError(err, "while loading providers")
58+
59+
handler := secret_storage_backend.NewHandler(logger, providers)
60+
exitOnError(err, "while creating new handler")
61+
62+
listenCfg := net.ListenConfig{}
63+
listener, err := listenCfg.Listen(ctx, "tcp", cfg.GRPCAddr)
64+
exitOnError(err, "while listening")
65+
66+
srv := grpc.NewServer()
67+
storage_backend.RegisterStorageBackendServer(srv, handler)
68+
69+
go func() {
70+
<-ctx.Done()
71+
logger.Info("Stopping server gracefully")
72+
srv.GracefulStop()
73+
}()
74+
75+
parallelServers.Go(func() error {
76+
logger.Info("Starting TCP server", zap.String("addr", cfg.GRPCAddr))
77+
return srv.Serve(listener)
78+
})
79+
80+
err = parallelServers.Wait()
81+
exitOnError(err, "while waiting for servers to finish gracefully")
82+
}
83+
84+
func exitOnError(err error, context string) {
85+
if err != nil {
86+
log.Fatalf("%s: %v", context, err)
87+
}
88+
}
89+
90+
func loadProviders(providerNames []string) (map[string]tellercore.Provider, error) {
91+
builtInProviders := tellerpkg.BuiltinProviders{}
92+
providersMap := map[string]tellercore.Provider{}
93+
94+
for _, providerName := range providerNames {
95+
provider, err := builtInProviders.GetProvider(providerName)
96+
if err != nil {
97+
return nil, errors.Wrapf(err, "while loading provider %q", provider)
98+
}
99+
100+
providersMap[providerName] = provider
101+
}
102+
103+
return providersMap, nil
104+
}

0 commit comments

Comments
 (0)