Skip to content

Commit 4b0ea1a

Browse files
kaplaneladjondot
authored andcommitted
Support cloudflare worker secrets
1 parent 3e06289 commit 4b0ea1a

8 files changed

+335
-17
lines changed

.teller.example.yml

+18-1
Original file line numberDiff line numberDiff line change
@@ -121,4 +121,21 @@ providers:
121121
# FOO_BAR:
122122
# path: # LastPass item ID
123123
# #field: by default taking password property. in case you want other property un-mark this line and set the LastPass property name
124-
124+
125+
126+
127+
# # Configure via environment variables for integration:
128+
# # CLOUDFLARE_API_KEY: Your Cloudflare api key.
129+
# # CLOUDFLARE_API_EMAIL: Your email associated with the api key.
130+
# # CLOUDFLARE_ACCOUNT_ID: Your account ID.
131+
#
132+
# cloudflare_workers_secrets:
133+
# env_sync:
134+
# source: script-id
135+
# env:
136+
# foo-secret:
137+
# path: foo-secret
138+
# source: script-id
139+
# foo-secret2:
140+
# path: foo-secret
141+
# source: script-id

Makefile

+1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ mocks:
55
mockgen -source pkg/providers/aws_secretsmanager.go -destination pkg/providers/mock_providers/aws_secretsmanager_mock.go
66
mockgen -source pkg/providers/aws_ssm.go -destination pkg/providers/mock_providers/aws_ssm_mock.go
77
mockgen -source pkg/providers/cloudflare_workers_kv.go -destination pkg/providers/mock_providers/cloudflare_workers_kv_mock.go
8+
mockgen -source pkg/providers/cloudflare_workers_secrets.go -destination pkg/providers/mock_providers/cloudflare_workers_secrets_mock.go
89
mockgen -source pkg/providers/consul.go -destination pkg/providers/mock_providers/consul_mock.go
910
mockgen -source pkg/providers/dotenv.go -destination pkg/providers/mock_providers/dotenv_mock.go
1011
mockgen -source pkg/providers/doppler.go -destination pkg/providers/mock_providers/doppler_mock.go

README.md

+44
Original file line numberDiff line numberDiff line change
@@ -876,6 +876,50 @@ providers:
876876
# Accesses the field `SOME_KEY` in the KV namespace and maps it to REMAPPED_KEY.
877877
field: SOME_KEY
878878
```
879+
880+
## Cloudflare Workers Secrets
881+
882+
### Usage:
883+
```sh
884+
$ teller put foo-secret=000000 --providers cloudflare_workers_secrets
885+
$ telelr put foo-secret=123 foo-secret2=456 --providers cloudflare_workers_secrets --sync # take from env_sync for using the same source for multiple secrets
886+
$ telelr delete foo-secret foo-secret2 --providers cloudflare_workers_secrets
887+
```
888+
889+
### Authentication
890+
891+
requires the following environment variables to be set:
892+
893+
`CLOUDFLARE_API_KEY`: Your Cloudflare api key.
894+
`CLOUDFLARE_API_EMAIL`: Your email associated with the api key.
895+
`CLOUDFLARE_ACCOUNT_ID`: Your account ID.
896+
897+
### Features
898+
899+
* Sync - `yes`
900+
* Mapping - `yes`
901+
* Modes - `write`
902+
* Key format
903+
* `source` - The script name
904+
* `path` - Name of the secret, when using `--sync` the path will overridden by the given parameters
905+
906+
### Example Config
907+
908+
```yaml
909+
910+
providers:
911+
cloudflare_workers_secrets:
912+
env_sync:
913+
source: script-id
914+
env:
915+
foo-secret:
916+
path: foo-secret
917+
source: script-id
918+
foo-secret2:
919+
path: foo-secret
920+
source: script-id
921+
```
922+
879923
## 1Password
880924
881925
### Authentication

pkg/providers.go

+19-16
Original file line numberDiff line numberDiff line change
@@ -17,22 +17,23 @@ type BuiltinProviders struct {
1717

1818
func (p *BuiltinProviders) ProviderHumanToMachine() map[string]string {
1919
return map[string]string{
20-
"Heroku": "heroku",
21-
"Vault by Hashicorp": "hashicorp_vault",
22-
"AWS SSM (aka paramstore)": "aws_ssm",
23-
"AWS Secrets Manager": "aws_secretsmanager",
24-
"Google Secret Manager": "google_secretmanager",
25-
"Etcd": "etcd",
26-
"Consul": "consul",
27-
".env": "dotenv",
28-
"Vercel": "vercel",
29-
"Azure Key Vault": "azure_keyvault",
30-
"Doppler": "doppler",
31-
"CyberArk Conjur": "cyberark_conjur",
32-
"Cloudlflare Workers KV": "cloudflare_workers_kv",
33-
"1Password": "1password",
34-
"Gopass": "gopass",
35-
"LastPass": "lastpass",
20+
"Heroku": "heroku",
21+
"Vault by Hashicorp": "hashicorp_vault",
22+
"AWS SSM (aka paramstore)": "aws_ssm",
23+
"AWS Secrets Manager": "aws_secretsmanager",
24+
"Google Secret Manager": "google_secretmanager",
25+
"Etcd": "etcd",
26+
"Consul": "consul",
27+
".env": "dotenv",
28+
"Vercel": "vercel",
29+
"Azure Key Vault": "azure_keyvault",
30+
"Doppler": "doppler",
31+
"CyberArk Conjur": "cyberark_conjur",
32+
"Cloudlflare Workers KV": "cloudflare_workers_kv",
33+
"Cloudlflare Workers Secrets": "cloudflare_workers_secrets",
34+
"1Password": "1password",
35+
"Gopass": "gopass",
36+
"LastPass": "lastpass",
3637
}
3738
}
3839

@@ -64,6 +65,8 @@ func (p *BuiltinProviders) GetProvider(name string) (core.Provider, error) {
6465
return providers.NewConjurClient()
6566
case "cloudflare_workers_kv":
6667
return providers.NewCloudflareClient()
68+
case "cloudflare_workers_secrets":
69+
return providers.NewCloudflareSecretsClient()
6770
case "1password":
6871
return providers.NewOnePassword()
6972
case "gopass":
+120
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
package providers
2+
3+
import (
4+
"context"
5+
"errors"
6+
"fmt"
7+
"os"
8+
9+
cloudflare "github.com/cloudflare/cloudflare-go"
10+
"github.com/spectralops/teller/pkg/core"
11+
)
12+
13+
var (
14+
ErrCloudFlareSourceFieldIsMissing = errors.New("`source` filed is missing")
15+
)
16+
17+
type CloudflareSecretsClient interface {
18+
SetWorkersSecret(ctx context.Context, script string, req *cloudflare.WorkersPutSecretRequest) (cloudflare.WorkersPutSecretResponse, error)
19+
DeleteWorkersSecret(ctx context.Context, script, secretName string) (cloudflare.Response, error)
20+
}
21+
22+
type CloudflareSecrets struct {
23+
client CloudflareSecretsClient
24+
}
25+
26+
func NewCloudflareSecretsClient() (core.Provider, error) {
27+
api, err := cloudflare.New(
28+
os.Getenv("CLOUDFLARE_API_KEY"),
29+
os.Getenv("CLOUDFLARE_API_EMAIL"),
30+
)
31+
32+
if err != nil {
33+
return nil, err
34+
}
35+
36+
cloudflare.UsingAccount(os.Getenv("CLOUDFLARE_ACCOUNT_ID"))(api) //nolint
37+
return &CloudflareSecrets{client: api}, nil
38+
}
39+
40+
func (c *CloudflareSecrets) Name() string {
41+
return "cloudflare_workers_secret"
42+
}
43+
44+
func (c *CloudflareSecrets) Put(p core.KeyPath, val string) error {
45+
46+
if p.Source == "" {
47+
return ErrCloudFlareSourceFieldIsMissing
48+
}
49+
50+
secretName, err := c.getSecretName(p)
51+
if err != nil {
52+
return err
53+
}
54+
55+
secretRequest := cloudflare.WorkersPutSecretRequest{
56+
Name: secretName,
57+
Text: val,
58+
Type: cloudflare.WorkerSecretTextBindingType,
59+
}
60+
61+
_, err = c.client.SetWorkersSecret(context.TODO(), p.Source, &secretRequest)
62+
63+
return err
64+
}
65+
66+
func (c *CloudflareSecrets) PutMapping(p core.KeyPath, m map[string]string) error {
67+
if p.Source == "" {
68+
return ErrCloudFlareSourceFieldIsMissing
69+
}
70+
71+
for k, v := range m {
72+
ap := p.WithEnv(fmt.Sprintf("%v/%v", p.Path, k))
73+
74+
err := c.Put(ap, v)
75+
if err != nil {
76+
return err
77+
}
78+
}
79+
return nil
80+
}
81+
82+
func (c *CloudflareSecrets) Delete(p core.KeyPath) error {
83+
84+
if p.Source == "" {
85+
return ErrCloudFlareSourceFieldIsMissing
86+
}
87+
88+
secretName, err := c.getSecretName(p)
89+
if err != nil {
90+
return err
91+
}
92+
93+
_, err = c.client.DeleteWorkersSecret(context.TODO(), p.Source, secretName)
94+
return err
95+
}
96+
97+
func (c *CloudflareSecrets) GetMapping(p core.KeyPath) ([]core.EnvEntry, error) {
98+
return nil, fmt.Errorf("%s does not support read functionality", c.Name())
99+
}
100+
101+
func (c *CloudflareSecrets) Get(p core.KeyPath) (*core.EnvEntry, error) {
102+
return nil, fmt.Errorf("%s does not support read functionality", c.Name())
103+
}
104+
105+
func (c *CloudflareSecrets) DeleteMapping(kp core.KeyPath) error {
106+
return fmt.Errorf("%s does not implement deleteMapping yet", c.Name())
107+
}
108+
109+
func (c *CloudflareSecrets) getSecretName(p core.KeyPath) (string, error) {
110+
111+
k := p.Field
112+
if k == "" {
113+
k = p.Env
114+
}
115+
if k == "" {
116+
return "", fmt.Errorf("key required for fetching secrets. Received \"\"")
117+
}
118+
return k, nil
119+
120+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
package providers
2+
3+
import (
4+
"testing"
5+
6+
"github.com/alecthomas/assert"
7+
cloudflare "github.com/cloudflare/cloudflare-go"
8+
"github.com/golang/mock/gomock"
9+
10+
"github.com/spectralops/teller/pkg/core"
11+
"github.com/spectralops/teller/pkg/providers/mock_providers"
12+
)
13+
14+
func TestCloudflareWorkersSecretsPut(t *testing.T) {
15+
ctrl := gomock.NewController(t)
16+
defer ctrl.Finish()
17+
client := mock_providers.NewMockCloudflareSecretsClient(ctrl)
18+
19+
expectedWorkerPutRequest := cloudflare.WorkersPutSecretRequest{
20+
Name: "MG_KEY",
21+
Text: "put-secret",
22+
Type: "secret_text",
23+
}
24+
client.EXPECT().SetWorkersSecret(gomock.Any(), "script-key", &expectedWorkerPutRequest).Return(cloudflare.WorkersPutSecretResponse{}, nil).AnyTimes()
25+
26+
c := CloudflareSecrets{
27+
client: client,
28+
}
29+
assert.Nil(t, c.Put(core.KeyPath{Field: "MG_KEY", Source: "script-key"}, "put-secret"))
30+
assert.Nil(t, c.Put(core.KeyPath{Env: "MG_KEY", Source: "script-key"}, "put-secret"))
31+
assert.NotNil(t, c.Put(core.KeyPath{Path: "script-key"}, "put-secret"))
32+
assert.EqualError(t, c.Delete(core.KeyPath{Field: "MG_KEY"}), ErrCloudFlareSourceFieldIsMissing.Error())
33+
}
34+
35+
func TestCloudflareWorkersSecretsDelete(t *testing.T) {
36+
ctrl := gomock.NewController(t)
37+
defer ctrl.Finish()
38+
client := mock_providers.NewMockCloudflareSecretsClient(ctrl)
39+
40+
client.EXPECT().DeleteWorkersSecret(gomock.Any(), "script-key", "MG_KEY").Return(cloudflare.Response{}, nil).AnyTimes()
41+
42+
c := CloudflareSecrets{
43+
client: client,
44+
}
45+
46+
assert.Nil(t, c.Delete(core.KeyPath{Field: "MG_KEY", Source: "script-key"}))
47+
assert.EqualError(t, c.Delete(core.KeyPath{Field: "MG_KEY"}), ErrCloudFlareSourceFieldIsMissing.Error())
48+
assert.NotNil(t, c.Delete(core.KeyPath{Path: "script-key"}))
49+
50+
}

pkg/providers/mock_providers/cloudflare_workers_secrets_mock.go

+66
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pkg/wizard_template.go

+17
Original file line numberDiff line numberDiff line change
@@ -199,4 +199,21 @@ providers:
199199
path: # Lastpass item ID
200200
# field: by default taking password property. in case you want other property un-mark this line and set the lastpass property name.
201201
{{end}}
202+
203+
{{- if index .ProviderKeys "cloudflare_workers_secrets" }}
204+
205+
# Configure via environment variables for integration:
206+
# CLOUDFLARE_API_KEY: Your Cloudflare api key.
207+
# CLOUDFLARE_API_EMAIL: Your email associated with the api key.
208+
# CLOUDFLARE_ACCOUNT_ID: Your account ID.
209+
210+
cloudflare_workers_secrets:
211+
env_sync:
212+
source: # Mandatory: script field
213+
env:
214+
script-value:
215+
path: foo-secret
216+
source: # Mandatory: script field
217+
{{end}}
218+
202219
`

0 commit comments

Comments
 (0)